Files
cloudron-box/src/oidc.js

211 lines
6.5 KiB
JavaScript
Raw Normal View History

'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);
2023-03-09 18:59:04 +01:00
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;
2023-03-09 18:59:04 +01:00
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) {
2023-03-09 18:59:04 +01:00
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}`;
}
2023-03-10 16:07:45 +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;
}
async function getMiddleware(routePrefix) {
const provider = await getProvider(routePrefix);
return provider.callback();
}