UNPKG

@adonisjs/auth

Version:

Official authentication provider for Adonis framework

1,104 lines (1,103 loc) 32.1 kB
import { n as E_UNAUTHORIZED_ACCESS } from "../../errors-eDV8ejO0.js"; import "../../symbols-C5QEqFvJ.js"; import { RuntimeException } from "@adonisjs/core/exceptions"; import { inspect } from "node:util"; import { createHash } from "node:crypto"; import string from "@adonisjs/core/helpers/string"; import { Secret, base64, safeEqual } from "@adonisjs/core/helpers"; //#region modules/session_guard/guard.ts /** * Session guard uses AdonisJS session store to track logged-in * user information. * * @template UseRememberTokens - Whether the guard supports remember me tokens * @template UserProvider - The user provider contract * * @example * const guard = new SessionGuard( * 'web', * ctx, * { useRememberMeTokens: true }, * emitter, * userProvider * ) * * const user = await guard.authenticate() * console.log('Authenticated user:', user.email) */ var SessionGuard = class { /** * A unique name for the guard. */ #name; /** * Reference to the current HTTP context */ #ctx; /** * Options accepted by the session guard */ #options; /** * Provider to lookup user details */ #userProvider; /** * Emitter to emit events */ #emitter; /** * Driver name of the guard */ driverName = "session"; /** * Whether or not the authentication has been attempted * during the current request. */ authenticationAttempted = false; /** * A boolean to know if a remember me token was used in attempt * to login a user. */ attemptedViaRemember = false; /** * A boolean to know if the current request has * been authenticated */ isAuthenticated = false; /** * A boolean to know if the current request is authenticated * using the "rememember_me" token. */ viaRemember = false; /** * Find if the user has been logged out during * the current request */ isLoggedOut = false; /** * Reference to an instance of the authenticated user. * The value only exists after calling one of the * following methods. * * - authenticate * - check * * You can use the "getUserOrFail" method to throw an exception if * the request is not authenticated. */ user; /** * The key used to store the logged-in user id inside * session * * @example * console.log('Session key:', guard.sessionKeyName) // 'auth_web' */ get sessionKeyName() { return `auth_${this.#name}`; } /** * The key used to store the remember me token cookie * * @example * console.log('Remember me key:', guard.rememberMeKeyName) // 'remember_web' */ get rememberMeKeyName() { return `remember_${this.#name}`; } /** * Creates a new SessionGuard instance * * @param name - Unique name for the guard instance * @param ctx - HTTP context for the current request * @param options - Configuration options for the session guard * @param emitter - Event emitter for guard events * @param userProvider - User provider for authentication * * @example * const guard = new SessionGuard( * 'web', * ctx, * { useRememberMeTokens: true, rememberMeTokensAge: '30d' }, * emitter, * userProvider * ) */ constructor(name, ctx, options, emitter, userProvider) { this.#name = name; this.#ctx = ctx; this.#options = { rememberMeTokensAge: "2 years", ...options }; this.#emitter = emitter; this.#userProvider = userProvider; } /** * Returns the session instance for the given request, * ensuring the property exists */ #getSession() { if (!("session" in this.#ctx)) throw new RuntimeException("Cannot authenticate user. Install and configure \"@adonisjs/session\" package"); return this.#ctx.session; } /** * Emits authentication failure, updates the local state, * and returns an exception to end the authentication * cycle. */ #authenticationFailed(sessionId) { this.isAuthenticated = false; this.viaRemember = false; this.user = void 0; this.isLoggedOut = false; const error = new E_UNAUTHORIZED_ACCESS("Invalid or expired user session", { guardDriverName: this.driverName }); this.#emitter.emit("session_auth:authentication_failed", { ctx: this.#ctx, guardName: this.#name, error, sessionId }); return error; } /** * Emits the authentication succeeded event and updates * the local state to reflect successful authentication */ #authenticationSucceeded(sessionId, user, rememberMeToken) { this.isAuthenticated = true; this.viaRemember = !!rememberMeToken; this.user = user; this.isLoggedOut = false; this.#emitter.emit("session_auth:authentication_succeeded", { ctx: this.#ctx, guardName: this.#name, sessionId, user, rememberMeToken }); } /** * Emits the login succeeded event and updates the login * state */ #loginSucceeded(sessionId, user, rememberMeToken) { this.user = user; this.isLoggedOut = false; this.#emitter.emit("session_auth:login_succeeded", { ctx: this.#ctx, guardName: this.#name, sessionId, user, rememberMeToken }); } /** * Creates session for a given user by their user id. */ #createSessionForUser(userId) { const session = this.#getSession(); session.put(this.sessionKeyName, userId); session.regenerate(); } /** * Creates the remember me cookie */ #createRememberMeCookie(value) { this.#ctx.response.encryptedCookie(this.rememberMeKeyName, value.release(), { maxAge: this.#options.rememberMeTokensAge, httpOnly: true }); } /** * Authenticates the user using its id read from the session * store. * * - We check the user exists in the db * - If not, throw exception. * - Otherwise, update local state to mark the user as logged-in */ async #authenticateViaId(userId, sessionId) { const providerUser = await this.#userProvider.findById(userId); if (!providerUser) throw this.#authenticationFailed(sessionId); this.#authenticationSucceeded(sessionId, providerUser.getOriginal()); return this.user; } /** * Authenticates user from the remember me cookie. Creates a fresh * session for them and recycles the remember me token as well. */ async #authenticateViaRememberCookie(rememberMeCookie, sessionId) { /** * This method is only invoked when "options.useRememberTokens" is set to * true and hence the user provider will have methods to manage tokens */ const userProvider = this.#userProvider; /** * Verify the token using the user provider. */ const token = await userProvider.verifyRememberToken(new Secret(rememberMeCookie)); if (!token) throw this.#authenticationFailed(sessionId); /** * Check if a user for the token exists. Otherwise abort * authentication */ const providerUser = await userProvider.findById(token.tokenableId); if (!providerUser) throw this.#authenticationFailed(sessionId); /** * Recycle remember token and the remember me cookie */ const recycledToken = await userProvider.recycleRememberToken(providerUser.getOriginal(), token.identifier, this.#options.rememberMeTokensAge); /** * Persist remember token inside the cookie */ this.#createRememberMeCookie(recycledToken.value); /** * Create session */ this.#createSessionForUser(providerUser.getId()); /** * Emit event and update local state */ this.#authenticationSucceeded(sessionId, providerUser.getOriginal(), token); return this.user; } /** * Returns an instance of the authenticated user. Or throws * an exception if the request is not authenticated. * * @throws {E_UNAUTHORIZED_ACCESS} When user is not authenticated * * @example * const user = guard.getUserOrFail() * console.log('User:', user.email) */ getUserOrFail() { if (!this.user) throw new E_UNAUTHORIZED_ACCESS("Invalid or expired user session", { guardDriverName: this.driverName }); return this.user; } /** * Login user using sessions. Optionally, you can also create * a remember me token to automatically login user when their * session expires. * * @param user - The user to login * @param remember - Whether to create a remember me token * * @example * await guard.login(user, true) * console.log('User logged in with remember me token') */ async login(user, remember = false) { const session = this.#getSession(); const providerUser = await this.#userProvider.createUserForGuard(user); this.#emitter.emit("session_auth:login_attempted", { ctx: this.#ctx, user, guardName: this.#name }); /** * Create remember me token and persist it with the provider * when remember me token is true. */ let token; if (remember) { if (!this.#options.useRememberMeTokens) throw new RuntimeException("Cannot use \"rememberMe\" feature. It has been disabled"); token = await this.#userProvider.createRememberToken(providerUser.getOriginal(), this.#options.rememberMeTokensAge); } /** * Persist remember token inside the cookie (if exists) * Otherwise remove the cookie */ if (token) this.#createRememberMeCookie(token.value); else this.#ctx.response.clearCookie(this.rememberMeKeyName); /** * Create session */ this.#createSessionForUser(providerUser.getId()); /** * Mark user as logged-in */ this.#loginSucceeded(session.sessionId, providerUser.getOriginal(), token); } /** * Logout a user by removing its state from the session * store and delete the remember me cookie (if any). * * @example * await guard.logout() * console.log('User logged out successfully') */ async logout() { const session = this.#getSession(); const rememberMeCookie = this.#ctx.request.encryptedCookie(this.rememberMeKeyName); /** * Clear client side state */ session.forget(this.sessionKeyName); this.#ctx.response.clearCookie(this.rememberMeKeyName); /** * Delete remember me token when * * - Tokens are enabled * - A cookie exists * - And we know about the user already */ if (this.user && rememberMeCookie && this.#options.useRememberMeTokens) { /** * Here we assume the userProvider has implemented APIs to manage remember * me tokens, since the "useRememberMeTokens" flag is enabled. */ const userProvider = this.#userProvider; const token = await userProvider.verifyRememberToken(new Secret(rememberMeCookie)); if (token) await userProvider.deleteRemeberToken(this.user, token.identifier); } /** * Notify the user has been logged out */ this.#emitter.emit("session_auth:logged_out", { ctx: this.#ctx, guardName: this.#name, user: this.user || null, sessionId: session.sessionId }); /** * Update local state */ this.user = void 0; this.viaRemember = false; this.isAuthenticated = false; this.isLoggedOut = true; } /** * Authenticate the current HTTP request by verifying the session * or remember me token and fails with an exception if authentication fails * * @throws {E_UNAUTHORIZED_ACCESS} When authentication fails * * @example * try { * const user = await guard.authenticate() * console.log('Authenticated as:', user.email) * } catch (error) { * console.log('Authentication failed') * } */ async authenticate() { /** * Return early when authentication has already been * attempted */ if (this.authenticationAttempted) return this.getUserOrFail(); /** * Notify we begin to attempt the authentication */ this.authenticationAttempted = true; const session = this.#getSession(); this.#emitter.emit("session_auth:authentication_attempted", { ctx: this.#ctx, sessionId: session.sessionId, guardName: this.#name }); /** * Check if there is a user id inside the session store. * If yes, fetch the user from the persistent storage * and mark them as logged-in */ const authUserId = session.get(this.sessionKeyName); if (authUserId) return this.#authenticateViaId(authUserId, session.sessionId); /** * If user provider supports remember me tokens and the remember me * cookie exists, then attempt to login + authenticate via * the remember me token. */ const rememberMeCookie = this.#ctx.request.encryptedCookie(this.rememberMeKeyName); if (rememberMeCookie && this.#options.useRememberMeTokens) { this.attemptedViaRemember = true; return this.#authenticateViaRememberCookie(rememberMeCookie, session.sessionId); } /** * Otherwise throw an exception */ throw this.#authenticationFailed(session.sessionId); } /** * Silently check if the user is authenticated or not, without * throwing any exceptions * * @example * const isAuthenticated = await guard.check() * if (isAuthenticated) { * console.log('User is authenticated:', guard.user.email) * } */ async check() { try { await this.authenticate(); return true; } catch (error) { if (error instanceof E_UNAUTHORIZED_ACCESS) return false; throw error; } } /** * Returns the session info for the clients to send during * an HTTP request to mark the user as logged-in. * * @param user - The user to authenticate as * * @example * const clientAuth = await guard.authenticateAsClient(user) * // Use clientAuth.session in API tests */ async authenticateAsClient(user) { const userId = (await this.#userProvider.createUserForGuard(user)).getId(); return { session: { [this.sessionKeyName]: userId } }; } }; //#endregion //#region modules/session_guard/remember_me_token.ts /** * Remember me token represents an opaque token that can be * used to automatically login a user without asking them * to re-login * * @example * const token = new RememberMeToken({ * identifier: 1, * tokenableId: 123, * hash: 'sha256hash', * createdAt: new Date(), * updatedAt: new Date(), * expiresAt: new Date(Date.now() + 86400000) * }) */ var RememberMeToken = class { /** * Decodes a publicly shared token and return the series * and the token value from it. * * Returns null when unable to decode the token because of * invalid format or encoding. * * @param value - The token value to decode * * @example * const decoded = RememberMeToken.decode('abc123.def456') * if (decoded) { * console.log('Token ID:', decoded.identifier) * console.log('Secret:', decoded.secret.release()) * } */ static decode(value) { /** * Ensure value is a string and starts with the prefix. */ if (typeof value !== "string") return null; /** * Remove prefix from the rest of the token. */ if (!value) return null; const [identifier, ...tokenValue] = value.split("."); if (!identifier || tokenValue.length === 0) return null; const decodedIdentifier = base64.urlDecode(identifier); const decodedSecret = base64.urlDecode(tokenValue.join(".")); if (!decodedIdentifier || !decodedSecret) return null; return { identifier: decodedIdentifier, secret: new Secret(decodedSecret) }; } /** * Creates a transient token that can be shared with the persistence * layer. * * @param userId - The ID of the user for whom the token is created * @param size - The size of the random secret to generate * @param expiresIn - Expiration time (seconds or duration string) * * @example * const transientToken = RememberMeToken.createTransientToken(123, 32, '30d') * // Store transientToken in database */ static createTransientToken(userId, size, expiresIn) { const expiresAt = /* @__PURE__ */ new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + string.seconds.parse(expiresIn)); return { userId, expiresAt, ...this.seed(size) }; } /** * Creates a secret opaque token and its hash. * * @param size - The size of the random string to generate * * @example * const { secret, hash } = RememberMeToken.seed(32) * console.log('Secret:', secret.release()) * console.log('Hash:', hash) */ static seed(size) { const secret = new Secret(string.random(size)); return { secret, hash: createHash("sha256").update(secret.release()).digest("hex") }; } /** * Identifer is a unique sequence to identify the * token within database. It should be the * primary/unique key */ identifier; /** * Reference to the user id for whom the token * is generated. */ tokenableId; /** * The value is a public representation of a token. It is created * by combining the "identifier"."secret" */ value; /** * Hash is computed from the seed to later verify the validity * of seed */ hash; /** * Date/time when the token instance was created */ createdAt; /** * Date/time when the token was updated */ updatedAt; /** * Timestamp at which the token will expire */ expiresAt; /** * Creates a new RememberMeToken instance * * @param attributes - Token attributes including identifier, user ID, hash, etc. * * @example * const token = new RememberMeToken({ * identifier: 1, * tokenableId: 123, * hash: 'sha256hash', * createdAt: new Date(), * updatedAt: new Date(), * expiresAt: new Date(Date.now() + 86400000) * }) */ constructor(attributes) { this.identifier = attributes.identifier; this.tokenableId = attributes.tokenableId; this.hash = attributes.hash; this.createdAt = attributes.createdAt; this.updatedAt = attributes.updatedAt; this.expiresAt = attributes.expiresAt; /** * Compute value when secret is provided */ if (attributes.secret) this.value = new Secret(`${base64.urlEncode(String(this.identifier))}.${base64.urlEncode(attributes.secret.release())}`); } /** * Check if the token has been expired. Verifies * the "expiresAt" timestamp with the current * date. * * @example * if (token.isExpired()) { * console.log('Remember me token has expired') * } else { * console.log('Token is still valid') * } */ isExpired() { return this.expiresAt < /* @__PURE__ */ new Date(); } /** * Verifies the value of a token against the pre-defined hash * * @param secret - The secret to verify against the stored hash * * @example * const isValid = token.verify(new Secret('user-provided-secret')) * if (isValid) { * console.log('Remember me token is valid') * } */ verify(secret) { const newHash = createHash("sha256").update(secret.release()).digest("hex"); return safeEqual(this.hash, newHash); } }; //#endregion //#region modules/session_guard/token_providers/db.ts /** * DbRememberMeTokensProvider uses lucid database service to fetch and * persist tokens for a given user. * * The user must be an instance of the associated user model. * * @template TokenableModel - The Lucid model that can have remember me tokens * * @example * const provider = new DbRememberMeTokensProvider({ * tokenableModel: () => import('#models/user'), * table: 'remember_me_tokens', * tokenSecretLength: 32 * }) */ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider { /** * Create tokens provider instance for a given Lucid model * * @param model - The tokenable model factory function * @param options - Optional configuration options * * @example * const provider = DbRememberMeTokensProvider.forModel( * () => import('#models/user'), * { table: 'user_remember_tokens' } * ) */ static forModel(model, options) { return new DbRememberMeTokensProvider({ tokenableModel: model, ...options || {} }); } /** * Database table to use for querying remember me tokens */ table; /** * The length for the token secret. A secret is a cryptographically * secure random string. */ tokenSecretLength; /** * Creates a new DbRememberMeTokensProvider instance * * @param options - Configuration options for the provider * * @example * const provider = new DbRememberMeTokensProvider({ * tokenableModel: () => import('#models/user'), * table: 'remember_me_tokens', * tokenSecretLength: 40 * }) */ constructor(options) { this.options = options; this.table = options.table || "remember_me_tokens"; this.tokenSecretLength = options.tokenSecretLength || 40; } /** * Check if value is an object */ #isObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } /** * Ensure the provided user is an instance of the user model and * has a primary key */ #ensureIsPersisted(user) { const model = this.options.tokenableModel; if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`); if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for managing remember me tokens. The value of column "${model.primaryKey}" is undefined or null`); } /** * Maps a database row to an instance token instance * * @param dbRow - The database row containing token data * * @example * const token = provider.dbRowToRememberMeToken({ * id: 1, * tokenable_id: 123, * hash: 'sha256hash', * // ... other columns * }) */ dbRowToRememberMeToken(dbRow) { return new RememberMeToken({ identifier: dbRow.id, tokenableId: dbRow.tokenable_id, hash: dbRow.hash, createdAt: typeof dbRow.created_at === "number" ? new Date(dbRow.created_at) : dbRow.created_at, updatedAt: typeof dbRow.updated_at === "number" ? new Date(dbRow.updated_at) : dbRow.updated_at, expiresAt: typeof dbRow.expires_at === "number" ? new Date(dbRow.expires_at) : dbRow.expires_at }); } /** * Returns a query client instance from the parent model * * @example * const db = await provider.getDb() * const tokens = await db.from('remember_me_tokens').select('*') */ async getDb() { const model = this.options.tokenableModel; return model.$adapter.query(model).client; } /** * Create a token for a user * * @param user - The user instance to create a token for * @param expiresIn - Token expiration time * * @example * const token = await provider.create(user, '30d') * console.log('Remember token:', token.value.release()) */ async create(user, expiresIn) { this.#ensureIsPersisted(user); const queryClient = await this.getDb(); /** * Creating a transient token. Transient token abstracts * the logic of creating a random secure secret and its * hash */ const transientToken = RememberMeToken.createTransientToken(user.$primaryKeyValue, this.tokenSecretLength, expiresIn); /** * Row to insert inside the database. We expect exactly these * columns to exist. */ const dbRow = { tokenable_id: transientToken.userId, hash: transientToken.hash, created_at: /* @__PURE__ */ new Date(), updated_at: /* @__PURE__ */ new Date(), expires_at: transientToken.expiresAt }; /** * Insert data to the database. */ const result = await queryClient.table(this.table).insert(dbRow).returning("id"); const id = this.#isObject(result[0]) ? result[0].id : result[0]; /** * Throw error when unable to find id in the return value of * the insert query */ if (!id) throw new RuntimeException(`Cannot save access token. The result "${inspect(result)}" of insert query is unexpected`); /** * Convert db row to a remember token */ return new RememberMeToken({ identifier: id, tokenableId: dbRow.tokenable_id, secret: transientToken.secret, hash: dbRow.hash, createdAt: dbRow.created_at, updatedAt: dbRow.updated_at, expiresAt: dbRow.expires_at }); } /** * Find a token for a user by the token id * * @param user - The user instance that owns the token * @param identifier - The token identifier to search for * * @example * const token = await provider.find(user, 123) * if (token) { * console.log('Found token with id:', token.identifier) * } */ async find(user, identifier) { this.#ensureIsPersisted(user); const dbRow = await (await this.getDb()).query().from(this.table).where({ id: identifier, tokenable_id: user.$primaryKeyValue }).limit(1).first(); if (!dbRow) return null; return this.dbRowToRememberMeToken(dbRow); } /** * Delete a token by its id * * @param user - The user instance that owns the token * @param identifier - The token identifier to delete * * @example * const deletedCount = await provider.delete(user, 123) * console.log('Deleted tokens:', deletedCount) */ async delete(user, identifier) { this.#ensureIsPersisted(user); return await (await this.getDb()).query().from(this.table).where({ id: identifier, tokenable_id: user.$primaryKeyValue }).del().exec(); } /** * Returns all the tokens a given user * * @param user - The user instance to get tokens for * * @example * const tokens = await provider.all(user) * console.log('User has', tokens.length, 'remember tokens') * tokens.forEach(token => console.log(token.identifier)) */ async all(user) { this.#ensureIsPersisted(user); return (await (await this.getDb()).query().from(this.table).where({ tokenable_id: user.$primaryKeyValue }).orderBy("id", "desc").exec()).map((dbRow) => { return this.dbRowToRememberMeToken(dbRow); }); } /** * Verifies a publicly shared remember me token and returns an * RememberMeToken for it. * * Returns null when unable to verify the token or find it * inside the storage * * @param tokenValue - The token value to verify * * @example * const token = await provider.verify(new Secret('rmt_abc123.def456')) * if (token && !token.isExpired()) { * console.log('Valid remember token for user:', token.tokenableId) * } */ async verify(tokenValue) { const decodedToken = RememberMeToken.decode(tokenValue.release()); if (!decodedToken) return null; const dbRow = await (await this.getDb()).query().from(this.table).where({ id: decodedToken.identifier }).limit(1).first(); if (!dbRow) return null; /** * Convert to remember me token instance */ const rememberMeToken = this.dbRowToRememberMeToken(dbRow); /** * Ensure the token secret matches the token hash */ if (!rememberMeToken.verify(decodedToken.secret) || rememberMeToken.isExpired()) return null; return rememberMeToken; } /** * Recycles a remember me token by deleting the old one and * creates a new one. * * Ideally, the recycle should update the existing token, but we * skip that for now and come back to it later and handle race * conditions as well. * * @param user - The user that owns the token * @param identifier - The token identifier to recycle * @param expiresIn - New expiration time * * @example * const newToken = await provider.recycle(user, 123, '30d') * console.log('Recycled token:', newToken.value.release()) */ async recycle(user, identifier, expiresIn) { await this.delete(user, identifier); return this.create(user, expiresIn); } }; //#endregion //#region modules/session_guard/user_providers/lucid.ts /** * Uses a lucid model to verify access tokens and find a user during * authentication * * @template UserModel - The Lucid model representing the user * * @example * const userProvider = new SessionLucidUserProvider({ * model: () => import('#models/user') * }) */ var SessionLucidUserProvider = class { /** * Reference to the lazily imported model */ model; /** * Creates a new SessionLucidUserProvider instance * * @param options - Configuration options for the user provider * * @example * const provider = new SessionLucidUserProvider({ * model: () => import('#models/user') * }) */ constructor(options) { this.options = options; } /** * Imports the model from the provider, returns and caches it * for further operations. * * @example * const UserModel = await provider.getModel() * const user = await UserModel.find(1) */ async getModel() { if (this.model && !("hot" in import.meta)) return this.model; this.model = (await this.options.model()).default; return this.model; } /** * Returns the tokens provider associated with the user model * * @example * const tokensProvider = await provider.getTokensProvider() * const token = await tokensProvider.create(user, '7d') */ async getTokensProvider() { const model = await this.getModel(); if (!model.rememberMeTokens) throw new RuntimeException(`Cannot use "${model.name}" model for verifying remember me tokens. Make sure to assign a token provider to the model.`); return model.rememberMeTokens; } /** * Creates an adapter user for the guard * * @param user - The user model instance * * @example * const guardUser = await provider.createUserForGuard(user) * console.log('User ID:', guardUser.getId()) * console.log('Original user:', guardUser.getOriginal()) */ async createUserForGuard(user) { const model = await this.getModel(); if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`); return { getId() { /** * Ensure user has a primary key */ if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for authentication. The value of column "${model.primaryKey}" is undefined or null`); return user.$primaryKeyValue; }, getOriginal() { return user; } }; } /** * Finds a user by their primary key value * * @param identifier - The user identifier to search for * * @example * const guardUser = await provider.findById(123) * if (guardUser) { * const originalUser = guardUser.getOriginal() * console.log('Found user:', originalUser.email) * } */ async findById(identifier) { const user = await (await this.getModel()).find(identifier); if (!user) return null; return this.createUserForGuard(user); } /** * Creates a remember token for a given user * * @param user - The user to create a token for * @param expiresIn - Token expiration time * * @example * const token = await provider.createRememberToken(user, '30d') * console.log('Remember token:', token.value.release()) */ async createRememberToken(user, expiresIn) { return (await this.getTokensProvider()).create(user, expiresIn); } /** * Verify a token by its publicly shared value * * @param tokenValue - The token value to verify * * @example * const token = await provider.verifyRememberToken( * new Secret('rmt_abc123.def456') * ) * if (token && !token.isExpired()) { * console.log('Valid remember token for user:', token.tokenableId) * } */ async verifyRememberToken(tokenValue) { return (await this.getTokensProvider()).verify(tokenValue); } /** * Delete a token for a user by the token identifier * * @param user - The user that owns the token * @param identifier - The token identifier to delete * * @example * const deletedCount = await provider.deleteRemeberToken(user, 123) * console.log('Deleted tokens:', deletedCount) */ async deleteRemeberToken(user, identifier) { return (await this.getTokensProvider()).delete(user, identifier); } /** * Recycle a token for a user by the token identifier * * @param user - The user that owns the token * @param identifier - The token identifier to recycle * @param expiresIn - New expiration time * * @example * const newToken = await provider.recycleRememberToken(user, 123, '30d') * console.log('Recycled token:', newToken.value.release()) */ async recycleRememberToken(user, identifier, expiresIn) { return (await this.getTokensProvider()).recycle(user, identifier, expiresIn); } }; //#endregion //#region modules/session_guard/define_config.ts /** * Configures session guard for authentication using HTTP sessions * * @param config - Configuration object containing the user provider and session options * * @example * const guard = sessionGuard({ * useRememberMeTokens: false, * provider: sessionUserProvider({ * model: () => import('#models/user') * }) * }) */ function sessionGuard(config) { return { async resolver(name, app) { const emitter = await app.container.make("emitter"); const provider = "resolver" in config.provider ? await config.provider.resolver(app) : config.provider; return (ctx) => new SessionGuard(name, ctx, config, emitter, provider); } }; } /** * Configures user provider that uses Lucid models to authenticate * users using sessions * * @param config - Configuration options for the Lucid user provider * * @example * const userProvider = sessionUserProvider({ * model: () => import('#models/user') * }) */ function sessionUserProvider(config) { return new SessionLucidUserProvider(config); } //#endregion export { DbRememberMeTokensProvider, RememberMeToken, SessionGuard, SessionLucidUserProvider, sessionGuard, sessionUserProvider };