@adonisjs/auth
Version:
Official authentication provider for Adonis framework
1,104 lines (1,103 loc) • 32.1 kB
JavaScript
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 };