@lifestreamdynamics/vault-sdk 1.0.0
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/LICENSE +21 -0
- package/README.md +1121 -0
- package/dist/client.d.ts +143 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +286 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +49 -0
- package/dist/errors.js.map +1 -0
- package/dist/handle-error.d.ts +8 -0
- package/dist/handle-error.d.ts.map +1 -0
- package/dist/handle-error.js +35 -0
- package/dist/handle-error.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/audit-logger.d.ts +29 -0
- package/dist/lib/audit-logger.d.ts.map +1 -0
- package/dist/lib/audit-logger.js +99 -0
- package/dist/lib/audit-logger.js.map +1 -0
- package/dist/lib/encryption.d.ts +34 -0
- package/dist/lib/encryption.d.ts.map +1 -0
- package/dist/lib/encryption.js +87 -0
- package/dist/lib/encryption.js.map +1 -0
- package/dist/lib/signature.d.ts +47 -0
- package/dist/lib/signature.d.ts.map +1 -0
- package/dist/lib/signature.js +71 -0
- package/dist/lib/signature.js.map +1 -0
- package/dist/lib/token-manager.d.ts +80 -0
- package/dist/lib/token-manager.d.ts.map +1 -0
- package/dist/lib/token-manager.js +116 -0
- package/dist/lib/token-manager.js.map +1 -0
- package/dist/resources/admin.d.ts +280 -0
- package/dist/resources/admin.d.ts.map +1 -0
- package/dist/resources/admin.js +236 -0
- package/dist/resources/admin.js.map +1 -0
- package/dist/resources/ai.d.ts +184 -0
- package/dist/resources/ai.d.ts.map +1 -0
- package/dist/resources/ai.js +179 -0
- package/dist/resources/ai.js.map +1 -0
- package/dist/resources/api-keys.d.ts +172 -0
- package/dist/resources/api-keys.d.ts.map +1 -0
- package/dist/resources/api-keys.js +166 -0
- package/dist/resources/api-keys.js.map +1 -0
- package/dist/resources/connectors.d.ts +263 -0
- package/dist/resources/connectors.d.ts.map +1 -0
- package/dist/resources/connectors.js +226 -0
- package/dist/resources/connectors.js.map +1 -0
- package/dist/resources/documents.d.ts +334 -0
- package/dist/resources/documents.d.ts.map +1 -0
- package/dist/resources/documents.js +377 -0
- package/dist/resources/documents.js.map +1 -0
- package/dist/resources/hooks.d.ts +195 -0
- package/dist/resources/hooks.d.ts.map +1 -0
- package/dist/resources/hooks.js +166 -0
- package/dist/resources/hooks.js.map +1 -0
- package/dist/resources/publish.d.ts +165 -0
- package/dist/resources/publish.d.ts.map +1 -0
- package/dist/resources/publish.js +150 -0
- package/dist/resources/publish.js.map +1 -0
- package/dist/resources/search.d.ts +94 -0
- package/dist/resources/search.d.ts.map +1 -0
- package/dist/resources/search.js +76 -0
- package/dist/resources/search.js.map +1 -0
- package/dist/resources/shares.d.ts +130 -0
- package/dist/resources/shares.d.ts.map +1 -0
- package/dist/resources/shares.js +115 -0
- package/dist/resources/shares.js.map +1 -0
- package/dist/resources/subscription.d.ts +172 -0
- package/dist/resources/subscription.d.ts.map +1 -0
- package/dist/resources/subscription.js +166 -0
- package/dist/resources/subscription.js.map +1 -0
- package/dist/resources/teams.d.ts +356 -0
- package/dist/resources/teams.d.ts.map +1 -0
- package/dist/resources/teams.js +395 -0
- package/dist/resources/teams.js.map +1 -0
- package/dist/resources/user.d.ts +92 -0
- package/dist/resources/user.d.ts.map +1 -0
- package/dist/resources/user.js +64 -0
- package/dist/resources/user.js.map +1 -0
- package/dist/resources/vaults.d.ts +144 -0
- package/dist/resources/vaults.d.ts.map +1 -0
- package/dist/resources/vaults.js +158 -0
- package/dist/resources/vaults.js.map +1 -0
- package/dist/resources/webhooks.d.ts +187 -0
- package/dist/resources/webhooks.d.ts.map +1 -0
- package/dist/resources/webhooks.js +171 -0
- package/dist/resources/webhooks.js.map +1 -0
- package/dist/types/api.d.ts +17 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +2 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/resources.d.ts +5 -0
- package/dist/types/resources.d.ts.map +1 -0
- package/dist/types/resources.js +2 -0
- package/dist/types/resources.js.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { LifestreamVaultClient } from './client.js';
|
|
2
|
+
export { VaultsResource } from './resources/vaults.js';
|
|
3
|
+
export { DocumentsResource } from './resources/documents.js';
|
|
4
|
+
export { SearchResource } from './resources/search.js';
|
|
5
|
+
export { AiResource } from './resources/ai.js';
|
|
6
|
+
export { ApiKeysResource } from './resources/api-keys.js';
|
|
7
|
+
export { UserResource } from './resources/user.js';
|
|
8
|
+
export { SubscriptionResource } from './resources/subscription.js';
|
|
9
|
+
export { TeamsResource } from './resources/teams.js';
|
|
10
|
+
export { SharesResource } from './resources/shares.js';
|
|
11
|
+
export { PublishResource } from './resources/publish.js';
|
|
12
|
+
export { ConnectorsResource } from './resources/connectors.js';
|
|
13
|
+
export { HooksResource } from './resources/hooks.js';
|
|
14
|
+
export { WebhooksResource } from './resources/webhooks.js';
|
|
15
|
+
export { AdminResource } from './resources/admin.js';
|
|
16
|
+
// Request signing
|
|
17
|
+
export { signRequest, buildSignaturePayload, signPayload, generateNonce, SIGNATURE_HEADER, SIGNATURE_TIMESTAMP_HEADER, SIGNATURE_NONCE_HEADER, MAX_TIMESTAMP_AGE_MS, } from './lib/signature.js';
|
|
18
|
+
// Audit logging
|
|
19
|
+
export { AuditLogger } from './lib/audit-logger.js';
|
|
20
|
+
// Encryption
|
|
21
|
+
export { generateVaultKey, encrypt as encryptContent, decrypt as decryptContent, isEncryptedEnvelope, } from './lib/encryption.js';
|
|
22
|
+
// Token management
|
|
23
|
+
export { TokenManager, decodeJwtPayload, isTokenExpired, } from './lib/token-manager.js';
|
|
24
|
+
// Error classes
|
|
25
|
+
export { SDKError, ValidationError, AuthenticationError, AuthorizationError, NotFoundError, ConflictError, RateLimitError, NetworkError, } from './errors.js';
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAsB,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEnE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAY/D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAerD,kBAAkB;AAClB,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,gBAAgB;AAChB,OAAO,EAAE,WAAW,EAA4C,MAAM,uBAAuB,CAAC;AAE9F,aAAa;AACb,OAAO,EACL,gBAAgB,EAChB,OAAO,IAAI,cAAc,EACzB,OAAO,IAAI,cAAc,EACzB,mBAAmB,GAEpB,MAAM,qBAAqB,CAAC;AAE7B,mBAAmB;AACnB,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,GAKf,MAAM,wBAAwB,CAAC;AAEhC,gBAAgB;AAChB,OAAO,EACL,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface AuditEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
method: string;
|
|
4
|
+
path: string;
|
|
5
|
+
status: number;
|
|
6
|
+
durationMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface AuditLoggerOptions {
|
|
9
|
+
logPath?: string;
|
|
10
|
+
maxSize?: number;
|
|
11
|
+
maxFiles?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class AuditLogger {
|
|
14
|
+
private readonly logPath;
|
|
15
|
+
private readonly maxSize;
|
|
16
|
+
private readonly maxFiles;
|
|
17
|
+
constructor(options?: AuditLoggerOptions);
|
|
18
|
+
getLogPath(): string;
|
|
19
|
+
log(entry: AuditEntry): void;
|
|
20
|
+
private rotateIfNeeded;
|
|
21
|
+
readEntries(options?: {
|
|
22
|
+
tail?: number;
|
|
23
|
+
status?: number;
|
|
24
|
+
since?: string;
|
|
25
|
+
until?: string;
|
|
26
|
+
}): AuditEntry[];
|
|
27
|
+
exportCsv(entries: AuditEntry[]): string;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=audit-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logger.d.ts","sourceRoot":"","sources":["../../src/lib/audit-logger.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,OAAO,GAAE,kBAAuB;IAM5C,UAAU,IAAI,MAAM;IAIpB,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAY5B,OAAO,CAAC,cAAc;IA+BtB,WAAW,CAAC,OAAO,GAAE;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KACX,GAAG,UAAU,EAAE;IAiCrB,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM;CAOzC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
const DEFAULT_LOG_DIR = path.join(os.homedir(), '.lsvault');
|
|
5
|
+
const DEFAULT_LOG_FILE = 'audit.log';
|
|
6
|
+
const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
|
|
7
|
+
const MAX_ROTATED_FILES = 5;
|
|
8
|
+
export class AuditLogger {
|
|
9
|
+
logPath;
|
|
10
|
+
maxSize;
|
|
11
|
+
maxFiles;
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.logPath = options.logPath || path.join(DEFAULT_LOG_DIR, DEFAULT_LOG_FILE);
|
|
14
|
+
this.maxSize = options.maxSize || MAX_LOG_SIZE;
|
|
15
|
+
this.maxFiles = options.maxFiles || MAX_ROTATED_FILES;
|
|
16
|
+
}
|
|
17
|
+
getLogPath() {
|
|
18
|
+
return this.logPath;
|
|
19
|
+
}
|
|
20
|
+
log(entry) {
|
|
21
|
+
const dir = path.dirname(this.logPath);
|
|
22
|
+
if (!fs.existsSync(dir)) {
|
|
23
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
this.rotateIfNeeded();
|
|
26
|
+
const line = JSON.stringify(entry) + '\n';
|
|
27
|
+
fs.appendFileSync(this.logPath, line, 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
rotateIfNeeded() {
|
|
30
|
+
if (!fs.existsSync(this.logPath))
|
|
31
|
+
return;
|
|
32
|
+
let stat;
|
|
33
|
+
try {
|
|
34
|
+
stat = fs.statSync(this.logPath);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (stat.size < this.maxSize)
|
|
40
|
+
return;
|
|
41
|
+
// Delete oldest rotated file if it exists
|
|
42
|
+
const oldest = `${this.logPath}.${this.maxFiles}`;
|
|
43
|
+
if (fs.existsSync(oldest)) {
|
|
44
|
+
fs.unlinkSync(oldest);
|
|
45
|
+
}
|
|
46
|
+
// Shift existing rotated files: .4 -> .5, .3 -> .4, etc.
|
|
47
|
+
for (let i = this.maxFiles - 1; i >= 1; i--) {
|
|
48
|
+
const src = `${this.logPath}.${i}`;
|
|
49
|
+
const dest = `${this.logPath}.${i + 1}`;
|
|
50
|
+
if (fs.existsSync(src)) {
|
|
51
|
+
fs.renameSync(src, dest);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Rotate current log to .1
|
|
55
|
+
fs.renameSync(this.logPath, `${this.logPath}.1`);
|
|
56
|
+
}
|
|
57
|
+
readEntries(options = {}) {
|
|
58
|
+
if (!fs.existsSync(this.logPath))
|
|
59
|
+
return [];
|
|
60
|
+
const content = fs.readFileSync(this.logPath, 'utf-8');
|
|
61
|
+
const lines = content.split('\n').filter(Boolean);
|
|
62
|
+
let entries = [];
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
try {
|
|
65
|
+
entries.push(JSON.parse(line));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Skip malformed lines
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (options.status !== undefined) {
|
|
72
|
+
entries = entries.filter(e => e.status === options.status);
|
|
73
|
+
}
|
|
74
|
+
if (options.since) {
|
|
75
|
+
const sinceDate = new Date(options.since);
|
|
76
|
+
entries = entries.filter(e => new Date(e.timestamp) >= sinceDate);
|
|
77
|
+
}
|
|
78
|
+
if (options.until) {
|
|
79
|
+
const untilDate = new Date(options.until);
|
|
80
|
+
entries = entries.filter(e => new Date(e.timestamp) <= untilDate);
|
|
81
|
+
}
|
|
82
|
+
if (options.tail !== undefined) {
|
|
83
|
+
entries = entries.slice(-options.tail);
|
|
84
|
+
}
|
|
85
|
+
return entries;
|
|
86
|
+
}
|
|
87
|
+
exportCsv(entries) {
|
|
88
|
+
const header = 'timestamp,method,path,status,durationMs';
|
|
89
|
+
const rows = entries.map(e => `${e.timestamp},${e.method},${csvEscape(e.path)},${e.status},${e.durationMs}`);
|
|
90
|
+
return [header, ...rows].join('\n') + '\n';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function csvEscape(value) {
|
|
94
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
95
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=audit-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logger.js","sourceRoot":"","sources":["../../src/lib/audit-logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Envelope format for encrypted document content. */
|
|
2
|
+
export interface EncryptedEnvelope {
|
|
3
|
+
version: 1;
|
|
4
|
+
algorithm: 'aes-256-gcm';
|
|
5
|
+
iv: string;
|
|
6
|
+
authTag: string;
|
|
7
|
+
ciphertext: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Generate a random 256-bit encryption key.
|
|
11
|
+
* Returns the key as a hex string (64 characters).
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateVaultKey(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Encrypt plaintext content using AES-256-GCM.
|
|
16
|
+
*
|
|
17
|
+
* @param plaintext - The plaintext string to encrypt
|
|
18
|
+
* @param keyHex - The 256-bit key as a hex string
|
|
19
|
+
* @returns The encrypted envelope as a JSON string
|
|
20
|
+
*/
|
|
21
|
+
export declare function encrypt(plaintext: string, keyHex: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Decrypt content from an encrypted envelope using AES-256-GCM.
|
|
24
|
+
*
|
|
25
|
+
* @param envelopeJson - The encrypted envelope as a JSON string
|
|
26
|
+
* @param keyHex - The 256-bit key as a hex string
|
|
27
|
+
* @returns The decrypted plaintext string
|
|
28
|
+
*/
|
|
29
|
+
export declare function decrypt(envelopeJson: string, keyHex: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a string is an encrypted envelope (basic structure check).
|
|
32
|
+
*/
|
|
33
|
+
export declare function isEncryptedEnvelope(content: string): boolean;
|
|
34
|
+
//# sourceMappingURL=encryption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/lib/encryption.ts"],"names":[],"mappings":"AAOA,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,aAAa,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAwBjE;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA4BpE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAa5D"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
3
|
+
const KEY_LENGTH = 32; // 256 bits
|
|
4
|
+
const IV_LENGTH = 12; // 96 bits (recommended for GCM)
|
|
5
|
+
const AUTH_TAG_LENGTH = 16; // 128 bits
|
|
6
|
+
/**
|
|
7
|
+
* Generate a random 256-bit encryption key.
|
|
8
|
+
* Returns the key as a hex string (64 characters).
|
|
9
|
+
*/
|
|
10
|
+
export function generateVaultKey() {
|
|
11
|
+
return crypto.randomBytes(KEY_LENGTH).toString('hex');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Encrypt plaintext content using AES-256-GCM.
|
|
15
|
+
*
|
|
16
|
+
* @param plaintext - The plaintext string to encrypt
|
|
17
|
+
* @param keyHex - The 256-bit key as a hex string
|
|
18
|
+
* @returns The encrypted envelope as a JSON string
|
|
19
|
+
*/
|
|
20
|
+
export function encrypt(plaintext, keyHex) {
|
|
21
|
+
const key = Buffer.from(keyHex, 'hex');
|
|
22
|
+
if (key.length !== KEY_LENGTH) {
|
|
23
|
+
throw new Error(`Invalid key length: expected ${KEY_LENGTH} bytes, got ${key.length}`);
|
|
24
|
+
}
|
|
25
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
26
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
27
|
+
const encrypted = Buffer.concat([
|
|
28
|
+
cipher.update(plaintext, 'utf8'),
|
|
29
|
+
cipher.final(),
|
|
30
|
+
]);
|
|
31
|
+
const authTag = cipher.getAuthTag();
|
|
32
|
+
const envelope = {
|
|
33
|
+
version: 1,
|
|
34
|
+
algorithm: ALGORITHM,
|
|
35
|
+
iv: iv.toString('hex'),
|
|
36
|
+
authTag: authTag.toString('hex'),
|
|
37
|
+
ciphertext: encrypted.toString('hex'),
|
|
38
|
+
};
|
|
39
|
+
return JSON.stringify(envelope);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Decrypt content from an encrypted envelope using AES-256-GCM.
|
|
43
|
+
*
|
|
44
|
+
* @param envelopeJson - The encrypted envelope as a JSON string
|
|
45
|
+
* @param keyHex - The 256-bit key as a hex string
|
|
46
|
+
* @returns The decrypted plaintext string
|
|
47
|
+
*/
|
|
48
|
+
export function decrypt(envelopeJson, keyHex) {
|
|
49
|
+
const key = Buffer.from(keyHex, 'hex');
|
|
50
|
+
if (key.length !== KEY_LENGTH) {
|
|
51
|
+
throw new Error(`Invalid key length: expected ${KEY_LENGTH} bytes, got ${key.length}`);
|
|
52
|
+
}
|
|
53
|
+
const envelope = JSON.parse(envelopeJson);
|
|
54
|
+
if (envelope.version !== 1) {
|
|
55
|
+
throw new Error(`Unsupported encryption envelope version: ${envelope.version}`);
|
|
56
|
+
}
|
|
57
|
+
if (envelope.algorithm !== ALGORITHM) {
|
|
58
|
+
throw new Error(`Unsupported encryption algorithm: ${envelope.algorithm}`);
|
|
59
|
+
}
|
|
60
|
+
const iv = Buffer.from(envelope.iv, 'hex');
|
|
61
|
+
const authTag = Buffer.from(envelope.authTag, 'hex');
|
|
62
|
+
const ciphertext = Buffer.from(envelope.ciphertext, 'hex');
|
|
63
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
64
|
+
decipher.setAuthTag(authTag);
|
|
65
|
+
const decrypted = Buffer.concat([
|
|
66
|
+
decipher.update(ciphertext),
|
|
67
|
+
decipher.final(),
|
|
68
|
+
]);
|
|
69
|
+
return decrypted.toString('utf8');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if a string is an encrypted envelope (basic structure check).
|
|
73
|
+
*/
|
|
74
|
+
export function isEncryptedEnvelope(content) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(content);
|
|
77
|
+
return (parsed.version === 1
|
|
78
|
+
&& parsed.algorithm === ALGORITHM
|
|
79
|
+
&& typeof parsed.iv === 'string'
|
|
80
|
+
&& typeof parsed.authTag === 'string'
|
|
81
|
+
&& typeof parsed.ciphertext === 'string');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=encryption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../src/lib/encryption.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headers used for HMAC request signing.
|
|
3
|
+
*/
|
|
4
|
+
export declare const SIGNATURE_HEADER = "x-signature";
|
|
5
|
+
export declare const SIGNATURE_TIMESTAMP_HEADER = "x-signature-timestamp";
|
|
6
|
+
export declare const SIGNATURE_NONCE_HEADER = "x-signature-nonce";
|
|
7
|
+
/**
|
|
8
|
+
* Maximum age (in milliseconds) for a signed request timestamp.
|
|
9
|
+
* Requests older than this are rejected to prevent replay attacks.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_TIMESTAMP_AGE_MS: number;
|
|
12
|
+
/**
|
|
13
|
+
* Constructs the canonical payload string for HMAC signing.
|
|
14
|
+
*
|
|
15
|
+
* Format: METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY_HASH
|
|
16
|
+
*
|
|
17
|
+
* @param method - HTTP method (uppercase)
|
|
18
|
+
* @param path - Request path (no query string)
|
|
19
|
+
* @param timestamp - ISO-8601 timestamp
|
|
20
|
+
* @param nonce - 16-byte hex nonce
|
|
21
|
+
* @param body - Request body string (empty string if no body)
|
|
22
|
+
* @returns The canonical payload string
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildSignaturePayload(method: string, path: string, timestamp: string, nonce: string, body: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Generates an HMAC-SHA256 signature for a request.
|
|
27
|
+
*
|
|
28
|
+
* @param secret - The API key used as the HMAC secret
|
|
29
|
+
* @param payload - The canonical payload string
|
|
30
|
+
* @returns Hex-encoded HMAC signature
|
|
31
|
+
*/
|
|
32
|
+
export declare function signPayload(secret: string, payload: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Generates a cryptographically secure 16-byte hex nonce.
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateNonce(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Signs a request and returns the headers to attach.
|
|
39
|
+
*
|
|
40
|
+
* @param apiKey - The full API key (used as HMAC secret)
|
|
41
|
+
* @param method - HTTP method
|
|
42
|
+
* @param path - Request path (without query string)
|
|
43
|
+
* @param body - Request body string (empty string for no body)
|
|
44
|
+
* @returns Object containing the three signature headers
|
|
45
|
+
*/
|
|
46
|
+
export declare function signRequest(apiKey: string, method: string, path: string, body?: string): Record<string, string>;
|
|
47
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/lib/signature.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAC9C,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAClE,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAAgB,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAKnE;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAW,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWxB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Headers used for HMAC request signing.
|
|
4
|
+
*/
|
|
5
|
+
export const SIGNATURE_HEADER = 'x-signature';
|
|
6
|
+
export const SIGNATURE_TIMESTAMP_HEADER = 'x-signature-timestamp';
|
|
7
|
+
export const SIGNATURE_NONCE_HEADER = 'x-signature-nonce';
|
|
8
|
+
/**
|
|
9
|
+
* Maximum age (in milliseconds) for a signed request timestamp.
|
|
10
|
+
* Requests older than this are rejected to prevent replay attacks.
|
|
11
|
+
*/
|
|
12
|
+
export const MAX_TIMESTAMP_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
13
|
+
/**
|
|
14
|
+
* Constructs the canonical payload string for HMAC signing.
|
|
15
|
+
*
|
|
16
|
+
* Format: METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY_HASH
|
|
17
|
+
*
|
|
18
|
+
* @param method - HTTP method (uppercase)
|
|
19
|
+
* @param path - Request path (no query string)
|
|
20
|
+
* @param timestamp - ISO-8601 timestamp
|
|
21
|
+
* @param nonce - 16-byte hex nonce
|
|
22
|
+
* @param body - Request body string (empty string if no body)
|
|
23
|
+
* @returns The canonical payload string
|
|
24
|
+
*/
|
|
25
|
+
export function buildSignaturePayload(method, path, timestamp, nonce, body) {
|
|
26
|
+
const bodyHash = crypto
|
|
27
|
+
.createHash('sha256')
|
|
28
|
+
.update(body)
|
|
29
|
+
.digest('hex');
|
|
30
|
+
return `${method.toUpperCase()}\n${path}\n${timestamp}\n${nonce}\n${bodyHash}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generates an HMAC-SHA256 signature for a request.
|
|
34
|
+
*
|
|
35
|
+
* @param secret - The API key used as the HMAC secret
|
|
36
|
+
* @param payload - The canonical payload string
|
|
37
|
+
* @returns Hex-encoded HMAC signature
|
|
38
|
+
*/
|
|
39
|
+
export function signPayload(secret, payload) {
|
|
40
|
+
return crypto
|
|
41
|
+
.createHmac('sha256', secret)
|
|
42
|
+
.update(payload)
|
|
43
|
+
.digest('hex');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generates a cryptographically secure 16-byte hex nonce.
|
|
47
|
+
*/
|
|
48
|
+
export function generateNonce() {
|
|
49
|
+
return crypto.randomBytes(16).toString('hex');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Signs a request and returns the headers to attach.
|
|
53
|
+
*
|
|
54
|
+
* @param apiKey - The full API key (used as HMAC secret)
|
|
55
|
+
* @param method - HTTP method
|
|
56
|
+
* @param path - Request path (without query string)
|
|
57
|
+
* @param body - Request body string (empty string for no body)
|
|
58
|
+
* @returns Object containing the three signature headers
|
|
59
|
+
*/
|
|
60
|
+
export function signRequest(apiKey, method, path, body = '') {
|
|
61
|
+
const timestamp = new Date().toISOString();
|
|
62
|
+
const nonce = generateNonce();
|
|
63
|
+
const payload = buildSignaturePayload(method, path, timestamp, nonce, body);
|
|
64
|
+
const signature = signPayload(apiKey, payload);
|
|
65
|
+
return {
|
|
66
|
+
[SIGNATURE_HEADER]: signature,
|
|
67
|
+
[SIGNATURE_TIMESTAMP_HEADER]: timestamp,
|
|
68
|
+
[SIGNATURE_NONCE_HEADER]: nonce,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/lib/signature.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAC9C,MAAM,CAAC,MAAM,0BAA0B,GAAG,uBAAuB,CAAC;AAClE,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAE1D;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAc,EACd,IAAY,EACZ,SAAiB,EACjB,KAAa,EACb,IAAY;IAEZ,MAAM,QAAQ,GAAG,MAAM;SACpB,UAAU,CAAC,QAAQ,CAAC;SACpB,MAAM,CAAC,IAAI,CAAC;SACZ,MAAM,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,KAAK,IAAI,KAAK,SAAS,KAAK,KAAK,KAAK,QAAQ,EAAE,CAAC;AACjF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,OAAe;IACzD,OAAO,MAAM;SACV,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;SAC5B,MAAM,CAAC,OAAO,CAAC;SACf,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,MAAc,EACd,IAAY,EACZ,OAAe,EAAE;IAEjB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE/C,OAAO;QACL,CAAC,gBAAgB,CAAC,EAAE,SAAS;QAC7B,CAAC,0BAA0B,CAAC,EAAE,SAAS;QACvC,CAAC,sBAAsB,CAAC,EAAE,KAAK;KAChC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { KyInstance } from 'ky';
|
|
2
|
+
/** Decoded JWT payload with standard claims. */
|
|
3
|
+
export interface JwtPayload {
|
|
4
|
+
exp: number;
|
|
5
|
+
iat?: number;
|
|
6
|
+
sub?: string;
|
|
7
|
+
email?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
/** Authentication tokens returned from login/refresh. */
|
|
11
|
+
export interface AuthTokens {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
user: {
|
|
14
|
+
id: string;
|
|
15
|
+
email: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
role: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Callback invoked when tokens are refreshed. */
|
|
22
|
+
export type OnTokenRefresh = (tokens: AuthTokens) => void;
|
|
23
|
+
/** Options for configuring the TokenManager. */
|
|
24
|
+
export interface TokenManagerOptions {
|
|
25
|
+
/** How many milliseconds before expiry to trigger a proactive refresh. Default: 60000 (1 min). */
|
|
26
|
+
refreshBufferMs?: number;
|
|
27
|
+
/** Called after a successful token refresh. */
|
|
28
|
+
onTokenRefresh?: OnTokenRefresh;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Decode a JWT payload without verification.
|
|
32
|
+
* Only decodes the payload section (second segment) from base64url.
|
|
33
|
+
*/
|
|
34
|
+
export declare function decodeJwtPayload(token: string): JwtPayload | null;
|
|
35
|
+
/**
|
|
36
|
+
* Check if a JWT token is expired or will expire within the given buffer.
|
|
37
|
+
*
|
|
38
|
+
* @param token - The JWT access token
|
|
39
|
+
* @param bufferMs - Milliseconds before actual expiry to consider it "expired"
|
|
40
|
+
* @returns true if the token is expired or will expire within bufferMs
|
|
41
|
+
*/
|
|
42
|
+
export declare function isTokenExpired(token: string, bufferMs?: number): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Manages JWT access token lifecycle with automatic refresh.
|
|
45
|
+
*
|
|
46
|
+
* Handles:
|
|
47
|
+
* - Proactive refresh before token expiry (beforeRequest hook)
|
|
48
|
+
* - Reactive refresh on 401 responses (afterResponse hook)
|
|
49
|
+
* - Infinite retry prevention via X-Retry-After-Refresh header
|
|
50
|
+
* - Deduplication of concurrent refresh requests
|
|
51
|
+
*/
|
|
52
|
+
export declare class TokenManager {
|
|
53
|
+
private accessToken;
|
|
54
|
+
private refreshToken;
|
|
55
|
+
private refreshPromise;
|
|
56
|
+
private readonly refreshBufferMs;
|
|
57
|
+
private readonly onTokenRefresh?;
|
|
58
|
+
constructor(accessToken: string, refreshToken: string | null, options?: TokenManagerOptions);
|
|
59
|
+
/** Get the current access token. */
|
|
60
|
+
getAccessToken(): string;
|
|
61
|
+
/** Get the current refresh token. */
|
|
62
|
+
getRefreshToken(): string | null;
|
|
63
|
+
/** Update the access token (e.g., after external login). */
|
|
64
|
+
setAccessToken(token: string): void;
|
|
65
|
+
/** Update the refresh token. */
|
|
66
|
+
setRefreshToken(token: string | null): void;
|
|
67
|
+
/** Check whether the current access token needs refreshing. */
|
|
68
|
+
needsRefresh(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Perform a token refresh using the API's /auth/refresh endpoint.
|
|
71
|
+
* Deduplicates concurrent calls so only one HTTP request is made.
|
|
72
|
+
*
|
|
73
|
+
* @param http - The ky instance to use for the refresh request (should NOT have auth hooks)
|
|
74
|
+
* @returns The new access token
|
|
75
|
+
* @throws If refresh fails (no refresh token, network error, etc.)
|
|
76
|
+
*/
|
|
77
|
+
refresh(http: KyInstance): Promise<string>;
|
|
78
|
+
private performRefresh;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=token-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/lib/token-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAErC,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,kDAAkD;AAClD,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;AAE1D,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IAClC,kGAAkG;IAClG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAoBjE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAe,GAAG,OAAO,CAMhF;AAED;;;;;;;;GAQG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,cAAc,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAiB;gBAG/C,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,GAAE,mBAAwB;IAQnC,oCAAoC;IACpC,cAAc,IAAI,MAAM;IAIxB,qCAAqC;IACrC,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,4DAA4D;IAC5D,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAInC,gCAAgC;IAChC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI3C,+DAA+D;IAC/D,YAAY,IAAI,OAAO;IAIvB;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;YAmBlC,cAAc;CAa7B"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode a JWT payload without verification.
|
|
3
|
+
* Only decodes the payload section (second segment) from base64url.
|
|
4
|
+
*/
|
|
5
|
+
export function decodeJwtPayload(token) {
|
|
6
|
+
try {
|
|
7
|
+
const parts = token.split('.');
|
|
8
|
+
if (parts.length !== 3)
|
|
9
|
+
return null;
|
|
10
|
+
const payload = parts[1];
|
|
11
|
+
// Base64url to base64
|
|
12
|
+
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
13
|
+
// Pad if needed
|
|
14
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
15
|
+
// Decode - works in both Node.js and browser
|
|
16
|
+
const decoded = typeof atob === 'function'
|
|
17
|
+
? atob(padded)
|
|
18
|
+
: Buffer.from(padded, 'base64').toString('utf-8');
|
|
19
|
+
return JSON.parse(decoded);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check if a JWT token is expired or will expire within the given buffer.
|
|
27
|
+
*
|
|
28
|
+
* @param token - The JWT access token
|
|
29
|
+
* @param bufferMs - Milliseconds before actual expiry to consider it "expired"
|
|
30
|
+
* @returns true if the token is expired or will expire within bufferMs
|
|
31
|
+
*/
|
|
32
|
+
export function isTokenExpired(token, bufferMs = 60_000) {
|
|
33
|
+
const payload = decodeJwtPayload(token);
|
|
34
|
+
if (!payload || !payload.exp)
|
|
35
|
+
return true;
|
|
36
|
+
const expiresAtMs = payload.exp * 1000;
|
|
37
|
+
return Date.now() > expiresAtMs - bufferMs;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Manages JWT access token lifecycle with automatic refresh.
|
|
41
|
+
*
|
|
42
|
+
* Handles:
|
|
43
|
+
* - Proactive refresh before token expiry (beforeRequest hook)
|
|
44
|
+
* - Reactive refresh on 401 responses (afterResponse hook)
|
|
45
|
+
* - Infinite retry prevention via X-Retry-After-Refresh header
|
|
46
|
+
* - Deduplication of concurrent refresh requests
|
|
47
|
+
*/
|
|
48
|
+
export class TokenManager {
|
|
49
|
+
accessToken;
|
|
50
|
+
refreshToken;
|
|
51
|
+
refreshPromise = null;
|
|
52
|
+
refreshBufferMs;
|
|
53
|
+
onTokenRefresh;
|
|
54
|
+
constructor(accessToken, refreshToken, options = {}) {
|
|
55
|
+
this.accessToken = accessToken;
|
|
56
|
+
this.refreshToken = refreshToken;
|
|
57
|
+
this.refreshBufferMs = options.refreshBufferMs ?? 60_000;
|
|
58
|
+
this.onTokenRefresh = options.onTokenRefresh;
|
|
59
|
+
}
|
|
60
|
+
/** Get the current access token. */
|
|
61
|
+
getAccessToken() {
|
|
62
|
+
return this.accessToken;
|
|
63
|
+
}
|
|
64
|
+
/** Get the current refresh token. */
|
|
65
|
+
getRefreshToken() {
|
|
66
|
+
return this.refreshToken;
|
|
67
|
+
}
|
|
68
|
+
/** Update the access token (e.g., after external login). */
|
|
69
|
+
setAccessToken(token) {
|
|
70
|
+
this.accessToken = token;
|
|
71
|
+
}
|
|
72
|
+
/** Update the refresh token. */
|
|
73
|
+
setRefreshToken(token) {
|
|
74
|
+
this.refreshToken = token;
|
|
75
|
+
}
|
|
76
|
+
/** Check whether the current access token needs refreshing. */
|
|
77
|
+
needsRefresh() {
|
|
78
|
+
return isTokenExpired(this.accessToken, this.refreshBufferMs);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Perform a token refresh using the API's /auth/refresh endpoint.
|
|
82
|
+
* Deduplicates concurrent calls so only one HTTP request is made.
|
|
83
|
+
*
|
|
84
|
+
* @param http - The ky instance to use for the refresh request (should NOT have auth hooks)
|
|
85
|
+
* @returns The new access token
|
|
86
|
+
* @throws If refresh fails (no refresh token, network error, etc.)
|
|
87
|
+
*/
|
|
88
|
+
async refresh(http) {
|
|
89
|
+
if (!this.refreshToken) {
|
|
90
|
+
throw new Error('No refresh token available');
|
|
91
|
+
}
|
|
92
|
+
// Deduplicate concurrent refresh requests
|
|
93
|
+
if (this.refreshPromise) {
|
|
94
|
+
return this.refreshPromise;
|
|
95
|
+
}
|
|
96
|
+
this.refreshPromise = this.performRefresh(http);
|
|
97
|
+
try {
|
|
98
|
+
return await this.refreshPromise;
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
this.refreshPromise = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async performRefresh(http) {
|
|
105
|
+
const response = await http.post('auth/refresh', {
|
|
106
|
+
headers: {
|
|
107
|
+
'X-Requested-With': 'LifestreamVaultSDK',
|
|
108
|
+
'Cookie': `lsv_refresh=${this.refreshToken}`,
|
|
109
|
+
},
|
|
110
|
+
}).json();
|
|
111
|
+
this.accessToken = response.accessToken;
|
|
112
|
+
this.onTokenRefresh?.(response);
|
|
113
|
+
return this.accessToken;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=token-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/lib/token-manager.ts"],"names":[],"mappings":"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"}
|