auth-vir 2.3.4 → 2.3.5

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.
@@ -1,4 +1,4 @@
1
- import { createBlockingInterval, type JsonCompatibleObject, type MaybePromise, type PartialWithUndefined, type SelectFrom, type SetOptionalWithUndefined } from '@augment-vir/common';
1
+ import { 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
4
  /**
@@ -23,10 +23,13 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
23
23
  * Get a response from the backend to see if the user is still authenticated. If the
24
24
  * response returns a non-authorized status, the user is wiped. Any other status is
25
25
  * ignored.
26
+ *
27
+ * If the user is not currently authorized, this should return `undefined` to prevent
28
+ * unnecessary network traffic.
26
29
  */
27
30
  performCheck: () => MaybePromise<SelectFrom<Response, {
28
31
  status: true;
29
- }>>;
32
+ }> | undefined>;
30
33
  /** @default {minutes: 1} */
31
34
  interval?: AnyDuration | undefined;
32
35
  };
@@ -46,11 +49,6 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
46
49
  export declare class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject = EmptyObject> {
47
50
  protected readonly config: FrontendAuthClientConfig;
48
51
  protected userCheckInterval: undefined | ReturnType<typeof createBlockingInterval>;
49
- /**
50
- * Keeps track of whether the latest state of auth is logged in (`true`) or not (`false`). This
51
- * is used for determining if the check user interval should keep running.
52
- */
53
- protected isLatestAuthorized: boolean;
54
52
  constructor(config?: FrontendAuthClientConfig);
55
53
  /**
56
54
  * Destroys the client and performs all necessary cleanup (like clearing the user check
@@ -92,8 +90,8 @@ export declare class FrontendAuthClient<AssumedUserParams extends JsonCompatible
92
90
  *
93
91
  * @returns `true` if the auth is okay, `false` otherwise.
94
92
  */
95
- verifyResponseAuth(response: Readonly<SetOptionalWithUndefined<SelectFrom<Response, {
93
+ verifyResponseAuth(response: Readonly<PartialWithUndefined<SelectFrom<Response, {
96
94
  status: true;
97
95
  headers: true;
98
- }>, 'headers'>>): Promise<boolean>;
96
+ }>>>): Promise<boolean>;
99
97
  }
@@ -11,22 +11,13 @@ import { AuthHeaderName } from '../headers.js';
11
11
  export class FrontendAuthClient {
12
12
  config;
13
13
  userCheckInterval;
14
- /**
15
- * Keeps track of whether the latest state of auth is logged in (`true`) or not (`false`). This
16
- * is used for determining if the check user interval should keep running.
17
- */
18
- isLatestAuthorized = false;
19
14
  constructor(config = {}) {
20
15
  this.config = config;
21
16
  if (config.checkUser) {
22
17
  this.userCheckInterval = createBlockingInterval(async () => {
23
- /** No need to check current user status when there is no user. */
24
- if (!this.isLatestAuthorized) {
25
- return;
26
- }
27
18
  const response = await config.checkUser?.performCheck();
28
19
  if (response) {
29
- this.isLatestAuthorized = await this.verifyResponseAuth({
20
+ await this.verifyResponseAuth({
30
21
  status: response.status,
31
22
  });
32
23
  }
@@ -111,7 +102,6 @@ export class FrontendAuthClient {
111
102
  }
112
103
  /** Wipes the current user auth. */
113
104
  async logout() {
114
- this.isLatestAuthorized = false;
115
105
  await this.config.authClearedCallback?.();
116
106
  wipeCurrentCsrfToken(this.config.overrides);
117
107
  }
@@ -132,7 +122,6 @@ export class FrontendAuthClient {
132
122
  throw new Error('Did not receive any CSRF token.');
133
123
  }
134
124
  storeCsrfToken(csrfToken, this.config.overrides);
135
- this.isLatestAuthorized = true;
136
125
  }
137
126
  /**
138
127
  * Use to verify _all_ responses received from the backend. Immediately logs the user out once
@@ -146,6 +135,11 @@ export class FrontendAuthClient {
146
135
  await this.logout();
147
136
  return false;
148
137
  }
138
+ /** If the response has a new CSRF token, store it. */
139
+ const { csrfToken } = extractCsrfTokenHeader(response, this.config.overrides);
140
+ if (csrfToken) {
141
+ storeCsrfToken(csrfToken, this.config.overrides);
142
+ }
149
143
  return true;
150
144
  }
151
145
  }
@@ -62,9 +62,9 @@ export type GetCsrfTokenResult = RequireExactlyOne<{
62
62
  *
63
63
  * @category Auth : Client
64
64
  */
65
- export declare function extractCsrfTokenHeader(response: Readonly<SelectFrom<Response, {
65
+ export declare function extractCsrfTokenHeader(response: Readonly<PartialWithUndefined<SelectFrom<Response, {
66
66
  headers: true;
67
- }>>, overrides?: PartialWithUndefined<{
67
+ }>>>, overrides?: PartialWithUndefined<{
68
68
  csrfHeaderName: string;
69
69
  }>): Readonly<GetCsrfTokenResult>;
70
70
  /**
@@ -45,7 +45,7 @@ export var CsrfTokenFailureReason;
45
45
  */
46
46
  export function extractCsrfTokenHeader(response, overrides = {}) {
47
47
  const csrfTokenHeaderName = overrides.csrfHeaderName || AuthHeaderName.CsrfToken;
48
- const rawCsrfToken = response.headers.get(csrfTokenHeaderName);
48
+ const rawCsrfToken = response.headers?.get(csrfTokenHeaderName);
49
49
  return parseCsrfToken(rawCsrfToken);
50
50
  }
51
51
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth-vir",
3
- "version": "2.3.4",
3
+ "version": "2.3.5",
4
4
  "description": "Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.",
5
5
  "keywords": [
6
6
  "auth",
@@ -42,8 +42,8 @@
42
42
  "test:web": "virmator test web"
43
43
  },
44
44
  "dependencies": {
45
- "@augment-vir/assert": "^31.45.0",
46
- "@augment-vir/common": "^31.45.0",
45
+ "@augment-vir/assert": "^31.47.0",
46
+ "@augment-vir/common": "^31.47.0",
47
47
  "date-vir": "^8.0.0",
48
48
  "hash-wasm": "^4.12.0",
49
49
  "jose": "^6.1.0",
@@ -52,7 +52,7 @@
52
52
  "url-vir": "^2.1.6"
53
53
  },
54
54
  "devDependencies": {
55
- "@augment-vir/test": "^31.45.0",
55
+ "@augment-vir/test": "^31.47.0",
56
56
  "@prisma/client": "^6.18.0",
57
57
  "@types/node": "^24.9.1",
58
58
  "@web/dev-server-esbuild": "^1.0.4",
@@ -62,7 +62,7 @@
62
62
  "@web/test-runner-visual-regression": "^0.10.0",
63
63
  "istanbul-smart-text-reporter": "^1.1.5",
64
64
  "markdown-code-example-inserter": "^3.0.3",
65
- "prisma-vir": "^2.0.0",
65
+ "prisma-vir": "^2.1.0",
66
66
  "typedoc": "^0.28.14"
67
67
  },
68
68
  "engines": {
@@ -5,7 +5,6 @@ import {
5
5
  type MaybePromise,
6
6
  type PartialWithUndefined,
7
7
  type SelectFrom,
8
- type SetOptionalWithUndefined,
9
8
  } from '@augment-vir/common';
10
9
  import {type AnyDuration} from 'date-vir';
11
10
  import {type EmptyObject} from 'type-fest';
@@ -41,14 +40,18 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
41
40
  * Get a response from the backend to see if the user is still authenticated. If the
42
41
  * response returns a non-authorized status, the user is wiped. Any other status is
43
42
  * ignored.
43
+ *
44
+ * If the user is not currently authorized, this should return `undefined` to prevent
45
+ * unnecessary network traffic.
44
46
  */
45
47
  performCheck: () => MaybePromise<
46
- SelectFrom<
47
- Response,
48
- {
49
- status: true;
50
- }
51
- >
48
+ | SelectFrom<
49
+ Response,
50
+ {
51
+ status: true;
52
+ }
53
+ >
54
+ | undefined
52
55
  >;
53
56
  /** @default {minutes: 1} */
54
57
  interval?: AnyDuration | undefined;
@@ -70,24 +73,15 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
70
73
  */
71
74
  export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject = EmptyObject> {
72
75
  protected userCheckInterval: undefined | ReturnType<typeof createBlockingInterval>;
73
- /**
74
- * Keeps track of whether the latest state of auth is logged in (`true`) or not (`false`). This
75
- * is used for determining if the check user interval should keep running.
76
- */
77
- protected isLatestAuthorized = false;
78
76
 
79
77
  constructor(protected readonly config: FrontendAuthClientConfig = {}) {
80
78
  if (config.checkUser) {
81
79
  this.userCheckInterval = createBlockingInterval(
82
80
  async () => {
83
- /** No need to check current user status when there is no user. */
84
- if (!this.isLatestAuthorized) {
85
- return;
86
- }
87
-
88
81
  const response = await config.checkUser?.performCheck();
82
+
89
83
  if (response) {
90
- this.isLatestAuthorized = await this.verifyResponseAuth({
84
+ await this.verifyResponseAuth({
91
85
  status: response.status,
92
86
  });
93
87
  }
@@ -194,7 +188,6 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
194
188
 
195
189
  /** Wipes the current user auth. */
196
190
  public async logout() {
197
- this.isLatestAuthorized = false;
198
191
  await this.config.authClearedCallback?.();
199
192
  wipeCurrentCsrfToken(this.config.overrides);
200
193
  }
@@ -229,7 +222,6 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
229
222
  }
230
223
 
231
224
  storeCsrfToken(csrfToken, this.config.overrides);
232
- this.isLatestAuthorized = true;
233
225
  }
234
226
 
235
227
  /**
@@ -240,15 +232,14 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
240
232
  */
241
233
  public async verifyResponseAuth(
242
234
  response: Readonly<
243
- SetOptionalWithUndefined<
235
+ PartialWithUndefined<
244
236
  SelectFrom<
245
237
  Response,
246
238
  {
247
239
  status: true;
248
240
  headers: true;
249
241
  }
250
- >,
251
- 'headers'
242
+ >
252
243
  >
253
244
  >,
254
245
  ): Promise<boolean> {
@@ -260,6 +251,12 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
260
251
  return false;
261
252
  }
262
253
 
254
+ /** If the response has a new CSRF token, store it. */
255
+ const {csrfToken} = extractCsrfTokenHeader(response, this.config.overrides);
256
+ if (csrfToken) {
257
+ storeCsrfToken(csrfToken, this.config.overrides);
258
+ }
259
+
263
260
  return true;
264
261
  }
265
262
  }
package/src/csrf-token.ts CHANGED
@@ -77,14 +77,14 @@ export type GetCsrfTokenResult = RequireExactlyOne<{
77
77
  * @category Auth : Client
78
78
  */
79
79
  export function extractCsrfTokenHeader(
80
- response: Readonly<SelectFrom<Response, {headers: true}>>,
80
+ response: Readonly<PartialWithUndefined<SelectFrom<Response, {headers: true}>>>,
81
81
  overrides: PartialWithUndefined<{
82
82
  csrfHeaderName: string;
83
83
  }> = {},
84
84
  ): Readonly<GetCsrfTokenResult> {
85
85
  const csrfTokenHeaderName = overrides.csrfHeaderName || AuthHeaderName.CsrfToken;
86
86
 
87
- const rawCsrfToken = response.headers.get(csrfTokenHeaderName);
87
+ const rawCsrfToken = response.headers?.get(csrfTokenHeaderName);
88
88
 
89
89
  return parseCsrfToken(rawCsrfToken);
90
90
  }