UNPKG

@adonisjs/auth

Version:

Official authentication provider for Adonis framework

339 lines (338 loc) 11.3 kB
"use strict"; /* * @adonisjs/auth * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionGuard = void 0; const utils_1 = require("@poppinss/utils"); const helpers_1 = require("@poppinss/utils/build/helpers"); const Base_1 = require("../Base"); const AuthenticationException_1 = require("../../Exceptions/AuthenticationException"); /** * Session guard enables user login using sessions. Also it allows for * setting remember me tokens for life long login */ class SessionGuard extends Base_1.BaseGuard { constructor(name, config, emitter, provider, ctx) { super(name, config, provider); this.emitter = emitter; this.ctx = ctx; /** * Number of years for the remember me token expiry */ this.rememberMeTokenExpiry = '5y'; } /** * The name of the session key name */ get sessionKeyName() { return `auth_${this.name}`; } /** * The name of the session key name */ get rememberMeKeyName() { return `remember_${this.name}`; } /** * Returns the session object from the context. */ getSession() { if (!this.ctx.session) { throw new utils_1.Exception('"@adonisjs/session" is required to use the "session" auth driver'); } return this.ctx.session; } /** * Set the user id inside the session. Also forces the session module * to re-generate the session id */ setSession(userId) { this.getSession().put(this.sessionKeyName, userId); this.getSession().regenerate(); } /** * Generate remember me token */ generateRememberMeToken() { return helpers_1.string.generateRandom(20); } /** * Sets the remember me cookie with the remember me token */ setRememberMeCookie(userId, token) { const value = { id: userId, token: token, }; this.ctx.response.encryptedCookie(this.rememberMeKeyName, value, { maxAge: this.rememberMeTokenExpiry, httpOnly: true, }); } /** * Clears the remember me cookie */ clearRememberMeCookie() { this.ctx.response.clearCookie(this.rememberMeKeyName); } /** * Clears user session and remember me cookie */ clearUserFromStorage() { this.getSession().forget(this.sessionKeyName); this.clearRememberMeCookie(); } /** * Returns data packet for the login event. Arguments are * * - The mapping identifier * - Logged in user * - HTTP context * - Remember me token (optional) */ getLoginEventData(user, token) { return { name: this.name, ctx: this.ctx, user, token, }; } /** * Returns data packet for the authenticate event. Arguments are * * - The mapping identifier * - Logged in user * - HTTP context * - A boolean to tell if logged in viaRemember or not */ getAuthenticateEventData(user, viaRemember) { return { name: this.name, ctx: this.ctx, user, viaRemember, }; } /** * Returns the user id for the current HTTP request */ getRequestSessionId() { return this.getSession().get(this.sessionKeyName); } /** * Verifies the remember me token */ verifyRememberMeToken(rememberMeToken) { if (!rememberMeToken || !rememberMeToken.id || !rememberMeToken.token) { throw AuthenticationException_1.AuthenticationException.invalidSession(this.name); } } /** * Returns user from the user session id */ async getUserForSessionId(id) { const authenticatable = await this.provider.findById(id); if (!authenticatable.user) { throw AuthenticationException_1.AuthenticationException.invalidSession(this.name); } return authenticatable; } /** * Returns user for the remember me token */ async getUserForRememberMeToken(id, token) { const authenticatable = await this.provider.findByRememberMeToken(id, token); if (!authenticatable.user) { throw AuthenticationException_1.AuthenticationException.invalidSession(this.name); } return authenticatable; } /** * Returns the remember me token of the user that is persisted * inside the db. If not persisted, we create one and persist * it */ async getPersistedRememberMeToken(providerUser) { /** * Create and persist the user remember me token, when an existing one is missing */ if (!providerUser.getRememberMeToken()) { this.ctx.logger.trace('generating fresh remember me token'); providerUser.setRememberMeToken(this.generateRememberMeToken()); await this.provider.updateRememberMeToken(providerUser); } return providerUser.getRememberMeToken(); } /** * Verify user credentials and perform login */ async attempt(uid, password, remember) { const user = await this.verifyCredentials(uid, password); await this.login(user, remember); return user; } /** * Login user using their id */ async loginViaId(id, remember) { const providerUser = await this.findById(id); await this.login(providerUser.user, remember); return providerUser.user; } /** * Login a user */ async login(user, remember) { /** * Since the login method is exposed to the end user, we cannot expect * them to instantiate and return an instance of authenticatable, so * we create one manually. */ const providerUser = await this.getUserForLogin(user, this.config.provider.identifierKey); /** * getUserForLogin raises exception when id is missing, so we can * safely assume it is defined */ const id = providerUser.getId(); /** * Set session */ this.setSession(id); /** * Set remember me token when enabled */ if (remember) { const rememberMeToken = await this.getPersistedRememberMeToken(providerUser); this.ctx.logger.trace('setting remember me cookie', { name: this.rememberMeKeyName }); this.setRememberMeCookie(id, rememberMeToken); } else { /** * Clear remember me cookie, which may have been set previously. */ this.clearRememberMeCookie(); } /** * Emit login event. It can be used to track user logins and their devices. */ this.emitter.emit('adonis:session:login', this.getLoginEventData(providerUser.user, providerUser.getRememberMeToken())); this.markUserAsLoggedIn(providerUser.user); return providerUser.user; } /** * Authenticates the current HTTP request by checking for the user * session. */ async authenticate() { if (this.authenticationAttempted) { return this.user; } this.authenticationAttempted = true; const sessionId = this.getRequestSessionId(); /** * If session id exists, then attempt to login the user using the * session and return early */ if (sessionId) { const providerUser = await this.getUserForSessionId(sessionId); this.markUserAsLoggedIn(providerUser.user, true); this.emitter.emit('adonis:session:authenticate', this.getAuthenticateEventData(providerUser.user, false)); return this.user; } /** * Otherwise look for remember me token. Raise exception, if both remember * me token and session id are missing. */ const rememberMeToken = this.ctx.request.encryptedCookie(this.rememberMeKeyName); if (!rememberMeToken) { throw AuthenticationException_1.AuthenticationException.invalidSession(this.name); } /** * Ensure remember me token is valid after reading it from the cookie */ this.verifyRememberMeToken(rememberMeToken); /** * Attempt to locate the user for remember me token */ const providerUser = await this.getUserForRememberMeToken(rememberMeToken.id, rememberMeToken.token); this.setSession(providerUser.getId()); this.setRememberMeCookie(rememberMeToken.id, rememberMeToken.token); this.markUserAsLoggedIn(providerUser.user, true, true); this.emitter.emit('adonis:session:authenticate', this.getAuthenticateEventData(providerUser.user, true)); return this.user; } /** * Same as [[authenticate]] but returns a boolean over raising exceptions */ async check() { try { await this.authenticate(); } catch (error) { /** * Throw error when it is not an instance of the authentication */ if (error instanceof AuthenticationException_1.AuthenticationException === false) { throw error; } this.ctx.logger.trace(error, 'Authentication failure'); } return this.isAuthenticated; } /** * Logout by clearing session and cookies */ async logout(recycleRememberToken) { /** * Return early when not attempting to re-generate the remember me token */ if (!recycleRememberToken) { this.clearUserFromStorage(); this.markUserAsLoggedOut(); return; } /** * Attempt to authenticate the current request if not already authenticated. This * will help us get an instance of the current user */ if (!this.authenticationAttempted) { await this.check(); } /** * If authentication passed, then re-generate the remember me token * for the current user. */ if (this.user) { const providerUser = await this.provider.getUserFor(this.user); this.ctx.logger.trace('re-generating remember me token'); providerUser.setRememberMeToken(this.generateRememberMeToken()); await this.provider.updateRememberMeToken(providerUser); } /** * Logout user */ this.clearUserFromStorage(); this.markUserAsLoggedOut(); } /** * Serialize toJSON for JSON.stringify */ toJSON() { return { isLoggedIn: this.isLoggedIn, isGuest: this.isGuest, viaRemember: this.viaRemember, authenticationAttempted: this.authenticationAttempted, isAuthenticated: this.isAuthenticated, user: this.user, }; } } exports.SessionGuard = SessionGuard;