auth-vir 2.7.2 → 3.0.1
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 +33 -11
- package/dist/auth-client/backend-auth.client.d.ts +29 -18
- package/dist/auth-client/backend-auth.client.js +47 -64
- package/dist/auth-client/frontend-auth.client.d.ts +24 -11
- package/dist/auth-client/frontend-auth.client.js +41 -32
- package/dist/auth-client/is-session-refresh-ready.d.ts +23 -0
- package/dist/auth-client/is-session-refresh-ready.js +21 -0
- package/dist/auth.d.ts +11 -19
- package/dist/auth.js +30 -49
- package/dist/cookie.d.ts +11 -2
- package/dist/cookie.js +16 -12
- package/dist/csrf-token.d.ts +52 -12
- package/dist/csrf-token.js +40 -15
- package/dist/hash.js +6 -4
- package/dist/headers.d.ts +0 -1
- package/dist/headers.js +0 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/jwt/jwt.d.ts +11 -1
- package/dist/jwt/jwt.js +9 -2
- package/dist/jwt/user-jwt.js +2 -1
- package/package.json +11 -11
- package/src/auth-client/backend-auth.client.ts +84 -97
- package/src/auth-client/frontend-auth.client.ts +97 -73
- package/src/auth-client/is-session-refresh-ready.ts +40 -0
- package/src/auth.ts +53 -99
- package/src/cookie.ts +38 -16
- package/src/csrf-token.ts +109 -48
- package/src/hash.ts +7 -4
- package/src/headers.ts +0 -1
- package/src/index.ts +1 -1
- package/src/jwt/jwt.ts +27 -2
- package/src/jwt/user-jwt.ts +2 -1
- package/dist/log.d.ts +0 -12
- package/dist/log.js +0 -20
- package/src/log.ts +0 -22
package/src/cookie.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {check} from '@augment-vir/assert';
|
|
2
|
-
import {safeMatch, type PartialWithUndefined} from '@augment-vir/common';
|
|
3
|
-
import {
|
|
2
|
+
import {escapeStringForRegExp, safeMatch, type PartialWithUndefined} from '@augment-vir/common';
|
|
3
|
+
import {
|
|
4
|
+
calculateRelativeDate,
|
|
5
|
+
convertDuration,
|
|
6
|
+
getNowInUtcTimezone,
|
|
7
|
+
type AnyDuration,
|
|
8
|
+
type FullDate,
|
|
9
|
+
type UtcTimezone,
|
|
10
|
+
} from 'date-vir';
|
|
4
11
|
import {type Primitive} from 'type-fest';
|
|
5
12
|
import {parseUrl} from 'url-vir';
|
|
6
13
|
import {type CreateJwtParams, type ParseJwtParams, type ParsedJwt} from './jwt/jwt.js';
|
|
@@ -53,6 +60,16 @@ export type CookieParams = {
|
|
|
53
60
|
isDev: boolean;
|
|
54
61
|
}>;
|
|
55
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Output from {@link generateAuthCookie}.
|
|
65
|
+
*
|
|
66
|
+
* @category Internal
|
|
67
|
+
*/
|
|
68
|
+
export type GenerateAuthCookieResult = {
|
|
69
|
+
cookie: string;
|
|
70
|
+
expiration: FullDate<UtcTimezone>;
|
|
71
|
+
};
|
|
72
|
+
|
|
56
73
|
/**
|
|
57
74
|
* Generate a secure cookie that stores the user JWT data. Used in host (backend) code.
|
|
58
75
|
*
|
|
@@ -61,19 +78,24 @@ export type CookieParams = {
|
|
|
61
78
|
export async function generateAuthCookie(
|
|
62
79
|
userJwtData: Readonly<JwtUserData>,
|
|
63
80
|
cookieConfig: Readonly<CookieParams>,
|
|
64
|
-
): Promise<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
): Promise<GenerateAuthCookieResult> {
|
|
82
|
+
const expiration = calculateRelativeDate(getNowInUtcTimezone(), cookieConfig.cookieDuration);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
cookie: generateCookie({
|
|
86
|
+
[cookieConfig.cookieName || 'auth']: await createUserJwt(
|
|
87
|
+
userJwtData,
|
|
88
|
+
cookieConfig.jwtParams,
|
|
89
|
+
),
|
|
90
|
+
Domain: parseUrl(cookieConfig.hostOrigin).hostname,
|
|
91
|
+
HttpOnly: true,
|
|
92
|
+
Path: '/',
|
|
93
|
+
SameSite: 'Strict',
|
|
94
|
+
'MAX-AGE': convertDuration(cookieConfig.cookieDuration, {seconds: true}).seconds,
|
|
95
|
+
Secure: !cookieConfig.isDev,
|
|
96
|
+
}),
|
|
97
|
+
expiration,
|
|
98
|
+
};
|
|
77
99
|
}
|
|
78
100
|
|
|
79
101
|
/**
|
|
@@ -136,7 +158,7 @@ export async function extractCookieJwt(
|
|
|
136
158
|
jwtParams: Readonly<ParseJwtParams>,
|
|
137
159
|
cookieName: string = AuthCookieName.Auth,
|
|
138
160
|
): Promise<undefined | ParsedJwt<JwtUserData>> {
|
|
139
|
-
const cookieRegExp = new RegExp(`${cookieName}=[^;]+(?:;|$)`);
|
|
161
|
+
const cookieRegExp = new RegExp(`${escapeStringForRegExp(cookieName)}=[^;]+(?:;|$)`);
|
|
140
162
|
|
|
141
163
|
const [cookieValue] = safeMatch(rawCookie, cookieRegExp);
|
|
142
164
|
|
package/src/csrf-token.ts
CHANGED
|
@@ -13,8 +13,6 @@ import {
|
|
|
13
13
|
} from 'date-vir';
|
|
14
14
|
import {defineShape, parseJsonWithShape} from 'object-shape-tester';
|
|
15
15
|
import {type RequireExactlyOne} from 'type-fest';
|
|
16
|
-
import {AuthHeaderName} from './headers.js';
|
|
17
|
-
import {authLog} from './log.js';
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* Shape definition for {@link CsrfToken}.
|
|
@@ -33,6 +31,15 @@ export const csrfTokenShape = defineShape({
|
|
|
33
31
|
*/
|
|
34
32
|
export type CsrfToken = typeof csrfTokenShape.runtimeType;
|
|
35
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Default allowed clock skew for CSRF token expiration checks. Accounts for differences between
|
|
36
|
+
* server and client clocks when checking token expiration.
|
|
37
|
+
*
|
|
38
|
+
* @category Internal
|
|
39
|
+
* @default {minutes: 5}
|
|
40
|
+
*/
|
|
41
|
+
export const defaultAllowedClockSkew: Readonly<AnyDuration> = {minutes: 5};
|
|
42
|
+
|
|
36
43
|
/**
|
|
37
44
|
* Generates a random, cryptographically secure CSRF token.
|
|
38
45
|
*
|
|
@@ -62,6 +69,37 @@ export enum CsrfTokenFailureReason {
|
|
|
62
69
|
Expired = 'expired',
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Options for specifying the CSRF token header name.
|
|
74
|
+
*
|
|
75
|
+
* @category Auth : Client
|
|
76
|
+
* @category Auth : Host
|
|
77
|
+
*/
|
|
78
|
+
export type CsrfHeaderNameOption = RequireExactlyOne<{
|
|
79
|
+
/** Prefix used to generate the header name: `${prefix}-auth-vir-csrf-token`. */
|
|
80
|
+
csrfHeaderPrefix: string;
|
|
81
|
+
/** Overrides the entire CSRF header name. */
|
|
82
|
+
csrfHeaderName: string;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolves a {@link CsrfHeaderNameOption} to the actual header name string.
|
|
87
|
+
*
|
|
88
|
+
* @category Auth : Client
|
|
89
|
+
* @category Auth : Host
|
|
90
|
+
*/
|
|
91
|
+
export function resolveCsrfHeaderName(option: Readonly<CsrfHeaderNameOption>): string {
|
|
92
|
+
if ('csrfHeaderName' in option && option.csrfHeaderName) {
|
|
93
|
+
return option.csrfHeaderName;
|
|
94
|
+
} else {
|
|
95
|
+
return [
|
|
96
|
+
option.csrfHeaderPrefix,
|
|
97
|
+
'auth-vir',
|
|
98
|
+
'csrf-token',
|
|
99
|
+
].join('-');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
65
103
|
/**
|
|
66
104
|
* Output from {@link getCurrentCsrfToken}.
|
|
67
105
|
*
|
|
@@ -79,15 +117,21 @@ export type GetCsrfTokenResult = RequireExactlyOne<{
|
|
|
79
117
|
*/
|
|
80
118
|
export function extractCsrfTokenHeader(
|
|
81
119
|
response: Readonly<PartialWithUndefined<SelectFrom<Response, {headers: true}>>>,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
120
|
+
csrfHeaderNameOption: Readonly<CsrfHeaderNameOption>,
|
|
121
|
+
options?: PartialWithUndefined<{
|
|
122
|
+
/**
|
|
123
|
+
* Allowed clock skew tolerance for CSRF token expiration checks.
|
|
124
|
+
*
|
|
125
|
+
* @default {minutes: 5}
|
|
126
|
+
*/
|
|
127
|
+
allowedClockSkew: Readonly<AnyDuration>;
|
|
128
|
+
}>,
|
|
85
129
|
): Readonly<GetCsrfTokenResult> {
|
|
86
|
-
const csrfTokenHeaderName =
|
|
130
|
+
const csrfTokenHeaderName = resolveCsrfHeaderName(csrfHeaderNameOption);
|
|
87
131
|
|
|
88
132
|
const rawCsrfToken = response.headers?.get(csrfTokenHeaderName);
|
|
89
133
|
|
|
90
|
-
return parseCsrfToken(rawCsrfToken);
|
|
134
|
+
return parseCsrfToken(rawCsrfToken, options);
|
|
91
135
|
}
|
|
92
136
|
|
|
93
137
|
/**
|
|
@@ -97,19 +141,18 @@ export function extractCsrfTokenHeader(
|
|
|
97
141
|
*/
|
|
98
142
|
export function storeCsrfToken(
|
|
99
143
|
csrfToken: Readonly<CsrfToken>,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}> = {},
|
|
144
|
+
options: Readonly<CsrfHeaderNameOption> &
|
|
145
|
+
PartialWithUndefined<{
|
|
146
|
+
/**
|
|
147
|
+
* Allows mocking or overriding the global `localStorage`.
|
|
148
|
+
*
|
|
149
|
+
* @default globalThis.localStorage
|
|
150
|
+
*/
|
|
151
|
+
localStorage: Pick<Storage, 'setItem' | 'removeItem'>;
|
|
152
|
+
}>,
|
|
110
153
|
) {
|
|
111
|
-
(
|
|
112
|
-
|
|
154
|
+
(options.localStorage || globalThis.localStorage).setItem(
|
|
155
|
+
resolveCsrfHeaderName(options),
|
|
113
156
|
JSON.stringify(csrfToken),
|
|
114
157
|
);
|
|
115
158
|
}
|
|
@@ -119,7 +162,18 @@ export function storeCsrfToken(
|
|
|
119
162
|
*
|
|
120
163
|
* @category Internal
|
|
121
164
|
*/
|
|
122
|
-
export function parseCsrfToken(
|
|
165
|
+
export function parseCsrfToken(
|
|
166
|
+
value: string | undefined | null,
|
|
167
|
+
options?: PartialWithUndefined<{
|
|
168
|
+
/**
|
|
169
|
+
* Allowed clock skew tolerance for CSRF token expiration checks. Accounts for differences
|
|
170
|
+
* between server and client clocks.
|
|
171
|
+
*
|
|
172
|
+
* @default {minutes: 5}
|
|
173
|
+
*/
|
|
174
|
+
allowedClockSkew: Readonly<AnyDuration>;
|
|
175
|
+
}>,
|
|
176
|
+
): Readonly<GetCsrfTokenResult> {
|
|
123
177
|
if (!value) {
|
|
124
178
|
return {
|
|
125
179
|
failure: CsrfTokenFailureReason.DoesNotExist,
|
|
@@ -143,10 +197,15 @@ export function parseCsrfToken(value: string | undefined | null): Readonly<GetCs
|
|
|
143
197
|
};
|
|
144
198
|
}
|
|
145
199
|
|
|
200
|
+
const effectiveExpiration = calculateRelativeDate(
|
|
201
|
+
csrfToken.expiration,
|
|
202
|
+
options?.allowedClockSkew || defaultAllowedClockSkew,
|
|
203
|
+
);
|
|
204
|
+
|
|
146
205
|
if (
|
|
147
206
|
isDateAfter({
|
|
148
207
|
fullDate: getNowInUtcTimezone(),
|
|
149
|
-
relativeTo:
|
|
208
|
+
relativeTo: effectiveExpiration,
|
|
150
209
|
})
|
|
151
210
|
) {
|
|
152
211
|
return {
|
|
@@ -166,23 +225,27 @@ export function parseCsrfToken(value: string | undefined | null): Readonly<GetCs
|
|
|
166
225
|
* @category Auth : Client
|
|
167
226
|
*/
|
|
168
227
|
export function getCurrentCsrfToken(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
228
|
+
options: Readonly<CsrfHeaderNameOption> &
|
|
229
|
+
PartialWithUndefined<{
|
|
230
|
+
/**
|
|
231
|
+
* Allows mocking or overriding the global `localStorage`.
|
|
232
|
+
*
|
|
233
|
+
* @default globalThis.localStorage
|
|
234
|
+
*/
|
|
235
|
+
localStorage: Pick<Storage, 'getItem'>;
|
|
236
|
+
/**
|
|
237
|
+
* Allowed clock skew tolerance for CSRF token expiration checks.
|
|
238
|
+
*
|
|
239
|
+
* @default {minutes: 5}
|
|
240
|
+
*/
|
|
241
|
+
allowedClockSkew: Readonly<AnyDuration>;
|
|
242
|
+
}>,
|
|
179
243
|
): Readonly<GetCsrfTokenResult> {
|
|
180
244
|
const rawCsrfToken: string | undefined =
|
|
181
|
-
(
|
|
182
|
-
|
|
183
|
-
) || undefined;
|
|
245
|
+
(options.localStorage || globalThis.localStorage).getItem(resolveCsrfHeaderName(options)) ||
|
|
246
|
+
undefined;
|
|
184
247
|
|
|
185
|
-
return parseCsrfToken(rawCsrfToken);
|
|
248
|
+
return parseCsrfToken(rawCsrfToken, options);
|
|
186
249
|
}
|
|
187
250
|
|
|
188
251
|
/**
|
|
@@ -192,19 +255,17 @@ export function getCurrentCsrfToken(
|
|
|
192
255
|
* @category Auth : Client
|
|
193
256
|
*/
|
|
194
257
|
export function wipeCurrentCsrfToken(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}> = {},
|
|
258
|
+
options: Readonly<CsrfHeaderNameOption> &
|
|
259
|
+
PartialWithUndefined<{
|
|
260
|
+
/**
|
|
261
|
+
* Allows mocking or overriding the global `localStorage`.
|
|
262
|
+
*
|
|
263
|
+
* @default globalThis.localStorage
|
|
264
|
+
*/
|
|
265
|
+
localStorage: Pick<Storage, 'removeItem'>;
|
|
266
|
+
}>,
|
|
205
267
|
) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
overrides.csrfHeaderName || AuthHeaderName.CsrfToken,
|
|
268
|
+
return (options.localStorage || globalThis.localStorage).removeItem(
|
|
269
|
+
resolveCsrfHeaderName(options),
|
|
209
270
|
);
|
|
210
271
|
}
|
package/src/hash.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {assertWrap} from '@augment-vir/assert';
|
|
1
2
|
import {
|
|
2
3
|
type AnyObject,
|
|
3
4
|
mergeDefinedProperties,
|
|
@@ -12,8 +13,8 @@ import {argon2id, argon2Verify, type IArgon2Options} from 'hash-wasm';
|
|
|
12
13
|
*/
|
|
13
14
|
export const defaultHashOptions: HashPasswordOptions = {
|
|
14
15
|
hashLength: 32,
|
|
15
|
-
iterations:
|
|
16
|
-
memorySize:
|
|
16
|
+
iterations: 2,
|
|
17
|
+
memorySize: 19_456,
|
|
17
18
|
parallelism: 1,
|
|
18
19
|
};
|
|
19
20
|
|
|
@@ -41,13 +42,15 @@ export async function hashPassword(
|
|
|
41
42
|
): Promise<string> {
|
|
42
43
|
const salt = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
const hash = await argon2id(
|
|
45
46
|
mergeDefinedProperties<AnyObject>(defaultHashOptions, options, {
|
|
46
47
|
outputType: 'encoded',
|
|
47
48
|
password: password.normalize(),
|
|
48
49
|
salt,
|
|
49
50
|
}) as IArgon2Options,
|
|
50
51
|
);
|
|
52
|
+
|
|
53
|
+
return assertWrap.isTruthy(hash);
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/**
|
|
@@ -76,6 +79,6 @@ export async function doesPasswordMatchHash({
|
|
|
76
79
|
}): Promise<boolean> {
|
|
77
80
|
return await argon2Verify({
|
|
78
81
|
hash,
|
|
79
|
-
password,
|
|
82
|
+
password: password.normalize(),
|
|
80
83
|
});
|
|
81
84
|
}
|
package/src/headers.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './auth-client/backend-auth.client.js';
|
|
2
2
|
export * from './auth-client/frontend-auth.client.js';
|
|
3
|
+
export * from './auth-client/is-session-refresh-ready.js';
|
|
3
4
|
export * from './auth.js';
|
|
4
5
|
export * from './cookie.js';
|
|
5
6
|
export * from './csrf-token.js';
|
|
@@ -8,5 +9,4 @@ export * from './headers.js';
|
|
|
8
9
|
export * from './jwt/jwt-keys.js';
|
|
9
10
|
export * from './jwt/jwt.js';
|
|
10
11
|
export * from './jwt/user-jwt.js';
|
|
11
|
-
export * from './log.js';
|
|
12
12
|
export * from './mock-local-storage.js';
|
package/src/jwt/jwt.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {type AnyObject, type PartialWithUndefined} from '@augment-vir/common';
|
|
|
3
3
|
import {
|
|
4
4
|
type AnyDuration,
|
|
5
5
|
calculateRelativeDate,
|
|
6
|
+
convertDuration,
|
|
6
7
|
createFullDateInUserTimezone,
|
|
7
8
|
createUtcFullDate,
|
|
8
9
|
type DateLike,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
type UtcTimezone,
|
|
13
14
|
} from 'date-vir';
|
|
14
15
|
import {EncryptJWT, jwtDecrypt, jwtVerify, SignJWT} from 'jose';
|
|
16
|
+
import {defaultAllowedClockSkew} from '../csrf-token.js';
|
|
15
17
|
import {type JwtKeys} from './jwt-keys.js';
|
|
16
18
|
|
|
17
19
|
const encryptionProtectedHeader = {alg: 'dir', enc: 'A256GCM'};
|
|
@@ -137,7 +139,16 @@ export async function createJwt<JwtData extends AnyObject = AnyObject>(
|
|
|
137
139
|
*
|
|
138
140
|
* @category Internal
|
|
139
141
|
*/
|
|
140
|
-
export type ParseJwtParams = Readonly<Pick<CreateJwtParams, 'issuer' | 'audience' | 'jwtKeys'
|
|
142
|
+
export type ParseJwtParams = Readonly<Pick<CreateJwtParams, 'issuer' | 'audience' | 'jwtKeys'>> &
|
|
143
|
+
PartialWithUndefined<{
|
|
144
|
+
/**
|
|
145
|
+
* Allowed clock skew tolerance for JWT expiration and timestamp checks. Accounts for
|
|
146
|
+
* differences between server and client clocks.
|
|
147
|
+
*
|
|
148
|
+
* @default {minutes: 5}
|
|
149
|
+
*/
|
|
150
|
+
allowedClockSkew: Readonly<AnyDuration>;
|
|
151
|
+
}>;
|
|
141
152
|
|
|
142
153
|
/**
|
|
143
154
|
* A fully parsed JWT with embedded data.
|
|
@@ -147,6 +158,8 @@ export type ParseJwtParams = Readonly<Pick<CreateJwtParams, 'issuer' | 'audience
|
|
|
147
158
|
export type ParsedJwt<JwtData extends AnyObject> = {
|
|
148
159
|
data: JwtData;
|
|
149
160
|
jwtExpiration: FullDate<UtcTimezone>;
|
|
161
|
+
/** When the JWT was issued (`iat` claim). */
|
|
162
|
+
jwtIssuedAt: FullDate<UtcTimezone>;
|
|
150
163
|
};
|
|
151
164
|
|
|
152
165
|
/**
|
|
@@ -167,6 +180,11 @@ export async function parseJwt<JwtData extends AnyObject = AnyObject>(
|
|
|
167
180
|
throw new TypeError('Decrypted jwt is not a string.');
|
|
168
181
|
}
|
|
169
182
|
|
|
183
|
+
const clockToleranceSeconds = convertDuration(
|
|
184
|
+
params.allowedClockSkew || defaultAllowedClockSkew,
|
|
185
|
+
{seconds: true},
|
|
186
|
+
).seconds;
|
|
187
|
+
|
|
170
188
|
const verifiedJwt = await jwtVerify(decryptedJwt.payload.jwt, params.jwtKeys.signingKey, {
|
|
171
189
|
issuer: params.issuer,
|
|
172
190
|
audience: params.audience,
|
|
@@ -175,9 +193,13 @@ export async function parseJwt<JwtData extends AnyObject = AnyObject>(
|
|
|
175
193
|
'aud',
|
|
176
194
|
'iss',
|
|
177
195
|
],
|
|
196
|
+
clockTolerance: clockToleranceSeconds,
|
|
178
197
|
});
|
|
179
198
|
|
|
180
|
-
if (
|
|
199
|
+
if (
|
|
200
|
+
!verifiedJwt.payload.iat ||
|
|
201
|
+
verifiedJwt.payload.iat * 1000 > Date.now() + clockToleranceSeconds * 1000
|
|
202
|
+
) {
|
|
181
203
|
throw new Error('"iat" claim timestamp check failed');
|
|
182
204
|
}
|
|
183
205
|
|
|
@@ -187,15 +209,18 @@ export async function parseJwt<JwtData extends AnyObject = AnyObject>(
|
|
|
187
209
|
throw new Error('Invalid signing protected header.');
|
|
188
210
|
}
|
|
189
211
|
|
|
212
|
+
const issuedAtSeconds = assertWrap.isDefined(verifiedJwt.payload.iat, 'JWT has no issued at.');
|
|
190
213
|
const expirationSeconds = assertWrap.isDefined(
|
|
191
214
|
verifiedJwt.payload.exp,
|
|
192
215
|
'JWT has no expiration.',
|
|
193
216
|
);
|
|
194
217
|
|
|
218
|
+
const jwtIssuedAt: FullDate<UtcTimezone> = parseJwtTimestamp(issuedAtSeconds);
|
|
195
219
|
const jwtExpiration: FullDate<UtcTimezone> = parseJwtTimestamp(expirationSeconds);
|
|
196
220
|
|
|
197
221
|
return {
|
|
198
222
|
data: data as JwtData,
|
|
199
223
|
jwtExpiration,
|
|
224
|
+
jwtIssuedAt,
|
|
200
225
|
};
|
|
201
226
|
}
|
package/src/jwt/user-jwt.ts
CHANGED
|
@@ -62,7 +62,7 @@ export async function parseUserJwt(
|
|
|
62
62
|
encryptedJwt: string,
|
|
63
63
|
params: Readonly<ParseJwtParams>,
|
|
64
64
|
): Promise<ParsedJwt<JwtUserData> | undefined> {
|
|
65
|
-
const {data, jwtExpiration} = await parseJwt(encryptedJwt, params);
|
|
65
|
+
const {data, jwtExpiration, jwtIssuedAt} = await parseJwt(encryptedJwt, params);
|
|
66
66
|
|
|
67
67
|
if (!checkValidShape(data, userJwtDataShape)) {
|
|
68
68
|
throw new TypeError('Verified jwt has wrong data.');
|
|
@@ -71,5 +71,6 @@ export async function parseUserJwt(
|
|
|
71
71
|
return {
|
|
72
72
|
data,
|
|
73
73
|
jwtExpiration,
|
|
74
|
+
jwtIssuedAt,
|
|
74
75
|
};
|
|
75
76
|
}
|
package/dist/log.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Send logs to the console for debugging.
|
|
3
|
-
*
|
|
4
|
-
* @category Internal
|
|
5
|
-
*/
|
|
6
|
-
export declare function authLog(...params: any[]): void;
|
|
7
|
-
/**
|
|
8
|
-
* Set to `false` to disable logging.
|
|
9
|
-
*
|
|
10
|
-
* @category Internal
|
|
11
|
-
*/
|
|
12
|
-
export declare function setShouldLogAuth(value: boolean): void;
|
package/dist/log.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Send logs to the console for debugging.
|
|
3
|
-
*
|
|
4
|
-
* @category Internal
|
|
5
|
-
*/
|
|
6
|
-
export function authLog(...params) {
|
|
7
|
-
if (!shouldLogAuth) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
console.info(...params);
|
|
11
|
-
}
|
|
12
|
-
let shouldLogAuth = true;
|
|
13
|
-
/**
|
|
14
|
-
* Set to `false` to disable logging.
|
|
15
|
-
*
|
|
16
|
-
* @category Internal
|
|
17
|
-
*/
|
|
18
|
-
export function setShouldLogAuth(value) {
|
|
19
|
-
shouldLogAuth = value;
|
|
20
|
-
}
|
package/src/log.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Send logs to the console for debugging.
|
|
3
|
-
*
|
|
4
|
-
* @category Internal
|
|
5
|
-
*/
|
|
6
|
-
export function authLog(...params: any[]) {
|
|
7
|
-
if (!shouldLogAuth) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
console.info(...params);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let shouldLogAuth = true;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Set to `false` to disable logging.
|
|
17
|
-
*
|
|
18
|
-
* @category Internal
|
|
19
|
-
*/
|
|
20
|
-
export function setShouldLogAuth(value: boolean) {
|
|
21
|
-
shouldLogAuth = value;
|
|
22
|
-
}
|