@spfn/auth 0.1.0-alpha.87 → 0.2.0-beta.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.
Files changed (151) hide show
  1. package/README.md +1385 -1199
  2. package/dist/config.d.ts +405 -0
  3. package/dist/config.js +240 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/dto-81uR9gzF.d.ts +630 -0
  6. package/dist/errors.d.ts +196 -0
  7. package/dist/errors.js +173 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +273 -14
  10. package/dist/index.js +511 -6665
  11. package/dist/index.js.map +1 -1
  12. package/dist/nextjs/api.js +345 -0
  13. package/dist/nextjs/api.js.map +1 -0
  14. package/dist/{adapters/nextjs → nextjs}/server.d.ts +47 -65
  15. package/dist/nextjs/server.js +179 -0
  16. package/dist/nextjs/server.js.map +1 -0
  17. package/dist/server.d.ts +4328 -529
  18. package/dist/server.js +7841 -1247
  19. package/dist/server.js.map +1 -1
  20. package/migrations/{0000_familiar_firebrand.sql → 0000_mysterious_colossus.sql} +53 -23
  21. package/migrations/meta/0000_snapshot.json +281 -46
  22. package/migrations/meta/_journal.json +2 -2
  23. package/package.json +33 -33
  24. package/dist/adapters/nextjs/api.d.ts +0 -446
  25. package/dist/adapters/nextjs/api.js +0 -3279
  26. package/dist/adapters/nextjs/api.js.map +0 -1
  27. package/dist/adapters/nextjs/server.js +0 -3645
  28. package/dist/adapters/nextjs/server.js.map +0 -1
  29. package/dist/lib/api/auth-codes-verify.d.ts +0 -37
  30. package/dist/lib/api/auth-codes-verify.js +0 -2949
  31. package/dist/lib/api/auth-codes-verify.js.map +0 -1
  32. package/dist/lib/api/auth-codes.d.ts +0 -37
  33. package/dist/lib/api/auth-codes.js +0 -2949
  34. package/dist/lib/api/auth-codes.js.map +0 -1
  35. package/dist/lib/api/auth-exists.d.ts +0 -38
  36. package/dist/lib/api/auth-exists.js +0 -2949
  37. package/dist/lib/api/auth-exists.js.map +0 -1
  38. package/dist/lib/api/auth-invitations-accept.d.ts +0 -38
  39. package/dist/lib/api/auth-invitations-accept.js +0 -2883
  40. package/dist/lib/api/auth-invitations-accept.js.map +0 -1
  41. package/dist/lib/api/auth-invitations-cancel.d.ts +0 -37
  42. package/dist/lib/api/auth-invitations-cancel.js +0 -2883
  43. package/dist/lib/api/auth-invitations-cancel.js.map +0 -1
  44. package/dist/lib/api/auth-invitations-delete.d.ts +0 -36
  45. package/dist/lib/api/auth-invitations-delete.js +0 -2883
  46. package/dist/lib/api/auth-invitations-delete.js.map +0 -1
  47. package/dist/lib/api/auth-invitations-resend.d.ts +0 -37
  48. package/dist/lib/api/auth-invitations-resend.js +0 -2883
  49. package/dist/lib/api/auth-invitations-resend.js.map +0 -1
  50. package/dist/lib/api/auth-invitations.d.ts +0 -109
  51. package/dist/lib/api/auth-invitations.js +0 -2887
  52. package/dist/lib/api/auth-invitations.js.map +0 -1
  53. package/dist/lib/api/auth-keys-rotate.d.ts +0 -37
  54. package/dist/lib/api/auth-keys-rotate.js +0 -2949
  55. package/dist/lib/api/auth-keys-rotate.js.map +0 -1
  56. package/dist/lib/api/auth-login.d.ts +0 -39
  57. package/dist/lib/api/auth-login.js +0 -2949
  58. package/dist/lib/api/auth-login.js.map +0 -1
  59. package/dist/lib/api/auth-logout.d.ts +0 -36
  60. package/dist/lib/api/auth-logout.js +0 -2949
  61. package/dist/lib/api/auth-logout.js.map +0 -1
  62. package/dist/lib/api/auth-me.d.ts +0 -50
  63. package/dist/lib/api/auth-me.js +0 -2949
  64. package/dist/lib/api/auth-me.js.map +0 -1
  65. package/dist/lib/api/auth-password.d.ts +0 -36
  66. package/dist/lib/api/auth-password.js +0 -2949
  67. package/dist/lib/api/auth-password.js.map +0 -1
  68. package/dist/lib/api/auth-register.d.ts +0 -38
  69. package/dist/lib/api/auth-register.js +0 -2949
  70. package/dist/lib/api/auth-register.js.map +0 -1
  71. package/dist/lib/api/index.d.ts +0 -356
  72. package/dist/lib/api/index.js +0 -3261
  73. package/dist/lib/api/index.js.map +0 -1
  74. package/dist/lib/config.d.ts +0 -70
  75. package/dist/lib/config.js +0 -64
  76. package/dist/lib/config.js.map +0 -1
  77. package/dist/lib/contracts/auth.d.ts +0 -302
  78. package/dist/lib/contracts/auth.js +0 -2951
  79. package/dist/lib/contracts/auth.js.map +0 -1
  80. package/dist/lib/contracts/index.d.ts +0 -3
  81. package/dist/lib/contracts/index.js +0 -3190
  82. package/dist/lib/contracts/index.js.map +0 -1
  83. package/dist/lib/contracts/invitation.d.ts +0 -243
  84. package/dist/lib/contracts/invitation.js +0 -2883
  85. package/dist/lib/contracts/invitation.js.map +0 -1
  86. package/dist/lib/crypto.d.ts +0 -76
  87. package/dist/lib/crypto.js +0 -127
  88. package/dist/lib/crypto.js.map +0 -1
  89. package/dist/lib/index.d.ts +0 -4
  90. package/dist/lib/index.js +0 -313
  91. package/dist/lib/index.js.map +0 -1
  92. package/dist/lib/session.d.ts +0 -68
  93. package/dist/lib/session.js +0 -126
  94. package/dist/lib/session.js.map +0 -1
  95. package/dist/lib/types/api.d.ts +0 -45
  96. package/dist/lib/types/api.js +0 -1
  97. package/dist/lib/types/api.js.map +0 -1
  98. package/dist/lib/types/index.d.ts +0 -3
  99. package/dist/lib/types/index.js +0 -2647
  100. package/dist/lib/types/index.js.map +0 -1
  101. package/dist/lib/types/schemas.d.ts +0 -45
  102. package/dist/lib/types/schemas.js +0 -2647
  103. package/dist/lib/types/schemas.js.map +0 -1
  104. package/dist/lib.js +0 -1
  105. package/dist/lib.js.map +0 -1
  106. package/dist/plugin.d.ts +0 -12
  107. package/dist/plugin.js +0 -9083
  108. package/dist/plugin.js.map +0 -1
  109. package/dist/server/entities/index.d.ts +0 -11
  110. package/dist/server/entities/index.js +0 -395
  111. package/dist/server/entities/index.js.map +0 -1
  112. package/dist/server/entities/invitations.d.ts +0 -241
  113. package/dist/server/entities/invitations.js +0 -184
  114. package/dist/server/entities/invitations.js.map +0 -1
  115. package/dist/server/entities/permissions.d.ts +0 -196
  116. package/dist/server/entities/permissions.js +0 -49
  117. package/dist/server/entities/permissions.js.map +0 -1
  118. package/dist/server/entities/role-permissions.d.ts +0 -107
  119. package/dist/server/entities/role-permissions.js +0 -115
  120. package/dist/server/entities/role-permissions.js.map +0 -1
  121. package/dist/server/entities/roles.d.ts +0 -196
  122. package/dist/server/entities/roles.js +0 -50
  123. package/dist/server/entities/roles.js.map +0 -1
  124. package/dist/server/entities/schema.d.ts +0 -14
  125. package/dist/server/entities/schema.js +0 -7
  126. package/dist/server/entities/schema.js.map +0 -1
  127. package/dist/server/entities/user-permissions.d.ts +0 -163
  128. package/dist/server/entities/user-permissions.js +0 -193
  129. package/dist/server/entities/user-permissions.js.map +0 -1
  130. package/dist/server/entities/user-public-keys.d.ts +0 -227
  131. package/dist/server/entities/user-public-keys.js +0 -156
  132. package/dist/server/entities/user-public-keys.js.map +0 -1
  133. package/dist/server/entities/user-social-accounts.d.ts +0 -189
  134. package/dist/server/entities/user-social-accounts.js +0 -149
  135. package/dist/server/entities/user-social-accounts.js.map +0 -1
  136. package/dist/server/entities/users.d.ts +0 -235
  137. package/dist/server/entities/users.js +0 -117
  138. package/dist/server/entities/users.js.map +0 -1
  139. package/dist/server/entities/verification-codes.d.ts +0 -191
  140. package/dist/server/entities/verification-codes.js +0 -49
  141. package/dist/server/entities/verification-codes.js.map +0 -1
  142. package/dist/server/routes/auth/index.d.ts +0 -10
  143. package/dist/server/routes/auth/index.js +0 -4460
  144. package/dist/server/routes/auth/index.js.map +0 -1
  145. package/dist/server/routes/index.d.ts +0 -6
  146. package/dist/server/routes/index.js +0 -6584
  147. package/dist/server/routes/index.js.map +0 -1
  148. package/dist/server/routes/invitations/index.d.ts +0 -10
  149. package/dist/server/routes/invitations/index.js +0 -4395
  150. package/dist/server/routes/invitations/index.js.map +0 -1
  151. /package/dist/{lib.d.ts → nextjs/api.d.ts} +0 -0
@@ -1,76 +0,0 @@
1
- import { Algorithm } from 'jsonwebtoken';
2
-
3
- /**
4
- * @spfn/auth - Client Crypto Helpers
5
- *
6
- * ES256 (ECDSA P-256) key generation and JWT signing for Next.js
7
- * Keys are stored in DER format (Base64 encoded) for efficiency
8
- *
9
- * Key Sizes:
10
- * - ES256 (ECDSA P-256): ~91 bytes (Base64: ~120 chars)
11
- * - RS256 (RSA 2048): ~294 bytes (Base64: ~392 chars)
12
- */
13
-
14
- type Unit = "Years" | "Year" | "Yrs" | "Yr" | "Y" | "Weeks" | "Week" | "W" | "Days" | "Day" | "D" | "Hours" | "Hour" | "Hrs" | "Hr" | "H" | "Minutes" | "Minute" | "Mins" | "Min" | "M" | "Seconds" | "Second" | "Secs" | "Sec" | "s" | "Milliseconds" | "Millisecond" | "Msecs" | "Msec" | "Ms";
15
- type UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>;
16
- type StringValue = `${number}` | `${number}${UnitAnyCase}` | `${number} ${UnitAnyCase}`;
17
- interface KeyPair {
18
- privateKey: string;
19
- publicKey: string;
20
- keyId: string;
21
- fingerprint: string;
22
- algorithm: 'ES256' | 'RS256';
23
- }
24
- interface TokenPayload {
25
- userId?: string;
26
- keyId?: string;
27
- timestamp?: number;
28
- exp?: number;
29
- iat?: number;
30
- iss?: string;
31
- [key: string]: any;
32
- }
33
- /**
34
- * Generate ECDSA P-256 key pair (ES256)
35
- * Recommended for optimal size and performance
36
- */
37
- declare function generateKeyPairES256(): KeyPair;
38
- /**
39
- * Generate RSA 2048 key pair (RS256)
40
- * Fallback option, larger size but wider compatibility
41
- */
42
- declare function generateKeyPairRS256(): KeyPair;
43
- /**
44
- * Generate key pair (defaults to ES256)
45
- */
46
- declare function generateKeyPair(algorithm?: 'ES256' | 'RS256'): KeyPair;
47
- /**
48
- * Generate JWT signed with client private key (DER format)
49
- */
50
- declare function generateClientToken(payload: Record<string, any>, privateKeyB64: string, algorithm: Algorithm, options?: {
51
- expiresIn?: StringValue | number;
52
- issuer?: string;
53
- }): string;
54
- /**
55
- * Verify JWT with public key (DER format)
56
- *
57
- * @internal Used for testing/validation only
58
- * For production verification, use server-side verifyClientToken
59
- */
60
- declare function verifyClientToken(token: string, publicKeyB64: string, algorithm: 'ES256' | 'RS256'): TokenPayload;
61
- /**
62
- * Get key size information
63
- */
64
- declare function getKeySize(publicKeyB64: string): {
65
- bytes: number;
66
- base64Length: number;
67
- };
68
- /**
69
- * Check if key should be rotated based on creation date
70
- */
71
- declare function shouldRotateKey(createdAt: Date, rotationDays?: number): {
72
- shouldRotate: boolean;
73
- daysRemaining: number;
74
- };
75
-
76
- export { type KeyPair, type TokenPayload, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, getKeySize, shouldRotateKey, verifyClientToken };
@@ -1,127 +0,0 @@
1
- // src/lib/crypto.ts
2
- import crypto from "crypto";
3
- import jwt from "jsonwebtoken";
4
- function generateKeyPairES256() {
5
- const keyId = crypto.randomUUID();
6
- const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", {
7
- namedCurve: "P-256",
8
- // ES256
9
- publicKeyEncoding: {
10
- type: "spki",
11
- format: "der"
12
- },
13
- privateKeyEncoding: {
14
- type: "pkcs8",
15
- format: "der"
16
- }
17
- });
18
- const privateKeyB64 = privateKey.toString("base64");
19
- const publicKeyB64 = publicKey.toString("base64");
20
- const fingerprint = crypto.createHash("sha256").update(publicKey).digest("hex");
21
- return {
22
- privateKey: privateKeyB64,
23
- publicKey: publicKeyB64,
24
- keyId,
25
- fingerprint,
26
- algorithm: "ES256"
27
- };
28
- }
29
- function generateKeyPairRS256() {
30
- const keyId = crypto.randomUUID();
31
- const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
32
- modulusLength: 2048,
33
- publicKeyEncoding: {
34
- type: "spki",
35
- format: "der"
36
- },
37
- privateKeyEncoding: {
38
- type: "pkcs8",
39
- format: "der"
40
- }
41
- });
42
- const privateKeyB64 = privateKey.toString("base64");
43
- const publicKeyB64 = publicKey.toString("base64");
44
- const fingerprint = crypto.createHash("sha256").update(publicKey).digest("hex");
45
- return {
46
- privateKey: privateKeyB64,
47
- publicKey: publicKeyB64,
48
- keyId,
49
- fingerprint,
50
- algorithm: "RS256"
51
- };
52
- }
53
- function generateKeyPair(algorithm = "ES256") {
54
- return algorithm === "ES256" ? generateKeyPairES256() : generateKeyPairRS256();
55
- }
56
- function generateClientToken(payload, privateKeyB64, algorithm, options) {
57
- try {
58
- const privateKeyDER = Buffer.from(privateKeyB64, "base64");
59
- const privateKeyObject = crypto.createPrivateKey({
60
- key: privateKeyDER,
61
- format: "der",
62
- type: "pkcs8"
63
- });
64
- const privateKeyPEM = privateKeyObject.export({
65
- type: "pkcs8",
66
- format: "pem"
67
- });
68
- const signOptions = {
69
- algorithm,
70
- issuer: options?.issuer || "spfn-client",
71
- expiresIn: options?.expiresIn ?? "15m"
72
- // Default to 15 minutes
73
- };
74
- return jwt.sign(payload, privateKeyPEM, signOptions);
75
- } catch (error) {
76
- throw new Error(
77
- `Failed to generate client token: ${error instanceof Error ? error.message : "Unknown error"}`
78
- );
79
- }
80
- }
81
- function verifyClientToken(token, publicKeyB64, algorithm) {
82
- try {
83
- const publicKeyDER = Buffer.from(publicKeyB64, "base64");
84
- const publicKeyObject = crypto.createPublicKey({
85
- key: publicKeyDER,
86
- format: "der",
87
- type: "spki"
88
- });
89
- return jwt.verify(token, publicKeyObject, {
90
- algorithms: [algorithm],
91
- issuer: "spfn-client"
92
- });
93
- } catch (error) {
94
- throw new Error(
95
- `Failed to verify token: ${error instanceof Error ? error.message : "Unknown error"}`
96
- );
97
- }
98
- }
99
- function getKeySize(publicKeyB64) {
100
- const keyDER = Buffer.from(publicKeyB64, "base64");
101
- return {
102
- bytes: keyDER.length,
103
- base64Length: publicKeyB64.length
104
- };
105
- }
106
- function shouldRotateKey(createdAt, rotationDays = 90) {
107
- const now = /* @__PURE__ */ new Date();
108
- const ageInDays = Math.floor(
109
- (now.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24)
110
- );
111
- const daysRemaining = Math.max(0, rotationDays - ageInDays);
112
- return {
113
- shouldRotate: daysRemaining <= 7,
114
- // Warn 7 days before expiry
115
- daysRemaining
116
- };
117
- }
118
- export {
119
- generateClientToken,
120
- generateKeyPair,
121
- generateKeyPairES256,
122
- generateKeyPairRS256,
123
- getKeySize,
124
- shouldRotateKey,
125
- verifyClientToken
126
- };
127
- //# sourceMappingURL=crypto.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/lib/crypto.ts"],"sourcesContent":["/**\n * @spfn/auth - Client Crypto Helpers\n *\n * ES256 (ECDSA P-256) key generation and JWT signing for Next.js\n * Keys are stored in DER format (Base64 encoded) for efficiency\n *\n * Key Sizes:\n * - ES256 (ECDSA P-256): ~91 bytes (Base64: ~120 chars)\n * - RS256 (RSA 2048): ~294 bytes (Base64: ~392 chars)\n */\n\nimport crypto from 'crypto';\nimport jwt, { type Algorithm, type SignOptions } from 'jsonwebtoken';\n\ntype Unit =\n | \"Years\"\n | \"Year\"\n | \"Yrs\"\n | \"Yr\"\n | \"Y\"\n | \"Weeks\"\n | \"Week\"\n | \"W\"\n | \"Days\"\n | \"Day\"\n | \"D\"\n | \"Hours\"\n | \"Hour\"\n | \"Hrs\"\n | \"Hr\"\n | \"H\"\n | \"Minutes\"\n | \"Minute\"\n | \"Mins\"\n | \"Min\"\n | \"M\"\n | \"Seconds\"\n | \"Second\"\n | \"Secs\"\n | \"Sec\"\n | \"s\"\n | \"Milliseconds\"\n | \"Millisecond\"\n | \"Msecs\"\n | \"Msec\"\n | \"Ms\";\n\ntype UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>;\n\ntype StringValue =\n | `${number}`\n | `${number}${UnitAnyCase}`\n | `${number} ${UnitAnyCase}`;\n\nexport interface KeyPair\n{\n privateKey: string; // Base64 encoded DER\n publicKey: string; // Base64 encoded DER\n keyId: string; // UUID\n fingerprint: string; // SHA-256 hash\n algorithm: 'ES256' | 'RS256';\n}\n\nexport interface TokenPayload\n{\n userId?: string;\n keyId?: string;\n timestamp?: number;\n exp?: number;\n iat?: number;\n iss?: string;\n [key: string]: any;\n}\n\n/**\n * Generate ECDSA P-256 key pair (ES256)\n * Recommended for optimal size and performance\n */\nexport function generateKeyPairES256(): KeyPair\n{\n const keyId = crypto.randomUUID();\n\n const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {\n namedCurve: 'P-256', // ES256\n publicKeyEncoding: {\n type: 'spki',\n format: 'der',\n },\n privateKeyEncoding: {\n type: 'pkcs8',\n format: 'der',\n },\n });\n\n // Convert Buffer to Base64\n const privateKeyB64 = privateKey.toString('base64');\n const publicKeyB64 = publicKey.toString('base64');\n\n // Generate fingerprint (SHA-256 of public key)\n const fingerprint = crypto\n .createHash('sha256')\n .update(publicKey)\n .digest('hex');\n\n return {\n privateKey: privateKeyB64,\n publicKey: publicKeyB64,\n keyId,\n fingerprint,\n algorithm: 'ES256',\n };\n}\n\n/**\n * Generate RSA 2048 key pair (RS256)\n * Fallback option, larger size but wider compatibility\n */\nexport function generateKeyPairRS256(): KeyPair\n{\n const keyId = crypto.randomUUID();\n\n const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {\n modulusLength: 2048,\n publicKeyEncoding: {\n type: 'spki',\n format: 'der',\n },\n privateKeyEncoding: {\n type: 'pkcs8',\n format: 'der',\n },\n });\n\n const privateKeyB64 = privateKey.toString('base64');\n const publicKeyB64 = publicKey.toString('base64');\n\n const fingerprint = crypto\n .createHash('sha256')\n .update(publicKey)\n .digest('hex');\n\n return {\n privateKey: privateKeyB64,\n publicKey: publicKeyB64,\n keyId,\n fingerprint,\n algorithm: 'RS256',\n };\n}\n\n/**\n * Generate key pair (defaults to ES256)\n */\nexport function generateKeyPair(\n algorithm: 'ES256' | 'RS256' = 'ES256'\n): KeyPair\n{\n return algorithm === 'ES256'\n ? generateKeyPairES256()\n : generateKeyPairRS256();\n}\n\n/**\n * Generate JWT signed with client private key (DER format)\n */\nexport function generateClientToken(\n payload: Record<string, any>,\n privateKeyB64: string,\n algorithm: Algorithm,\n options?: {\n expiresIn?: StringValue | number;\n issuer?: string;\n }\n): string\n{\n try\n {\n // Convert Base64 back to Buffer\n const privateKeyDER = Buffer.from(privateKeyB64, 'base64');\n\n // Create key object for signing\n const privateKeyObject = crypto.createPrivateKey({\n key: privateKeyDER,\n format: 'der',\n type: 'pkcs8',\n });\n\n // Export as PEM for jwt.sign\n const privateKeyPEM = privateKeyObject.export({\n type: 'pkcs8',\n format: 'pem',\n });\n\n const signOptions: SignOptions = {\n algorithm,\n issuer: options?.issuer || 'spfn-client',\n expiresIn: options?.expiresIn ?? '15m', // Default to 15 minutes\n }\n\n return jwt.sign(payload, privateKeyPEM, signOptions);\n }\n catch (error)\n {\n throw new Error(\n `Failed to generate client token: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Verify JWT with public key (DER format)\n *\n * @internal Used for testing/validation only\n * For production verification, use server-side verifyClientToken\n */\nexport function verifyClientToken(\n token: string,\n publicKeyB64: string,\n algorithm: 'ES256' | 'RS256'\n): TokenPayload\n{\n try\n {\n // Convert Base64 to Buffer\n const publicKeyDER = Buffer.from(publicKeyB64, 'base64');\n\n // Create key object for verification\n const publicKeyObject = crypto.createPublicKey({\n key: publicKeyDER,\n format: 'der',\n type: 'spki',\n });\n\n return jwt.verify(token, publicKeyObject, {\n algorithms: [algorithm],\n issuer: 'spfn-client',\n }) as TokenPayload;\n }\n catch (error)\n {\n throw new Error(\n `Failed to verify token: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Get key size information\n */\nexport function getKeySize(publicKeyB64: string): {\n bytes: number;\n base64Length: number;\n}\n{\n const keyDER = Buffer.from(publicKeyB64, 'base64');\n\n return {\n bytes: keyDER.length,\n base64Length: publicKeyB64.length,\n };\n}\n\n/**\n * Check if key should be rotated based on creation date\n */\nexport function shouldRotateKey(\n createdAt: Date,\n rotationDays: number = 90\n): {\n shouldRotate: boolean;\n daysRemaining: number;\n}\n{\n const now = new Date();\n const ageInDays = Math.floor(\n (now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24)\n );\n const daysRemaining = Math.max(0, rotationDays - ageInDays);\n\n return {\n shouldRotate: daysRemaining <= 7, // Warn 7 days before expiry\n daysRemaining,\n };\n}"],"mappings":"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","names":[]}
@@ -1,4 +0,0 @@
1
- export { KeyPair, TokenPayload, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, getKeySize, shouldRotateKey, verifyClientToken } from './crypto.js';
2
- export { SessionData, getSessionInfo, sealSession, shouldRefreshSession, unsealSession, validateSessionSecret } from './session.js';
3
- export { AuthConfig, COOKIE_NAMES, configureAuth, getAuthConfig, getSessionTtl, parseDuration } from './config.js';
4
- import 'jsonwebtoken';
package/dist/lib/index.js DELETED
@@ -1,313 +0,0 @@
1
- // src/lib/crypto.ts
2
- import crypto2 from "crypto";
3
- import jwt from "jsonwebtoken";
4
- function generateKeyPairES256() {
5
- const keyId = crypto2.randomUUID();
6
- const { privateKey, publicKey } = crypto2.generateKeyPairSync("ec", {
7
- namedCurve: "P-256",
8
- // ES256
9
- publicKeyEncoding: {
10
- type: "spki",
11
- format: "der"
12
- },
13
- privateKeyEncoding: {
14
- type: "pkcs8",
15
- format: "der"
16
- }
17
- });
18
- const privateKeyB64 = privateKey.toString("base64");
19
- const publicKeyB64 = publicKey.toString("base64");
20
- const fingerprint = crypto2.createHash("sha256").update(publicKey).digest("hex");
21
- return {
22
- privateKey: privateKeyB64,
23
- publicKey: publicKeyB64,
24
- keyId,
25
- fingerprint,
26
- algorithm: "ES256"
27
- };
28
- }
29
- function generateKeyPairRS256() {
30
- const keyId = crypto2.randomUUID();
31
- const { privateKey, publicKey } = crypto2.generateKeyPairSync("rsa", {
32
- modulusLength: 2048,
33
- publicKeyEncoding: {
34
- type: "spki",
35
- format: "der"
36
- },
37
- privateKeyEncoding: {
38
- type: "pkcs8",
39
- format: "der"
40
- }
41
- });
42
- const privateKeyB64 = privateKey.toString("base64");
43
- const publicKeyB64 = publicKey.toString("base64");
44
- const fingerprint = crypto2.createHash("sha256").update(publicKey).digest("hex");
45
- return {
46
- privateKey: privateKeyB64,
47
- publicKey: publicKeyB64,
48
- keyId,
49
- fingerprint,
50
- algorithm: "RS256"
51
- };
52
- }
53
- function generateKeyPair(algorithm = "ES256") {
54
- return algorithm === "ES256" ? generateKeyPairES256() : generateKeyPairRS256();
55
- }
56
- function generateClientToken(payload, privateKeyB64, algorithm, options) {
57
- try {
58
- const privateKeyDER = Buffer.from(privateKeyB64, "base64");
59
- const privateKeyObject = crypto2.createPrivateKey({
60
- key: privateKeyDER,
61
- format: "der",
62
- type: "pkcs8"
63
- });
64
- const privateKeyPEM = privateKeyObject.export({
65
- type: "pkcs8",
66
- format: "pem"
67
- });
68
- const signOptions = {
69
- algorithm,
70
- issuer: options?.issuer || "spfn-client",
71
- expiresIn: options?.expiresIn ?? "15m"
72
- // Default to 15 minutes
73
- };
74
- return jwt.sign(payload, privateKeyPEM, signOptions);
75
- } catch (error) {
76
- throw new Error(
77
- `Failed to generate client token: ${error instanceof Error ? error.message : "Unknown error"}`
78
- );
79
- }
80
- }
81
- function verifyClientToken(token, publicKeyB64, algorithm) {
82
- try {
83
- const publicKeyDER = Buffer.from(publicKeyB64, "base64");
84
- const publicKeyObject = crypto2.createPublicKey({
85
- key: publicKeyDER,
86
- format: "der",
87
- type: "spki"
88
- });
89
- return jwt.verify(token, publicKeyObject, {
90
- algorithms: [algorithm],
91
- issuer: "spfn-client"
92
- });
93
- } catch (error) {
94
- throw new Error(
95
- `Failed to verify token: ${error instanceof Error ? error.message : "Unknown error"}`
96
- );
97
- }
98
- }
99
- function getKeySize(publicKeyB64) {
100
- const keyDER = Buffer.from(publicKeyB64, "base64");
101
- return {
102
- bytes: keyDER.length,
103
- base64Length: publicKeyB64.length
104
- };
105
- }
106
- function shouldRotateKey(createdAt, rotationDays = 90) {
107
- const now = /* @__PURE__ */ new Date();
108
- const ageInDays = Math.floor(
109
- (now.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24)
110
- );
111
- const daysRemaining = Math.max(0, rotationDays - ageInDays);
112
- return {
113
- shouldRotate: daysRemaining <= 7,
114
- // Warn 7 days before expiry
115
- daysRemaining
116
- };
117
- }
118
-
119
- // src/lib/session.ts
120
- import * as jose from "jose";
121
- function calculateEntropy(str) {
122
- const len = str.length;
123
- const frequencies = /* @__PURE__ */ new Map();
124
- for (const char of str) {
125
- frequencies.set(char, (frequencies.get(char) || 0) + 1);
126
- }
127
- let entropy = 0;
128
- for (const count of frequencies.values()) {
129
- const probability = count / len;
130
- entropy -= probability * Math.log2(probability);
131
- }
132
- return entropy;
133
- }
134
- async function getSessionSecret() {
135
- const secret = process.env.SPFN_AUTH_SESSION_SECRET || // New prefixed version (recommended)
136
- process.env.SESSION_SECRET;
137
- if (!secret) {
138
- throw new Error("SPFN_AUTH_SESSION_SECRET environment variable is not set");
139
- }
140
- if (secret.length < 32) {
141
- throw new Error("SPFN_AUTH_SESSION_SECRET must be at least 32 characters long");
142
- }
143
- const encoder = new TextEncoder();
144
- const data = encoder.encode(secret);
145
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
146
- return new Uint8Array(hashBuffer);
147
- }
148
- async function sealSession(data, ttl = 60 * 60 * 24 * 7) {
149
- const secret = await getSessionSecret();
150
- return await new jose.EncryptJWT({ data }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${ttl}s`).setIssuer("spfn-auth").setAudience("spfn-client").encrypt(secret);
151
- }
152
- async function unsealSession(jwt2) {
153
- const secret = await getSessionSecret();
154
- try {
155
- const { payload } = await jose.jwtDecrypt(jwt2, secret, {
156
- issuer: "spfn-auth",
157
- audience: "spfn-client"
158
- });
159
- return payload.data;
160
- } catch (err) {
161
- if (err instanceof jose.errors.JWTExpired) {
162
- throw new Error("Session expired");
163
- }
164
- if (err instanceof jose.errors.JWEDecryptionFailed) {
165
- throw new Error("Invalid session");
166
- }
167
- if (err instanceof jose.errors.JWTClaimValidationFailed) {
168
- throw new Error("Session validation failed");
169
- }
170
- throw new Error("Failed to unseal session");
171
- }
172
- }
173
- async function getSessionInfo(jwt2) {
174
- const secret = await getSessionSecret();
175
- try {
176
- const { payload } = await jose.jwtDecrypt(jwt2, secret);
177
- return {
178
- issuedAt: new Date(payload.iat * 1e3),
179
- expiresAt: new Date(payload.exp * 1e3),
180
- issuer: payload.iss || "",
181
- audience: Array.isArray(payload.aud) ? payload.aud[0] : payload.aud || ""
182
- };
183
- } catch (err) {
184
- if (process.env.NODE_ENV !== "production") {
185
- console.warn("[Session] Failed to get session info:", err instanceof Error ? err.message : "Unknown error");
186
- }
187
- return null;
188
- }
189
- }
190
- async function shouldRefreshSession(jwt2, thresholdHours = 24) {
191
- const info = await getSessionInfo(jwt2);
192
- if (!info) {
193
- return true;
194
- }
195
- const hoursRemaining = (info.expiresAt.getTime() - Date.now()) / (1e3 * 60 * 60);
196
- return hoursRemaining < thresholdHours;
197
- }
198
- function validateSessionSecret() {
199
- try {
200
- const secret = process.env.SPFN_AUTH_SESSION_SECRET || // New prefixed version (recommended)
201
- process.env.SESSION_SECRET;
202
- if (!secret) {
203
- return { valid: false, error: "SPFN_AUTH_SESSION_SECRET is not set" };
204
- }
205
- const length = secret.length;
206
- const uniqueChars = new Set(secret).size;
207
- const entropy = calculateEntropy(secret);
208
- if (length < 32) {
209
- return {
210
- valid: false,
211
- error: `SPFN_AUTH_SESSION_SECRET too short (${length} chars, minimum 32)`,
212
- details: { length, uniqueChars, entropy }
213
- };
214
- }
215
- if (uniqueChars < 16) {
216
- return {
217
- valid: false,
218
- error: `SPFN_AUTH_SESSION_SECRET has low diversity (${uniqueChars} unique chars, minimum 16)`,
219
- details: { length, uniqueChars, entropy }
220
- };
221
- }
222
- if (entropy < 3.5) {
223
- return {
224
- valid: false,
225
- error: `SPFN_AUTH_SESSION_SECRET has low entropy (${entropy.toFixed(2)} bits/char, minimum 3.5). Use a more random secret.`,
226
- details: { length, uniqueChars, entropy }
227
- };
228
- }
229
- return {
230
- valid: true,
231
- details: { length, uniqueChars, entropy }
232
- };
233
- } catch (err) {
234
- return { valid: false, error: "Failed to validate SPFN_AUTH_SESSION_SECRET" };
235
- }
236
- }
237
-
238
- // src/lib/config.ts
239
- var COOKIE_NAMES = {
240
- /** Encrypted session data (userId, privateKey, keyId, algorithm) */
241
- SESSION: "spfn_session",
242
- /** Current key ID (for key rotation) */
243
- SESSION_KEY_ID: "spfn_session_key_id"
244
- };
245
- function parseDuration(duration) {
246
- if (typeof duration === "number") {
247
- return duration;
248
- }
249
- const match = duration.match(/^(\d+)([dhms]?)$/);
250
- if (!match) {
251
- throw new Error(`Invalid duration format: ${duration}. Use format like '30d', '12h', '45m', '3600s', or plain number.`);
252
- }
253
- const value = parseInt(match[1], 10);
254
- const unit = match[2] || "s";
255
- switch (unit) {
256
- case "d":
257
- return value * 24 * 60 * 60;
258
- case "h":
259
- return value * 60 * 60;
260
- case "m":
261
- return value * 60;
262
- case "s":
263
- return value;
264
- default:
265
- throw new Error(`Unknown duration unit: ${unit}`);
266
- }
267
- }
268
- var globalConfig = {
269
- sessionTtl: "7d"
270
- // Default: 7 days
271
- };
272
- function configureAuth(config) {
273
- globalConfig = {
274
- ...globalConfig,
275
- ...config
276
- };
277
- }
278
- function getAuthConfig() {
279
- return { ...globalConfig };
280
- }
281
- function getSessionTtl(override) {
282
- if (override !== void 0) {
283
- return parseDuration(override);
284
- }
285
- if (globalConfig.sessionTtl !== void 0) {
286
- return parseDuration(globalConfig.sessionTtl);
287
- }
288
- const envTtl = process.env.SPFN_AUTH_SESSION_TTL;
289
- if (envTtl) {
290
- return parseDuration(envTtl);
291
- }
292
- return 7 * 24 * 60 * 60;
293
- }
294
- export {
295
- COOKIE_NAMES,
296
- configureAuth,
297
- generateClientToken,
298
- generateKeyPair,
299
- generateKeyPairES256,
300
- generateKeyPairRS256,
301
- getAuthConfig,
302
- getKeySize,
303
- getSessionInfo,
304
- getSessionTtl,
305
- parseDuration,
306
- sealSession,
307
- shouldRefreshSession,
308
- shouldRotateKey,
309
- unsealSession,
310
- validateSessionSecret,
311
- verifyClientToken
312
- };
313
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/lib/crypto.ts","../../src/lib/session.ts","../../src/lib/config.ts"],"sourcesContent":["/**\n * @spfn/auth - Client Crypto Helpers\n *\n * ES256 (ECDSA P-256) key generation and JWT signing for Next.js\n * Keys are stored in DER format (Base64 encoded) for efficiency\n *\n * Key Sizes:\n * - ES256 (ECDSA P-256): ~91 bytes (Base64: ~120 chars)\n * - RS256 (RSA 2048): ~294 bytes (Base64: ~392 chars)\n */\n\nimport crypto from 'crypto';\nimport jwt, { type Algorithm, type SignOptions } from 'jsonwebtoken';\n\ntype Unit =\n | \"Years\"\n | \"Year\"\n | \"Yrs\"\n | \"Yr\"\n | \"Y\"\n | \"Weeks\"\n | \"Week\"\n | \"W\"\n | \"Days\"\n | \"Day\"\n | \"D\"\n | \"Hours\"\n | \"Hour\"\n | \"Hrs\"\n | \"Hr\"\n | \"H\"\n | \"Minutes\"\n | \"Minute\"\n | \"Mins\"\n | \"Min\"\n | \"M\"\n | \"Seconds\"\n | \"Second\"\n | \"Secs\"\n | \"Sec\"\n | \"s\"\n | \"Milliseconds\"\n | \"Millisecond\"\n | \"Msecs\"\n | \"Msec\"\n | \"Ms\";\n\ntype UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>;\n\ntype StringValue =\n | `${number}`\n | `${number}${UnitAnyCase}`\n | `${number} ${UnitAnyCase}`;\n\nexport interface KeyPair\n{\n privateKey: string; // Base64 encoded DER\n publicKey: string; // Base64 encoded DER\n keyId: string; // UUID\n fingerprint: string; // SHA-256 hash\n algorithm: 'ES256' | 'RS256';\n}\n\nexport interface TokenPayload\n{\n userId?: string;\n keyId?: string;\n timestamp?: number;\n exp?: number;\n iat?: number;\n iss?: string;\n [key: string]: any;\n}\n\n/**\n * Generate ECDSA P-256 key pair (ES256)\n * Recommended for optimal size and performance\n */\nexport function generateKeyPairES256(): KeyPair\n{\n const keyId = crypto.randomUUID();\n\n const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {\n namedCurve: 'P-256', // ES256\n publicKeyEncoding: {\n type: 'spki',\n format: 'der',\n },\n privateKeyEncoding: {\n type: 'pkcs8',\n format: 'der',\n },\n });\n\n // Convert Buffer to Base64\n const privateKeyB64 = privateKey.toString('base64');\n const publicKeyB64 = publicKey.toString('base64');\n\n // Generate fingerprint (SHA-256 of public key)\n const fingerprint = crypto\n .createHash('sha256')\n .update(publicKey)\n .digest('hex');\n\n return {\n privateKey: privateKeyB64,\n publicKey: publicKeyB64,\n keyId,\n fingerprint,\n algorithm: 'ES256',\n };\n}\n\n/**\n * Generate RSA 2048 key pair (RS256)\n * Fallback option, larger size but wider compatibility\n */\nexport function generateKeyPairRS256(): KeyPair\n{\n const keyId = crypto.randomUUID();\n\n const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {\n modulusLength: 2048,\n publicKeyEncoding: {\n type: 'spki',\n format: 'der',\n },\n privateKeyEncoding: {\n type: 'pkcs8',\n format: 'der',\n },\n });\n\n const privateKeyB64 = privateKey.toString('base64');\n const publicKeyB64 = publicKey.toString('base64');\n\n const fingerprint = crypto\n .createHash('sha256')\n .update(publicKey)\n .digest('hex');\n\n return {\n privateKey: privateKeyB64,\n publicKey: publicKeyB64,\n keyId,\n fingerprint,\n algorithm: 'RS256',\n };\n}\n\n/**\n * Generate key pair (defaults to ES256)\n */\nexport function generateKeyPair(\n algorithm: 'ES256' | 'RS256' = 'ES256'\n): KeyPair\n{\n return algorithm === 'ES256'\n ? generateKeyPairES256()\n : generateKeyPairRS256();\n}\n\n/**\n * Generate JWT signed with client private key (DER format)\n */\nexport function generateClientToken(\n payload: Record<string, any>,\n privateKeyB64: string,\n algorithm: Algorithm,\n options?: {\n expiresIn?: StringValue | number;\n issuer?: string;\n }\n): string\n{\n try\n {\n // Convert Base64 back to Buffer\n const privateKeyDER = Buffer.from(privateKeyB64, 'base64');\n\n // Create key object for signing\n const privateKeyObject = crypto.createPrivateKey({\n key: privateKeyDER,\n format: 'der',\n type: 'pkcs8',\n });\n\n // Export as PEM for jwt.sign\n const privateKeyPEM = privateKeyObject.export({\n type: 'pkcs8',\n format: 'pem',\n });\n\n const signOptions: SignOptions = {\n algorithm,\n issuer: options?.issuer || 'spfn-client',\n expiresIn: options?.expiresIn ?? '15m', // Default to 15 minutes\n }\n\n return jwt.sign(payload, privateKeyPEM, signOptions);\n }\n catch (error)\n {\n throw new Error(\n `Failed to generate client token: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Verify JWT with public key (DER format)\n *\n * @internal Used for testing/validation only\n * For production verification, use server-side verifyClientToken\n */\nexport function verifyClientToken(\n token: string,\n publicKeyB64: string,\n algorithm: 'ES256' | 'RS256'\n): TokenPayload\n{\n try\n {\n // Convert Base64 to Buffer\n const publicKeyDER = Buffer.from(publicKeyB64, 'base64');\n\n // Create key object for verification\n const publicKeyObject = crypto.createPublicKey({\n key: publicKeyDER,\n format: 'der',\n type: 'spki',\n });\n\n return jwt.verify(token, publicKeyObject, {\n algorithms: [algorithm],\n issuer: 'spfn-client',\n }) as TokenPayload;\n }\n catch (error)\n {\n throw new Error(\n `Failed to verify token: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Get key size information\n */\nexport function getKeySize(publicKeyB64: string): {\n bytes: number;\n base64Length: number;\n}\n{\n const keyDER = Buffer.from(publicKeyB64, 'base64');\n\n return {\n bytes: keyDER.length,\n base64Length: publicKeyB64.length,\n };\n}\n\n/**\n * Check if key should be rotated based on creation date\n */\nexport function shouldRotateKey(\n createdAt: Date,\n rotationDays: number = 90\n): {\n shouldRotate: boolean;\n daysRemaining: number;\n}\n{\n const now = new Date();\n const ageInDays = Math.floor(\n (now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24)\n );\n const daysRemaining = Math.max(0, rotationDays - ageInDays);\n\n return {\n shouldRotate: daysRemaining <= 7, // Warn 7 days before expiry\n daysRemaining,\n };\n}","/**\n * @spfn/auth - Client Session Management\n *\n * Uses Jose JWE (JSON Web Encryption) to securely store session data in cookies\n * More efficient than Iron Session with better Edge Runtime support\n */\n\nimport * as jose from 'jose';\n\nexport interface SessionData\n{\n userId: string;\n privateKey: string; // Base64 encoded DER\n keyId: string;\n algorithm: 'ES256' | 'RS256';\n}\n\n/**\n * Calculate Shannon entropy of a string\n * Returns entropy in bits per character\n *\n * @param str - String to calculate entropy for\n * @returns Entropy value (0 to ~6.6 bits for printable ASCII)\n */\nfunction calculateEntropy(str: string): number\n{\n const len = str.length;\n const frequencies = new Map<string, number>();\n\n // Count character frequencies\n for (const char of str)\n {\n frequencies.set(char, (frequencies.get(char) || 0) + 1);\n }\n\n // Calculate Shannon entropy\n let entropy = 0;\n for (const count of frequencies.values())\n {\n const probability = count / len;\n entropy -= probability * Math.log2(probability);\n }\n\n return entropy;\n}\n\n/**\n * Get session secret from environment\n * Must be at least 32 characters (256-bit)\n *\n * Derives a 32-byte key using SHA-256 to ensure compatibility with Jose A256GCM\n */\nasync function getSessionSecret(): Promise<Uint8Array>\n{\n const secret =\n process.env.SPFN_AUTH_SESSION_SECRET || // New prefixed version (recommended)\n process.env.SESSION_SECRET; // Legacy fallback\n\n if (!secret)\n {\n throw new Error('SPFN_AUTH_SESSION_SECRET environment variable is not set');\n }\n\n if (secret.length < 32)\n {\n throw new Error('SPFN_AUTH_SESSION_SECRET must be at least 32 characters long');\n }\n\n // Derive a 32-byte key using SHA-256 for A256GCM compatibility\n // Use Web Crypto API for universal compatibility (browser + Node.js)\n const encoder = new TextEncoder();\n const data = encoder.encode(secret);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n return new Uint8Array(hashBuffer);\n}\n\n/**\n * Seal session data into encrypted JWT (JWE)\n *\n * @param data - Session data to encrypt\n * @param ttl - Time to live in seconds (default: 7 days)\n * @returns Encrypted JWT string\n */\nexport async function sealSession(\n data: SessionData,\n ttl: number = 60 * 60 * 24 * 7 // 7 days\n): Promise<string>\n{\n const secret = await getSessionSecret();\n\n return await new jose.EncryptJWT({ data })\n .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })\n .setIssuedAt()\n .setExpirationTime(`${ttl}s`)\n .setIssuer('spfn-auth')\n .setAudience('spfn-client')\n .encrypt(secret);\n}\n\n/**\n * Unseal encrypted JWT (JWE) to session data\n *\n * @param jwt - Encrypted JWT string\n * @returns Session data\n * @throws Error if session is invalid or expired\n */\nexport async function unsealSession(jwt: string): Promise<SessionData>\n{\n const secret = await getSessionSecret();\n\n try\n {\n const { payload } = await jose.jwtDecrypt(jwt, secret, {\n issuer: 'spfn-auth',\n audience: 'spfn-client',\n });\n\n return payload.data as SessionData;\n }\n catch (err)\n {\n if (err instanceof jose.errors.JWTExpired)\n {\n throw new Error('Session expired');\n }\n\n if (err instanceof jose.errors.JWEDecryptionFailed)\n {\n throw new Error('Invalid session');\n }\n\n if (err instanceof jose.errors.JWTClaimValidationFailed)\n {\n throw new Error('Session validation failed');\n }\n\n throw new Error('Failed to unseal session');\n }\n}\n\n/**\n * Get session metadata without decrypting\n *\n * @param jwt - Encrypted JWT string\n * @returns Session metadata or null if invalid\n */\nexport async function getSessionInfo(jwt: string): Promise<{\n issuedAt: Date;\n expiresAt: Date;\n issuer: string;\n audience: string;\n} | null>\n{\n const secret = await getSessionSecret();\n\n try\n {\n const { payload } = await jose.jwtDecrypt(jwt, secret);\n\n return {\n issuedAt: new Date(payload.iat! * 1000),\n expiresAt: new Date(payload.exp! * 1000),\n issuer: payload.iss || '',\n audience: Array.isArray(payload.aud) ? payload.aud[0] : payload.aud || '',\n };\n }\n catch (err)\n {\n // Log error for debugging but return null for graceful handling\n if (process.env.NODE_ENV !== 'production')\n {\n console.warn('[Session] Failed to get session info:', err instanceof Error ? err.message : 'Unknown error');\n }\n return null;\n }\n}\n\n/**\n * Check if session is about to expire (within threshold)\n *\n * @param jwt - Encrypted JWT string\n * @param thresholdHours - Hours before expiry to trigger refresh (default: 24)\n * @returns True if session should be refreshed\n */\nexport async function shouldRefreshSession(\n jwt: string,\n thresholdHours: number = 24\n): Promise<boolean>\n{\n const info = await getSessionInfo(jwt);\n\n if (!info)\n {\n return true;\n }\n\n const hoursRemaining = (info.expiresAt.getTime() - Date.now()) / (1000 * 60 * 60);\n\n return hoursRemaining < thresholdHours;\n}\n\n/**\n * Validate session secret strength\n * Call this at startup to ensure proper configuration\n *\n * Validation criteria:\n * - Minimum 32 characters (256-bit)\n * - Minimum 16 unique characters\n * - Minimum 3.5 bits/char Shannon entropy (good randomness)\n */\nexport function validateSessionSecret(): {\n valid: boolean;\n error?: string;\n details?: {\n length: number;\n uniqueChars: number;\n entropy: number;\n };\n}\n{\n try\n {\n const secret =\n process.env.SPFN_AUTH_SESSION_SECRET || // New prefixed version (recommended)\n process.env.SESSION_SECRET; // Legacy fallback\n\n if (!secret)\n {\n return { valid: false, error: 'SPFN_AUTH_SESSION_SECRET is not set' };\n }\n\n const length = secret.length;\n const uniqueChars = new Set(secret).size;\n const entropy = calculateEntropy(secret);\n\n // Check length (minimum 32 chars for 256-bit)\n if (length < 32)\n {\n return {\n valid: false,\n error: `SPFN_AUTH_SESSION_SECRET too short (${length} chars, minimum 32)`,\n details: { length, uniqueChars, entropy },\n };\n }\n\n // Check unique character diversity\n if (uniqueChars < 16)\n {\n return {\n valid: false,\n error: `SPFN_AUTH_SESSION_SECRET has low diversity (${uniqueChars} unique chars, minimum 16)`,\n details: { length, uniqueChars, entropy },\n };\n }\n\n // Check Shannon entropy (3.5 bits/char is good randomness)\n // For reference:\n // - Random lowercase: ~4.7 bits/char\n // - Random alphanumeric: ~5.2 bits/char\n // - Random printable ASCII: ~6.6 bits/char\n // - \"aaaaaaa...\": ~0 bits/char\n // - \"abcabcabc...\": ~1.58 bits/char\n if (entropy < 3.5)\n {\n return {\n valid: false,\n error: `SPFN_AUTH_SESSION_SECRET has low entropy (${entropy.toFixed(2)} bits/char, minimum 3.5). Use a more random secret.`,\n details: { length, uniqueChars, entropy },\n };\n }\n\n return {\n valid: true,\n details: { length, uniqueChars, entropy },\n };\n }\n catch (err)\n {\n return { valid: false, error: 'Failed to validate SPFN_AUTH_SESSION_SECRET' };\n }\n}","/**\n * @spfn/auth - Global Configuration\n *\n * Manages global auth configuration including session TTL\n */\n\n/**\n * Cookie names used by SPFN Auth\n */\nexport const COOKIE_NAMES = {\n /** Encrypted session data (userId, privateKey, keyId, algorithm) */\n SESSION: 'spfn_session',\n /** Current key ID (for key rotation) */\n SESSION_KEY_ID: 'spfn_session_key_id',\n} as const;\n\n/**\n * Parse duration string to seconds\n *\n * Supports: '30d', '12h', '45m', '3600s', or plain number\n *\n * @example\n * parseDuration('30d') // 2592000 (30 days in seconds)\n * parseDuration('12h') // 43200\n * parseDuration('45m') // 2700\n * parseDuration('3600') // 3600\n */\nexport function parseDuration(duration: string | number): number\n{\n if (typeof duration === 'number')\n {\n return duration;\n }\n\n const match = duration.match(/^(\\d+)([dhms]?)$/);\n if (!match)\n {\n throw new Error(`Invalid duration format: ${duration}. Use format like '30d', '12h', '45m', '3600s', or plain number.`);\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2] || 's';\n\n switch (unit)\n {\n case 'd':\n return value * 24 * 60 * 60;\n case 'h':\n return value * 60 * 60;\n case 'm':\n return value * 60;\n case 's':\n return value;\n default:\n throw new Error(`Unknown duration unit: ${unit}`);\n }\n}\n\n/**\n * Auth configuration\n */\nexport interface AuthConfig\n{\n /**\n * Default session TTL in seconds or duration string\n *\n * Supports:\n * - Number: seconds (e.g., 2592000)\n * - String: '30d', '12h', '45m', '3600s'\n *\n * @default 7d (7 days)\n */\n sessionTtl?: string | number;\n}\n\n/**\n * Global auth configuration state\n */\nlet globalConfig: AuthConfig = {\n sessionTtl: '7d', // Default: 7 days\n};\n\n/**\n * Configure global auth settings\n *\n * @param config - Auth configuration\n *\n * @example\n * ```typescript\n * configureAuth({\n * sessionTtl: '30d', // 30 days\n * });\n * ```\n */\nexport function configureAuth(config: AuthConfig): void\n{\n globalConfig = {\n ...globalConfig,\n ...config,\n };\n}\n\n/**\n * Get current auth configuration\n */\nexport function getAuthConfig(): AuthConfig\n{\n return { ...globalConfig };\n}\n\n/**\n * Get session TTL in seconds\n *\n * Priority:\n * 1. Runtime override (remember parameter)\n * 2. Global config (configureAuth)\n * 3. Environment variable (SPFN_AUTH_SESSION_TTL)\n * 4. Default (7 days)\n */\nexport function getSessionTtl(override?: string | number): number\n{\n // 1. Runtime override\n if (override !== undefined)\n {\n return parseDuration(override);\n }\n\n // 2. Global config\n if (globalConfig.sessionTtl !== undefined)\n {\n return parseDuration(globalConfig.sessionTtl);\n }\n\n // 3. Environment variable\n const envTtl = process.env.SPFN_AUTH_SESSION_TTL;\n if (envTtl)\n {\n return parseDuration(envTtl);\n }\n\n // 4. Default: 7 days\n return 7 * 24 * 60 * 60;\n}"],"mappings":"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","names":["crypto","jwt"]}
@@ -1,68 +0,0 @@
1
- /**
2
- * @spfn/auth - Client Session Management
3
- *
4
- * Uses Jose JWE (JSON Web Encryption) to securely store session data in cookies
5
- * More efficient than Iron Session with better Edge Runtime support
6
- */
7
- interface SessionData {
8
- userId: string;
9
- privateKey: string;
10
- keyId: string;
11
- algorithm: 'ES256' | 'RS256';
12
- }
13
- /**
14
- * Seal session data into encrypted JWT (JWE)
15
- *
16
- * @param data - Session data to encrypt
17
- * @param ttl - Time to live in seconds (default: 7 days)
18
- * @returns Encrypted JWT string
19
- */
20
- declare function sealSession(data: SessionData, ttl?: number): Promise<string>;
21
- /**
22
- * Unseal encrypted JWT (JWE) to session data
23
- *
24
- * @param jwt - Encrypted JWT string
25
- * @returns Session data
26
- * @throws Error if session is invalid or expired
27
- */
28
- declare function unsealSession(jwt: string): Promise<SessionData>;
29
- /**
30
- * Get session metadata without decrypting
31
- *
32
- * @param jwt - Encrypted JWT string
33
- * @returns Session metadata or null if invalid
34
- */
35
- declare function getSessionInfo(jwt: string): Promise<{
36
- issuedAt: Date;
37
- expiresAt: Date;
38
- issuer: string;
39
- audience: string;
40
- } | null>;
41
- /**
42
- * Check if session is about to expire (within threshold)
43
- *
44
- * @param jwt - Encrypted JWT string
45
- * @param thresholdHours - Hours before expiry to trigger refresh (default: 24)
46
- * @returns True if session should be refreshed
47
- */
48
- declare function shouldRefreshSession(jwt: string, thresholdHours?: number): Promise<boolean>;
49
- /**
50
- * Validate session secret strength
51
- * Call this at startup to ensure proper configuration
52
- *
53
- * Validation criteria:
54
- * - Minimum 32 characters (256-bit)
55
- * - Minimum 16 unique characters
56
- * - Minimum 3.5 bits/char Shannon entropy (good randomness)
57
- */
58
- declare function validateSessionSecret(): {
59
- valid: boolean;
60
- error?: string;
61
- details?: {
62
- length: number;
63
- uniqueChars: number;
64
- entropy: number;
65
- };
66
- };
67
-
68
- export { type SessionData, getSessionInfo, sealSession, shouldRefreshSession, unsealSession, validateSessionSecret };