@dainprotocol/oauth2-token-manager 0.1.0 โ 0.1.1
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/README.md +392 -312
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +197 -1
- package/dist/index.d.ts +197 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +28 -31
package/README.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
A powerful, storage-agnostic OAuth2 token management library built for scalable multi-system architectures. This library provides comprehensive token lifecycle management with pluggable storage adapters, built-in security features, and support for multiple OAuth2 providers.
|
|
4
4
|
|
|
5
|
+
## ๐ Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#-features)
|
|
8
|
+
- [Installation](#-installation)
|
|
9
|
+
- [Quick Start](#-quick-start)
|
|
10
|
+
- [Granular API (Advanced)](#-granular-api-advanced)
|
|
11
|
+
- [Context-Aware Resolution](#context-aware-resolution)
|
|
12
|
+
- [User Management](#user-management)
|
|
13
|
+
- [Token Operations](#token-operations)
|
|
14
|
+
- [Bulk Operations](#bulk-operations)
|
|
15
|
+
- [System & Scope Management](#system--scope-management)
|
|
16
|
+
- [Architecture](#-architecture)
|
|
17
|
+
- [API Reference](#-api-reference)
|
|
18
|
+
- [Storage Adapters](#storage-adapters)
|
|
19
|
+
- [Provider Configuration](#provider-configuration)
|
|
20
|
+
- [Advanced Features](#-advanced-features)
|
|
21
|
+
- [Examples](#-examples)
|
|
22
|
+
- [Production Deployment](#-production-deployment)
|
|
23
|
+
|
|
5
24
|
## ๐ Features
|
|
6
25
|
|
|
7
26
|
- **๐ Storage Agnostic**: Use any storage backend (In-Memory, PostgreSQL, or build your own adapter)
|
|
@@ -12,6 +31,7 @@ A powerful, storage-agnostic OAuth2 token management library built for scalable
|
|
|
12
31
|
- **๐ค User Management**: Comprehensive user lifecycle with email/external ID support
|
|
13
32
|
- **๐ง Profile Integration**: Automatic profile fetching from OAuth providers
|
|
14
33
|
- **๐ฏ Flexible Scoping**: Fine-grained permission management
|
|
34
|
+
- **๐จ Default Configuration**: Automatic default system and scope creation
|
|
15
35
|
- **๐ก Developer Friendly**: Both context-managed and granular APIs
|
|
16
36
|
- **๐งช Fully Tested**: Comprehensive test coverage with Vitest
|
|
17
37
|
|
|
@@ -26,6 +46,9 @@ npm install @dainprotocol/oauth2-token-manager
|
|
|
26
46
|
```bash
|
|
27
47
|
# PostgreSQL adapter
|
|
28
48
|
npm install @dainprotocol/oauth2-storage-postgres
|
|
49
|
+
|
|
50
|
+
# Drizzle adapter (supports PostgreSQL, MySQL, SQLite)
|
|
51
|
+
npm install @dainprotocol/oauth2-storage-drizzle
|
|
29
52
|
```
|
|
30
53
|
|
|
31
54
|
## ๐ Quick Start
|
|
@@ -70,44 +93,219 @@ const { url, state } = await oauth.authorize({
|
|
|
70
93
|
// Handle callback
|
|
71
94
|
const result = await oauth.handleCallback(code, state);
|
|
72
95
|
console.log('User authenticated:', result.userId);
|
|
96
|
+
|
|
97
|
+
// === NEW: Simplified Granular API (V2) ===
|
|
98
|
+
// Access tokens without specifying system/scope IDs
|
|
99
|
+
const accessToken = await oauth.granularV2.getAccessToken({
|
|
100
|
+
email: 'user@example.com',
|
|
101
|
+
provider: 'google',
|
|
102
|
+
});
|
|
73
103
|
```
|
|
74
104
|
|
|
75
|
-
|
|
105
|
+
## ๐ฏ Granular API (Advanced)
|
|
106
|
+
|
|
107
|
+
The granular API (`oauth.granularV2`) provides full control over the token hierarchy with intelligent system and scope resolution. It's the recommended way to interact with tokens when you need fine-grained control.
|
|
108
|
+
|
|
109
|
+
### Context-Aware Resolution
|
|
110
|
+
|
|
111
|
+
The API follows this priority order for resolving system and scope:
|
|
112
|
+
|
|
113
|
+
1. **Explicit parameters** - If you provide systemId/scopeId in the method call, those are used
|
|
114
|
+
2. **Context from OAuth2Client** - If not provided, it checks the current context (useSystem/setDefaultScope)
|
|
115
|
+
3. **Default system/scope** - If no context is set, it falls back to the default system and scope
|
|
76
116
|
|
|
77
117
|
```typescript
|
|
78
|
-
|
|
79
|
-
import { PostgresStorageFactory } from '@dainprotocol/oauth2-storage-postgres';
|
|
118
|
+
// === Context-Aware Usage ===
|
|
80
119
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
120
|
+
// Set context in main client
|
|
121
|
+
await oauth.useSystem('production-system');
|
|
122
|
+
oauth.setDefaultScope('api-access');
|
|
123
|
+
|
|
124
|
+
// Granular V2 will now use this context automatically
|
|
125
|
+
const user = await oauth.granularV2.getOrCreateUser({
|
|
126
|
+
email: 'user@example.com',
|
|
127
|
+
options: { metadata: { role: 'user' } },
|
|
128
|
+
// No need to specify systemId - uses 'production-system' from context
|
|
88
129
|
});
|
|
89
130
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
131
|
+
// Token operations use the context scope
|
|
132
|
+
const token = await oauth.granularV2.getAccessToken({
|
|
133
|
+
email: 'user@example.com',
|
|
134
|
+
provider: 'google',
|
|
135
|
+
// No need to specify scopeId - uses 'api-access' from context
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Override with explicit parameters when needed
|
|
139
|
+
const differentToken = await oauth.granularV2.getAccessToken({
|
|
140
|
+
email: 'user@example.com',
|
|
141
|
+
provider: 'google',
|
|
142
|
+
systemId: 'another-system', // This takes precedence
|
|
143
|
+
scopeId: 'different-scope', // This takes precedence
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### User Management
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Create or get users with optional system specification
|
|
151
|
+
const user = await oauth.granularV2.getOrCreateUser({
|
|
152
|
+
email: 'user@example.com',
|
|
153
|
+
options: { metadata: { role: 'admin' } },
|
|
154
|
+
// systemId is optional - uses context or defaults
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Find users by various criteria
|
|
158
|
+
const userByEmail = await oauth.granularV2.findUserByEmail({
|
|
159
|
+
email: 'user@example.com',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const userByExternalId = await oauth.granularV2.findUserByExternalId({
|
|
163
|
+
externalId: 'external-123',
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Get all users in a system
|
|
167
|
+
const systemUsers = await oauth.granularV2.getUsersBySystem();
|
|
168
|
+
// Or specify a different system
|
|
169
|
+
const otherSystemUsers = await oauth.granularV2.getUsersBySystem('other-system-id');
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Token Operations
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// Save new tokens
|
|
176
|
+
const savedToken = await oauth.granularV2.saveToken({
|
|
177
|
+
userId: user.id,
|
|
178
|
+
provider: 'google',
|
|
179
|
+
email: 'user@example.com',
|
|
180
|
+
token: oauthToken,
|
|
181
|
+
// systemId and scopeId are optional
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Query tokens with flexible parameters
|
|
185
|
+
const userTokens = await oauth.granularV2.getTokens({
|
|
186
|
+
userId: user.id,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const providerTokens = await oauth.granularV2.getTokens({
|
|
190
|
+
userId: user.id,
|
|
191
|
+
provider: 'google',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const emailTokens = await oauth.granularV2.getTokens({
|
|
195
|
+
email: 'user@example.com',
|
|
196
|
+
// systemId resolved from context or defaults
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Get valid tokens with auto-refresh
|
|
200
|
+
const validToken = await oauth.granularV2.getValidToken({
|
|
201
|
+
email: 'user@example.com',
|
|
202
|
+
provider: 'google',
|
|
203
|
+
options: {
|
|
204
|
+
autoRefresh: true,
|
|
205
|
+
refreshBuffer: 5, // Refresh 5 minutes before expiry
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Get access token directly
|
|
210
|
+
const accessToken = await oauth.granularV2.getAccessToken({
|
|
211
|
+
userId: user.id,
|
|
212
|
+
provider: 'google',
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Execute with valid token
|
|
216
|
+
await oauth.granularV2.withValidToken(
|
|
217
|
+
{
|
|
218
|
+
email: 'user@example.com',
|
|
219
|
+
provider: 'google',
|
|
99
220
|
},
|
|
221
|
+
async (accessToken) => {
|
|
222
|
+
// Make API calls with the token
|
|
223
|
+
const response = await fetch('https://api.example.com/data', {
|
|
224
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
225
|
+
});
|
|
226
|
+
return response.json();
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Bulk Operations
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// Get all valid tokens for a user
|
|
235
|
+
const allUserTokens = await oauth.granularV2.getAllValidTokens({
|
|
236
|
+
userId: user.id,
|
|
237
|
+
options: { autoRefresh: true },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Get all valid tokens for an email
|
|
241
|
+
const allEmailTokens = await oauth.granularV2.getAllValidTokens({
|
|
242
|
+
email: 'user@example.com',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Filter by provider
|
|
246
|
+
const googleTokens = await oauth.granularV2.getAllValidTokens({
|
|
247
|
+
email: 'user@example.com',
|
|
248
|
+
provider: 'google',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Check token existence
|
|
252
|
+
const hasToken = await oauth.granularV2.hasToken({
|
|
253
|
+
email: 'user@example.com',
|
|
254
|
+
provider: 'google',
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Delete tokens
|
|
258
|
+
await oauth.granularV2.deleteTokens({
|
|
259
|
+
email: 'user@example.com',
|
|
260
|
+
provider: 'google',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Delete all tokens for a user
|
|
264
|
+
await oauth.granularV2.deleteTokens({
|
|
265
|
+
userId: user.id,
|
|
100
266
|
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### System & Scope Management
|
|
101
270
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
271
|
+
```typescript
|
|
272
|
+
// Get current defaults
|
|
273
|
+
const defaultSystem = await oauth.granularV2.getSystem();
|
|
274
|
+
const defaultScope = await oauth.granularV2.getScope();
|
|
275
|
+
|
|
276
|
+
// Create new scope in current/default system
|
|
277
|
+
const newScope = await oauth.granularV2.createScope({
|
|
278
|
+
name: 'api-access',
|
|
105
279
|
type: 'access',
|
|
106
|
-
permissions: ['read
|
|
280
|
+
permissions: ['read', 'write'],
|
|
107
281
|
isolated: true,
|
|
108
282
|
});
|
|
283
|
+
|
|
284
|
+
// Create scope in specific system
|
|
285
|
+
const anotherScope = await oauth.granularV2.createScope({
|
|
286
|
+
systemId: 'specific-system-id',
|
|
287
|
+
name: 'admin-access',
|
|
288
|
+
type: 'access',
|
|
289
|
+
permissions: ['*'],
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Get all scopes in current system
|
|
293
|
+
const scopes = await oauth.granularV2.getScopesBySystem();
|
|
294
|
+
|
|
295
|
+
// Initialize defaults explicitly if needed
|
|
296
|
+
const { system, scope } = await oauth.granularV2.ensureDefaults();
|
|
109
297
|
```
|
|
110
298
|
|
|
299
|
+
### Why Use Granular API?
|
|
300
|
+
|
|
301
|
+
The granular API is ideal for:
|
|
302
|
+
|
|
303
|
+
- **Single-system applications** - Simplified usage with automatic defaults
|
|
304
|
+
- **Multi-tenant applications** - Easy context switching between systems
|
|
305
|
+
- **Backend services** - Stateless operations with explicit parameters
|
|
306
|
+
- **Complex token management** - Full control over token lifecycle
|
|
307
|
+
- **Gradual migration** - Start simple, add complexity as needed
|
|
308
|
+
|
|
111
309
|
## ๐๏ธ Architecture
|
|
112
310
|
|
|
113
311
|
### Core Components
|
|
@@ -116,8 +314,8 @@ const scope = await oauth.createScope('api-access', {
|
|
|
116
314
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
117
315
|
โ OAuth2Client โ
|
|
118
316
|
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
119
|
-
โ โ Context API โ โ
|
|
120
|
-
โ โ (Simplified) โ โ (Full Control)
|
|
317
|
+
โ โ Context API โ โ Granular API V2 โ โ
|
|
318
|
+
โ โ (Simplified) โ โ (Full Control + Smart) โ โ
|
|
121
319
|
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
122
320
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
123
321
|
โ
|
|
@@ -181,19 +379,67 @@ interface UserToken {
|
|
|
181
379
|
5. **Email Uniqueness**: For the same provider, a user cannot have multiple tokens with the same email (validated via profile fetcher)
|
|
182
380
|
6. **Cross-Provider Emails**: The same email can exist across different providers
|
|
183
381
|
|
|
382
|
+
### Default System and Scope
|
|
383
|
+
|
|
384
|
+
The library provides automatic initialization of a default system and scope for simplified usage:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import {
|
|
388
|
+
DEFAULT_SYSTEM_NAME,
|
|
389
|
+
DEFAULT_SCOPE_NAME,
|
|
390
|
+
initializeDefaults,
|
|
391
|
+
} from '@dainprotocol/oauth2-token-manager';
|
|
392
|
+
|
|
393
|
+
// Automatic initialization
|
|
394
|
+
const oauth = new OAuth2Client({
|
|
395
|
+
providers: {
|
|
396
|
+
/* ... */
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// If no system/scope is set, they will be automatically initialized on first use
|
|
401
|
+
const { url, state } = await oauth.authorize({ provider: 'google' });
|
|
402
|
+
|
|
403
|
+
// Or manually initialize defaults
|
|
404
|
+
await oauth.initializeDefaults();
|
|
405
|
+
|
|
406
|
+
// Check if defaults exist
|
|
407
|
+
const defaultSystem = await oauth.getDefaultSystem();
|
|
408
|
+
const defaultScope = await oauth.getDefaultScope();
|
|
409
|
+
|
|
410
|
+
// Default constants
|
|
411
|
+
console.log(DEFAULT_SYSTEM_NAME); // 'oauth2-token-manager-default-system'
|
|
412
|
+
console.log(DEFAULT_SCOPE_NAME); // 'oauth2-token-manager-default-scope'
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The default system and scope are created with:
|
|
416
|
+
|
|
417
|
+
- **System**: Name-based identification, includes metadata `{ isDefault: true }`
|
|
418
|
+
- **Scope**: Type 'access', permissions ['read', 'write'], not isolated
|
|
419
|
+
|
|
184
420
|
## ๐ API Reference
|
|
185
421
|
|
|
186
422
|
### OAuth2Client
|
|
187
423
|
|
|
188
424
|
The main client class providing both context-managed and granular APIs.
|
|
189
425
|
|
|
190
|
-
#### Context-Managed API (Recommended)
|
|
426
|
+
#### Context-Managed API (Recommended for Simple Use Cases)
|
|
191
427
|
|
|
192
428
|
```typescript
|
|
193
429
|
// System management
|
|
194
430
|
await oauth.createSystem('MyApp');
|
|
431
|
+
await oauth.getOrCreateSystem('MyApp'); // Idempotent - returns existing if found
|
|
195
432
|
await oauth.useSystem(systemId);
|
|
196
433
|
|
|
434
|
+
// Scope management
|
|
435
|
+
await oauth.createScope('api-access');
|
|
436
|
+
await oauth.getOrCreateScope('api-access'); // Idempotent - returns existing if found
|
|
437
|
+
|
|
438
|
+
// Default system/scope (automatic initialization)
|
|
439
|
+
await oauth.initializeDefaults(); // Creates default system and scope if they don't exist
|
|
440
|
+
const defaultSystem = await oauth.getDefaultSystem();
|
|
441
|
+
const defaultScope = await oauth.getDefaultScope();
|
|
442
|
+
|
|
197
443
|
// User management
|
|
198
444
|
const user = await oauth.getOrCreateUser({ email: 'user@example.com' });
|
|
199
445
|
await oauth.useUser(userId);
|
|
@@ -218,89 +464,6 @@ const allTokens = await oauth.getAllValidTokensForUser(userId);
|
|
|
218
464
|
await oauth.revokeTokens('google'); // Revokes for current user/scope/provider
|
|
219
465
|
```
|
|
220
466
|
|
|
221
|
-
#### Granular API (Advanced)
|
|
222
|
-
|
|
223
|
-
The granular API provides full control over the token hierarchy:
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
// === User-Centric Token Queries (Primary Key: User) ===
|
|
227
|
-
|
|
228
|
-
// Get ALL tokens for a user across all scopes/providers
|
|
229
|
-
const userTokens = await oauth.granular.getTokensByUser(userId);
|
|
230
|
-
|
|
231
|
-
// Get tokens for user in specific scope (across all providers)
|
|
232
|
-
const scopeTokens = await oauth.granular.getTokensByUserAndScope(userId, scopeId);
|
|
233
|
-
|
|
234
|
-
// Get tokens for user with specific provider (across all scopes)
|
|
235
|
-
const providerTokens = await oauth.granular.getTokensByUserAndProvider(userId, 'google');
|
|
236
|
-
|
|
237
|
-
// Get tokens for user/scope/provider combination (can be multiple!)
|
|
238
|
-
const specificTokens = await oauth.granular.getTokensByUserScopeProvider(userId, scopeId, 'google');
|
|
239
|
-
|
|
240
|
-
// === Cross-User Queries (System/Scope Level) ===
|
|
241
|
-
|
|
242
|
-
// Get all tokens in a scope across all users
|
|
243
|
-
const scopeAllTokens = await oauth.granular.getTokensByScope(systemId, scopeId);
|
|
244
|
-
|
|
245
|
-
// Get all tokens for a provider across all users in system
|
|
246
|
-
const providerAllTokens = await oauth.granular.getTokensByProvider(systemId, 'google');
|
|
247
|
-
|
|
248
|
-
// Get all tokens in a system
|
|
249
|
-
const systemTokens = await oauth.granular.getTokensBySystem(systemId);
|
|
250
|
-
|
|
251
|
-
// === Email-Based Queries ===
|
|
252
|
-
|
|
253
|
-
// Find tokens by email (cross-user, cross-provider)
|
|
254
|
-
const emailTokens = await oauth.granular.findTokensByEmail('user@example.com', systemId);
|
|
255
|
-
|
|
256
|
-
// Find tokens by email in specific scope
|
|
257
|
-
const emailScopeTokens = await oauth.granular.findTokensByEmailAndScope(
|
|
258
|
-
'user@example.com',
|
|
259
|
-
systemId,
|
|
260
|
-
scopeId,
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
// Find tokens by email for specific provider
|
|
264
|
-
const emailProviderTokens = await oauth.granular.findTokensByEmailAndProvider(
|
|
265
|
-
'user@example.com',
|
|
266
|
-
systemId,
|
|
267
|
-
'google',
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
// Find specific token by email/scope/provider (returns single token or null)
|
|
271
|
-
const specificToken = await oauth.granular.findTokenByEmailScopeProvider(
|
|
272
|
-
'user@example.com',
|
|
273
|
-
systemId,
|
|
274
|
-
scopeId,
|
|
275
|
-
'google',
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
// === Token Operations ===
|
|
279
|
-
|
|
280
|
-
// Get valid token for user (auto-refresh, takes first if multiple exist)
|
|
281
|
-
const validToken = await oauth.granular.getValidTokenForUser(userId, scopeId, 'google');
|
|
282
|
-
|
|
283
|
-
// Get access token for user (convenience method)
|
|
284
|
-
const accessToken = await oauth.granular.getAccessTokenForUser(userId, scopeId, 'google');
|
|
285
|
-
|
|
286
|
-
// Save new token for user
|
|
287
|
-
const savedToken = await oauth.granular.saveTokenForUser(
|
|
288
|
-
userId,
|
|
289
|
-
systemId,
|
|
290
|
-
scopeId,
|
|
291
|
-
'google',
|
|
292
|
-
'user@example.com',
|
|
293
|
-
oauthToken,
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
// === Token Management ===
|
|
297
|
-
|
|
298
|
-
// Delete tokens by different criteria
|
|
299
|
-
await oauth.granular.deleteTokensByUser(userId); // All tokens for user
|
|
300
|
-
await oauth.granular.deleteTokensByUserAndScope(userId, scopeId); // User's tokens in scope
|
|
301
|
-
await oauth.granular.deleteTokensByUserAndProvider(userId, 'google'); // User's tokens for provider
|
|
302
|
-
```
|
|
303
|
-
|
|
304
467
|
### Storage Adapters
|
|
305
468
|
|
|
306
469
|
#### Built-in Memory Adapter
|
|
@@ -327,18 +490,77 @@ const storage = await PostgresStorageFactory.create({
|
|
|
327
490
|
});
|
|
328
491
|
```
|
|
329
492
|
|
|
493
|
+
#### Drizzle Adapter (Multi-Database)
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
import { DrizzleStorageFactory } from '@dainprotocol/oauth2-storage-drizzle';
|
|
497
|
+
|
|
498
|
+
// PostgreSQL
|
|
499
|
+
const pgStorage = await DrizzleStorageFactory.create({
|
|
500
|
+
type: 'postgresql',
|
|
501
|
+
config: {
|
|
502
|
+
host: 'localhost',
|
|
503
|
+
port: 5432,
|
|
504
|
+
user: 'oauth2_user',
|
|
505
|
+
password: 'password',
|
|
506
|
+
database: 'oauth2_db',
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// MySQL
|
|
511
|
+
const mysqlStorage = await DrizzleStorageFactory.create({
|
|
512
|
+
type: 'mysql',
|
|
513
|
+
config: {
|
|
514
|
+
host: 'localhost',
|
|
515
|
+
port: 3306,
|
|
516
|
+
user: 'oauth2_user',
|
|
517
|
+
password: 'password',
|
|
518
|
+
database: 'oauth2_db',
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// SQLite
|
|
523
|
+
const sqliteStorage = await DrizzleStorageFactory.create({
|
|
524
|
+
type: 'sqlite',
|
|
525
|
+
config: {
|
|
526
|
+
filename: './oauth2.db',
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
330
531
|
#### Custom Storage Adapter
|
|
331
532
|
|
|
332
533
|
```typescript
|
|
333
534
|
import { StorageAdapter } from '@dainprotocol/oauth2-token-manager';
|
|
334
535
|
|
|
335
536
|
class MyCustomAdapter implements StorageAdapter {
|
|
537
|
+
// System operations
|
|
336
538
|
async createSystem(system) {
|
|
337
539
|
/* implement */
|
|
338
540
|
}
|
|
541
|
+
async getOrCreateSystem(system) {
|
|
542
|
+
// Find by name, create if not exists
|
|
543
|
+
/* implement */
|
|
544
|
+
}
|
|
339
545
|
async getSystem(id) {
|
|
340
546
|
/* implement */
|
|
341
547
|
}
|
|
548
|
+
|
|
549
|
+
// Scope operations
|
|
550
|
+
async createScope(scope) {
|
|
551
|
+
/* implement */
|
|
552
|
+
}
|
|
553
|
+
async getOrCreateScope(scope) {
|
|
554
|
+
// Find by systemId + name, create if not exists
|
|
555
|
+
/* implement */
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// User operations
|
|
559
|
+
async getOrCreateUser(input) {
|
|
560
|
+
// Find by email or externalId, create if not exists
|
|
561
|
+
/* implement */
|
|
562
|
+
}
|
|
563
|
+
|
|
342
564
|
// ... implement all required methods
|
|
343
565
|
}
|
|
344
566
|
```
|
|
@@ -398,64 +620,6 @@ class MyCustomAdapter implements StorageAdapter {
|
|
|
398
620
|
}
|
|
399
621
|
```
|
|
400
622
|
|
|
401
|
-
#### Google OAuth2 with Offline Access
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
const oauth = new OAuth2Client({
|
|
405
|
-
providers: {
|
|
406
|
-
google: {
|
|
407
|
-
clientId: 'your-client-id',
|
|
408
|
-
clientSecret: 'your-client-secret',
|
|
409
|
-
redirectUri: 'http://localhost:3000/auth/callback',
|
|
410
|
-
scopes: ['profile', 'email'],
|
|
411
|
-
// Override default offline access parameters
|
|
412
|
-
extraAuthParams: {
|
|
413
|
-
access_type: 'offline', // Request refresh token
|
|
414
|
-
prompt: 'consent', // Force consent screen
|
|
415
|
-
include_granted_scopes: 'true' // Include previously granted scopes
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// The library automatically handles refresh tokens
|
|
422
|
-
const token = await oauth.getAccessToken('google', {
|
|
423
|
-
autoRefresh: true,
|
|
424
|
-
refreshBuffer: 5 // Refresh 5 minutes before expiry
|
|
425
|
-
});
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
#### Customizing Authorization Parameters
|
|
429
|
-
|
|
430
|
-
Each provider supports customization through `extraAuthParams` and `additionalParams`:
|
|
431
|
-
|
|
432
|
-
```typescript
|
|
433
|
-
{
|
|
434
|
-
google: {
|
|
435
|
-
// ... other config ...
|
|
436
|
-
extraAuthParams: {
|
|
437
|
-
access_type: 'offline', // For refresh tokens
|
|
438
|
-
prompt: 'select_account', // Force account selection
|
|
439
|
-
hd: 'yourdomain.com' // Limit to specific Google Workspace domain
|
|
440
|
-
}
|
|
441
|
-
},
|
|
442
|
-
microsoft: {
|
|
443
|
-
// ... other config ...
|
|
444
|
-
extraAuthParams: {
|
|
445
|
-
prompt: 'select_account',
|
|
446
|
-
domain_hint: 'yourdomain.com'
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
Available parameters for Google OAuth2:
|
|
453
|
-
- `access_type`: 'online' (default) or 'offline' (for refresh tokens)
|
|
454
|
-
- `prompt`: 'none', 'consent', 'select_account'
|
|
455
|
-
- `include_granted_scopes`: 'true' or 'false'
|
|
456
|
-
- `login_hint`: User's email address
|
|
457
|
-
- `hd`: Google Workspace domain restriction
|
|
458
|
-
|
|
459
623
|
## ๐ง Advanced Features
|
|
460
624
|
|
|
461
625
|
### Token Auto-Refresh
|
|
@@ -463,7 +627,7 @@ Available parameters for Google OAuth2:
|
|
|
463
627
|
```typescript
|
|
464
628
|
const accessToken = await oauth.getAccessToken('google', {
|
|
465
629
|
autoRefresh: true,
|
|
466
|
-
refreshBuffer:
|
|
630
|
+
refreshBuffer: 10, // Refresh early to avoid expiry
|
|
467
631
|
expirationBuffer: 30, // Consider expired 30 seconds early
|
|
468
632
|
});
|
|
469
633
|
```
|
|
@@ -480,86 +644,6 @@ const result = await oauth.handleCallback(code, state, {
|
|
|
480
644
|
});
|
|
481
645
|
```
|
|
482
646
|
|
|
483
|
-
### Email-Based Operations
|
|
484
|
-
|
|
485
|
-
```typescript
|
|
486
|
-
// Get all valid tokens for an email across all providers in a system
|
|
487
|
-
// Note: This returns an array since one email can have tokens from multiple providers
|
|
488
|
-
const emailTokens = await oauth.getAllValidTokensForEmail('user@example.com', systemId);
|
|
489
|
-
// Returns: { provider: string; scopeId: string; token: OAuth2Token; userToken: UserToken }[]
|
|
490
|
-
|
|
491
|
-
// Get specific token by email (returns single token or null)
|
|
492
|
-
// This enforces the email uniqueness rule within provider/scope
|
|
493
|
-
const token = await oauth.getTokenForEmail('user@example.com', systemId, scopeId, 'google');
|
|
494
|
-
|
|
495
|
-
// Get valid token for email with auto-refresh
|
|
496
|
-
const validToken = await oauth.getValidTokenForEmail(
|
|
497
|
-
'user@example.com',
|
|
498
|
-
systemId,
|
|
499
|
-
scopeId,
|
|
500
|
-
'google',
|
|
501
|
-
{ autoRefresh: true },
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
// Get access token for email
|
|
505
|
-
const accessToken = await oauth.getAccessTokenForEmail(
|
|
506
|
-
'user@example.com',
|
|
507
|
-
systemId,
|
|
508
|
-
scopeId,
|
|
509
|
-
'google',
|
|
510
|
-
);
|
|
511
|
-
|
|
512
|
-
// Execute with valid token for email
|
|
513
|
-
await oauth.withValidTokenForEmail(
|
|
514
|
-
'user@example.com',
|
|
515
|
-
systemId,
|
|
516
|
-
scopeId,
|
|
517
|
-
'google',
|
|
518
|
-
async (accessToken) => {
|
|
519
|
-
console.log('Using token for email:', accessToken);
|
|
520
|
-
},
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
// Check if email has token for specific provider/scope
|
|
524
|
-
const hasToken = await oauth.hasTokenForEmail('user@example.com', systemId, scopeId, 'google');
|
|
525
|
-
|
|
526
|
-
// Revoke tokens for email
|
|
527
|
-
await oauth.revokeTokensForEmail('user@example.com', systemId, scopeId, 'google');
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
### User-Centric Operations (Stateless)
|
|
531
|
-
|
|
532
|
-
For backend APIs where you have explicit user IDs:
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
// Get access token for specific user/scope/provider
|
|
536
|
-
// Note: Takes the first (most recent) token if multiple exist
|
|
537
|
-
const accessToken = await oauth.getAccessTokenForUser(userId, systemId, scopeId, 'google', {
|
|
538
|
-
autoRefresh: true,
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// Execute with valid token for specific user
|
|
542
|
-
await oauth.withValidTokenForUser(userId, systemId, scopeId, 'google', async (accessToken) => {
|
|
543
|
-
// Make API calls with the token
|
|
544
|
-
return apiResponse;
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
// Get all valid tokens for a user with auto-refresh
|
|
548
|
-
const userTokens = await oauth.getAllValidTokensForUser(userId, {
|
|
549
|
-
autoRefresh: true,
|
|
550
|
-
refreshBuffer: 5, // Refresh 5 minutes before expiry
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// Check if user has tokens for specific provider/scope
|
|
554
|
-
const hasToken = await oauth.hasTokenForUser(userId, systemId, scopeId, 'google');
|
|
555
|
-
|
|
556
|
-
// Get user token entity (includes metadata)
|
|
557
|
-
const userToken = await oauth.getUserTokenForUser(userId, systemId, scopeId, 'google');
|
|
558
|
-
|
|
559
|
-
// Revoke specific tokens
|
|
560
|
-
await oauth.revokeTokensForUser(userId, systemId, scopeId, 'google');
|
|
561
|
-
```
|
|
562
|
-
|
|
563
647
|
### PKCE Support
|
|
564
648
|
|
|
565
649
|
```typescript
|
|
@@ -582,52 +666,25 @@ const isExpired = oauth.isTokenExpired(token, {
|
|
|
582
666
|
const validToken = await oauth.ensureValidToken('google');
|
|
583
667
|
```
|
|
584
668
|
|
|
585
|
-
##
|
|
586
|
-
|
|
587
|
-
### State Management
|
|
588
|
-
|
|
589
|
-
- Cryptographically secure state generation
|
|
590
|
-
- Automatic state validation and cleanup
|
|
591
|
-
- Configurable state expiration
|
|
592
|
-
|
|
593
|
-
### PKCE (Proof Key for Code Exchange)
|
|
594
|
-
|
|
595
|
-
- Built-in PKCE support for public clients
|
|
596
|
-
- Automatic code verifier generation
|
|
597
|
-
- Enhanced security for mobile and SPA applications
|
|
598
|
-
|
|
599
|
-
### Token Encryption
|
|
600
|
-
|
|
601
|
-
- Secure token storage with optional encryption
|
|
602
|
-
- Configurable seal keys for sensitive data
|
|
603
|
-
- Protection against token theft
|
|
604
|
-
|
|
605
|
-
### Email Validation
|
|
606
|
-
|
|
607
|
-
- Automatic email conflict detection
|
|
608
|
-
- Profile-based user validation
|
|
609
|
-
- Cross-provider email consistency
|
|
610
|
-
|
|
611
|
-
## ๐งช Testing
|
|
612
|
-
|
|
613
|
-
The library includes comprehensive tests using Vitest:
|
|
669
|
+
## ๐ข Examples
|
|
614
670
|
|
|
615
|
-
|
|
616
|
-
# Run tests
|
|
617
|
-
npm test
|
|
671
|
+
### Multi-Tenant Application
|
|
618
672
|
|
|
619
|
-
|
|
620
|
-
|
|
673
|
+
```typescript
|
|
674
|
+
// Each tenant gets their own system
|
|
675
|
+
const tenantSystem = await oauth.createSystem(`Tenant-${tenantId}`);
|
|
621
676
|
|
|
622
|
-
|
|
623
|
-
|
|
677
|
+
// Tenant-specific user management
|
|
678
|
+
await oauth.useSystem(tenantSystem.id);
|
|
679
|
+
const tenantUser = await oauth.getOrCreateUser({
|
|
680
|
+
email: userEmail,
|
|
681
|
+
metadata: { tenantId, role: 'admin' },
|
|
682
|
+
});
|
|
624
683
|
|
|
625
|
-
|
|
626
|
-
|
|
684
|
+
// Tenant-isolated tokens
|
|
685
|
+
const tokens = await oauth.granularV2.getTokens({ systemId: tenantSystem.id });
|
|
627
686
|
```
|
|
628
687
|
|
|
629
|
-
## ๐ข Multi-System Examples
|
|
630
|
-
|
|
631
688
|
### SaaS Platform with Multiple Apps
|
|
632
689
|
|
|
633
690
|
```typescript
|
|
@@ -659,23 +716,6 @@ const { url } = await oauth.authorize({
|
|
|
659
716
|
});
|
|
660
717
|
```
|
|
661
718
|
|
|
662
|
-
### Multi-Tenant Application
|
|
663
|
-
|
|
664
|
-
```typescript
|
|
665
|
-
// Each tenant gets their own system
|
|
666
|
-
const tenantSystem = await oauth.createSystem(`Tenant-${tenantId}`);
|
|
667
|
-
|
|
668
|
-
// Tenant-specific user management
|
|
669
|
-
await oauth.useSystem(tenantSystem.id);
|
|
670
|
-
const tenantUser = await oauth.getOrCreateUser({
|
|
671
|
-
email: userEmail,
|
|
672
|
-
metadata: { tenantId, role: 'admin' },
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
// Tenant-isolated tokens
|
|
676
|
-
const tokens = await oauth.granular.getTokensBySystem(tenantSystem.id);
|
|
677
|
-
```
|
|
678
|
-
|
|
679
719
|
## ๐ Production Deployment
|
|
680
720
|
|
|
681
721
|
### Environment Configuration
|
|
@@ -743,6 +783,50 @@ try {
|
|
|
743
783
|
}
|
|
744
784
|
```
|
|
745
785
|
|
|
786
|
+
## ๐ Security Features
|
|
787
|
+
|
|
788
|
+
### State Management
|
|
789
|
+
|
|
790
|
+
- Cryptographically secure state generation
|
|
791
|
+
- Automatic state validation and cleanup
|
|
792
|
+
- Configurable state expiration
|
|
793
|
+
|
|
794
|
+
### PKCE (Proof Key for Code Exchange)
|
|
795
|
+
|
|
796
|
+
- Built-in PKCE support for public clients
|
|
797
|
+
- Automatic code verifier generation
|
|
798
|
+
- Enhanced security for mobile and SPA applications
|
|
799
|
+
|
|
800
|
+
### Token Encryption
|
|
801
|
+
|
|
802
|
+
- Secure token storage with optional encryption
|
|
803
|
+
- Configurable seal keys for sensitive data
|
|
804
|
+
- Protection against token theft
|
|
805
|
+
|
|
806
|
+
### Email Validation
|
|
807
|
+
|
|
808
|
+
- Automatic email conflict detection
|
|
809
|
+
- Profile-based user validation
|
|
810
|
+
- Cross-provider email consistency
|
|
811
|
+
|
|
812
|
+
## ๐งช Testing
|
|
813
|
+
|
|
814
|
+
The library includes comprehensive tests using Vitest:
|
|
815
|
+
|
|
816
|
+
```bash
|
|
817
|
+
# Run tests
|
|
818
|
+
npm test
|
|
819
|
+
|
|
820
|
+
# Run tests with UI
|
|
821
|
+
npm run test:ui
|
|
822
|
+
|
|
823
|
+
# Run tests with coverage
|
|
824
|
+
npm run test:coverage
|
|
825
|
+
|
|
826
|
+
# Watch mode
|
|
827
|
+
npm run test:watch
|
|
828
|
+
```
|
|
829
|
+
|
|
746
830
|
## ๐ค Contributing
|
|
747
831
|
|
|
748
832
|
1. Fork the repository
|
|
@@ -777,10 +861,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
777
861
|
|
|
778
862
|
## ๐โโ๏ธ Support
|
|
779
863
|
|
|
780
|
-
- ๐ [Documentation](https://github.com/
|
|
781
|
-
- ๐ [Issue Tracker](https://github.com/
|
|
782
|
-
- ๐ฌ [Discussions](https://github.com/
|
|
783
|
-
|
|
784
|
-
## ๐ Credits
|
|
785
|
-
|
|
786
|
-
Created with โค๏ธ by [Blureffect](https://blureffect.co)
|
|
864
|
+
- ๐ [Documentation](https://github.com/dainprotocol/oauth2-token-manager#readme)
|
|
865
|
+
- ๐ [Issue Tracker](https://github.com/dainprotocol/oauth2-token-manager/issues)
|
|
866
|
+
- ๐ฌ [Discussions](https://github.com/dainprotocol/oauth2-token-manager/discussions)
|