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'],