auth-vir 2.7.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -81,10 +81,19 @@ import {
81
81
  parseJwtKeys,
82
82
  type CookieParams,
83
83
  type CreateJwtParams,
84
+ type CsrfHeaderNameOption,
84
85
  } from 'auth-vir';
85
86
 
86
87
  type MyUserId = string;
87
88
 
89
+ /**
90
+ * The CSRF header prefix for this app. Either `csrfHeaderPrefix` or `csrfHeaderName` must be
91
+ * provided to all CSRF-related functions.
92
+ */
93
+ const csrfOption: CsrfHeaderNameOption = {
94
+ csrfHeaderPrefix: 'my-app',
95
+ };
96
+
88
97
  /**
89
98
  * Use this for a /login endpoint.
90
99
  *
@@ -105,7 +114,7 @@ export async function handleLogin(
105
114
  throw new Error('Credentials mismatch.');
106
115
  }
107
116
 
108
- const authHeaders = await generateSuccessfulLoginHeaders(user.id, cookieParams);
117
+ const authHeaders = await generateSuccessfulLoginHeaders(user.id, cookieParams, csrfOption);
109
118
  response.setHeaders(new Headers(authHeaders));
110
119
  }
111
120
 
@@ -121,7 +130,7 @@ export async function createUser(
121
130
  ) {
122
131
  const newUser = await createUserInDatabase(userRequestData);
123
132
 
124
- const authHeaders = await generateSuccessfulLoginHeaders(newUser.id, cookieParams);
133
+ const authHeaders = await generateSuccessfulLoginHeaders(newUser.id, cookieParams, csrfOption);
125
134
  response.setHeaders(new Headers(authHeaders));
126
135
  }
127
136
 
@@ -132,7 +141,7 @@ export async function createUser(
132
141
  */
133
142
  export async function getAuthenticatedUser(request: ClientRequest) {
134
143
  const userId = (
135
- await extractUserIdFromRequestHeaders<MyUserId>(request.getHeaders(), jwtParams)
144
+ await extractUserIdFromRequestHeaders<MyUserId>(request.getHeaders(), jwtParams, csrfOption)
136
145
  )?.userId;
137
146
  const user = userId ? findUserInDatabaseById(userId) : undefined;
138
147
 
@@ -252,15 +261,28 @@ Here's a full example of how to use all the client / frontend side auth function
252
261
 
253
262
  ```TypeScript
254
263
  import {HttpStatus} from '@augment-vir/common';
255
- import {AuthHeaderName} from '../headers.js';
256
- import {getCurrentCsrfToken, handleAuthResponse, wipeCurrentCsrfToken} from 'auth-vir';
264
+ import {
265
+ type CsrfHeaderNameOption,
266
+ getCurrentCsrfToken,
267
+ handleAuthResponse,
268
+ resolveCsrfHeaderName,
269
+ wipeCurrentCsrfToken,
270
+ } from 'auth-vir';
271
+
272
+ /**
273
+ * The CSRF header prefix for this app. Either `csrfHeaderPrefix` or `csrfHeaderName` must be
274
+ * provided to all CSRF-related functions.
275
+ */
276
+ const csrfOption: CsrfHeaderNameOption = {
277
+ csrfHeaderPrefix: 'my-app',
278
+ };
257
279
 
258
280
  /** Call this when the user logs in for the first time this session. */
259
281
  export async function sendLoginRequest(
260
282
  userLoginData: {username: string; password: string},
261
283
  loginUrl: string,
262
284
  ) {
263
- if (getCurrentCsrfToken().csrfToken) {
285
+ if (getCurrentCsrfToken(csrfOption).csrfToken) {
264
286
  throw new Error('Already logged in.');
265
287
  }
266
288
 
@@ -270,7 +292,7 @@ export async function sendLoginRequest(
270
292
  credentials: 'include',
271
293
  });
272
294
 
273
- handleAuthResponse(response);
295
+ handleAuthResponse(response, csrfOption);
274
296
 
275
297
  return response;
276
298
  }
@@ -281,7 +303,7 @@ export async function sendAuthenticatedRequest(
281
303
  requestInit: Omit<RequestInit, 'headers'> = {},
282
304
  headers: Record<string, string> = {},
283
305
  ) {
284
- const {csrfToken} = getCurrentCsrfToken();
306
+ const {csrfToken} = getCurrentCsrfToken(csrfOption);
285
307
 
286
308
  if (!csrfToken) {
287
309
  throw new Error('Not authenticated.');
@@ -292,7 +314,7 @@ export async function sendAuthenticatedRequest(
292
314
  credentials: 'include',
293
315
  headers: {
294
316
  ...headers,
295
- [AuthHeaderName.CsrfToken]: csrfToken.token,
317
+ [resolveCsrfHeaderName(csrfOption)]: csrfToken.token,
296
318
  },
297
319
  });
298
320
 
@@ -302,7 +324,7 @@ export async function sendAuthenticatedRequest(
302
324
  * another tab.)
303
325
  */
304
326
  if (response.status === HttpStatus.Unauthorized) {
305
- wipeCurrentCsrfToken();
327
+ wipeCurrentCsrfToken(csrfOption);
306
328
  throw new Error(`User no longer logged in.`);
307
329
  } else {
308
330
  return response;
@@ -311,7 +333,7 @@ export async function sendAuthenticatedRequest(
311
333
 
312
334
  /** Call this when the user explicitly clicks a "log out" button. */
313
335
  export function logout() {
314
- wipeCurrentCsrfToken();
336
+ wipeCurrentCsrfToken(csrfOption);
315
337
  }
316
338
  ```
317
339
 
@@ -4,9 +4,9 @@ import { type IncomingHttpHeaders, type OutgoingHttpHeaders } from 'node:http';
4
4
  import { type EmptyObject, type RequireExactlyOne, type RequireOneOrNone } from 'type-fest';
5
5
  import { type UserIdResult } from '../auth.js';
6
6
  import { type CookieParams } from '../cookie.js';
7
- import { AuthHeaderName } from '../headers.js';
7
+ import { type CsrfHeaderNameOption } from '../csrf-token.js';
8
8
  import { type JwtKeys, type RawJwtKeys } from '../jwt/jwt-keys.js';
9
- import { type CreateJwtParams } from '../jwt/jwt.js';
9
+ import { type CreateJwtParams, type ParseJwtParams } from '../jwt/jwt.js';
10
10
  /**
11
11
  * Output from `BackendAuthClient.getSecureUser()`.
12
12
  *
@@ -31,7 +31,8 @@ export type GetUserResult<DatabaseUser extends AnyObject> = {
31
31
  *
32
32
  * @category Internal
33
33
  */
34
- export type BackendAuthClientConfig<DatabaseUser extends AnyObject, UserId extends string | number, AssumedUserParams extends JsonCompatibleObject = EmptyObject, CsrfHeaderName extends string = AuthHeaderName.CsrfToken> = Readonly<{
34
+ export type BackendAuthClientConfig<DatabaseUser extends AnyObject, UserId extends string | number, AssumedUserParams extends JsonCompatibleObject = EmptyObject> = Readonly<{
35
+ csrf: Readonly<CsrfHeaderNameOption>;
35
36
  /** The origin of your backend that is offering auth cookies. */
36
37
  serviceOrigin: string;
37
38
  /** Finds the relevant user from your own database. */
@@ -58,6 +59,11 @@ export type BackendAuthClientConfig<DatabaseUser extends AnyObject, UserId exten
58
59
  */
59
60
  isDev: boolean;
60
61
  } & PartialWithUndefined<{
62
+ /**
63
+ * Overwrite the header name used for tracking is an admin is assuming the identity of
64
+ * another user.
65
+ */
66
+ assumedUserHeaderName: string;
61
67
  /**
62
68
  * Optionally generate a service origin from request headers. The generated origin is used
63
69
  * for set-cookie headers.
@@ -107,30 +113,33 @@ export type BackendAuthClientConfig<DatabaseUser extends AnyObject, UserId exten
107
113
  *
108
114
  * @default {minutes: 2}
109
115
  */
110
- sessionRefreshTimeout: Readonly<AnyDuration>;
116
+ sessionRefreshStartTime: Readonly<AnyDuration>;
111
117
  /**
112
118
  * The maximum duration a session can last, regardless of activity. After this time, the
113
119
  * user will be logged out even if they are actively using the application.
114
120
  *
115
- * @default {weeks: 2}
121
+ * @default {days: 1.5}
116
122
  */
117
123
  maxSessionDuration: Readonly<AnyDuration>;
118
- overrides: PartialWithUndefined<{
119
- csrfHeaderName: CsrfHeaderName;
120
- assumedUserHeaderName: string;
121
- }>;
124
+ /**
125
+ * Allowed clock skew tolerance for JWT and CSRF token expiration checks. Accounts for
126
+ * differences between server and client clocks.
127
+ *
128
+ * @default {minutes: 5}
129
+ */
130
+ allowedClockSkew: Readonly<AnyDuration>;
122
131
  }>>;
123
132
  /**
124
133
  * An auth client for creating and validating JWTs embedded in cookies. This should only be used in
125
134
  * a backend environment as it accesses native Node packages.
126
135
  *
127
136
  * @category Auth : Host
128
- * @category Client
137
+ * @category Clients
129
138
  */
130
- export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId extends string | number, AssumedUserParams extends AnyObject = EmptyObject, CsrfHeaderName extends string = AuthHeaderName.CsrfToken> {
131
- protected readonly config: BackendAuthClientConfig<DatabaseUser, UserId, AssumedUserParams, CsrfHeaderName>;
139
+ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId extends string | number, AssumedUserParams extends AnyObject = EmptyObject> {
140
+ protected readonly config: BackendAuthClientConfig<DatabaseUser, UserId, AssumedUserParams>;
132
141
  protected cachedParsedJwtKeys: Record<string, Readonly<JwtKeys>>;
133
- constructor(config: BackendAuthClientConfig<DatabaseUser, UserId, AssumedUserParams, CsrfHeaderName>);
142
+ constructor(config: BackendAuthClientConfig<DatabaseUser, UserId, AssumedUserParams>);
134
143
  /** Get all the parameters used for cookie generation. */
135
144
  protected getCookieParams({ isSignUpCookie, requestHeaders, }: {
136
145
  /**
@@ -173,7 +182,7 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
173
182
  * Get all the JWT params used when creating the auth cookie, in case you need them for
174
183
  * something else too.
175
184
  */
176
- getJwtParams(): Promise<Readonly<CreateJwtParams>>;
185
+ getJwtParams(): Promise<Readonly<CreateJwtParams> & ParseJwtParams>;
177
186
  /** Use these headers to log out the user. */
178
187
  createLogoutHeaders(params: Readonly<RequireExactlyOne<{
179
188
  allCookies: true;
@@ -181,7 +190,7 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
181
190
  }> & {
182
191
  /** Overrides the client's already established `serviceOrigin`. */
183
192
  serviceOrigin?: string | undefined;
184
- }>): Promise<Partial<Record<CsrfHeaderName, string>> & {
193
+ }>): Promise<Record<string, string | string[]> & {
185
194
  'set-cookie': string[];
186
195
  }>;
187
196
  /** Use these headers to log a user in. */
@@ -1,25 +1,26 @@
1
1
  import { ensureArray, } from '@augment-vir/common';
2
- import { calculateRelativeDate, createUtcFullDate, getNowInUtcTimezone, isDateAfter, negateDuration, } from 'date-vir';
2
+ import { calculateRelativeDate, createUtcFullDate, getNowInUtcTimezone, isDateAfter, } from 'date-vir';
3
3
  import { extractUserIdFromRequestHeaders, generateLogoutHeaders, generateSuccessfulLoginHeaders, insecureExtractUserIdFromCookieAlone, } from '../auth.js';
4
- import { AuthCookieName } from '../cookie.js';
4
+ import { AuthCookieName, generateAuthCookie } from '../cookie.js';
5
+ import { defaultAllowedClockSkew, resolveCsrfHeaderName, } from '../csrf-token.js';
5
6
  import { AuthHeaderName, mergeHeaderValues } from '../headers.js';
6
7
  import { parseJwtKeys } from '../jwt/jwt-keys.js';
7
- import { authLog } from '../log.js';
8
+ import { isSessionRefreshReady } from './is-session-refresh-ready.js';
8
9
  const defaultSessionIdleTimeout = {
9
10
  minutes: 20,
10
11
  };
11
- const defaultSessionRefreshTimeout = {
12
+ const defaultSessionRefreshStartTime = {
12
13
  minutes: 2,
13
14
  };
14
15
  const defaultMaxSessionDuration = {
15
- weeks: 2,
16
+ days: 1.5,
16
17
  };
17
18
  /**
18
19
  * An auth client for creating and validating JWTs embedded in cookies. This should only be used in
19
20
  * a backend environment as it accesses native Node packages.
20
21
  *
21
22
  * @category Auth : Host
22
- * @category Client
23
+ * @category Clients
23
24
  */
24
25
  export class BackendAuthClient {
25
26
  config;
@@ -58,16 +59,13 @@ export class BackendAuthClient {
58
59
  /** Creates a `'cookie-set'` header to refresh the user's session cookie. */
59
60
  async createCookieRefreshHeaders({ userIdResult, requestHeaders, }) {
60
61
  const now = getNowInUtcTimezone();
61
- /** Double check that the JWT hasn't already expired. */
62
+ const clockSkew = this.config.allowedClockSkew || defaultAllowedClockSkew;
63
+ /** Double check that the JWT hasn't already expired (with clock skew tolerance). */
62
64
  const isExpiredAlready = isDateAfter({
63
65
  fullDate: now,
64
- relativeTo: userIdResult.jwtExpiration,
66
+ relativeTo: calculateRelativeDate(userIdResult.jwtExpiration, clockSkew),
65
67
  });
66
68
  if (isExpiredAlready) {
67
- authLog('auth-vir: SESSION EXPIRED - JWT already expired, user will be logged out', {
68
- userId: userIdResult.userId,
69
- jwtExpiration: userIdResult.jwtExpiration,
70
- });
71
69
  return undefined;
72
70
  }
73
71
  /**
@@ -83,40 +81,34 @@ export class BackendAuthClient {
83
81
  relativeTo: maxSessionEndDate,
84
82
  });
85
83
  if (isSessionExpired) {
86
- authLog('auth-vir: SESSION EXPIRED - max session duration exceeded, user will be logged out', {
87
- userId: userIdResult.userId,
88
- sessionStartedAt: userIdResult.sessionStartedAt,
89
- maxSessionDuration,
90
- });
91
84
  return undefined;
92
85
  }
93
86
  }
94
- /**
95
- * This check performs the following: the current time + the refresh threshold > JWT
96
- * expiration.
97
- *
98
- * Visually, this check looks like this:
99
- *
100
- * X C=======Y=======R Z
101
- *
102
- * - C = current time
103
- * - R = C + refresh threshold
104
- * - `=` = the time frame in which {@link isRefreshReady} = true.
105
- * - X = JWT expiration that has already expired (rejected by {@link isExpiredAlready}.
106
- * - Y = JWT expiration within the refresh threshold: {@link isRefreshReady} = true.
107
- * - Z = JWT expiration outside the refresh threshold: {@link isRefreshReady} = false.
108
- */
109
- const sessionRefreshTimeout = this.config.sessionRefreshTimeout || defaultSessionRefreshTimeout;
110
- const isRefreshReady = isDateAfter({
111
- fullDate: now,
112
- relativeTo: calculateRelativeDate(userIdResult.jwtExpiration, negateDuration(sessionRefreshTimeout)),
87
+ const sessionRefreshStartTime = this.config.sessionRefreshStartTime || defaultSessionRefreshStartTime;
88
+ const isRefreshReady = isSessionRefreshReady({
89
+ now,
90
+ jwtIssuedAt: userIdResult.jwtIssuedAt,
91
+ sessionRefreshStartTime,
113
92
  });
114
93
  if (isRefreshReady) {
115
- return this.createLoginHeaders({
94
+ const isSignUpCookie = userIdResult.cookieName === AuthCookieName.SignUp;
95
+ const cookieParams = await this.getCookieParams({
96
+ isSignUpCookie,
116
97
  requestHeaders,
117
- userId: userIdResult.userId,
118
- isSignUpCookie: userIdResult.cookieName === AuthCookieName.SignUp,
119
98
  });
99
+ const csrfHeaderName = resolveCsrfHeaderName(this.config.csrf);
100
+ const { cookie, expiration } = await generateAuthCookie({
101
+ csrfToken: userIdResult.csrfToken,
102
+ userId: userIdResult.userId,
103
+ sessionStartedAt: userIdResult.sessionStartedAt || Date.now(),
104
+ }, cookieParams);
105
+ return {
106
+ 'set-cookie': cookie,
107
+ [csrfHeaderName]: JSON.stringify({
108
+ token: userIdResult.csrfToken,
109
+ expiration,
110
+ }),
111
+ };
120
112
  }
121
113
  else {
122
114
  return undefined;
@@ -127,7 +119,7 @@ export class BackendAuthClient {
127
119
  if (!this.config.assumeUser || !(await this.config.assumeUser.canAssumeUser(user))) {
128
120
  return undefined;
129
121
  }
130
- const assumedUserHeader = ensureArray(headers[this.config.overrides?.assumedUserHeaderName || AuthHeaderName.AssumedUser])[0];
122
+ const assumedUserHeader = ensureArray(headers[this.config.assumedUserHeaderName || AuthHeaderName.AssumedUser])[0];
131
123
  if (!assumedUserHeader) {
132
124
  return undefined;
133
125
  }
@@ -144,9 +136,8 @@ export class BackendAuthClient {
144
136
  }
145
137
  /** Securely extract a user from their request headers. */
146
138
  async getSecureUser({ requestHeaders, isSignUpCookie, allowUserAuthRefresh, }) {
147
- const userIdResult = await extractUserIdFromRequestHeaders(requestHeaders, await this.getJwtParams(), isSignUpCookie ? AuthCookieName.SignUp : AuthCookieName.Auth, this.config.overrides);
139
+ const userIdResult = await extractUserIdFromRequestHeaders(requestHeaders, await this.getJwtParams(), this.config.csrf, isSignUpCookie ? AuthCookieName.SignUp : AuthCookieName.Auth);
148
140
  if (!userIdResult) {
149
- authLog('auth-vir: getSecureUser failed - could not extract user from request');
150
141
  return undefined;
151
142
  }
152
143
  const user = await this.getDatabaseUser({
@@ -155,9 +146,6 @@ export class BackendAuthClient {
155
146
  isSignUpCookie,
156
147
  });
157
148
  if (!user) {
158
- authLog('auth-vir: getSecureUser failed - user not found in database', {
159
- userId: userIdResult.userId,
160
- });
161
149
  return undefined;
162
150
  }
163
151
  const assumedUser = await this.getAssumedUser({
@@ -182,7 +170,7 @@ export class BackendAuthClient {
182
170
  const rawJwtKeys = await this.config.getJwtKeys();
183
171
  const cacheKey = JSON.stringify(rawJwtKeys);
184
172
  const cachedParsedKeys = this.cachedParsedJwtKeys[cacheKey];
185
- const parsedKeys = cachedParsedKeys ?? (await parseJwtKeys(rawJwtKeys));
173
+ const parsedKeys = cachedParsedKeys || (await parseJwtKeys(rawJwtKeys));
186
174
  if (!cachedParsedKeys) {
187
175
  this.cachedParsedJwtKeys = { [cacheKey]: parsedKeys };
188
176
  }
@@ -191,25 +179,22 @@ export class BackendAuthClient {
191
179
  audience: 'server-context',
192
180
  issuer: 'server-auth',
193
181
  jwtDuration: this.config.userSessionIdleTimeout || defaultSessionIdleTimeout,
182
+ allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
194
183
  };
195
184
  }
196
185
  /** Use these headers to log out the user. */
197
186
  async createLogoutHeaders(params) {
198
- authLog('auth-vir: LOGOUT - BackendAuthClient.createLogoutHeaders called', {
199
- allCookies: 'allCookies' in params ? params.allCookies : undefined,
200
- isSignUpCookie: 'isSignUpCookie' in params ? params.isSignUpCookie : undefined,
201
- }, new Error().stack);
202
187
  const signUpCookieHeaders = params.allCookies || params.isSignUpCookie
203
188
  ? generateLogoutHeaders(await this.getCookieParams({
204
189
  isSignUpCookie: true,
205
190
  requestHeaders: undefined,
206
- }), this.config.overrides)
191
+ }), this.config.csrf)
207
192
  : undefined;
208
193
  const authCookieHeaders = params.allCookies || !params.isSignUpCookie
209
194
  ? generateLogoutHeaders(await this.getCookieParams({
210
195
  isSignUpCookie: false,
211
196
  requestHeaders: undefined,
212
- }), this.config.overrides)
197
+ }), this.config.csrf)
213
198
  : undefined;
214
199
  const setCookieHeader = {
215
200
  'set-cookie': mergeHeaderValues(signUpCookieHeaders?.['set-cookie'], authCookieHeaders?.['set-cookie']),
@@ -231,14 +216,14 @@ export class BackendAuthClient {
231
216
  ? generateLogoutHeaders(await this.getCookieParams({
232
217
  isSignUpCookie: !isSignUpCookie,
233
218
  requestHeaders,
234
- }), this.config.overrides)
219
+ }), this.config.csrf)
235
220
  : undefined;
236
- const existingUserIdResult = await extractUserIdFromRequestHeaders(requestHeaders, await this.getJwtParams(), isSignUpCookie ? AuthCookieName.SignUp : AuthCookieName.Auth, this.config.overrides);
221
+ const existingUserIdResult = await extractUserIdFromRequestHeaders(requestHeaders, await this.getJwtParams(), this.config.csrf, isSignUpCookie ? AuthCookieName.SignUp : AuthCookieName.Auth);
237
222
  const sessionStartedAt = existingUserIdResult?.sessionStartedAt;
238
223
  const newCookieHeaders = await generateSuccessfulLoginHeaders(userId, await this.getCookieParams({
239
224
  isSignUpCookie,
240
225
  requestHeaders,
241
- }), this.config.overrides, sessionStartedAt);
226
+ }), this.config.csrf, sessionStartedAt);
242
227
  return {
243
228
  ...newCookieHeaders,
244
229
  'set-cookie': mergeHeaderValues(newCookieHeaders['set-cookie'], discardOppositeCookieHeaders?.['set-cookie']),
@@ -268,7 +253,6 @@ export class BackendAuthClient {
268
253
  // eslint-disable-next-line @typescript-eslint/no-deprecated
269
254
  const userIdResult = await insecureExtractUserIdFromCookieAlone(requestHeaders, await this.getJwtParams(), AuthCookieName.Auth);
270
255
  if (!userIdResult) {
271
- authLog('auth-vir: getInsecureUser failed - could not extract user from request');
272
256
  return undefined;
273
257
  }
274
258
  const user = await this.getDatabaseUser({
@@ -277,9 +261,6 @@ export class BackendAuthClient {
277
261
  assumingUser: undefined,
278
262
  });
279
263
  if (!user) {
280
- authLog('auth-vir: getInsecureUser failed - user not found in database', {
281
- userId: userIdResult.userId,
282
- });
283
264
  return undefined;
284
265
  }
285
266
  const refreshHeaders = allowUserAuthRefresh &&
@@ -1,12 +1,15 @@
1
1
  import { type createBlockingInterval, type JsonCompatibleObject, type MaybePromise, type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
2
2
  import { type AnyDuration } from 'date-vir';
3
3
  import { type EmptyObject } from 'type-fest';
4
+ import { type CsrfHeaderNameOption } from '../csrf-token.js';
4
5
  /**
5
6
  * Config for {@link FrontendAuthClient}.
6
7
  *
7
8
  * @category Internal
8
9
  */
9
- export type FrontendAuthClientConfig = PartialWithUndefined<{
10
+ export type FrontendAuthClientConfig = Readonly<{
11
+ csrf: Readonly<CsrfHeaderNameOption>;
12
+ }> & PartialWithUndefined<{
10
13
  /**
11
14
  * Determine if the current user can assume the identity of another user. If this is not
12
15
  * defined, all users will be blocked from assuming other user identities.
@@ -15,8 +18,8 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
15
18
  /** Called whenever the current user becomes unauthorized and their CSRF token is wiped. */
16
19
  authClearedCallback: () => MaybePromise<void>;
17
20
  /**
18
- * Performs automatic checks on an interval to see if the user is still authenticated. Omit this
19
- * to turn off automatic checks.
21
+ * Performs automatic checks on an interval to see if the user is still authenticated. Omit
22
+ * this to turn off automatic checks.
20
23
  */
21
24
  checkUser: {
22
25
  /**
@@ -27,8 +30,8 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
27
30
  * If the user is not currently authorized, this should return `undefined` to prevent
28
31
  * unnecessary network traffic.
29
32
  *
30
- * This will be called any time the user interacts with the page, debounced by the adjacent
31
- * `debounce` property.
33
+ * This will be called any time the user interacts with the page, debounced by the
34
+ * adjacent `debounce` property.
32
35
  */
33
36
  performCheck: () => MaybePromise<SelectFrom<Response, {
34
37
  status: true;
@@ -40,10 +43,20 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
40
43
  */
41
44
  debounce?: AnyDuration | undefined;
42
45
  };
46
+ /**
47
+ * Overwrite the header name used for tracking is an admin is assuming the identity of
48
+ * another user.
49
+ */
50
+ assumedUserHeaderName: string;
51
+ /**
52
+ * Allowed clock skew tolerance for CSRF token expiration checks. Accounts for differences
53
+ * between server and client clocks.
54
+ *
55
+ * @default {minutes: 5}
56
+ */
57
+ allowedClockSkew: Readonly<AnyDuration>;
43
58
  overrides: PartialWithUndefined<{
44
59
  localStorage: Pick<Storage, 'setItem' | 'removeItem' | 'getItem'>;
45
- csrfHeaderName: string;
46
- assumedUserHeaderName: string;
47
60
  }>;
48
61
  }>;
49
62
  /**
@@ -51,21 +64,21 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
51
64
  * in a frontend environment as it accesses native browser APIs.
52
65
  *
53
66
  * @category Auth : Client
54
- * @category Client
67
+ * @category Clients
55
68
  */
56
69
  export declare class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject = EmptyObject> {
57
70
  protected readonly config: FrontendAuthClientConfig;
58
71
  protected userCheckInterval: undefined | ReturnType<typeof createBlockingInterval>;
59
72
  /** Used to clean up the activity listener on `.destroy()`. */
60
73
  protected removeActivityListener: VoidFunction | undefined;
61
- constructor(config?: FrontendAuthClientConfig);
74
+ constructor(config: FrontendAuthClientConfig);
62
75
  /**
63
76
  * Destroys the client and performs all necessary cleanup (like clearing the user check
64
77
  * interval).
65
78
  */
66
79
  destroy(): void;
67
80
  /** Wraps {@link getCurrentCsrfToken} to automatically handle wiping an invalid CSRF token. */
68
- getCurrentCsrfToken(): Promise<string | undefined>;
81
+ getCurrentCsrfToken(): string | undefined;
69
82
  /**
70
83
  * Assume the given user. Pass `undefined` to wipe the currently assumed user.
71
84
  *
@@ -80,7 +93,7 @@ export declare class FrontendAuthClient<AssumedUserParams extends JsonCompatible
80
93
  * `@augment-vir/common`](https://electrovir.github.io/augment-vir/functions/mergeDeep.html) to
81
94
  * combine them with these.
82
95
  */
83
- createAuthenticatedRequestInit(): Promise<RequestInit>;
96
+ createAuthenticatedRequestInit(): RequestInit;
84
97
  /** Wipes the current user auth. */
85
98
  logout(): Promise<void>;
86
99
  /**