@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/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # @jezweb/oauth-token-manager
2
+
3
+ OAuth token management for Cloudflare Workers. Store, refresh, and retrieve tokens for downstream API access.
4
+
5
+ ## The Problem
6
+
7
+ When your application needs to call APIs on behalf of users (Google Calendar, GitHub, Xero, etc.), you need to:
8
+
9
+ 1. **Store** OAuth tokens securely (encrypted at rest)
10
+ 2. **Refresh** expired tokens automatically
11
+ 3. **Retrieve** valid tokens for API calls
12
+ 4. **Handle** errors gracefully (expired, revoked, insufficient scopes)
13
+
14
+ Most auth libraries focus on **identity** ("who is this user?") not **API access** ("act on their behalf"). This package fills that gap.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @jezweb/oauth-token-manager
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import { TokenManager, KVStorage } from '@jezweb/oauth-token-manager';
26
+
27
+ // Initialize
28
+ const tokens = new TokenManager({
29
+ storage: new KVStorage({
30
+ namespace: env.TOKEN_KV,
31
+ encryptionKey: env.TOKEN_ENCRYPTION_KEY,
32
+ }),
33
+ providers: {
34
+ google: {
35
+ clientId: env.GOOGLE_CLIENT_ID,
36
+ clientSecret: env.GOOGLE_CLIENT_SECRET,
37
+ },
38
+ },
39
+ });
40
+
41
+ // Store token after OAuth callback
42
+ await tokens.store({
43
+ userId: 'user-123',
44
+ provider: 'google',
45
+ accessToken: 'ya29.xxx',
46
+ refreshToken: '1//xxx',
47
+ expiresAt: Date.now() + 3600000,
48
+ scopes: ['https://www.googleapis.com/auth/calendar'],
49
+ });
50
+
51
+ // Get valid token (auto-refreshes if expired)
52
+ const { accessToken } = await tokens.get({
53
+ userId: 'user-123',
54
+ provider: 'google',
55
+ });
56
+
57
+ // Use token for API call
58
+ const response = await fetch('https://www.googleapis.com/calendar/v3/calendars', {
59
+ headers: { Authorization: `Bearer ${accessToken}` },
60
+ });
61
+ ```
62
+
63
+ ## Features
64
+
65
+ - **Encrypted storage** - Tokens encrypted at rest using AES-256-GCM
66
+ - **Automatic refresh** - Tokens refreshed before expiry (5 min buffer by default)
67
+ - **Scope validation** - Verify required scopes before returning tokens
68
+ - **Built-in providers** - Google, Microsoft, GitHub out of the box
69
+ - **Cloudflare-native** - Built specifically for Workers + KV
70
+ - **Clear errors** - Typed errors guide recovery actions
71
+
72
+ ## API
73
+
74
+ ### `TokenManager`
75
+
76
+ Main class for token management.
77
+
78
+ ```typescript
79
+ const tokens = new TokenManager({
80
+ storage: TokenStorage, // KVStorage or custom (handles encryption)
81
+ providers: { // Provider configs for refresh
82
+ google?: ProviderConfig,
83
+ microsoft?: ProviderConfig,
84
+ github?: ProviderConfig,
85
+ },
86
+ defaultRefreshBuffer?: number, // ms before expiry to refresh (default: 5 min)
87
+ });
88
+ ```
89
+
90
+ #### Methods
91
+
92
+ | Method | Description |
93
+ |--------|-------------|
94
+ | `store(options)` | Store a new token or update existing |
95
+ | `get(options)` | Get valid token (auto-refreshes) |
96
+ | `list(options)` | List connected providers for a user |
97
+ | `revoke(options)` | Delete a token |
98
+ | `has(userId, provider)` | Check if token exists |
99
+
100
+ ### Error Types
101
+
102
+ | Error | Meaning | Recovery |
103
+ |-------|---------|----------|
104
+ | `TokenNotFoundError` | No token for user/provider | Redirect to OAuth |
105
+ | `TokenExpiredError` | Token expired, refresh failed | Redirect to OAuth |
106
+ | `InsufficientScopesError` | Missing required scopes | Redirect to OAuth with incremental consent |
107
+ | `ProviderNotConfiguredError` | Provider not in config | Add provider config |
108
+
109
+ ## Supported Providers
110
+
111
+ | Provider | Refresh Support | Token Lifetime | Notes |
112
+ |----------|-----------------|----------------|-------|
113
+ | Google | ✅ Yes | ~1 hour | Requires `access_type=offline` |
114
+ | Microsoft | ✅ Yes | ~1 hour | Token rotation by default |
115
+ | GitHub | ❌ No | Never expires | Tokens valid until revoked |
116
+
117
+ ## Storage Adapters
118
+
119
+ ### KV Storage (Recommended)
120
+
121
+ ```typescript
122
+ import { KVStorage } from '@jezweb/oauth-token-manager/storage/kv';
123
+
124
+ const storage = new KVStorage({
125
+ namespace: env.TOKEN_KV,
126
+ encryptionKey: env.TOKEN_ENCRYPTION_KEY,
127
+ keyPrefix: 'tokens', // optional, default: 'tokens'
128
+ });
129
+ ```
130
+
131
+ ### D1 Storage (Coming Soon)
132
+
133
+ D1 adapter for stronger consistency and complex queries.
134
+
135
+ ## Wrangler Setup
136
+
137
+ ```toml
138
+ # wrangler.toml
139
+ kv_namespaces = [
140
+ { binding = "TOKEN_KV", id = "your-kv-id" }
141
+ ]
142
+
143
+ [vars]
144
+ # Store encryption key as secret, not here!
145
+ ```
146
+
147
+ ```bash
148
+ # Set encryption key (generate with: openssl rand -base64 32)
149
+ echo "your-32-byte-key" | wrangler secret put TOKEN_ENCRYPTION_KEY
150
+ ```
151
+
152
+ ## Use Cases
153
+
154
+ - **MCP Servers** - Call Google Calendar, GitHub, etc. on behalf of users
155
+ - **CRM integrations** - Sync with external calendars, email
156
+ - **Social media tools** - Post to Twitter, LinkedIn
157
+ - **Accounting apps** - Connect to Xero, QuickBooks
158
+
159
+ ## Architecture
160
+
161
+ This package handles **outbound** OAuth (your app calling external APIs).
162
+
163
+ For **inbound** OAuth (clients authenticating to your app), use:
164
+ - [`@cloudflare/workers-oauth-provider`](https://github.com/cloudflare/workers-oauth-provider)
165
+ - [better-auth](https://better-auth.com)
166
+
167
+ ```
168
+ ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐
169
+ │ MCP Client │────▶│ Your App │────▶│ External API │
170
+ │ (Claude.ai) │ │ │ │ (Google, etc) │
171
+ └─────────────┘ └─────────────────┘ └─────────────────┘
172
+ │ │ │
173
+ ▼ ▼ ▼
174
+ Inbound auth Token Manager External OAuth
175
+ (who is client?) (this package) (act on behalf)
176
+ ```
177
+
178
+ ## Security
179
+
180
+ See [SECURITY.md](./SECURITY.md) for security considerations.
181
+
182
+ ## License
183
+
184
+ MIT © [Jezweb](https://jezweb.com.au)
package/SECURITY.md ADDED
@@ -0,0 +1,162 @@
1
+ # Security Considerations
2
+
3
+ This document describes the security model of `@jezweb/oauth-token-manager`.
4
+
5
+ ## Token Encryption
6
+
7
+ ### Algorithm
8
+
9
+ - **Encryption**: AES-256-GCM (Galois/Counter Mode)
10
+ - **Key Derivation**: PBKDF2 with SHA-256, 100,000 iterations
11
+ - **IV**: Random 12 bytes per encryption
12
+ - **Salt**: Random 16 bytes per encryption
13
+
14
+ ### What's Encrypted
15
+
16
+ | Field | Encrypted | Reason |
17
+ |-------|-----------|--------|
18
+ | `accessToken` | ✅ Yes | Sensitive credential |
19
+ | `refreshToken` | ✅ Yes | Sensitive credential |
20
+ | `userId` | ❌ No | Needed for lookup |
21
+ | `provider` | ❌ No | Needed for lookup |
22
+ | `scopes` | ❌ No | Not sensitive |
23
+ | `expiresAt` | ❌ No | Useful for auditing |
24
+ | `createdAt` | ❌ No | Useful for auditing |
25
+ | `updatedAt` | ❌ No | Useful for auditing |
26
+
27
+ ### Security Properties
28
+
29
+ 1. **Confidentiality**: Tokens cannot be read without the encryption key
30
+ 2. **Integrity**: GCM authentication tag detects tampering
31
+ 3. **Forward secrecy**: Each encryption uses a unique salt + IV
32
+ 4. **No key exposure**: Encryption key never stored, only used
33
+
34
+ ## Encryption Key Management
35
+
36
+ ### Requirements
37
+
38
+ - **Length**: 32+ bytes recommended (256 bits)
39
+ - **Randomness**: Use cryptographically secure random generation
40
+ - **Storage**: Store as Wrangler secret, never in code or env vars
41
+
42
+ ### Generating a Key
43
+
44
+ ```bash
45
+ # Generate a secure key
46
+ openssl rand -base64 32
47
+
48
+ # Store as Wrangler secret
49
+ echo "your-key" | wrangler secret put TOKEN_ENCRYPTION_KEY
50
+ ```
51
+
52
+ ### Key Rotation
53
+
54
+ Key rotation is NOT currently supported. If you need to rotate:
55
+
56
+ 1. Deploy new version with new key
57
+ 2. Users must re-authenticate to get new tokens
58
+ 3. Old tokens become unreadable
59
+
60
+ Future versions may support key rotation with re-encryption.
61
+
62
+ ## Storage Security
63
+
64
+ ### KV Storage
65
+
66
+ - Tokens stored with user-specific keys: `tokens:{userId}:{provider}`
67
+ - Index stored separately: `token-index:{userId}`
68
+ - No cross-user data access possible with correct key structure
69
+
70
+ ### Access Control
71
+
72
+ - Your Worker has full access to the KV namespace
73
+ - Implement authorization in your Worker to control which users can access which tokens
74
+ - Never expose TokenManager methods directly to untrusted input
75
+
76
+ ## Provider Credentials
77
+
78
+ ### Storage
79
+
80
+ - Provider `clientId` and `clientSecret` should be stored as Wrangler secrets
81
+ - Never hardcode credentials in source code
82
+ - Use environment variables via Wrangler bindings
83
+
84
+ ### Exposure Risk
85
+
86
+ If provider credentials are compromised:
87
+
88
+ 1. Attacker could refresh tokens (if they also have refresh tokens)
89
+ 2. Attacker could NOT decrypt stored tokens without encryption key
90
+ 3. Revoke compromised credentials immediately in provider console
91
+
92
+ ## Attack Vectors
93
+
94
+ ### Storage Breach
95
+
96
+ If KV storage is compromised:
97
+
98
+ | Data Exposed | Risk | Mitigation |
99
+ |--------------|------|------------|
100
+ | Encrypted tokens | Low | Cannot decrypt without key |
101
+ | User IDs | Medium | Consider hashing user IDs |
102
+ | Scopes | Low | Not sensitive |
103
+ | Timestamps | Low | Audit trail only |
104
+
105
+ ### Encryption Key Breach
106
+
107
+ If encryption key is compromised:
108
+
109
+ | Risk | Impact |
110
+ |------|--------|
111
+ | Decrypt all tokens | High - full API access |
112
+ | Impersonate users | High - act as any user |
113
+
114
+ **Mitigation**: Rotate key immediately, invalidate all tokens.
115
+
116
+ ### Provider Token Theft
117
+
118
+ If decrypted tokens are stolen:
119
+
120
+ | Token Type | Risk | Mitigation |
121
+ |------------|------|------------|
122
+ | Access token | Time-limited (~1h) | Short expiry |
123
+ | Refresh token | Long-lived | Revoke at provider |
124
+
125
+ ## Best Practices
126
+
127
+ ### Do
128
+
129
+ - ✅ Use strong encryption keys (32+ bytes, random)
130
+ - ✅ Store encryption key as Wrangler secret
131
+ - ✅ Store provider credentials as secrets
132
+ - ✅ Validate user authorization before token access
133
+ - ✅ Log token access for audit (without logging tokens)
134
+ - ✅ Monitor for unusual access patterns
135
+
136
+ ### Don't
137
+
138
+ - ❌ Log tokens or encryption keys
139
+ - ❌ Include tokens in error messages
140
+ - ❌ Store encryption key in source code
141
+ - ❌ Use predictable encryption keys
142
+ - ❌ Skip user authorization checks
143
+
144
+ ## Reporting Vulnerabilities
145
+
146
+ If you discover a security vulnerability:
147
+
148
+ 1. **Do not** open a public GitHub issue
149
+ 2. Email security concerns to jeremy@jezweb.net
150
+ 3. Include steps to reproduce
151
+ 4. Allow 90 days for fix before disclosure
152
+
153
+ ## Compliance
154
+
155
+ This package:
156
+
157
+ - Uses industry-standard encryption (AES-256-GCM)
158
+ - Does not transmit tokens to third parties
159
+ - Does not store encryption keys
160
+ - Provides audit trail via timestamps
161
+
162
+ For specific compliance requirements (GDPR, SOC2, etc.), consult your compliance team about overall system architecture.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Cryptographic utilities for token encryption at rest
3
+ *
4
+ * Uses Web Crypto API (available in Cloudflare Workers, browsers, Node 18+)
5
+ * Algorithm: AES-256-GCM (authenticated encryption)
6
+ *
7
+ * Security properties:
8
+ * - Confidentiality: Tokens are encrypted and unreadable without the key
9
+ * - Integrity: Tampering with ciphertext is detected (GCM auth tag)
10
+ * - Key derivation: PBKDF2 derives strong key from your secret
11
+ * - Random IVs: Same plaintext produces different ciphertext each time
12
+ */
13
+ /**
14
+ * Encrypt plaintext using AES-256-GCM
15
+ *
16
+ * Output format: base64(salt + iv + ciphertext + authTag)
17
+ * - salt: 16 bytes (for key derivation)
18
+ * - iv: 12 bytes (initialization vector)
19
+ * - ciphertext: variable length
20
+ * - authTag: 16 bytes (included in ciphertext by Web Crypto)
21
+ *
22
+ * @param plaintext - Data to encrypt
23
+ * @param encryptionKey - Secret key/password for encryption
24
+ * @returns Base64-encoded encrypted data
25
+ */
26
+ export declare function encrypt(plaintext: string, encryptionKey: string): Promise<string>;
27
+ /**
28
+ * Decrypt data encrypted with encrypt()
29
+ *
30
+ * @param encryptedData - Base64-encoded encrypted data
31
+ * @param encryptionKey - Secret key/password used for encryption
32
+ * @returns Decrypted plaintext
33
+ */
34
+ export declare function decrypt(encryptedData: string, encryptionKey: string): Promise<string>;
35
+ /**
36
+ * Encrypt an object as JSON
37
+ */
38
+ export declare function encryptObject<T>(obj: T, encryptionKey: string): Promise<string>;
39
+ /**
40
+ * Decrypt JSON back to an object
41
+ */
42
+ export declare function decryptObject<T>(encryptedData: string, encryptionKey: string): Promise<T>;
43
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0CH;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAC3B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC3B,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,GAAG,EAAE,CAAC,EACN,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,CAAC,CAAC,CAGZ"}
package/dist/crypto.js ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Cryptographic utilities for token encryption at rest
3
+ *
4
+ * Uses Web Crypto API (available in Cloudflare Workers, browsers, Node 18+)
5
+ * Algorithm: AES-256-GCM (authenticated encryption)
6
+ *
7
+ * Security properties:
8
+ * - Confidentiality: Tokens are encrypted and unreadable without the key
9
+ * - Integrity: Tampering with ciphertext is detected (GCM auth tag)
10
+ * - Key derivation: PBKDF2 derives strong key from your secret
11
+ * - Random IVs: Same plaintext produces different ciphertext each time
12
+ */
13
+ import { CryptoError } from './errors';
14
+ // AES-GCM parameters
15
+ const ALGORITHM = 'AES-GCM';
16
+ const KEY_LENGTH = 256; // bits
17
+ const IV_LENGTH = 12; // bytes (96 bits, recommended for GCM)
18
+ const SALT_LENGTH = 16; // bytes
19
+ const PBKDF2_ITERATIONS = 100000;
20
+ /**
21
+ * Derive a cryptographic key from a password/secret using PBKDF2
22
+ */
23
+ async function deriveKey(secret, salt) {
24
+ // Import the secret as a key for PBKDF2
25
+ const keyMaterial = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), 'PBKDF2', false, ['deriveKey']);
26
+ // Derive AES key using PBKDF2
27
+ return crypto.subtle.deriveKey({
28
+ name: 'PBKDF2',
29
+ salt,
30
+ iterations: PBKDF2_ITERATIONS,
31
+ hash: 'SHA-256',
32
+ }, keyMaterial, { name: ALGORITHM, length: KEY_LENGTH }, false, ['encrypt', 'decrypt']);
33
+ }
34
+ /**
35
+ * Encrypt plaintext using AES-256-GCM
36
+ *
37
+ * Output format: base64(salt + iv + ciphertext + authTag)
38
+ * - salt: 16 bytes (for key derivation)
39
+ * - iv: 12 bytes (initialization vector)
40
+ * - ciphertext: variable length
41
+ * - authTag: 16 bytes (included in ciphertext by Web Crypto)
42
+ *
43
+ * @param plaintext - Data to encrypt
44
+ * @param encryptionKey - Secret key/password for encryption
45
+ * @returns Base64-encoded encrypted data
46
+ */
47
+ export async function encrypt(plaintext, encryptionKey) {
48
+ try {
49
+ // Generate random salt and IV
50
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
51
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
52
+ // Derive key from secret
53
+ const key = await deriveKey(encryptionKey, salt);
54
+ // Encrypt the data
55
+ const encoded = new TextEncoder().encode(plaintext);
56
+ const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, encoded);
57
+ // Combine salt + iv + ciphertext into single buffer
58
+ const combined = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
59
+ combined.set(salt, 0);
60
+ combined.set(iv, salt.length);
61
+ combined.set(new Uint8Array(ciphertext), salt.length + iv.length);
62
+ // Return as base64
63
+ return btoa(String.fromCharCode(...combined));
64
+ }
65
+ catch (error) {
66
+ throw new CryptoError('encrypt', error instanceof Error ? error : undefined);
67
+ }
68
+ }
69
+ /**
70
+ * Decrypt data encrypted with encrypt()
71
+ *
72
+ * @param encryptedData - Base64-encoded encrypted data
73
+ * @param encryptionKey - Secret key/password used for encryption
74
+ * @returns Decrypted plaintext
75
+ */
76
+ export async function decrypt(encryptedData, encryptionKey) {
77
+ try {
78
+ // Decode base64
79
+ const combined = Uint8Array.from(atob(encryptedData), (c) => c.charCodeAt(0));
80
+ // Extract salt, iv, and ciphertext
81
+ const salt = combined.slice(0, SALT_LENGTH);
82
+ const iv = combined.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
83
+ const ciphertext = combined.slice(SALT_LENGTH + IV_LENGTH);
84
+ // Derive key from secret
85
+ const key = await deriveKey(encryptionKey, salt);
86
+ // Decrypt the data
87
+ const decrypted = await crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext);
88
+ return new TextDecoder().decode(decrypted);
89
+ }
90
+ catch (error) {
91
+ throw new CryptoError('decrypt', error instanceof Error ? error : undefined);
92
+ }
93
+ }
94
+ /**
95
+ * Encrypt an object as JSON
96
+ */
97
+ export async function encryptObject(obj, encryptionKey) {
98
+ return encrypt(JSON.stringify(obj), encryptionKey);
99
+ }
100
+ /**
101
+ * Decrypt JSON back to an object
102
+ */
103
+ export async function decryptObject(encryptedData, encryptionKey) {
104
+ const json = await decrypt(encryptedData, encryptionKey);
105
+ return JSON.parse(json);
106
+ }
107
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
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 declare class TokenManagerError extends Error {
11
+ readonly code: string;
12
+ constructor(message: string, code: string);
13
+ }
14
+ /**
15
+ * Token not found for the given user and provider
16
+ *
17
+ * Recovery: Redirect user to OAuth flow to connect this provider
18
+ */
19
+ export declare class TokenNotFoundError extends TokenManagerError {
20
+ readonly userId: string;
21
+ readonly provider: string;
22
+ constructor(userId: string, provider: string);
23
+ }
24
+ /**
25
+ * Token has expired and refresh failed or no refresh token available
26
+ *
27
+ * Recovery: Redirect user to OAuth flow to re-authenticate
28
+ */
29
+ export declare class TokenExpiredError extends TokenManagerError {
30
+ readonly userId: string;
31
+ readonly provider: string;
32
+ readonly reason: 'no_refresh_token' | 'refresh_failed' | 'refresh_token_expired';
33
+ constructor(userId: string, provider: string, reason: 'no_refresh_token' | 'refresh_failed' | 'refresh_token_expired');
34
+ }
35
+ /**
36
+ * Token exists but doesn't have the required scopes
37
+ *
38
+ * Recovery: Redirect user to OAuth flow with incremental consent for missing scopes
39
+ */
40
+ export declare class InsufficientScopesError extends TokenManagerError {
41
+ readonly userId: string;
42
+ readonly provider: string;
43
+ readonly requiredScopes: string[];
44
+ readonly grantedScopes: string[];
45
+ constructor(userId: string, provider: string, requiredScopes: string[], grantedScopes: string[]);
46
+ get missingScopes(): string[];
47
+ }
48
+ /**
49
+ * Provider is not configured in the token manager
50
+ *
51
+ * Recovery: Add provider configuration to TokenManager constructor
52
+ */
53
+ export declare class ProviderNotConfiguredError extends TokenManagerError {
54
+ readonly provider: string;
55
+ constructor(provider: string);
56
+ }
57
+ /**
58
+ * Encryption/decryption failed
59
+ *
60
+ * Recovery: Check encryption key is correct and hasn't changed
61
+ */
62
+ export declare class CryptoError extends TokenManagerError {
63
+ readonly cause?: Error | undefined;
64
+ constructor(operation: 'encrypt' | 'decrypt', cause?: Error | undefined);
65
+ }
66
+ /**
67
+ * Storage operation failed
68
+ *
69
+ * Recovery: Check storage backend (KV/D1) is available and configured correctly
70
+ */
71
+ export declare class StorageError extends TokenManagerError {
72
+ readonly cause?: Error | undefined;
73
+ constructor(operation: 'get' | 'set' | 'delete' | 'list', cause?: Error | undefined);
74
+ }
75
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;aAGxB,IAAI,EAAE,MAAM;gBAD5B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM;CAK/B;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,iBAAiB;aAErC,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,MAAM;gBADhB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM;CAQnC;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,iBAAiB;aAEpC,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,uBAAuB;gBAFvE,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,uBAAuB;CAa1F;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,iBAAiB;aAE1C,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,MAAM;aAChB,cAAc,EAAE,MAAM,EAAE;aACxB,aAAa,EAAE,MAAM,EAAE;gBAHvB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EAAE,EACxB,aAAa,EAAE,MAAM,EAAE;IAUzC,IAAI,aAAa,IAAI,MAAM,EAAE,CAE5B;CACF;AAED;;;;GAIG;AACH,qBAAa,0BAA2B,SAAQ,iBAAiB;aACnC,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAO7C;AAED;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,iBAAiB;aAG9B,KAAK,CAAC,EAAE,KAAK;gBAD7B,SAAS,EAAE,SAAS,GAAG,SAAS,EAChB,KAAK,CAAC,EAAE,KAAK,YAAA;CAQhC;AAED;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,iBAAiB;aAG/B,KAAK,CAAC,EAAE,KAAK;gBAD7B,SAAS,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,EAC5B,KAAK,CAAC,EAAE,KAAK,YAAA;CAQhC"}