diff --git a/src/oidcserver.js b/src/oidcserver.js index 1263d46cd..a75ad581c 100644 --- a/src/oidcserver.js +++ b/src/oidcserver.js @@ -304,86 +304,80 @@ class StorageAdapter { } } +async function renderError(error) { + const data = { + ICON_URL: '/api/v1/cloudron/avatar', + NAME: 'Cloudron', + ERROR_MESSAGE: error.error_description || error.error_detail || error.message || 'Internal error', + FOOTER: marked.parse(await branding.renderFooter()) + }; + + debug('renderError: %o', error); + let html = fs.readFileSync(path.join(__dirname, '/../dashboard/dist/oidc_error.html'), 'utf8'); + for (const key in data) { + html = html.replaceAll(`##${key}##`, data[key]); + } + return html; +} + async function renderInteractionPage(req, res) { - try { - const { uid, prompt, params, session } = await gOidcProvider.interactionDetails(req, res); + const { uid, prompt, params, session } = await gOidcProvider.interactionDetails(req, res); - const client = await oidcClients.get(params.client_id); + const client = await oidcClients.get(params.client_id); + if (!client) return res.send(await renderError(new Error('Client not found'))); - let app = null; - if (client.appId) app = await apps.get(client.appId); + const app = client.appId ? await apps.get(client.appId) : null; + if (client.appId && !app) return res.send(await renderError(new Error('App not found'))); - switch (prompt.name) { - case 'login': { - const options = { - SUBMIT_URL: `${ROUTE_PREFIX}/interaction/${uid}/login`, - ICON_URL: '/api/v1/cloudron/avatar', - NAME: client?.name || await branding.getCloudronName(), - FOOTER: marked.parse(await branding.renderFooter()), - NOTE: (client.id === oidcClients.ID_WEBADMIN && constants.DEMO) ? '
This is a demo. Username and password is "cloudron"
' : '' - }; + res.set('Content-Type', 'text/html'); - if (app) { - options.NAME = app.label || app.fqdn; - options.ICON_URL = app.iconUrl; - } - - // great ejs replacement! - let html = fs.readFileSync(__dirname + '/../dashboard/dist/login.html', 'utf-8'); - Object.keys(options).forEach(key => { - html = html.replaceAll(`##${key}##`, options[key]); - }); - - return res.send(html); - } - case 'consent': { - let hasAccess = false; - - const data = { - ICON_URL: '/api/v1/cloudron/avatar', - NAME: client?.name || '', - FOOTER: marked.parse(await branding.renderFooter()) - }; - - // check if user has access to the app if client refers to an app - if (app) { - const user = await users.get(session.accountId); - - data.NAME = app.label || app.fqdn; - data.ICON_URL = app.iconUrl; - hasAccess = apps.canAccess(app, user); - } else { - hasAccess = true; - } - - data.SUBMIT_URL = `${ROUTE_PREFIX}/interaction/${uid}/${hasAccess ? 'confirm' : 'abort'}`; - - let html = fs.readFileSync(path.join(__dirname, hasAccess ? '/../dashboard/dist/oidc_interaction_confirm.html' : '/../dashboard/dist/oidc_interaction_abort.html'), 'utf8'); - Object.keys(data).forEach(key => { - html = html.replaceAll(`##${key}##`, data[key]); - }); - - return res.send(html); - } - default: - return undefined; - } - } catch (error) { - debug('route interaction get error', error); - - const data = { + if (prompt.name === 'login') { + const options = { + SUBMIT_URL: `${ROUTE_PREFIX}/interaction/${uid}/login`, ICON_URL: '/api/v1/cloudron/avatar', - NAME: 'Cloudron', - ERROR_MESSAGE: error.error_description || 'Internal error', + NAME: client.name || await branding.getCloudronName(), + FOOTER: marked.parse(await branding.renderFooter()), + NOTE: (client.id === oidcClients.ID_WEBADMIN && constants.DEMO) ? '
This is a demo. Username and password is "cloudron"
' : '' + }; + + if (app) { + options.NAME = app.label || app.fqdn; + options.ICON_URL = app.iconUrl; + } + + let html = fs.readFileSync(__dirname + '/../dashboard/dist/login.html', 'utf-8'); + for (const key in options) { + html = html.replaceAll(`##${key}##`, options[key]); + } + + return res.send(html); + } else if (prompt.name === 'consent') { + let hasAccess = false; + + const options = { + ICON_URL: '/api/v1/cloudron/avatar', + NAME: client.name || '', FOOTER: marked.parse(await branding.renderFooter()) }; - let html = fs.readFileSync(path.join(__dirname, '/../dashboard/dist/oidc_error.html'), 'utf8'); - Object.keys(data).forEach(key => { - html = html.replaceAll(`##${key}##`, data[key]); - }); + // check if user has access to the app if client refers to an app + if (app) { + const user = await users.get(session.accountId); + + options.NAME = app.label || app.fqdn; + options.ICON_URL = app.iconUrl; + hasAccess = apps.canAccess(app, user); + } else { + hasAccess = true; + } + + options.SUBMIT_URL = `${ROUTE_PREFIX}/interaction/${uid}/${hasAccess ? 'confirm' : 'abort'}`; + + let html = fs.readFileSync(path.join(__dirname, hasAccess ? '/../dashboard/dist/oidc_interaction_confirm.html' : '/../dashboard/dist/oidc_interaction_abort.html'), 'utf8'); + for (const key in options) { + html = html.replaceAll(`##${key}##`, options[key]); + } - res.set('Content-Type', 'text/html'); return res.send(html); } } @@ -401,8 +395,7 @@ async function interactionLogin(req, res, next) { debug(`interactionLogin: for OpenID client ${clientId} from ${ip}`); - // This is the auto login via token hack - if (req.body.autoLoginToken) { + if (req.body.autoLoginToken) { // auto login for first admin/owner if (typeof req.body.autoLoginToken !== 'string') return next(new HttpError(400, 'autoLoginToken must be string if provided')); const token = await tokens.getByAccessToken(req.body.autoLoginToken); @@ -575,24 +568,6 @@ async function getClaims(userId/*, use, scope*/) { return claims; } -async function renderError(ctx, out, error) { - const data = { - ICON_URL: '/api/v1/cloudron/avatar', - NAME: 'Cloudron', - ERROR_MESSAGE: error.error_description || error.error_detail || 'Unknown error', - FOOTER: marked.parse(await branding.renderFooter()) - }; - - debug('renderError: %o', error); - let html = fs.readFileSync(path.join(__dirname, '/../dashboard/dist/oidc_error.html'), 'utf8'); - Object.keys(data).forEach(key => { - html = html.replaceAll(`##${key}##`, data[key]); - }); - - ctx.type = 'html'; - ctx.body = html; -} - async function start() { assert(gHttpServer === null, 'OIDC server already started'); assert(gOidcProvider === null, 'OIDC provider already started'); @@ -635,16 +610,19 @@ async function start() { } const configuration = { - findAccount: async function(ctx, id) { + findAccount: async function (ctx, id) { return { accountId: id, claims: async (use, scope) => await getClaims(id, use, scope) }; }, - renderError, + renderError: async function (ctx, out, error) { + ctx.type = 'html'; + ctx.body = await renderError(error); + }, adapter: StorageAdapter, interactions: { - url: async function(ctx, interaction) { + url: async function (ctx, interaction) { return `${ROUTE_PREFIX}/interaction/${interaction.uid}`; } },