@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
package/dist/errors.js ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Custom error types for OAuth Token Manager
3
+ *
4
+ * These errors provide clear, actionable information about what went wrong
5
+ * and what the application should do to recover.
6
+ */
7
+ /**
8
+ * Base error class for all token manager errors
9
+ */
10
+ export class TokenManagerError extends Error {
11
+ code;
12
+ constructor(message, code) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = 'TokenManagerError';
16
+ }
17
+ }
18
+ /**
19
+ * Token not found for the given user and provider
20
+ *
21
+ * Recovery: Redirect user to OAuth flow to connect this provider
22
+ */
23
+ export class TokenNotFoundError extends TokenManagerError {
24
+ userId;
25
+ provider;
26
+ constructor(userId, provider) {
27
+ super(`No token found for user "${userId}" and provider "${provider}". User needs to connect this provider.`, 'TOKEN_NOT_FOUND');
28
+ this.userId = userId;
29
+ this.provider = provider;
30
+ this.name = 'TokenNotFoundError';
31
+ }
32
+ }
33
+ /**
34
+ * Token has expired and refresh failed or no refresh token available
35
+ *
36
+ * Recovery: Redirect user to OAuth flow to re-authenticate
37
+ */
38
+ export class TokenExpiredError extends TokenManagerError {
39
+ userId;
40
+ provider;
41
+ reason;
42
+ constructor(userId, provider, reason) {
43
+ const reasons = {
44
+ no_refresh_token: 'No refresh token available',
45
+ refresh_failed: 'Token refresh request failed',
46
+ refresh_token_expired: 'Refresh token has expired',
47
+ };
48
+ super(`Token expired for user "${userId}" and provider "${provider}". ${reasons[reason]}. User needs to re-authenticate.`, 'TOKEN_EXPIRED');
49
+ this.userId = userId;
50
+ this.provider = provider;
51
+ this.reason = reason;
52
+ this.name = 'TokenExpiredError';
53
+ }
54
+ }
55
+ /**
56
+ * Token exists but doesn't have the required scopes
57
+ *
58
+ * Recovery: Redirect user to OAuth flow with incremental consent for missing scopes
59
+ */
60
+ export class InsufficientScopesError extends TokenManagerError {
61
+ userId;
62
+ provider;
63
+ requiredScopes;
64
+ grantedScopes;
65
+ constructor(userId, provider, requiredScopes, grantedScopes) {
66
+ const missing = requiredScopes.filter((s) => !grantedScopes.includes(s));
67
+ super(`Token for user "${userId}" and provider "${provider}" is missing required scopes: ${missing.join(', ')}. User needs to grant additional permissions.`, 'INSUFFICIENT_SCOPES');
68
+ this.userId = userId;
69
+ this.provider = provider;
70
+ this.requiredScopes = requiredScopes;
71
+ this.grantedScopes = grantedScopes;
72
+ this.name = 'InsufficientScopesError';
73
+ }
74
+ get missingScopes() {
75
+ return this.requiredScopes.filter((s) => !this.grantedScopes.includes(s));
76
+ }
77
+ }
78
+ /**
79
+ * Provider is not configured in the token manager
80
+ *
81
+ * Recovery: Add provider configuration to TokenManager constructor
82
+ */
83
+ export class ProviderNotConfiguredError extends TokenManagerError {
84
+ provider;
85
+ constructor(provider) {
86
+ super(`Provider "${provider}" is not configured. Add it to the TokenManager providers config.`, 'PROVIDER_NOT_CONFIGURED');
87
+ this.provider = provider;
88
+ this.name = 'ProviderNotConfiguredError';
89
+ }
90
+ }
91
+ /**
92
+ * Encryption/decryption failed
93
+ *
94
+ * Recovery: Check encryption key is correct and hasn't changed
95
+ */
96
+ export class CryptoError extends TokenManagerError {
97
+ cause;
98
+ constructor(operation, cause) {
99
+ super(`Failed to ${operation} token data. This may indicate a corrupted token or incorrect encryption key.`, 'CRYPTO_ERROR');
100
+ this.cause = cause;
101
+ this.name = 'CryptoError';
102
+ }
103
+ }
104
+ /**
105
+ * Storage operation failed
106
+ *
107
+ * Recovery: Check storage backend (KV/D1) is available and configured correctly
108
+ */
109
+ export class StorageError extends TokenManagerError {
110
+ cause;
111
+ constructor(operation, cause) {
112
+ super(`Storage operation "${operation}" failed. Check your storage backend configuration.`, 'STORAGE_ERROR');
113
+ this.cause = cause;
114
+ this.name = 'StorageError';
115
+ }
116
+ }
117
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @jezweb/oauth-token-manager
3
+ *
4
+ * OAuth token management for Cloudflare Workers.
5
+ * Store, refresh, and retrieve tokens for downstream API access.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { TokenManager } from '@jezweb/oauth-token-manager';
10
+ * import { KVStorage } from '@jezweb/oauth-token-manager/storage/kv';
11
+ *
12
+ * const tokens = new TokenManager({
13
+ * storage: new KVStorage({
14
+ * namespace: env.TOKEN_KV,
15
+ * encryptionKey: env.TOKEN_ENCRYPTION_KEY,
16
+ * }),
17
+ * encryptionKey: env.TOKEN_ENCRYPTION_KEY,
18
+ * providers: {
19
+ * google: {
20
+ * clientId: env.GOOGLE_CLIENT_ID,
21
+ * clientSecret: env.GOOGLE_CLIENT_SECRET,
22
+ * },
23
+ * },
24
+ * });
25
+ *
26
+ * // Store token after OAuth callback
27
+ * await tokens.store({
28
+ * userId: 'user-123',
29
+ * provider: 'google',
30
+ * accessToken: '...',
31
+ * refreshToken: '...',
32
+ * expiresAt: Date.now() + 3600000,
33
+ * scopes: ['calendar', 'drive'],
34
+ * });
35
+ *
36
+ * // Get valid token (auto-refreshes if expired)
37
+ * const { accessToken } = await tokens.get({
38
+ * userId: 'user-123',
39
+ * provider: 'google',
40
+ * requiredScopes: ['calendar'],
41
+ * });
42
+ * ```
43
+ *
44
+ * @packageDocumentation
45
+ */
46
+ export { TokenManager } from './token-manager';
47
+ export type { TokenManagerConfig, TokenStorage, TokenProvider, ProviderConfig, StoredToken, TokenData, StoreTokenOptions, GetTokenOptions, ListTokensOptions, ConnectedProvider, RevokeTokenOptions, RefreshResult, } from './types';
48
+ export { TokenManagerError, TokenNotFoundError, TokenExpiredError, InsufficientScopesError, ProviderNotConfiguredError, CryptoError, StorageError, } from './errors';
49
+ export { encrypt, decrypt, encryptObject, decryptObject } from './crypto';
50
+ export { KVStorage, type KVStorageOptions } from './storage/kv';
51
+ export { GoogleProvider, googleProvider } from './providers/google';
52
+ export { MicrosoftProvider, microsoftProvider } from './providers/microsoft';
53
+ export { GitHubProvider, githubProvider, revokeGitHubToken, } from './providers/github';
54
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,YAAY,EACV,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,0BAA0B,EAC1B,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG1E,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EACL,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @jezweb/oauth-token-manager
3
+ *
4
+ * OAuth token management for Cloudflare Workers.
5
+ * Store, refresh, and retrieve tokens for downstream API access.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { TokenManager } from '@jezweb/oauth-token-manager';
10
+ * import { KVStorage } from '@jezweb/oauth-token-manager/storage/kv';
11
+ *
12
+ * const tokens = new TokenManager({
13
+ * storage: new KVStorage({
14
+ * namespace: env.TOKEN_KV,
15
+ * encryptionKey: env.TOKEN_ENCRYPTION_KEY,
16
+ * }),
17
+ * encryptionKey: env.TOKEN_ENCRYPTION_KEY,
18
+ * providers: {
19
+ * google: {
20
+ * clientId: env.GOOGLE_CLIENT_ID,
21
+ * clientSecret: env.GOOGLE_CLIENT_SECRET,
22
+ * },
23
+ * },
24
+ * });
25
+ *
26
+ * // Store token after OAuth callback
27
+ * await tokens.store({
28
+ * userId: 'user-123',
29
+ * provider: 'google',
30
+ * accessToken: '...',
31
+ * refreshToken: '...',
32
+ * expiresAt: Date.now() + 3600000,
33
+ * scopes: ['calendar', 'drive'],
34
+ * });
35
+ *
36
+ * // Get valid token (auto-refreshes if expired)
37
+ * const { accessToken } = await tokens.get({
38
+ * userId: 'user-123',
39
+ * provider: 'google',
40
+ * requiredScopes: ['calendar'],
41
+ * });
42
+ * ```
43
+ *
44
+ * @packageDocumentation
45
+ */
46
+ // Main class
47
+ export { TokenManager } from './token-manager';
48
+ // Errors
49
+ export { TokenManagerError, TokenNotFoundError, TokenExpiredError, InsufficientScopesError, ProviderNotConfiguredError, CryptoError, StorageError, } from './errors';
50
+ // Crypto utilities (for advanced usage)
51
+ export { encrypt, decrypt, encryptObject, decryptObject } from './crypto';
52
+ // Storage adapters (re-exported for convenience)
53
+ export { KVStorage } from './storage/kv';
54
+ // Provider implementations (for extension)
55
+ export { GoogleProvider, googleProvider } from './providers/google';
56
+ export { MicrosoftProvider, microsoftProvider } from './providers/microsoft';
57
+ export { GitHubProvider, githubProvider, revokeGitHubToken, } from './providers/github';
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAkB/C,SAAS;AACT,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,0BAA0B,EAC1B,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,wCAAwC;AACxC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1E,iDAAiD;AACjD,OAAO,EAAE,SAAS,EAAyB,MAAM,cAAc,CAAC;AAEhE,2CAA2C;AAC3C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EACL,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * GitHub OAuth Provider
3
+ *
4
+ * Token characteristics:
5
+ * - Access token lifetime: Does not expire!
6
+ * - Refresh token: Not applicable (tokens don't expire)
7
+ * - Token rotation: Not applicable
8
+ *
9
+ * GitHub tokens are valid until explicitly revoked by the user or
10
+ * the OAuth app is deleted. This makes GitHub simpler to handle
11
+ * but also means stale tokens may accumulate if users disconnect
12
+ * without revoking access.
13
+ *
14
+ * To revoke a token programmatically, use the GitHub API:
15
+ * DELETE /applications/{client_id}/token
16
+ */
17
+ import type { TokenProvider, ProviderConfig, RefreshResult } from '../types';
18
+ /**
19
+ * GitHub OAuth token provider
20
+ *
21
+ * Note: GitHub tokens don't expire, so refresh() is a no-op that
22
+ * always returns null (indicating no refresh was needed/possible).
23
+ */
24
+ export declare class GitHubProvider implements TokenProvider {
25
+ readonly id = "github";
26
+ readonly supportsRefresh = false;
27
+ refresh(_refreshToken: string, _config: ProviderConfig): Promise<RefreshResult | null>;
28
+ }
29
+ /**
30
+ * Default GitHub provider instance
31
+ */
32
+ export declare const githubProvider: GitHubProvider;
33
+ /**
34
+ * Revoke a GitHub OAuth token
35
+ *
36
+ * Call this when a user disconnects their GitHub account to properly
37
+ * clean up the token on GitHub's side.
38
+ *
39
+ * @param accessToken - The token to revoke
40
+ * @param clientId - Your GitHub OAuth app client ID
41
+ * @param clientSecret - Your GitHub OAuth app client secret
42
+ * @returns true if revoked successfully, false otherwise
43
+ */
44
+ export declare function revokeGitHubToken(accessToken: string, clientId: string, clientSecret: string): Promise<boolean>;
45
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/providers/github.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7E;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,aAAa;IAClD,QAAQ,CAAC,EAAE,YAAY;IACvB,QAAQ,CAAC,eAAe,SAAS;IAE3B,OAAO,CACX,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CASjC;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,gBAAuB,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC,CAuBlB"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * GitHub OAuth Provider
3
+ *
4
+ * Token characteristics:
5
+ * - Access token lifetime: Does not expire!
6
+ * - Refresh token: Not applicable (tokens don't expire)
7
+ * - Token rotation: Not applicable
8
+ *
9
+ * GitHub tokens are valid until explicitly revoked by the user or
10
+ * the OAuth app is deleted. This makes GitHub simpler to handle
11
+ * but also means stale tokens may accumulate if users disconnect
12
+ * without revoking access.
13
+ *
14
+ * To revoke a token programmatically, use the GitHub API:
15
+ * DELETE /applications/{client_id}/token
16
+ */
17
+ /**
18
+ * GitHub OAuth token provider
19
+ *
20
+ * Note: GitHub tokens don't expire, so refresh() is a no-op that
21
+ * always returns null (indicating no refresh was needed/possible).
22
+ */
23
+ export class GitHubProvider {
24
+ id = 'github';
25
+ supportsRefresh = false;
26
+ async refresh(_refreshToken, _config) {
27
+ // GitHub tokens don't expire - no refresh needed
28
+ // If a token is invalid, user needs to re-authenticate
29
+ console.warn('[GitHubProvider] refresh() called but GitHub tokens do not expire. ' +
30
+ 'If the token is invalid, user needs to re-authenticate.');
31
+ return null;
32
+ }
33
+ }
34
+ /**
35
+ * Default GitHub provider instance
36
+ */
37
+ export const githubProvider = new GitHubProvider();
38
+ /**
39
+ * Revoke a GitHub OAuth token
40
+ *
41
+ * Call this when a user disconnects their GitHub account to properly
42
+ * clean up the token on GitHub's side.
43
+ *
44
+ * @param accessToken - The token to revoke
45
+ * @param clientId - Your GitHub OAuth app client ID
46
+ * @param clientSecret - Your GitHub OAuth app client secret
47
+ * @returns true if revoked successfully, false otherwise
48
+ */
49
+ export async function revokeGitHubToken(accessToken, clientId, clientSecret) {
50
+ try {
51
+ const response = await fetch(`https://api.github.com/applications/${clientId}/token`, {
52
+ method: 'DELETE',
53
+ headers: {
54
+ Accept: 'application/vnd.github+json',
55
+ 'X-GitHub-Api-Version': '2022-11-28',
56
+ Authorization: `Basic ${btoa(`${clientId}:${clientSecret}`)}`,
57
+ 'Content-Type': 'application/json',
58
+ },
59
+ body: JSON.stringify({ access_token: accessToken }),
60
+ });
61
+ // 204 No Content = success
62
+ // 404 = token already invalid/revoked
63
+ return response.status === 204 || response.status === 404;
64
+ }
65
+ catch (error) {
66
+ console.error('[GitHubProvider] Token revocation failed:', error);
67
+ return false;
68
+ }
69
+ }
70
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/providers/github.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IAChB,EAAE,GAAG,QAAQ,CAAC;IACd,eAAe,GAAG,KAAK,CAAC;IAEjC,KAAK,CAAC,OAAO,CACX,aAAqB,EACrB,OAAuB;QAEvB,iDAAiD;QACjD,uDAAuD;QACvD,OAAO,CAAC,IAAI,CACV,qEAAqE;YACnE,yDAAyD,CAC5D,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,WAAmB,EACnB,QAAgB,EAChB,YAAoB;IAEpB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,uCAAuC,QAAQ,QAAQ,EACvD;YACE,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE;gBACP,MAAM,EAAE,6BAA6B;gBACrC,sBAAsB,EAAE,YAAY;gBACpC,aAAa,EAAE,SAAS,IAAI,CAAC,GAAG,QAAQ,IAAI,YAAY,EAAE,CAAC,EAAE;gBAC7D,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;SACpD,CACF,CAAC;QAEF,2BAA2B;QAC3B,sCAAsC;QACtC,OAAO,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;QAClE,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Google OAuth Provider
3
+ *
4
+ * Token characteristics:
5
+ * - Access token lifetime: ~1 hour
6
+ * - Refresh token: Does not expire (unless revoked)
7
+ * - Token rotation: Optional (configurable in Google Cloud Console)
8
+ *
9
+ * Requires `access_type=offline` during initial OAuth to get refresh token
10
+ */
11
+ import type { TokenProvider, ProviderConfig, RefreshResult } from '../types';
12
+ /**
13
+ * Google OAuth token provider
14
+ */
15
+ export declare class GoogleProvider implements TokenProvider {
16
+ readonly id = "google";
17
+ readonly supportsRefresh = true;
18
+ refresh(refreshToken: string, config: ProviderConfig): Promise<RefreshResult | null>;
19
+ }
20
+ /**
21
+ * Default Google provider instance
22
+ */
23
+ export declare const googleProvider: GoogleProvider;
24
+ //# sourceMappingURL=google.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiB7E;;GAEG;AACH,qBAAa,cAAe,YAAW,aAAa;IAClD,QAAQ,CAAC,EAAE,YAAY;IACvB,QAAQ,CAAC,eAAe,QAAQ;IAE1B,OAAO,CACX,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CAgDjC;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Google OAuth Provider
3
+ *
4
+ * Token characteristics:
5
+ * - Access token lifetime: ~1 hour
6
+ * - Refresh token: Does not expire (unless revoked)
7
+ * - Token rotation: Optional (configurable in Google Cloud Console)
8
+ *
9
+ * Requires `access_type=offline` during initial OAuth to get refresh token
10
+ */
11
+ const TOKEN_URL = 'https://oauth2.googleapis.com/token';
12
+ /**
13
+ * Google OAuth token provider
14
+ */
15
+ export class GoogleProvider {
16
+ id = 'google';
17
+ supportsRefresh = true;
18
+ async refresh(refreshToken, config) {
19
+ try {
20
+ const response = await fetch(TOKEN_URL, {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/x-www-form-urlencoded',
24
+ },
25
+ body: new URLSearchParams({
26
+ client_id: config.clientId,
27
+ client_secret: config.clientSecret,
28
+ refresh_token: refreshToken,
29
+ grant_type: 'refresh_token',
30
+ }).toString(),
31
+ });
32
+ if (!response.ok) {
33
+ const error = (await response.json());
34
+ console.error(`[GoogleProvider] Token refresh failed: ${error.error} - ${error.error_description}`);
35
+ // Check for specific errors that indicate re-auth is needed
36
+ if (error.error === 'invalid_grant' ||
37
+ error.error === 'unauthorized_client') {
38
+ // Refresh token is invalid/revoked - user needs to re-authenticate
39
+ return null;
40
+ }
41
+ // Other errors - throw to retry later
42
+ throw new Error(`Token refresh failed: ${error.error}`);
43
+ }
44
+ const data = (await response.json());
45
+ return {
46
+ accessToken: data.access_token,
47
+ // Google may return a new refresh token (rare, but handle it)
48
+ refreshToken: data.refresh_token,
49
+ expiresAt: Date.now() + data.expires_in * 1000,
50
+ };
51
+ }
52
+ catch (error) {
53
+ console.error('[GoogleProvider] Refresh error:', error);
54
+ // Network errors or unexpected issues - return null to trigger re-auth
55
+ return null;
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Default Google provider instance
61
+ */
62
+ export const googleProvider = new GoogleProvider();
63
+ //# sourceMappingURL=google.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,SAAS,GAAG,qCAAqC,CAAC;AAexD;;GAEG;AACH,MAAM,OAAO,cAAc;IAChB,EAAE,GAAG,QAAQ,CAAC;IACd,eAAe,GAAG,IAAI,CAAC;IAEhC,KAAK,CAAC,OAAO,CACX,YAAoB,EACpB,MAAsB;QAEtB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBACtC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,mCAAmC;iBACpD;gBACD,IAAI,EAAE,IAAI,eAAe,CAAC;oBACxB,SAAS,EAAE,MAAM,CAAC,QAAQ;oBAC1B,aAAa,EAAE,MAAM,CAAC,YAAY;oBAClC,aAAa,EAAE,YAAY;oBAC3B,UAAU,EAAE,eAAe;iBAC5B,CAAC,CAAC,QAAQ,EAAE;aACd,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,KAAK,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAwB,CAAC;gBAC7D,OAAO,CAAC,KAAK,CACX,0CAA0C,KAAK,CAAC,KAAK,MAAM,KAAK,CAAC,iBAAiB,EAAE,CACrF,CAAC;gBAEF,4DAA4D;gBAC5D,IACE,KAAK,CAAC,KAAK,KAAK,eAAe;oBAC/B,KAAK,CAAC,KAAK,KAAK,qBAAqB,EACrC,CAAC;oBACD,mEAAmE;oBACnE,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,sCAAsC;gBACtC,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YAC1D,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAwB,CAAC;YAE5D,OAAO;gBACL,WAAW,EAAE,IAAI,CAAC,YAAY;gBAC9B,8DAA8D;gBAC9D,YAAY,EAAE,IAAI,CAAC,aAAa;gBAChC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI;aAC/C,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;YACxD,uEAAuE;YACvE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Microsoft OAuth Provider (Azure AD / Entra)
3
+ *
4
+ * Token characteristics:
5
+ * - Access token lifetime: ~1 hour (configurable via token lifetime policies)
6
+ * - Refresh token: 90 days (revoked on password change)
7
+ * - Token rotation: Yes (Microsoft rotates refresh tokens by default)
8
+ *
9
+ * Tenant options:
10
+ * - 'common': Any Microsoft account (personal + work)
11
+ * - 'organizations': Work/school accounts only
12
+ * - 'consumers': Personal Microsoft accounts only
13
+ * - '{tenant-id}': Specific organization only
14
+ */
15
+ import type { TokenProvider, ProviderConfig, RefreshResult } from '../types';
16
+ /**
17
+ * Microsoft OAuth token provider
18
+ */
19
+ export declare class MicrosoftProvider implements TokenProvider {
20
+ readonly id = "microsoft";
21
+ readonly supportsRefresh = true;
22
+ private getTokenUrl;
23
+ refresh(refreshToken: string, config: ProviderConfig): Promise<RefreshResult | null>;
24
+ }
25
+ /**
26
+ * Default Microsoft provider instance
27
+ */
28
+ export declare const microsoftProvider: MicrosoftProvider;
29
+ //# sourceMappingURL=microsoft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"microsoft.d.ts","sourceRoot":"","sources":["../../src/providers/microsoft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAkB7E;;GAEG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IACrD,QAAQ,CAAC,EAAE,eAAe;IAC1B,QAAQ,CAAC,eAAe,QAAQ;IAEhC,OAAO,CAAC,WAAW;IAIb,OAAO,CACX,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CAqDjC;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Microsoft OAuth Provider (Azure AD / Entra)
3
+ *
4
+ * Token characteristics:
5
+ * - Access token lifetime: ~1 hour (configurable via token lifetime policies)
6
+ * - Refresh token: 90 days (revoked on password change)
7
+ * - Token rotation: Yes (Microsoft rotates refresh tokens by default)
8
+ *
9
+ * Tenant options:
10
+ * - 'common': Any Microsoft account (personal + work)
11
+ * - 'organizations': Work/school accounts only
12
+ * - 'consumers': Personal Microsoft accounts only
13
+ * - '{tenant-id}': Specific organization only
14
+ */
15
+ const DEFAULT_TENANT = 'common';
16
+ /**
17
+ * Microsoft OAuth token provider
18
+ */
19
+ export class MicrosoftProvider {
20
+ id = 'microsoft';
21
+ supportsRefresh = true;
22
+ getTokenUrl(tenantId) {
23
+ return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
24
+ }
25
+ async refresh(refreshToken, config) {
26
+ const tenantId = config.tenantId ?? DEFAULT_TENANT;
27
+ const tokenUrl = this.getTokenUrl(tenantId);
28
+ try {
29
+ const response = await fetch(tokenUrl, {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/x-www-form-urlencoded',
33
+ },
34
+ body: new URLSearchParams({
35
+ client_id: config.clientId,
36
+ client_secret: config.clientSecret,
37
+ refresh_token: refreshToken,
38
+ grant_type: 'refresh_token',
39
+ }).toString(),
40
+ });
41
+ if (!response.ok) {
42
+ const error = (await response.json());
43
+ console.error(`[MicrosoftProvider] Token refresh failed: ${error.error} - ${error.error_description}`);
44
+ // Check for specific AADSTS errors that indicate re-auth is needed
45
+ // AADSTS70000: Refresh token expired
46
+ // AADSTS50173: Refresh token expired (password change)
47
+ // AADSTS700082: Refresh token expired (inactivity)
48
+ if (error.error === 'invalid_grant' ||
49
+ error.error_codes?.some((code) => [70000, 50173, 700082].includes(code))) {
50
+ return null;
51
+ }
52
+ throw new Error(`Token refresh failed: ${error.error}`);
53
+ }
54
+ const data = (await response.json());
55
+ return {
56
+ accessToken: data.access_token,
57
+ // Microsoft typically returns a new refresh token - always use it!
58
+ refreshToken: data.refresh_token,
59
+ expiresAt: Date.now() + data.expires_in * 1000,
60
+ };
61
+ }
62
+ catch (error) {
63
+ console.error('[MicrosoftProvider] Refresh error:', error);
64
+ return null;
65
+ }
66
+ }
67
+ }
68
+ /**
69
+ * Default Microsoft provider instance
70
+ */
71
+ export const microsoftProvider = new MicrosoftProvider();
72
+ //# sourceMappingURL=microsoft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"microsoft.js","sourceRoot":"","sources":["../../src/providers/microsoft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,cAAc,GAAG,QAAQ,CAAC;AAgBhC;;GAEG;AACH,MAAM,OAAO,iBAAiB;IACnB,EAAE,GAAG,WAAW,CAAC;IACjB,eAAe,GAAG,IAAI,CAAC;IAExB,WAAW,CAAC,QAAgB;QAClC,OAAO,qCAAqC,QAAQ,oBAAoB,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,OAAO,CACX,YAAoB,EACpB,MAAsB;QAEtB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,cAAc,CAAC;QACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;gBACrC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,mCAAmC;iBACpD;gBACD,IAAI,EAAE,IAAI,eAAe,CAAC;oBACxB,SAAS,EAAE,MAAM,CAAC,QAAQ;oBAC1B,aAAa,EAAE,MAAM,CAAC,YAAY;oBAClC,aAAa,EAAE,YAAY;oBAC3B,UAAU,EAAE,eAAe;iBAC5B,CAAC,CAAC,QAAQ,EAAE;aACd,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,KAAK,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAC;gBAChE,OAAO,CAAC,KAAK,CACX,6CAA6C,KAAK,CAAC,KAAK,MAAM,KAAK,CAAC,iBAAiB,EAAE,CACxF,CAAC;gBAEF,mEAAmE;gBACnE,qCAAqC;gBACrC,uDAAuD;gBACvD,mDAAmD;gBACnD,IACE,KAAK,CAAC,KAAK,KAAK,eAAe;oBAC/B,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAC/B,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CACtC,EACD,CAAC;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YAC1D,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAC;YAE/D,OAAO;gBACL,WAAW,EAAE,IAAI,CAAC,YAAY;gBAC9B,mEAAmE;gBACnE,YAAY,EAAE,IAAI,CAAC,aAAa;gBAChC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI;aAC/C,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Provider types
3
+ *
4
+ * Re-export from main types for convenience
5
+ */
6
+ export type { TokenProvider, ProviderConfig, RefreshResult } from '../types';
7
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Provider 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/providers/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,22 @@
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
+ import type { TokenStorage, StoredToken, ConnectedProvider } from '../types';
10
+ /**
11
+ * D1 Storage adapter for Cloudflare D1
12
+ *
13
+ * NOT YET IMPLEMENTED - Use KVStorage for now
14
+ */
15
+ export declare class D1Storage implements TokenStorage {
16
+ constructor(_db: D1Database, _encryptionKey: string);
17
+ get(_userId: string, _provider: string): Promise<StoredToken | null>;
18
+ set(_token: StoredToken): Promise<void>;
19
+ delete(_userId: string, _provider: string): Promise<void>;
20
+ list(_userId: string): Promise<ConnectedProvider[]>;
21
+ }
22
+ //# sourceMappingURL=d1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/storage/d1.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7E;;;;GAIG;AACH,qBAAa,SAAU,YAAW,YAAY;gBAChC,GAAG,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM;IAMnD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAIpE,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;CAGpD"}