auth-vir 5.1.0 → 5.2.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 +8 -8
- package/dist/auth-client/backend-auth.client.d.ts +7 -2
- package/dist/auth-client/backend-auth.client.js +29 -5
- package/dist/auth-client/frontend-auth.client.d.ts +6 -2
- package/dist/auth-client/frontend-auth.client.js +6 -4
- package/dist/auth.d.ts +22 -9
- package/dist/auth.js +15 -5
- package/dist/cookie.d.ts +28 -5
- package/dist/cookie.js +22 -7
- package/dist/csrf-token.d.ts +2 -2
- package/dist/csrf-token.js +5 -4
- package/dist/generated/client.d.ts +4 -2
- package/dist/generated/client.js +5 -3
- package/dist/generated/internal/class.d.ts +17 -11
- package/dist/generated/internal/class.js +19 -52
- package/dist/generated/internal/prismaNamespace.d.ts +105 -51
- package/dist/generated/internal/prismaNamespace.js +10 -10
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +6 -21
- package/dist/generated/internal/prismaNamespaceBrowser.js +6 -6
- package/dist/generated/models/User.d.ts +6 -2
- package/dist/jwt/jwt-keys.js +13 -11
- package/package.json +20 -21
- package/src/auth-client/backend-auth.client.ts +43 -19
- package/src/auth-client/frontend-auth.client.ts +12 -7
- package/src/auth.ts +50 -24
- package/src/cookie.ts +55 -15
- package/src/csrf-token.ts +10 -5
- package/src/generated/client.ts +6 -6
- package/src/generated/internal/class.ts +35 -65
- package/src/generated/internal/prismaNamespace.ts +112 -41
- package/src/generated/internal/prismaNamespaceBrowser.ts +8 -6
- package/src/generated/models/User.ts +6 -1
- package/src/jwt/jwt-keys.ts +21 -16
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ For the easiest usage, construct and use `BackendAuthClient` on your server and
|
|
|
54
54
|
Use this on your host / server / backend to authenticate client / frontend requests.
|
|
55
55
|
|
|
56
56
|
1. Expose the [`AuthHeaderName.CsrfToken`](https://electrovir.github.io/auth-vir/variables/AuthHeaderName.html) (or just `'csrf-token'`) header via CORS headers with either of the following options:
|
|
57
|
-
1. Set `customHeaders: [AuthHeaderName.CsrfToken]` in `
|
|
57
|
+
1. Set `customHeaders: [AuthHeaderName.CsrfToken]` in `implementApi` from [`@rest-vir/host`](https://www.npmjs.com/package/@rest-vir/host).
|
|
58
58
|
2. Set the header `Access-Control-Allow-Headers` to (at least) `AuthHeaderName.CsrfToken`.
|
|
59
59
|
2. Set the `Access-Control-Allow-Origin` header (it cannot be `*`) and properly implement CORS headers and responses.
|
|
60
60
|
3. Generate JWT signing and encryption keys with one of the following:
|
|
@@ -156,12 +156,12 @@ export async function createUser(
|
|
|
156
156
|
*/
|
|
157
157
|
export async function getAuthenticatedUser(request: ClientRequest) {
|
|
158
158
|
const userId = (
|
|
159
|
-
await extractUserIdFromRequestHeaders<MyUserId>(
|
|
160
|
-
request.getHeaders(),
|
|
159
|
+
await extractUserIdFromRequestHeaders<MyUserId>({
|
|
160
|
+
headers: request.getHeaders(),
|
|
161
161
|
jwtParams,
|
|
162
|
-
csrfOption,
|
|
163
|
-
AuthCookie.Auth,
|
|
164
|
-
)
|
|
162
|
+
csrfHeaderNameOption: csrfOption,
|
|
163
|
+
cookieName: AuthCookie.Auth,
|
|
164
|
+
})
|
|
165
165
|
)?.userId;
|
|
166
166
|
const user = userId ? findUserInDatabaseById(userId) : undefined;
|
|
167
167
|
|
|
@@ -332,7 +332,7 @@ export async function sendAuthenticatedRequest(
|
|
|
332
332
|
});
|
|
333
333
|
|
|
334
334
|
if (response.status === HttpStatus.Unauthorized) {
|
|
335
|
-
throw new Error(
|
|
335
|
+
throw new Error('User no longer logged in.');
|
|
336
336
|
} else {
|
|
337
337
|
return response;
|
|
338
338
|
}
|
|
@@ -354,7 +354,7 @@ export async function logout(logoutUrl: string) {
|
|
|
354
354
|
All of these configurations must be set for the auth exports in this package to function properly:
|
|
355
355
|
|
|
356
356
|
- Expose the [`AuthHeaderName.CsrfToken`](https://electrovir.github.io/auth-vir/variables/AuthHeaderName.html) (or just `'csrf-token'`) header via CORS headers with either of the following options:
|
|
357
|
-
1. Set `customHeaders: [AuthHeaderName.CsrfToken]` in `
|
|
357
|
+
1. Set `customHeaders: [AuthHeaderName.CsrfToken]` in `implementApi` from [`@rest-vir/host`](https://www.npmjs.com/package/@rest-vir/host).
|
|
358
358
|
2. Set the header `Access-Control-Allow-Headers` to (at least) `AuthHeaderName.CsrfToken`.
|
|
359
359
|
- Set `credentials: include` in all fetch requests on the client that need to use or set the auth cookie.
|
|
360
360
|
- Server CORS should set `Access-Control-Allow-Origin` (it cannot be `*`).
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type AnyObject, type JsonCompatibleObject, type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
|
|
1
|
+
import { type AnyObject, type EmptyObject, type JsonCompatibleObject, type MaybePromise, type PartialWithUndefined, type RequireExactlyOne, type RequireOneOrNone } from '@augment-vir/common';
|
|
2
2
|
import { type AnyDuration } from 'date-vir';
|
|
3
3
|
import { type IncomingHttpHeaders, type OutgoingHttpHeaders } from 'node:http';
|
|
4
|
-
import { type EmptyObject, type RequireExactlyOne, type RequireOneOrNone } from 'type-fest';
|
|
5
4
|
import { type UserIdResult } from '../auth.js';
|
|
6
5
|
import { type CookieParams } from '../cookie.js';
|
|
7
6
|
import { type CsrfHeaderNameOption } from '../csrf-token.js';
|
|
@@ -150,6 +149,12 @@ export type BackendAuthClientConfig<DatabaseUser extends AnyObject, UserId exten
|
|
|
150
149
|
* JWT embedded in the `HttpOnly` auth cookie.
|
|
151
150
|
*/
|
|
152
151
|
csrfCookieOrigin: string;
|
|
152
|
+
/**
|
|
153
|
+
* Optional suffix appended to cookie names (e.g., `'staging'` produces `auth-staging`,
|
|
154
|
+
* `auth-vir-csrf-staging`). When `undefined`, cookie names are unchanged. Useful for
|
|
155
|
+
* running multiple environments on the same domain without cookie collisions.
|
|
156
|
+
*/
|
|
157
|
+
cookieNameSuffix: string;
|
|
153
158
|
}>>;
|
|
154
159
|
/**
|
|
155
160
|
* An auth client for creating and validating JWTs embedded in cookies. This should only be used in
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ensureArray, } from '@augment-vir/common';
|
|
2
2
|
import { calculateRelativeDate, createUtcFullDate, getNowInUtcTimezone, isDateAfter, } from 'date-vir';
|
|
3
3
|
import { extractUserIdFromRequestHeaders, generateLogoutHeaders, insecureExtractUserIdFromCookieAlone, } from '../auth.js';
|
|
4
|
-
import { AuthCookie, clearCsrfCookie, generateAuthCookie, generateCsrfCookie, } from '../cookie.js';
|
|
4
|
+
import { AuthCookie, clearCsrfCookie, generateAuthCookie, generateCsrfCookie, resolveCookieName, } from '../cookie.js';
|
|
5
5
|
import { generateCsrfToken } from '../csrf-token.js';
|
|
6
6
|
import { AuthHeaderName, mergeHeaderValues } from '../headers.js';
|
|
7
7
|
import { parseJwtKeys } from '../jwt/jwt-keys.js';
|
|
@@ -64,6 +64,7 @@ export class BackendAuthClient {
|
|
|
64
64
|
jwtParams: await this.getJwtParams(),
|
|
65
65
|
isDev: this.config.isDev,
|
|
66
66
|
authCookie: isSignUpCookie ? AuthCookie.SignUp : AuthCookie.Auth,
|
|
67
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
/** Calls the provided `getUserFromDatabase` config. */
|
|
@@ -154,6 +155,7 @@ export class BackendAuthClient {
|
|
|
154
155
|
const csrfCookie = generateCsrfCookie(userIdResult.csrfToken, {
|
|
155
156
|
...cookieParams,
|
|
156
157
|
hostOrigin: this.resolveCsrfCookieOrigin(cookieParams.hostOrigin),
|
|
158
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
157
159
|
});
|
|
158
160
|
return {
|
|
159
161
|
'set-cookie': [
|
|
@@ -197,7 +199,13 @@ export class BackendAuthClient {
|
|
|
197
199
|
}
|
|
198
200
|
/** Securely extract a user from their request headers. */
|
|
199
201
|
async getSecureUser({ requestHeaders, isSignUpCookie, allowUserAuthRefresh, }) {
|
|
200
|
-
const userIdResult = await extractUserIdFromRequestHeaders(
|
|
202
|
+
const userIdResult = await extractUserIdFromRequestHeaders({
|
|
203
|
+
headers: requestHeaders,
|
|
204
|
+
jwtParams: await this.getJwtParams(),
|
|
205
|
+
csrfHeaderNameOption: this.config.csrf,
|
|
206
|
+
cookieName: isSignUpCookie ? AuthCookie.SignUp : AuthCookie.Auth,
|
|
207
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
208
|
+
});
|
|
201
209
|
if (!userIdResult) {
|
|
202
210
|
this.logForUser({
|
|
203
211
|
user: undefined,
|
|
@@ -244,6 +252,7 @@ export class BackendAuthClient {
|
|
|
244
252
|
const csrfCookie = generateCsrfCookie(userIdResult.csrfToken, {
|
|
245
253
|
hostOrigin: this.resolveCsrfCookieOrigin(authCookieOrigin),
|
|
246
254
|
isDev: this.config.isDev,
|
|
255
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
247
256
|
});
|
|
248
257
|
return {
|
|
249
258
|
user: assumedUser || user,
|
|
@@ -302,6 +311,7 @@ export class BackendAuthClient {
|
|
|
302
311
|
? clearCsrfCookie({
|
|
303
312
|
hostOrigin: this.config.csrfCookieOrigin,
|
|
304
313
|
isDev: this.config.isDev,
|
|
314
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
305
315
|
})
|
|
306
316
|
: undefined;
|
|
307
317
|
return {
|
|
@@ -321,6 +331,7 @@ export class BackendAuthClient {
|
|
|
321
331
|
const csrfCookie = generateCsrfCookie(existingUserIdResult.csrfToken, {
|
|
322
332
|
...cookieParams,
|
|
323
333
|
hostOrigin: this.resolveCsrfCookieOrigin(cookieParams.hostOrigin),
|
|
334
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
324
335
|
});
|
|
325
336
|
return {
|
|
326
337
|
'set-cookie': [
|
|
@@ -340,6 +351,7 @@ export class BackendAuthClient {
|
|
|
340
351
|
const csrfCookie = generateCsrfCookie(csrfToken, {
|
|
341
352
|
...cookieParams,
|
|
342
353
|
hostOrigin: this.resolveCsrfCookieOrigin(cookieParams.hostOrigin),
|
|
354
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
343
355
|
});
|
|
344
356
|
return {
|
|
345
357
|
'set-cookie': [
|
|
@@ -351,7 +363,8 @@ export class BackendAuthClient {
|
|
|
351
363
|
/** Use these headers to log a user in. */
|
|
352
364
|
async createLoginHeaders({ userId, requestHeaders, isSignUpCookie, }) {
|
|
353
365
|
const oppositeCookieName = isSignUpCookie ? AuthCookie.Auth : AuthCookie.SignUp;
|
|
354
|
-
const
|
|
366
|
+
const resolvedOppositeCookieName = resolveCookieName(oppositeCookieName, this.config.cookieNameSuffix);
|
|
367
|
+
const hasExistingOppositeCookie = requestHeaders.cookie?.includes(`${resolvedOppositeCookieName}=`);
|
|
355
368
|
const discardOppositeCookieHeaders = hasExistingOppositeCookie
|
|
356
369
|
? generateLogoutHeaders(await this.getCookieParams({
|
|
357
370
|
isSignUpCookie: !isSignUpCookie,
|
|
@@ -360,7 +373,13 @@ export class BackendAuthClient {
|
|
|
360
373
|
preserveCsrf: true,
|
|
361
374
|
})
|
|
362
375
|
: undefined;
|
|
363
|
-
const existingUserIdResult = await extractUserIdFromRequestHeaders(
|
|
376
|
+
const existingUserIdResult = await extractUserIdFromRequestHeaders({
|
|
377
|
+
headers: requestHeaders,
|
|
378
|
+
jwtParams: await this.getJwtParams(),
|
|
379
|
+
csrfHeaderNameOption: this.config.csrf,
|
|
380
|
+
cookieName: isSignUpCookie ? AuthCookie.SignUp : AuthCookie.Auth,
|
|
381
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
382
|
+
});
|
|
364
383
|
const cookieParams = await this.getCookieParams({
|
|
365
384
|
isSignUpCookie,
|
|
366
385
|
requestHeaders,
|
|
@@ -405,7 +424,12 @@ export class BackendAuthClient {
|
|
|
405
424
|
*/
|
|
406
425
|
async getInsecureUser({ requestHeaders, allowUserAuthRefresh, }) {
|
|
407
426
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
408
|
-
const userIdResult = await insecureExtractUserIdFromCookieAlone(
|
|
427
|
+
const userIdResult = await insecureExtractUserIdFromCookieAlone({
|
|
428
|
+
headers: requestHeaders,
|
|
429
|
+
jwtParams: await this.getJwtParams(),
|
|
430
|
+
cookieName: AuthCookie.Auth,
|
|
431
|
+
cookieNameSuffix: this.config.cookieNameSuffix,
|
|
432
|
+
});
|
|
409
433
|
if (!userIdResult) {
|
|
410
434
|
this.logForUser({
|
|
411
435
|
user: undefined,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type createBlockingInterval, type JsonCompatibleObject, type MaybePromise, type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
|
|
1
|
+
import { type createBlockingInterval, type EmptyObject, type JsonCompatibleObject, type MaybePromise, type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
|
|
2
2
|
import { type AnyDuration } from 'date-vir';
|
|
3
|
-
import { type EmptyObject } from 'type-fest';
|
|
4
3
|
import { type CsrfHeaderNameOption } from '../csrf-token.js';
|
|
5
4
|
/**
|
|
6
5
|
* Config for {@link FrontendAuthClient}.
|
|
@@ -10,6 +9,11 @@ import { type CsrfHeaderNameOption } from '../csrf-token.js';
|
|
|
10
9
|
export type FrontendAuthClientConfig = Readonly<{
|
|
11
10
|
csrf: Readonly<CsrfHeaderNameOption>;
|
|
12
11
|
}> & PartialWithUndefined<{
|
|
12
|
+
/**
|
|
13
|
+
* Optional suffix appended to cookie names (e.g., `'staging'` produces
|
|
14
|
+
* `auth-vir-csrf-staging`). When `undefined`, cookie names are unchanged.
|
|
15
|
+
*/
|
|
16
|
+
cookieNameSuffix: string;
|
|
13
17
|
/**
|
|
14
18
|
* Determine if the current user can assume the identity of another user. If this is not
|
|
15
19
|
* defined, all users will be blocked from assuming other user identities.
|
|
@@ -53,11 +53,13 @@ export class FrontendAuthClient {
|
|
|
53
53
|
localStorage.removeItem(storageKey);
|
|
54
54
|
return true;
|
|
55
55
|
}
|
|
56
|
-
else if (
|
|
56
|
+
else if (await this.config.canAssumeUser?.()) {
|
|
57
|
+
localStorage.setItem(storageKey, JSON.stringify(assumedUserParams));
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
57
61
|
return false;
|
|
58
62
|
}
|
|
59
|
-
localStorage.setItem(storageKey, JSON.stringify(assumedUserParams));
|
|
60
|
-
return true;
|
|
61
63
|
}
|
|
62
64
|
/** Gets the assumed user params stored in local storage, if any. */
|
|
63
65
|
getAssumedUser() {
|
|
@@ -79,7 +81,7 @@ export class FrontendAuthClient {
|
|
|
79
81
|
* combine them with these.
|
|
80
82
|
*/
|
|
81
83
|
createAuthenticatedRequestInit() {
|
|
82
|
-
const csrfToken = getCurrentCsrfToken();
|
|
84
|
+
const csrfToken = getCurrentCsrfToken(this.config.cookieNameSuffix);
|
|
83
85
|
const assumedUser = this.getAssumedUser();
|
|
84
86
|
const headers = {
|
|
85
87
|
...(csrfToken
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type SelectFrom } from '@augment-vir/common';
|
|
1
|
+
import { type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
|
|
2
2
|
import { type FullDate, type UtcTimezone } from 'date-vir';
|
|
3
|
-
import { AuthCookie, type CookieParams } from './cookie.js';
|
|
3
|
+
import { type AuthCookie, type CookieParams } from './cookie.js';
|
|
4
4
|
import { type CsrfHeaderNameOption } from './csrf-token.js';
|
|
5
5
|
import { type ParseJwtParams } from './jwt/jwt.js';
|
|
6
6
|
import { type JwtUserData } from './jwt/user-jwt.js';
|
|
@@ -37,7 +37,13 @@ export type UserIdResult<UserId extends string | number> = {
|
|
|
37
37
|
* @category Auth : Host
|
|
38
38
|
* @returns The extracted user id or `undefined` if no valid auth headers exist.
|
|
39
39
|
*/
|
|
40
|
-
export declare function extractUserIdFromRequestHeaders<UserId extends string | number>(headers
|
|
40
|
+
export declare function extractUserIdFromRequestHeaders<UserId extends string | number>({ headers, jwtParams, csrfHeaderNameOption, cookieName, cookieNameSuffix, }: Readonly<{
|
|
41
|
+
headers: HeaderContainer;
|
|
42
|
+
jwtParams: Readonly<ParseJwtParams>;
|
|
43
|
+
csrfHeaderNameOption: Readonly<CsrfHeaderNameOption>;
|
|
44
|
+
cookieName: AuthCookie;
|
|
45
|
+
cookieNameSuffix?: string | undefined;
|
|
46
|
+
}>): Promise<Readonly<UserIdResult<UserId>> | undefined>;
|
|
41
47
|
/**
|
|
42
48
|
* Extract a user id from just the cookie, without CSRF token validation. This is _less secure_ than
|
|
43
49
|
* {@link extractUserIdFromRequestHeaders} as a result. This should only be used in rare
|
|
@@ -46,7 +52,12 @@ export declare function extractUserIdFromRequestHeaders<UserId extends string |
|
|
|
46
52
|
* @deprecated Prefer {@link extractUserIdFromRequestHeaders} instead: it is more secure.
|
|
47
53
|
* @category Auth : Host
|
|
48
54
|
*/
|
|
49
|
-
export declare function insecureExtractUserIdFromCookieAlone<UserId extends string | number>(headers
|
|
55
|
+
export declare function insecureExtractUserIdFromCookieAlone<UserId extends string | number>({ headers, jwtParams, cookieName, cookieNameSuffix, }: Readonly<{
|
|
56
|
+
headers: HeaderContainer;
|
|
57
|
+
jwtParams: Readonly<ParseJwtParams>;
|
|
58
|
+
cookieName: AuthCookie;
|
|
59
|
+
cookieNameSuffix?: string | undefined;
|
|
60
|
+
}>): Promise<Readonly<UserIdResult<UserId>> | undefined>;
|
|
50
61
|
/**
|
|
51
62
|
* Used by host (backend) code to set headers on a response object. Sets both the auth JWT cookie
|
|
52
63
|
* and the CSRF token cookie. The CSRF cookie is not `HttpOnly` so that frontend JavaScript can read
|
|
@@ -71,11 +82,13 @@ sessionStartedAt?: number | undefined): Promise<Record<string, string[]>>;
|
|
|
71
82
|
export declare function generateLogoutHeaders(cookieConfig: Readonly<SelectFrom<CookieParams, {
|
|
72
83
|
hostOrigin: true;
|
|
73
84
|
isDev: true;
|
|
74
|
-
}
|
|
85
|
+
}>> & PartialWithUndefined<{
|
|
86
|
+
cookieNameSuffix: string;
|
|
87
|
+
}>, options?: Readonly<PartialWithUndefined<{
|
|
75
88
|
/**
|
|
76
|
-
* When `true`, the CSRF cookie is preserved (not cleared). Use this when clearing only
|
|
77
|
-
* cookie type (e.g., the auth cookie) while keeping the other active session (e.g.,
|
|
89
|
+
* When `true`, the CSRF cookie is preserved (not cleared). Use this when clearing only
|
|
90
|
+
* one cookie type (e.g., the auth cookie) while keeping the other active session (e.g.,
|
|
78
91
|
* sign-up) that still needs its CSRF token.
|
|
79
92
|
*/
|
|
80
|
-
preserveCsrf
|
|
81
|
-
}
|
|
93
|
+
preserveCsrf: boolean;
|
|
94
|
+
}>>): Record<string, string[]>;
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { clearAuthCookie, clearCsrfCookie, extractCookieJwt, generateAuthCookie, generateCsrfCookie, } from './cookie.js';
|
|
2
2
|
import { generateCsrfToken, resolveCsrfHeaderName } from './csrf-token.js';
|
|
3
3
|
function readHeader(headers, headerName) {
|
|
4
4
|
if (headers instanceof Headers) {
|
|
@@ -28,14 +28,19 @@ function readCsrfTokenHeader(headers, csrfHeaderNameOption) {
|
|
|
28
28
|
* @category Auth : Host
|
|
29
29
|
* @returns The extracted user id or `undefined` if no valid auth headers exist.
|
|
30
30
|
*/
|
|
31
|
-
export async function extractUserIdFromRequestHeaders(headers, jwtParams, csrfHeaderNameOption, cookieName
|
|
31
|
+
export async function extractUserIdFromRequestHeaders({ headers, jwtParams, csrfHeaderNameOption, cookieName, cookieNameSuffix, }) {
|
|
32
32
|
try {
|
|
33
33
|
const csrfToken = readCsrfTokenHeader(headers, csrfHeaderNameOption);
|
|
34
34
|
const cookie = readHeader(headers, 'cookie');
|
|
35
35
|
if (!cookie || !csrfToken) {
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
38
|
-
const jwt = await extractCookieJwt(
|
|
38
|
+
const jwt = await extractCookieJwt({
|
|
39
|
+
rawCookie: cookie,
|
|
40
|
+
jwtParams,
|
|
41
|
+
cookieName,
|
|
42
|
+
cookieNameSuffix,
|
|
43
|
+
});
|
|
39
44
|
if (!jwt || jwt.data.csrfToken !== csrfToken) {
|
|
40
45
|
return undefined;
|
|
41
46
|
}
|
|
@@ -60,13 +65,18 @@ export async function extractUserIdFromRequestHeaders(headers, jwtParams, csrfHe
|
|
|
60
65
|
* @deprecated Prefer {@link extractUserIdFromRequestHeaders} instead: it is more secure.
|
|
61
66
|
* @category Auth : Host
|
|
62
67
|
*/
|
|
63
|
-
export async function insecureExtractUserIdFromCookieAlone(headers, jwtParams, cookieName) {
|
|
68
|
+
export async function insecureExtractUserIdFromCookieAlone({ headers, jwtParams, cookieName, cookieNameSuffix, }) {
|
|
64
69
|
try {
|
|
65
70
|
const cookie = readHeader(headers, 'cookie');
|
|
66
71
|
if (!cookie) {
|
|
67
72
|
return undefined;
|
|
68
73
|
}
|
|
69
|
-
const jwt = await extractCookieJwt(
|
|
74
|
+
const jwt = await extractCookieJwt({
|
|
75
|
+
rawCookie: cookie,
|
|
76
|
+
jwtParams,
|
|
77
|
+
cookieName,
|
|
78
|
+
cookieNameSuffix,
|
|
79
|
+
});
|
|
70
80
|
if (!jwt) {
|
|
71
81
|
return undefined;
|
|
72
82
|
}
|
package/dist/cookie.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
|
|
1
|
+
import { type PartialWithUndefined, type Primitive, type SelectFrom } from '@augment-vir/common';
|
|
2
2
|
import { type AnyDuration } from 'date-vir';
|
|
3
|
-
import { type Primitive } from 'type-fest';
|
|
4
3
|
import { type CreateJwtParams, type ParseJwtParams, type ParsedJwt } from './jwt/jwt.js';
|
|
5
4
|
import { type JwtUserData } from './jwt/user-jwt.js';
|
|
6
5
|
/**
|
|
@@ -16,6 +15,13 @@ export declare enum AuthCookie {
|
|
|
16
15
|
/** Used for storing the CSRF token. Not `HttpOnly` so that frontend JS can read it. */
|
|
17
16
|
Csrf = "auth-vir-csrf"
|
|
18
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolves a cookie name by appending a suffix when provided. When `cookieNameSuffix` is
|
|
20
|
+
* `undefined`, the base name is returned unchanged.
|
|
21
|
+
*
|
|
22
|
+
* @category Internal
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveCookieName(baseCookieName: AuthCookie, cookieNameSuffix?: string | undefined): string;
|
|
19
25
|
/**
|
|
20
26
|
* Parameters for {@link generateAuthCookie}.
|
|
21
27
|
*
|
|
@@ -54,6 +60,12 @@ export type CookieParams = {
|
|
|
54
60
|
* @default false
|
|
55
61
|
*/
|
|
56
62
|
isDev: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Optional suffix appended to cookie names (e.g., `'staging'` produces `auth-staging`). When
|
|
65
|
+
* `undefined`, cookie names are unchanged. Useful for running multiple environments on the same
|
|
66
|
+
* domain without cookie collisions.
|
|
67
|
+
*/
|
|
68
|
+
cookieNameSuffix: string;
|
|
57
69
|
}>;
|
|
58
70
|
/**
|
|
59
71
|
* Generate a secure cookie that stores the user JWT data. Used in host (backend) code.
|
|
@@ -75,7 +87,9 @@ export declare function generateAuthCookie(userJwtData: Readonly<JwtUserData>, c
|
|
|
75
87
|
export declare function generateCsrfCookie(csrfToken: string, cookieConfig: Readonly<SelectFrom<CookieParams, {
|
|
76
88
|
hostOrigin: true;
|
|
77
89
|
isDev: true;
|
|
78
|
-
}>>
|
|
90
|
+
}>> & PartialWithUndefined<{
|
|
91
|
+
cookieNameSuffix: string;
|
|
92
|
+
}>): string;
|
|
79
93
|
/**
|
|
80
94
|
* Generate a cookie value that will clear the previous auth cookie. Use this when signing out.
|
|
81
95
|
*
|
|
@@ -86,6 +100,7 @@ export declare function clearAuthCookie(cookieConfig: Readonly<SelectFrom<Cookie
|
|
|
86
100
|
isDev: true;
|
|
87
101
|
}>> & PartialWithUndefined<{
|
|
88
102
|
authCookie: AuthCookie;
|
|
103
|
+
cookieNameSuffix: string;
|
|
89
104
|
}>): string;
|
|
90
105
|
/**
|
|
91
106
|
* Generate a cookie value that will clear the CSRF token cookie. Use this when signing out.
|
|
@@ -95,7 +110,9 @@ export declare function clearAuthCookie(cookieConfig: Readonly<SelectFrom<Cookie
|
|
|
95
110
|
export declare function clearCsrfCookie(cookieConfig: Readonly<SelectFrom<CookieParams, {
|
|
96
111
|
hostOrigin: true;
|
|
97
112
|
isDev: true;
|
|
98
|
-
}>>
|
|
113
|
+
}>> & PartialWithUndefined<{
|
|
114
|
+
cookieNameSuffix: string;
|
|
115
|
+
}>): string;
|
|
99
116
|
/**
|
|
100
117
|
* Generate a cookie string from a raw set of parameters.
|
|
101
118
|
*
|
|
@@ -108,4 +125,10 @@ export declare function generateCookie(params: Readonly<Record<string, Exclude<P
|
|
|
108
125
|
* @category Internal
|
|
109
126
|
* @returns The extracted auth Cookie JWT data or `undefined` if no valid auth JWT data was found.
|
|
110
127
|
*/
|
|
111
|
-
export declare function extractCookieJwt(rawCookie
|
|
128
|
+
export declare function extractCookieJwt({ rawCookie, jwtParams, cookieName, cookieNameSuffix, }: {
|
|
129
|
+
rawCookie: string;
|
|
130
|
+
jwtParams: Readonly<ParseJwtParams>;
|
|
131
|
+
cookieName: AuthCookie;
|
|
132
|
+
} & PartialWithUndefined<{
|
|
133
|
+
cookieNameSuffix: string;
|
|
134
|
+
}>): Promise<undefined | ParsedJwt<JwtUserData>>;
|
package/dist/cookie.js
CHANGED
|
@@ -17,6 +17,20 @@ export var AuthCookie;
|
|
|
17
17
|
/** Used for storing the CSRF token. Not `HttpOnly` so that frontend JS can read it. */
|
|
18
18
|
AuthCookie["Csrf"] = "auth-vir-csrf";
|
|
19
19
|
})(AuthCookie || (AuthCookie = {}));
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a cookie name by appending a suffix when provided. When `cookieNameSuffix` is
|
|
22
|
+
* `undefined`, the base name is returned unchanged.
|
|
23
|
+
*
|
|
24
|
+
* @category Internal
|
|
25
|
+
*/
|
|
26
|
+
export function resolveCookieName(baseCookieName, cookieNameSuffix) {
|
|
27
|
+
return [
|
|
28
|
+
baseCookieName,
|
|
29
|
+
cookieNameSuffix,
|
|
30
|
+
]
|
|
31
|
+
.filter(check.isTruthy)
|
|
32
|
+
.join('-');
|
|
33
|
+
}
|
|
20
34
|
function generateSetCookie({ name, value, httpOnly, cookieConfig, }) {
|
|
21
35
|
return generateCookie({
|
|
22
36
|
[name]: value,
|
|
@@ -39,7 +53,7 @@ function generateSetCookie({ name, value, httpOnly, cookieConfig, }) {
|
|
|
39
53
|
*/
|
|
40
54
|
export async function generateAuthCookie(userJwtData, cookieConfig) {
|
|
41
55
|
return generateSetCookie({
|
|
42
|
-
name: cookieConfig.authCookie || AuthCookie.Auth,
|
|
56
|
+
name: resolveCookieName(cookieConfig.authCookie || AuthCookie.Auth, cookieConfig.cookieNameSuffix),
|
|
43
57
|
value: await createUserJwt(userJwtData, cookieConfig.jwtParams),
|
|
44
58
|
httpOnly: true,
|
|
45
59
|
cookieConfig,
|
|
@@ -58,7 +72,7 @@ export async function generateAuthCookie(userJwtData, cookieConfig) {
|
|
|
58
72
|
*/
|
|
59
73
|
export function generateCsrfCookie(csrfToken, cookieConfig) {
|
|
60
74
|
return generateSetCookie({
|
|
61
|
-
name: AuthCookie.Csrf,
|
|
75
|
+
name: resolveCookieName(AuthCookie.Csrf, cookieConfig.cookieNameSuffix),
|
|
62
76
|
value: csrfToken,
|
|
63
77
|
httpOnly: false,
|
|
64
78
|
cookieConfig: {
|
|
@@ -76,7 +90,7 @@ export function generateCsrfCookie(csrfToken, cookieConfig) {
|
|
|
76
90
|
*/
|
|
77
91
|
export function clearAuthCookie(cookieConfig) {
|
|
78
92
|
return generateSetCookie({
|
|
79
|
-
name: cookieConfig.authCookie || AuthCookie.Auth,
|
|
93
|
+
name: resolveCookieName(cookieConfig.authCookie || AuthCookie.Auth, cookieConfig.cookieNameSuffix),
|
|
80
94
|
value: 'redacted',
|
|
81
95
|
httpOnly: true,
|
|
82
96
|
cookieConfig,
|
|
@@ -89,7 +103,7 @@ export function clearAuthCookie(cookieConfig) {
|
|
|
89
103
|
*/
|
|
90
104
|
export function clearCsrfCookie(cookieConfig) {
|
|
91
105
|
return generateSetCookie({
|
|
92
|
-
name: AuthCookie.Csrf,
|
|
106
|
+
name: resolveCookieName(AuthCookie.Csrf, cookieConfig.cookieNameSuffix),
|
|
93
107
|
value: 'redacted',
|
|
94
108
|
httpOnly: false,
|
|
95
109
|
cookieConfig,
|
|
@@ -125,13 +139,14 @@ export function generateCookie(params) {
|
|
|
125
139
|
* @category Internal
|
|
126
140
|
* @returns The extracted auth Cookie JWT data or `undefined` if no valid auth JWT data was found.
|
|
127
141
|
*/
|
|
128
|
-
export async function extractCookieJwt(rawCookie, jwtParams, cookieName) {
|
|
129
|
-
const
|
|
142
|
+
export async function extractCookieJwt({ rawCookie, jwtParams, cookieName, cookieNameSuffix, }) {
|
|
143
|
+
const resolvedName = resolveCookieName(cookieName, cookieNameSuffix);
|
|
144
|
+
const cookieRegExp = new RegExp(`${escapeStringForRegExp(resolvedName)}=[^;]+(?:;|$)`);
|
|
130
145
|
const [cookieValue] = safeMatch(rawCookie, cookieRegExp);
|
|
131
146
|
if (!cookieValue) {
|
|
132
147
|
return undefined;
|
|
133
148
|
}
|
|
134
|
-
const rawJwt = cookieValue.replace(`${
|
|
149
|
+
const rawJwt = cookieValue.replace(`${resolvedName}=`, '').replace(';', '');
|
|
135
150
|
const jwt = await parseUserJwt(rawJwt, jwtParams);
|
|
136
151
|
return jwt;
|
|
137
152
|
}
|
package/dist/csrf-token.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RequireExactlyOne } from '
|
|
1
|
+
import { type RequireExactlyOne } from '@augment-vir/common';
|
|
2
2
|
/**
|
|
3
3
|
* Generates a random, cryptographically secure CSRF token string.
|
|
4
4
|
*
|
|
@@ -30,4 +30,4 @@ export declare function resolveCsrfHeaderName(options: Readonly<CsrfHeaderNameOp
|
|
|
30
30
|
*
|
|
31
31
|
* @category Auth : Client
|
|
32
32
|
*/
|
|
33
|
-
export declare function getCurrentCsrfToken(): string | undefined;
|
|
33
|
+
export declare function getCurrentCsrfToken(cookieNameSuffix?: string | undefined): string | undefined;
|
package/dist/csrf-token.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { check } from '@augment-vir/assert';
|
|
2
|
-
import { escapeStringForRegExp, randomString, safeMatch } from '@augment-vir/common';
|
|
3
|
-
import { AuthCookie } from './cookie.js';
|
|
2
|
+
import { escapeStringForRegExp, randomString, safeMatch, } from '@augment-vir/common';
|
|
3
|
+
import { AuthCookie, resolveCookieName } from './cookie.js';
|
|
4
4
|
/**
|
|
5
5
|
* Generates a random, cryptographically secure CSRF token string.
|
|
6
6
|
*
|
|
@@ -35,8 +35,9 @@ export function resolveCsrfHeaderName(options) {
|
|
|
35
35
|
*
|
|
36
36
|
* @category Auth : Client
|
|
37
37
|
*/
|
|
38
|
-
export function getCurrentCsrfToken() {
|
|
39
|
-
const
|
|
38
|
+
export function getCurrentCsrfToken(cookieNameSuffix) {
|
|
39
|
+
const resolvedName = resolveCookieName(AuthCookie.Csrf, cookieNameSuffix);
|
|
40
|
+
const cookieRegExp = new RegExp(`${escapeStringForRegExp(resolvedName)}=([^;]+)`);
|
|
40
41
|
const [, value,] = safeMatch(globalThis.document.cookie, cookieRegExp);
|
|
41
42
|
return value || undefined;
|
|
42
43
|
}
|
|
@@ -9,12 +9,14 @@ export * from "./enums.js";
|
|
|
9
9
|
* Type-safe database client for TypeScript
|
|
10
10
|
* @example
|
|
11
11
|
* ```
|
|
12
|
-
* const prisma = new PrismaClient(
|
|
12
|
+
* const prisma = new PrismaClient({
|
|
13
|
+
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
|
|
14
|
+
* })
|
|
13
15
|
* // Fetch zero or more Users
|
|
14
16
|
* const users = await prisma.user.findMany()
|
|
15
17
|
* ```
|
|
16
18
|
*
|
|
17
|
-
* Read more in our [docs](https://
|
|
19
|
+
* Read more in our [docs](https://pris.ly/d/client).
|
|
18
20
|
*/
|
|
19
21
|
export declare const PrismaClient: $Class.PrismaClientConstructor;
|
|
20
22
|
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>;
|
package/dist/generated/client.js
CHANGED
|
@@ -21,12 +21,14 @@ export * from "./enums.js";
|
|
|
21
21
|
* Type-safe database client for TypeScript
|
|
22
22
|
* @example
|
|
23
23
|
* ```
|
|
24
|
-
* const prisma = new PrismaClient(
|
|
24
|
+
* const prisma = new PrismaClient({
|
|
25
|
+
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
|
|
26
|
+
* })
|
|
25
27
|
* // Fetch zero or more Users
|
|
26
28
|
* const users = await prisma.user.findMany()
|
|
27
29
|
* ```
|
|
28
30
|
*
|
|
29
|
-
* Read more in our [docs](https://
|
|
31
|
+
* Read more in our [docs](https://pris.ly/d/client).
|
|
30
32
|
*/
|
|
31
|
-
export const PrismaClient = $Class.getPrismaClientClass(
|
|
33
|
+
export const PrismaClient = $Class.getPrismaClientClass();
|
|
32
34
|
export { Prisma };
|
|
@@ -8,16 +8,18 @@ export interface PrismaClientConstructor {
|
|
|
8
8
|
* Type-safe database client for TypeScript
|
|
9
9
|
* @example
|
|
10
10
|
* ```
|
|
11
|
-
* const prisma = new PrismaClient(
|
|
11
|
+
* const prisma = new PrismaClient({
|
|
12
|
+
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
|
|
13
|
+
* })
|
|
12
14
|
* // Fetch zero or more Users
|
|
13
15
|
* const users = await prisma.user.findMany()
|
|
14
16
|
* ```
|
|
15
17
|
*
|
|
16
|
-
* Read more in our [docs](https://
|
|
18
|
+
* Read more in our [docs](https://pris.ly/d/client).
|
|
17
19
|
*/
|
|
18
20
|
new <Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions, LogOpts extends LogOptions<Options> = LogOptions<Options>, OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends {
|
|
19
21
|
omit: infer U;
|
|
20
|
-
} ? U : Prisma.PrismaClientOptions['omit'], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs>(options
|
|
22
|
+
} ? U : Prisma.PrismaClientOptions['omit'], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs>(options: Prisma.PrismaClientConstructorArgs<Options>): PrismaClient<LogOpts, OmitOpts, ExtArgs>;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* ## Prisma Client
|
|
@@ -25,12 +27,14 @@ export interface PrismaClientConstructor {
|
|
|
25
27
|
* Type-safe database client for TypeScript
|
|
26
28
|
* @example
|
|
27
29
|
* ```
|
|
28
|
-
* const prisma = new PrismaClient(
|
|
30
|
+
* const prisma = new PrismaClient({
|
|
31
|
+
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
|
|
32
|
+
* })
|
|
29
33
|
* // Fetch zero or more Users
|
|
30
34
|
* const users = await prisma.user.findMany()
|
|
31
35
|
* ```
|
|
32
36
|
*
|
|
33
|
-
* Read more in our [docs](https://
|
|
37
|
+
* Read more in our [docs](https://pris.ly/d/client).
|
|
34
38
|
*/
|
|
35
39
|
export interface PrismaClient<in LogOpts extends Prisma.LogLevel = never, in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = Prisma.PrismaClientOptions['omit'], in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> {
|
|
36
40
|
[K: symbol]: {
|
|
@@ -52,7 +56,7 @@ export interface PrismaClient<in LogOpts extends Prisma.LogLevel = never, in out
|
|
|
52
56
|
* const result = await prisma.$executeRaw`UPDATE User SET cool = ${true} WHERE email = ${'user@email.com'};`
|
|
53
57
|
* ```
|
|
54
58
|
*
|
|
55
|
-
* Read more in our [docs](https://
|
|
59
|
+
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
|
56
60
|
*/
|
|
57
61
|
$executeRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<number>;
|
|
58
62
|
/**
|
|
@@ -63,7 +67,7 @@ export interface PrismaClient<in LogOpts extends Prisma.LogLevel = never, in out
|
|
|
63
67
|
* const result = await prisma.$executeRawUnsafe('UPDATE User SET cool = $1 WHERE email = $2 ;', true, 'user@email.com')
|
|
64
68
|
* ```
|
|
65
69
|
*
|
|
66
|
-
* Read more in our [docs](https://
|
|
70
|
+
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
|
67
71
|
*/
|
|
68
72
|
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>;
|
|
69
73
|
/**
|
|
@@ -73,7 +77,7 @@ export interface PrismaClient<in LogOpts extends Prisma.LogLevel = never, in out
|
|
|
73
77
|
* const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${1} OR email = ${'user@email.com'};`
|
|
74
78
|
* ```
|
|
75
79
|
*
|
|
76
|
-
* Read more in our [docs](https://
|
|
80
|
+
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
|
77
81
|
*/
|
|
78
82
|
$queryRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<T>;
|
|
79
83
|
/**
|
|
@@ -84,7 +88,7 @@ export interface PrismaClient<in LogOpts extends Prisma.LogLevel = never, in out
|
|
|
84
88
|
* const result = await prisma.$queryRawUnsafe('SELECT * FROM User WHERE id = $1 OR email = $2;', 1, 'user@email.com')
|
|
85
89
|
* ```
|
|
86
90
|
*
|
|
87
|
-
* Read more in our [docs](https://
|
|
91
|
+
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
|
88
92
|
*/
|
|
89
93
|
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>;
|
|
90
94
|
/**
|
|
@@ -98,9 +102,11 @@ export interface PrismaClient<in LogOpts extends Prisma.LogLevel = never, in out
|
|
|
98
102
|
* ])
|
|
99
103
|
* ```
|
|
100
104
|
*
|
|
101
|
-
* Read more in our [docs](https://www.prisma.io/docs/
|
|
105
|
+
* Read more in our [docs](https://www.prisma.io/docs/orm/prisma-client/queries/transactions).
|
|
102
106
|
*/
|
|
103
107
|
$transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: {
|
|
108
|
+
maxWait?: number;
|
|
109
|
+
timeout?: number;
|
|
104
110
|
isolationLevel?: Prisma.TransactionIsolationLevel;
|
|
105
111
|
}): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>;
|
|
106
112
|
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: {
|
|
@@ -123,4 +129,4 @@ export interface PrismaClient<in LogOpts extends Prisma.LogLevel = never, in out
|
|
|
123
129
|
omit: OmitOpts;
|
|
124
130
|
}>;
|
|
125
131
|
}
|
|
126
|
-
export declare function getPrismaClientClass(
|
|
132
|
+
export declare function getPrismaClientClass(): PrismaClientConstructor;
|