diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index 570e76ceb..6713f0069 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -56,6 +56,12 @@ export default class ApplicationSerializer extends RESTSerializer { json.attributes[key] = json[key]; delete json[key]; } + // Push nested secrets down into the secrets key + if (options.isNestedSecret && json[key]) { + if (!json.secrets) json.secrets = {}; + json.secrets[key] = json[key]; + delete json[key]; + } return value; } @@ -205,10 +211,7 @@ export default class ApplicationSerializer extends RESTSerializer { const scopeID = get(normalizedHash, 'scope.id'); if (scopeID) normalizedHash.scope.scope_id = scopeID; normalizedHash = this.normalizeNestedAttributes(typeClass, normalizedHash); - // Delete any lingering values on write-only fields - typeClass.attributes.forEach((attribute) => { - if (attribute.options.writeOnly) hash[attribute.name] = null; - }); + normalizedHash = this.normalizeNestedSecrets(typeClass, normalizedHash); return super.normalize(typeClass, normalizedHash); } @@ -228,7 +231,6 @@ export default class ApplicationSerializer extends RESTSerializer { * @return {Object} */ normalizeNestedAttributes(typeClass, hash) { - const normalizedHash = copy(hash, true); typeClass.attributes.forEach((attribute) => { const { isNestedAttribute } = attribute.options; const attributeValue = normalizedHash.attributes?.[attribute.name]; @@ -238,4 +240,27 @@ export default class ApplicationSerializer extends RESTSerializer { }); return normalizedHash; } + + /** + * If an attribute is declared with the `isNestedSecret` option, it will + * be explicitly unset in the incoming payload to `null`. This helps ensure + * secret values do not linger in the UI. + * + * For example: + * `@attr('string', { isNestedSecret: true }) superSecretShhh;` + * + * @param {Model} typeClass + * @param {Object} hash + * @return {Object} + */ + normalizeNestedSecrets(typeClass, hash) { + typeClass.attributes.forEach((attribute) => { + const { + name, + options: { isNestedSecret }, + } = attribute; + if (isNestedSecret) normalizedHash[name] = null; + }); + return normalizedHash; + } }