auth-vir
Version:
Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.
916 lines (843 loc) • 32.5 kB
text/typescript
import {
ensureArray,
type AnyObject,
type JsonCompatibleObject,
type MaybePromise,
type PartialWithUndefined,
} from '@augment-vir/common';
import {
calculateRelativeDate,
createUtcFullDate,
getNowInUtcTimezone,
isDateAfter,
type AnyDuration,
} from 'date-vir';
import {type IncomingHttpHeaders, type OutgoingHttpHeaders} from 'node:http';
import {type EmptyObject, type RequireExactlyOne, type RequireOneOrNone} from 'type-fest';
import {
extractUserIdFromRequestHeaders,
generateLogoutHeaders,
insecureExtractUserIdFromCookieAlone,
type UserIdResult,
} from '../auth.js';
import {
AuthCookie,
clearCsrfCookie,
generateAuthCookie,
generateCsrfCookie,
resolveCookieName,
type CookieParams,
} from '../cookie.js';
import {generateCsrfToken, type CsrfHeaderNameOption} from '../csrf-token.js';
import {AuthHeaderName, mergeHeaderValues} from '../headers.js';
import {generateNewJwtKeys, parseJwtKeys, type JwtKeys, type RawJwtKeys} from '../jwt/jwt-keys.js';
import {defaultAllowedClockSkew, type CreateJwtParams, type ParseJwtParams} from '../jwt/jwt.js';
import {isSessionRefreshReady} from './is-session-refresh-ready.js';
/**
* Output from `BackendAuthClient.getSecureUser()`.
*
* @category Internal
*/
export type GetUserResult<DatabaseUser extends AnyObject> = {
/** The retrieved user. */
user: DatabaseUser;
/**
* When `true`, indicates that the current `user` result is as assumed user. This can only be
* `true` if you've configured user assuming in `BackendAuthClient`.
*/
isAssumed: boolean;
/**
* This should be merged into your own response headers. It usually contains auth cookie
* duration refresh headers.
*/
responseHeaders: OutgoingHttpHeaders;
};
/**
* Config for {@link BackendAuthClient}.
*
* @category Internal
*/
export type BackendAuthClientConfig<
DatabaseUser extends AnyObject,
UserId extends string | number,
AssumedUserParams extends JsonCompatibleObject = EmptyObject,
> = Readonly<
{
csrf: Readonly<CsrfHeaderNameOption>;
/** The origin of your backend that is offering auth cookies. */
serviceOrigin: string;
/** Finds the relevant user from your own database. */
getUserFromDatabase: (userParams: {
/** The user id extracted from the request cookie. */
userId: UserId;
/** Indicates that we're loading the user from a sign up cookie. */
isSignUpCookie: boolean;
/**
* If this is set, we're attempting to load a database user for the purpose of assuming
* their user identity. Otherwise, this is `undefined`.
*/
assumingUser: AssumedUserParams | undefined;
requestHeaders: Readonly<IncomingHttpHeaders>;
}) => MaybePromise<DatabaseUser | undefined | null>;
/**
* Get JWT keys produced by {@link generateNewJwtKeys}. Make sure that each time this is
* called, the same JWT keys are returned (do not call {@link generateNewJwtKeys} each time
* this is called). Any time the JWT keys change, all current sessions will terminate.
*/
getJwtKeys: () => MaybePromise<Readonly<RawJwtKeys>>;
/**
* When `isDev` is set, cookies do not require HTTPS (so they can be used with
* http://localhost).
*/
isDev: boolean;
} & PartialWithUndefined<{
/** If this returns true, logging will be enabled while handling the relevant session. */
enableLogging(params: {
user: DatabaseUser | undefined;
userId: UserId | undefined;
assumedUserParams: AssumedUserParams | undefined;
}): boolean;
/**
* Overwrite the header name used for tracking is an admin is assuming the identity of
* another user.
*/
assumedUserHeaderName: string;
/**
* Optionally generate a service origin from request headers. The generated origin is used
* for set-cookie headers.
*/
generateServiceOrigin(params: {
requestHeaders: Readonly<IncomingHttpHeaders>;
}): MaybePromise<undefined | string>;
/** If provided, logs will be sent to this method. */
log?: (message: string, extraData: AnyObject) => void;
/**
* Set this to allow specific users (determined by `canAssumeUser`) to assume the identity
* of other users. This should only be used for admins so that they can troubleshoot user
* issues.
*
* @see {@link AuthHeaderName}
*/
assumeUser: {
/**
* Parse the assumed user header value.
*
* @see {@link AuthHeaderName}
*/
parseAssumedUserHeaderValue: (
/**
* The assumed user header value.
*
* @see {@link AuthHeaderName}
*/
data: string,
) => MaybePromise<
| {
assumedUserParams: AssumedUserParams;
userId: UserId;
}
| undefined
>;
/**
* Return `true` to allow the current/original user to assume identities of other users.
* Return `false` to block it. It is recommended to only return `true` for admin users.
*
* @see {@link AuthHeaderName}
*/
canAssumeUser: (originalUser: DatabaseUser) => MaybePromise<boolean>;
};
/**
* This determines how long a cookie will be valid until it needs to be refreshed.
*
* @default {minutes: 20}
*/
userSessionIdleTimeout: Readonly<AnyDuration>;
/**
* How long into a user's session when we should start trying to refresh their session.
*
* @default {minutes: 2}
*/
sessionRefreshStartTime: Readonly<AnyDuration>;
/**
* The maximum duration a session can last, regardless of activity. After this time, the
* user will be logged out even if they are actively using the application.
*
* @default {days: 1.5}
*/
maxSessionDuration: Readonly<AnyDuration>;
/**
* Allowed clock skew tolerance for JWT and CSRF token expiration checks. Accounts for
* differences between server and client clocks.
*
* @default {minutes: 5}
*/
allowedClockSkew: Readonly<AnyDuration>;
/**
* Optional separate origin for the CSRF cookie's `Domain` attribute. When set, the
* non-`HttpOnly` CSRF cookie will use this origin's hostname instead of `serviceOrigin`.
*
* This is useful when the backend and frontend live on different subdomains that don't
* share a common parent narrower than the top-level domain. The `HttpOnly` auth cookie
* stays scoped to `serviceOrigin` (protecting it from unrelated subdomains), while the CSRF
* cookie uses the broader domain so frontend JavaScript can read it via `document.cookie`.
*
* The CSRF token alone is not a security risk — it is only meaningful when paired with the
* JWT embedded in the `HttpOnly` auth cookie.
*/
csrfCookieOrigin: string;
/**
* Optional suffix appended to cookie names (e.g., `'staging'` produces `auth-staging`,
* `auth-vir-csrf-staging`). When `undefined`, cookie names are unchanged. Useful for
* running multiple environments on the same domain without cookie collisions.
*/
cookieNameSuffix: string;
}>
>;
const defaultSessionIdleTimeout: Readonly<AnyDuration> = {
minutes: 20,
};
const defaultSessionRefreshStartTime: Readonly<AnyDuration> = {
minutes: 2,
};
const defaultMaxSessionDuration: Readonly<AnyDuration> = {
days: 1.5,
};
/**
* An auth client for creating and validating JWTs embedded in cookies. This should only be used in
* a backend environment as it accesses native Node packages.
*
* @category Auth : Host
* @category Clients
*/
export class BackendAuthClient<
DatabaseUser extends AnyObject,
UserId extends string | number,
AssumedUserParams extends AnyObject = EmptyObject,
> {
protected cachedParsedJwtKeys: Record<string, Readonly<JwtKeys>> = {};
constructor(
protected readonly config: BackendAuthClientConfig<DatabaseUser, UserId, AssumedUserParams>,
) {}
/**
* Resolves the origin to use for CSRF cookie generation. Returns `csrfCookieOrigin` if
* configured, otherwise falls back to the auth cookie origin.
*/
protected resolveCsrfCookieOrigin(authCookieOrigin: string): string {
return this.config.csrfCookieOrigin || authCookieOrigin;
}
/** Conditionally logs a message if logging is enabled for the given user context. */
protected logForUser(
params: {
user: DatabaseUser | undefined;
userId: UserId | undefined;
assumedUserParams: AssumedUserParams | undefined;
},
message: string,
extra?: Record<string, unknown>,
): void {
if (this.config.enableLogging?.(params)) {
const extraData = {
userId: params.userId,
...extra,
};
if (this.config.log) {
this.config.log(message, extraData);
} else {
console.info(`[auth-vir] ${message}`, extraData);
}
}
}
/** Get all the parameters used for cookie generation. */
protected async getCookieParams({
isSignUpCookie,
requestHeaders,
}: {
/**
* Set this to `true` when we are setting the initial cookie right after a user signs up.
* This allows them to auto-authorize when they verify their email address.
*
* This should only be set to `true` when a new user is signing up.
*/
isSignUpCookie: boolean;
requestHeaders: Readonly<IncomingHttpHeaders> | undefined;
}): Promise<Readonly<CookieParams>> {
const serviceOrigin = requestHeaders
? await this.config.generateServiceOrigin?.({
requestHeaders,
})
: undefined;
return {
cookieDuration: this.config.userSessionIdleTimeout || defaultSessionIdleTimeout,
hostOrigin: serviceOrigin || this.config.serviceOrigin,
jwtParams: await this.getJwtParams(),
isDev: this.config.isDev,
authCookie: isSignUpCookie ? AuthCookie.SignUp : AuthCookie.Auth,
cookieNameSuffix: this.config.cookieNameSuffix,
};
}
/** Calls the provided `getUserFromDatabase` config. */
protected async getDatabaseUser({
isSignUpCookie,
userId,
assumingUser,
requestHeaders,
}: {
userId: UserId | undefined;
assumingUser: AssumedUserParams | undefined;
isSignUpCookie: boolean;
requestHeaders: IncomingHttpHeaders;
}): Promise<undefined | DatabaseUser> {
if (!userId) {
return undefined;
}
const authenticatedUser = await this.config.getUserFromDatabase({
assumingUser,
userId,
isSignUpCookie,
requestHeaders,
});
if (!authenticatedUser) {
this.logForUser(
{
user: undefined,
userId,
assumedUserParams: assumingUser,
},
'getUserFromDatabase returned no user',
{
isSignUpCookie,
},
);
return undefined;
}
return authenticatedUser;
}
/** Creates a `'cookie-set'` header to refresh the user's session cookie. */
protected async createCookieRefreshHeaders({
userIdResult,
requestHeaders,
}: {
userIdResult: Readonly<UserIdResult<UserId>>;
requestHeaders: IncomingHttpHeaders;
}): Promise<OutgoingHttpHeaders | undefined> {
const now = getNowInUtcTimezone();
const clockSkew = this.config.allowedClockSkew || defaultAllowedClockSkew;
/** Double check that the JWT hasn't already expired (with clock skew tolerance). */
const isExpiredAlready = isDateAfter({
fullDate: now,
relativeTo: calculateRelativeDate(userIdResult.jwtExpiration, clockSkew),
});
if (isExpiredAlready) {
this.logForUser(
{
user: undefined,
userId: userIdResult.userId,
assumedUserParams: undefined,
},
'Session refresh denied: JWT already expired (even with clock skew tolerance)',
{
jwtExpiration: userIdResult.jwtExpiration,
now: JSON.stringify(now),
},
);
return undefined;
}
/**
* Check if the session has exceeded the max session duration. If so, don't refresh the
* session and let it expire naturally.
*/
const maxSessionDuration = this.config.maxSessionDuration || defaultMaxSessionDuration;
if (userIdResult.sessionStartedAt) {
const sessionStartDate = createUtcFullDate(userIdResult.sessionStartedAt);
const maxSessionEndDate = calculateRelativeDate(sessionStartDate, maxSessionDuration);
const isSessionExpired = isDateAfter({
fullDate: now,
relativeTo: maxSessionEndDate,
});
if (isSessionExpired) {
this.logForUser(
{
user: undefined,
userId: userIdResult.userId,
assumedUserParams: undefined,
},
'Session refresh denied: max session duration exceeded',
{
sessionStartedAt: userIdResult.sessionStartedAt,
maxSessionEndDate: JSON.stringify(maxSessionEndDate),
now: JSON.stringify(now),
},
);
return undefined;
}
}
const sessionRefreshStartTime =
this.config.sessionRefreshStartTime || defaultSessionRefreshStartTime;
const isRefreshReady = isSessionRefreshReady({
now,
jwtIssuedAt: userIdResult.jwtIssuedAt,
sessionRefreshStartTime,
});
if (isRefreshReady) {
const isSignUpCookie = userIdResult.cookieName === AuthCookie.SignUp;
const cookieParams = await this.getCookieParams({
isSignUpCookie,
requestHeaders,
});
const authCookie = await generateAuthCookie(
{
csrfToken: userIdResult.csrfToken,
userId: userIdResult.userId,
sessionStartedAt: userIdResult.sessionStartedAt || Date.now(),
},
cookieParams,
);
const csrfCookie = generateCsrfCookie(userIdResult.csrfToken, {
...cookieParams,
hostOrigin: this.resolveCsrfCookieOrigin(cookieParams.hostOrigin),
cookieNameSuffix: this.config.cookieNameSuffix,
});
return {
'set-cookie': [
authCookie,
csrfCookie,
],
};
} else {
this.logForUser(
{
user: undefined,
userId: userIdResult.userId,
assumedUserParams: undefined,
},
'Session refresh skipped: not yet ready for refresh',
{
jwtIssuedAt: userIdResult.jwtIssuedAt,
sessionRefreshStartTime,
},
);
return undefined;
}
}
/** Reads the user's assumed user headers and, if configured, gets the assumed user. */
protected async getAssumedUser({
requestHeaders,
user,
}: {
user: DatabaseUser;
requestHeaders: IncomingHttpHeaders;
}): Promise<DatabaseUser | undefined> {
if (!this.config.assumeUser || !(await this.config.assumeUser.canAssumeUser(user))) {
return undefined;
}
const assumedUserHeader: string | undefined = ensureArray(
requestHeaders[this.config.assumedUserHeaderName || AuthHeaderName.AssumedUser],
)[0];
if (!assumedUserHeader) {
return undefined;
}
const parsedAssumedUserData =
await this.config.assumeUser.parseAssumedUserHeaderValue(assumedUserHeader);
if (!parsedAssumedUserData || !parsedAssumedUserData.userId) {
return undefined;
}
const assumedUser = await this.getDatabaseUser({
isSignUpCookie: false,
userId: parsedAssumedUserData.userId,
assumingUser: parsedAssumedUserData.assumedUserParams,
requestHeaders,
});
return assumedUser;
}
/** Securely extract a user from their request headers. */
public async getSecureUser({
requestHeaders,
isSignUpCookie,
allowUserAuthRefresh,
}: {
requestHeaders: IncomingHttpHeaders;
isSignUpCookie: boolean;
/**
* If true, this method will generate headers to refresh the user's auth session. This
* should likely only be done with a specific endpoint, like whatever endpoint you trigger
* with the frontend auth client's `checkUser.performCheck` callback.
*/
allowUserAuthRefresh: boolean;
}): Promise<GetUserResult<DatabaseUser> | undefined> {
const userIdResult = await extractUserIdFromRequestHeaders<UserId>({
headers: requestHeaders,
jwtParams: await this.getJwtParams(),
csrfHeaderNameOption: this.config.csrf,
cookieName: isSignUpCookie ? AuthCookie.SignUp : AuthCookie.Auth,
cookieNameSuffix: this.config.cookieNameSuffix,
});
if (!userIdResult) {
this.logForUser(
{
user: undefined,
userId: undefined,
assumedUserParams: undefined,
},
'getSecureUser: failed to extract user ID from request headers (invalid JWT, missing cookie, or CSRF mismatch)',
{
isSignUpCookie,
},
);
return undefined;
}
const user = await this.getDatabaseUser({
userId: userIdResult.userId,
assumingUser: undefined,
isSignUpCookie,
requestHeaders,
});
if (!user) {
this.logForUser(
{
user: undefined,
userId: userIdResult.userId,
assumedUserParams: undefined,
},
'getSecureUser: user not found in database',
{
isSignUpCookie,
},
);
return undefined;
}
const assumedUser = await this.getAssumedUser({
requestHeaders,
user,
});
const cookieRefreshHeaders = allowUserAuthRefresh
? await this.createCookieRefreshHeaders({
userIdResult,
requestHeaders,
})
: undefined;
/**
* Always include the CSRF cookie so it gets re-established if the browser clears it. When
* session refresh fires, its headers already include a CSRF cookie.
*/
const authCookieOrigin =
(await this.config.generateServiceOrigin?.({
requestHeaders,
})) || this.config.serviceOrigin;
const csrfCookie = generateCsrfCookie(userIdResult.csrfToken, {
hostOrigin: this.resolveCsrfCookieOrigin(authCookieOrigin),
isDev: this.config.isDev,
cookieNameSuffix: this.config.cookieNameSuffix,
});
return {
user: assumedUser || user,
isAssumed: !!assumedUser,
responseHeaders: {
'set-cookie': mergeHeaderValues(cookieRefreshHeaders?.['set-cookie'], csrfCookie),
},
};
}
/**
* Get all the JWT params used when creating the auth cookie, in case you need them for
* something else too.
*/
public async getJwtParams(): Promise<Readonly<CreateJwtParams> & ParseJwtParams> {
const rawJwtKeys = await this.config.getJwtKeys();
const cacheKey = JSON.stringify(rawJwtKeys);
const cachedParsedKeys = this.cachedParsedJwtKeys[cacheKey];
const parsedKeys = cachedParsedKeys || (await parseJwtKeys(rawJwtKeys));
if (!cachedParsedKeys) {
this.cachedParsedJwtKeys = {
[cacheKey]: parsedKeys,
};
}
return {
jwtKeys: parsedKeys,
audience: 'server-context',
issuer: 'server-auth',
jwtDuration: this.config.userSessionIdleTimeout || defaultSessionIdleTimeout,
allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
};
}
/** Use these headers to log out the user. */
public async createLogoutHeaders(
params: Readonly<
RequireExactlyOne<{
allCookies: true;
isSignUpCookie: boolean;
}> & {
/** Overrides the client's already established `serviceOrigin`. */
serviceOrigin?: string | undefined;
}
>,
): Promise<
Record<string, string | string[]> & {
'set-cookie': string[];
}
> {
const clearingAllCookies = !!params.allCookies;
const signUpCookieHeaders =
params.allCookies || params.isSignUpCookie
? generateLogoutHeaders(
await this.getCookieParams({
isSignUpCookie: true,
requestHeaders: undefined,
}),
{
preserveCsrf: !clearingAllCookies,
},
)
: undefined;
const authCookieHeaders =
params.allCookies || !params.isSignUpCookie
? generateLogoutHeaders(
await this.getCookieParams({
isSignUpCookie: false,
requestHeaders: undefined,
}),
{
preserveCsrf: !clearingAllCookies,
},
)
: undefined;
/**
* When `csrfCookieOrigin` is configured, the CSRF cookie lives on a broader domain than the
* auth cookie. Clear it on that broader domain too so stale tokens don't linger.
*/
const broadCsrfClearCookie =
clearingAllCookies && this.config.csrfCookieOrigin
? clearCsrfCookie({
hostOrigin: this.config.csrfCookieOrigin,
isDev: this.config.isDev,
cookieNameSuffix: this.config.cookieNameSuffix,
})
: undefined;
return {
'set-cookie': mergeHeaderValues(
signUpCookieHeaders?.['set-cookie'],
authCookieHeaders?.['set-cookie'],
broadCsrfClearCookie,
),
};
}
/**
* Refreshes a login session by reissuing the auth cookie with the same CSRF token instead of
* generating a new one.
*/
protected async refreshLoginHeaders({
userId,
cookieParams,
existingUserIdResult,
}: {
userId: UserId;
cookieParams: Readonly<CookieParams>;
existingUserIdResult: Readonly<UserIdResult<UserId>>;
}): Promise<Record<string, string | string[]>> {
const authCookie = await generateAuthCookie(
{
csrfToken: existingUserIdResult.csrfToken,
userId,
sessionStartedAt: existingUserIdResult.sessionStartedAt,
},
cookieParams,
);
const csrfCookie = generateCsrfCookie(existingUserIdResult.csrfToken, {
...cookieParams,
hostOrigin: this.resolveCsrfCookieOrigin(cookieParams.hostOrigin),
cookieNameSuffix: this.config.cookieNameSuffix,
});
return {
'set-cookie': [
authCookie,
csrfCookie,
],
};
}
/** Generates login headers for a brand-new session (no existing JWT to reuse). */
protected async generateFreshLoginHeaders(
userId: UserId,
cookieParams: Readonly<CookieParams>,
): Promise<Record<string, string[]>> {
const csrfToken = generateCsrfToken();
const authCookie = await generateAuthCookie(
{
csrfToken,
userId,
sessionStartedAt: Date.now(),
},
cookieParams,
);
const csrfCookie = generateCsrfCookie(csrfToken, {
...cookieParams,
hostOrigin: this.resolveCsrfCookieOrigin(cookieParams.hostOrigin),
cookieNameSuffix: this.config.cookieNameSuffix,
});
return {
'set-cookie': [
authCookie,
csrfCookie,
],
};
}
/** Use these headers to log a user in. */
public async createLoginHeaders({
userId,
requestHeaders,
isSignUpCookie,
}: {
userId: UserId;
requestHeaders: IncomingHttpHeaders;
isSignUpCookie: boolean;
}): Promise<OutgoingHttpHeaders> {
const oppositeCookieName = isSignUpCookie ? AuthCookie.Auth : AuthCookie.SignUp;
const resolvedOppositeCookieName = resolveCookieName(
oppositeCookieName,
this.config.cookieNameSuffix,
);
const hasExistingOppositeCookie = requestHeaders.cookie?.includes(
`${resolvedOppositeCookieName}=`,
);
const discardOppositeCookieHeaders = hasExistingOppositeCookie
? generateLogoutHeaders(
await this.getCookieParams({
isSignUpCookie: !isSignUpCookie,
requestHeaders,
}),
{
preserveCsrf: true,
},
)
: undefined;
const existingUserIdResult = await extractUserIdFromRequestHeaders<UserId>({
headers: requestHeaders,
jwtParams: await this.getJwtParams(),
csrfHeaderNameOption: this.config.csrf,
cookieName: isSignUpCookie ? AuthCookie.SignUp : AuthCookie.Auth,
cookieNameSuffix: this.config.cookieNameSuffix,
});
const cookieParams = await this.getCookieParams({
isSignUpCookie,
requestHeaders,
});
const newCookieHeaders = existingUserIdResult
? await this.refreshLoginHeaders({
userId,
cookieParams,
existingUserIdResult,
})
: await this.generateFreshLoginHeaders(userId, cookieParams);
return {
...newCookieHeaders,
'set-cookie': mergeHeaderValues(
newCookieHeaders['set-cookie'],
discardOppositeCookieHeaders?.['set-cookie'],
),
...(isSignUpCookie
? {
[AuthHeaderName.IsSignUpAuth]: 'true',
}
: {}),
};
}
/** Combines `.getInsecureUser()` and `.getSecureUser()` into one method. */
public async getInsecureOrSecureUser(params: {
requestHeaders: IncomingHttpHeaders;
isSignUpCookie: boolean;
/**
* If true, this method will generate headers to refresh the user's auth session. This
* should likely only be done with a specific endpoint, like whatever endpoint you trigger
* with the frontend auth client's `checkUser.performCheck` callback.
*/
allowUserAuthRefresh: boolean;
/** Overrides the client's already established `serviceOrigin`. */
serviceOrigin?: string | undefined;
}): Promise<
RequireOneOrNone<{
secureUser: GetUserResult<DatabaseUser>;
/**
* @deprecated This only half authenticates the user. It should only be used in
* circumstances where JavaScript cannot be used to attach the CSRF token header to
* the request (like when opening a PDF file). Use `.getSecureUser()` instead,
* whenever possible.
*/
insecureUser: GetUserResult<DatabaseUser>;
}>
> {
const secureUser = await this.getSecureUser(params);
if (secureUser) {
return {
secureUser,
};
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
const insecureUser = await this.getInsecureUser(params);
return insecureUser
? {
insecureUser,
}
: {};
}
/**
* @deprecated This only half authenticates the user. It should only be used in circumstances
* where JavaScript cannot be used to attach the CSRF token header to the request (like when
* opening a PDF file). Use `.getSecureUser()` instead, whenever possible.
*/
public async getInsecureUser({
requestHeaders,
allowUserAuthRefresh,
}: {
requestHeaders: IncomingHttpHeaders;
/**
* If true, this method will generate headers to refresh the user's auth session. This
* should likely only be done with a specific endpoint, like whatever endpoint you trigger
* with the frontend auth client's `checkUser.performCheck` callback.
*/
allowUserAuthRefresh: boolean;
}): Promise<GetUserResult<DatabaseUser> | undefined> {
// eslint-disable-next-line @typescript-eslint/no-deprecated
const userIdResult = await insecureExtractUserIdFromCookieAlone<UserId>({
headers: requestHeaders,
jwtParams: await this.getJwtParams(),
cookieName: AuthCookie.Auth,
cookieNameSuffix: this.config.cookieNameSuffix,
});
if (!userIdResult) {
this.logForUser(
{
user: undefined,
userId: undefined,
assumedUserParams: undefined,
},
'getInsecureUser: failed to extract user ID from cookie (invalid JWT or missing cookie)',
);
return undefined;
}
const user = await this.getDatabaseUser({
isSignUpCookie: false,
userId: userIdResult.userId,
assumingUser: undefined,
requestHeaders,
});
if (!user) {
this.logForUser(
{
user: undefined,
userId: userIdResult.userId,
assumedUserParams: undefined,
},
'getInsecureUser: user not found in database',
);
return undefined;
}
const refreshHeaders =
allowUserAuthRefresh &&
(await this.createCookieRefreshHeaders({
userIdResult,
requestHeaders,
}));
return {
user,
isAssumed: false,
responseHeaders: refreshHeaders || {},
};
}
}