auth-vir 2.4.2 → 2.5.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.
@@ -58,6 +58,13 @@ export type BackendAuthClientConfig<DatabaseUser extends AnyObject, UserId exten
58
58
  */
59
59
  isDev: boolean;
60
60
  } & PartialWithUndefined<{
61
+ /**
62
+ * Optionally generate a service origin from request headers. The generated origin is used
63
+ * for set-cookie headers.
64
+ */
65
+ generateServiceOrigin(params: {
66
+ requestHeaders: Readonly<IncomingHttpHeaders>;
67
+ }): MaybePromise<undefined | string>;
61
68
  /**
62
69
  * Set this to allow specific users (determined by `canAssumeUser`) to assume the identity
63
70
  * of other users. This should only be used for admins so that they can troubleshoot user
@@ -119,7 +126,7 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
119
126
  protected cachedParsedJwtKeys: Record<string, Readonly<JwtKeys>>;
120
127
  constructor(config: BackendAuthClientConfig<DatabaseUser, UserId, AssumedUserParams, CsrfHeaderName>);
121
128
  /** Get all the parameters used for cookie generation. */
122
- protected getCookieParams({ isSignUpCookie, serviceOrigin, }: {
129
+ protected getCookieParams({ isSignUpCookie, requestHeaders, }: {
123
130
  /**
124
131
  * Set this to `true` when we are setting the initial cookie right after a user signs up.
125
132
  * This allows them to auto-authorize when they verify their email address.
@@ -127,8 +134,7 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
127
134
  * This should only be set to `true` when a new user is signing up.
128
135
  */
129
136
  isSignUpCookie: boolean;
130
- /** Overrides the client's already established `serviceOrigin`. */
131
- serviceOrigin?: string | undefined;
137
+ requestHeaders: Readonly<IncomingHttpHeaders> | undefined;
132
138
  }): Promise<Readonly<CookieParams>>;
133
139
  /** Calls the provided `getUserFromDatabase` config. */
134
140
  protected getDatabaseUser({ isSignUpCookie, userId, assumingUser, }: {
@@ -137,10 +143,9 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
137
143
  isSignUpCookie: boolean;
138
144
  }): Promise<undefined | DatabaseUser>;
139
145
  /** Creates a `'cookie-set'` header to refresh the user's session cookie. */
140
- protected createCookieRefreshHeaders({ userIdResult, serviceOrigin, }: {
146
+ protected createCookieRefreshHeaders({ userIdResult, requestHeaders, }: {
141
147
  userIdResult: Readonly<UserIdResult<UserId>>;
142
- /** Overrides the client's already established `serviceOrigin`. */
143
- serviceOrigin?: string | undefined;
148
+ requestHeaders: IncomingHttpHeaders;
144
149
  }): Promise<OutgoingHttpHeaders | undefined>;
145
150
  /** Reads the user's assumed user headers and, if configured, gets the assumed user. */
146
151
  protected getAssumedUser({ headers, user, }: {
@@ -148,7 +153,7 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
148
153
  headers: IncomingHttpHeaders;
149
154
  }): Promise<DatabaseUser | undefined>;
150
155
  /** Securely extract a user from their request headers. */
151
- getSecureUser({ requestHeaders, isSignUpCookie, allowUserAuthRefresh, serviceOrigin, }: {
156
+ getSecureUser({ requestHeaders, isSignUpCookie, allowUserAuthRefresh, }: {
152
157
  requestHeaders: IncomingHttpHeaders;
153
158
  isSignUpCookie: boolean;
154
159
  /**
@@ -157,8 +162,6 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
157
162
  * with the frontend auth client's `checkUser.performCheck` callback.
158
163
  */
159
164
  allowUserAuthRefresh: boolean;
160
- /** Overrides the client's already established `serviceOrigin`. */
161
- serviceOrigin?: string | undefined;
162
165
  }): Promise<GetUserResult<DatabaseUser> | undefined>;
163
166
  /**
164
167
  * Get all the JWT params used when creating the auth cookie, in case you need them for
@@ -176,12 +179,10 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
176
179
  'set-cookie': string[];
177
180
  }>;
178
181
  /** Use these headers to log a user in. */
179
- createLoginHeaders({ userId, requestHeaders, isSignUpCookie, serviceOrigin, }: {
182
+ createLoginHeaders({ userId, requestHeaders, isSignUpCookie, }: {
180
183
  userId: UserId;
181
184
  requestHeaders: IncomingHttpHeaders;
182
185
  isSignUpCookie: boolean;
183
- /** Overrides the client's already established `serviceOrigin`. */
184
- serviceOrigin?: string | undefined;
185
186
  }): Promise<OutgoingHttpHeaders>;
186
187
  /** Combines `.getInsecureUser()` and `.getSecureUser()` into one method. */
187
188
  getInsecureOrSecureUser(params: {
@@ -210,7 +211,7 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
210
211
  * where JavaScript cannot be used to attach the CSRF token header to the request (like when
211
212
  * opening a PDF file). Use `.getSecureUser()` instead, whenever possible.
212
213
  */
213
- getInsecureUser({ requestHeaders, allowUserAuthRefresh, serviceOrigin, }: {
214
+ getInsecureUser({ requestHeaders, allowUserAuthRefresh, }: {
214
215
  requestHeaders: IncomingHttpHeaders;
215
216
  /**
216
217
  * If true, this method will generate headers to refresh the user's auth session. This
@@ -218,7 +219,5 @@ export declare class BackendAuthClient<DatabaseUser extends AnyObject, UserId ex
218
219
  * with the frontend auth client's `checkUser.performCheck` callback.
219
220
  */
220
221
  allowUserAuthRefresh: boolean;
221
- /** Overrides the client's already established `serviceOrigin`. */
222
- serviceOrigin?: string | undefined;
223
222
  }): Promise<GetUserResult<DatabaseUser> | undefined>;
224
223
  }
@@ -24,7 +24,10 @@ export class BackendAuthClient {
24
24
  this.config = config;
25
25
  }
26
26
  /** Get all the parameters used for cookie generation. */
27
- async getCookieParams({ isSignUpCookie, serviceOrigin, }) {
27
+ async getCookieParams({ isSignUpCookie, requestHeaders, }) {
28
+ const serviceOrigin = requestHeaders
29
+ ? await this.config.generateServiceOrigin?.({ requestHeaders })
30
+ : undefined;
28
31
  return {
29
32
  cookieDuration: this.config.userSessionIdleTimeout || defaultSessionIdleTimeout,
30
33
  hostOrigin: serviceOrigin || this.config.serviceOrigin,
@@ -49,7 +52,7 @@ export class BackendAuthClient {
49
52
  return authenticatedUser;
50
53
  }
51
54
  /** Creates a `'cookie-set'` header to refresh the user's session cookie. */
52
- async createCookieRefreshHeaders({ userIdResult, serviceOrigin, }) {
55
+ async createCookieRefreshHeaders({ userIdResult, requestHeaders, }) {
53
56
  const now = getNowInUtcTimezone();
54
57
  /** Double check that the JWT hasn't already expired. */
55
58
  const isExpiredAlready = isDateAfter({
@@ -80,10 +83,9 @@ export class BackendAuthClient {
80
83
  });
81
84
  if (isRefreshReady) {
82
85
  return this.createLoginHeaders({
83
- requestHeaders: {},
86
+ requestHeaders,
84
87
  userId: userIdResult.userId,
85
88
  isSignUpCookie: userIdResult.cookieName === AuthCookieName.SignUp,
86
- serviceOrigin,
87
89
  });
88
90
  }
89
91
  else {
@@ -111,7 +113,7 @@ export class BackendAuthClient {
111
113
  return assumedUser;
112
114
  }
113
115
  /** Securely extract a user from their request headers. */
114
- async getSecureUser({ requestHeaders, isSignUpCookie, allowUserAuthRefresh, serviceOrigin, }) {
116
+ async getSecureUser({ requestHeaders, isSignUpCookie, allowUserAuthRefresh, }) {
115
117
  const userIdResult = await extractUserIdFromRequestHeaders(requestHeaders, await this.getJwtParams(), isSignUpCookie ? AuthCookieName.SignUp : AuthCookieName.Auth, this.config.overrides);
116
118
  if (!userIdResult) {
117
119
  return undefined;
@@ -130,7 +132,7 @@ export class BackendAuthClient {
130
132
  });
131
133
  const cookieRefreshHeaders = (await this.createCookieRefreshHeaders({
132
134
  userIdResult,
133
- serviceOrigin,
135
+ requestHeaders,
134
136
  })) || {};
135
137
  return {
136
138
  user: assumedUser || user,
@@ -162,13 +164,13 @@ export class BackendAuthClient {
162
164
  const signUpCookieHeaders = params.allCookies || params.isSignUpCookie
163
165
  ? generateLogoutHeaders(await this.getCookieParams({
164
166
  isSignUpCookie: true,
165
- serviceOrigin: params.serviceOrigin,
167
+ requestHeaders: undefined,
166
168
  }), this.config.overrides)
167
169
  : undefined;
168
170
  const authCookieHeaders = params.allCookies || !params.isSignUpCookie
169
171
  ? generateLogoutHeaders(await this.getCookieParams({
170
172
  isSignUpCookie: false,
171
- serviceOrigin: params.serviceOrigin,
173
+ requestHeaders: undefined,
172
174
  }), this.config.overrides)
173
175
  : undefined;
174
176
  const setCookieHeader = {
@@ -184,18 +186,18 @@ export class BackendAuthClient {
184
186
  };
185
187
  }
186
188
  /** Use these headers to log a user in. */
187
- async createLoginHeaders({ userId, requestHeaders, isSignUpCookie, serviceOrigin, }) {
189
+ async createLoginHeaders({ userId, requestHeaders, isSignUpCookie, }) {
188
190
  const oppositeCookieName = isSignUpCookie ? AuthCookieName.Auth : AuthCookieName.SignUp;
189
191
  const hasExistingOppositeCookie = requestHeaders.cookie?.includes(`${oppositeCookieName}=`);
190
192
  const discardOppositeCookieHeaders = hasExistingOppositeCookie
191
193
  ? generateLogoutHeaders(await this.getCookieParams({
192
194
  isSignUpCookie: !isSignUpCookie,
193
- serviceOrigin,
195
+ requestHeaders,
194
196
  }), this.config.overrides)
195
197
  : undefined;
196
198
  const newCookieHeaders = await generateSuccessfulLoginHeaders(userId, await this.getCookieParams({
197
199
  isSignUpCookie,
198
- serviceOrigin,
200
+ requestHeaders,
199
201
  }), this.config.overrides);
200
202
  return {
201
203
  ...newCookieHeaders,
@@ -222,7 +224,7 @@ export class BackendAuthClient {
222
224
  * where JavaScript cannot be used to attach the CSRF token header to the request (like when
223
225
  * opening a PDF file). Use `.getSecureUser()` instead, whenever possible.
224
226
  */
225
- async getInsecureUser({ requestHeaders, allowUserAuthRefresh, serviceOrigin, }) {
227
+ async getInsecureUser({ requestHeaders, allowUserAuthRefresh, }) {
226
228
  // eslint-disable-next-line @typescript-eslint/no-deprecated
227
229
  const userIdResult = await insecureExtractUserIdFromCookieAlone(requestHeaders, await this.getJwtParams(), AuthCookieName.Auth);
228
230
  if (!userIdResult) {
@@ -239,7 +241,7 @@ export class BackendAuthClient {
239
241
  const refreshHeaders = allowUserAuthRefresh &&
240
242
  (await this.createCookieRefreshHeaders({
241
243
  userIdResult,
242
- serviceOrigin,
244
+ requestHeaders,
243
245
  }));
244
246
  return {
245
247
  user,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth-vir",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.",
5
5
  "keywords": [
6
6
  "auth",
@@ -78,6 +78,13 @@ export type BackendAuthClientConfig<
78
78
  */
79
79
  isDev: boolean;
80
80
  } & PartialWithUndefined<{
81
+ /**
82
+ * Optionally generate a service origin from request headers. The generated origin is used
83
+ * for set-cookie headers.
84
+ */
85
+ generateServiceOrigin(params: {
86
+ requestHeaders: Readonly<IncomingHttpHeaders>;
87
+ }): MaybePromise<undefined | string>;
81
88
  /**
82
89
  * Set this to allow specific users (determined by `canAssumeUser`) to assume the identity
83
90
  * of other users. This should only be used for admins so that they can troubleshoot user
@@ -168,7 +175,7 @@ export class BackendAuthClient<
168
175
  /** Get all the parameters used for cookie generation. */
169
176
  protected async getCookieParams({
170
177
  isSignUpCookie,
171
- serviceOrigin,
178
+ requestHeaders,
172
179
  }: {
173
180
  /**
174
181
  * Set this to `true` when we are setting the initial cookie right after a user signs up.
@@ -177,9 +184,12 @@ export class BackendAuthClient<
177
184
  * This should only be set to `true` when a new user is signing up.
178
185
  */
179
186
  isSignUpCookie: boolean;
180
- /** Overrides the client's already established `serviceOrigin`. */
181
- serviceOrigin?: string | undefined;
187
+ requestHeaders: Readonly<IncomingHttpHeaders> | undefined;
182
188
  }): Promise<Readonly<CookieParams>> {
189
+ const serviceOrigin = requestHeaders
190
+ ? await this.config.generateServiceOrigin?.({requestHeaders})
191
+ : undefined;
192
+
183
193
  return {
184
194
  cookieDuration: this.config.userSessionIdleTimeout || defaultSessionIdleTimeout,
185
195
  hostOrigin: serviceOrigin || this.config.serviceOrigin,
@@ -219,11 +229,10 @@ export class BackendAuthClient<
219
229
  /** Creates a `'cookie-set'` header to refresh the user's session cookie. */
220
230
  protected async createCookieRefreshHeaders({
221
231
  userIdResult,
222
- serviceOrigin,
232
+ requestHeaders,
223
233
  }: {
224
234
  userIdResult: Readonly<UserIdResult<UserId>>;
225
- /** Overrides the client's already established `serviceOrigin`. */
226
- serviceOrigin?: string | undefined;
235
+ requestHeaders: IncomingHttpHeaders;
227
236
  }): Promise<OutgoingHttpHeaders | undefined> {
228
237
  const now = getNowInUtcTimezone();
229
238
 
@@ -262,10 +271,9 @@ export class BackendAuthClient<
262
271
 
263
272
  if (isRefreshReady) {
264
273
  return this.createLoginHeaders({
265
- requestHeaders: {},
274
+ requestHeaders,
266
275
  userId: userIdResult.userId,
267
276
  isSignUpCookie: userIdResult.cookieName === AuthCookieName.SignUp,
268
- serviceOrigin,
269
277
  });
270
278
  } else {
271
279
  return undefined;
@@ -313,7 +321,6 @@ export class BackendAuthClient<
313
321
  requestHeaders,
314
322
  isSignUpCookie,
315
323
  allowUserAuthRefresh,
316
- serviceOrigin,
317
324
  }: {
318
325
  requestHeaders: IncomingHttpHeaders;
319
326
  isSignUpCookie: boolean;
@@ -323,8 +330,6 @@ export class BackendAuthClient<
323
330
  * with the frontend auth client's `checkUser.performCheck` callback.
324
331
  */
325
332
  allowUserAuthRefresh: boolean;
326
- /** Overrides the client's already established `serviceOrigin`. */
327
- serviceOrigin?: string | undefined;
328
333
  }): Promise<GetUserResult<DatabaseUser> | undefined> {
329
334
  const userIdResult = await extractUserIdFromRequestHeaders<UserId>(
330
335
  requestHeaders,
@@ -354,7 +359,7 @@ export class BackendAuthClient<
354
359
  const cookieRefreshHeaders =
355
360
  (await this.createCookieRefreshHeaders({
356
361
  userIdResult,
357
- serviceOrigin,
362
+ requestHeaders,
358
363
  })) || {};
359
364
 
360
365
  return {
@@ -408,7 +413,7 @@ export class BackendAuthClient<
408
413
  ? (generateLogoutHeaders(
409
414
  await this.getCookieParams({
410
415
  isSignUpCookie: true,
411
- serviceOrigin: params.serviceOrigin,
416
+ requestHeaders: undefined,
412
417
  }),
413
418
  this.config.overrides,
414
419
  ) satisfies Record<CsrfHeaderName, string>)
@@ -418,7 +423,7 @@ export class BackendAuthClient<
418
423
  ? (generateLogoutHeaders(
419
424
  await this.getCookieParams({
420
425
  isSignUpCookie: false,
421
- serviceOrigin: params.serviceOrigin,
426
+ requestHeaders: undefined,
422
427
  }),
423
428
  this.config.overrides,
424
429
  ) satisfies Record<CsrfHeaderName, string>)
@@ -448,13 +453,10 @@ export class BackendAuthClient<
448
453
  userId,
449
454
  requestHeaders,
450
455
  isSignUpCookie,
451
- serviceOrigin,
452
456
  }: {
453
457
  userId: UserId;
454
458
  requestHeaders: IncomingHttpHeaders;
455
459
  isSignUpCookie: boolean;
456
- /** Overrides the client's already established `serviceOrigin`. */
457
- serviceOrigin?: string | undefined;
458
460
  }): Promise<OutgoingHttpHeaders> {
459
461
  const oppositeCookieName = isSignUpCookie ? AuthCookieName.Auth : AuthCookieName.SignUp;
460
462
  const hasExistingOppositeCookie = requestHeaders.cookie?.includes(`${oppositeCookieName}=`);
@@ -463,7 +465,7 @@ export class BackendAuthClient<
463
465
  ? generateLogoutHeaders(
464
466
  await this.getCookieParams({
465
467
  isSignUpCookie: !isSignUpCookie,
466
- serviceOrigin,
468
+ requestHeaders,
467
469
  }),
468
470
  this.config.overrides,
469
471
  )
@@ -473,7 +475,7 @@ export class BackendAuthClient<
473
475
  userId,
474
476
  await this.getCookieParams({
475
477
  isSignUpCookie,
476
- serviceOrigin,
478
+ requestHeaders,
477
479
  }),
478
480
  this.config.overrides,
479
481
  );
@@ -536,7 +538,6 @@ export class BackendAuthClient<
536
538
  public async getInsecureUser({
537
539
  requestHeaders,
538
540
  allowUserAuthRefresh,
539
- serviceOrigin,
540
541
  }: {
541
542
  requestHeaders: IncomingHttpHeaders;
542
543
  /**
@@ -545,8 +546,6 @@ export class BackendAuthClient<
545
546
  * with the frontend auth client's `checkUser.performCheck` callback.
546
547
  */
547
548
  allowUserAuthRefresh: boolean;
548
- /** Overrides the client's already established `serviceOrigin`. */
549
- serviceOrigin?: string | undefined;
550
549
  }): Promise<GetUserResult<DatabaseUser> | undefined> {
551
550
  // eslint-disable-next-line @typescript-eslint/no-deprecated
552
551
  const userIdResult = await insecureExtractUserIdFromCookieAlone<UserId>(
@@ -573,7 +572,7 @@ export class BackendAuthClient<
573
572
  allowUserAuthRefresh &&
574
573
  (await this.createCookieRefreshHeaders({
575
574
  userIdResult,
576
- serviceOrigin,
575
+ requestHeaders,
577
576
  }));
578
577
 
579
578
  return {