'use strict'; exports = module.exports = { getProvider, getMiddleware }; const debug = require('debug')('box:oidc'), fs = require('fs'), path = require('path'), paths = require('./paths.js'), 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); this.store[id] = { id, expiresIn, payload, consumed: false }; 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; return this.store[id].payload; } /** * * 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) { if (this.store[d].payload.uid === uid) return this.store[d].payload; } 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) { debug(`[${this.name}] consume id:${id}`); if (this.store[id]) this.store[id].consumed = true; } /** * * 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; } } } } async function getProvider(routePrefix) { const { Provider } = await import('oidc-provider'); const configuration = { async findAccount(ctx, id) { debug(`findAccount ctx:${ctx} id:${id}`); return { accountId: id, async claims(use, scope) { return { sub: id }; }, }; }, adapter: CloudronAdapter, interactions: { url: async function(ctx, interaction) { return `${routePrefix}/interaction/${interaction.uid}`; } }, 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; } async function getMiddleware(routePrefix) { const provider = await getProvider(routePrefix); return provider.callback(); }