moleculer-iam
Version:
Centralized IAM module for moleculer. Including a certified OIDC provider and an Identity provider for user profile, credentials, and custom claims management. Custom claims could be defined/updated by declarative schema which contains claims validation a
234 lines • 8.81 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OIDCProviderContextProxy = void 0;
const tslib_1 = require("tslib");
const error_1 = require("./error");
// need to hack oidc-provider private methods
// ref: https://github.com/panva/node-oidc-provider/blob/9306f66bdbcdff01400773f26539cf35951b9ce8/lib/models/client.js#L385
// @ts-ignore
const weak_cache_1 = tslib_1.__importDefault(require("oidc-provider/lib/helpers/weak_cache"));
// @ts-ignore
const session_1 = tslib_1.__importDefault(require("oidc-provider/lib/shared/session"));
const JSON = "application/json";
const HTML = "text/html";
const PUBLIC = "__public__";
const SECRET = "__secret__";
class OIDCProviderContextProxy {
constructor(ctx, builder) {
this.ctx = ctx;
this.builder = builder;
this.session = {}; // filled later
this.metadata = {}; // filled later
this.getNamedURL = (name, opts) => {
return this.provider.urlFor(name, opts).replace(this.builder.issuer, "");
};
this.shouldSaveSession = false;
}
get idp() {
return this.builder.app.idp;
}
get provider() {
return this.builder.app.op;
}
get getURL() {
return this.builder.app.getURL;
}
get routes() {
return this.builder.app.getRoutes(this.interaction && this.interaction.prompt && this.interaction.prompt.name);
}
// response methods
async render(name, error, additionalRoutes) {
await this.ensureSessionSaved();
const { ctx } = this;
// response { error: {} } when is XHR and stateProps has error
if (this.isXHR && error) {
const response = { error };
ctx.type = JSON;
ctx.body = response;
return;
}
// else response { state: {...} }
const state = {
name,
error,
routes: {
...this.routes,
...additionalRoutes,
},
metadata: this.metadata,
locale: ctx.locale,
session: this.sessionPublicState,
interaction: this.interaction,
// current op interaction information (login, consent)
client: this.clientMetadata,
user: this.userClaims,
device: this.device,
authorizedClients: await this.getAuthorizedClientsProps(),
};
if (this.isXHR) {
const response = { state };
ctx.type = JSON;
ctx.body = response;
return;
}
// unwrap enhanced context to secure vulnerability, then delegate response to app renderer
ctx.type = HTML;
return this.builder.app.appRenderer.render(ctx.unwrap(), state);
}
async redirectWithUpdate(promptUpdate, allowedPromptNames) {
await this.ensureSessionSaved();
// finish interaction prompt
const { ctx, provider } = this;
this.assertPrompt();
const mergedResult = { ...this.interaction.result, ...promptUpdate };
const redirectURL = await provider.interactionResult(ctx.req, ctx.res, mergedResult, { mergeWithLastSubmission: true });
// overwrite session account if need and re-parse interaction state
if (mergedResult.login) {
await provider.setProviderSession(ctx.req, ctx.res, mergedResult.login);
await this.readProviderSession();
}
return this.redirect(redirectURL);
}
async redirect(url) {
await this.ensureSessionSaved();
const redirectURL = url.startsWith("/") ? this.getURL(url) : url; // add prefix for local redirection
if (this.isXHR) {
const response = { redirect: redirectURL };
this.ctx.body = response;
return;
}
this.ctx.redirect(redirectURL);
}
async end() {
await this.ensureSessionSaved();
const response = { session: this.sessionPublicState };
this.ctx.type = JSON;
this.ctx.body = response;
}
// session management
get sessionPublicState() {
return this.session.state && this.session.state[PUBLIC] || {};
}
get sessionSecretState() {
return this.session.state && this.session.state[SECRET] || {};
}
setSessionPublicState(update) {
return this.setSessionState(prevState => ({
...prevState,
[PUBLIC]: update(prevState[PUBLIC] || {}),
}));
}
setSessionSecretState(update) {
return this.setSessionState(prevState => ({
...prevState,
[SECRET]: update(prevState[SECRET] || {}),
}));
}
setSessionState(update) {
this.session.state = update(this.session.state || {});
this.shouldSaveSession = true;
}
async ensureSessionSaved() {
if (this.shouldSaveSession) {
// @ts-ignore
await this.session.save();
this.shouldSaveSession = false;
}
}
// utility methods
get isXHR() {
return this.ctx.accepts(JSON, HTML) === JSON;
}
assertPrompt(allowedPromptNames) {
const { interaction } = this;
if (!(interaction && (!allowedPromptNames || allowedPromptNames.includes(interaction.prompt.name)))) {
throw new error_1.OIDCProviderProxyErrors.InvalidPromptSession();
}
}
async getPublicClientProps(client) {
if (!client)
return;
return {
client_id: client.clientId,
client_name: client.clientName,
logo_uri: client.logoUri,
tos_uri: client.tosUri,
policy_uri: client.policyUri,
client_uri: client.clientUri,
};
}
async getPublicUserProps(id) {
if (!id)
return;
const { sub, email, picture, name } = await id.claims("userinfo", "profile email");
return {
sub,
email,
name,
picture,
};
}
async getAuthorizedClientsProps() {
if (!this.session || !this.session.authorizations) {
return undefined;
}
const authorizations = this.session.authorizations;
return Promise.all(Object.keys(authorizations)
.map(async (clientId) => this.provider.Client.find(clientId)
.then(client => this.getPublicClientProps(client))
.then(clientProps => {
if (clientProps) {
clientProps.authorization = authorizations[clientId];
}
return clientProps;
})))
.then(authorizedClients => authorizedClients.filter(c => !!c));
}
// parse metadata and collect information
async _dangerouslyCreate() {
const { ctx, idp, provider } = this;
const hiddenProvider = weak_cache_1.default(provider);
// @ts-ignore ensure oidc context
if (!ctx.oidc) {
Object.defineProperty(ctx, "oidc", { value: new hiddenProvider.OIDCContext(ctx) });
}
// @ts-ignore ensure session
const originalSessionState = ctx.oidc.session && ctx.oidc.session.state || {};
// @ts-ignore ensure session
await session_1.default(ctx, () => {
// @ts-ignore to set Set-Cookie response header
ctx.oidc.session.touched = true;
// @ts-ignore
this.session = this.ctx.oidc.session;
this.session.state = { ...this.session.state, ...originalSessionState };
});
// create metadata
const configuration = hiddenProvider.configuration();
this.metadata = {
federationProviders: this.builder.app.federation.providerNames,
mandatoryScopes: idp.claims.mandatoryScopes,
supportedScopes: idp.claims.supportedScopes,
discovery: configuration.discovery,
};
await this.readProviderSession();
return this;
}
async readProviderSession() {
const { ctx, idp, provider } = this;
this.user = this.session.account ? (await idp.findOrFail({ id: this.session.account })) : undefined;
if (this.user) {
this.userClaims = await this.getPublicUserProps(this.user);
}
try {
const interaction = await provider.interactionDetails(ctx.req, ctx.res);
this.interaction = interaction;
this.client = interaction.params.client_id ? await provider.Client.find(interaction.params.client_id) : undefined;
if (this.client) {
this.clientMetadata = await this.getPublicClientProps(this.client);
}
}
catch (err) { }
}
}
exports.OIDCProviderContextProxy = OIDCProviderContextProxy;
//# sourceMappingURL=context.js.map