diff --git a/dashboard/oidc_device_confirm.html b/dashboard/oidc_device_confirm.html new file mode 100644 index 000000000..093699b42 --- /dev/null +++ b/dashboard/oidc_device_confirm.html @@ -0,0 +1,37 @@ + + + + + Confirm Device + + + + +
+

Confirm Device

+

+ <%= clientName %> +

+ The following code should be displayed on your device

+ <%= userCode %> +

+ If you did not initiate this action or the code does not match, please close this window or click abort. +

+ <%- form %> + +
+ +
+
+ + diff --git a/dashboard/oidc_device_input.html b/dashboard/oidc_device_input.html new file mode 100644 index 000000000..83f4ba0c8 --- /dev/null +++ b/dashboard/oidc_device_input.html @@ -0,0 +1,27 @@ + + + + + Sign-in + + + + +
+

Sign-in

+ <%- message %> + <%- form %> + +
+ + diff --git a/dashboard/oidc_device_success.html b/dashboard/oidc_device_success.html new file mode 100644 index 000000000..a089734a6 --- /dev/null +++ b/dashboard/oidc_device_success.html @@ -0,0 +1,20 @@ + + + + + Success + + + + +
+

Success

+

Your device has been authorized. You can close this window.

+
+ + diff --git a/dashboard/vite.config.mjs b/dashboard/vite.config.mjs index 6ec4d2d54..03396b2c2 100644 --- a/dashboard/vite.config.mjs +++ b/dashboard/vite.config.mjs @@ -14,6 +14,9 @@ export default defineConfig({ resolve('oidc_error.html'), resolve('oidc_interaction_confirm.html'), resolve('oidc_interaction_abort.html'), + resolve('oidc_device_input.html'), + resolve('oidc_device_confirm.html'), + resolve('oidc_device_success.html'), resolve('logs.html'), resolve('passwordreset.html'), resolve('restore.html'), @@ -43,6 +46,9 @@ export default defineConfig({ oidc_error: resolve('oidc_error.html'), oidc_interaction_confirm: resolve('oidc_interaction_confirm.html'), oidc_interaction_abort: resolve('oidc_interaction_abort.html'), + oidc_device_input: resolve('oidc_device_input.html'), + oidc_device_confirm: resolve('oidc_device_confirm.html'), + oidc_device_success: resolve('oidc_device_success.html'), logs: resolve('logs.html'), notfound: resolve('notfound.html'), passwordreset: resolve('passwordreset.html'), diff --git a/src/oidcserver.js b/src/oidcserver.js index 8546574b1..8cdc385e8 100644 --- a/src/oidcserver.js +++ b/src/oidcserver.js @@ -274,6 +274,9 @@ async function cleanupExpired() { } } +const TEMPLATE_DEVICE_INPUT = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'oidc_device_input.html'), 'utf-8'); +const TEMPLATE_DEVICE_CONFIRM = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'oidc_device_confirm.html'), 'utf-8'); +const TEMPLATE_DEVICE_SUCCESS = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'oidc_device_success.html'), 'utf-8'); const TEMPLATE_LOGIN = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'oidc_login.html'), 'utf-8'); const TEMPLATE_INTERACTION_CONFIRM = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'oidc_interaction_confirm.html'), 'utf8'); const TEMPLATE_INTERACTION_ABORT = fs.readFileSync(path.join(paths.DASHBOARD_DIR, 'oidc_interaction_abort.html'), 'utf8'); @@ -674,7 +677,30 @@ async function start() { } }, devInteractions: { enabled: false }, - deviceFlow: { enabled: true, charset: 'base-20', mask: '****-****' }, + deviceFlow: { + enabled: true, + charset: 'base-20', + mask: '****-****', + userCodeInputSource: async function (ctx, form, out, err) { + let message; + if (err && (err.userCode || err.name === 'NoCodeError')) { + message = '

The code you entered is incorrect. Try again

'; + } else if (err && err.name === 'AbortedError') { + message = '

The sign-in request was interrupted

'; + } else if (err) { + message = '

There was an error processing your request

'; + } else { + message = '

Enter the code displayed on your device

'; + } + ctx.body = ejs.render(TEMPLATE_DEVICE_INPUT, { message, form }); + }, + userCodeConfirmSource: async function (ctx, form, client, deviceInfo, userCode) { + ctx.body = ejs.render(TEMPLATE_DEVICE_CONFIRM, { clientName: ctx.oidc.client.clientName || ctx.oidc.client.clientId, userCode, form }); + }, + successSource: async function (ctx) { + ctx.body = ejs.render(TEMPLATE_DEVICE_SUCCESS, {}); + }, + }, }, clientDefaults: { response_types: ['code', 'id_token'],