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
283 lines • 12.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProviderApplicationBuilder = void 0;
const tslib_1 = require("tslib");
const koa_bodyparser_1 = tslib_1.__importDefault(require("koa-bodyparser"));
const koa_compose_1 = tslib_1.__importDefault(require("koa-compose"));
const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
const koajs_nocache_1 = tslib_1.__importDefault(require("koajs-nocache"));
const change_case_1 = require("change-case");
const context_1 = require("./context");
const federation_1 = require("./federation");
const renderer_1 = require("./renderer");
class ProviderApplicationBuilder {
constructor(builder) {
this.builder = builder;
this._prefix = "/op";
this.getURL = (path, withHost) => `${withHost ? this.builder.issuer : ""}${this.prefix}${path}`;
this.wrapContext = async (ctx, next) => {
ctx.idp = this.idp;
ctx.op = await new context_1.OIDCProviderContextProxy(ctx, this.builder)._dangerouslyCreate();
ctx.unwrap = () => {
delete ctx.idp;
delete ctx.op;
delete ctx.locale;
return ctx;
};
return next();
};
this.errorHandler = async (ctx, next) => {
try {
await next();
}
catch (err) {
this.logger.error("app error", err);
// normalize and
const { error, name, message, status, statusCode, code, status_code, error_description, expose, ...otherProps } = err;
// set status
let normalizedStatus = status || statusCode || code || status_code || 500;
if (isNaN(normalizedStatus))
normalizedStatus = 500;
ctx.status = normalizedStatus;
const normalizedError = {
error: change_case_1.pascalCase(error || name || "UnexpectedError"),
error_description: error_description || message || "Unexpected error.",
...((expose || this.builder.dev) ? otherProps : {}),
};
return ctx.op.render("error", normalizedError);
}
};
this.routerMiddleware = koa_compose_1.default([
koajs_nocache_1.default(),
koa_bodyparser_1.default(),
this.wrapContext,
this.errorHandler,
]);
// internally named routes render default functions
// ref: https://github.com/panva/node-oidc-provider/blob/74b434c627248c82ca9db5aed3a03f0acd0d7214/lib/shared/error_handler.js#L47
this.renderErrorProxy = (ctx, out, error) => {
return this.wrapContext(ctx, () => {
return this.errorHandler(ctx, () => {
throw error;
});
});
};
this.renderLogout = async (ctx, xsrf) => {
return ctx.op.render("logout", undefined, {
// destroy sessions
"logout.confirm": {
url: ctx.op.getNamedURL("end_session_confirm"),
method: "POST",
payload: {
xsrf,
logout: "true",
},
synchronous: true,
},
// without session destroy
"logout.redirect": {
url: ctx.op.getNamedURL("end_session_confirm"),
method: "POST",
payload: {
xsrf,
},
synchronous: true,
},
});
};
// ref: https://github.com/panva/node-oidc-provider/blob/e5ecd85c346761f1ac7a89b8bf174b873be09239/lib/actions/end_session.js#L89
this.logoutSourceProxy = (ctx) => {
// @ts-ignore
return this.wrapContext(ctx, () => {
const op = ctx.op;
ctx.assert(op.user);
const xsrf = op.session.state && op.session.state.secret;
return this.renderLogout(ctx, xsrf);
});
};
this.renderLogoutEnd = async (ctx) => {
return ctx.op.render("logout.end");
};
// ref: https://github.com/panva/node-oidc-provider/blob/e5ecd85c346761f1ac7a89b8bf174b873be09239/lib/actions/end_session.js#L272
this.postLogoutSuccessSourceProxy = async (ctx) => {
return this.wrapContext(ctx, async () => {
const op = ctx.op;
op.clientMetadata = await op.getPublicClientProps(ctx.oidc.client);
return this.renderLogoutEnd(ctx);
});
};
this.renderDeviceFlow = async (ctx, userCode, xsrf) => {
return ctx.op.render("device_flow", undefined, {
"device_flow.submit": {
url: ctx.op.getNamedURL("code_verification"),
method: "POST",
payload: {
xsrf,
user_code: userCode,
},
},
});
};
// ref: https://github.com/panva/node-oidc-provider/blob/74b434c627248c82ca9db5aed3a03f0acd0d7214/lib/actions/code_verification.js#L38
this.deviceFlowUserCodeInputSourceProxy = (ctx, formHTML, out, error) => {
return this.wrapContext(ctx, () => {
const op = ctx.op;
ctx.assert(op.user && op.client);
if (error || out) {
this.logger.error("internal device code flow error", error || out);
throw out || error;
}
const xsrf = op.session.state && op.session.state.secret;
return this.renderDeviceFlow(ctx, ctx.oidc.params.user_code || "", xsrf);
});
};
this.renderDeviceFlowConfirm = async (ctx, userCode, xsrf, device) => {
return ctx.op.render("device_flow.confirm", undefined, {
"device_flow.verify": {
url: ctx.op.getNamedURL("code_verification"),
method: "POST",
payload: {
xsrf,
user_code: userCode,
confirm: "true",
},
},
"device_flow.abort": {
url: ctx.op.getNamedURL("code_verification"),
method: "POST",
payload: {
xsrf,
user_code: userCode,
abort: "true",
},
},
});
};
// ref: https://github.com/panva/node-oidc-provider/blob/74b434c627248c82ca9db5aed3a03f0acd0d7214/lib/actions/code_verification.js#L54
this.deviceFlowUserCodeConfirmSourceProxy = (ctx, formHTML, client, device, userCode) => {
return this.wrapContext(ctx, () => {
const op = ctx.op;
ctx.assert(op.user && op.client);
op.device = device;
const xsrf = op.session.state && op.session.state.secret;
return this.renderDeviceFlowConfirm(ctx, userCode, xsrf, device);
});
};
this.renderDeviceFlowEnd = async (ctx) => {
return ctx.op.render("device_flow.end");
};
// ref: https://github.com/panva/node-oidc-provider/blob/ae8a4589c582b96f4e9ca0432307da15792ac29d/lib/actions/authorization/device_user_flow_response.js#L42
this.deviceFlowSuccessSourceProxy = (ctx) => {
return this.wrapContext(ctx, () => {
return this.renderDeviceFlowEnd(ctx);
});
};
this._config = {
url: (ctx, interaction) => {
return `${this.prefix}/${interaction.prompt.name}`;
},
policy: [],
};
this.logger = builder.logger;
// create router
this.router = new koa_router_1.default({
prefix: this.prefix,
sensitive: true,
strict: false,
})
.use(this.routerMiddleware);
this.router._setPrefix = this.router.prefix.bind(this.router);
this.router.prefix = () => {
this.logger.warn("rather call builder.setPrefix, it will not affect");
return this.router;
};
// create federation builder
this.federation = new federation_1.IdentityFederationBuilder(this.builder);
}
get prefix() {
return this._prefix;
}
_dangerouslySetPrefix(prefix) {
this.builder.assertBuilding();
this._prefix = prefix;
this.router._setPrefix(prefix)
.use(this.routerMiddleware); // re-apply middleware
this.logger.info(`OIDC application route path configured:`, `${prefix}/:path`);
}
get idp() {
return this.builder.idp;
}
get op() {
return this.builder._dagerouslyGetProvider();
}
// default render function
setRendererFactory(factory, options) {
this._appRenderer = factory({
prefix: this.prefix,
dev: this.builder.dev,
logger: this.logger,
}, options);
return this;
}
get appRenderer() {
if (!this._appRenderer) {
this.setRendererFactory(renderer_1.dummyAppStateRendererFactory);
}
return this._appRenderer;
}
setRoutesFactory(factory) {
this._routesFactory = factory;
return this;
}
getRoutes(promptName) {
if (!this._routesFactory) {
this.logger.warn("routes factory not configured; which is to ensure available xhr/page request endpoints for each prompts");
return {};
}
return this._routesFactory(promptName);
}
setPrompts(prompts) {
this.builder.assertBuilding();
this._config.policy.splice(0, this._config.policy.length, ...prompts);
return this;
}
_dangerouslyGetDynamicConfiguration() {
this.builder.assertBuilding();
const { renderErrorProxy, logoutSourceProxy, postLogoutSuccessSourceProxy, deviceFlowUserCodeInputSourceProxy, deviceFlowUserCodeConfirmSourceProxy, deviceFlowSuccessSourceProxy, } = this;
return {
renderError: renderErrorProxy,
logoutSource: logoutSourceProxy,
interactions: this._config,
postLogoutSuccessSource: postLogoutSuccessSourceProxy,
features: {
deviceFlow: {
userCodeInputSource: deviceFlowUserCodeInputSourceProxy,
userCodeConfirmSource: deviceFlowUserCodeConfirmSourceProxy,
successSource: deviceFlowSuccessSourceProxy,
},
},
};
}
_dangerouslyBuild() {
this.builder.assertBuilding();
// normalize oidc-provider original error for xhr error response, ref: https://github.com/panva/node-oidc-provider/blob/master/lib/shared/error_handler.js#L49
this.op.app.middleware.unshift(async (ctx, next) => {
await next();
if (ctx.body && typeof ctx.body.error === "string") {
ctx.body = { error: change_case_1.pascalCase(ctx.body.error) };
}
});
// mount app renderer and app
this.op.app.use(koa_compose_1.default([
// apply additional "app renderer" middleware (like serving static files), order matters for security's sake
...(this.appRenderer.routes ? this.appRenderer.routes() : []),
// apply "app router" middleware
// this.router.allowedMethods(), // support "OPTIONS" methods
this.router.routes(),
]));
// build federation configuration
this.federation._dangerouslyBuild();
}
}
exports.ProviderApplicationBuilder = ProviderApplicationBuilder;
//# sourceMappingURL=app.js.map