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
|
|
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<
|
|
93
|
+
verifyResponseAuth(response: Readonly<PartialWithUndefined<SelectFrom<Response, {
|
|
96
94
|
status: true;
|
|
97
95
|
headers: true;
|
|
98
|
-
}
|
|
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
|
-
|
|
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
|
}
|
package/dist/csrf-token.d.ts
CHANGED
|
@@ -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
|
-
}
|
|
67
|
+
}>>>, overrides?: PartialWithUndefined<{
|
|
68
68
|
csrfHeaderName: string;
|
|
69
69
|
}>): Readonly<GetCsrfTokenResult>;
|
|
70
70
|
/**
|
package/dist/csrf-token.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
46
|
-
"@augment-vir/common": "^31.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
87
|
+
const rawCsrfToken = response.headers?.get(csrfTokenHeaderName);
|
|
88
88
|
|
|
89
89
|
return parseCsrfToken(rawCsrfToken);
|
|
90
90
|
}
|