UNPKG

@adonisjs/auth

Version:

Official authentication provider for Adonis framework

154 lines (153 loc) 5.82 kB
import { t as E_INVALID_CREDENTIALS } from "../../errors-eDV8ejO0.js"; import { RuntimeException } from "@adonisjs/core/exceptions"; import { beforeSave } from "@adonisjs/lucid/orm"; //#region \0@oxc-project+runtime@0.122.0/helpers/decorate.js function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } //#endregion //#region src/mixins/lucid.ts /** * Mixing to add user lookup and password verification methods * on a model. * * Under the hood, this mixin defines following methods and hooks * * - beforeSave hook to hash user password * - findForAuth method to find a user during authentication * - verifyCredentials method to verify user credentials and prevent * timing attacks. * * @param hash - Function that returns a Hash instance for password hashing * @param options - Configuration options with uids and password column name * * @example * import { withAuthFinder } from '@adonisjs/auth/mixins/lucid' * * class User extends withAuthFinder(hash, { * uids: ['email', 'username'], * passwordColumnName: 'password' * })(BaseModel) { * // User model implementation * } */ function withAuthFinder(hash, options) { let normalizedOptions = { uids: ["email"], passwordColumnName: "password", ...options }; let hashFactory = typeof hash === "function" ? hash : () => hash.use(); return function(superclass) { class UserWithUserFinder extends superclass { /** * Hook to hash user password when creating or updating * the user model. * * @param user - The user instance being saved * * @example * // This hook runs automatically before saving * const user = new User() * user.password = 'plaintext' * await user.save() // password will be hashed automatically */ static async hashPassword(user) { if (user.$dirty[normalizedOptions.passwordColumnName]) user[normalizedOptions.passwordColumnName] = await hashFactory().make(user[normalizedOptions.passwordColumnName]); } /** * Finds the user for authentication via "verifyCredentials". * Feel free to override this method to customize the user * lookup behavior. * * @param uids - Array of column names to search in * @param value - The value to search for * * @example * const user = await User.findForAuth(['email', 'username'], 'john@example.com') */ static findForAuth(uids, value) { const query = this.query(); uids.forEach((uid) => query.orWhere(uid, value)); return query.limit(1).first(); } /** * Find a user by uid and verify their password. This method is * safe from timing attacks. * * @param uid - The user identifier (email, username, etc.) * @param password - The plain text password to verify * * @throws {E_INVALID_CREDENTIALS} When credentials are invalid * * @example * const user = await User.verifyCredentials('john@example.com', 'password123') * console.log('Authenticated user:', user.email) */ static async verifyCredentials(uid, password) { /** * Fail when uid or the password are missing */ if (!uid || !password) throw new E_INVALID_CREDENTIALS("Invalid user credentials"); const user = await this.findForAuth(normalizedOptions.uids, uid); if (!user) { await hashFactory().make(password); throw new E_INVALID_CREDENTIALS("Invalid user credentials"); } if (await user.verifyPassword(password)) return user; throw new E_INVALID_CREDENTIALS("Invalid user credentials"); } /** * Verifies the plain password against the user's password * hash * * @param plainPassword - The plain text password to verify * * @throws {RuntimeException} When password column value is undefined or null * * @example * const isValid = await user.verifyPassword('password123') * if (isValid) { * console.log('Password is correct') * } */ verifyPassword(plainPassword) { const passwordHash = this[normalizedOptions.passwordColumnName]; if (!passwordHash) throw new RuntimeException(`Cannot verify password. The value for "${normalizedOptions.passwordColumnName}" column is undefined or null`); return hashFactory().verify(passwordHash, plainPassword); } /** * Validates a plain password against the user's stored password hash. * Throws a validation error if the password doesn't match. * * @param plainPassword - The plain text password to validate * @param passwordFieldName - Optional field name for the error message (defaults to 'currentPassword') * * @throws {ValidationError} When the password is incorrect * * @example * await user.validatePassword('oldPassword123', 'currentPassword') */ async validatePassword(plainPassword, passwordFieldName) { if (!await this.verifyPassword(plainPassword)) { const error = /* @__PURE__ */ new Error("Validation Error"); Object.defineProperty(error, "code", { value: "E_VALIDATION_ERROR" }); Object.defineProperty(error, "status", { value: 422 }); Object.defineProperty(error, "messages", { value: [{ field: passwordFieldName ?? "currentPassword", message: "The current password is incorrect", rule: "current_password" }] }); throw error; } } } __decorate([beforeSave()], UserWithUserFinder, "hashPassword", null); return UserWithUserFinder; }; } //#endregion export { withAuthFinder };