auth-vir 5.0.2 → 5.0.4

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.
Files changed (49) hide show
  1. package/dist/auth-client/backend-auth.client.d.ts +263 -0
  2. package/dist/auth-client/backend-auth.client.js +398 -0
  3. package/dist/auth-client/frontend-auth.client.d.ts +113 -0
  4. package/dist/auth-client/frontend-auth.client.js +131 -0
  5. package/dist/auth-client/is-session-refresh-ready.d.ts +23 -0
  6. package/dist/auth-client/is-session-refresh-ready.js +21 -0
  7. package/dist/auth.d.ts +81 -0
  8. package/dist/auth.js +132 -0
  9. package/dist/cookie.d.ts +111 -0
  10. package/dist/cookie.js +137 -0
  11. package/dist/csrf-token.d.ts +33 -0
  12. package/dist/csrf-token.js +42 -0
  13. package/dist/generated/browser.d.ts +9 -0
  14. package/dist/generated/browser.js +17 -0
  15. package/dist/generated/client.d.ts +26 -0
  16. package/dist/generated/client.js +32 -0
  17. package/dist/generated/commonInputTypes.d.ts +122 -0
  18. package/dist/generated/commonInputTypes.js +1 -0
  19. package/dist/generated/enums.d.ts +1 -0
  20. package/dist/generated/enums.js +10 -0
  21. package/dist/generated/internal/class.d.ts +126 -0
  22. package/dist/generated/internal/class.js +85 -0
  23. package/dist/generated/internal/prismaNamespace.d.ts +545 -0
  24. package/dist/generated/internal/prismaNamespace.js +102 -0
  25. package/dist/generated/internal/prismaNamespaceBrowser.d.ts +75 -0
  26. package/dist/generated/internal/prismaNamespaceBrowser.js +70 -0
  27. package/dist/generated/models/User.d.ts +980 -0
  28. package/dist/generated/models/User.js +1 -0
  29. package/dist/generated/models.d.ts +2 -0
  30. package/dist/generated/models.js +1 -0
  31. package/dist/generated/shapes.gen.d.ts +8 -0
  32. package/dist/generated/shapes.gen.js +11 -0
  33. package/dist/hash.d.ts +42 -0
  34. package/dist/hash.js +52 -0
  35. package/dist/headers.d.ts +19 -0
  36. package/dist/headers.js +32 -0
  37. package/dist/index.d.ts +11 -0
  38. package/dist/index.js +11 -0
  39. package/dist/jwt/jwt-keys.d.ts +44 -0
  40. package/dist/jwt/jwt-keys.js +57 -0
  41. package/dist/jwt/jwt-keys.script.d.ts +1 -0
  42. package/dist/jwt/jwt-keys.script.js +3 -0
  43. package/dist/jwt/jwt.d.ts +126 -0
  44. package/dist/jwt/jwt.js +109 -0
  45. package/dist/jwt/user-jwt.d.ts +44 -0
  46. package/dist/jwt/user-jwt.js +53 -0
  47. package/package.json +4 -4
  48. package/src/auth-client/backend-auth.client.ts +3 -0
  49. package/src/auth.ts +5 -1
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export type * from './models/User.js';
2
+ export type * from './commonInputTypes.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { type UserId } from './models/User.js';
2
+ export declare const UserIdShape: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnsafe<UserId>>;
3
+ export declare const UserShape: import("object-shape-tester").Shape<{
4
+ id: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnsafe<UserId>>;
5
+ createdAt: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnsafe<`${number}-${number}-${number}T${number}:${number}:${number}.${number}Z`>>;
6
+ updatedAt: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnsafe<`${number}-${number}-${number}T${number}:${number}:${number}.${number}Z`>>;
7
+ name: string;
8
+ }>;
@@ -0,0 +1,11 @@
1
+ /** AUTO-GENERATED FILE. DO NOT EDIT DIRECTLY. */
2
+ // @ts-nocheck
3
+ import { defineShape, typedStringShape } from 'object-shape-tester';
4
+ import { utcIsoStringShape } from 'date-vir';
5
+ export const UserIdShape = typedStringShape();
6
+ export const UserShape = defineShape({
7
+ id: UserIdShape,
8
+ createdAt: utcIsoStringShape(),
9
+ updatedAt: utcIsoStringShape(),
10
+ name: '',
11
+ });
package/dist/hash.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { type PartialWithUndefined } from '@augment-vir/common';
2
+ import { type IArgon2Options } from 'hash-wasm';
3
+ /**
4
+ * Default value for {@link HashPasswordOptions}.
5
+ *
6
+ * @category Internal
7
+ */
8
+ export declare const defaultHashOptions: HashPasswordOptions;
9
+ /**
10
+ * Options for {@link hashPassword}.
11
+ *
12
+ * @category Internal
13
+ */
14
+ export type HashPasswordOptions = PartialWithUndefined<Omit<IArgon2Options, 'outputType' | 'salt' | 'password' | 'secret'>>;
15
+ /**
16
+ * Hashes a password using the Argon2id algorithm so passwords don't need to be stored in plain
17
+ * text. The output of this function is safe to store in a database for future credential
18
+ * comparisons.
19
+ *
20
+ * @category Auth : Host
21
+ * @returns The hashed password.
22
+ * @see https://en.wikipedia.org/wiki/Argon2
23
+ */
24
+ export declare function hashPassword(password: string, options?: HashPasswordOptions): Promise<string>;
25
+ /**
26
+ * A utility that provides more accurate string byte size than doing `string.length`.
27
+ *
28
+ * @category Internal
29
+ */
30
+ export declare function getByteLength(input: string): number;
31
+ /**
32
+ * Checks if the given password is a match by comparing it to the previously computed and stored
33
+ * hash.
34
+ *
35
+ * @category Auth : Host
36
+ */
37
+ export declare function doesPasswordMatchHash({ password, hash, }: {
38
+ /** The password entered by the user in their login attempt. */
39
+ password: string;
40
+ /** The stored password hash for that user. */
41
+ hash: string;
42
+ }): Promise<boolean>;
package/dist/hash.js ADDED
@@ -0,0 +1,52 @@
1
+ import { assertWrap } from '@augment-vir/assert';
2
+ import { mergeDefinedProperties, } from '@augment-vir/common';
3
+ import { argon2id, argon2Verify } from 'hash-wasm';
4
+ /**
5
+ * Default value for {@link HashPasswordOptions}.
6
+ *
7
+ * @category Internal
8
+ */
9
+ export const defaultHashOptions = {
10
+ hashLength: 32,
11
+ iterations: 2,
12
+ memorySize: 19_456,
13
+ parallelism: 1,
14
+ };
15
+ /**
16
+ * Hashes a password using the Argon2id algorithm so passwords don't need to be stored in plain
17
+ * text. The output of this function is safe to store in a database for future credential
18
+ * comparisons.
19
+ *
20
+ * @category Auth : Host
21
+ * @returns The hashed password.
22
+ * @see https://en.wikipedia.org/wiki/Argon2
23
+ */
24
+ export async function hashPassword(password, options = {}) {
25
+ const salt = globalThis.crypto.getRandomValues(new Uint8Array(16));
26
+ const hash = await argon2id(mergeDefinedProperties(defaultHashOptions, options, {
27
+ outputType: 'encoded',
28
+ password: password.normalize(),
29
+ salt,
30
+ }));
31
+ return assertWrap.isTruthy(hash);
32
+ }
33
+ /**
34
+ * A utility that provides more accurate string byte size than doing `string.length`.
35
+ *
36
+ * @category Internal
37
+ */
38
+ export function getByteLength(input) {
39
+ return new Blob([input]).size;
40
+ }
41
+ /**
42
+ * Checks if the given password is a match by comparing it to the previously computed and stored
43
+ * hash.
44
+ *
45
+ * @category Auth : Host
46
+ */
47
+ export async function doesPasswordMatchHash({ password, hash, }) {
48
+ return await argon2Verify({
49
+ hash,
50
+ password: password.normalize(),
51
+ });
52
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * All custom headers used by auth-vir.
3
+ *
4
+ * @category Internal
5
+ */
6
+ export declare enum AuthHeaderName {
7
+ AssumedUser = "assumed-user",
8
+ /**
9
+ * Used to track if the current user is signed in only with a sign-up cookie, which prevents us
10
+ * from prematurely wiping their CSRF token.
11
+ */
12
+ IsSignUpAuth = "is-sign-up-auth"
13
+ }
14
+ /**
15
+ * Merges multiple header values into a single array of header values.
16
+ *
17
+ * @category Internal
18
+ */
19
+ export declare function mergeHeaderValues(...values: (string | string[] | undefined)[]): string[];
@@ -0,0 +1,32 @@
1
+ import { check } from '@augment-vir/assert';
2
+ /**
3
+ * All custom headers used by auth-vir.
4
+ *
5
+ * @category Internal
6
+ */
7
+ export var AuthHeaderName;
8
+ (function (AuthHeaderName) {
9
+ AuthHeaderName["AssumedUser"] = "assumed-user";
10
+ /**
11
+ * Used to track if the current user is signed in only with a sign-up cookie, which prevents us
12
+ * from prematurely wiping their CSRF token.
13
+ */
14
+ AuthHeaderName["IsSignUpAuth"] = "is-sign-up-auth";
15
+ })(AuthHeaderName || (AuthHeaderName = {}));
16
+ /**
17
+ * Merges multiple header values into a single array of header values.
18
+ *
19
+ * @category Internal
20
+ */
21
+ export function mergeHeaderValues(...values) {
22
+ const finalHeaderValues = [];
23
+ values.forEach((value) => {
24
+ if (check.isArray(value)) {
25
+ finalHeaderValues.push(...value);
26
+ }
27
+ else if (check.isString(value)) {
28
+ finalHeaderValues.push(value);
29
+ }
30
+ });
31
+ return finalHeaderValues;
32
+ }
@@ -0,0 +1,11 @@
1
+ export * from './auth-client/backend-auth.client.js';
2
+ export * from './auth-client/frontend-auth.client.js';
3
+ export * from './auth-client/is-session-refresh-ready.js';
4
+ export * from './auth.js';
5
+ export * from './cookie.js';
6
+ export * from './csrf-token.js';
7
+ export * from './hash.js';
8
+ export * from './headers.js';
9
+ export * from './jwt/jwt-keys.js';
10
+ export * from './jwt/jwt.js';
11
+ export * from './jwt/user-jwt.js';
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export * from './auth-client/backend-auth.client.js';
2
+ export * from './auth-client/frontend-auth.client.js';
3
+ export * from './auth-client/is-session-refresh-ready.js';
4
+ export * from './auth.js';
5
+ export * from './cookie.js';
6
+ export * from './csrf-token.js';
7
+ export * from './hash.js';
8
+ export * from './headers.js';
9
+ export * from './jwt/jwt-keys.js';
10
+ export * from './jwt/jwt.js';
11
+ export * from './jwt/user-jwt.js';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The keys required to sign and encrypt the JWT in their raw form for storage in a secure secrets
3
+ * database (such as AWS Secrets Manager) for later parsing by {@link parseJwtKeys}.
4
+ *
5
+ * These keys should be kept secret and never shared with any frontend, client, etc.
6
+ *
7
+ * @category Internal
8
+ */
9
+ export type RawJwtKeys = Readonly<{
10
+ encryptionKey: string;
11
+ signingKey: string;
12
+ }>;
13
+ /**
14
+ * The keys required to sign and encrypt the JWT.
15
+ *
16
+ * These keys should be kept secret and never shared with any frontend, client, etc.
17
+ *
18
+ * @category Internal
19
+ */
20
+ export type JwtKeys = Readonly<{
21
+ /**
22
+ * Encryption key for JWTs. This is a Uint8Array because `EncryptJWT.encrypt` does not support
23
+ * `CryptoKey` for our chosen encryption algorithm.
24
+ */
25
+ encryptionKey: Readonly<Uint8Array>;
26
+ /** Signing key for JWTs. */
27
+ signingKey: Readonly<CryptoKey>;
28
+ }>;
29
+ /**
30
+ * Generate fresh and serialized JWT signing and encryption keys. These should be stored in a secure
31
+ * secrets database (such as AWS Secrets Manager) for later parsing by {@link parseJwtKeys}.
32
+ *
33
+ * These keys should be kept secret and never shared with any frontend, client, etc.
34
+ *
35
+ * @category Keys
36
+ */
37
+ export declare function generateNewJwtKeys(): Promise<RawJwtKeys>;
38
+ /**
39
+ * Parses an instance of {@link RawJwtKeys} and produces the final {@link JwtKeys} object required by
40
+ * all authentication functionality.
41
+ *
42
+ * @category Keys
43
+ */
44
+ export declare function parseJwtKeys(rawKeys: Readonly<RawJwtKeys>): Promise<Readonly<JwtKeys>>;
@@ -0,0 +1,57 @@
1
+ import { assertWrap } from '@augment-vir/assert';
2
+ import { base64url } from 'jose';
3
+ const signingKeyOptions = [
4
+ {
5
+ name: 'HMAC',
6
+ hash: 'SHA-512',
7
+ },
8
+ true,
9
+ [
10
+ 'sign',
11
+ 'verify',
12
+ ],
13
+ ];
14
+ /**
15
+ * Generate fresh and serialized JWT signing and encryption keys. These should be stored in a secure
16
+ * secrets database (such as AWS Secrets Manager) for later parsing by {@link parseJwtKeys}.
17
+ *
18
+ * These keys should be kept secret and never shared with any frontend, client, etc.
19
+ *
20
+ * @category Keys
21
+ */
22
+ export async function generateNewJwtKeys() {
23
+ return {
24
+ encryptionKey: assertWrap.isDefined((await globalThis.crypto.subtle.exportKey('jwk', await globalThis.crypto.subtle.generateKey({
25
+ name: 'AES-GCM',
26
+ length: 256,
27
+ }, true, [
28
+ 'encrypt',
29
+ 'decrypt',
30
+ ]))).k),
31
+ signingKey: assertWrap.isDefined((await globalThis.crypto.subtle.exportKey('jwk', await globalThis.crypto.subtle.generateKey(...signingKeyOptions))).k),
32
+ };
33
+ }
34
+ /**
35
+ * Parses an instance of {@link RawJwtKeys} and produces the final {@link JwtKeys} object required by
36
+ * all authentication functionality.
37
+ *
38
+ * @category Keys
39
+ */
40
+ export async function parseJwtKeys(rawKeys) {
41
+ if (!rawKeys.encryptionKey) {
42
+ throw new Error('JWT encryption key is empty');
43
+ }
44
+ else if (!rawKeys.signingKey) {
45
+ throw new Error('JWT signing key is empty');
46
+ }
47
+ return {
48
+ encryptionKey: base64url.decode(rawKeys.encryptionKey),
49
+ signingKey: await crypto.subtle.importKey('jwk', {
50
+ k: rawKeys.signingKey,
51
+ alg: 'HS512',
52
+ ext: signingKeyOptions[1],
53
+ key_ops: [...signingKeyOptions[2]],
54
+ kty: 'oct',
55
+ }, ...signingKeyOptions),
56
+ };
57
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { stringifyWithJson5 } from '@augment-vir/common';
2
+ import { generateNewJwtKeys } from './jwt-keys.js';
3
+ console.info(stringifyWithJson5(await generateNewJwtKeys()));
@@ -0,0 +1,126 @@
1
+ import { type AnyObject, type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
2
+ import { type AnyDuration, type DateLike, type FullDate, type UtcTimezone } from 'date-vir';
3
+ import { type JwtKeys } from './jwt-keys.js';
4
+ /**
5
+ * Default allowed clock skew for JWT expiration checks. Accounts for differences between server and
6
+ * client clocks.
7
+ *
8
+ * @category Internal
9
+ * @default {minutes: 5}
10
+ */
11
+ export declare const defaultAllowedClockSkew: Readonly<AnyDuration>;
12
+ /**
13
+ * Params for {@link createJwt}.
14
+ *
15
+ * @category Internal
16
+ */
17
+ export type CreateJwtParams = Readonly<{
18
+ /**
19
+ * The keys required to sign and encrypt the JWT.
20
+ *
21
+ * These keys should be kept secret and never shared with any frontend, client, etc.
22
+ */
23
+ jwtKeys: Readonly<JwtKeys>;
24
+ /**
25
+ * The name of the company, the name of the service, or the URL to the service that originally
26
+ * issued the JWT. The same value must be used when creating and parsing a JWT or the parse will
27
+ * fail.
28
+ *
29
+ * This name can be anything you want.
30
+ *
31
+ * @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
32
+ */
33
+ issuer: string;
34
+ /**
35
+ * The arbitrary name or URL of the client intended to consume the JWT. The host and client must
36
+ * both know this name in order for the token to be signed and read correctly.
37
+ *
38
+ * This name can be anything you want.
39
+ *
40
+ * @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3
41
+ */
42
+ audience: string;
43
+ /**
44
+ * The duration until the JWT expires.
45
+ *
46
+ * @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
47
+ */
48
+ jwtDuration: Readonly<AnyDuration>;
49
+ }> & Readonly<PartialWithUndefined<{
50
+ /**
51
+ * Set a custom issued at date.
52
+ *
53
+ * This should usually not be overridden.
54
+ *
55
+ * @default Date.now()
56
+ * @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6
57
+ */
58
+ issuedAt: DateLike;
59
+ /**
60
+ * Set a custom date for when the JWT will become valid. The JWT will be considered
61
+ * invalid and not be processed until this date.
62
+ *
63
+ * This should usually not be overridden.
64
+ *
65
+ * @default
66
+ * none, the JWT will be immediately valid
67
+ * @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5
68
+ */
69
+ notValidUntil: DateLike;
70
+ }>>;
71
+ /**
72
+ * JWT uses seconds since the epoch per RFC 7519, whereas `toTimestamp` uses milliseconds.
73
+ *
74
+ * @category Internal
75
+ */
76
+ export declare function toJwtTimestamp(date: Readonly<FullDate>): number;
77
+ /**
78
+ * Converts a JWT timestamp (in seconds) into a FullDate instance.
79
+ *
80
+ * @category Internal
81
+ */
82
+ export declare function parseJwtTimestamp(seconds: number): FullDate<UtcTimezone>;
83
+ /**
84
+ * Creates a signed and encrypted JWT that contains the given data.
85
+ *
86
+ * @category Internal
87
+ */
88
+ export declare function createJwt<JwtData extends AnyObject = AnyObject>(
89
+ /** The data to be included in the JWT. */
90
+ data: JwtData, params: Readonly<CreateJwtParams>): Promise<string>;
91
+ /**
92
+ * Params for {@link parseJwt}.
93
+ *
94
+ * @category Internal
95
+ */
96
+ export type ParseJwtParams = Readonly<SelectFrom<CreateJwtParams, {
97
+ issuer: true;
98
+ audience: true;
99
+ jwtKeys: true;
100
+ }>> & PartialWithUndefined<{
101
+ /**
102
+ * Allowed clock skew tolerance for JWT expiration and timestamp checks. Accounts for
103
+ * differences between server and client clocks.
104
+ *
105
+ * @default {minutes: 5}
106
+ */
107
+ allowedClockSkew: Readonly<AnyDuration>;
108
+ }>;
109
+ /**
110
+ * A fully parsed JWT with embedded data.
111
+ *
112
+ * @category Internal
113
+ */
114
+ export type ParsedJwt<JwtData extends AnyObject> = {
115
+ data: JwtData;
116
+ jwtExpiration: FullDate<UtcTimezone>;
117
+ /** When the JWT was issued (`iat` claim). */
118
+ jwtIssuedAt: FullDate<UtcTimezone>;
119
+ };
120
+ /**
121
+ * Parse and extract all data from an encrypted and signed JWT.
122
+ *
123
+ * @category Internal
124
+ * @throws Errors if the decryption, signature verification, or other JWT requirements fail
125
+ */
126
+ export declare function parseJwt<JwtData extends AnyObject = AnyObject>(encryptedJwt: string, params: Readonly<ParseJwtParams>): Promise<ParsedJwt<JwtData>>;
@@ -0,0 +1,109 @@
1
+ import { assertWrap, check } from '@augment-vir/assert';
2
+ import { calculateRelativeDate, convertDuration, createFullDateInUserTimezone, createUtcFullDate, getNowInUtcTimezone, toTimestamp, } from 'date-vir';
3
+ import { EncryptJWT, jwtDecrypt, jwtVerify, SignJWT } from 'jose';
4
+ const encryptionProtectedHeader = {
5
+ alg: 'dir',
6
+ enc: 'A256GCM',
7
+ };
8
+ const signingProtectedHeader = {
9
+ alg: 'HS512',
10
+ };
11
+ /**
12
+ * Default allowed clock skew for JWT expiration checks. Accounts for differences between server and
13
+ * client clocks.
14
+ *
15
+ * @category Internal
16
+ * @default {minutes: 5}
17
+ */
18
+ export const defaultAllowedClockSkew = {
19
+ minutes: 5,
20
+ };
21
+ /**
22
+ * JWT uses seconds since the epoch per RFC 7519, whereas `toTimestamp` uses milliseconds.
23
+ *
24
+ * @category Internal
25
+ */
26
+ export function toJwtTimestamp(date) {
27
+ return Math.floor(toTimestamp(date) / 1000);
28
+ }
29
+ /**
30
+ * Converts a JWT timestamp (in seconds) into a FullDate instance.
31
+ *
32
+ * @category Internal
33
+ */
34
+ export function parseJwtTimestamp(seconds) {
35
+ return createUtcFullDate(seconds * 1000);
36
+ }
37
+ /**
38
+ * Creates a signed and encrypted JWT that contains the given data.
39
+ *
40
+ * @category Internal
41
+ */
42
+ export async function createJwt(
43
+ /** The data to be included in the JWT. */
44
+ data, params) {
45
+ const rawJwt = new SignJWT({
46
+ data,
47
+ })
48
+ .setProtectedHeader(signingProtectedHeader)
49
+ .setIssuedAt(params.issuedAt
50
+ ? toJwtTimestamp(createFullDateInUserTimezone(params.issuedAt))
51
+ : undefined)
52
+ .setIssuer(params.issuer)
53
+ .setAudience(params.audience)
54
+ .setExpirationTime(toJwtTimestamp(calculateRelativeDate(getNowInUtcTimezone(), params.jwtDuration)));
55
+ if (params.notValidUntil) {
56
+ rawJwt.setNotBefore(toJwtTimestamp(createFullDateInUserTimezone(params.notValidUntil)));
57
+ }
58
+ const signedJwt = await rawJwt.sign(params.jwtKeys.signingKey);
59
+ return await new EncryptJWT({
60
+ jwt: signedJwt,
61
+ })
62
+ .setProtectedHeader(encryptionProtectedHeader)
63
+ .encrypt(params.jwtKeys.encryptionKey);
64
+ }
65
+ /**
66
+ * Parse and extract all data from an encrypted and signed JWT.
67
+ *
68
+ * @category Internal
69
+ * @throws Errors if the decryption, signature verification, or other JWT requirements fail
70
+ */
71
+ export async function parseJwt(encryptedJwt, params) {
72
+ const decryptedJwt = await jwtDecrypt(encryptedJwt, params.jwtKeys.encryptionKey);
73
+ if (!check.deepEquals(decryptedJwt.protectedHeader, encryptionProtectedHeader)) {
74
+ throw new Error('Invalid encryption protected header.');
75
+ }
76
+ else if (!check.isString(decryptedJwt.payload.jwt)) {
77
+ throw new TypeError('Decrypted jwt is not a string.');
78
+ }
79
+ const clockToleranceSeconds = convertDuration(params.allowedClockSkew || defaultAllowedClockSkew, {
80
+ seconds: true,
81
+ }).seconds;
82
+ const verifiedJwt = await jwtVerify(decryptedJwt.payload.jwt, params.jwtKeys.signingKey, {
83
+ issuer: params.issuer,
84
+ audience: params.audience,
85
+ requiredClaims: [
86
+ 'iat',
87
+ 'aud',
88
+ 'iss',
89
+ ],
90
+ clockTolerance: clockToleranceSeconds,
91
+ });
92
+ if (!verifiedJwt.payload.iat ||
93
+ verifiedJwt.payload.iat * 1000 > Date.now() + clockToleranceSeconds * 1000) {
94
+ throw new Error('"iat" claim timestamp check failed');
95
+ }
96
+ const data = verifiedJwt.payload.data;
97
+ if (!check.deepEquals(verifiedJwt.protectedHeader, signingProtectedHeader)) {
98
+ throw new Error('Invalid signing protected header.');
99
+ }
100
+ const issuedAtSeconds = assertWrap.isDefined(verifiedJwt.payload.iat, 'JWT has no issued at.');
101
+ const expirationSeconds = assertWrap.isDefined(verifiedJwt.payload.exp, 'JWT has no expiration.');
102
+ const jwtIssuedAt = parseJwtTimestamp(issuedAtSeconds);
103
+ const jwtExpiration = parseJwtTimestamp(expirationSeconds);
104
+ return {
105
+ data: data,
106
+ jwtExpiration,
107
+ jwtIssuedAt,
108
+ };
109
+ }
@@ -0,0 +1,44 @@
1
+ import { type CreateJwtParams, type ParsedJwt, type ParseJwtParams } from './jwt.js';
2
+ /**
3
+ * Shape definition and source of truth for {@link JwtUserData}.
4
+ *
5
+ * @category Internal
6
+ */
7
+ export declare const userJwtDataShape: import("object-shape-tester").Shape<{
8
+ /** The id from your database of the user you're authenticating. */
9
+ userId: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnion<(import("@sinclair/typebox").TString | import("@sinclair/typebox").TNumber)[]>>;
10
+ /**
11
+ * CSRF token. This can be any cryptographically secure randomized string.
12
+ *
13
+ * Consider using {@link generateCsrfToken} to generate this.
14
+ */
15
+ csrfToken: string;
16
+ /**
17
+ * Unix timestamp (in milliseconds) when the session was originally started. This is used to
18
+ * enforce the max session duration. If not present, the session is considered to have started
19
+ * when the JWT was issued.
20
+ */
21
+ sessionStartedAt: import("object-shape-tester").Shape<import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TUndefined, import("@sinclair/typebox").TNumber]>>>;
22
+ }>;
23
+ /**
24
+ * Data required for user JWTs.
25
+ *
26
+ * @category Internal
27
+ */
28
+ export type JwtUserData = typeof userJwtDataShape.runtimeType;
29
+ /**
30
+ * Creates a new signed and encrypted {@link JwtUserData} when a client (frontend) successfully
31
+ * authenticates with the host (backend). This is used by host (backend) code to establish a new
32
+ * user session. The output of this function should be sent to the client (frontend) for storage.
33
+ *
34
+ * @category Internal
35
+ */
36
+ export declare function createUserJwt(data: Readonly<JwtUserData>, params: Readonly<CreateJwtParams>): Promise<string>;
37
+ /**
38
+ * Parses a {@link JwtUserData} generated from {@link createUserJwt}. This should be used on the host
39
+ * (backend) to a client (frontend) request. Do not use this function in client (frontend) code: it
40
+ * requires JWT signing keys which should not be shared with any client (frontend).
41
+ *
42
+ * @category Internal
43
+ */
44
+ export declare function parseUserJwt(encryptedJwt: string, params: Readonly<ParseJwtParams>): Promise<ParsedJwt<JwtUserData> | undefined>;
@@ -0,0 +1,53 @@
1
+ import { checkValidShape, defineShape, optionalShape, unionShape } from 'object-shape-tester';
2
+ import { createJwt, parseJwt, } from './jwt.js';
3
+ /**
4
+ * Shape definition and source of truth for {@link JwtUserData}.
5
+ *
6
+ * @category Internal
7
+ */
8
+ export const userJwtDataShape = defineShape({
9
+ /** The id from your database of the user you're authenticating. */
10
+ userId: unionShape('', -1),
11
+ /**
12
+ * CSRF token. This can be any cryptographically secure randomized string.
13
+ *
14
+ * Consider using {@link generateCsrfToken} to generate this.
15
+ */
16
+ csrfToken: '',
17
+ /**
18
+ * Unix timestamp (in milliseconds) when the session was originally started. This is used to
19
+ * enforce the max session duration. If not present, the session is considered to have started
20
+ * when the JWT was issued.
21
+ */
22
+ sessionStartedAt: optionalShape(0, {
23
+ alsoUndefined: true,
24
+ }),
25
+ });
26
+ /**
27
+ * Creates a new signed and encrypted {@link JwtUserData} when a client (frontend) successfully
28
+ * authenticates with the host (backend). This is used by host (backend) code to establish a new
29
+ * user session. The output of this function should be sent to the client (frontend) for storage.
30
+ *
31
+ * @category Internal
32
+ */
33
+ export async function createUserJwt(data, params) {
34
+ return await createJwt(data, params);
35
+ }
36
+ /**
37
+ * Parses a {@link JwtUserData} generated from {@link createUserJwt}. This should be used on the host
38
+ * (backend) to a client (frontend) request. Do not use this function in client (frontend) code: it
39
+ * requires JWT signing keys which should not be shared with any client (frontend).
40
+ *
41
+ * @category Internal
42
+ */
43
+ export async function parseUserJwt(encryptedJwt, params) {
44
+ const { data, jwtExpiration, jwtIssuedAt } = await parseJwt(encryptedJwt, params);
45
+ if (!checkValidShape(data, userJwtDataShape)) {
46
+ throw new TypeError('Verified jwt has wrong data.');
47
+ }
48
+ return {
49
+ data,
50
+ jwtExpiration,
51
+ jwtIssuedAt,
52
+ };
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth-vir",
3
- "version": "5.0.2",
3
+ "version": "5.0.4",
4
4
  "description": "Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.",
5
5
  "keywords": [
6
6
  "auth",
@@ -25,9 +25,9 @@
25
25
  "url": "https://github.com/electrovir"
26
26
  },
27
27
  "type": "module",
28
- "main": "src/index.ts",
29
- "module": "src/index.ts",
30
- "types": "src/index.ts",
28
+ "main": "dist/index.js",
29
+ "module": "dist/index.js",
30
+ "types": "dist/index.d.ts",
31
31
  "bin": "bin.js",
32
32
  "scripts": {
33
33
  "build": "virmator frontend build",