@bubltec/mycota-tokens 0.2.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/dist/encrypted.store.d.ts +17 -0
- package/dist/encrypted.store.d.ts.map +1 -0
- package/dist/encrypted.store.js +39 -0
- package/dist/encrypted.store.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.store.d.ts +9 -0
- package/dist/memory.store.d.ts.map +1 -0
- package/dist/memory.store.js +22 -0
- package/dist/memory.store.js.map +1 -0
- package/dist/oauth-refresh.d.ts +19 -0
- package/dist/oauth-refresh.d.ts.map +1 -0
- package/dist/oauth-refresh.js +89 -0
- package/dist/oauth-refresh.js.map +1 -0
- package/dist/ports.d.ts +33 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +2 -0
- package/dist/ports.js.map +1 -0
- package/dist/secret-box.d.ts +17 -0
- package/dist/secret-box.d.ts.map +1 -0
- package/dist/secret-box.js +39 -0
- package/dist/secret-box.js.map +1 -0
- package/dist/tokens.spec.d.ts +2 -0
- package/dist/tokens.spec.d.ts.map +1 -0
- package/dist/tokens.spec.js +86 -0
- package/dist/tokens.spec.js.map +1 -0
- package/dist/vault.d.ts +40 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +90 -0
- package/dist/vault.js.map +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SecretBox, TokenRecord, TokenStore } from './ports.js';
|
|
2
|
+
/**
|
|
3
|
+
* Encrypts `tokens` at rest. Metadata (provider, subject, health) stays
|
|
4
|
+
* plaintext so Connections can list `needs_reauth` without opening the box.
|
|
5
|
+
*/
|
|
6
|
+
export declare class EncryptedTokenStore implements TokenStore {
|
|
7
|
+
private readonly inner;
|
|
8
|
+
private readonly box;
|
|
9
|
+
constructor(inner: TokenStore, box: SecretBox);
|
|
10
|
+
get(provider: string, subjectId: string): Promise<TokenRecord | undefined>;
|
|
11
|
+
put(record: TokenRecord): Promise<void>;
|
|
12
|
+
delete(provider: string, subjectId: string): Promise<void>;
|
|
13
|
+
listByProvider(provider: string): Promise<TokenRecord[]>;
|
|
14
|
+
private seal;
|
|
15
|
+
private open;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=encrypted.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encrypted.store.d.ts","sourceRoot":"","sources":["../src/encrypted.store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKrE;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IAElD,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBADH,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,SAAS;IAG3B,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAM1E,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;YAKhD,IAAI;YAKJ,IAAI;CAInB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const encoder = new TextEncoder();
|
|
2
|
+
const decoder = new TextDecoder();
|
|
3
|
+
/**
|
|
4
|
+
* Encrypts `tokens` at rest. Metadata (provider, subject, health) stays
|
|
5
|
+
* plaintext so Connections can list `needs_reauth` without opening the box.
|
|
6
|
+
*/
|
|
7
|
+
export class EncryptedTokenStore {
|
|
8
|
+
inner;
|
|
9
|
+
box;
|
|
10
|
+
constructor(inner, box) {
|
|
11
|
+
this.inner = inner;
|
|
12
|
+
this.box = box;
|
|
13
|
+
}
|
|
14
|
+
async get(provider, subjectId) {
|
|
15
|
+
const record = await this.inner.get(provider, subjectId);
|
|
16
|
+
if (!record)
|
|
17
|
+
return undefined;
|
|
18
|
+
return this.open(record);
|
|
19
|
+
}
|
|
20
|
+
async put(record) {
|
|
21
|
+
await this.inner.put(await this.seal(record));
|
|
22
|
+
}
|
|
23
|
+
async delete(provider, subjectId) {
|
|
24
|
+
await this.inner.delete(provider, subjectId);
|
|
25
|
+
}
|
|
26
|
+
async listByProvider(provider) {
|
|
27
|
+
const records = await this.inner.listByProvider(provider);
|
|
28
|
+
return Promise.all(records.map((record) => this.open(record)));
|
|
29
|
+
}
|
|
30
|
+
async seal(record) {
|
|
31
|
+
const sealed = await this.box.seal(encoder.encode(JSON.stringify(record.tokens)));
|
|
32
|
+
return { ...record, tokens: { accessToken: Buffer.from(sealed).toString('base64') } };
|
|
33
|
+
}
|
|
34
|
+
async open(record) {
|
|
35
|
+
const opened = await this.box.open(Buffer.from(record.tokens.accessToken, 'base64'));
|
|
36
|
+
return { ...record, tokens: JSON.parse(decoder.decode(opened)) };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=encrypted.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encrypted.store.js","sourceRoot":"","sources":["../src/encrypted.store.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAClC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC;;;GAGG;AACH,MAAM,OAAO,mBAAmB;IAEX;IACA;IAFnB,YACmB,KAAiB,EACjB,GAAc;QADd,UAAK,GAAL,KAAK,CAAY;QACjB,QAAG,GAAH,GAAG,CAAW;IAC9B,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAC,QAAgB,EAAE,SAAiB;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAC9B,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAmB;QAC3B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,SAAiB;QAC9C,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,QAAgB;QACnC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC1D,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IAEO,KAAK,CAAC,IAAI,CAAC,MAAmB;QACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAClF,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;IACxF,CAAC;IAEO,KAAK,CAAC,IAAI,CAAC,MAAmB;QACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;QACrF,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAA0B,EAAE,CAAC;IAC5F,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { AccessTokenResult, SecretBox, TokenHealth, TokenRecord, TokenRefresher, TokenSet, TokenStore, } from './ports.js';
|
|
2
|
+
export { MemoryTokenStore } from './memory.store.js';
|
|
3
|
+
export { AesGcmSecretBox, PlainSecretBox } from './secret-box.js';
|
|
4
|
+
export { EncryptedTokenStore } from './encrypted.store.js';
|
|
5
|
+
export { ReauthRequiredError, TokenVault, type TokenVaultOptions } from './vault.js';
|
|
6
|
+
export { metaTokenRefresher, tiktokTokenRefresher, xTokenRefresher, type FetchLike, type OAuthAppConfig, } from './oauth-refresh.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,WAAW,EACX,cAAc,EACd,QAAQ,EACR,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,KAAK,SAAS,EACd,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { MemoryTokenStore } from './memory.store.js';
|
|
2
|
+
export { AesGcmSecretBox, PlainSecretBox } from './secret-box.js';
|
|
3
|
+
export { EncryptedTokenStore } from './encrypted.store.js';
|
|
4
|
+
export { ReauthRequiredError, TokenVault } from './vault.js';
|
|
5
|
+
export { metaTokenRefresher, tiktokTokenRefresher, xTokenRefresher, } from './oauth-refresh.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAA0B,MAAM,YAAY,CAAC;AACrF,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,GAGhB,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TokenRecord, TokenStore } from './ports.js';
|
|
2
|
+
export declare class MemoryTokenStore implements TokenStore {
|
|
3
|
+
private readonly records;
|
|
4
|
+
get(provider: string, subjectId: string): Promise<TokenRecord | undefined>;
|
|
5
|
+
put(record: TokenRecord): Promise<void>;
|
|
6
|
+
delete(provider: string, subjectId: string): Promise<void>;
|
|
7
|
+
listByProvider(provider: string): Promise<TokenRecord[]>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=memory.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.store.d.ts","sourceRoot":"","sources":["../src/memory.store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM1D,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAEpD,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAK1E,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAK/D"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function key(provider, subjectId) {
|
|
2
|
+
return `${provider}:${subjectId}`;
|
|
3
|
+
}
|
|
4
|
+
export class MemoryTokenStore {
|
|
5
|
+
records = new Map();
|
|
6
|
+
async get(provider, subjectId) {
|
|
7
|
+
const record = this.records.get(key(provider, subjectId));
|
|
8
|
+
return record ? structuredClone(record) : undefined;
|
|
9
|
+
}
|
|
10
|
+
async put(record) {
|
|
11
|
+
this.records.set(key(record.provider, record.subjectId), structuredClone(record));
|
|
12
|
+
}
|
|
13
|
+
async delete(provider, subjectId) {
|
|
14
|
+
this.records.delete(key(provider, subjectId));
|
|
15
|
+
}
|
|
16
|
+
async listByProvider(provider) {
|
|
17
|
+
return [...this.records.values()]
|
|
18
|
+
.filter((record) => record.provider === provider)
|
|
19
|
+
.map((record) => structuredClone(record));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=memory.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.store.js","sourceRoot":"","sources":["../src/memory.store.ts"],"names":[],"mappings":"AAEA,SAAS,GAAG,CAAC,QAAgB,EAAE,SAAiB;IAC9C,OAAO,GAAG,QAAQ,IAAI,SAAS,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,OAAO,gBAAgB;IACV,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE1D,KAAK,CAAC,GAAG,CAAC,QAAgB,EAAE,SAAiB;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;QAC1D,OAAO,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAmB;QAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,SAAiB;QAC9C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,QAAgB;QACnC,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;aAC9B,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC;aAChD,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;CACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TokenRefresher } from './ports.js';
|
|
2
|
+
export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
export interface OAuthAppConfig {
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function metaTokenRefresher(config: OAuthAppConfig, options?: {
|
|
8
|
+
fetch?: FetchLike;
|
|
9
|
+
graphBaseUrl?: string;
|
|
10
|
+
}): TokenRefresher;
|
|
11
|
+
export declare function tiktokTokenRefresher(config: OAuthAppConfig, options?: {
|
|
12
|
+
fetch?: FetchLike;
|
|
13
|
+
tokenUrl?: string;
|
|
14
|
+
}): TokenRefresher;
|
|
15
|
+
export declare function xTokenRefresher(config: OAuthAppConfig, options?: {
|
|
16
|
+
fetch?: FetchLike;
|
|
17
|
+
tokenUrl?: string;
|
|
18
|
+
}): TokenRefresher;
|
|
19
|
+
//# sourceMappingURL=oauth-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-refresh.d.ts","sourceRoot":"","sources":["../src/oauth-refresh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAY,MAAM,YAAY,CAAC;AAE3D,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE/E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAmBD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACzD,cAAc,CAmBhB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,cAAc,CA0BhB;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,cAAc,CA2BhB"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
async function readJson(response) {
|
|
2
|
+
const text = await response.text();
|
|
3
|
+
if (!response.ok)
|
|
4
|
+
throw new Error(`oauth ${response.status}: ${text}`);
|
|
5
|
+
return JSON.parse(text);
|
|
6
|
+
}
|
|
7
|
+
function requireRefreshToken(tokens) {
|
|
8
|
+
if (!tokens.refreshToken)
|
|
9
|
+
throw new Error('refresh token missing');
|
|
10
|
+
return tokens.refreshToken;
|
|
11
|
+
}
|
|
12
|
+
function expiryFromExpiresIn(expiresIn) {
|
|
13
|
+
const seconds = Number(expiresIn);
|
|
14
|
+
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
15
|
+
return undefined;
|
|
16
|
+
return new Date(Date.now() + seconds * 1000).toISOString();
|
|
17
|
+
}
|
|
18
|
+
export function metaTokenRefresher(config, options = {}) {
|
|
19
|
+
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
20
|
+
const base = options.graphBaseUrl ?? 'https://graph.facebook.com/v22.0';
|
|
21
|
+
return {
|
|
22
|
+
async refresh(tokens) {
|
|
23
|
+
const params = new URLSearchParams({
|
|
24
|
+
grant_type: 'fb_exchange_token',
|
|
25
|
+
client_id: config.clientId,
|
|
26
|
+
client_secret: config.clientSecret,
|
|
27
|
+
fb_exchange_token: tokens.accessToken,
|
|
28
|
+
});
|
|
29
|
+
const body = await readJson(await fetchFn(`${base}/oauth/access_token?${params}`));
|
|
30
|
+
return {
|
|
31
|
+
accessToken: String(body.access_token ?? ''),
|
|
32
|
+
refreshToken: tokens.refreshToken,
|
|
33
|
+
expiresAt: expiryFromExpiresIn(body.expires_in),
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function tiktokTokenRefresher(config, options = {}) {
|
|
39
|
+
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
40
|
+
const tokenUrl = options.tokenUrl ?? 'https://open.tiktokapis.com/v2/oauth/token/';
|
|
41
|
+
return {
|
|
42
|
+
async refresh(tokens) {
|
|
43
|
+
const refreshToken = requireRefreshToken(tokens);
|
|
44
|
+
const body = await readJson(await fetchFn(tokenUrl, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
47
|
+
body: new URLSearchParams({
|
|
48
|
+
client_key: config.clientId,
|
|
49
|
+
client_secret: config.clientSecret,
|
|
50
|
+
grant_type: 'refresh_token',
|
|
51
|
+
refresh_token: refreshToken,
|
|
52
|
+
}),
|
|
53
|
+
}));
|
|
54
|
+
const data = body.data ?? body;
|
|
55
|
+
return {
|
|
56
|
+
accessToken: String(data.access_token ?? ''),
|
|
57
|
+
refreshToken: String(data.refresh_token ?? refreshToken),
|
|
58
|
+
expiresAt: expiryFromExpiresIn(data.expires_in),
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function xTokenRefresher(config, options = {}) {
|
|
64
|
+
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
65
|
+
const tokenUrl = options.tokenUrl ?? 'https://api.x.com/2/oauth2/token';
|
|
66
|
+
const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64');
|
|
67
|
+
return {
|
|
68
|
+
async refresh(tokens) {
|
|
69
|
+
const refreshToken = requireRefreshToken(tokens);
|
|
70
|
+
const body = await readJson(await fetchFn(tokenUrl, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
authorization: `Basic ${basic}`,
|
|
74
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
75
|
+
},
|
|
76
|
+
body: new URLSearchParams({
|
|
77
|
+
grant_type: 'refresh_token',
|
|
78
|
+
refresh_token: refreshToken,
|
|
79
|
+
}),
|
|
80
|
+
}));
|
|
81
|
+
return {
|
|
82
|
+
accessToken: String(body.access_token ?? ''),
|
|
83
|
+
refreshToken: String(body.refresh_token ?? refreshToken),
|
|
84
|
+
expiresAt: expiryFromExpiresIn(body.expires_in),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=oauth-refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-refresh.js","sourceRoot":"","sources":["../src/oauth-refresh.ts"],"names":[],"mappings":"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"}
|
package/dist/ports.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type TokenHealth = 'ok' | 'needs_reauth' | 'degraded';
|
|
2
|
+
export interface TokenSet {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
refreshToken?: string;
|
|
5
|
+
expiresAt?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TokenRecord {
|
|
8
|
+
provider: string;
|
|
9
|
+
subjectId: string;
|
|
10
|
+
displayName?: string;
|
|
11
|
+
tokens: TokenSet;
|
|
12
|
+
health: TokenHealth;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
export interface TokenStore {
|
|
16
|
+
get(provider: string, subjectId: string): Promise<TokenRecord | undefined>;
|
|
17
|
+
put(record: TokenRecord): Promise<void>;
|
|
18
|
+
delete(provider: string, subjectId: string): Promise<void>;
|
|
19
|
+
listByProvider(provider: string): Promise<TokenRecord[]>;
|
|
20
|
+
}
|
|
21
|
+
export interface TokenRefresher {
|
|
22
|
+
refresh(tokens: TokenSet): Promise<TokenSet>;
|
|
23
|
+
}
|
|
24
|
+
export interface SecretBox {
|
|
25
|
+
seal(plaintext: Uint8Array): Promise<Uint8Array>;
|
|
26
|
+
open(ciphertext: Uint8Array): Promise<Uint8Array>;
|
|
27
|
+
}
|
|
28
|
+
export interface AccessTokenResult {
|
|
29
|
+
accessToken: string;
|
|
30
|
+
health: TokenHealth;
|
|
31
|
+
record: TokenRecord;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../src/ports.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,cAAc,GAAG,UAAU,CAAC;AAE7D,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC3E,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;CACrB"}
|
package/dist/ports.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.js","sourceRoot":"","sources":["../src/ports.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SecretBox } from './ports.js';
|
|
2
|
+
/**
|
|
3
|
+
* AES-256-GCM. Ciphertext is iv (12) + tag (16) + ciphertext. The consuming
|
|
4
|
+
* app supplies the 32-byte key from SSM — this package does not read env.
|
|
5
|
+
*/
|
|
6
|
+
export declare class AesGcmSecretBox implements SecretBox {
|
|
7
|
+
private readonly key;
|
|
8
|
+
constructor(key: Uint8Array);
|
|
9
|
+
seal(plaintext: Uint8Array): Promise<Uint8Array>;
|
|
10
|
+
open(ciphertext: Uint8Array): Promise<Uint8Array>;
|
|
11
|
+
}
|
|
12
|
+
/** Identity box for tests. Not for production. */
|
|
13
|
+
export declare class PlainSecretBox implements SecretBox {
|
|
14
|
+
seal(plaintext: Uint8Array): Promise<Uint8Array>;
|
|
15
|
+
open(ciphertext: Uint8Array): Promise<Uint8Array>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=secret-box.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-box.d.ts","sourceRoot":"","sources":["../src/secret-box.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,UAAU;IAItC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAQhD,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;CASxD;AAED,kDAAkD;AAClD,qBAAa,cAAe,YAAW,SAAS;IACxC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAIhD,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;CAGxD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* AES-256-GCM. Ciphertext is iv (12) + tag (16) + ciphertext. The consuming
|
|
4
|
+
* app supplies the 32-byte key from SSM — this package does not read env.
|
|
5
|
+
*/
|
|
6
|
+
export class AesGcmSecretBox {
|
|
7
|
+
key;
|
|
8
|
+
constructor(key) {
|
|
9
|
+
this.key = key;
|
|
10
|
+
if (key.byteLength !== 32)
|
|
11
|
+
throw new Error('AES-256-GCM key must be 32 bytes');
|
|
12
|
+
}
|
|
13
|
+
async seal(plaintext) {
|
|
14
|
+
const iv = randomBytes(12);
|
|
15
|
+
const cipher = createCipheriv('aes-256-gcm', this.key, iv);
|
|
16
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
17
|
+
const tag = cipher.getAuthTag();
|
|
18
|
+
return Buffer.concat([iv, tag, encrypted]);
|
|
19
|
+
}
|
|
20
|
+
async open(ciphertext) {
|
|
21
|
+
const buf = Buffer.from(ciphertext);
|
|
22
|
+
const iv = buf.subarray(0, 12);
|
|
23
|
+
const tag = buf.subarray(12, 28);
|
|
24
|
+
const encrypted = buf.subarray(28);
|
|
25
|
+
const decipher = createDecipheriv('aes-256-gcm', this.key, iv);
|
|
26
|
+
decipher.setAuthTag(tag);
|
|
27
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Identity box for tests. Not for production. */
|
|
31
|
+
export class PlainSecretBox {
|
|
32
|
+
async seal(plaintext) {
|
|
33
|
+
return plaintext;
|
|
34
|
+
}
|
|
35
|
+
async open(ciphertext) {
|
|
36
|
+
return ciphertext;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=secret-box.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-box.js","sourceRoot":"","sources":["../src/secret-box.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG5E;;;GAGG;AACH,MAAM,OAAO,eAAe;IACG;IAA7B,YAA6B,GAAe;QAAf,QAAG,GAAH,GAAG,CAAY;QAC1C,IAAI,GAAG,CAAC,UAAU,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAqB;QAC9B,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAsB;QAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/D,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;CACF;AAED,kDAAkD;AAClD,MAAM,OAAO,cAAc;IACzB,KAAK,CAAC,IAAI,CAAC,SAAqB;QAC9B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAsB;QAC/B,OAAO,UAAU,CAAC;IACpB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.spec.d.ts","sourceRoot":"","sources":["../src/tokens.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { EncryptedTokenStore } from './encrypted.store.js';
|
|
3
|
+
import { MemoryTokenStore } from './memory.store.js';
|
|
4
|
+
import { tiktokTokenRefresher } from './oauth-refresh.js';
|
|
5
|
+
import { AesGcmSecretBox, PlainSecretBox } from './secret-box.js';
|
|
6
|
+
import { ReauthRequiredError, TokenVault } from './vault.js';
|
|
7
|
+
describe('TokenVault', () => {
|
|
8
|
+
it('refreshes before expiry and stores the new access token', async () => {
|
|
9
|
+
const store = new MemoryTokenStore();
|
|
10
|
+
const refresher = { refresh: vi.fn(async () => ({ accessToken: 'new', refreshToken: 'r2', expiresAt: '2026-09-02T00:00:00.000Z' })) };
|
|
11
|
+
const vault = new TokenVault({
|
|
12
|
+
store,
|
|
13
|
+
refreshers: { tiktok: refresher },
|
|
14
|
+
clock: () => new Date('2026-09-01T00:00:00.000Z'),
|
|
15
|
+
refreshSkewMs: 60_000,
|
|
16
|
+
});
|
|
17
|
+
await vault.put({
|
|
18
|
+
provider: 'tiktok',
|
|
19
|
+
subjectId: 'acct_1',
|
|
20
|
+
tokens: {
|
|
21
|
+
accessToken: 'old',
|
|
22
|
+
refreshToken: 'r1',
|
|
23
|
+
expiresAt: '2026-09-01T00:00:30.000Z',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const result = await vault.accessToken('tiktok', 'acct_1');
|
|
27
|
+
expect(result.accessToken).toBe('new');
|
|
28
|
+
expect(refresher.refresh).toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
it('marks needs_reauth when refresh fails', async () => {
|
|
31
|
+
const store = new MemoryTokenStore();
|
|
32
|
+
const vault = new TokenVault({
|
|
33
|
+
store,
|
|
34
|
+
refreshers: {
|
|
35
|
+
instagram: { refresh: async () => { throw new Error('invalid_grant'); } },
|
|
36
|
+
},
|
|
37
|
+
clock: () => new Date('2026-09-01T00:00:00.000Z'),
|
|
38
|
+
});
|
|
39
|
+
await vault.put({
|
|
40
|
+
provider: 'instagram',
|
|
41
|
+
subjectId: 'ig_1',
|
|
42
|
+
tokens: { accessToken: 'old', refreshToken: 'r', expiresAt: '2026-09-01T00:00:00.000Z' },
|
|
43
|
+
});
|
|
44
|
+
await expect(vault.accessToken('instagram', 'ig_1')).rejects.toBeInstanceOf(ReauthRequiredError);
|
|
45
|
+
expect((await vault.get('instagram', 'ig_1'))?.health).toBe('needs_reauth');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('EncryptedTokenStore', () => {
|
|
49
|
+
it('round-trips through AES-GCM without leaving the raw access token in the inner store', async () => {
|
|
50
|
+
const inner = new MemoryTokenStore();
|
|
51
|
+
const key = new Uint8Array(32).fill(7);
|
|
52
|
+
const store = new EncryptedTokenStore(inner, new AesGcmSecretBox(key));
|
|
53
|
+
await store.put({
|
|
54
|
+
provider: 'x',
|
|
55
|
+
subjectId: 'acct_x',
|
|
56
|
+
tokens: { accessToken: 'secret-token', refreshToken: 'rt' },
|
|
57
|
+
health: 'ok',
|
|
58
|
+
updatedAt: '2026-09-01T00:00:00.000Z',
|
|
59
|
+
});
|
|
60
|
+
const raw = await inner.get('x', 'acct_x');
|
|
61
|
+
expect(raw?.tokens.accessToken).not.toBe('secret-token');
|
|
62
|
+
expect((await store.get('x', 'acct_x'))?.tokens.accessToken).toBe('secret-token');
|
|
63
|
+
});
|
|
64
|
+
it('plain box is identity (tests only)', async () => {
|
|
65
|
+
const store = new EncryptedTokenStore(new MemoryTokenStore(), new PlainSecretBox());
|
|
66
|
+
await store.put({
|
|
67
|
+
provider: 'facebook',
|
|
68
|
+
subjectId: 'p1',
|
|
69
|
+
tokens: { accessToken: 'a' },
|
|
70
|
+
health: 'ok',
|
|
71
|
+
updatedAt: '2026-09-01T00:00:00.000Z',
|
|
72
|
+
});
|
|
73
|
+
expect((await store.get('facebook', 'p1'))?.tokens.accessToken).toBe('a');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('tiktokTokenRefresher', () => {
|
|
77
|
+
it('posts refresh_token and maps expires_in', async () => {
|
|
78
|
+
const fetchFn = vi.fn(async () => new Response(JSON.stringify({ access_token: 'n', refresh_token: 'nr', expires_in: 3600 }), { status: 200 }));
|
|
79
|
+
const refresher = tiktokTokenRefresher({ clientId: 'id', clientSecret: 'sec' }, { fetch: fetchFn });
|
|
80
|
+
const next = await refresher.refresh({ accessToken: 'old', refreshToken: 'r' });
|
|
81
|
+
expect(next.accessToken).toBe('n');
|
|
82
|
+
expect(next.refreshToken).toBe('nr');
|
|
83
|
+
expect(fetchFn).toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=tokens.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.spec.js","sourceRoot":"","sources":["../src/tokens.spec.ts"],"names":[],"mappings":"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"}
|
package/dist/vault.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AccessTokenResult, TokenRecord, TokenRefresher, TokenSet, TokenStore } from './ports.js';
|
|
2
|
+
export interface TokenVaultOptions {
|
|
3
|
+
store: TokenStore;
|
|
4
|
+
refreshers?: Record<string, TokenRefresher>;
|
|
5
|
+
clock?: () => Date;
|
|
6
|
+
/** Refresh this many ms before `expiresAt`. Default 60s. */
|
|
7
|
+
refreshSkewMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Stores OAuth tokens, refreshes them before expiry, and marks
|
|
11
|
+
* `needs_reauth` when refresh fails — the Zoho/Buffer silent-drop counter.
|
|
12
|
+
*/
|
|
13
|
+
export declare class TokenVault {
|
|
14
|
+
private readonly options;
|
|
15
|
+
constructor(options: TokenVaultOptions);
|
|
16
|
+
put(input: {
|
|
17
|
+
provider: string;
|
|
18
|
+
subjectId: string;
|
|
19
|
+
tokens: TokenSet;
|
|
20
|
+
displayName?: string;
|
|
21
|
+
}): Promise<TokenRecord>;
|
|
22
|
+
get(provider: string, subjectId: string): Promise<TokenRecord | undefined>;
|
|
23
|
+
delete(provider: string, subjectId: string): Promise<void>;
|
|
24
|
+
list(provider: string): Promise<TokenRecord[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a usable access token, refreshing when close to expiry. On
|
|
27
|
+
* refresh failure the record is `needs_reauth` and this throws.
|
|
28
|
+
*/
|
|
29
|
+
accessToken(provider: string, subjectId: string): Promise<AccessTokenResult>;
|
|
30
|
+
private needsRefresh;
|
|
31
|
+
private mark;
|
|
32
|
+
private now;
|
|
33
|
+
}
|
|
34
|
+
export declare class ReauthRequiredError extends Error {
|
|
35
|
+
readonly provider: string;
|
|
36
|
+
readonly subjectId: string;
|
|
37
|
+
readonly code = "needs_reauth";
|
|
38
|
+
constructor(provider: string, subjectId: string);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=vault.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvG,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAEjD,GAAG,CAAC,KAAK,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,QAAQ,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,WAAW,CAAC;IAalB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAI1E,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAIpD;;;OAGG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAgClF,OAAO,CAAC,YAAY;YAMN,IAAI;IAIlB,OAAO,CAAC,GAAG;CAGZ;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAI1C,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM;IAJ5B,QAAQ,CAAC,IAAI,kBAAkB;gBAGpB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;CAK7B"}
|
package/dist/vault.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stores OAuth tokens, refreshes them before expiry, and marks
|
|
3
|
+
* `needs_reauth` when refresh fails — the Zoho/Buffer silent-drop counter.
|
|
4
|
+
*/
|
|
5
|
+
export class TokenVault {
|
|
6
|
+
options;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
async put(input) {
|
|
11
|
+
const record = {
|
|
12
|
+
provider: input.provider,
|
|
13
|
+
subjectId: input.subjectId,
|
|
14
|
+
displayName: input.displayName,
|
|
15
|
+
tokens: input.tokens,
|
|
16
|
+
health: 'ok',
|
|
17
|
+
updatedAt: this.now().toISOString(),
|
|
18
|
+
};
|
|
19
|
+
await this.options.store.put(record);
|
|
20
|
+
return record;
|
|
21
|
+
}
|
|
22
|
+
async get(provider, subjectId) {
|
|
23
|
+
return this.options.store.get(provider, subjectId);
|
|
24
|
+
}
|
|
25
|
+
async delete(provider, subjectId) {
|
|
26
|
+
await this.options.store.delete(provider, subjectId);
|
|
27
|
+
}
|
|
28
|
+
async list(provider) {
|
|
29
|
+
return this.options.store.listByProvider(provider);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns a usable access token, refreshing when close to expiry. On
|
|
33
|
+
* refresh failure the record is `needs_reauth` and this throws.
|
|
34
|
+
*/
|
|
35
|
+
async accessToken(provider, subjectId) {
|
|
36
|
+
const record = await this.options.store.get(provider, subjectId);
|
|
37
|
+
if (!record)
|
|
38
|
+
throw new Error(`no tokens for ${provider}:${subjectId}`);
|
|
39
|
+
if (record.health === 'needs_reauth') {
|
|
40
|
+
throw new ReauthRequiredError(provider, subjectId);
|
|
41
|
+
}
|
|
42
|
+
if (!this.needsRefresh(record.tokens)) {
|
|
43
|
+
return { accessToken: record.tokens.accessToken, health: record.health, record };
|
|
44
|
+
}
|
|
45
|
+
const refresher = this.options.refreshers?.[provider];
|
|
46
|
+
if (!refresher || !record.tokens.refreshToken) {
|
|
47
|
+
await this.mark(record, 'needs_reauth');
|
|
48
|
+
throw new ReauthRequiredError(provider, subjectId);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const nextTokens = await refresher.refresh(record.tokens);
|
|
52
|
+
const next = {
|
|
53
|
+
...record,
|
|
54
|
+
tokens: nextTokens,
|
|
55
|
+
health: 'ok',
|
|
56
|
+
updatedAt: this.now().toISOString(),
|
|
57
|
+
};
|
|
58
|
+
await this.options.store.put(next);
|
|
59
|
+
return { accessToken: nextTokens.accessToken, health: 'ok', record: next };
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
await this.mark(record, 'needs_reauth');
|
|
63
|
+
throw new ReauthRequiredError(provider, subjectId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
needsRefresh(tokens) {
|
|
67
|
+
if (!tokens.expiresAt)
|
|
68
|
+
return false;
|
|
69
|
+
const skew = this.options.refreshSkewMs ?? 60_000;
|
|
70
|
+
return new Date(tokens.expiresAt).getTime() - this.now().getTime() <= skew;
|
|
71
|
+
}
|
|
72
|
+
async mark(record, health) {
|
|
73
|
+
await this.options.store.put({ ...record, health, updatedAt: this.now().toISOString() });
|
|
74
|
+
}
|
|
75
|
+
now() {
|
|
76
|
+
return this.options.clock?.() ?? new Date();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export class ReauthRequiredError extends Error {
|
|
80
|
+
provider;
|
|
81
|
+
subjectId;
|
|
82
|
+
code = 'needs_reauth';
|
|
83
|
+
constructor(provider, subjectId) {
|
|
84
|
+
super(`${provider}:${subjectId} needs reauth`);
|
|
85
|
+
this.provider = provider;
|
|
86
|
+
this.subjectId = subjectId;
|
|
87
|
+
this.name = 'ReauthRequiredError';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=vault.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.js","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bubltec/mycota-tokens",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -b",
|
|
21
|
+
"typecheck": "tsc -b --noEmit",
|
|
22
|
+
"lint": "oxlint -c ../../.oxlintrc.json src",
|
|
23
|
+
"test": "vitest run"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.10.0",
|
|
27
|
+
"typescript": "^5.8.0",
|
|
28
|
+
"vitest": "^2.1.0"
|
|
29
|
+
}
|
|
30
|
+
}
|