auth-vir 2.7.2 → 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,11 +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
- if (!isSignUpCookie) {
150
- authLog('auth-vir: getSecureUser failed - could not extract user from request');
151
- }
152
141
  return undefined;
153
142
  }
154
143
  const user = await this.getDatabaseUser({
@@ -157,9 +146,6 @@ export class BackendAuthClient {
157
146
  isSignUpCookie,
158
147
  });
159
148
  if (!user) {
160
- authLog('auth-vir: getSecureUser failed - user not found in database', {
161
- userId: userIdResult.userId,
162
- });
163
149
  return undefined;
164
150
  }
165
151
  const assumedUser = await this.getAssumedUser({
@@ -184,7 +170,7 @@ export class BackendAuthClient {
184
170
  const rawJwtKeys = await this.config.getJwtKeys();
185
171
  const cacheKey = JSON.stringify(rawJwtKeys);
186
172
  const cachedParsedKeys = this.cachedParsedJwtKeys[cacheKey];
187
- const parsedKeys = cachedParsedKeys ?? (await parseJwtKeys(rawJwtKeys));
173
+ const parsedKeys = cachedParsedKeys || (await parseJwtKeys(rawJwtKeys));
188
174
  if (!cachedParsedKeys) {
189
175
  this.cachedParsedJwtKeys = { [cacheKey]: parsedKeys };
190
176
  }
@@ -193,25 +179,22 @@ export class BackendAuthClient {
193
179
  audience: 'server-context',
194
180
  issuer: 'server-auth',
195
181
  jwtDuration: this.config.userSessionIdleTimeout || defaultSessionIdleTimeout,
182
+ allowedClockSkew: this.config.allowedClockSkew || defaultAllowedClockSkew,
196
183
  };
197
184
  }
198
185
  /** Use these headers to log out the user. */
199
186
  async createLogoutHeaders(params) {
200
- authLog('auth-vir: LOGOUT - BackendAuthClient.createLogoutHeaders called', {
201
- allCookies: 'allCookies' in params ? params.allCookies : undefined,
202
- isSignUpCookie: 'isSignUpCookie' in params ? params.isSignUpCookie : undefined,
203
- }, new Error().stack);
204
187
  const signUpCookieHeaders = params.allCookies || params.isSignUpCookie
205
188
  ? generateLogoutHeaders(await this.getCookieParams({
206
189
  isSignUpCookie: true,
207
190
  requestHeaders: undefined,
208
- }), this.config.overrides)
191
+ }), this.config.csrf)
209
192
  : undefined;
210
193
  const authCookieHeaders = params.allCookies || !params.isSignUpCookie
211
194
  ? generateLogoutHeaders(await this.getCookieParams({
212
195
  isSignUpCookie: false,
213
196
  requestHeaders: undefined,
214
- }), this.config.overrides)
197
+ }), this.config.csrf)
215
198
  : undefined;
216
199
  const setCookieHeader = {
217
200
  'set-cookie': mergeHeaderValues(signUpCookieHeaders?.['set-cookie'], authCookieHeaders?.['set-cookie']),
@@ -233,14 +216,14 @@ export class BackendAuthClient {
233
216
  ? generateLogoutHeaders(await this.getCookieParams({
234
217
  isSignUpCookie: !isSignUpCookie,
235
218
  requestHeaders,
236
- }), this.config.overrides)
219
+ }), this.config.csrf)
237
220
  : undefined;
238
- 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);
239
222
  const sessionStartedAt = existingUserIdResult?.sessionStartedAt;
240
223
  const newCookieHeaders = await generateSuccessfulLoginHeaders(userId, await this.getCookieParams({
241
224
  isSignUpCookie,
242
225
  requestHeaders,
243
- }), this.config.overrides, sessionStartedAt);
226
+ }), this.config.csrf, sessionStartedAt);
244
227
  return {
245
228
  ...newCookieHeaders,
246
229
  'set-cookie': mergeHeaderValues(newCookieHeaders['set-cookie'], discardOppositeCookieHeaders?.['set-cookie']),
@@ -270,7 +253,6 @@ export class BackendAuthClient {
270
253
  // eslint-disable-next-line @typescript-eslint/no-deprecated
271
254
  const userIdResult = await insecureExtractUserIdFromCookieAlone(requestHeaders, await this.getJwtParams(), AuthCookieName.Auth);
272
255
  if (!userIdResult) {
273
- authLog('auth-vir: getInsecureUser failed - could not extract user from request');
274
256
  return undefined;
275
257
  }
276
258
  const user = await this.getDatabaseUser({
@@ -279,9 +261,6 @@ export class BackendAuthClient {
279
261
  assumingUser: undefined,
280
262
  });
281
263
  if (!user) {
282
- authLog('auth-vir: getInsecureUser failed - user not found in database', {
283
- userId: userIdResult.userId,
284
- });
285
264
  return undefined;
286
265
  }
287
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
  /**