UNPKG

@mvx/identity

Version:

identity is oidc for mvc, type-mvc is base on koa. Decorator, Ioc, AOP mvc framework on server.

344 lines (342 loc) 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Authenticator = void 0; const tslib_1 = require("tslib"); const ioc_1 = require("@tsdi/ioc"); const http = require("http"); const ContextExtends_1 = require("./ContextExtends"); const results_1 = require("./results"); const SessionStrategy_1 = require("./SessionStrategy"); const errors_1 = require("../errors"); const IAuthenticator_1 = require("./IAuthenticator"); const mvc_1 = require("@mvx/mvc"); /** * `Authenticator` constructor. * */ let Authenticator = class Authenticator { constructor() { this._userProperty = 'user'; this._rolesProperty = 'roles'; this.strategies = new Map(); this.serializers = []; this.deserializers = []; this.infoTransformers = []; this.use(new SessionStrategy_1.SessionStrategy()); } get userProperty() { return this._userProperty; } get rolesProperty() { return this._rolesProperty; } /** * get strategy. * * @param {string} name * @returns {IStrategy} * @memberof IAuthenticator */ get(name) { return this.strategies.get(name); } use(name, strategy) { if (strategy === undefined) { strategy = name; name = strategy.name; } if (!name) { throw new Error('Authentication strategies must have a name'); } this.strategies.set(name, strategy); return this; } /** * Un-utilize the `strategy` with given `name`. * * In typical applications, the necessary authentication strategies are static, * configured once and always available. As such, there is often no need to * invoke this function. * * However, in certain situations, applications may need dynamically configure * and de-configure authentication strategies. The `use()`/`unuse()` * combination satisfies these scenarios. * * Examples: * * passport.unuse('legacy-api'); * */ unuse(name) { this.strategies.delete(name); return this; } /** * Passport's primary initialization middleware. * * Intializes Passport for incoming requests, allowing * authentication strategies to be applied. * * If sessions are being utilized, applications must set up Passport with * functions to serialize a user into and out of a session. For example, a * common pattern is to serialize just the user ID into the session (due to the * fact that it is desirable to store the minimum amount of data in a session). * When a subsequent request arrives for the session, the full User object can * be loaded from the database by ID. * * Note that additional middleware is required to persist login state, so we * must use the `connect.session()` middleware _before_ `passport.initialize()`. * * If sessions are being used, this middleware must be in use by the * Koa application for Passport to operate. If the application is * entirely stateless (not using sessions), this middleware is not necessary, * but its use will not have any adverse impact. * * Options: * - `userProperty` Property to set on `ctx.state` upon login, defaults to _user_ * * Examples: * app.use(connect.cookieParser()); * * app.use(connect.session({ secret: 'keyboard cat' })); * app.use(passport.initialize()); * app.use(passport.initialize({ userProperty: 'currentUser' })); * app.use(passport.session()); * * passport.serializeUser(function(user, done) { * done(null, user.id); * }); * * passport.deserializeUser(function(id, done) { * User.findById(id, function (err, user) { * done(err, user); * }); * }); * */ initialize(options = {}) { this._userProperty = options.userProperty || 'user'; this._rolesProperty = options.rolesProperty || 'roles'; return async (ctx, next) => { ctx.passport = this; const session = ctx.session; if (!session) { throw new Error('Session middleware is needed with passport middleware!'); } if (!('passport' in session)) { ctx.session.passport = { user: undefined }; } if (!('message' in session)) { session.message = {}; } ctx.session = session; ContextExtends_1.contextExtends(ctx); await next(); }; } authenticate(strategyNames, options = {}, callback) { if (ioc_1.isFunction(options)) { callback = options; options = {}; } let multi = true; // Cast `strategy` to an array, allowing authentication to pass through a chain of // strategies. The first strategy to succeed, redirect, or error will halt // the chain. Authentication failures will proceed through each strategy in // series, ultimately failing if all strategies fail. // // This is typically used on API endpoints to allow clients to authenticate // using their preferred choice of Basic, Digest, token-based schemes, etc. // It is not feasible to construct a chain of multiple strategies that involve // redirection (for example both Facebook and Twitter), since the first one to // redirect will halt the chain. if (ioc_1.isString(strategyNames)) { strategyNames = [strategyNames]; multi = false; } return async (ctx, next) => { // accumulator for failures from each strategy in the chain const failures = ctx.failures = []; await ioc_1.chain(strategyNames.map(strategyName => async (ctx1, step) => { const strategy = this.strategies.get(strategyName); if (!strategy) { throw new Error(`Unknown authentication strategy "${strategyName}"`); } try { const res = await strategy.authenticate(ctx1, options); if (res instanceof results_1.FailResult) { res.action(ctx1); } else if (res instanceof results_1.SuccessResult || res instanceof results_1.RedirectResult) { await res.action(ctx1); } else { await res.action(ctx1, callback); return step(); } } catch (error) { new results_1.FailResult(error.toString(), 401).action(ctx1); } }), ctx); if (!failures.length) { return await next(); } if (callback) { if (!multi) { return callback(null, false, failures[0].challenge, failures[0].status); } else { const challenges = failures.map(f => f.challenge); const statuses = failures.map(f => f.status); return callback(null, false, challenges, statuses); } } // Strategies are ordered by priority. For the purpose of flashing a // message, the first failure will be displayed. // const challenge = (failures[0] || {}).challenge || {}; if (options.failureMessage && failures[0].challenge.type) { const challenge = failures[0].challenge; if (!(challenge.type in ctx.session.message)) { ctx.session.message[challenge.type] = []; } ctx.session.message[challenge.type].push(challenge.messages); } if (options.failureRedirect) { return ctx.redirect(options.failureRedirect); } // When failure handling is not delegated to the application, the default // is to respond with 401 Unauthorized. Note that the WWW-Authenticate // header will be set according to the strategies in use (see // actions#fail). If multiple strategies failed, each of their challenges // will be included in the response. const rchallenge = []; let rstatus; let status; for (const failure of failures) { status = failure.status; rstatus = rstatus || status; if (ioc_1.isString(failure.challenge)) { rchallenge.push(failure.challenge); } } ctx.status = rstatus || 401; if (ctx.status === 401 && rchallenge.length) { ctx.set('WWW-Authenticate', rchallenge); } if (options.failWithError) { throw new errors_1.AuthenticationError(rstatus, http.STATUS_CODES[ctx.status]); } // ctx.res.statusMessage = http.STATUS_CODES[ctx.status]; ctx.response.message = http.STATUS_CODES[ctx.status]; ctx.res.end(http.STATUS_CODES[ctx.status]); throw new mvc_1.UnauthorizedError(ctx.response.message); }; } /** * Middleware that will authorize a third-party account using the given * `strategy` name, with optional `options`. * * If authorization is successful, the result provided by the strategy's verify * callback will be assigned to `ctx.state.account`. The existing login session and * `ctx.state.user` will be unaffected. * * This function is particularly useful when connecting third-party accounts * to the local account of a user that is currently authenticated. * * Examples: * * passport.authorize('twitter-authz', { failureRedirect: '/account' }); */ authorize(strategy, options = {}, callback) { options.userProperty = 'account'; return this.authenticate(strategy, options, callback); } /** * Middleware that will restore login state from a session. * * Web applications typically use sessions to maintain login state between * requests. For example, a user will authenticate by entering credentials into * a form which is submitted to the server. If the credentials are valid, a * login session is established by setting a cookie containing a session * identifier in the user's web browser. The web browser will send this cookie * in subsequent requests to the server, allowing a session to be maintained. * * If sessions are being utilized, and a login session has been established, * this middleware will populate `req.user` with the current user. * * Note that sessions are not strictly required for Passport to operate. * However, as a general rule, most web applications will make use of sessions. * An exception to this rule would be an API server, which expects each HTTP * request to provide credentials in an Authorization header. * * Examples: * * app.use(connect.cookieParser()); * app.use(connect.session({ secret: 'keyboard cat' })); * app.use(passport.initialize()); * app.use(passport.session()); * * Options: * - `pauseStream` Pause the request stream before deserializing the user * object from the session. Defaults to _false_. Should * be set to true in cases where middleware consuming the * request body is configured after passport and the * deserializeUser method is asynchronous. * * @api public */ session(options) { return this.authenticate('session', options); } async serializeUser(user, ctx) { if (typeof user === 'function') { return this.serializers.push(user); } for (const layer of this.serializers) { const obj = await layer(user, ctx); if (obj || obj === 0) { return obj; } } throw new Error('Failed to serialize user into session'); } async deserializeUser(obj, ctx) { if (ioc_1.isFunction(obj)) { return this.deserializers.push(obj); } for (const layer of this.deserializers) { const user = await layer(obj, ctx); if (user) { return user; } else if (user === null || user === false) { return false; } } throw new Error('Failed to deserialize user out of session'); } async transformAuthInfo(info, ctx) { if (ioc_1.isFunction(info)) { return this.infoTransformers.push(info); } // private implementation that traverses the chain of transformers, // attempting to transform auth info for (const layer of this.infoTransformers) { const tinfo = await layer(info, ctx); if (tinfo) { return tinfo; } } return info; } static ρAnn() { return { "name": "Authenticator", "params": { "get": ["name"], "use": ["name", "strategy"], "unuse": ["name"], "initialize": ["options"], "authenticate": ["strategyNames", "options", "callback"], "authorize": ["strategy", "options", "callback"], "session": ["options"], "serializeUser": ["user", "ctx"], "deserializeUser": ["obj", "ctx"], "transformAuthInfo": ["info", "ctx"] } }; } }; Authenticator = tslib_1.__decorate([ ioc_1.Singleton(IAuthenticator_1.AuthenticatorToken), tslib_1.__metadata("design:paramtypes", []) ], Authenticator); exports.Authenticator = Authenticator; //# sourceMappingURL=../sourcemaps/passports/Authenticator.js.map