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
224 lines • 8.97 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OIDCProviderProxy = void 0;
const tslib_1 = require("tslib");
const kleur = tslib_1.__importStar(require("kleur"));
const uuid_1 = require("uuid");
const accept_language_parser_1 = require("accept-language-parser");
const app_1 = require("../app");
const config_1 = require("./config");
const error_types_1 = require("./error.types");
// ref: https://github.com/panva/node-oidc-provider/blob/9306f66bdbcdff01400773f26539cf35951b9ce8/lib/models/client.js#L385
// @ts-ignore : need to hack oidc-provider private methods
const weak_cache_1 = tslib_1.__importDefault(require("oidc-provider/lib/helpers/weak_cache"));
let OIDCProviderProxy = /** @class */ (() => {
class OIDCProviderProxy {
constructor(props, options) {
this.props = props;
const { logger, idp } = props;
this.logger = logger;
// apply static options and get the provider instance and proxy config which can be set dynamically
const { app, ...staticConfig } = options;
const builder = new config_1.ProviderConfigBuilder({
logger,
idp,
}, staticConfig);
// build main logic with options
app_1.buildApplication(builder, app);
// create provider and adapter
this.provider = builder._dangerouslyBuild();
this.adapter = builder.adapter;
}
get hidden() {
return weak_cache_1.default(this.provider);
}
// http handler application (Koa)
get app() {
return this.provider.app;
}
get configuration() {
return this.hidden.configuration();
}
get supportedLocales() {
if (!this._supportedLocales) {
this._supportedLocales = [...new Set([...this.configuration.discovery.ui_locales_supported || []])];
}
return this._supportedLocales;
}
parseLocale(locale) {
const locales = this.supportedLocales;
const raw = accept_language_parser_1.pick(locales, locale || "", { loose: true }) || locales[0] || "ko-KR";
const [language, region] = raw.split("-");
const [_, requestedRegion] = (locale || "").split("-"); // request locale region will take precedence over matched one
return { language, region: requestedRegion || region || "KR" };
}
get issuer() {
return this.provider.issuer;
}
async start() {
await this.adapter.start();
await this.syncSupportedClaimsAndScopes();
}
stop() {
return this.adapter.stop();
}
// programmable interfaces
get Session() {
return this.adapter.getModel("Session");
}
get AccessToken() {
return this.adapter.getModel("AccessToken");
}
get AuthorizationCode() {
return this.adapter.getModel("AuthorizationCode");
}
get RefreshToken() {
return this.adapter.getModel("RefreshToken");
}
get DeviceCode() {
return this.adapter.getModel("DeviceCode");
}
get ClientCredentials() {
return this.adapter.getModel("ClientCredentials");
}
get Client() {
return this.adapter.getModel("Client");
}
get InitialAccessToken() {
return this.adapter.getModel("InitialAccessToken");
}
get RegistrationAccessToken() {
return this.adapter.getModel("RegistrationAccessToken");
}
get Interaction() {
return this.adapter.getModel("Interaction");
}
get ReplayDetection() {
return this.adapter.getModel("ReplayDetection");
}
get PushedAuthorizationRequest() {
return this.adapter.getModel("PushedAuthorizationRequest");
}
async findClient(id) {
return this.Client.find(id);
}
async findClientOrFail(id) {
const client = await this.findClient(id);
if (!client) {
throw new error_types_1.OIDCErrors.InvalidClient("client_not_found");
}
return client;
}
async createClient(metadata) {
if (metadata.client_id && await this.findClient(metadata.client_id)) {
throw new error_types_1.OIDCErrors.InvalidClient("client_id_duplicated");
}
this.logger.info(`create client ${kleur.cyan(metadata.client_id)}:`, metadata);
const client = await this.hidden.clientAdd({
...metadata,
client_secret: OIDCProviderProxy.generateClientSecret(),
}, { store: true });
return client.metadata();
}
async updateClient(metadata) {
const old = await this.findClient(metadata.client_id);
// update client_secret
if (metadata.reset_client_secret === true) {
metadata = {
...metadata,
client_secret: OIDCProviderProxy.generateClientSecret(),
};
delete metadata.reset_client_secret;
}
this.logger.info(`update client ${kleur.cyan(metadata.client_id || "<unknown>")}:`, require("util").inspect(metadata));
const client = await this.hidden.clientAdd({
...old,
...metadata,
}, { store: true });
return client.metadata();
}
async deleteClient(id) {
await this.findClientOrFail(id);
this.logger.info(`delete client ${kleur.cyan(id)}`);
this.hidden.clientRemove(id);
}
async getClients(args) {
return this.Client.get(args);
}
async countClients(args) {
return this.Client.count(args);
}
static generateClientSecret() {
return uuid_1.v4().replace(/\-/g, "") + uuid_1.v4().replace(/\-/g, "");
}
async countModels(kind, args) {
const model = this.adapter.getModel(kind);
return model.count(args);
}
async getModels(kind, args) {
const model = this.adapter.getModel(kind);
return model.get(args);
}
async deleteModels(kind, args) {
const model = this.adapter.getModel(kind);
return model.delete(args);
}
async syncSupportedClaimsAndScopes() {
// set available scopes and claims
const claimsSchemata = await this.props.idp.claims.getActiveClaimsSchemata();
this.updateSupportedClaimsAndScopes(claimsSchemata);
}
// set supported claims and scopes (hacky)
// ref: https://github.com/panva/node-oidc-provider/blob/ae8a4589c582b96f4e9ca0432307da15792ac29d/lib/helpers/claims.js#L54
updateSupportedClaimsAndScopes(defs) {
const config = this.configuration;
const claimsFilter = config.claims;
const claimsSupported = config.claimsSupported;
defs.forEach(schema => {
let obj = claimsFilter.get(schema.scope);
if (obj === null) {
return;
}
if (!obj) {
obj = {};
claimsFilter.set(schema.scope, obj);
}
obj[schema.key] = null;
claimsSupported.add(schema.key);
});
const scopes = config.scopes;
const availableScopes = defs.map(schema => schema.scope).concat(["openid", "offline_access"]);
for (const s of availableScopes) {
scopes.add(s);
}
for (const s of scopes.values()) {
if (!availableScopes.includes(s)) {
scopes.delete(s);
}
}
// log result
this.logger.info(`available claims for each scopes has been updated:\n${[...claimsFilter.entries()]
.map(([scope, claims]) => {
return claims
? `${kleur.green(scope.padEnd(20))}: ${kleur.white(Object.keys(claims).join(", "))}`
: `${kleur.green().dim("(token)".padEnd(20))}: ${kleur.dim(scope)}`;
})
.join("\n")}`);
}
}
OIDCProviderProxy.volatileModelNames = [
"Session",
"AccessToken",
"AuthorizationCode",
"RefreshToken",
"DeviceCode",
"InitialAccessToken",
"RegistrationAccessToken",
"Interaction",
"ReplayDetection",
"PushedAuthorizationRequest",
];
return OIDCProviderProxy;
})();
exports.OIDCProviderProxy = OIDCProviderProxy;
//# sourceMappingURL=proxy.js.map