auth-vir 2.0.3 → 2.1.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.
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type JsonCompatibleObject, type MaybePromise, type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
|
|
1
|
+
import { type JsonCompatibleObject, type MaybePromise, type PartialWithUndefined, type SelectFrom, type SetOptionalWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type AnyDuration } from 'date-vir';
|
|
2
3
|
import { type EmptyObject } from 'type-fest';
|
|
3
4
|
/**
|
|
4
5
|
* Config for {@link FrontendAuthClient}.
|
|
@@ -13,6 +14,22 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
|
|
|
13
14
|
canAssumeUser: () => MaybePromise<boolean>;
|
|
14
15
|
/** Called whenever the current user becomes unauthorized and their CSRF token is wiped. */
|
|
15
16
|
authClearedCallback: () => MaybePromise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Performs automatic checks on an interval to see if the user is still authenticated. Omit this
|
|
19
|
+
* to turn off automatic checks.
|
|
20
|
+
*/
|
|
21
|
+
checkUser: {
|
|
22
|
+
/**
|
|
23
|
+
* Get a response from the backend to see if the user is still authenticated. If the
|
|
24
|
+
* response returns a non-authorized status, the user is wiped. Any other status is
|
|
25
|
+
* ignored.
|
|
26
|
+
*/
|
|
27
|
+
performCheck: () => MaybePromise<SelectFrom<Response, {
|
|
28
|
+
status: true;
|
|
29
|
+
}>>;
|
|
30
|
+
/** @default {minutes: 1} */
|
|
31
|
+
interval?: AnyDuration | undefined;
|
|
32
|
+
};
|
|
16
33
|
overrides: PartialWithUndefined<{
|
|
17
34
|
localStorage: Pick<Storage, 'setItem' | 'removeItem' | 'getItem'>;
|
|
18
35
|
csrfHeaderName: string;
|
|
@@ -28,11 +45,21 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
|
|
|
28
45
|
*/
|
|
29
46
|
export declare class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject = EmptyObject> {
|
|
30
47
|
protected readonly config: FrontendAuthClientConfig;
|
|
48
|
+
protected userCheckInterval: undefined | ReturnType<typeof globalThis.setInterval>;
|
|
31
49
|
constructor(config?: FrontendAuthClientConfig);
|
|
50
|
+
/**
|
|
51
|
+
* Destroys the client and performs all necessary cleanup (like clearing the user check
|
|
52
|
+
* interval).
|
|
53
|
+
*/
|
|
54
|
+
destroy(): void;
|
|
32
55
|
/** Wraps {@link getCurrentCsrfToken} to automatically handle wiping an invalid CSRF token. */
|
|
33
56
|
getCurrentCsrfToken(): Promise<string | undefined>;
|
|
34
|
-
/**
|
|
35
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Assume the given user. Pass `undefined` to wipe the currently assumed user.
|
|
59
|
+
*
|
|
60
|
+
* @returns Whether the assumed user setting or clearing succeeded or not.
|
|
61
|
+
*/
|
|
62
|
+
assumeUser(assumedUserParams: Readonly<AssumedUserParams> | undefined): Promise<boolean>;
|
|
36
63
|
/** Gets the assumed user params stored in local storage, if any. */
|
|
37
64
|
getAssumedUser(): AssumedUserParams | undefined;
|
|
38
65
|
/**
|
|
@@ -58,8 +85,8 @@ export declare class FrontendAuthClient<AssumedUserParams extends JsonCompatible
|
|
|
58
85
|
* Use to verify _all_ responses received from the backend. Immediately logs the user out once
|
|
59
86
|
* an unauthorized response is detected.
|
|
60
87
|
*/
|
|
61
|
-
verifyResponseAuth(response: Readonly<SelectFrom<Response, {
|
|
88
|
+
verifyResponseAuth(response: Readonly<SetOptionalWithUndefined<SelectFrom<Response, {
|
|
62
89
|
status: true;
|
|
63
90
|
headers: true;
|
|
64
|
-
}>>): Promise<void>;
|
|
91
|
+
}>, 'headers'>>): Promise<void>;
|
|
65
92
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpStatus, } from '@augment-vir/common';
|
|
2
|
+
import { convertDuration } from 'date-vir';
|
|
2
3
|
import { CsrfTokenFailureReason, extractCsrfTokenHeader, getCurrentCsrfToken, storeCsrfToken, wipeCurrentCsrfToken, } from '../csrf-token.js';
|
|
3
4
|
import { AuthHeaderName } from '../headers.js';
|
|
4
5
|
/**
|
|
@@ -10,8 +11,29 @@ import { AuthHeaderName } from '../headers.js';
|
|
|
10
11
|
*/
|
|
11
12
|
export class FrontendAuthClient {
|
|
12
13
|
config;
|
|
14
|
+
userCheckInterval;
|
|
13
15
|
constructor(config = {}) {
|
|
14
16
|
this.config = config;
|
|
17
|
+
if (config.checkUser) {
|
|
18
|
+
const intervalDuration = convertDuration(config.checkUser.interval || { minutes: 1 }, {
|
|
19
|
+
milliseconds: true,
|
|
20
|
+
}).milliseconds;
|
|
21
|
+
this.userCheckInterval = globalThis.setInterval(async () => {
|
|
22
|
+
const response = await config.checkUser?.performCheck();
|
|
23
|
+
if (response) {
|
|
24
|
+
await this.verifyResponseAuth({
|
|
25
|
+
status: response.status,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}, intervalDuration);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Destroys the client and performs all necessary cleanup (like clearing the user check
|
|
33
|
+
* interval).
|
|
34
|
+
*/
|
|
35
|
+
destroy() {
|
|
36
|
+
globalThis.clearInterval(this.userCheckInterval);
|
|
15
37
|
}
|
|
16
38
|
/** Wraps {@link getCurrentCsrfToken} to automatically handle wiping an invalid CSRF token. */
|
|
17
39
|
async getCurrentCsrfToken() {
|
|
@@ -25,12 +47,22 @@ export class FrontendAuthClient {
|
|
|
25
47
|
return csrfTokenResult.csrfToken?.token;
|
|
26
48
|
}
|
|
27
49
|
}
|
|
28
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Assume the given user. Pass `undefined` to wipe the currently assumed user.
|
|
52
|
+
*
|
|
53
|
+
* @returns Whether the assumed user setting or clearing succeeded or not.
|
|
54
|
+
*/
|
|
29
55
|
async assumeUser(assumedUserParams) {
|
|
56
|
+
const localStorage = this.config.overrides?.localStorage || globalThis.localStorage;
|
|
57
|
+
const storageKey = this.config.overrides?.assumedUserHeaderName || AuthHeaderName.AssumedUser;
|
|
58
|
+
if (!assumedUserParams) {
|
|
59
|
+
localStorage.removeItem(storageKey);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
30
62
|
if (!(await this.config.canAssumeUser?.())) {
|
|
31
63
|
return false;
|
|
32
64
|
}
|
|
33
|
-
|
|
65
|
+
localStorage.setItem(storageKey, JSON.stringify(assumedUserParams));
|
|
34
66
|
return true;
|
|
35
67
|
}
|
|
36
68
|
/** Gets the assumed user params stored in local storage, if any. */
|
|
@@ -101,7 +133,7 @@ export class FrontendAuthClient {
|
|
|
101
133
|
*/
|
|
102
134
|
async verifyResponseAuth(response) {
|
|
103
135
|
if (response.status === HttpStatus.Unauthorized &&
|
|
104
|
-
!response.headers
|
|
136
|
+
!response.headers?.get(AuthHeaderName.IsSignUpAuth)) {
|
|
105
137
|
await this.logout();
|
|
106
138
|
}
|
|
107
139
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
type MaybePromise,
|
|
5
5
|
type PartialWithUndefined,
|
|
6
6
|
type SelectFrom,
|
|
7
|
+
type SetOptionalWithUndefined,
|
|
7
8
|
} from '@augment-vir/common';
|
|
9
|
+
import {convertDuration, type AnyDuration} from 'date-vir';
|
|
8
10
|
import {type EmptyObject} from 'type-fest';
|
|
9
11
|
import {
|
|
10
12
|
CsrfTokenFailureReason,
|
|
@@ -28,6 +30,29 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
|
|
|
28
30
|
canAssumeUser: () => MaybePromise<boolean>;
|
|
29
31
|
/** Called whenever the current user becomes unauthorized and their CSRF token is wiped. */
|
|
30
32
|
authClearedCallback: () => MaybePromise<void>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Performs automatic checks on an interval to see if the user is still authenticated. Omit this
|
|
36
|
+
* to turn off automatic checks.
|
|
37
|
+
*/
|
|
38
|
+
checkUser: {
|
|
39
|
+
/**
|
|
40
|
+
* Get a response from the backend to see if the user is still authenticated. If the
|
|
41
|
+
* response returns a non-authorized status, the user is wiped. Any other status is
|
|
42
|
+
* ignored.
|
|
43
|
+
*/
|
|
44
|
+
performCheck: () => MaybePromise<
|
|
45
|
+
SelectFrom<
|
|
46
|
+
Response,
|
|
47
|
+
{
|
|
48
|
+
status: true;
|
|
49
|
+
}
|
|
50
|
+
>
|
|
51
|
+
>;
|
|
52
|
+
/** @default {minutes: 1} */
|
|
53
|
+
interval?: AnyDuration | undefined;
|
|
54
|
+
};
|
|
55
|
+
|
|
31
56
|
overrides: PartialWithUndefined<{
|
|
32
57
|
localStorage: Pick<Storage, 'setItem' | 'removeItem' | 'getItem'>;
|
|
33
58
|
csrfHeaderName: string;
|
|
@@ -43,7 +68,32 @@ export type FrontendAuthClientConfig = PartialWithUndefined<{
|
|
|
43
68
|
* @category Client
|
|
44
69
|
*/
|
|
45
70
|
export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject = EmptyObject> {
|
|
46
|
-
|
|
71
|
+
protected userCheckInterval: undefined | ReturnType<typeof globalThis.setInterval>;
|
|
72
|
+
|
|
73
|
+
constructor(protected readonly config: FrontendAuthClientConfig = {}) {
|
|
74
|
+
if (config.checkUser) {
|
|
75
|
+
const intervalDuration = convertDuration(config.checkUser.interval || {minutes: 1}, {
|
|
76
|
+
milliseconds: true,
|
|
77
|
+
}).milliseconds;
|
|
78
|
+
|
|
79
|
+
this.userCheckInterval = globalThis.setInterval(async () => {
|
|
80
|
+
const response = await config.checkUser?.performCheck();
|
|
81
|
+
if (response) {
|
|
82
|
+
await this.verifyResponseAuth({
|
|
83
|
+
status: response.status,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}, intervalDuration);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Destroys the client and performs all necessary cleanup (like clearing the user check
|
|
92
|
+
* interval).
|
|
93
|
+
*/
|
|
94
|
+
public destroy() {
|
|
95
|
+
globalThis.clearInterval(this.userCheckInterval);
|
|
96
|
+
}
|
|
47
97
|
|
|
48
98
|
/** Wraps {@link getCurrentCsrfToken} to automatically handle wiping an invalid CSRF token. */
|
|
49
99
|
public async getCurrentCsrfToken(): Promise<string | undefined> {
|
|
@@ -60,16 +110,28 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
60
110
|
}
|
|
61
111
|
}
|
|
62
112
|
|
|
63
|
-
/**
|
|
64
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Assume the given user. Pass `undefined` to wipe the currently assumed user.
|
|
115
|
+
*
|
|
116
|
+
* @returns Whether the assumed user setting or clearing succeeded or not.
|
|
117
|
+
*/
|
|
118
|
+
public async assumeUser(
|
|
119
|
+
assumedUserParams: Readonly<AssumedUserParams> | undefined,
|
|
120
|
+
): Promise<boolean> {
|
|
121
|
+
const localStorage = this.config.overrides?.localStorage || globalThis.localStorage;
|
|
122
|
+
const storageKey =
|
|
123
|
+
this.config.overrides?.assumedUserHeaderName || AuthHeaderName.AssumedUser;
|
|
124
|
+
|
|
125
|
+
if (!assumedUserParams) {
|
|
126
|
+
localStorage.removeItem(storageKey);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
65
130
|
if (!(await this.config.canAssumeUser?.())) {
|
|
66
131
|
return false;
|
|
67
132
|
}
|
|
68
133
|
|
|
69
|
-
(
|
|
70
|
-
this.config.overrides?.assumedUserHeaderName || AuthHeaderName.AssumedUser,
|
|
71
|
-
JSON.stringify(assumedUserParams),
|
|
72
|
-
);
|
|
134
|
+
localStorage.setItem(storageKey, JSON.stringify(assumedUserParams));
|
|
73
135
|
|
|
74
136
|
return true;
|
|
75
137
|
}
|
|
@@ -164,18 +226,21 @@ export class FrontendAuthClient<AssumedUserParams extends JsonCompatibleObject =
|
|
|
164
226
|
*/
|
|
165
227
|
public async verifyResponseAuth(
|
|
166
228
|
response: Readonly<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
229
|
+
SetOptionalWithUndefined<
|
|
230
|
+
SelectFrom<
|
|
231
|
+
Response,
|
|
232
|
+
{
|
|
233
|
+
status: true;
|
|
234
|
+
headers: true;
|
|
235
|
+
}
|
|
236
|
+
>,
|
|
237
|
+
'headers'
|
|
173
238
|
>
|
|
174
239
|
>,
|
|
175
240
|
): Promise<void> {
|
|
176
241
|
if (
|
|
177
242
|
response.status === HttpStatus.Unauthorized &&
|
|
178
|
-
!response.headers
|
|
243
|
+
!response.headers?.get(AuthHeaderName.IsSignUpAuth)
|
|
179
244
|
) {
|
|
180
245
|
await this.logout();
|
|
181
246
|
}
|