@jezweb/oauth-token-manager 0.1.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.
Files changed (51) hide show
  1. package/README.md +184 -0
  2. package/SECURITY.md +162 -0
  3. package/dist/crypto.d.ts +43 -0
  4. package/dist/crypto.d.ts.map +1 -0
  5. package/dist/crypto.js +107 -0
  6. package/dist/crypto.js.map +1 -0
  7. package/dist/errors.d.ts +75 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +117 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +54 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +58 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/providers/github.d.ts +45 -0
  16. package/dist/providers/github.d.ts.map +1 -0
  17. package/dist/providers/github.js +70 -0
  18. package/dist/providers/github.js.map +1 -0
  19. package/dist/providers/google.d.ts +24 -0
  20. package/dist/providers/google.d.ts.map +1 -0
  21. package/dist/providers/google.js +63 -0
  22. package/dist/providers/google.js.map +1 -0
  23. package/dist/providers/microsoft.d.ts +29 -0
  24. package/dist/providers/microsoft.d.ts.map +1 -0
  25. package/dist/providers/microsoft.js +72 -0
  26. package/dist/providers/microsoft.js.map +1 -0
  27. package/dist/providers/types.d.ts +7 -0
  28. package/dist/providers/types.d.ts.map +1 -0
  29. package/dist/providers/types.js +7 -0
  30. package/dist/providers/types.js.map +1 -0
  31. package/dist/storage/d1.d.ts +22 -0
  32. package/dist/storage/d1.d.ts.map +1 -0
  33. package/dist/storage/d1.js +31 -0
  34. package/dist/storage/d1.js.map +1 -0
  35. package/dist/storage/kv.d.ts +38 -0
  36. package/dist/storage/kv.d.ts.map +1 -0
  37. package/dist/storage/kv.js +143 -0
  38. package/dist/storage/kv.js.map +1 -0
  39. package/dist/storage/types.d.ts +7 -0
  40. package/dist/storage/types.d.ts.map +1 -0
  41. package/dist/storage/types.js +7 -0
  42. package/dist/storage/types.js.map +1 -0
  43. package/dist/token-manager.d.ts +88 -0
  44. package/dist/token-manager.d.ts.map +1 -0
  45. package/dist/token-manager.js +199 -0
  46. package/dist/token-manager.js.map +1 -0
  47. package/dist/types.d.ts +158 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +5 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +88 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Cloudflare D1 Storage Adapter (Placeholder)
3
+ *
4
+ * D1 provides stronger consistency guarantees than KV and allows
5
+ * for more complex queries (e.g., find all tokens expiring soon).
6
+ *
7
+ * TODO: Implement full D1 adapter with migrations
8
+ */
9
+ /**
10
+ * D1 Storage adapter for Cloudflare D1
11
+ *
12
+ * NOT YET IMPLEMENTED - Use KVStorage for now
13
+ */
14
+ export class D1Storage {
15
+ constructor(_db, _encryptionKey) {
16
+ throw new Error('D1Storage is not yet implemented. Use KVStorage instead, or contribute an implementation!');
17
+ }
18
+ get(_userId, _provider) {
19
+ throw new Error('Not implemented');
20
+ }
21
+ set(_token) {
22
+ throw new Error('Not implemented');
23
+ }
24
+ delete(_userId, _provider) {
25
+ throw new Error('Not implemented');
26
+ }
27
+ list(_userId) {
28
+ throw new Error('Not implemented');
29
+ }
30
+ }
31
+ //# sourceMappingURL=d1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.js","sourceRoot":"","sources":["../../src/storage/d1.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;GAIG;AACH,MAAM,OAAO,SAAS;IACpB,YAAY,GAAe,EAAE,cAAsB;QACjD,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;IACJ,CAAC;IAED,GAAG,CAAC,OAAe,EAAE,SAAiB;QACpC,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;IAED,GAAG,CAAC,MAAmB;QACrB,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,CAAC,OAAe,EAAE,SAAiB;QACvC,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Cloudflare KV Storage Adapter
3
+ *
4
+ * Key structure:
5
+ * - tokens:{userId}:{provider} → encrypted token data
6
+ * - token-index:{userId} → JSON array of provider IDs (for listing)
7
+ *
8
+ * Note: KV is eventually consistent. For strict consistency needs, use D1 adapter.
9
+ */
10
+ import type { TokenStorage, StoredToken, ConnectedProvider } from '../types';
11
+ export interface KVStorageOptions {
12
+ /** Cloudflare KV Namespace binding */
13
+ namespace: KVNamespace;
14
+ /** Encryption key for token data */
15
+ encryptionKey: string;
16
+ /** Optional key prefix (default: 'tokens') */
17
+ keyPrefix?: string;
18
+ }
19
+ /**
20
+ * KV Storage adapter for Cloudflare Workers KV
21
+ */
22
+ export declare class KVStorage implements TokenStorage {
23
+ private readonly kv;
24
+ private readonly encryptionKey;
25
+ private readonly keyPrefix;
26
+ constructor(options: KVStorageOptions);
27
+ private tokenKey;
28
+ private indexKey;
29
+ get(userId: string, provider: string): Promise<StoredToken | null>;
30
+ set(token: StoredToken): Promise<void>;
31
+ delete(userId: string, provider: string): Promise<void>;
32
+ list(userId: string): Promise<ConnectedProvider[]>;
33
+ /**
34
+ * Update the provider index for a user
35
+ */
36
+ private updateIndex;
37
+ }
38
+ //# sourceMappingURL=kv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.d.ts","sourceRoot":"","sources":["../../src/storage/kv.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAyBlB,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,SAAS,EAAE,WAAW,CAAC;IACvB,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,SAAU,YAAW,YAAY;IAC5C,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,EAAE,gBAAgB;IAMrC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,QAAQ;IAIV,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAyBlE,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IA8BxD;;OAEG;YACW,WAAW;CAyB1B"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Cloudflare KV Storage Adapter
3
+ *
4
+ * Key structure:
5
+ * - tokens:{userId}:{provider} → encrypted token data
6
+ * - token-index:{userId} → JSON array of provider IDs (for listing)
7
+ *
8
+ * Note: KV is eventually consistent. For strict consistency needs, use D1 adapter.
9
+ */
10
+ import { StorageError } from '../errors';
11
+ import { encrypt, decrypt } from '../crypto';
12
+ const KEY_PREFIX = 'tokens';
13
+ const INDEX_PREFIX = 'token-index';
14
+ /**
15
+ * KV Storage adapter for Cloudflare Workers KV
16
+ */
17
+ export class KVStorage {
18
+ kv;
19
+ encryptionKey;
20
+ keyPrefix;
21
+ constructor(options) {
22
+ this.kv = options.namespace;
23
+ this.encryptionKey = options.encryptionKey;
24
+ this.keyPrefix = options.keyPrefix ?? KEY_PREFIX;
25
+ }
26
+ tokenKey(userId, provider) {
27
+ return `${this.keyPrefix}:${userId}:${provider}`;
28
+ }
29
+ indexKey(userId) {
30
+ return `${INDEX_PREFIX}:${userId}`;
31
+ }
32
+ async get(userId, provider) {
33
+ try {
34
+ const key = this.tokenKey(userId, provider);
35
+ const data = await this.kv.get(key, 'json');
36
+ if (!data) {
37
+ return null;
38
+ }
39
+ // Decrypt sensitive fields
40
+ const accessToken = await decrypt(data.accessToken, this.encryptionKey);
41
+ const refreshToken = data.refreshToken
42
+ ? await decrypt(data.refreshToken, this.encryptionKey)
43
+ : undefined;
44
+ return {
45
+ ...data,
46
+ accessToken,
47
+ refreshToken,
48
+ };
49
+ }
50
+ catch (error) {
51
+ throw new StorageError('get', error instanceof Error ? error : undefined);
52
+ }
53
+ }
54
+ async set(token) {
55
+ try {
56
+ const key = this.tokenKey(token.userId, token.provider);
57
+ // Encrypt sensitive fields
58
+ const encryptedAccessToken = await encrypt(token.accessToken, this.encryptionKey);
59
+ const encryptedRefreshToken = token.refreshToken
60
+ ? await encrypt(token.refreshToken, this.encryptionKey)
61
+ : undefined;
62
+ const data = {
63
+ userId: token.userId,
64
+ provider: token.provider,
65
+ accessToken: encryptedAccessToken,
66
+ refreshToken: encryptedRefreshToken,
67
+ expiresAt: token.expiresAt,
68
+ scopes: token.scopes,
69
+ createdAt: token.createdAt,
70
+ updatedAt: token.updatedAt,
71
+ };
72
+ // Store the token
73
+ await this.kv.put(key, JSON.stringify(data));
74
+ // Update the index
75
+ await this.updateIndex(token.userId, token.provider, 'add');
76
+ }
77
+ catch (error) {
78
+ throw new StorageError('set', error instanceof Error ? error : undefined);
79
+ }
80
+ }
81
+ async delete(userId, provider) {
82
+ try {
83
+ const key = this.tokenKey(userId, provider);
84
+ await this.kv.delete(key);
85
+ await this.updateIndex(userId, provider, 'remove');
86
+ }
87
+ catch (error) {
88
+ throw new StorageError('delete', error instanceof Error ? error : undefined);
89
+ }
90
+ }
91
+ async list(userId) {
92
+ try {
93
+ const indexKey = this.indexKey(userId);
94
+ const providers = await this.kv.get(indexKey, 'json');
95
+ if (!providers || providers.length === 0) {
96
+ return [];
97
+ }
98
+ // Fetch each provider's token data
99
+ const results = [];
100
+ for (const provider of providers) {
101
+ const token = await this.get(userId, provider);
102
+ if (token) {
103
+ results.push({
104
+ provider: token.provider,
105
+ scopes: token.scopes,
106
+ connectedAt: token.createdAt,
107
+ expiresAt: token.expiresAt,
108
+ });
109
+ }
110
+ }
111
+ return results;
112
+ }
113
+ catch (error) {
114
+ throw new StorageError('list', error instanceof Error ? error : undefined);
115
+ }
116
+ }
117
+ /**
118
+ * Update the provider index for a user
119
+ */
120
+ async updateIndex(userId, provider, action) {
121
+ const indexKey = this.indexKey(userId);
122
+ const current = (await this.kv.get(indexKey, 'json')) ?? [];
123
+ let updated;
124
+ if (action === 'add') {
125
+ if (!current.includes(provider)) {
126
+ updated = [...current, provider];
127
+ }
128
+ else {
129
+ return; // Already in index
130
+ }
131
+ }
132
+ else {
133
+ updated = current.filter((p) => p !== provider);
134
+ }
135
+ if (updated.length === 0) {
136
+ await this.kv.delete(indexKey);
137
+ }
138
+ else {
139
+ await this.kv.put(indexKey, JSON.stringify(updated));
140
+ }
141
+ }
142
+ }
143
+ //# sourceMappingURL=kv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.js","sourceRoot":"","sources":["../../src/storage/kv.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Storage adapter types
3
+ *
4
+ * Re-export from main types for convenience
5
+ */
6
+ export type { TokenStorage, StoredToken, ConnectedProvider } from '../types';
7
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Storage adapter types
3
+ *
4
+ * Re-export from main types for convenience
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * OAuth Token Manager
3
+ *
4
+ * Main entry point for storing, retrieving, and refreshing OAuth tokens
5
+ * for downstream API access in Cloudflare Workers.
6
+ */
7
+ import type { TokenManagerConfig, TokenProvider, TokenData, StoreTokenOptions, GetTokenOptions, ListTokensOptions, ConnectedProvider, RevokeTokenOptions } from './types';
8
+ /**
9
+ * OAuth Token Manager
10
+ *
11
+ * Manages OAuth tokens for downstream API access:
12
+ * - Stores tokens encrypted at rest
13
+ * - Automatically refreshes expired tokens
14
+ * - Validates required scopes
15
+ * - Supports Google, Microsoft, and GitHub out of the box
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const tokens = new TokenManager({
20
+ * storage: new KVStorage({ namespace: env.TOKEN_KV, encryptionKey: env.TOKEN_KEY }),
21
+ * encryptionKey: env.TOKEN_KEY,
22
+ * providers: {
23
+ * google: {
24
+ * clientId: env.GOOGLE_CLIENT_ID,
25
+ * clientSecret: env.GOOGLE_CLIENT_SECRET,
26
+ * },
27
+ * },
28
+ * });
29
+ *
30
+ * // Store token after OAuth callback
31
+ * await tokens.store({ userId, provider: 'google', accessToken, refreshToken, expiresAt, scopes });
32
+ *
33
+ * // Get valid token (auto-refreshes if needed)
34
+ * const { accessToken } = await tokens.get({ userId, provider: 'google' });
35
+ * ```
36
+ */
37
+ export declare class TokenManager {
38
+ private readonly storage;
39
+ private readonly providers;
40
+ private readonly defaultRefreshBuffer;
41
+ constructor(config: TokenManagerConfig);
42
+ /**
43
+ * Store a new token or update an existing one
44
+ *
45
+ * Call this after a successful OAuth callback to store the user's tokens.
46
+ */
47
+ store(options: StoreTokenOptions): Promise<void>;
48
+ /**
49
+ * Get a valid access token for API calls
50
+ *
51
+ * - Returns the current token if still valid
52
+ * - Automatically refreshes if expired or expiring soon
53
+ * - Validates required scopes if specified
54
+ *
55
+ * @throws TokenNotFoundError - User hasn't connected this provider
56
+ * @throws TokenExpiredError - Token expired and refresh failed
57
+ * @throws InsufficientScopesError - Token missing required scopes
58
+ * @throws ProviderNotConfiguredError - Provider not in config (for refresh)
59
+ */
60
+ get(options: GetTokenOptions): Promise<TokenData>;
61
+ /**
62
+ * List all connected providers for a user
63
+ */
64
+ list(options: ListTokensOptions): Promise<ConnectedProvider[]>;
65
+ /**
66
+ * Revoke/delete a token
67
+ *
68
+ * Note: This only removes the token from storage. For providers that
69
+ * support token revocation (e.g., GitHub), you may want to also call
70
+ * the provider's revocation endpoint.
71
+ */
72
+ revoke(options: RevokeTokenOptions): Promise<void>;
73
+ /**
74
+ * Check if a user has a token for a provider (without retrieving it)
75
+ */
76
+ has(userId: string, provider: string): Promise<boolean>;
77
+ /**
78
+ * Refresh an expired token
79
+ */
80
+ private refreshToken;
81
+ /**
82
+ * Register a custom provider implementation
83
+ *
84
+ * Use this to add support for providers beyond Google/Microsoft/GitHub
85
+ */
86
+ static registerProvider(provider: TokenProvider): void;
87
+ }
88
+ //# sourceMappingURL=token-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../src/token-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAElB,aAAa,EAGb,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAyBjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;gBAElC,MAAM,EAAE,kBAAkB;IAMtC;;;;OAIG;IACG,KAAK,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBtD;;;;;;;;;;;OAWG;IACG,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC;IA0CvD;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAIpE;;;;;;OAMG;IACG,MAAM,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD;;OAEG;IACG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7D;;OAEG;YACW,YAAY;IAuD1B;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;CAGvD"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * OAuth Token Manager
3
+ *
4
+ * Main entry point for storing, retrieving, and refreshing OAuth tokens
5
+ * for downstream API access in Cloudflare Workers.
6
+ */
7
+ import { TokenNotFoundError, TokenExpiredError, InsufficientScopesError, ProviderNotConfiguredError, } from './errors';
8
+ import { GoogleProvider } from './providers/google';
9
+ import { MicrosoftProvider } from './providers/microsoft';
10
+ import { GitHubProvider } from './providers/github';
11
+ // Default refresh buffer: 5 minutes before expiry
12
+ const DEFAULT_REFRESH_BUFFER_MS = 5 * 60 * 1000;
13
+ /**
14
+ * Built-in provider instances
15
+ */
16
+ const builtInProviders = {
17
+ google: new GoogleProvider(),
18
+ microsoft: new MicrosoftProvider(),
19
+ github: new GitHubProvider(),
20
+ };
21
+ /**
22
+ * OAuth Token Manager
23
+ *
24
+ * Manages OAuth tokens for downstream API access:
25
+ * - Stores tokens encrypted at rest
26
+ * - Automatically refreshes expired tokens
27
+ * - Validates required scopes
28
+ * - Supports Google, Microsoft, and GitHub out of the box
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const tokens = new TokenManager({
33
+ * storage: new KVStorage({ namespace: env.TOKEN_KV, encryptionKey: env.TOKEN_KEY }),
34
+ * encryptionKey: env.TOKEN_KEY,
35
+ * providers: {
36
+ * google: {
37
+ * clientId: env.GOOGLE_CLIENT_ID,
38
+ * clientSecret: env.GOOGLE_CLIENT_SECRET,
39
+ * },
40
+ * },
41
+ * });
42
+ *
43
+ * // Store token after OAuth callback
44
+ * await tokens.store({ userId, provider: 'google', accessToken, refreshToken, expiresAt, scopes });
45
+ *
46
+ * // Get valid token (auto-refreshes if needed)
47
+ * const { accessToken } = await tokens.get({ userId, provider: 'google' });
48
+ * ```
49
+ */
50
+ export class TokenManager {
51
+ storage;
52
+ providers;
53
+ defaultRefreshBuffer;
54
+ constructor(config) {
55
+ this.storage = config.storage;
56
+ this.providers = new Map(Object.entries(config.providers).filter(([, v]) => v !== undefined));
57
+ this.defaultRefreshBuffer = config.defaultRefreshBuffer ?? DEFAULT_REFRESH_BUFFER_MS;
58
+ }
59
+ /**
60
+ * Store a new token or update an existing one
61
+ *
62
+ * Call this after a successful OAuth callback to store the user's tokens.
63
+ */
64
+ async store(options) {
65
+ const now = Date.now();
66
+ // Check if token already exists (update vs create)
67
+ const existing = await this.storage.get(options.userId, options.provider);
68
+ const token = {
69
+ userId: options.userId,
70
+ provider: options.provider,
71
+ accessToken: options.accessToken,
72
+ refreshToken: options.refreshToken,
73
+ expiresAt: options.expiresAt,
74
+ scopes: options.scopes,
75
+ createdAt: existing?.createdAt ?? now,
76
+ updatedAt: now,
77
+ };
78
+ await this.storage.set(token);
79
+ }
80
+ /**
81
+ * Get a valid access token for API calls
82
+ *
83
+ * - Returns the current token if still valid
84
+ * - Automatically refreshes if expired or expiring soon
85
+ * - Validates required scopes if specified
86
+ *
87
+ * @throws TokenNotFoundError - User hasn't connected this provider
88
+ * @throws TokenExpiredError - Token expired and refresh failed
89
+ * @throws InsufficientScopesError - Token missing required scopes
90
+ * @throws ProviderNotConfiguredError - Provider not in config (for refresh)
91
+ */
92
+ async get(options) {
93
+ const { userId, provider, requiredScopes, refreshBuffer } = options;
94
+ // Fetch stored token
95
+ const stored = await this.storage.get(userId, provider);
96
+ if (!stored) {
97
+ throw new TokenNotFoundError(userId, provider);
98
+ }
99
+ // Check required scopes
100
+ if (requiredScopes && requiredScopes.length > 0) {
101
+ const hasAllScopes = requiredScopes.every((scope) => stored.scopes.includes(scope));
102
+ if (!hasAllScopes) {
103
+ throw new InsufficientScopesError(userId, provider, requiredScopes, stored.scopes);
104
+ }
105
+ }
106
+ // Check if token needs refresh
107
+ const bufferMs = refreshBuffer ?? this.defaultRefreshBuffer;
108
+ const needsRefresh = stored.expiresAt && Date.now() + bufferMs >= stored.expiresAt;
109
+ if (needsRefresh) {
110
+ return await this.refreshToken(stored);
111
+ }
112
+ return {
113
+ accessToken: stored.accessToken,
114
+ refreshToken: stored.refreshToken,
115
+ expiresAt: stored.expiresAt,
116
+ scopes: stored.scopes,
117
+ };
118
+ }
119
+ /**
120
+ * List all connected providers for a user
121
+ */
122
+ async list(options) {
123
+ return this.storage.list(options.userId);
124
+ }
125
+ /**
126
+ * Revoke/delete a token
127
+ *
128
+ * Note: This only removes the token from storage. For providers that
129
+ * support token revocation (e.g., GitHub), you may want to also call
130
+ * the provider's revocation endpoint.
131
+ */
132
+ async revoke(options) {
133
+ await this.storage.delete(options.userId, options.provider);
134
+ }
135
+ /**
136
+ * Check if a user has a token for a provider (without retrieving it)
137
+ */
138
+ async has(userId, provider) {
139
+ const token = await this.storage.get(userId, provider);
140
+ return token !== null;
141
+ }
142
+ /**
143
+ * Refresh an expired token
144
+ */
145
+ async refreshToken(stored) {
146
+ const { userId, provider, refreshToken } = stored;
147
+ // Check for refresh token
148
+ if (!refreshToken) {
149
+ throw new TokenExpiredError(userId, provider, 'no_refresh_token');
150
+ }
151
+ // Get provider config
152
+ const providerConfig = this.providers.get(provider);
153
+ if (!providerConfig) {
154
+ throw new ProviderNotConfiguredError(provider);
155
+ }
156
+ // Get provider implementation
157
+ const providerImpl = builtInProviders[provider];
158
+ if (!providerImpl) {
159
+ // For custom providers without built-in implementation,
160
+ // we can't refresh - user needs to re-authenticate
161
+ throw new TokenExpiredError(userId, provider, 'refresh_failed');
162
+ }
163
+ // Check if provider supports refresh
164
+ if (!providerImpl.supportsRefresh) {
165
+ // Provider tokens don't expire (e.g., GitHub)
166
+ // If we got here, the token must be invalid
167
+ throw new TokenExpiredError(userId, provider, 'refresh_failed');
168
+ }
169
+ // Attempt refresh
170
+ const refreshed = await providerImpl.refresh(refreshToken, providerConfig);
171
+ if (!refreshed) {
172
+ throw new TokenExpiredError(userId, provider, 'refresh_failed');
173
+ }
174
+ // Update stored token with new values
175
+ const updatedToken = {
176
+ ...stored,
177
+ accessToken: refreshed.accessToken,
178
+ refreshToken: refreshed.refreshToken ?? stored.refreshToken,
179
+ expiresAt: refreshed.expiresAt,
180
+ updatedAt: Date.now(),
181
+ };
182
+ await this.storage.set(updatedToken);
183
+ return {
184
+ accessToken: updatedToken.accessToken,
185
+ refreshToken: updatedToken.refreshToken,
186
+ expiresAt: updatedToken.expiresAt,
187
+ scopes: updatedToken.scopes,
188
+ };
189
+ }
190
+ /**
191
+ * Register a custom provider implementation
192
+ *
193
+ * Use this to add support for providers beyond Google/Microsoft/GitHub
194
+ */
195
+ static registerProvider(provider) {
196
+ builtInProviders[provider.id] = provider;
197
+ }
198
+ }
199
+ //# sourceMappingURL=token-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../src/token-manager.ts"],"names":[],"mappings":"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"}