sprint-es 0.0.43 ā 0.0.45
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/dist/cjs/cli.cjs +0 -18
- package/dist/cjs/modules/jwt/index.cjs +79 -50
- package/dist/esm/cli.js +0 -18
- package/dist/esm/modules/jwt/index.js +79 -50
- package/dist/types/modules/jwt/index.d.ts +4 -4
- package/dist/types/modules/jwt/index.d.ts.map +1 -1
- package/dist/types/modules/utils/index.d.ts +4 -0
- package/dist/types/modules/utils/index.d.ts.map +1 -0
- package/package.json +6 -1
package/dist/cjs/cli.cjs
CHANGED
|
@@ -4,7 +4,6 @@ const child_process = require("child_process");
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const crypto = require("crypto");
|
|
6
6
|
const path = require("path");
|
|
7
|
-
const dotenv = require("dotenv");
|
|
8
7
|
function _interopNamespaceDefault(e) {
|
|
9
8
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
10
9
|
if (e) {
|
|
@@ -56,21 +55,6 @@ function getProjectRoot() {
|
|
|
56
55
|
}
|
|
57
56
|
return process.cwd();
|
|
58
57
|
}
|
|
59
|
-
function loadEnv() {
|
|
60
|
-
const projectRoot2 = getProjectRoot();
|
|
61
|
-
const envFile = fs.existsSync(path.join(projectRoot2, ".env")) ? ".env" : fs.existsSync(path.join(projectRoot2, ".env.development")) ? ".env.development" : fs.existsSync(path.join(projectRoot2, ".env.production")) ? ".env.production" : null;
|
|
62
|
-
if (envFile) dotenv.config({ path: path.join(projectRoot2, envFile), quiet: true });
|
|
63
|
-
}
|
|
64
|
-
function checkJwtKeys() {
|
|
65
|
-
loadEnv();
|
|
66
|
-
const publicKey = process.env.JWT_PUBLIC_KEY;
|
|
67
|
-
const privateKey = process.env.JWT_PRIVATE_KEY;
|
|
68
|
-
if (!publicKey || !privateKey) {
|
|
69
|
-
console.error("\nā Error: JWT keys not configured.");
|
|
70
|
-
console.error("Run 'npm run generate:keys' to generate new keys.\n");
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
58
|
const projectRoot = getProjectRoot();
|
|
75
59
|
function runCommand(cmd, envVars) {
|
|
76
60
|
const child = child_process.spawn(cmd, args.slice(1), {
|
|
@@ -85,7 +69,6 @@ function runCommand(cmd, envVars) {
|
|
|
85
69
|
}
|
|
86
70
|
switch (command) {
|
|
87
71
|
case "dev":
|
|
88
|
-
checkJwtKeys();
|
|
89
72
|
console.log("š Starting development server with hot reload...");
|
|
90
73
|
const srcFile = fs.existsSync(path.join(projectRoot, "src/app.ts")) ? "src/app.ts" : fs.existsSync(path.join(projectRoot, "src/app.js")) ? "src/app.js" : fs.existsSync(path.join(projectRoot, "src/index.ts")) ? "src/index.ts" : "src/index.js";
|
|
91
74
|
runCommand(`tsx --watch ${srcFile}`, { NODE_ENV: "development" });
|
|
@@ -100,7 +83,6 @@ switch (command) {
|
|
|
100
83
|
}
|
|
101
84
|
break;
|
|
102
85
|
case "start":
|
|
103
|
-
checkJwtKeys();
|
|
104
86
|
console.log("š Starting production server...");
|
|
105
87
|
runCommand("node dist/index.js", { NODE_ENV: "production" });
|
|
106
88
|
break;
|
|
@@ -1,80 +1,112 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const crypto = require("crypto");
|
|
4
|
+
const base64UrlEncode = (buffer) => buffer.toString("base64url");
|
|
5
|
+
const base64UrlDecode = (str) => Buffer.from(str, "base64url");
|
|
6
|
+
const decodeBase64Url = (str) => Buffer.from(str, "base64url").toString("utf8");
|
|
4
7
|
const ALGORITHM = "ES256";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
function base64UrlDecode(str) {
|
|
9
|
-
const pad = str.length % 4;
|
|
10
|
-
const padded = pad ? str + "=".repeat(4 - pad) : str;
|
|
11
|
-
return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
12
|
-
}
|
|
13
|
-
function decodeBase64Url(str) {
|
|
14
|
-
return Buffer.from(str, "base64url").toString();
|
|
15
|
-
}
|
|
16
|
-
function generateKeyPair(algorithm = ALGORITHM) {
|
|
8
|
+
const ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
9
|
+
function generateKeyPair() {
|
|
17
10
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", {
|
|
18
11
|
namedCurve: "prime256v1",
|
|
19
|
-
publicKeyEncoding: {
|
|
20
|
-
|
|
21
|
-
format: "pem"
|
|
22
|
-
},
|
|
23
|
-
privateKeyEncoding: {
|
|
24
|
-
type: "pkcs8",
|
|
25
|
-
format: "pem"
|
|
26
|
-
}
|
|
12
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
13
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
27
14
|
});
|
|
28
15
|
return { publicKey, privateKey };
|
|
29
16
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
17
|
+
function parseExpiry(expiresIn) {
|
|
18
|
+
if (typeof expiresIn === "number") return expiresIn;
|
|
19
|
+
const units = {
|
|
20
|
+
s: 1,
|
|
21
|
+
m: 60,
|
|
22
|
+
h: 3600,
|
|
23
|
+
d: 86400,
|
|
24
|
+
w: 604800
|
|
25
|
+
};
|
|
26
|
+
const match = expiresIn.match(/^(\d+)([smhdw])$/);
|
|
27
|
+
if (!match) throw new Error(`Invalid expiresIn format: "${expiresIn}". Use e.g. "15m", "2h", "7d".`);
|
|
28
|
+
return parseInt(match[1]) * units[match[2]];
|
|
29
|
+
}
|
|
30
|
+
function buildClaims(payload, options) {
|
|
32
31
|
const now = Math.floor(Date.now() / 1e3);
|
|
33
|
-
const claims = { ...payload };
|
|
34
|
-
if (options.expiresIn)
|
|
35
|
-
claims.exp = typeof options.expiresIn === "number" ? now + options.expiresIn : now + parseInt(options.expiresIn);
|
|
36
|
-
}
|
|
32
|
+
const claims = { ...payload, iat: now };
|
|
33
|
+
if (options.expiresIn !== void 0) claims.exp = now + parseExpiry(options.expiresIn);
|
|
37
34
|
if (options.issuer) claims.iss = options.issuer;
|
|
38
35
|
if (options.subject) claims.sub = options.subject;
|
|
39
36
|
if (options.audience) claims.aud = options.audience;
|
|
40
|
-
claims
|
|
37
|
+
return claims;
|
|
38
|
+
}
|
|
39
|
+
function encryptPayload(plaintext, secret) {
|
|
40
|
+
const key = crypto.scryptSync(secret, "sprint-jwt-salt", 32);
|
|
41
|
+
const iv = crypto.randomBytes(12);
|
|
42
|
+
const cipher = crypto.createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
43
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
44
|
+
const tag = cipher.getAuthTag();
|
|
45
|
+
return base64UrlEncode(Buffer.concat([iv, tag, encrypted]));
|
|
46
|
+
}
|
|
47
|
+
function decryptPayload(data, secret) {
|
|
48
|
+
const key = crypto.scryptSync(secret, "sprint-jwt-salt", 32);
|
|
49
|
+
const buf = base64UrlDecode(data);
|
|
50
|
+
if (buf.length < 28) throw new Error("Invalid encrypted payload.");
|
|
51
|
+
const iv = buf.subarray(0, 12);
|
|
52
|
+
const tag = buf.subarray(12, 28);
|
|
53
|
+
const ciphertext = buf.subarray(28);
|
|
54
|
+
const decipher = crypto.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
55
|
+
decipher.setAuthTag(tag);
|
|
56
|
+
return decipher.update(ciphertext).toString("utf8") + decipher.final("utf8");
|
|
57
|
+
}
|
|
58
|
+
function sign(payload, privateKey, options = {}) {
|
|
59
|
+
const header = { alg: ALGORITHM, typ: "JWT" };
|
|
60
|
+
const claims = buildClaims(payload, options);
|
|
41
61
|
const encodedHeader = base64UrlEncode(Buffer.from(JSON.stringify(header)));
|
|
42
62
|
const encodedPayload = base64UrlEncode(Buffer.from(JSON.stringify(claims)));
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
64
|
+
const signer = crypto.createSign("SHA256");
|
|
65
|
+
signer.update(signingInput);
|
|
66
|
+
const signature = signer.sign(privateKey);
|
|
67
|
+
return `${signingInput}.${base64UrlEncode(signature)}`;
|
|
47
68
|
}
|
|
48
69
|
function verify(token, publicKey) {
|
|
49
70
|
try {
|
|
50
71
|
const parts = token.split(".");
|
|
51
72
|
if (parts.length !== 3) return null;
|
|
52
|
-
const [encodedHeader, encodedPayload,
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const isValid =
|
|
73
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
74
|
+
const verifier = crypto.createVerify("SHA256");
|
|
75
|
+
verifier.update(`${encodedHeader}.${encodedPayload}`);
|
|
76
|
+
const isValid = verifier.verify(publicKey, base64UrlDecode(encodedSignature));
|
|
56
77
|
if (!isValid) return null;
|
|
57
78
|
const payload = JSON.parse(decodeBase64Url(encodedPayload));
|
|
58
|
-
|
|
79
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
80
|
+
if (payload.exp !== void 0 && payload.exp < now) return null;
|
|
59
81
|
return payload;
|
|
60
82
|
} catch {
|
|
61
83
|
return null;
|
|
62
84
|
}
|
|
63
85
|
}
|
|
64
|
-
function signEncrypted(payload, privateKey, options = {}) {
|
|
65
|
-
|
|
86
|
+
function signEncrypted(payload, privateKey, encryptionSecret, options = {}) {
|
|
87
|
+
const encrypted = encryptPayload(JSON.stringify(payload), encryptionSecret);
|
|
88
|
+
return `sprx_${sign({ enc: encrypted }, privateKey, options).slice(2)}`;
|
|
66
89
|
}
|
|
67
|
-
function verifyEncrypted(token, publicKey) {
|
|
68
|
-
|
|
90
|
+
function verifyEncrypted(token, publicKey, encryptionSecret) {
|
|
91
|
+
if (!token.startsWith("sprx_")) return null;
|
|
92
|
+
const verified = verify(`ey${token.slice(5)}`, publicKey);
|
|
93
|
+
if (!verified?.enc || typeof verified.enc !== "string") return null;
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(decryptPayload(verified.enc, encryptionSecret));
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
69
99
|
}
|
|
70
100
|
function createTokenPair(payload, privateKey, options = {}) {
|
|
71
101
|
const accessToken = sign(payload, privateKey, options);
|
|
72
|
-
const
|
|
73
|
-
|
|
102
|
+
const refreshToken = sign(
|
|
103
|
+
{ sub: payload.sub ?? payload.id, type: "refresh" },
|
|
104
|
+
privateKey,
|
|
105
|
+
{ expiresIn: "7d", issuer: options.issuer }
|
|
106
|
+
);
|
|
74
107
|
return { accessToken, refreshToken };
|
|
75
108
|
}
|
|
76
|
-
function verifyTokenPair(
|
|
77
|
-
const [accessToken, refreshToken] = token.split(".");
|
|
109
|
+
function verifyTokenPair(accessToken, refreshToken, publicKey) {
|
|
78
110
|
return {
|
|
79
111
|
accessToken: verify(accessToken, publicKey),
|
|
80
112
|
refreshToken: verify(refreshToken, publicKey)
|
|
@@ -83,12 +115,9 @@ function verifyTokenPair(token, publicKey) {
|
|
|
83
115
|
function getJwtFromEnv() {
|
|
84
116
|
let publicKey = process.env.JWT_PUBLIC_KEY;
|
|
85
117
|
let privateKey = process.env.JWT_PRIVATE_KEY;
|
|
86
|
-
if (!publicKey || !privateKey)
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
publicKey = publicKey.replace(/^['"]|['"]$/g, "").replace(/\\n/g, "\n");
|
|
90
|
-
privateKey = privateKey.replace(/^['"]|['"]$/g, "").replace(/\\n/g, "\n");
|
|
91
|
-
return { publicKey, privateKey };
|
|
118
|
+
if (!publicKey || !privateKey) throw new Error("JWT keys not configured. Run 'npm run generate:keys' and add the keys to your .env file.");
|
|
119
|
+
const normalize = (k) => k.replace(/^['"]|['"]$/g, "").replace(/\\n/g, "\n");
|
|
120
|
+
return { publicKey: normalize(publicKey), privateKey: normalize(privateKey) };
|
|
92
121
|
}
|
|
93
122
|
exports.createTokenPair = createTokenPair;
|
|
94
123
|
exports.generateKeyPair = generateKeyPair;
|
package/dist/esm/cli.js
CHANGED
|
@@ -3,7 +3,6 @@ import { spawn } from "child_process";
|
|
|
3
3
|
import { existsSync } from "fs";
|
|
4
4
|
import * as crypto from "crypto";
|
|
5
5
|
import { join, resolve } from "path";
|
|
6
|
-
import dotenv from "dotenv";
|
|
7
6
|
const args = process.argv.slice(2);
|
|
8
7
|
const command = args[0];
|
|
9
8
|
if (!command) {
|
|
@@ -38,21 +37,6 @@ function getProjectRoot() {
|
|
|
38
37
|
}
|
|
39
38
|
return process.cwd();
|
|
40
39
|
}
|
|
41
|
-
function loadEnv() {
|
|
42
|
-
const projectRoot2 = getProjectRoot();
|
|
43
|
-
const envFile = existsSync(join(projectRoot2, ".env")) ? ".env" : existsSync(join(projectRoot2, ".env.development")) ? ".env.development" : existsSync(join(projectRoot2, ".env.production")) ? ".env.production" : null;
|
|
44
|
-
if (envFile) dotenv.config({ path: join(projectRoot2, envFile), quiet: true });
|
|
45
|
-
}
|
|
46
|
-
function checkJwtKeys() {
|
|
47
|
-
loadEnv();
|
|
48
|
-
const publicKey = process.env.JWT_PUBLIC_KEY;
|
|
49
|
-
const privateKey = process.env.JWT_PRIVATE_KEY;
|
|
50
|
-
if (!publicKey || !privateKey) {
|
|
51
|
-
console.error("\nā Error: JWT keys not configured.");
|
|
52
|
-
console.error("Run 'npm run generate:keys' to generate new keys.\n");
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
40
|
const projectRoot = getProjectRoot();
|
|
57
41
|
function runCommand(cmd, envVars) {
|
|
58
42
|
const child = spawn(cmd, args.slice(1), {
|
|
@@ -67,7 +51,6 @@ function runCommand(cmd, envVars) {
|
|
|
67
51
|
}
|
|
68
52
|
switch (command) {
|
|
69
53
|
case "dev":
|
|
70
|
-
checkJwtKeys();
|
|
71
54
|
console.log("š Starting development server with hot reload...");
|
|
72
55
|
const srcFile = existsSync(join(projectRoot, "src/app.ts")) ? "src/app.ts" : existsSync(join(projectRoot, "src/app.js")) ? "src/app.js" : existsSync(join(projectRoot, "src/index.ts")) ? "src/index.ts" : "src/index.js";
|
|
73
56
|
runCommand(`tsx --watch ${srcFile}`, { NODE_ENV: "development" });
|
|
@@ -82,7 +65,6 @@ switch (command) {
|
|
|
82
65
|
}
|
|
83
66
|
break;
|
|
84
67
|
case "start":
|
|
85
|
-
checkJwtKeys();
|
|
86
68
|
console.log("š Starting production server...");
|
|
87
69
|
runCommand("node dist/index.js", { NODE_ENV: "production" });
|
|
88
70
|
break;
|
|
@@ -1,78 +1,110 @@
|
|
|
1
1
|
import crypto__default from "crypto";
|
|
2
|
+
const base64UrlEncode = (buffer) => buffer.toString("base64url");
|
|
3
|
+
const base64UrlDecode = (str) => Buffer.from(str, "base64url");
|
|
4
|
+
const decodeBase64Url = (str) => Buffer.from(str, "base64url").toString("utf8");
|
|
2
5
|
const ALGORITHM = "ES256";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
function base64UrlDecode(str) {
|
|
7
|
-
const pad = str.length % 4;
|
|
8
|
-
const padded = pad ? str + "=".repeat(4 - pad) : str;
|
|
9
|
-
return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
10
|
-
}
|
|
11
|
-
function decodeBase64Url(str) {
|
|
12
|
-
return Buffer.from(str, "base64url").toString();
|
|
13
|
-
}
|
|
14
|
-
function generateKeyPair(algorithm = ALGORITHM) {
|
|
6
|
+
const ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
7
|
+
function generateKeyPair() {
|
|
15
8
|
const { publicKey, privateKey } = crypto__default.generateKeyPairSync("ec", {
|
|
16
9
|
namedCurve: "prime256v1",
|
|
17
|
-
publicKeyEncoding: {
|
|
18
|
-
|
|
19
|
-
format: "pem"
|
|
20
|
-
},
|
|
21
|
-
privateKeyEncoding: {
|
|
22
|
-
type: "pkcs8",
|
|
23
|
-
format: "pem"
|
|
24
|
-
}
|
|
10
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
11
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
25
12
|
});
|
|
26
13
|
return { publicKey, privateKey };
|
|
27
14
|
}
|
|
28
|
-
function
|
|
29
|
-
|
|
15
|
+
function parseExpiry(expiresIn) {
|
|
16
|
+
if (typeof expiresIn === "number") return expiresIn;
|
|
17
|
+
const units = {
|
|
18
|
+
s: 1,
|
|
19
|
+
m: 60,
|
|
20
|
+
h: 3600,
|
|
21
|
+
d: 86400,
|
|
22
|
+
w: 604800
|
|
23
|
+
};
|
|
24
|
+
const match = expiresIn.match(/^(\d+)([smhdw])$/);
|
|
25
|
+
if (!match) throw new Error(`Invalid expiresIn format: "${expiresIn}". Use e.g. "15m", "2h", "7d".`);
|
|
26
|
+
return parseInt(match[1]) * units[match[2]];
|
|
27
|
+
}
|
|
28
|
+
function buildClaims(payload, options) {
|
|
30
29
|
const now = Math.floor(Date.now() / 1e3);
|
|
31
|
-
const claims = { ...payload };
|
|
32
|
-
if (options.expiresIn)
|
|
33
|
-
claims.exp = typeof options.expiresIn === "number" ? now + options.expiresIn : now + parseInt(options.expiresIn);
|
|
34
|
-
}
|
|
30
|
+
const claims = { ...payload, iat: now };
|
|
31
|
+
if (options.expiresIn !== void 0) claims.exp = now + parseExpiry(options.expiresIn);
|
|
35
32
|
if (options.issuer) claims.iss = options.issuer;
|
|
36
33
|
if (options.subject) claims.sub = options.subject;
|
|
37
34
|
if (options.audience) claims.aud = options.audience;
|
|
38
|
-
claims
|
|
35
|
+
return claims;
|
|
36
|
+
}
|
|
37
|
+
function encryptPayload(plaintext, secret) {
|
|
38
|
+
const key = crypto__default.scryptSync(secret, "sprint-jwt-salt", 32);
|
|
39
|
+
const iv = crypto__default.randomBytes(12);
|
|
40
|
+
const cipher = crypto__default.createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
41
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
42
|
+
const tag = cipher.getAuthTag();
|
|
43
|
+
return base64UrlEncode(Buffer.concat([iv, tag, encrypted]));
|
|
44
|
+
}
|
|
45
|
+
function decryptPayload(data, secret) {
|
|
46
|
+
const key = crypto__default.scryptSync(secret, "sprint-jwt-salt", 32);
|
|
47
|
+
const buf = base64UrlDecode(data);
|
|
48
|
+
if (buf.length < 28) throw new Error("Invalid encrypted payload.");
|
|
49
|
+
const iv = buf.subarray(0, 12);
|
|
50
|
+
const tag = buf.subarray(12, 28);
|
|
51
|
+
const ciphertext = buf.subarray(28);
|
|
52
|
+
const decipher = crypto__default.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
53
|
+
decipher.setAuthTag(tag);
|
|
54
|
+
return decipher.update(ciphertext).toString("utf8") + decipher.final("utf8");
|
|
55
|
+
}
|
|
56
|
+
function sign(payload, privateKey, options = {}) {
|
|
57
|
+
const header = { alg: ALGORITHM, typ: "JWT" };
|
|
58
|
+
const claims = buildClaims(payload, options);
|
|
39
59
|
const encodedHeader = base64UrlEncode(Buffer.from(JSON.stringify(header)));
|
|
40
60
|
const encodedPayload = base64UrlEncode(Buffer.from(JSON.stringify(claims)));
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
62
|
+
const signer = crypto__default.createSign("SHA256");
|
|
63
|
+
signer.update(signingInput);
|
|
64
|
+
const signature = signer.sign(privateKey);
|
|
65
|
+
return `${signingInput}.${base64UrlEncode(signature)}`;
|
|
45
66
|
}
|
|
46
67
|
function verify(token, publicKey) {
|
|
47
68
|
try {
|
|
48
69
|
const parts = token.split(".");
|
|
49
70
|
if (parts.length !== 3) return null;
|
|
50
|
-
const [encodedHeader, encodedPayload,
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const isValid =
|
|
71
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
72
|
+
const verifier = crypto__default.createVerify("SHA256");
|
|
73
|
+
verifier.update(`${encodedHeader}.${encodedPayload}`);
|
|
74
|
+
const isValid = verifier.verify(publicKey, base64UrlDecode(encodedSignature));
|
|
54
75
|
if (!isValid) return null;
|
|
55
76
|
const payload = JSON.parse(decodeBase64Url(encodedPayload));
|
|
56
|
-
|
|
77
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
78
|
+
if (payload.exp !== void 0 && payload.exp < now) return null;
|
|
57
79
|
return payload;
|
|
58
80
|
} catch {
|
|
59
81
|
return null;
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
|
-
function signEncrypted(payload, privateKey, options = {}) {
|
|
63
|
-
|
|
84
|
+
function signEncrypted(payload, privateKey, encryptionSecret, options = {}) {
|
|
85
|
+
const encrypted = encryptPayload(JSON.stringify(payload), encryptionSecret);
|
|
86
|
+
return `sprx_${sign({ enc: encrypted }, privateKey, options).slice(2)}`;
|
|
64
87
|
}
|
|
65
|
-
function verifyEncrypted(token, publicKey) {
|
|
66
|
-
|
|
88
|
+
function verifyEncrypted(token, publicKey, encryptionSecret) {
|
|
89
|
+
if (!token.startsWith("sprx_")) return null;
|
|
90
|
+
const verified = verify(`ey${token.slice(5)}`, publicKey);
|
|
91
|
+
if (!verified?.enc || typeof verified.enc !== "string") return null;
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(decryptPayload(verified.enc, encryptionSecret));
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
67
97
|
}
|
|
68
98
|
function createTokenPair(payload, privateKey, options = {}) {
|
|
69
99
|
const accessToken = sign(payload, privateKey, options);
|
|
70
|
-
const
|
|
71
|
-
|
|
100
|
+
const refreshToken = sign(
|
|
101
|
+
{ sub: payload.sub ?? payload.id, type: "refresh" },
|
|
102
|
+
privateKey,
|
|
103
|
+
{ expiresIn: "7d", issuer: options.issuer }
|
|
104
|
+
);
|
|
72
105
|
return { accessToken, refreshToken };
|
|
73
106
|
}
|
|
74
|
-
function verifyTokenPair(
|
|
75
|
-
const [accessToken, refreshToken] = token.split(".");
|
|
107
|
+
function verifyTokenPair(accessToken, refreshToken, publicKey) {
|
|
76
108
|
return {
|
|
77
109
|
accessToken: verify(accessToken, publicKey),
|
|
78
110
|
refreshToken: verify(refreshToken, publicKey)
|
|
@@ -81,12 +113,9 @@ function verifyTokenPair(token, publicKey) {
|
|
|
81
113
|
function getJwtFromEnv() {
|
|
82
114
|
let publicKey = process.env.JWT_PUBLIC_KEY;
|
|
83
115
|
let privateKey = process.env.JWT_PRIVATE_KEY;
|
|
84
|
-
if (!publicKey || !privateKey)
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
publicKey = publicKey.replace(/^['"]|['"]$/g, "").replace(/\\n/g, "\n");
|
|
88
|
-
privateKey = privateKey.replace(/^['"]|['"]$/g, "").replace(/\\n/g, "\n");
|
|
89
|
-
return { publicKey, privateKey };
|
|
116
|
+
if (!publicKey || !privateKey) throw new Error("JWT keys not configured. Run 'npm run generate:keys' and add the keys to your .env file.");
|
|
117
|
+
const normalize = (k) => k.replace(/^['"]|['"]$/g, "").replace(/\\n/g, "\n");
|
|
118
|
+
return { publicKey: normalize(publicKey), privateKey: normalize(privateKey) };
|
|
90
119
|
}
|
|
91
120
|
export {
|
|
92
121
|
createTokenPair,
|
|
@@ -11,16 +11,16 @@ export interface KeyPair {
|
|
|
11
11
|
publicKey: string;
|
|
12
12
|
privateKey: string;
|
|
13
13
|
}
|
|
14
|
-
export declare function generateKeyPair(
|
|
14
|
+
export declare function generateKeyPair(): KeyPair;
|
|
15
15
|
export declare function sign(payload: JWTPayload, privateKey: string, options?: JWTOptions): string;
|
|
16
16
|
export declare function verify(token: string, publicKey: string): JWTPayload | null;
|
|
17
|
-
export declare function signEncrypted(payload: JWTPayload, privateKey: string, options?: JWTOptions): string;
|
|
18
|
-
export declare function verifyEncrypted(token: string, publicKey: string): JWTPayload | null;
|
|
17
|
+
export declare function signEncrypted(payload: JWTPayload, privateKey: string, encryptionSecret: string, options?: JWTOptions): string;
|
|
18
|
+
export declare function verifyEncrypted(token: string, publicKey: string, encryptionSecret: string): JWTPayload | null;
|
|
19
19
|
export declare function createTokenPair(payload: JWTPayload, privateKey: string, options?: JWTOptions): {
|
|
20
20
|
accessToken: string;
|
|
21
21
|
refreshToken: string;
|
|
22
22
|
};
|
|
23
|
-
export declare function verifyTokenPair(
|
|
23
|
+
export declare function verifyTokenPair(accessToken: string, refreshToken: string, publicKey: string): {
|
|
24
24
|
accessToken: JWTPayload | null;
|
|
25
25
|
refreshToken: JWTPayload | null;
|
|
26
26
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/jwt/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/jwt/index.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,UAAU;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,OAAO;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,eAAe,IAAI,OAAO,CAOzC;AAwDD,wBAAgB,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,MAAM,CAa9F;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAoB1E;AAMD,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,MAAM,CAGjI;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAU7G;AAID,wBAAgB,eAAe,CAC3B,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,UAAe,GACzB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAQ/C;AAED,wBAAgB,eAAe,CAC3B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAClB;IAAE,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC;IAAC,YAAY,EAAE,UAAU,GAAG,IAAI,CAAA;CAAE,CAKrE;AAID,wBAAgB,aAAa,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;CAAE,CAQ1E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/utils/index.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,MAAsC,CAAC;AACxF,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,KAAG,MAAuC,CAAC;AACtF,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,KAAG,MAAwD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprint-es",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.45",
|
|
4
4
|
"description": "Sprint - Quickly API",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -42,6 +42,11 @@
|
|
|
42
42
|
"types": "./dist/types/modules/jwt/index.d.ts",
|
|
43
43
|
"require": "./dist/cjs/modules/jwt/index.cjs",
|
|
44
44
|
"import": "./dist/esm/modules/jwt/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./utils": {
|
|
47
|
+
"types": "./dist/types/modules/utils/index.d.ts",
|
|
48
|
+
"require": "./dist/cjs/modules/utils/index.cjs",
|
|
49
|
+
"import": "./dist/esm/modules/utils/index.js"
|
|
45
50
|
}
|
|
46
51
|
},
|
|
47
52
|
"files": [
|