@eaccess/auth 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.
package/README.md ADDED
@@ -0,0 +1,877 @@
1
+ # @prsm/easy-auth
2
+
3
+ An Express authentication middleware specifically designed for Postgres that provides complete authentication functionality without being tied to any specific ORM, query builder, or user table structure. Comprehensive auth without overwhelming complexity. A clean separation of concerns -- not conflating authentication with user management.
4
+
5
+ ## Features
6
+
7
+ - **Flexible User Mapping**: Links to your existing user table structure
8
+ - **Zero ORM Dependencies**: Pure SQL with configurable table prefixes
9
+ - **Complete Auth Flow**: Registration, login, email verification, password reset
10
+ - **Role-based Permissions**: Built-in role system with bitmasks
11
+ - **Remember Me**: Persistent login tokens
12
+ - **Session Management**: Force logout, logout everywhere
13
+ - **Admin Functions**: User management and impersonation
14
+ - **OAuth Integration**: GitHub, Google, Azure providers with extensible architecture
15
+ - **TypeScript Support**: Full type safety
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @prsm/easy-auth express-session
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import express from 'express';
27
+ import session from 'express-session';
28
+ import { Pool } from 'pg';
29
+ import { createAuthMiddleware, createAuthTables } from '@prsm/easy-auth';
30
+
31
+ const app = express();
32
+ const pool = new Pool({ connectionString: 'postgresql://...' });
33
+
34
+ // Setup session middleware
35
+ app.use(session({
36
+ secret: 'your-session-secret',
37
+ resave: false,
38
+ saveUninitialized: false,
39
+ }));
40
+
41
+ // Configure auth middleware
42
+ const authConfig = {
43
+ db: pool,
44
+ tablePrefix: 'auth_', // Creates: auth_accounts, auth_confirmations, etc.
45
+ };
46
+
47
+ // Create auth tables (run once)
48
+ await createAuthTables(authConfig);
49
+
50
+ // Add auth middleware
51
+ app.use(createAuthMiddleware(authConfig));
52
+
53
+ // Now use auth in your routes
54
+ app.post('/register', async (req, res) => {
55
+ try {
56
+ // Option 1: Let the library auto-generate a UUID for the user
57
+ const account = await req.auth.register(
58
+ req.body.email,
59
+ req.body.password,
60
+ undefined, // Auto-generates UUID
61
+ (token) => {
62
+ // Send confirmation email with token
63
+ console.log('Confirmation token:', token);
64
+ }
65
+ );
66
+
67
+ // Option 2: Link to your existing user system
68
+ // const user = await db.insert(users).values({...}).returning();
69
+ // const account = await req.auth.register(
70
+ // req.body.email,
71
+ // req.body.password,
72
+ // user.id, // Link to your user
73
+ // (token) => {
74
+ // console.log('Confirmation token:', token);
75
+ // }
76
+ // );
77
+ res.json({ success: true, account });
78
+ } catch (error) {
79
+ res.status(400).json({ error: error.message });
80
+ }
81
+ });
82
+
83
+ app.post('/login', async (req, res) => {
84
+ try {
85
+ await req.auth.login(req.body.email, req.body.password, req.body.remember);
86
+ res.json({ success: true });
87
+ } catch (error) {
88
+ res.status(401).json({ error: error.message });
89
+ }
90
+ });
91
+
92
+ app.get('/profile', (req, res) => {
93
+ if (!req.auth.isLoggedIn()) {
94
+ return res.status(401).json({ error: 'Not logged in' });
95
+ }
96
+
97
+ res.json({
98
+ email: req.auth.getEmail(),
99
+ status: req.auth.getStatusName(),
100
+ roles: req.auth.getRoleNames(),
101
+ isAdmin: await req.auth.isAdmin(),
102
+ });
103
+ });
104
+ ```
105
+
106
+ ## OAuth Setup
107
+
108
+ Easy-auth supports OAuth providers (GitHub, Google, Azure) with a clean, extensible API.
109
+
110
+ ### OAuth Configuration
111
+
112
+ ```typescript
113
+ import express from 'express';
114
+ import session from 'express-session';
115
+ import { Pool } from 'pg';
116
+ import { createAuthMiddleware, createAuthTables, type OAuthUserData } from '@prsm/easy-auth';
117
+
118
+ const app = express();
119
+ const pool = new Pool({ connectionString: 'postgresql://...' });
120
+
121
+ // Your app's user table (example)
122
+ const users: Array<{ id: number; name: string; email: string }> = [];
123
+
124
+ const authConfig = {
125
+ db: pool,
126
+ // Optional: OAuth createUser function to handle new user registration
127
+ createUser: async (userData: OAuthUserData) => {
128
+ // userData contains: { id, email, username?, name?, avatar? }
129
+ // Create user in your app's user table
130
+ const user = await db.insert(users).values({
131
+ name: userData.name || userData.username,
132
+ email: userData.email,
133
+ }).returning();
134
+
135
+ return user.id; // Return the new user's ID
136
+ },
137
+ tablePrefix: 'auth_',
138
+
139
+ // OAuth provider configuration
140
+ providers: {
141
+ github: {
142
+ clientId: process.env.GITHUB_CLIENT_ID!,
143
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
144
+ redirectUri: 'http://localhost:3000/auth/github/callback'
145
+ },
146
+ google: {
147
+ clientId: process.env.GOOGLE_CLIENT_ID!,
148
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
149
+ redirectUri: 'http://localhost:3000/auth/google/callback'
150
+ },
151
+ azure: {
152
+ clientId: process.env.AZURE_CLIENT_ID!,
153
+ clientSecret: process.env.AZURE_CLIENT_SECRET!,
154
+ tenantId: process.env.AZURE_TENANT_ID!,
155
+ redirectUri: 'http://localhost:3000/auth/azure/callback'
156
+ }
157
+ }
158
+ };
159
+
160
+ app.use(createAuthMiddleware(authConfig));
161
+ ```
162
+
163
+ ### OAuth Routes
164
+
165
+ ```typescript
166
+ // Initiate OAuth flow
167
+ app.get('/auth/github', (req, res) => {
168
+ const authUrl = req.auth.providers.github.getAuthUrl();
169
+ res.redirect(authUrl);
170
+ });
171
+
172
+ // Handle OAuth callback (this does everything!)
173
+ app.get('/auth/github/callback', async (req, res) => {
174
+ try {
175
+ await req.auth.providers.github.handleCallback(req);
176
+ res.redirect('/dashboard'); // Success!
177
+ } catch (error) {
178
+ if (error.message.includes('already have an account')) {
179
+ res.redirect('/login?error=email_taken');
180
+ } else {
181
+ res.redirect('/login?error=oauth_failed');
182
+ }
183
+ }
184
+ });
185
+
186
+ // Same pattern for Google and Azure
187
+ app.get('/auth/google', (req, res) => {
188
+ const authUrl = req.auth.providers.google.getAuthUrl();
189
+ res.redirect(authUrl);
190
+ });
191
+
192
+ app.get('/auth/google/callback', async (req, res) => {
193
+ try {
194
+ await req.auth.providers.google.handleCallback(req);
195
+ res.redirect('/dashboard');
196
+ } catch (error) {
197
+ res.redirect('/login?error=oauth_failed');
198
+ }
199
+ });
200
+ ```
201
+
202
+ ### Frontend Integration
203
+
204
+ ```html
205
+ <!-- Login page -->
206
+ <a href="/auth/github" class="oauth-btn">
207
+ <img src="/github-icon.svg" /> Login with GitHub
208
+ </a>
209
+ <a href="/auth/google" class="oauth-btn">
210
+ <img src="/google-icon.svg" /> Login with Google
211
+ </a>
212
+ <a href="/auth/azure" class="oauth-btn">
213
+ <img src="/azure-icon.svg" /> Login with Azure
214
+ </a>
215
+ ```
216
+
217
+ ### OAuth Flow Explained
218
+
219
+ 1. **User clicks "Login with GitHub"** → Browser goes to `/auth/github`
220
+ 2. **Server redirects to GitHub** → User sees GitHub's login page
221
+ 3. **User authorizes your app** → GitHub redirects to `/auth/github/callback?code=abc123`
222
+ 4. **Server processes callback** → `handleCallback()` does:
223
+ - Exchange code for access token
224
+ - Fetch user data from GitHub API
225
+ - Check if OAuth user exists (by provider + provider_id)
226
+ - If exists: log them in
227
+ - If new but email exists: throw error
228
+ - If completely new: call `createUser()`, create account + provider record, log them in
229
+
230
+ ### OAuth Error Handling
231
+
232
+ ```typescript
233
+ app.get('/auth/github/callback', async (req, res) => {
234
+ try {
235
+ await req.auth.providers.github.handleCallback(req);
236
+ res.redirect('/dashboard');
237
+ } catch (error) {
238
+ if (error.message.includes('already have an account')) {
239
+ // Email exists with different login method
240
+ res.redirect('/login?error=Please use your existing email/password login');
241
+ } else if (error.message.includes('No authorization code')) {
242
+ // User cancelled or OAuth flow failed
243
+ res.redirect('/login?error=Authorization cancelled');
244
+ } else {
245
+ // Other OAuth errors
246
+ console.error('OAuth error:', error);
247
+ res.redirect('/login?error=Login failed, please try again');
248
+ }
249
+ }
250
+ });
251
+ ```
252
+
253
+ ### Environment Variables
254
+
255
+ Create a `.env` file:
256
+
257
+ ```bash
258
+ # GitHub OAuth App (https://github.com/settings/developers)
259
+ GITHUB_CLIENT_ID=your_github_client_id
260
+ GITHUB_CLIENT_SECRET=your_github_client_secret
261
+
262
+ # Google OAuth App (https://console.cloud.google.com/)
263
+ GOOGLE_CLIENT_ID=your_google_client_id.apps.googleusercontent.com
264
+ GOOGLE_CLIENT_SECRET=your_google_client_secret
265
+
266
+ # Azure OAuth App (https://portal.azure.com/)
267
+ AZURE_CLIENT_ID=your_azure_client_id
268
+ AZURE_CLIENT_SECRET=your_azure_client_secret
269
+ AZURE_TENANT_ID=your_azure_tenant_id
270
+ ```
271
+
272
+ ### Advanced OAuth Usage
273
+
274
+ For more control over the OAuth flow:
275
+
276
+ ```typescript
277
+ app.get('/auth/github/callback', async (req, res) => {
278
+ try {
279
+ // Get user data without logging in
280
+ const userData = await req.auth.providers.github.getUserData(req);
281
+
282
+ // Your custom logic here
283
+ const existingUser = await findUserByEmail(userData.email);
284
+ if (existingUser && !existingUser.allowOAuth) {
285
+ throw new Error('OAuth disabled for this account');
286
+ }
287
+
288
+ // Then complete the OAuth flow manually
289
+ await req.auth.providers.github.handleCallback(req);
290
+
291
+ res.json({ success: true, user: userData });
292
+ } catch (error) {
293
+ res.status(400).json({ error: error.message });
294
+ }
295
+ });
296
+ ```
297
+
298
+ ## Multi-Factor Authentication (MFA)
299
+
300
+ Easy-auth supports TOTP (authenticator apps), Email OTP, and SMS OTP for enhanced security.
301
+
302
+ ### MFA Configuration
303
+
304
+ Enable MFA in your auth config:
305
+
306
+ ```typescript
307
+ const authConfig = {
308
+ db: pool,
309
+
310
+ twoFactor: {
311
+ enabled: true,
312
+ requireForOAuth: false, // Skip MFA for OAuth users (optional)
313
+ issuer: 'MyApp', // TOTP issuer name
314
+ codeLength: 6, // OTP code length
315
+ tokenExpiry: '5m', // OTP expiration
316
+ totpWindow: 1, // TOTP time window tolerance
317
+ backupCodesCount: 10 // Number of backup codes
318
+ }
319
+ };
320
+ ```
321
+
322
+ ### MFA Login Flow
323
+
324
+ When MFA is enabled, the login process becomes:
325
+
326
+ ```typescript
327
+ app.post('/login', async (req, res) => {
328
+ try {
329
+ await req.auth.login(req.body.email, req.body.password);
330
+ res.json({ success: true });
331
+ } catch (error) {
332
+ if (error instanceof SecondFactorRequiredError) {
333
+ // User needs to complete MFA
334
+ return res.status(202).json({
335
+ requiresTwoFactor: true,
336
+ availableMethods: error.challenge,
337
+ message: 'Please complete two-factor authentication'
338
+ });
339
+ }
340
+ res.status(401).json({ error: error.message });
341
+ }
342
+ });
343
+ ```
344
+
345
+ ### MFA Challenge Structure
346
+
347
+ The `SecondFactorRequiredError.challenge` contains:
348
+
349
+ ```typescript
350
+ interface TwoFactorChallenge {
351
+ totp?: boolean; // TOTP available
352
+ email?: {
353
+ otpValue: string; // The actual OTP code that should be sent via email
354
+ maskedContact: string; // "j***@example.com"
355
+ };
356
+ sms?: {
357
+ otpValue: string; // The actual OTP code that should be sent via SMS
358
+ maskedContact: string; // "+1***90"
359
+ };
360
+ selectors?: {
361
+ email?: string; // Internal selector (stored in session & database)
362
+ sms?: string; // Internal selector (stored in session & database)
363
+ };
364
+ }
365
+ ```
366
+
367
+ **Important**: The `otpValue` fields contain the actual codes that should be delivered to the user. The `selectors` are internal identifiers used by the library. In production, you should:
368
+ 1. Send the `otpValue` codes via your email/SMS service
369
+ 2. Remove both `otpValue` and `selectors` from client responses for security
370
+ 3. Only return the `maskedContact` to the frontend (selectors are automatically stored in the user's session)
371
+
372
+ ### Completing MFA Login
373
+
374
+ After receiving `SecondFactorRequiredError`, verify the second factor:
375
+
376
+ ```typescript
377
+ app.post('/verify-2fa', async (req, res) => {
378
+ try {
379
+ const { code, method } = req.body;
380
+
381
+ // Verify based on method
382
+ switch (method) {
383
+ case 'totp':
384
+ await req.auth.twoFactor.verify.totp(code);
385
+ break;
386
+ case 'email':
387
+ await req.auth.twoFactor.verify.email(code);
388
+ break;
389
+ case 'sms':
390
+ await req.auth.twoFactor.verify.sms(code);
391
+ break;
392
+ case 'backup':
393
+ await req.auth.twoFactor.verify.backupCode(code);
394
+ break;
395
+ case 'otp':
396
+ // Smart OTP - works for both email and SMS
397
+ await req.auth.twoFactor.verify.otp(code);
398
+ break;
399
+ }
400
+
401
+ // Complete login
402
+ await req.auth.completeTwoFactorLogin();
403
+ res.json({ success: true });
404
+ } catch (error) {
405
+ res.status(400).json({ error: error.message });
406
+ }
407
+ });
408
+ ```
409
+
410
+ ### MFA Enrollment
411
+
412
+ Users can enroll in multiple MFA methods:
413
+
414
+ #### TOTP (Authenticator App)
415
+
416
+ ```typescript
417
+ app.post('/setup-totp', async (req, res) => {
418
+ try {
419
+ const { secret, qrCode, backupCodes } = await req.auth.twoFactor.setup.totp();
420
+
421
+ // Show QR code to user for scanning with authenticator app
422
+ res.json({
423
+ secret, // Manual entry secret
424
+ qrCode, // QR code URL for scanning
425
+ backupCodes // One-time backup codes
426
+ });
427
+ } catch (error) {
428
+ res.status(400).json({ error: error.message });
429
+ }
430
+ });
431
+ ```
432
+
433
+ #### Email OTP
434
+
435
+ ```typescript
436
+ app.post('/setup-email-2fa', async (req, res) => {
437
+ try {
438
+ await req.auth.twoFactor.setup.email();
439
+ res.json({ success: true });
440
+ } catch (error) {
441
+ res.status(400).json({ error: error.message });
442
+ }
443
+ });
444
+ ```
445
+
446
+ #### SMS OTP
447
+
448
+ ```typescript
449
+ app.post('/setup-sms-2fa', async (req, res) => {
450
+ try {
451
+ const { phoneNumber } = req.body;
452
+ await req.auth.twoFactor.setup.sms(phoneNumber);
453
+ res.json({ success: true });
454
+ } catch (error) {
455
+ res.status(400).json({ error: error.message });
456
+ }
457
+ });
458
+ ```
459
+
460
+ ### MFA Enrollment with Verification
461
+
462
+ For production apps, require verification during enrollment:
463
+
464
+ ```typescript
465
+ app.post('/setup-totp', async (req, res) => {
466
+ try {
467
+ // Setup but require verification
468
+ const { secret, qrCode } = await req.auth.twoFactor.setup.totp(true);
469
+ res.json({ secret, qrCode, requiresVerification: true });
470
+ } catch (error) {
471
+ res.status(400).json({ error: error.message });
472
+ }
473
+ });
474
+
475
+ app.post('/verify-totp-setup', async (req, res) => {
476
+ try {
477
+ const { code } = req.body;
478
+ const backupCodes = await req.auth.twoFactor.complete.totp(code);
479
+ res.json({ success: true, backupCodes });
480
+ } catch (error) {
481
+ res.status(400).json({ error: error.message });
482
+ }
483
+ });
484
+ ```
485
+
486
+ ### MFA Management
487
+
488
+ ```typescript
489
+ // Check MFA status
490
+ app.get('/mfa-status', async (req, res) => {
491
+ const status = {
492
+ enabled: await req.auth.twoFactor.isEnabled(),
493
+ methods: {
494
+ totp: await req.auth.twoFactor.totpEnabled(),
495
+ email: await req.auth.twoFactor.emailEnabled(),
496
+ sms: await req.auth.twoFactor.smsEnabled()
497
+ }
498
+ };
499
+ res.json(status);
500
+ });
501
+
502
+ // Disable MFA method
503
+ app.delete('/mfa/:method', async (req, res) => {
504
+ try {
505
+ const mechanism = req.params.method === 'totp' ? 1 :
506
+ req.params.method === 'email' ? 2 : 3;
507
+ await req.auth.twoFactor.disable(mechanism);
508
+ res.json({ success: true });
509
+ } catch (error) {
510
+ res.status(400).json({ error: error.message });
511
+ }
512
+ });
513
+
514
+ // Generate new backup codes
515
+ app.post('/mfa/backup-codes', async (req, res) => {
516
+ try {
517
+ const backupCodes = await req.auth.twoFactor.generateNewBackupCodes();
518
+ res.json({ backupCodes });
519
+ } catch (error) {
520
+ res.status(400).json({ error: error.message });
521
+ }
522
+ });
523
+ ```
524
+
525
+ ## Configuration
526
+
527
+ ### User ID Mapping
528
+
529
+ The auth library maintains its own auth tables (accounts, roles, sessions) that can optionally link to your application's user records via a user ID.
530
+
531
+ **Registration now takes an optional userId parameter**:
532
+
533
+ ```typescript
534
+ app.post('/register', async (req, res) => {
535
+ // Option 1: Let easy-auth auto-generate a UUID (simplest)
536
+ const account = await req.auth.register(req.body.email, req.body.password);
537
+
538
+ // Option 2: Link to your existing user table
539
+ const user = await db.insert(users).values({
540
+ name: req.body.name,
541
+ email: req.body.email
542
+ }).returning();
543
+
544
+ const account = await req.auth.register(req.body.email, req.body.password, user.id);
545
+
546
+ res.json({ success: true, userId: user.id });
547
+ });
548
+ ```
549
+
550
+ **For OAuth**, you can optionally provide a `createUser` function to handle new OAuth users. This is the ONLY use case for `createUser` - it's not used for regular registration or admin user creation:
551
+
552
+ ```typescript
553
+ const authConfig = {
554
+ db: pool,
555
+ // ONLY used for OAuth new user creation
556
+ createUser: async (userData: OAuthUserData) => {
557
+ // Create user in your app's user table
558
+ const user = await db.insert(users).values({
559
+ name: userData.name || userData.username,
560
+ email: userData.email,
561
+ }).returning();
562
+
563
+ return user.id; // This will be stored as user_id in auth tables
564
+ }
565
+ }
566
+ ```
567
+
568
+ **If you don't provide `createUser` for OAuth**, a UUID will be auto-generated - no configuration needed!
569
+
570
+ **For login**, simply call `req.auth.login()`. You don't need to identify the user beforehand because the `login` method itself does the authentication using the provided credentials.
571
+
572
+ ```typescript
573
+ app.post('/login', async (req, res) => {
574
+ try {
575
+ await req.auth.login(req.body.email, req.body.password);
576
+ } catch (error) {
577
+ if (error instanceof UserNotFoundError || error instanceof InvalidPasswordError) {
578
+ return res.status(401).json({ error: 'Invalid email or password' });
579
+ }
580
+
581
+ if (error instanceof UserInactiveError) {
582
+ return res.status(403).json({ error: 'Account inactive' });
583
+ }
584
+
585
+ throw error;
586
+ }
587
+
588
+ res.json({ success: true });
589
+ });
590
+ ```
591
+
592
+ **Important**: If you use `req.session.userId`, it could be helpful to augment the session type if you're using TypeScript:
593
+
594
+ ```typescript
595
+ declare module "express-session" {
596
+ interface SessionData {
597
+ userId?: string;
598
+ }
599
+ }
600
+ ```
601
+
602
+ ### AuthConfig
603
+
604
+ ```typescript
605
+ interface AuthConfig {
606
+ // PostgreSQL connection pool
607
+ db: Pool;
608
+
609
+ // Optional OAuth new user creation function
610
+ createUser?: (userData: OAuthUserData) => string | number | Promise<string | number>; // Called when OAuth user doesn't exist in your system
611
+
612
+ // Optional settings
613
+ tablePrefix?: string; // default: 'user_'
614
+ minPasswordLength?: number; // default: 8
615
+ maxPasswordLength?: number; // default: 64
616
+ rememberDuration?: string; // default: '30d'
617
+ rememberCookieName?: string; // default: 'remember_token'
618
+ resyncInterval?: string; // default: '30s'
619
+
620
+ // OAuth provider configuration
621
+ providers?: {
622
+ github?: GitHubProviderConfig;
623
+ google?: GoogleProviderConfig;
624
+ azure?: AzureProviderConfig;
625
+ };
626
+
627
+ // Multi-factor authentication
628
+ twoFactor?: {
629
+ enabled?: boolean; // default: false
630
+ requireForOAuth?: boolean; // default: false
631
+ issuer?: string; // default: 'EasyAuth'
632
+ codeLength?: number; // default: 6
633
+ tokenExpiry?: string; // default: '5m'
634
+ totpWindow?: number; // default: 1
635
+ backupCodesCount?: number; // default: 10
636
+ };
637
+ }
638
+ ```
639
+
640
+ ## Database Schema
641
+
642
+ The library creates its own tables that link to your existing user table:
643
+
644
+ ```sql
645
+ -- your existing user table
646
+ CREATE TABLE users (
647
+ id SERIAL PRIMARY KEY,
648
+ name VARCHAR(100),
649
+ -- whatever else
650
+ );
651
+
652
+ -- library creates these tables
653
+ CREATE TABLE user_accounts (
654
+ id SERIAL PRIMARY KEY,
655
+ user_id VARCHAR(255) NOT NULL, -- links to your users.id or auto-generated UUID
656
+ email VARCHAR(255) NOT NULL UNIQUE,
657
+ password VARCHAR(255) NOT NULL,
658
+ verified BOOLEAN DEFAULT FALSE,
659
+ status INTEGER DEFAULT 0,
660
+ rolemask INTEGER DEFAULT 0,
661
+ -- ...
662
+ );
663
+
664
+ -- also: user_confirmations, user_remembers, user_resets, user_providers
665
+ -- MFA tables: user_2fa_methods, user_2fa_tokens
666
+ -- Activity: user_activity_log
667
+ ```
668
+
669
+ ## API Reference
670
+
671
+ ### Auth Manager (`req.auth`)
672
+
673
+ #### Authentication
674
+ - `isLoggedIn(): boolean`
675
+ - `login(email, password, remember?): Promise<void>`
676
+ - `completeTwoFactorLogin(): Promise<void>`
677
+ - `logout(): Promise<void>`
678
+ - `register(email, password, callback?): Promise<AuthAccount>`
679
+
680
+ #### User Info
681
+ - `getId(): number | null`
682
+ - `getEmail(): string | null`
683
+ - `getStatus(): number | null`
684
+ - `getVerified(): boolean | null`
685
+ - `getRoleNames(rolemask?): string[]`
686
+ - `getStatusName(): string | null`
687
+
688
+ #### Permissions
689
+ - `hasRole(role): Promise<boolean>`
690
+ - `isAdmin(): Promise<boolean>`
691
+ - `isRemembered(): boolean`
692
+
693
+ #### Email Management
694
+ - `changeEmail(newEmail, callback): Promise<void>`
695
+ - `confirmEmail(token): Promise<string>`
696
+ - `confirmEmailAndLogin(token, remember?): Promise<void>`
697
+
698
+ #### Password Management
699
+ - `resetPassword(email, expiresAfter?, maxRequests?, callback?): Promise<void>`
700
+ - `confirmResetPassword(token, password, logout?): Promise<void>`
701
+ - `verifyPassword(password): Promise<boolean>`
702
+
703
+ #### Session Management
704
+ - `logoutEverywhere(): Promise<void>`
705
+ - `logoutEverywhereElse(): Promise<void>`
706
+
707
+ #### Multi-Factor Authentication (`req.auth.twoFactor`)
708
+ - `isEnabled(): Promise<boolean>`
709
+ - `totpEnabled(): Promise<boolean>`
710
+ - `emailEnabled(): Promise<boolean>`
711
+ - `smsEnabled(): Promise<boolean>`
712
+ - `getEnabledMethods(): Promise<TwoFactorMechanism[]>`
713
+
714
+ **Setup Methods:**
715
+ - `setup.totp(requireVerification?): Promise<TwoFactorSetupResult>`
716
+ - `setup.email(email?, requireVerification?): Promise<void>`
717
+ - `setup.sms(phone, requireVerification?): Promise<void>`
718
+
719
+ **Completion Methods (for verification during enrollment):**
720
+ - `complete.totp(code): Promise<string[]>`
721
+ - `complete.email(code): Promise<void>`
722
+ - `complete.sms(code): Promise<void>`
723
+
724
+ **Verification Methods (during login):**
725
+ - `verify.totp(code): Promise<void>`
726
+ - `verify.email(code): Promise<void>`
727
+ - `verify.sms(code): Promise<void>`
728
+ - `verify.backupCode(code): Promise<void>`
729
+ - `verify.otp(code): Promise<void>`
730
+
731
+ **Management Methods:**
732
+ - `disable(mechanism): Promise<void>`
733
+ - `generateNewBackupCodes(): Promise<string[]>`
734
+ - `getContact(mechanism): Promise<string | null>`
735
+
736
+ ### Admin Manager (`req.authAdmin`)
737
+
738
+ #### User Management
739
+ - `createUser(credentials, callback?): Promise<AuthAccount>`
740
+ - `loginAsUserBy(identifier): Promise<void>`
741
+ - `deleteUserBy(identifier): Promise<void>`
742
+
743
+ #### Role Management
744
+ - `addRoleForUserBy(identifier, role): Promise<void>`
745
+ - `removeRoleForUserBy(identifier, role): Promise<void>`
746
+ - `hasRoleForUserBy(identifier, role): Promise<boolean>`
747
+
748
+ #### Account Management
749
+ - `changePasswordForUserBy(identifier, password): Promise<void>`
750
+ - `setStatusForUserBy(identifier, status): Promise<void>`
751
+ - `initiatePasswordResetForUserBy(identifier, expiresAfter?, callback?): Promise<void>`
752
+
753
+ ### Schema Utilities
754
+
755
+ ```typescript
756
+ import { createAuthTables, dropAuthTables, cleanupExpiredTokens, getAuthTableStats } from '@prsm/easy-auth';
757
+
758
+ // Setup tables
759
+ await createAuthTables(config);
760
+
761
+ // Cleanup (useful for cron jobs)
762
+ await cleanupExpiredTokens(config);
763
+
764
+ // Get statistics
765
+ const stats = await getAuthTableStats(config);
766
+ console.log(`${stats.accounts} accounts, ${stats.expiredRemembers} expired tokens`);
767
+
768
+ // Remove all auth tables
769
+ await dropAuthTables(config);
770
+ ```
771
+
772
+ ## Constants
773
+
774
+ ```typescript
775
+ import { AuthStatus, AuthRole } from '@prsm/easy-auth';
776
+
777
+ // User statuses
778
+ AuthStatus.Normal // 0
779
+ AuthStatus.Archived // 1
780
+ AuthStatus.Banned // 2
781
+ AuthStatus.Locked // 3
782
+ AuthStatus.PendingReview // 4
783
+ AuthStatus.Suspended // 5
784
+
785
+ // User roles (bitmask)
786
+ AuthRole.Admin // 1
787
+ AuthRole.Author // 2
788
+ AuthRole.Collaborator // 4
789
+ // ... many more
790
+ ```
791
+
792
+ ## Error Handling
793
+
794
+ ```typescript
795
+ import {
796
+ EmailTakenError,
797
+ InvalidPasswordError,
798
+ UserNotFoundError,
799
+ SecondFactorRequiredError,
800
+ InvalidTwoFactorCodeError
801
+ } from '@prsm/easy-auth';
802
+
803
+ app.post('/register', async (req, res) => {
804
+ try {
805
+ await req.auth.register(email, password);
806
+ } catch (error) {
807
+ if (error instanceof EmailTakenError) {
808
+ return res.status(409).json({ error: 'Email already exists' });
809
+ }
810
+ if (error instanceof InvalidPasswordError) {
811
+ return res.status(400).json({ error: 'Password too weak' });
812
+ }
813
+ throw error;
814
+ }
815
+ });
816
+
817
+ app.post('/login', async (req, res) => {
818
+ try {
819
+ await req.auth.login(req.body.email, req.body.password);
820
+ res.json({ success: true });
821
+ } catch (error) {
822
+ if (error instanceof SecondFactorRequiredError) {
823
+ return res.status(202).json({
824
+ requiresTwoFactor: true,
825
+ availableMethods: error.challenge
826
+ });
827
+ }
828
+ if (error instanceof InvalidTwoFactorCodeError) {
829
+ return res.status(400).json({ error: 'Invalid verification code' });
830
+ }
831
+ throw error;
832
+ }
833
+ });
834
+ ```
835
+
836
+ ## Examples
837
+
838
+ ### Database Setup
839
+
840
+ ```typescript
841
+ import { Pool } from 'pg';
842
+
843
+ const pool = new Pool({
844
+ connectionString: 'postgresql://user:password@localhost:5432/dbname'
845
+ });
846
+
847
+ const config = {
848
+ db: pool,
849
+ tablePrefix: 'auth_',
850
+ };
851
+ ```
852
+
853
+ ### Role-Based Access Control
854
+
855
+ ```typescript
856
+ app.get('/admin', async (req, res) => {
857
+ if (!req.auth.isLoggedIn()) {
858
+ return res.status(401).json({ error: 'Not logged in' });
859
+ }
860
+
861
+ if (!await req.auth.hasRole(AuthRole.Admin)) {
862
+ return res.status(403).json({ error: 'Admin access required' });
863
+ }
864
+
865
+ // Admin-only content
866
+ });
867
+
868
+ // Add role to user
869
+ await req.authAdmin.addRoleForUserBy(
870
+ { email: 'user@example.com' },
871
+ AuthRole.Admin | AuthRole.Editor
872
+ );
873
+ ```
874
+
875
+ ## License
876
+
877
+ MIT