@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
JavaScript
"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