oidc: refactor

This commit is contained in:
Girish Ramakrishnan
2025-06-11 23:26:22 +02:00
parent 5baa08eb0c
commit d24189e8aa

View File

@@ -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) ? '<div style="text-align: center;">This is a demo. Username and password is "cloudron"</div>' : ''
};
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) ? '<div style="text-align: center;">This is a demo. Username and password is "cloudron"</div>' : ''
};
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}`;
}
},