diff --git a/docs/resource-specific-documentation.md b/docs/resource-specific-documentation.md index d4d71772..035e6416 100644 --- a/docs/resource-specific-documentation.md +++ b/docs/resource-specific-documentation.md @@ -12,6 +12,8 @@ The Deploy CLI's own client grant is intentionally not exported nor configurable Multilingual custom text prompts follow a particular hierarchy. Under the root-level `prompts` resource property is a proprietary `customText` property that is used to bundle custom text translations with other prompts settings. Underneath `customText` is the two-character language code. Thirdly is the prompt ID, followed by the screen ID, followed by text ID. +RenderSettings of a prompt-screen follow a particular hierarchy. Under the root-level `prompts` we store `screenRenderers` property that is used to configure the rendering settings of a given prompt & screen. Thirdly is the prompt Name, followed by the screen Name mapped to the respective renderer configs file. Refer [more](https://auth0.com/docs/customize/login-pages/advanced-customizations/getting-started/configure-acul-screens) on this. + **Hierarchy** ```yaml @@ -21,11 +23,29 @@ prompts: : # prompt ID : # prompt screen ID : 'Some text' + screenRenderers: + - : + : ./prompts/screenRenderSettings/promptName_screenName.json #Add the renderer configs for a given prompt & a given screen ``` -**Example** +**YAML Example** + +``` +Folder structure when in YAML mode. + +./prompts/ + /screenRenderSettings + /signup-id_signup-id.json + /login-id_login-id.json + /login-passwordless_login-passwordless-email-code.json + /login-passwordless_login-passwordless-sms-otp.json + /login-password_login-password.json + /signup-password_signup-password.json +./tenant.yaml +``` ```yaml +# Contents of ./tenant.yaml prompts: identifier_first: true universal_login_experience: classic @@ -43,7 +63,60 @@ prompts: mfa-login-options: pageTitle: 'Log in to ${clientName}' authenticatorNamesSMS: 'SMS' + screenRenderers: + - signup-id: + signup-id: ./prompts/screenRenderSettings/signup-id_signup-id.json + - login-passwordless: + login-passwordless-email-code: ./prompts/screenRenderSettings/login-passwordless_login-passwordless-email-code.json + login-passwordless-sms-otp: ./prompts/screenRenderSettings/login-passwordless_login-passwordless-sms-otp.json +``` + +**Directory Example** + ``` +Folder structure when in directory mode. + +./prompts/ + /screenRenderSettings + /signup-id_signup-id.json + /login-id_login-id.json + /login-passwordless_login-passwordless-email-code.json + /login-passwordless_login-passwordless-sms-otp.json + /login-password_login-password.json + /signup-password_signup-password.json + /custom-text.json + /prompts.json +``` + +Contents of `promptName_screenName.json` + +```json +{ + "prompt": "signup-id", + "screen": "signup-id", + "rendering_mode": "advanced", + "context_configuration": [ + "branding.settings", + "branding.themes.default" + ], + "default_head_tags_disabled": false, + "head_tags": [ + { + "tag": "script", + "attributes": { + "src": "URL_TO_YOUR_ASSET", + "async": true, + "defer": true, + "integrity": [ + "ASSET_SHA" + ] + } + } + ] +} +``` + + ## Databases diff --git a/examples/directory/prompts/custom-text.json b/examples/directory/prompts/custom-text.json new file mode 100644 index 00000000..b551f954 --- /dev/null +++ b/examples/directory/prompts/custom-text.json @@ -0,0 +1,9 @@ +{ + "en": { + "login-id": { + "login-id": { + "invalid-email-format": "Email is not valid." + } + } + } +} diff --git a/examples/directory/prompts/prompts.json b/examples/directory/prompts/prompts.json new file mode 100644 index 00000000..012781ff --- /dev/null +++ b/examples/directory/prompts/prompts.json @@ -0,0 +1,6 @@ +{ + "universal_login_experience": "new", + "identifier_first": false, + "webauthn_platform_first_factor": true, + "enable_ulp_wcag_compliance": false +} diff --git a/examples/directory/prompts/screenRenderSettings/login-id_login-id.json b/examples/directory/prompts/screenRenderSettings/login-id_login-id.json new file mode 100644 index 00000000..c0ce9caa --- /dev/null +++ b/examples/directory/prompts/screenRenderSettings/login-id_login-id.json @@ -0,0 +1,33 @@ +{ + "prompt": "login-id", + "screen": "login-id", + "rendering_mode": "advanced", + "context_configuration": [ + "branding.settings", + "branding.themes.default" + ], + "default_head_tags_disabled": true, + "head_tags": [ + { + "tag": "script", + "attributes": { + "src": "http://127.0.0.1:8090/index.js", + "defer": true + } + }, + { + "tag": "link", + "attributes": { + "rel": "stylesheet", + "href": "http://127.0.0.1:8090/index.css" + } + }, + { + "tag": "meta", + "attributes": { + "name": "viewport", + "content": "width=device-width, initial-scale=1" + } + } + ] +} diff --git a/examples/directory/prompts/screenRenderSettings/signup-id_signup-id.json b/examples/directory/prompts/screenRenderSettings/signup-id_signup-id.json new file mode 100644 index 00000000..5cacd41b --- /dev/null +++ b/examples/directory/prompts/screenRenderSettings/signup-id_signup-id.json @@ -0,0 +1,20 @@ +{ + "prompt": "signup-id", + "screen": "signup-id", + "rendering_mode": "advanced", + "context_configuration": [], + "default_head_tags_disabled": false, + "head_tags": [ + { + "tag": "script", + "attributes": { + "src": "URL_TO_YOUR_ASSET", + "async": true, + "defer": true, + "integrity": [ + "ASSET_SHA" + ] + } + } + ] +} diff --git a/examples/yaml/prompts/screenRenderSettings/login-id_login-id.json b/examples/yaml/prompts/screenRenderSettings/login-id_login-id.json new file mode 100644 index 00000000..c0ce9caa --- /dev/null +++ b/examples/yaml/prompts/screenRenderSettings/login-id_login-id.json @@ -0,0 +1,33 @@ +{ + "prompt": "login-id", + "screen": "login-id", + "rendering_mode": "advanced", + "context_configuration": [ + "branding.settings", + "branding.themes.default" + ], + "default_head_tags_disabled": true, + "head_tags": [ + { + "tag": "script", + "attributes": { + "src": "http://127.0.0.1:8090/index.js", + "defer": true + } + }, + { + "tag": "link", + "attributes": { + "rel": "stylesheet", + "href": "http://127.0.0.1:8090/index.css" + } + }, + { + "tag": "meta", + "attributes": { + "name": "viewport", + "content": "width=device-width, initial-scale=1" + } + } + ] +} diff --git a/examples/yaml/prompts/screenRenderSettings/signup-id_signup-id.json b/examples/yaml/prompts/screenRenderSettings/signup-id_signup-id.json new file mode 100644 index 00000000..5cacd41b --- /dev/null +++ b/examples/yaml/prompts/screenRenderSettings/signup-id_signup-id.json @@ -0,0 +1,20 @@ +{ + "prompt": "signup-id", + "screen": "signup-id", + "rendering_mode": "advanced", + "context_configuration": [], + "default_head_tags_disabled": false, + "head_tags": [ + { + "tag": "script", + "attributes": { + "src": "URL_TO_YOUR_ASSET", + "async": true, + "defer": true, + "integrity": [ + "ASSET_SHA" + ] + } + } + ] +} diff --git a/examples/yaml/tenant.yaml b/examples/yaml/tenant.yaml index 3994f60a..3dab98b0 100644 --- a/examples/yaml/tenant.yaml +++ b/examples/yaml/tenant.yaml @@ -184,3 +184,19 @@ triggers: post-user-registration: [] pre-user-registration: [] send-phone-message: [] + +prompts: + customText: + en: + login-id: + login-id: + invalid-email-format: Email is not valid. + enable_ulp_wcag_compliance: false + identifier_first: false + partials: {} + screenRenderers: + - signup-id: + signup-id: ./prompts/screenRenderSettings/signup-id_signup-id.json + - login-passwordless: + login-passwordless-email-code: ./prompts/screenRenderSettings/login-passwordless_login-passwordless-email-code.json + login-passwordless-sms-otp: ./prompts/screenRenderSettings/login-passwordless_login-passwordless-sms-otp.json diff --git a/src/tools/auth0/handlers/prompts.ts b/src/tools/auth0/handlers/prompts.ts index 5aebd76b..7ce85c44 100644 --- a/src/tools/auth0/handlers/prompts.ts +++ b/src/tools/auth0/handlers/prompts.ts @@ -608,6 +608,7 @@ export default class PromptsHandler extends DefaultHandler { } else { updatePayload = { ...updatePrams, + rendering_mode }; } diff --git a/test/context/directory/prompts.test.ts b/test/context/directory/prompts.test.ts index f99c4867..f6018a49 100644 --- a/test/context/directory/prompts.test.ts +++ b/test/context/directory/prompts.test.ts @@ -15,10 +15,13 @@ import { const dir = path.join(testDataDir, 'directory', 'promptsDump'); const promptsDirectory = path.join(dir, constants.PROMPTS_DIRECTORY); +const promptsScreenSettingsDirectory = path.join(promptsDirectory, constants.PROMPTS_SCREEN_RENDER_DIRECTORY); const promptsSettingsFile = 'prompts.json'; const customTextFile = 'custom-text.json'; const partialsFile = 'partials.json'; +const signupIdSettingsFile = 'signup-id_signup-id.json'; +const loginIdSettingsFile = 'login-id_login-id.json'; describe('#directory context prompts', () => { it('should parse prompts', async () => { @@ -77,9 +80,67 @@ describe('#directory context prompts', () => { }, }; + const settingsfiles = { + [constants.PROMPTS_SCREEN_RENDER_DIRECTORY]: { + [signupIdSettingsFile]: JSON.stringify({ + 'prompt': 'signup-id', + 'screen': 'signup-id', + 'rendering_mode': 'standard', + 'context_configuration': [], + 'default_head_tags_disabled': false, + 'head_tags': [ + { + 'tag': 'script', + 'attributes': { + 'src': 'URL_TO_YOUR_ASSET', + 'async': true, + 'defer': true, + 'integrity': [ + 'ASSET_SHA' + ] + } + } + ] + }), + [loginIdSettingsFile]: JSON.stringify({ + 'prompt': 'login-id', + 'screen': 'login-id', + 'rendering_mode': 'advanced', + 'context_configuration': [], + 'default_head_tags_disabled': false, + 'head_tags': [ + { + 'tag': 'script', + 'attributes': { + 'src': 'http://127.0.0.1:8080/index.js', + 'defer': true + } + }, + { + 'tag': 'link', + 'attributes': { + 'rel': 'stylesheet', + 'href': 'http://127.0.0.1:8090/index.css' + } + }, + { + 'tag': 'meta', + 'attributes': { + 'name': 'viewport', + 'content': 'width=device-width, initial-scale=1' + } + } + ] + }), + } + }; + const repoDir = path.join(testDataDir, 'directory', 'prompts'); createDir(repoDir, files); + const settingsDir = path.join(repoDir,constants.PROMPTS_DIRECTORY, constants.PROMPTS_SCREEN_RENDER_DIRECTORY); + createDir(settingsDir, settingsfiles); + const partialsDir = path.join( repoDir, constants.PROMPTS_DIRECTORY, @@ -101,6 +162,21 @@ describe('#directory context prompts', () => { createDirWithNestedDir(partialsDir, partialsFiles); + const screenSettingsDir = path.join( + repoDir, + constants.PROMPTS_DIRECTORY, + constants.PROMPTS_SCREEN_RENDER_DIRECTORY + ); + + const signupIdSettingsFiles = { + 'signup-id_signup-id.json': '{ "prompt": "signup-id", "screen": "signup-id", "rendering_mode": "advanced","default_head_tags_disabled": false,' + + '"context_configuration": [ "branding.settings", "branding.themes.default"], "head_tags": [{"attributes": {"async": true, "defer": true , ' + + '"integrity": ["sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g=="], "src": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"}, "tag": "script"}]}', + 'login-id_login-id.json': '{ "prompt": "login-id", "screen": "login-id", "rendering_mode": "standard", "context_configuration": [],"default_head_tags_disabled": false}', + }; + + createDir(repoDir, { [screenSettingsDir]:signupIdSettingsFiles }); + const config = { AUTH0_INPUT_FILE: repoDir, AUTH0_KEYWORD_REPLACE_MAPPINGS: { @@ -149,7 +225,35 @@ describe('#directory context prompts', () => { }, }, }, - screenRenderers: [], + screenRenderers: [ + { + 'prompt': 'login-id', + 'screen': 'login-id', + 'rendering_mode': 'standard', + 'context_configuration': [], + 'default_head_tags_disabled': false, + }, + { + 'prompt': 'signup-id', + 'screen': 'signup-id', + 'rendering_mode': 'advanced', + 'context_configuration': ['branding.settings','branding.themes.default'], + 'default_head_tags_disabled': false, + 'head_tags': [ + { + 'tag': 'script', + 'attributes': { + 'src': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js', + 'async': true, + 'defer': true, + 'integrity': [ + 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==' + ] + } + } + ] + } + ], }); }); @@ -360,7 +464,7 @@ describe('#directory context prompts', () => { expect(dumpedFiles).to.have.length(0); }); - it('should dump prompts settings and prompts custom text when API responses are empty', async () => { + it('should dump prompts settings, prompts custom text when API responses are empty and screen renderers', async () => { cleanThenMkdir(dir); const context = new Context({ AUTH0_INPUT_FILE: dir }); @@ -398,11 +502,41 @@ describe('#directory context prompts', () => { }, }, }, + screenRenderers: [ + { + 'prompt': 'login-id', + 'screen': 'login-id', + 'rendering_mode': 'standard', + 'context_configuration': [], + 'default_head_tags_disabled': false, + }, + { + 'prompt': 'signup-id', + 'screen': 'signup-id', + 'rendering_mode': 'advanced', + 'context_configuration': ['branding.settings','branding.themes.default'], + 'default_head_tags_disabled': false, + 'head_tags': [ + { + 'tag': 'script', + 'attributes': { + 'src': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js', + 'async': true, + 'defer': true, + 'integrity': [ + 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==' + ] + } + } + ] + } + ], }; await promptsHandler.dump(context); const dumpedFiles = getFiles(promptsDirectory, ['.json']); + const dumpedScreenSettingsFiles = getFiles(promptsScreenSettingsDirectory, ['.json']); expect(dumpedFiles).to.deep.equal([ path.join(promptsDirectory, customTextFile), @@ -410,6 +544,11 @@ describe('#directory context prompts', () => { path.join(promptsDirectory, promptsSettingsFile), ]); + expect(dumpedScreenSettingsFiles).to.deep.equal([ + path.join(promptsScreenSettingsDirectory, loginIdSettingsFile), + path.join(promptsScreenSettingsDirectory, signupIdSettingsFile), + ]); + expect(loadJSON(path.join(promptsDirectory, customTextFile), {})).to.deep.equal( context.assets.prompts.customText ); @@ -439,5 +578,34 @@ describe('#directory context prompts', () => { universal_login_experience: context.assets.prompts.universal_login_experience, identifier_first: context.assets.prompts.identifier_first, }); + + expect(loadJSON(path.join(promptsScreenSettingsDirectory, loginIdSettingsFile), {})).to.deep.equal({ + 'prompt': 'login-id', + 'screen': 'login-id', + 'rendering_mode': 'standard', + 'context_configuration': [], + 'default_head_tags_disabled': false, + }); + + expect(loadJSON(path.join(promptsScreenSettingsDirectory, signupIdSettingsFile), {})).to.deep.equal({ + 'prompt': 'signup-id', + 'screen': 'signup-id', + 'rendering_mode': 'advanced', + 'context_configuration': ['branding.settings','branding.themes.default'], + 'default_head_tags_disabled': false, + 'head_tags': [ + { + 'tag': 'script', + 'attributes': { + 'src': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js', + 'async': true, + 'defer': true, + 'integrity': [ + 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==' + ] + } + } + ] + }); }); }); diff --git a/test/context/yaml/prompts.test.ts b/test/context/yaml/prompts.test.ts index 82775a50..1bede202 100644 --- a/test/context/yaml/prompts.test.ts +++ b/test/context/yaml/prompts.test.ts @@ -253,7 +253,7 @@ describe('#YAML context prompts', () => { }); it('should dump prompts settings, prompts custom text and screen renderers', async () => { - const dir = path.join(testDataDir, 'yaml', 'prompts'); + const dir = path.join(testDataDir, 'yaml'); cleanThenMkdir(dir); const context = new Context( { AUTH0_INPUT_FILE: path.join(dir, './test.yml') },