@adonisjs/auth
Version:
Official authentication provider for Adonis framework
154 lines (153 loc) • 5.82 kB
JavaScript
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 };