@eaccess/auth 0.1.21 → 1.0.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 +90 -41
- package/dist/index.cjs +63 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +62 -52
- package/dist/index.js.map +1 -1
- package/package.json +17 -9
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @eaccess/auth
|
|
2
2
|
|
|
3
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
4
|
|
|
@@ -17,7 +17,7 @@ An Express authentication middleware specifically designed for Postgres that pro
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install @
|
|
20
|
+
npm install @eaccess/auth express-session
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Quick Start
|
|
@@ -26,7 +26,7 @@ npm install @prsm/easy-auth express-session
|
|
|
26
26
|
import express from 'express';
|
|
27
27
|
import session from 'express-session';
|
|
28
28
|
import { Pool } from 'pg';
|
|
29
|
-
import { createAuthMiddleware, createAuthTables } from '@
|
|
29
|
+
import { createAuthMiddleware, createAuthTables } from '@eaccess/auth';
|
|
30
30
|
|
|
31
31
|
const app = express();
|
|
32
32
|
const pool = new Pool({ connectionString: 'postgresql://...' });
|
|
@@ -93,7 +93,7 @@ app.get('/profile', (req, res) => {
|
|
|
93
93
|
if (!req.auth.isLoggedIn()) {
|
|
94
94
|
return res.status(401).json({ error: 'Not logged in' });
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
res.json({
|
|
98
98
|
email: req.auth.getEmail(),
|
|
99
99
|
status: req.auth.getStatusName(),
|
|
@@ -113,7 +113,7 @@ Easy-auth supports OAuth providers (GitHub, Google, Azure) with a clean, extensi
|
|
|
113
113
|
import express from 'express';
|
|
114
114
|
import session from 'express-session';
|
|
115
115
|
import { Pool } from 'pg';
|
|
116
|
-
import { createAuthMiddleware, createAuthTables, type OAuthUserData } from '@
|
|
116
|
+
import { createAuthMiddleware, createAuthTables, type OAuthUserData } from '@eaccess/auth';
|
|
117
117
|
|
|
118
118
|
const app = express();
|
|
119
119
|
const pool = new Pool({ connectionString: 'postgresql://...' });
|
|
@@ -131,11 +131,11 @@ const authConfig = {
|
|
|
131
131
|
name: userData.name || userData.username,
|
|
132
132
|
email: userData.email,
|
|
133
133
|
}).returning();
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
return user.id; // Return the new user's ID
|
|
136
136
|
},
|
|
137
137
|
tablePrefix: 'auth_',
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
// OAuth provider configuration
|
|
140
140
|
providers: {
|
|
141
141
|
github: {
|
|
@@ -217,7 +217,7 @@ app.get('/auth/google/callback', async (req, res) => {
|
|
|
217
217
|
### OAuth Flow Explained
|
|
218
218
|
|
|
219
219
|
1. **User clicks "Login with GitHub"** → Browser goes to `/auth/github`
|
|
220
|
-
2. **Server redirects to GitHub** → User sees GitHub's login page
|
|
220
|
+
2. **Server redirects to GitHub** → User sees GitHub's login page
|
|
221
221
|
3. **User authorizes your app** → GitHub redirects to `/auth/github/callback?code=abc123`
|
|
222
222
|
4. **Server processes callback** → `handleCallback()` does:
|
|
223
223
|
- Exchange code for access token
|
|
@@ -278,16 +278,16 @@ app.get('/auth/github/callback', async (req, res) => {
|
|
|
278
278
|
try {
|
|
279
279
|
// Get user data without logging in
|
|
280
280
|
const userData = await req.auth.providers.github.getUserData(req);
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
// Your custom logic here
|
|
283
283
|
const existingUser = await findUserByEmail(userData.email);
|
|
284
284
|
if (existingUser && !existingUser.allowOAuth) {
|
|
285
285
|
throw new Error('OAuth disabled for this account');
|
|
286
286
|
}
|
|
287
|
-
|
|
287
|
+
|
|
288
288
|
// Then complete the OAuth flow manually
|
|
289
289
|
await req.auth.providers.github.handleCallback(req);
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
res.json({ success: true, user: userData });
|
|
292
292
|
} catch (error) {
|
|
293
293
|
res.status(400).json({ error: error.message });
|
|
@@ -306,7 +306,7 @@ Enable MFA in your auth config:
|
|
|
306
306
|
```typescript
|
|
307
307
|
const authConfig = {
|
|
308
308
|
db: pool,
|
|
309
|
-
|
|
309
|
+
|
|
310
310
|
twoFactor: {
|
|
311
311
|
enabled: true,
|
|
312
312
|
requireForOAuth: false, // Skip MFA for OAuth users (optional)
|
|
@@ -377,7 +377,7 @@ After receiving `SecondFactorRequiredError`, verify the second factor:
|
|
|
377
377
|
app.post('/verify-2fa', async (req, res) => {
|
|
378
378
|
try {
|
|
379
379
|
const { code, method } = req.body;
|
|
380
|
-
|
|
380
|
+
|
|
381
381
|
// Verify based on method
|
|
382
382
|
switch (method) {
|
|
383
383
|
case 'totp':
|
|
@@ -397,7 +397,7 @@ app.post('/verify-2fa', async (req, res) => {
|
|
|
397
397
|
await req.auth.twoFactor.verify.otp(code);
|
|
398
398
|
break;
|
|
399
399
|
}
|
|
400
|
-
|
|
400
|
+
|
|
401
401
|
// Complete login
|
|
402
402
|
await req.auth.completeTwoFactorLogin();
|
|
403
403
|
res.json({ success: true });
|
|
@@ -417,9 +417,9 @@ Users can enroll in multiple MFA methods:
|
|
|
417
417
|
app.post('/setup-totp', async (req, res) => {
|
|
418
418
|
try {
|
|
419
419
|
const { secret, qrCode, backupCodes } = await req.auth.twoFactor.setup.totp();
|
|
420
|
-
|
|
420
|
+
|
|
421
421
|
// Show QR code to user for scanning with authenticator app
|
|
422
|
-
res.json({
|
|
422
|
+
res.json({
|
|
423
423
|
secret, // Manual entry secret
|
|
424
424
|
qrCode, // QR code URL for scanning
|
|
425
425
|
backupCodes // One-time backup codes
|
|
@@ -502,7 +502,7 @@ app.get('/mfa-status', async (req, res) => {
|
|
|
502
502
|
// Disable MFA method
|
|
503
503
|
app.delete('/mfa/:method', async (req, res) => {
|
|
504
504
|
try {
|
|
505
|
-
const mechanism = req.params.method === 'totp' ? 1 :
|
|
505
|
+
const mechanism = req.params.method === 'totp' ? 1 :
|
|
506
506
|
req.params.method === 'email' ? 2 : 3;
|
|
507
507
|
await req.auth.twoFactor.disable(mechanism);
|
|
508
508
|
res.json({ success: true });
|
|
@@ -534,15 +534,15 @@ The auth library maintains its own auth tables (accounts, roles, sessions) that
|
|
|
534
534
|
app.post('/register', async (req, res) => {
|
|
535
535
|
// Option 1: Let easy-auth auto-generate a UUID (simplest)
|
|
536
536
|
const account = await req.auth.register(req.body.email, req.body.password);
|
|
537
|
-
|
|
537
|
+
|
|
538
538
|
// Option 2: Link to your existing user table
|
|
539
539
|
const user = await db.insert(users).values({
|
|
540
540
|
name: req.body.name,
|
|
541
541
|
email: req.body.email
|
|
542
542
|
}).returning();
|
|
543
|
-
|
|
543
|
+
|
|
544
544
|
const account = await req.auth.register(req.body.email, req.body.password, user.id);
|
|
545
|
-
|
|
545
|
+
|
|
546
546
|
res.json({ success: true, userId: user.id });
|
|
547
547
|
});
|
|
548
548
|
```
|
|
@@ -559,7 +559,7 @@ const authConfig = {
|
|
|
559
559
|
name: userData.name || userData.username,
|
|
560
560
|
email: userData.email,
|
|
561
561
|
}).returning();
|
|
562
|
-
|
|
562
|
+
|
|
563
563
|
return user.id; // This will be stored as user_id in auth tables
|
|
564
564
|
}
|
|
565
565
|
}
|
|
@@ -584,7 +584,7 @@ app.post('/login', async (req, res) => {
|
|
|
584
584
|
|
|
585
585
|
throw error;
|
|
586
586
|
}
|
|
587
|
-
|
|
587
|
+
|
|
588
588
|
res.json({ success: true });
|
|
589
589
|
});
|
|
590
590
|
```
|
|
@@ -605,25 +605,26 @@ declare module "express-session" {
|
|
|
605
605
|
interface AuthConfig {
|
|
606
606
|
// PostgreSQL connection pool
|
|
607
607
|
db: Pool;
|
|
608
|
-
|
|
608
|
+
|
|
609
609
|
// Optional OAuth new user creation function
|
|
610
610
|
createUser?: (userData: OAuthUserData) => string | number | Promise<string | number>; // Called when OAuth user doesn't exist in your system
|
|
611
|
-
|
|
611
|
+
|
|
612
612
|
// Optional settings
|
|
613
613
|
tablePrefix?: string; // default: 'user_'
|
|
614
|
+
roles?: Record<string, number>; // custom roles from defineRoles(), default: AuthRole
|
|
614
615
|
minPasswordLength?: number; // default: 8
|
|
615
616
|
maxPasswordLength?: number; // default: 64
|
|
616
617
|
rememberDuration?: string; // default: '30d'
|
|
617
618
|
rememberCookieName?: string; // default: 'remember_token'
|
|
618
619
|
resyncInterval?: string; // default: '30s'
|
|
619
|
-
|
|
620
|
+
|
|
620
621
|
// OAuth provider configuration
|
|
621
622
|
providers?: {
|
|
622
623
|
github?: GitHubProviderConfig;
|
|
623
624
|
google?: GoogleProviderConfig;
|
|
624
625
|
azure?: AzureProviderConfig;
|
|
625
626
|
};
|
|
626
|
-
|
|
627
|
+
|
|
627
628
|
// Multi-factor authentication
|
|
628
629
|
twoFactor?: {
|
|
629
630
|
enabled?: boolean; // default: false
|
|
@@ -733,14 +734,14 @@ CREATE TABLE user_accounts (
|
|
|
733
734
|
- `generateNewBackupCodes(): Promise<string[]>`
|
|
734
735
|
- `getContact(mechanism): Promise<string | null>`
|
|
735
736
|
|
|
736
|
-
### Admin
|
|
737
|
+
### Admin Functions (also on `req.auth`)
|
|
737
738
|
|
|
738
739
|
#### User Management
|
|
739
740
|
- `createUser(credentials, callback?): Promise<AuthAccount>`
|
|
740
741
|
- `loginAsUserBy(identifier): Promise<void>`
|
|
741
742
|
- `deleteUserBy(identifier): Promise<void>`
|
|
742
743
|
|
|
743
|
-
#### Role Management
|
|
744
|
+
#### Role Management
|
|
744
745
|
- `addRoleForUserBy(identifier, role): Promise<void>`
|
|
745
746
|
- `removeRoleForUserBy(identifier, role): Promise<void>`
|
|
746
747
|
- `hasRoleForUserBy(identifier, role): Promise<boolean>`
|
|
@@ -753,7 +754,7 @@ CREATE TABLE user_accounts (
|
|
|
753
754
|
### Schema Utilities
|
|
754
755
|
|
|
755
756
|
```typescript
|
|
756
|
-
import { createAuthTables, dropAuthTables, cleanupExpiredTokens, getAuthTableStats } from '@
|
|
757
|
+
import { createAuthTables, dropAuthTables, cleanupExpiredTokens, getAuthTableStats } from '@eaccess/auth';
|
|
757
758
|
|
|
758
759
|
// Setup tables
|
|
759
760
|
await createAuthTables(config);
|
|
@@ -769,20 +770,68 @@ console.log(`${stats.accounts} accounts, ${stats.expiredRemembers} expired token
|
|
|
769
770
|
await dropAuthTables(config);
|
|
770
771
|
```
|
|
771
772
|
|
|
773
|
+
## Custom Roles
|
|
774
|
+
|
|
775
|
+
The default `AuthRole` enum provides 21 predefined roles, but most apps need their own. Use `defineRoles` to create a custom role set:
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
import { defineRoles } from '@eaccess/auth';
|
|
779
|
+
|
|
780
|
+
const Roles = defineRoles('owner', 'editor', 'viewer');
|
|
781
|
+
// { owner: 1, editor: 2, viewer: 4 }
|
|
782
|
+
|
|
783
|
+
const authConfig = {
|
|
784
|
+
db: pool,
|
|
785
|
+
roles: Roles, // getRoleNames() will use these names
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// usage
|
|
789
|
+
await req.auth.addRoleForUserBy({ email: 'user@example.com' }, Roles.owner | Roles.editor);
|
|
790
|
+
req.auth.getRoleNames(); // ['owner', 'editor']
|
|
791
|
+
await req.auth.hasRole(Roles.editor); // true
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
Names are preserved exactly as provided - no transformation. Max 31 roles (Postgres INTEGER is 32-bit signed). If you don't set `config.roles`, the default `AuthRole` enum is used.
|
|
795
|
+
|
|
796
|
+
The admin panel (`@eaccess/admin`) reads `config.roles` from the backend automatically, so custom roles show up in the user management UI without any extra configuration.
|
|
797
|
+
|
|
798
|
+
## Standalone Auth (no request context)
|
|
799
|
+
|
|
800
|
+
For server-side operations outside of Express routes (scripts, workers, cron jobs), use `createAuthContext`:
|
|
801
|
+
|
|
802
|
+
```typescript
|
|
803
|
+
import { createAuthContext } from '@eaccess/auth';
|
|
804
|
+
|
|
805
|
+
const auth = createAuthContext(authConfig);
|
|
806
|
+
|
|
807
|
+
await auth.createUser({ email: 'user@example.com', password: 'password123' });
|
|
808
|
+
await auth.addRoleForUserBy({ email: 'user@example.com' }, Roles.editor);
|
|
809
|
+
await auth.resetPassword('user@example.com', '1h', 3, (token) => sendResetEmail(token));
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
For WebSocket or raw HTTP authentication:
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
import { authenticateRequest } from '@eaccess/auth';
|
|
816
|
+
|
|
817
|
+
const result = await authenticateRequest(authConfig, req, sessionMiddleware);
|
|
818
|
+
// result: { account: AuthAccount | null, source: 'session' | 'remember' | null }
|
|
819
|
+
```
|
|
820
|
+
|
|
772
821
|
## Constants
|
|
773
822
|
|
|
774
823
|
```typescript
|
|
775
|
-
import { AuthStatus, AuthRole } from '@
|
|
824
|
+
import { AuthStatus, AuthRole } from '@eaccess/auth';
|
|
776
825
|
|
|
777
826
|
// User statuses
|
|
778
827
|
AuthStatus.Normal // 0
|
|
779
|
-
AuthStatus.Archived // 1
|
|
828
|
+
AuthStatus.Archived // 1
|
|
780
829
|
AuthStatus.Banned // 2
|
|
781
830
|
AuthStatus.Locked // 3
|
|
782
831
|
AuthStatus.PendingReview // 4
|
|
783
832
|
AuthStatus.Suspended // 5
|
|
784
833
|
|
|
785
|
-
//
|
|
834
|
+
// Default roles (bitmask) - or use defineRoles() for custom roles
|
|
786
835
|
AuthRole.Admin // 1
|
|
787
836
|
AuthRole.Author // 2
|
|
788
837
|
AuthRole.Collaborator // 4
|
|
@@ -792,13 +841,13 @@ AuthRole.Collaborator // 4
|
|
|
792
841
|
## Error Handling
|
|
793
842
|
|
|
794
843
|
```typescript
|
|
795
|
-
import {
|
|
796
|
-
EmailTakenError,
|
|
797
|
-
InvalidPasswordError,
|
|
844
|
+
import {
|
|
845
|
+
EmailTakenError,
|
|
846
|
+
InvalidPasswordError,
|
|
798
847
|
UserNotFoundError,
|
|
799
848
|
SecondFactorRequiredError,
|
|
800
849
|
InvalidTwoFactorCodeError
|
|
801
|
-
} from '@
|
|
850
|
+
} from '@eaccess/auth';
|
|
802
851
|
|
|
803
852
|
app.post('/register', async (req, res) => {
|
|
804
853
|
try {
|
|
@@ -840,7 +889,7 @@ app.post('/login', async (req, res) => {
|
|
|
840
889
|
```typescript
|
|
841
890
|
import { Pool } from 'pg';
|
|
842
891
|
|
|
843
|
-
const pool = new Pool({
|
|
892
|
+
const pool = new Pool({
|
|
844
893
|
connectionString: 'postgresql://user:password@localhost:5432/dbname'
|
|
845
894
|
});
|
|
846
895
|
|
|
@@ -857,17 +906,17 @@ app.get('/admin', async (req, res) => {
|
|
|
857
906
|
if (!req.auth.isLoggedIn()) {
|
|
858
907
|
return res.status(401).json({ error: 'Not logged in' });
|
|
859
908
|
}
|
|
860
|
-
|
|
909
|
+
|
|
861
910
|
if (!await req.auth.hasRole(AuthRole.Admin)) {
|
|
862
911
|
return res.status(403).json({ error: 'Admin access required' });
|
|
863
912
|
}
|
|
864
|
-
|
|
913
|
+
|
|
865
914
|
// Admin-only content
|
|
866
915
|
});
|
|
867
916
|
|
|
868
917
|
// Add role to user
|
|
869
|
-
await req.
|
|
870
|
-
{ email: 'user@example.com' },
|
|
918
|
+
await req.auth.addRoleForUserBy(
|
|
919
|
+
{ email: 'user@example.com' },
|
|
871
920
|
AuthRole.Admin | AuthRole.Editor
|
|
872
921
|
);
|
|
873
922
|
```
|