2023-03-08 16:41:59 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
|
|
|
|
getProvider,
|
2023-03-10 17:13:33 +01:00
|
|
|
attachInteractionRoutes
|
2023-03-08 16:41:59 +01:00
|
|
|
};
|
|
|
|
|
|
2023-03-10 17:13:33 +01:00
|
|
|
const assert = require('assert'),
|
|
|
|
|
debug = require('debug')('box:oidc'),
|
2023-03-08 16:41:59 +01:00
|
|
|
fs = require('fs'),
|
|
|
|
|
path = require('path'),
|
|
|
|
|
paths = require('./paths.js'),
|
2023-03-13 17:01:52 +01:00
|
|
|
BoxError = require('./boxerror.js'),
|
|
|
|
|
HttpError = require('connect-lastmile').HttpError,
|
2023-03-13 19:08:41 +01:00
|
|
|
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
2023-03-13 17:01:52 +01:00
|
|
|
users = require('./users.js'),
|
|
|
|
|
safe = require('safetydance'),
|
2023-03-08 16:41:59 +01:00
|
|
|
settings = require('./settings.js');
|
|
|
|
|
|
|
|
|
|
class CloudronAdapter {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Creates an instance of MyAdapter for an oidc-provider model.
|
|
|
|
|
*
|
|
|
|
|
* @constructor
|
|
|
|
|
* @param {string} name Name of the oidc-provider model. One of "Grant, "Session", "AccessToken",
|
|
|
|
|
* "AuthorizationCode", "RefreshToken", "ClientCredentials", "Client", "InitialAccessToken",
|
|
|
|
|
* "RegistrationAccessToken", "DeviceCode", "Interaction", "ReplayDetection",
|
|
|
|
|
* "BackchannelAuthenticationRequest", or "PushedAuthorizationRequest"
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
constructor(name) {
|
|
|
|
|
this.name = name;
|
|
|
|
|
this.fileStorePath = path.join(paths.PLATFORM_DATA_DIR, `oidc-${name}.json`);
|
|
|
|
|
|
|
|
|
|
debug(`Creating adapter for ${name} backed by ${this.fileStorePath}`);
|
|
|
|
|
|
|
|
|
|
let data = {};
|
|
|
|
|
try {
|
|
|
|
|
data = JSON.parse(fs.readFileSync(this.fileStorePath), 'utf8');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
debug(`filestore for adapter ${name} not found, start with new one`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.store = data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Update or Create an instance of an oidc-provider model.
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} Promise fulfilled when the operation succeeded. Rejected with error when
|
|
|
|
|
* encountered.
|
|
|
|
|
* @param {string} id Identifier that oidc-provider will use to reference this model instance for
|
|
|
|
|
* future operations.
|
|
|
|
|
* @param {object} payload Object with all properties intended for storage.
|
|
|
|
|
* @param {integer} expiresIn Number of seconds intended for this model to be stored.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
async upsert(id, payload, expiresIn) {
|
|
|
|
|
debug(`[${this.name}] upsert id:${id} expiresIn:${expiresIn}`, payload);
|
|
|
|
|
|
2023-03-09 18:59:04 +01:00
|
|
|
this.store[id] = { id, expiresIn, payload, consumed: false };
|
2023-03-08 16:41:59 +01:00
|
|
|
fs.writeFileSync(this.fileStorePath, JSON.stringify(this.store), 'utf8');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Return previously stored instance of an oidc-provider model.
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} Promise fulfilled with what was previously stored for the id (when found and
|
|
|
|
|
* not dropped yet due to expiration) or falsy value when not found anymore. Rejected with error
|
|
|
|
|
* when encountered.
|
|
|
|
|
* @param {string} id Identifier of oidc-provider model
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
async find(id) {
|
|
|
|
|
debug(`[${this.name}] find id:${id}`);
|
|
|
|
|
|
|
|
|
|
if (!this.store[id]) return false;
|
|
|
|
|
|
2023-03-09 18:59:04 +01:00
|
|
|
return this.store[id].payload;
|
2023-03-08 16:41:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Return previously stored instance of DeviceCode by the end-user entered user code. You only
|
|
|
|
|
* need this method for the deviceFlow feature
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} Promise fulfilled with the stored device code object (when found and not
|
|
|
|
|
* dropped yet due to expiration) or falsy value when not found anymore. Rejected with error
|
|
|
|
|
* when encountered.
|
|
|
|
|
* @param {string} userCode the user_code value associated with a DeviceCode instance
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
async findByUserCode(userCode) {
|
|
|
|
|
debug(`[${this.name}] FIXME findByUserCode userCode:${userCode}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Return previously stored instance of Session by its uid reference property.
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} Promise fulfilled with the stored session object (when found and not
|
|
|
|
|
* dropped yet due to expiration) or falsy value when not found anymore. Rejected with error
|
|
|
|
|
* when encountered.
|
|
|
|
|
* @param {string} uid the uid value associated with a Session instance
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
async findByUid(uid) {
|
|
|
|
|
debug(`[${this.name}] findByUid uid:${uid}`);
|
|
|
|
|
|
|
|
|
|
for (let d in this.store) {
|
2023-03-09 19:16:36 +01:00
|
|
|
if (this.store[d].payload.uid === uid) return this.store[d].payload;
|
2023-03-08 16:41:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Mark a stored oidc-provider model as consumed (not yet expired though!). Future finds for this
|
|
|
|
|
* id should be fulfilled with an object containing additional property named "consumed" with a
|
|
|
|
|
* truthy value (timestamp, date, boolean, etc).
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} Promise fulfilled when the operation succeeded. Rejected with error when
|
|
|
|
|
* encountered.
|
|
|
|
|
* @param {string} id Identifier of oidc-provider model
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
async consume(id) {
|
2023-03-09 18:59:04 +01:00
|
|
|
debug(`[${this.name}] consume id:${id}`);
|
|
|
|
|
|
|
|
|
|
if (this.store[id]) this.store[id].consumed = true;
|
2023-03-08 16:41:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Destroy/Drop/Remove a stored oidc-provider model. Future finds for this id should be fulfilled
|
|
|
|
|
* with falsy values.
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} Promise fulfilled when the operation succeeded. Rejected with error when
|
|
|
|
|
* encountered.
|
|
|
|
|
* @param {string} id Identifier of oidc-provider model
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
async destroy(id) {
|
|
|
|
|
debug(`[${this.name}] destroy id:${id}`);
|
|
|
|
|
|
|
|
|
|
delete this.store[id];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Destroy/Drop/Remove a stored oidc-provider model by its grantId property reference. Future
|
|
|
|
|
* finds for all tokens having this grantId value should be fulfilled with falsy values.
|
|
|
|
|
*
|
|
|
|
|
* @return {Promise} Promise fulfilled when the operation succeeded. Rejected with error when
|
|
|
|
|
* encountered.
|
|
|
|
|
* @param {string} grantId the grantId value associated with a this model's instance
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
async revokeByGrantId(grantId) {
|
|
|
|
|
debug(`[${this.name}] revokeByGrantId grantId:${grantId}`);
|
|
|
|
|
|
|
|
|
|
for (let d in this.store) {
|
|
|
|
|
if (this.store[d].grantId === grantId) {
|
|
|
|
|
delete this.store[d];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 17:13:33 +01:00
|
|
|
const store = new Map();
|
|
|
|
|
const logins = new Map();
|
|
|
|
|
|
|
|
|
|
class Account {
|
|
|
|
|
constructor(id, profile) {
|
|
|
|
|
this.accountId = id || 'FIXME_someid';
|
|
|
|
|
this.profile = profile;
|
|
|
|
|
store.set(this.accountId, this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param use - can either be "id_token" or "userinfo", depending on
|
|
|
|
|
* where the specific claims are intended to be put in.
|
|
|
|
|
* @param scope - the intended scope, while oidc-provider will mask
|
|
|
|
|
* claims depending on the scope automatically you might want to skip
|
|
|
|
|
* loading some claims from external resources etc. based on this detail
|
|
|
|
|
* or not return them in id tokens but only userinfo and so on.
|
|
|
|
|
*/
|
|
|
|
|
async claims(use, scope) { // eslint-disable-line no-unused-vars
|
|
|
|
|
if (this.profile) {
|
|
|
|
|
return {
|
|
|
|
|
sub: this.accountId, // it is essential to always return a sub claim
|
|
|
|
|
email: this.profile.email,
|
|
|
|
|
email_verified: this.profile.email_verified,
|
|
|
|
|
family_name: this.profile.family_name,
|
|
|
|
|
given_name: this.profile.given_name,
|
|
|
|
|
locale: this.profile.locale,
|
|
|
|
|
name: this.profile.name,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
sub: this.accountId, // it is essential to always return a sub claim
|
|
|
|
|
|
|
|
|
|
address: {
|
|
|
|
|
country: '000',
|
|
|
|
|
formatted: '000',
|
|
|
|
|
locality: '000',
|
|
|
|
|
postal_code: '000',
|
|
|
|
|
region: '000',
|
|
|
|
|
street_address: '000',
|
|
|
|
|
},
|
|
|
|
|
birthdate: '1987-10-16',
|
|
|
|
|
email: 'johndoe@example.com',
|
|
|
|
|
email_verified: false,
|
|
|
|
|
family_name: 'Doe',
|
|
|
|
|
gender: 'male',
|
|
|
|
|
given_name: 'John',
|
|
|
|
|
locale: 'en-US',
|
|
|
|
|
middle_name: 'Middle',
|
|
|
|
|
name: 'John Doe',
|
|
|
|
|
nickname: 'Johny',
|
|
|
|
|
phone_number: '+49 000 000000',
|
|
|
|
|
phone_number_verified: false,
|
|
|
|
|
picture: 'http://lorempixel.com/400/200/',
|
|
|
|
|
preferred_username: 'johnny',
|
|
|
|
|
profile: 'https://johnswebsite.com',
|
|
|
|
|
updated_at: 1454704946,
|
|
|
|
|
website: 'http://example.com',
|
|
|
|
|
zoneinfo: 'Europe/Berlin',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async findByFederated(provider, claims) {
|
|
|
|
|
const id = `${provider}.${claims.sub}`;
|
|
|
|
|
if (!logins.get(id)) {
|
|
|
|
|
logins.set(id, new Account(id, claims));
|
|
|
|
|
}
|
|
|
|
|
return logins.get(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async findByLogin(login) {
|
|
|
|
|
if (!logins.get(login)) {
|
|
|
|
|
logins.set(login, new Account(login));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return logins.get(login);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async findAccount(ctx, id, token) { // eslint-disable-line no-unused-vars
|
|
|
|
|
// token is a reference to the token used for which a given account is being loaded,
|
|
|
|
|
// it is undefined in scenarios where account claims are returned from authorization endpoint
|
|
|
|
|
// ctx is the koa request context
|
|
|
|
|
if (!store.get(id)) new Account(id); // eslint-disable-line no-new
|
|
|
|
|
return store.get(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function attachInteractionRoutes(routePrefix, app, provider) {
|
|
|
|
|
assert.strictEqual(typeof routePrefix, 'string');
|
|
|
|
|
assert.strictEqual(typeof app, 'function'); // express app
|
|
|
|
|
assert.strictEqual(typeof provider, 'object');
|
|
|
|
|
|
|
|
|
|
function setNoCache(req, res, next) {
|
|
|
|
|
res.set('cache-control', 'no-store');
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.get(routePrefix + '/interaction/:uid', setNoCache, async (req, res, next) => {
|
|
|
|
|
try {
|
|
|
|
|
const { uid, prompt, params, session } = await provider.interactionDetails(req, res);
|
2023-03-13 19:08:41 +01:00
|
|
|
console.log('details', await provider.interactionDetails(req, res));
|
2023-03-10 17:13:33 +01:00
|
|
|
|
2023-03-11 17:22:27 +01:00
|
|
|
debug(`route interaction get uid:${uid} prompt.name:${prompt.name} client_id:${params.client_id} session:${session}`);
|
2023-03-10 17:13:33 +01:00
|
|
|
|
|
|
|
|
const client = await provider.Client.find(params.client_id);
|
|
|
|
|
|
|
|
|
|
switch (prompt.name) {
|
|
|
|
|
case 'login': {
|
|
|
|
|
return res.render('login', {
|
|
|
|
|
client,
|
2023-03-11 17:22:27 +01:00
|
|
|
submitUrl: `${routePrefix}/interaction/${uid}/login`,
|
2023-03-10 17:13:33 +01:00
|
|
|
uid,
|
|
|
|
|
details: prompt.details,
|
|
|
|
|
params,
|
|
|
|
|
title: 'Sign-in',
|
|
|
|
|
session: session ? debug(session) : undefined,
|
|
|
|
|
dbg: {
|
|
|
|
|
params: debug(params),
|
|
|
|
|
prompt: debug(prompt),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
case 'consent': {
|
|
|
|
|
return res.render('interaction', {
|
|
|
|
|
client,
|
2023-03-11 17:22:27 +01:00
|
|
|
submitUrl: `${routePrefix}/interaction/${uid}/confirm`,
|
2023-03-10 17:13:33 +01:00
|
|
|
uid,
|
|
|
|
|
details: prompt.details,
|
|
|
|
|
params,
|
|
|
|
|
title: 'Authorize',
|
|
|
|
|
session: session ? debug(session) : undefined,
|
|
|
|
|
dbg: {
|
|
|
|
|
params: debug(params),
|
|
|
|
|
prompt: debug(prompt),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return next(err);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.post(routePrefix + '/interaction/:uid/login', setNoCache, async (req, res, next) => {
|
2023-03-13 19:08:41 +01:00
|
|
|
const [detailsError, details] = await safe(provider.interactionDetails(req, res));
|
|
|
|
|
if (detailsError) return next(new HttpError(500, detailsError));
|
2023-03-10 17:13:33 +01:00
|
|
|
|
2023-03-13 19:08:41 +01:00
|
|
|
const uid = details.uid;
|
|
|
|
|
const prompt = details.prompt;
|
|
|
|
|
const name = prompt.name;
|
2023-03-10 17:13:33 +01:00
|
|
|
|
2023-03-13 19:08:41 +01:00
|
|
|
debug(`route interaction login post uid:${uid} prompt.name:${name}`, req.body);
|
2023-03-10 17:13:33 +01:00
|
|
|
|
2023-03-13 19:08:41 +01:00
|
|
|
assert.equal(name, 'login');
|
2023-03-13 17:01:52 +01:00
|
|
|
|
2023-03-13 19:08:41 +01:00
|
|
|
if (!req.body.username || typeof req.body.username !== 'string') return next(new HttpError(400, 'A username must be non-empty string'));
|
|
|
|
|
if (!req.body.password || typeof req.body.password !== 'string') return next(new HttpError(400, 'A password must be non-empty string'));
|
|
|
|
|
if ('totpToken' in req.body && typeof req.body.totpToken !== 'string') return next(new HttpError(400, 'totpToken must be a string' ));
|
2023-03-13 17:01:52 +01:00
|
|
|
|
2023-03-13 19:08:41 +01:00
|
|
|
const { username, password, totpToken } = req.body;
|
2023-03-13 17:01:52 +01:00
|
|
|
|
2023-03-13 19:08:41 +01:00
|
|
|
const verifyFunc = username.indexOf('@') === -1 ? users.verifyWithUsername : users.verifyWithEmail;
|
2023-03-13 17:01:52 +01:00
|
|
|
|
2023-03-13 19:08:41 +01:00
|
|
|
const [verifyError, user] = await safe(verifyFunc(username, password, users.AP_WEBADMIN, { totpToken }));
|
|
|
|
|
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, verifyError.message));
|
|
|
|
|
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new HttpError(401, 'Unauthorized'));
|
|
|
|
|
if (verifyError) return next(new HttpError(500, verifyError));
|
|
|
|
|
if (!user) return next(new HttpError(401, 'Unauthorized'));
|
2023-03-10 17:13:33 +01:00
|
|
|
|
2023-03-13 19:08:41 +01:00
|
|
|
// TODO we may have to check what else the Account class provides, in which case we have to map those things
|
|
|
|
|
const result = {
|
|
|
|
|
login: {
|
|
|
|
|
accountId: user.id,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const [interactionFinishError, interaction] = await safe(provider.interactionFinished(req, res, result));
|
|
|
|
|
if (interactionFinishError) return next(new HttpError(500, interactionFinishError));
|
|
|
|
|
|
|
|
|
|
next(new HttpSuccess(200, { redirectTo: interaction.redirectTo }));
|
2023-03-10 17:13:33 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.post(routePrefix + '/interaction/:uid/confirm', setNoCache, async (req, res, next) => {
|
|
|
|
|
try {
|
|
|
|
|
const interactionDetails = await provider.interactionDetails(req, res);
|
|
|
|
|
const { uid, prompt: { name, details }, params, session: { accountId } } = interactionDetails;
|
|
|
|
|
|
2023-03-11 17:22:27 +01:00
|
|
|
debug(`route interaction confirm post uid:${uid} prompt.name:${name} accountId:${accountId}`);
|
2023-03-10 17:13:33 +01:00
|
|
|
|
|
|
|
|
assert.equal(name, 'consent');
|
|
|
|
|
|
|
|
|
|
let { grantId } = interactionDetails;
|
|
|
|
|
let grant;
|
|
|
|
|
|
|
|
|
|
if (grantId) {
|
|
|
|
|
// we'll be modifying existing grant in existing session
|
|
|
|
|
grant = await provider.Grant.find(grantId);
|
|
|
|
|
} else {
|
|
|
|
|
// we're establishing a new grant
|
|
|
|
|
grant = new provider.Grant({
|
|
|
|
|
accountId,
|
|
|
|
|
clientId: params.client_id,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (details.missingOIDCScope) {
|
|
|
|
|
grant.addOIDCScope(details.missingOIDCScope.join(' '));
|
|
|
|
|
}
|
|
|
|
|
if (details.missingOIDCClaims) {
|
|
|
|
|
grant.addOIDCClaims(details.missingOIDCClaims);
|
|
|
|
|
}
|
|
|
|
|
if (details.missingResourceScopes) {
|
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
|
|
|
for (const [indicator, scopes] of Object.entries(details.missingResourceScopes)) {
|
|
|
|
|
grant.addResourceScope(indicator, scopes.join(' '));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
grantId = await grant.save();
|
|
|
|
|
|
|
|
|
|
const consent = {};
|
|
|
|
|
if (!interactionDetails.grantId) {
|
|
|
|
|
// we don't have to pass grantId to consent, we're just modifying existing one
|
|
|
|
|
consent.grantId = grantId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = { consent };
|
|
|
|
|
await provider.interactionFinished(req, res, result, { mergeWithLastSubmission: true });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
next(err);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get(routePrefix + '/interaction/:uid/abort', setNoCache, async (req, res, next) => {
|
2023-03-11 17:22:27 +01:00
|
|
|
debug(`route interaction abort`);
|
2023-03-10 17:13:33 +01:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = {
|
|
|
|
|
error: 'access_denied',
|
|
|
|
|
error_description: 'End-User aborted interaction',
|
|
|
|
|
};
|
|
|
|
|
await provider.interactionFinished(req, res, result, { mergeWithLastSubmission: false });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
next(err);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-09 20:17:27 +01:00
|
|
|
async function getProvider(routePrefix) {
|
2023-03-10 17:13:33 +01:00
|
|
|
assert.strictEqual(typeof routePrefix, 'string');
|
|
|
|
|
|
2023-03-08 16:41:59 +01:00
|
|
|
const { Provider } = await import('oidc-provider');
|
|
|
|
|
|
|
|
|
|
const configuration = {
|
2023-03-10 17:13:33 +01:00
|
|
|
// use the one from Account class I guess?
|
2023-03-09 19:16:36 +01:00
|
|
|
async findAccount(ctx, id) {
|
|
|
|
|
debug(`findAccount ctx:${ctx} id:${id}`);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
accountId: id,
|
|
|
|
|
async claims(use, scope) { return { sub: id }; },
|
|
|
|
|
};
|
|
|
|
|
},
|
2023-03-08 16:41:59 +01:00
|
|
|
adapter: CloudronAdapter,
|
2023-03-09 20:17:27 +01:00
|
|
|
interactions: {
|
|
|
|
|
url: async function(ctx, interaction) {
|
|
|
|
|
return `${routePrefix}/interaction/${interaction.uid}`;
|
|
|
|
|
}
|
2023-03-10 16:07:45 +01:00
|
|
|
},
|
2023-03-11 17:22:27 +01:00
|
|
|
features: {
|
2023-03-13 19:08:41 +01:00
|
|
|
devInteractions: { enabled: false }
|
2023-03-11 17:22:27 +01:00
|
|
|
},
|
2023-03-08 16:41:59 +01:00
|
|
|
clients: [{
|
|
|
|
|
client_id: 'foo',
|
|
|
|
|
client_secret: 'bar',
|
|
|
|
|
redirect_uris: ['https://openidconnect.net/callback'],
|
|
|
|
|
}],
|
|
|
|
|
pkce: {
|
|
|
|
|
required: function pkceRequired(ctx, client) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const provider = new Provider(`https://${settings.dashboardFqdn()}`, configuration);
|
|
|
|
|
provider.proxy = true
|
|
|
|
|
|
|
|
|
|
return provider;
|
|
|
|
|
}
|