@spfn/auth 0.1.0-alpha.88 → 0.2.0-beta.10
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 +1385 -1199
- package/dist/config.d.ts +409 -0
- package/dist/config.js +244 -0
- package/dist/config.js.map +1 -0
- package/dist/dto-CRlgoCP5.d.ts +645 -0
- package/dist/errors.d.ts +196 -0
- package/dist/errors.js +173 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +287 -14
- package/dist/index.js +511 -6665
- package/dist/index.js.map +1 -1
- package/dist/nextjs/api.js +345 -0
- package/dist/nextjs/api.js.map +1 -0
- package/dist/{adapters/nextjs → nextjs}/server.d.ts +47 -65
- package/dist/nextjs/server.js +178 -0
- package/dist/nextjs/server.js.map +1 -0
- package/dist/server.d.ts +4444 -514
- package/dist/server.js +7977 -1210
- package/dist/server.js.map +1 -1
- package/migrations/0000_premium_famine.sql +292 -0
- package/migrations/meta/0000_snapshot.json +281 -46
- package/migrations/meta/_journal.json +2 -2
- package/package.json +37 -33
- package/dist/adapters/nextjs/api.d.ts +0 -446
- package/dist/adapters/nextjs/api.js +0 -3279
- package/dist/adapters/nextjs/api.js.map +0 -1
- package/dist/adapters/nextjs/server.js +0 -3645
- package/dist/adapters/nextjs/server.js.map +0 -1
- package/dist/lib/api/auth-codes-verify.d.ts +0 -37
- package/dist/lib/api/auth-codes-verify.js +0 -2949
- package/dist/lib/api/auth-codes-verify.js.map +0 -1
- package/dist/lib/api/auth-codes.d.ts +0 -37
- package/dist/lib/api/auth-codes.js +0 -2949
- package/dist/lib/api/auth-codes.js.map +0 -1
- package/dist/lib/api/auth-exists.d.ts +0 -38
- package/dist/lib/api/auth-exists.js +0 -2949
- package/dist/lib/api/auth-exists.js.map +0 -1
- package/dist/lib/api/auth-invitations-accept.d.ts +0 -38
- package/dist/lib/api/auth-invitations-accept.js +0 -2883
- package/dist/lib/api/auth-invitations-accept.js.map +0 -1
- package/dist/lib/api/auth-invitations-cancel.d.ts +0 -37
- package/dist/lib/api/auth-invitations-cancel.js +0 -2883
- package/dist/lib/api/auth-invitations-cancel.js.map +0 -1
- package/dist/lib/api/auth-invitations-delete.d.ts +0 -36
- package/dist/lib/api/auth-invitations-delete.js +0 -2883
- package/dist/lib/api/auth-invitations-delete.js.map +0 -1
- package/dist/lib/api/auth-invitations-resend.d.ts +0 -37
- package/dist/lib/api/auth-invitations-resend.js +0 -2883
- package/dist/lib/api/auth-invitations-resend.js.map +0 -1
- package/dist/lib/api/auth-invitations.d.ts +0 -109
- package/dist/lib/api/auth-invitations.js +0 -2887
- package/dist/lib/api/auth-invitations.js.map +0 -1
- package/dist/lib/api/auth-keys-rotate.d.ts +0 -37
- package/dist/lib/api/auth-keys-rotate.js +0 -2949
- package/dist/lib/api/auth-keys-rotate.js.map +0 -1
- package/dist/lib/api/auth-login.d.ts +0 -39
- package/dist/lib/api/auth-login.js +0 -2949
- package/dist/lib/api/auth-login.js.map +0 -1
- package/dist/lib/api/auth-logout.d.ts +0 -36
- package/dist/lib/api/auth-logout.js +0 -2949
- package/dist/lib/api/auth-logout.js.map +0 -1
- package/dist/lib/api/auth-me.d.ts +0 -50
- package/dist/lib/api/auth-me.js +0 -2949
- package/dist/lib/api/auth-me.js.map +0 -1
- package/dist/lib/api/auth-password.d.ts +0 -36
- package/dist/lib/api/auth-password.js +0 -2949
- package/dist/lib/api/auth-password.js.map +0 -1
- package/dist/lib/api/auth-register.d.ts +0 -38
- package/dist/lib/api/auth-register.js +0 -2949
- package/dist/lib/api/auth-register.js.map +0 -1
- package/dist/lib/api/index.d.ts +0 -356
- package/dist/lib/api/index.js +0 -3261
- package/dist/lib/api/index.js.map +0 -1
- package/dist/lib/config.d.ts +0 -70
- package/dist/lib/config.js +0 -64
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/contracts/auth.d.ts +0 -302
- package/dist/lib/contracts/auth.js +0 -2951
- package/dist/lib/contracts/auth.js.map +0 -1
- package/dist/lib/contracts/index.d.ts +0 -3
- package/dist/lib/contracts/index.js +0 -3190
- package/dist/lib/contracts/index.js.map +0 -1
- package/dist/lib/contracts/invitation.d.ts +0 -243
- package/dist/lib/contracts/invitation.js +0 -2883
- package/dist/lib/contracts/invitation.js.map +0 -1
- package/dist/lib/crypto.d.ts +0 -76
- package/dist/lib/crypto.js +0 -127
- package/dist/lib/crypto.js.map +0 -1
- package/dist/lib/index.d.ts +0 -4
- package/dist/lib/index.js +0 -313
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/session.d.ts +0 -68
- package/dist/lib/session.js +0 -126
- package/dist/lib/session.js.map +0 -1
- package/dist/lib/types/api.d.ts +0 -45
- package/dist/lib/types/api.js +0 -1
- package/dist/lib/types/api.js.map +0 -1
- package/dist/lib/types/index.d.ts +0 -3
- package/dist/lib/types/index.js +0 -2647
- package/dist/lib/types/index.js.map +0 -1
- package/dist/lib/types/schemas.d.ts +0 -45
- package/dist/lib/types/schemas.js +0 -2647
- package/dist/lib/types/schemas.js.map +0 -1
- package/dist/lib.js +0 -1
- package/dist/lib.js.map +0 -1
- package/dist/plugin.d.ts +0 -12
- package/dist/plugin.js +0 -9083
- package/dist/plugin.js.map +0 -1
- package/dist/server/entities/index.d.ts +0 -11
- package/dist/server/entities/index.js +0 -395
- package/dist/server/entities/index.js.map +0 -1
- package/dist/server/entities/invitations.d.ts +0 -241
- package/dist/server/entities/invitations.js +0 -184
- package/dist/server/entities/invitations.js.map +0 -1
- package/dist/server/entities/permissions.d.ts +0 -196
- package/dist/server/entities/permissions.js +0 -49
- package/dist/server/entities/permissions.js.map +0 -1
- package/dist/server/entities/role-permissions.d.ts +0 -107
- package/dist/server/entities/role-permissions.js +0 -115
- package/dist/server/entities/role-permissions.js.map +0 -1
- package/dist/server/entities/roles.d.ts +0 -196
- package/dist/server/entities/roles.js +0 -50
- package/dist/server/entities/roles.js.map +0 -1
- package/dist/server/entities/schema.d.ts +0 -14
- package/dist/server/entities/schema.js +0 -7
- package/dist/server/entities/schema.js.map +0 -1
- package/dist/server/entities/user-permissions.d.ts +0 -163
- package/dist/server/entities/user-permissions.js +0 -193
- package/dist/server/entities/user-permissions.js.map +0 -1
- package/dist/server/entities/user-public-keys.d.ts +0 -227
- package/dist/server/entities/user-public-keys.js +0 -156
- package/dist/server/entities/user-public-keys.js.map +0 -1
- package/dist/server/entities/user-social-accounts.d.ts +0 -189
- package/dist/server/entities/user-social-accounts.js +0 -149
- package/dist/server/entities/user-social-accounts.js.map +0 -1
- package/dist/server/entities/users.d.ts +0 -235
- package/dist/server/entities/users.js +0 -117
- package/dist/server/entities/users.js.map +0 -1
- package/dist/server/entities/verification-codes.d.ts +0 -191
- package/dist/server/entities/verification-codes.js +0 -49
- package/dist/server/entities/verification-codes.js.map +0 -1
- package/dist/server/routes/auth/index.d.ts +0 -10
- package/dist/server/routes/auth/index.js +0 -4460
- package/dist/server/routes/auth/index.js.map +0 -1
- package/dist/server/routes/index.d.ts +0 -6
- package/dist/server/routes/index.js +0 -6584
- package/dist/server/routes/index.js.map +0 -1
- package/dist/server/routes/invitations/index.d.ts +0 -10
- package/dist/server/routes/invitations/index.js +0 -4395
- package/dist/server/routes/invitations/index.js.map +0 -1
- package/migrations/0000_skinny_christian_walker.sql +0 -167
- /package/dist/{lib.d.ts → nextjs/api.d.ts} +0 -0
package/dist/lib/crypto.d.ts
DELETED
|
@@ -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 };
|
package/dist/lib/crypto.js
DELETED
|
@@ -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
|
package/dist/lib/crypto.js.map
DELETED
|
@@ -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":[]}
|
package/dist/lib/index.d.ts
DELETED
|
@@ -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
|
package/dist/lib/index.js.map
DELETED
|
@@ -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"]}
|
package/dist/lib/session.d.ts
DELETED
|
@@ -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 };
|