@ofeklabs/horizon-auth 0.3.0 โ 0.4.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 +544 -175
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/horizon-auth-env.config.d.ts +3 -0
- package/dist/lib/horizon-auth-env.config.js +173 -0
- package/dist/lib/horizon-auth-env.config.js.map +1 -0
- package/dist/lib/horizon-auth.module.d.ts +1 -1
- package/dist/lib/horizon-auth.module.js +10 -8
- package/dist/lib/horizon-auth.module.js.map +1 -1
- package/dist/social-auth/social-auth.service.d.ts +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
# @ofeklabs/horizon-auth
|
|
2
2
|
|
|
3
|
-
Production-ready NestJS authentication module with 2026 security standards.
|
|
3
|
+
Production-ready NestJS authentication module with 2026 security standards. One package, two modes: embedded auth or SSO - configured entirely through ENV variables.
|
|
4
4
|
|
|
5
|
-
## Use Cases
|
|
5
|
+
## ๐ฏ Use Cases
|
|
6
6
|
|
|
7
|
-
### 1.
|
|
7
|
+
### 1. Embedded Auth
|
|
8
|
+
Each application has its own isolated authentication. Perfect for standalone apps.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
myapp.com (App + Auth)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### 2. Portfolio SSO (Recommended)
|
|
8
15
|
Deploy one auth service, use it across all your projects. Users sign in once and access everything.
|
|
9
16
|
|
|
10
17
|
```
|
|
@@ -15,8 +22,7 @@ auth.yourdomain.com (Auth Service)
|
|
|
15
22
|
โโโ project3.yourdomain.com
|
|
16
23
|
```
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
Each application has its own isolated authentication.
|
|
25
|
+
**Same package, different ENV variables!** No code changes needed.
|
|
20
26
|
|
|
21
27
|
## Features
|
|
22
28
|
|
|
@@ -29,99 +35,128 @@ Each application has its own isolated authentication.
|
|
|
29
35
|
- ๐ฏ **Type-Safe**: Full TypeScript support
|
|
30
36
|
- ๐ฆ **Zero Config**: Sensible defaults, fully customizable
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
### ๐ Enterprise Features (v0.3.0)
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
- ๐ **Two-Factor Authentication**: TOTP-based 2FA with QR codes and backup codes
|
|
41
|
+
- ๐ฑ **Device Management**: Track, list, and revoke user devices
|
|
42
|
+
- ๐ **Push Notifications**: Register and manage push tokens (FCM/APNS)
|
|
43
|
+
- ๐ค **Account Management**: Deactivate, reactivate, and delete accounts
|
|
44
|
+
- ๐ **Social Login**: Google and Facebook OAuth integration
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
## ๐ Quick Start
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
### Installation
|
|
39
49
|
|
|
40
50
|
```bash
|
|
41
|
-
|
|
42
|
-
npm install
|
|
43
|
-
npm run build
|
|
44
|
-
npm run start:prod
|
|
51
|
+
npm install @ofeklabs/horizon-auth @prisma/client ioredis passport passport-jwt
|
|
52
|
+
npm install -D prisma
|
|
45
53
|
```
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
JWT_PUBLIC_KEY=<your-public-key>
|
|
53
|
-
COOKIE_DOMAIN=.yourdomain.com
|
|
54
|
-
NODE_ENV=production
|
|
55
|
+
### Generate RSA Keys
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
openssl genrsa -out private.pem 2048
|
|
59
|
+
openssl rsa -in private.pem -pubout -out public.pem
|
|
55
60
|
```
|
|
56
61
|
|
|
57
|
-
|
|
62
|
+
---
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
## Configuration Options
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
### Option 1: Environment Variables Only (Recommended โญ)
|
|
67
|
+
|
|
68
|
+
**Zero code configuration** - just set ENV variables and you're done!
|
|
69
|
+
|
|
70
|
+
#### Embedded Mode (Standalone App)
|
|
71
|
+
|
|
72
|
+
```env
|
|
73
|
+
# .env
|
|
74
|
+
AUTH_MODE=full
|
|
75
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
|
|
76
|
+
REDIS_HOST=localhost
|
|
77
|
+
REDIS_PORT=6379
|
|
78
|
+
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
|
79
|
+
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
80
|
+
|
|
81
|
+
# Optional: Enable features
|
|
82
|
+
ENABLE_2FA=true
|
|
83
|
+
ENABLE_DEVICE_MGMT=true
|
|
84
|
+
ENABLE_PUSH=true
|
|
85
|
+
ENABLE_ACCOUNT_MGMT=true
|
|
86
|
+
APP_NAME=MyApp
|
|
63
87
|
```
|
|
64
88
|
|
|
65
89
|
```typescript
|
|
66
90
|
// app.module.ts
|
|
67
|
-
HorizonAuthModule
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
cookie: {
|
|
74
|
-
domain: process.env.COOKIE_DOMAIN,
|
|
75
|
-
secure: process.env.NODE_ENV === 'production',
|
|
76
|
-
},
|
|
91
|
+
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
|
|
92
|
+
|
|
93
|
+
@Module({
|
|
94
|
+
imports: [
|
|
95
|
+
HorizonAuthModule.forRoot(), // ๐ No config needed! Uses ENV variables
|
|
96
|
+
],
|
|
77
97
|
})
|
|
98
|
+
export class AppModule {}
|
|
78
99
|
```
|
|
79
100
|
|
|
101
|
+
#### SSO Mode (Multiple Projects, One Auth)
|
|
102
|
+
|
|
103
|
+
**Auth Service (auth.yourdomain.com):**
|
|
80
104
|
```env
|
|
81
|
-
|
|
105
|
+
AUTH_MODE=full
|
|
106
|
+
DATABASE_URL=postgresql://user:password@host:5432/auth_db
|
|
107
|
+
REDIS_HOST=redis.yourdomain.com
|
|
108
|
+
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
|
109
|
+
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
110
|
+
COOKIE_DOMAIN=.yourdomain.com
|
|
111
|
+
NODE_ENV=production
|
|
112
|
+
ENABLE_2FA=true
|
|
113
|
+
ENABLE_DEVICE_MGMT=true
|
|
114
|
+
APP_NAME=YourCompany
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Your Projects (project1.yourdomain.com, project2.yourdomain.com):**
|
|
118
|
+
```env
|
|
119
|
+
AUTH_MODE=sso
|
|
82
120
|
AUTH_SERVICE_URL=https://auth.yourdomain.com
|
|
83
|
-
JWT_PUBLIC_KEY
|
|
121
|
+
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
84
122
|
COOKIE_DOMAIN=.yourdomain.com
|
|
123
|
+
NODE_ENV=production
|
|
85
124
|
```
|
|
86
125
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
- project3.yourdomain.com
|
|
126
|
+
```typescript
|
|
127
|
+
// app.module.ts (same for all projects)
|
|
128
|
+
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
|
|
91
129
|
|
|
92
|
-
|
|
130
|
+
@Module({
|
|
131
|
+
imports: [
|
|
132
|
+
HorizonAuthModule.forRoot(), // ๐ Automatically uses SSO mode from ENV
|
|
133
|
+
],
|
|
134
|
+
})
|
|
135
|
+
export class AppModule {}
|
|
136
|
+
```
|
|
93
137
|
|
|
94
|
-
|
|
138
|
+
**That's it!** Deploy your auth service once, then use the same package in all your projects with different ENV variables. No code changes needed!
|
|
95
139
|
|
|
96
|
-
|
|
140
|
+
**๐ See [ENV-CONFIGURATION.md](https://github.com/OfekItzhaki/horizon-auth-platform/blob/main/ENV-CONFIGURATION.md) for complete ENV reference.**
|
|
97
141
|
|
|
98
|
-
|
|
142
|
+
**๐ See [DEPLOYMENT-EXAMPLES.md](https://github.com/OfekItzhaki/horizon-auth-platform/blob/main/DEPLOYMENT-EXAMPLES.md) for real-world deployment scenarios.**
|
|
99
143
|
|
|
100
|
-
|
|
144
|
+
---
|
|
101
145
|
|
|
102
|
-
|
|
103
|
-
npm install @ofeklabs/horizon-auth @prisma/client ioredis passport passport-jwt
|
|
104
|
-
npm install -D prisma
|
|
105
|
-
```
|
|
146
|
+
### Option 2: Code Configuration (Advanced)
|
|
106
147
|
|
|
107
|
-
|
|
148
|
+
If you prefer code-based configuration or need dynamic config:
|
|
108
149
|
|
|
109
|
-
|
|
110
|
-
# Generate private key
|
|
111
|
-
openssl genrsa -out private.pem 2048
|
|
150
|
+
### Option 2: Code Configuration (Advanced)
|
|
112
151
|
|
|
113
|
-
|
|
114
|
-
openssl rsa -in private.pem -pubout -out public.pem
|
|
115
|
-
```
|
|
152
|
+
If you prefer code-based configuration or need dynamic config:
|
|
116
153
|
|
|
117
|
-
|
|
154
|
+
#### Embedded Mode
|
|
118
155
|
|
|
119
156
|
```typescript
|
|
120
157
|
// app.module.ts
|
|
121
158
|
import { Module } from '@nestjs/common';
|
|
122
159
|
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
|
|
123
|
-
import { readFileSync } from 'fs';
|
|
124
|
-
import { join } from 'path';
|
|
125
160
|
|
|
126
161
|
@Module({
|
|
127
162
|
imports: [
|
|
@@ -132,12 +167,19 @@ import { join } from 'path';
|
|
|
132
167
|
redis: {
|
|
133
168
|
host: process.env.REDIS_HOST || 'localhost',
|
|
134
169
|
port: parseInt(process.env.REDIS_PORT) || 6379,
|
|
135
|
-
password: process.env.REDIS_PASSWORD,
|
|
136
170
|
},
|
|
137
171
|
jwt: {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
172
|
+
privateKey: process.env.JWT_PRIVATE_KEY,
|
|
173
|
+
publicKey: process.env.JWT_PUBLIC_KEY,
|
|
174
|
+
},
|
|
175
|
+
features: {
|
|
176
|
+
twoFactor: {
|
|
177
|
+
enabled: true,
|
|
178
|
+
issuer: 'YourApp',
|
|
179
|
+
},
|
|
180
|
+
deviceManagement: {
|
|
181
|
+
enabled: true,
|
|
182
|
+
},
|
|
141
183
|
},
|
|
142
184
|
}),
|
|
143
185
|
],
|
|
@@ -145,17 +187,48 @@ import { join } from 'path';
|
|
|
145
187
|
export class AppModule {}
|
|
146
188
|
```
|
|
147
189
|
|
|
148
|
-
|
|
190
|
+
#### SSO Mode
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// app.module.ts
|
|
194
|
+
import { HorizonAuthModule } from '@ofeklabs/horizon-auth';
|
|
195
|
+
|
|
196
|
+
@Module({
|
|
197
|
+
imports: [
|
|
198
|
+
HorizonAuthModule.forRoot({
|
|
199
|
+
ssoMode: true,
|
|
200
|
+
authServiceUrl: process.env.AUTH_SERVICE_URL,
|
|
201
|
+
jwt: {
|
|
202
|
+
publicKey: process.env.JWT_PUBLIC_KEY,
|
|
203
|
+
},
|
|
204
|
+
cookie: {
|
|
205
|
+
domain: process.env.COOKIE_DOMAIN,
|
|
206
|
+
secure: process.env.NODE_ENV === 'production',
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
],
|
|
210
|
+
})
|
|
211
|
+
export class AppModule {}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Note:** ENV variables are still loaded automatically. Code config overrides ENV values.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## ๐๏ธ Database Setup
|
|
149
219
|
|
|
150
220
|
```bash
|
|
151
|
-
# Start PostgreSQL and Redis
|
|
152
|
-
docker-
|
|
221
|
+
# Start PostgreSQL and Redis (using Docker)
|
|
222
|
+
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:15
|
|
223
|
+
docker run -d -p 6379:6379 redis:7-alpine
|
|
153
224
|
|
|
154
225
|
# Run Prisma migrations
|
|
155
226
|
npx prisma migrate dev
|
|
156
227
|
```
|
|
157
228
|
|
|
158
|
-
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## ๐จ Use in Your Application
|
|
159
232
|
|
|
160
233
|
```typescript
|
|
161
234
|
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
|
|
@@ -164,8 +237,6 @@ import {
|
|
|
164
237
|
CurrentUser,
|
|
165
238
|
JwtAuthGuard,
|
|
166
239
|
Roles,
|
|
167
|
-
LoginDto,
|
|
168
|
-
RegisterDto,
|
|
169
240
|
} from '@ofeklabs/horizon-auth';
|
|
170
241
|
|
|
171
242
|
@Controller()
|
|
@@ -194,69 +265,193 @@ export class AppController {
|
|
|
194
265
|
}
|
|
195
266
|
```
|
|
196
267
|
|
|
197
|
-
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## ๐ก API Endpoints
|
|
198
271
|
|
|
199
272
|
The package automatically provides these endpoints:
|
|
200
273
|
|
|
201
|
-
### Authentication
|
|
274
|
+
### Core Authentication
|
|
202
275
|
|
|
203
276
|
- `POST /auth/register` - Register new user
|
|
204
277
|
- `POST /auth/login` - Login with email/password
|
|
205
278
|
- `POST /auth/refresh` - Refresh access token
|
|
206
279
|
- `POST /auth/logout` - Logout and revoke tokens
|
|
207
280
|
- `GET /auth/profile` - Get current user profile
|
|
208
|
-
|
|
209
|
-
### Password Management
|
|
210
|
-
|
|
211
281
|
- `POST /auth/password-reset/request` - Request password reset
|
|
212
282
|
- `POST /auth/password-reset/complete` - Complete password reset
|
|
213
283
|
- `POST /auth/verify-email` - Verify email address
|
|
214
284
|
|
|
285
|
+
### Enterprise Features (v0.3.0+)
|
|
286
|
+
|
|
287
|
+
**Two-Factor Authentication:**
|
|
288
|
+
- `POST /auth/2fa/enable` - Enable 2FA and get QR code
|
|
289
|
+
- `POST /auth/2fa/verify` - Verify 2FA setup
|
|
290
|
+
- `POST /auth/2fa/verify-login` - Complete login with 2FA
|
|
291
|
+
- `POST /auth/2fa/disable` - Disable 2FA
|
|
292
|
+
- `POST /auth/2fa/backup-codes/regenerate` - Regenerate backup codes
|
|
293
|
+
|
|
294
|
+
**Device Management:**
|
|
295
|
+
- `GET /auth/devices` - List user's active devices
|
|
296
|
+
- `POST /auth/devices/:deviceId/revoke` - Revoke specific device
|
|
297
|
+
|
|
298
|
+
**Push Notifications:**
|
|
299
|
+
- `POST /auth/push-tokens` - Register push notification token
|
|
300
|
+
- `DELETE /auth/push-tokens/:tokenId` - Revoke push token
|
|
301
|
+
|
|
302
|
+
**Account Management:**
|
|
303
|
+
- `POST /auth/account/deactivate` - Deactivate account
|
|
304
|
+
- `POST /auth/account/reactivate` - Reactivate account
|
|
305
|
+
- `DELETE /auth/account` - Permanently delete account
|
|
306
|
+
|
|
307
|
+
**Social Login:**
|
|
308
|
+
- `POST /auth/social/google` - Google OAuth callback
|
|
309
|
+
- `POST /auth/social/facebook` - Facebook OAuth callback
|
|
310
|
+
|
|
215
311
|
### Cross-Language Support
|
|
216
312
|
|
|
217
313
|
- `GET /.well-known/jwks.json` - Public keys for JWT verification
|
|
218
314
|
|
|
219
|
-
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## ๐ฏ How SSO Mode Works
|
|
318
|
+
|
|
319
|
+
When you deploy in SSO mode, here's what happens:
|
|
320
|
+
|
|
321
|
+
1. **Auth Service (auth.yourdomain.com):**
|
|
322
|
+
- Runs in `AUTH_MODE=full`
|
|
323
|
+
- Has database and Redis
|
|
324
|
+
- Handles login, registration, 2FA, etc.
|
|
325
|
+
- Issues JWT tokens
|
|
326
|
+
- Sets cookies for `.yourdomain.com`
|
|
327
|
+
|
|
328
|
+
2. **Your Projects (project1.yourdomain.com, project2.yourdomain.com):**
|
|
329
|
+
- Run in `AUTH_MODE=sso`
|
|
330
|
+
- No database or Redis needed
|
|
331
|
+
- Only verify JWT tokens using public key
|
|
332
|
+
- Read cookies from `.yourdomain.com`
|
|
333
|
+
- Users are automatically authenticated!
|
|
334
|
+
|
|
335
|
+
3. **User Experience:**
|
|
336
|
+
- User visits `project1.yourdomain.com`
|
|
337
|
+
- Not logged in โ Your frontend redirects to `auth.yourdomain.com/login`
|
|
338
|
+
- User logs in at auth service
|
|
339
|
+
- Cookie set for `.yourdomain.com`
|
|
340
|
+
- Redirect back to `project1.yourdomain.com`
|
|
341
|
+
- User is now logged in!
|
|
342
|
+
- User visits `project2.yourdomain.com` โ Already logged in! (same cookie)
|
|
343
|
+
|
|
344
|
+
**Important:** This package is API-only. You need to build your own login/register UI that calls the auth endpoints.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## ๐ง Configuration Reference
|
|
349
|
+
|
|
350
|
+
## ๐ง Configuration Reference
|
|
351
|
+
|
|
352
|
+
### Complete Configuration Interface
|
|
220
353
|
|
|
221
354
|
```typescript
|
|
222
355
|
interface HorizonAuthConfig {
|
|
223
|
-
//
|
|
224
|
-
|
|
356
|
+
// Mode Selection
|
|
357
|
+
ssoMode?: boolean; // false = full auth service, true = token verification only
|
|
358
|
+
authServiceUrl?: string; // Required when ssoMode=true
|
|
359
|
+
|
|
360
|
+
// Database (required when ssoMode=false)
|
|
361
|
+
database?: {
|
|
225
362
|
url: string;
|
|
226
363
|
};
|
|
227
|
-
|
|
364
|
+
|
|
365
|
+
// Redis (required when ssoMode=false)
|
|
366
|
+
redis?: {
|
|
228
367
|
host: string;
|
|
229
368
|
port: number;
|
|
230
369
|
password?: string;
|
|
231
370
|
db?: number;
|
|
232
371
|
};
|
|
372
|
+
|
|
373
|
+
// JWT (always required)
|
|
233
374
|
jwt: {
|
|
234
|
-
privateKey
|
|
235
|
-
publicKey: string; //
|
|
375
|
+
privateKey?: string; // Required when ssoMode=false
|
|
376
|
+
publicKey: string; // Always required
|
|
236
377
|
accessTokenExpiry?: string; // Default: '15m'
|
|
237
378
|
refreshTokenExpiry?: string; // Default: '7d'
|
|
238
379
|
issuer?: string; // Default: 'horizon-auth'
|
|
239
380
|
audience?: string; // Default: 'horizon-api'
|
|
381
|
+
kid?: string; // Default: 'horizon-auth-key-1'
|
|
240
382
|
};
|
|
241
|
-
|
|
242
|
-
//
|
|
383
|
+
|
|
384
|
+
// Cookie Configuration
|
|
385
|
+
cookie?: {
|
|
386
|
+
domain?: string; // e.g., '.yourdomain.com' for SSO
|
|
387
|
+
secure?: boolean; // Default: true in production
|
|
388
|
+
sameSite?: 'strict' | 'lax' | 'none'; // Default: 'lax'
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Multi-Tenant Support
|
|
243
392
|
multiTenant?: {
|
|
244
393
|
enabled: boolean;
|
|
245
394
|
tenantIdExtractor?: 'header' | 'subdomain' | 'custom';
|
|
246
395
|
defaultTenantId?: string;
|
|
247
396
|
};
|
|
397
|
+
|
|
398
|
+
// Rate Limiting
|
|
248
399
|
rateLimit?: {
|
|
249
400
|
login?: { limit: number; ttl: number };
|
|
250
401
|
register?: { limit: number; ttl: number };
|
|
251
402
|
passwordReset?: { limit: number; ttl: number };
|
|
252
403
|
};
|
|
404
|
+
|
|
405
|
+
// Email Configuration
|
|
406
|
+
email?: {
|
|
407
|
+
provider: 'resend' | 'sendgrid' | 'custom';
|
|
408
|
+
apiKey?: string;
|
|
409
|
+
from?: string;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// Global Guards
|
|
253
413
|
guards?: {
|
|
254
414
|
applyJwtGuardGlobally?: boolean;
|
|
255
415
|
};
|
|
416
|
+
|
|
417
|
+
// Enterprise Features
|
|
418
|
+
features?: {
|
|
419
|
+
twoFactor?: {
|
|
420
|
+
enabled: boolean;
|
|
421
|
+
issuer?: string; // Shows in authenticator apps
|
|
422
|
+
};
|
|
423
|
+
deviceManagement?: {
|
|
424
|
+
enabled: boolean;
|
|
425
|
+
maxDevicesPerUser?: number;
|
|
426
|
+
};
|
|
427
|
+
pushNotifications?: {
|
|
428
|
+
enabled: boolean;
|
|
429
|
+
};
|
|
430
|
+
accountManagement?: {
|
|
431
|
+
enabled: boolean;
|
|
432
|
+
allowReactivation?: boolean;
|
|
433
|
+
};
|
|
434
|
+
socialLogin?: {
|
|
435
|
+
google?: {
|
|
436
|
+
clientId: string;
|
|
437
|
+
clientSecret: string;
|
|
438
|
+
callbackUrl: string;
|
|
439
|
+
};
|
|
440
|
+
facebook?: {
|
|
441
|
+
appId: string;
|
|
442
|
+
appSecret: string;
|
|
443
|
+
callbackUrl: string;
|
|
444
|
+
};
|
|
445
|
+
};
|
|
446
|
+
};
|
|
256
447
|
}
|
|
257
448
|
```
|
|
258
449
|
|
|
259
|
-
|
|
450
|
+
**๐ก Tip:** Use ENV variables instead of code config for maximum flexibility!
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## ๐ญ Decorators
|
|
260
455
|
|
|
261
456
|
### @Public()
|
|
262
457
|
Mark routes as publicly accessible (skip authentication)
|
|
@@ -300,105 +495,113 @@ getTenantData(@CurrentTenant() tenantId: string) {
|
|
|
300
495
|
}
|
|
301
496
|
```
|
|
302
497
|
|
|
303
|
-
##
|
|
498
|
+
## Enterprise Features Usage
|
|
499
|
+
|
|
500
|
+
### Two-Factor Authentication
|
|
304
501
|
|
|
305
502
|
```typescript
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
tenantIdExtractor: 'header', // or 'subdomain' or 'custom'
|
|
311
|
-
defaultTenantId: 'default',
|
|
312
|
-
},
|
|
503
|
+
// Enable 2FA for a user
|
|
504
|
+
const setup = await fetch('/auth/2fa/enable', {
|
|
505
|
+
method: 'POST',
|
|
506
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
313
507
|
});
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
## Dev SSO Mode
|
|
317
|
-
|
|
318
|
-
For local development with multiple microservices:
|
|
508
|
+
const { secret, qrCode } = await setup.json();
|
|
319
509
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
version: '3.8'
|
|
323
|
-
services:
|
|
324
|
-
auth-service:
|
|
325
|
-
build: .
|
|
326
|
-
ports:
|
|
327
|
-
- '3000:3000'
|
|
328
|
-
environment:
|
|
329
|
-
COOKIE_DOMAIN: '.localhost'
|
|
330
|
-
REDIS_HOST: redis
|
|
331
|
-
depends_on:
|
|
332
|
-
- postgres
|
|
333
|
-
- redis
|
|
334
|
-
```
|
|
510
|
+
// Display QR code to user (qrCode is a data URL)
|
|
511
|
+
// User scans with Google Authenticator or Authy
|
|
335
512
|
|
|
336
|
-
|
|
513
|
+
// Verify setup with code from authenticator
|
|
514
|
+
await fetch('/auth/2fa/verify', {
|
|
515
|
+
method: 'POST',
|
|
516
|
+
headers: {
|
|
517
|
+
Authorization: `Bearer ${accessToken}`,
|
|
518
|
+
'Content-Type': 'application/json',
|
|
519
|
+
},
|
|
520
|
+
body: JSON.stringify({ code: '123456' }),
|
|
521
|
+
});
|
|
337
522
|
|
|
338
|
-
|
|
523
|
+
// Login with 2FA
|
|
524
|
+
const loginResponse = await fetch('/auth/login', {
|
|
525
|
+
method: 'POST',
|
|
526
|
+
body: JSON.stringify({ email, password }),
|
|
527
|
+
});
|
|
339
528
|
|
|
340
|
-
|
|
529
|
+
if (loginResponse.requiresTwoFactor) {
|
|
530
|
+
// Prompt user for 2FA code
|
|
531
|
+
await fetch('/auth/2fa/verify-login', {
|
|
532
|
+
method: 'POST',
|
|
533
|
+
body: JSON.stringify({
|
|
534
|
+
userId: loginResponse.userId,
|
|
535
|
+
code: '123456', // or backup code
|
|
536
|
+
}),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
```
|
|
341
540
|
|
|
342
|
-
|
|
343
|
-
using Microsoft.IdentityModel.Tokens;
|
|
344
|
-
using System.IdentityModel.Tokens.Jwt;
|
|
541
|
+
### Device Management
|
|
345
542
|
|
|
346
|
-
|
|
347
|
-
|
|
543
|
+
```typescript
|
|
544
|
+
// List user's devices (automatically tracked on login)
|
|
545
|
+
const devices = await fetch('/auth/devices', {
|
|
546
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
547
|
+
}).then(r => r.json());
|
|
548
|
+
|
|
549
|
+
// Revoke a specific device
|
|
550
|
+
await fetch(`/auth/devices/${deviceId}/revoke`, {
|
|
551
|
+
method: 'POST',
|
|
552
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
553
|
+
});
|
|
554
|
+
```
|
|
348
555
|
|
|
349
|
-
|
|
350
|
-
var validationParameters = new TokenValidationParameters
|
|
351
|
-
{
|
|
352
|
-
ValidateIssuerSigningKey = true,
|
|
353
|
-
IssuerSigningKeys = keys.Keys,
|
|
354
|
-
ValidateIssuer = true,
|
|
355
|
-
ValidIssuer = "horizon-auth",
|
|
356
|
-
ValidateAudience = true,
|
|
357
|
-
ValidAudience = "horizon-api"
|
|
358
|
-
};
|
|
556
|
+
### Push Notifications
|
|
359
557
|
|
|
360
|
-
|
|
558
|
+
```typescript
|
|
559
|
+
// Register push token (requires device from login)
|
|
560
|
+
await fetch('/auth/push-tokens', {
|
|
561
|
+
method: 'POST',
|
|
562
|
+
headers: {
|
|
563
|
+
Authorization: `Bearer ${accessToken}`,
|
|
564
|
+
'Content-Type': 'application/json',
|
|
565
|
+
},
|
|
566
|
+
body: JSON.stringify({
|
|
567
|
+
token: fcmToken, // From Firebase Cloud Messaging
|
|
568
|
+
tokenType: 'FCM', // or 'APNS' for iOS
|
|
569
|
+
deviceId: deviceId, // From device list
|
|
570
|
+
}),
|
|
571
|
+
});
|
|
361
572
|
```
|
|
362
573
|
|
|
363
|
-
###
|
|
574
|
+
### Account Management
|
|
364
575
|
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
|
|
576
|
+
```typescript
|
|
577
|
+
// Deactivate account
|
|
578
|
+
await fetch('/auth/account/deactivate', {
|
|
579
|
+
method: 'POST',
|
|
580
|
+
headers: {
|
|
581
|
+
Authorization: `Bearer ${accessToken}`,
|
|
582
|
+
'Content-Type': 'application/json',
|
|
583
|
+
},
|
|
584
|
+
body: JSON.stringify({
|
|
585
|
+
reason: 'Taking a break',
|
|
586
|
+
}),
|
|
587
|
+
});
|
|
368
588
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
589
|
+
// Reactivate account (no auth required)
|
|
590
|
+
await fetch('/auth/account/reactivate', {
|
|
591
|
+
method: 'POST',
|
|
592
|
+
body: JSON.stringify({ email, password }),
|
|
593
|
+
});
|
|
372
594
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
algorithms=["RS256"],
|
|
379
|
-
issuer="horizon-auth",
|
|
380
|
-
audience="horizon-api"
|
|
381
|
-
)
|
|
595
|
+
// Permanently delete account
|
|
596
|
+
await fetch('/auth/account', {
|
|
597
|
+
method: 'DELETE',
|
|
598
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
599
|
+
});
|
|
382
600
|
```
|
|
383
601
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
1. **Always use HTTPS in production**
|
|
387
|
-
2. **Store RSA keys securely** (environment variables, secrets manager)
|
|
388
|
-
3. **Enable rate limiting** to prevent brute force attacks
|
|
389
|
-
4. **Monitor security events** (failed logins, token reuse)
|
|
390
|
-
5. **Rotate JWT keys periodically**
|
|
391
|
-
6. **Use strong Redis passwords** in production
|
|
392
|
-
|
|
393
|
-
## Environment Variables
|
|
394
|
-
|
|
395
|
-
```env
|
|
396
|
-
# Database
|
|
397
|
-
DATABASE_URL=postgresql://user:password@localhost:5432/horizon_auth
|
|
602
|
+
---
|
|
398
603
|
|
|
399
|
-
|
|
400
|
-
REDIS_HOST=localhost
|
|
401
|
-
REDIS_PORT=6379
|
|
604
|
+
## ๐ Cross-Language Token Verification
|
|
402
605
|
REDIS_PASSWORD=your_redis_password
|
|
403
606
|
|
|
404
607
|
# JWT Keys (for production - use multiline env vars)
|
|
@@ -548,22 +751,188 @@ docker run -p 3000:3000 \
|
|
|
548
751
|
- All user tokens have been revoked for security
|
|
549
752
|
- User needs to login again
|
|
550
753
|
|
|
551
|
-
|
|
754
|
+
### "Device ID required for push tokens"
|
|
755
|
+
- Push tokens require a valid deviceId
|
|
756
|
+
- Devices are created automatically on login (not registration)
|
|
757
|
+
- Login first, then get device list, then register push token
|
|
758
|
+
|
|
759
|
+
### "Feature disabled" error
|
|
760
|
+
- The feature you're trying to use is not enabled in configuration
|
|
761
|
+
- Enable it in `HorizonAuthModule.forRoot({ features: { ... } })`
|
|
762
|
+
|
|
763
|
+
## Testing Status
|
|
764
|
+
|
|
765
|
+
**โ
Fully Tested:**
|
|
766
|
+
- User registration and login
|
|
767
|
+
- JWT token generation and validation
|
|
768
|
+
- Refresh token rotation
|
|
769
|
+
- 2FA setup (QR code generation)
|
|
770
|
+
- Account deactivation/reactivation
|
|
771
|
+
- Login protection for deactivated accounts
|
|
772
|
+
|
|
773
|
+
**โ ๏ธ Requires Manual Testing:**
|
|
774
|
+
- 2FA login flow (requires actual authenticator app)
|
|
775
|
+
- Device tracking (automatic on login)
|
|
776
|
+
- Push token registration (requires valid device)
|
|
777
|
+
- Social login (requires OAuth credentials from Google/Facebook)
|
|
778
|
+
- Backup codes usage during login
|
|
779
|
+
|
|
780
|
+
## ๐ Cross-Language Token Verification
|
|
781
|
+
|
|
782
|
+
Your other services (in any language) can verify tokens using the JWKS endpoint:
|
|
783
|
+
|
|
784
|
+
### C# Example
|
|
552
785
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
- Database schema migration
|
|
786
|
+
```csharp
|
|
787
|
+
using Microsoft.IdentityModel.Tokens;
|
|
788
|
+
using System.IdentityModel.Tokens.Jwt;
|
|
557
789
|
|
|
558
|
-
|
|
790
|
+
var jwks = await httpClient.GetStringAsync("https://auth.yourdomain.com/.well-known/jwks.json");
|
|
791
|
+
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(jwks);
|
|
792
|
+
|
|
793
|
+
var tokenHandler = new JwtSecurityTokenHandler();
|
|
794
|
+
var validationParameters = new TokenValidationParameters
|
|
795
|
+
{
|
|
796
|
+
ValidateIssuerSigningKey = true,
|
|
797
|
+
IssuerSigningKeys = keys.Keys,
|
|
798
|
+
ValidateIssuer = true,
|
|
799
|
+
ValidIssuer = "horizon-auth",
|
|
800
|
+
ValidateAudience = true,
|
|
801
|
+
ValidAudience = "horizon-api"
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
### Python Example
|
|
808
|
+
|
|
809
|
+
```python
|
|
810
|
+
import jwt
|
|
811
|
+
import requests
|
|
812
|
+
|
|
813
|
+
# Fetch JWKS
|
|
814
|
+
jwks_url = "https://auth.yourdomain.com/.well-known/jwks.json"
|
|
815
|
+
jwks = requests.get(jwks_url).json()
|
|
816
|
+
|
|
817
|
+
# Verify token
|
|
818
|
+
token = "your-jwt-token"
|
|
819
|
+
decoded = jwt.decode(
|
|
820
|
+
token,
|
|
821
|
+
jwks,
|
|
822
|
+
algorithms=["RS256"],
|
|
823
|
+
issuer="horizon-auth",
|
|
824
|
+
audience="horizon-api"
|
|
825
|
+
)
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
## ๐ Troubleshooting
|
|
831
|
+
|
|
832
|
+
### "AUTH_SERVICE_URL is required when AUTH_MODE=sso"
|
|
833
|
+
Set `AUTH_SERVICE_URL` in your ENV file when using SSO mode.
|
|
834
|
+
|
|
835
|
+
### "JWT_PRIVATE_KEY is required when AUTH_MODE=full"
|
|
836
|
+
Make sure you've set `JWT_PRIVATE_KEY` when running in full mode (auth service).
|
|
837
|
+
|
|
838
|
+
### "Invalid or expired access token"
|
|
839
|
+
- Check that your JWT keys are correctly configured
|
|
840
|
+
- Verify token hasn't expired (default 15 minutes)
|
|
841
|
+
- Ensure token isn't blacklisted in Redis
|
|
842
|
+
|
|
843
|
+
### "Redis connection error"
|
|
844
|
+
- Verify Redis is running: `docker ps` or check your cloud Redis service
|
|
845
|
+
- Check Redis connection settings in ENV
|
|
846
|
+
- Test connection: `redis-cli ping`
|
|
847
|
+
|
|
848
|
+
### "Token reuse detected"
|
|
849
|
+
- This is a security feature - someone tried to reuse a revoked refresh token
|
|
850
|
+
- All user tokens have been revoked for security
|
|
851
|
+
- User needs to login again
|
|
852
|
+
|
|
853
|
+
### "Device ID required for push tokens"
|
|
854
|
+
- Push tokens require a valid deviceId
|
|
855
|
+
- Devices are created automatically on login (not registration)
|
|
856
|
+
- Login first, then get device list, then register push token
|
|
857
|
+
|
|
858
|
+
### "Feature disabled" error
|
|
859
|
+
- The feature you're trying to use is not enabled
|
|
860
|
+
- Enable it via ENV: `ENABLE_2FA=true`, `ENABLE_DEVICE_MGMT=true`, etc.
|
|
861
|
+
|
|
862
|
+
### Cookies not working across subdomains
|
|
863
|
+
- Make sure `COOKIE_DOMAIN` starts with a dot: `.yourdomain.com`
|
|
864
|
+
- Verify `COOKIE_SECURE=true` in production (requires HTTPS)
|
|
865
|
+
- Check that all apps use the same cookie domain
|
|
866
|
+
|
|
867
|
+
---
|
|
868
|
+
|
|
869
|
+
## ๐ Documentation
|
|
870
|
+
|
|
871
|
+
- **[ENV-CONFIGURATION.md](https://github.com/OfekItzhaki/horizon-auth-platform/blob/main/ENV-CONFIGURATION.md)** - Complete environment variable reference
|
|
872
|
+
- **[DEPLOYMENT-EXAMPLES.md](https://github.com/OfekItzhaki/horizon-auth-platform/blob/main/DEPLOYMENT-EXAMPLES.md)** - Real-world deployment scenarios
|
|
873
|
+
- **[GitHub Repository](https://github.com/OfekItzhaki/horizon-auth-platform)** - Source code and issues
|
|
874
|
+
|
|
875
|
+
---
|
|
876
|
+
|
|
877
|
+
## ๐งช Testing Status
|
|
878
|
+
|
|
879
|
+
**โ
Fully Tested:**
|
|
880
|
+
- User registration and login
|
|
881
|
+
- JWT token generation and validation
|
|
882
|
+
- Refresh token rotation
|
|
883
|
+
- 2FA setup (QR code generation)
|
|
884
|
+
- Account deactivation/reactivation
|
|
885
|
+
- Login protection for deactivated accounts
|
|
886
|
+
- SSO mode token verification
|
|
887
|
+
|
|
888
|
+
**โ ๏ธ Requires Manual Testing:**
|
|
889
|
+
- 2FA login flow (requires actual authenticator app)
|
|
890
|
+
- Device tracking (automatic on login)
|
|
891
|
+
- Push token registration (requires valid device)
|
|
892
|
+
- Social login (requires OAuth credentials from Google/Facebook)
|
|
893
|
+
- Backup codes usage during login
|
|
894
|
+
|
|
895
|
+
---
|
|
896
|
+
|
|
897
|
+
## ๐ฆ What's New
|
|
898
|
+
|
|
899
|
+
### v0.4.0 (Latest)
|
|
900
|
+
- โจ **ENV-based configuration** - Zero code changes needed!
|
|
901
|
+
- โจ **Flexible deployment** - Same package for embedded and SSO modes
|
|
902
|
+
- ๐ Complete ENV variable documentation
|
|
903
|
+
- ๐ Real-world deployment examples
|
|
904
|
+
- ๐ง Improved configuration merging
|
|
905
|
+
|
|
906
|
+
### v0.3.0
|
|
907
|
+
- โจ Two-Factor Authentication (TOTP)
|
|
908
|
+
- โจ Device Management
|
|
909
|
+
- โจ Push Notifications
|
|
910
|
+
- โจ Account Management
|
|
911
|
+
- โจ Social Login (Google, Facebook)
|
|
912
|
+
|
|
913
|
+
### v0.2.1
|
|
914
|
+
- ๐ Fixed SSO mode JWT strategy
|
|
915
|
+
- ๐ง Dynamic module configuration
|
|
916
|
+
|
|
917
|
+
---
|
|
918
|
+
|
|
919
|
+
## ๐ License
|
|
559
920
|
|
|
560
921
|
MIT
|
|
561
922
|
|
|
562
|
-
|
|
923
|
+
---
|
|
924
|
+
|
|
925
|
+
## ๐ค Support
|
|
926
|
+
|
|
927
|
+
- **Issues:** [GitHub Issues](https://github.com/OfekItzhaki/horizon-auth-platform/issues)
|
|
928
|
+
- **Discussions:** [GitHub Discussions](https://github.com/OfekItzhaki/horizon-auth-platform/discussions)
|
|
563
929
|
|
|
564
|
-
|
|
565
|
-
|
|
930
|
+
---
|
|
931
|
+
|
|
932
|
+
## ๐จโ๐ป Author
|
|
566
933
|
|
|
567
|
-
|
|
934
|
+
Created by **Ofek Itzhaki**
|
|
935
|
+
|
|
936
|
+
---
|
|
568
937
|
|
|
569
|
-
|
|
938
|
+
**โญ If this package helps you, consider giving it a star on GitHub!**
|