@ofeklabs/horizon-auth 0.1.0 → 0.2.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
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
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. Deploy once, use everywhere.
|
|
4
|
+
|
|
5
|
+
## Use Cases
|
|
6
|
+
|
|
7
|
+
### 1. Portfolio SSO (Recommended)
|
|
8
|
+
Deploy one auth service, use it across all your projects. Users sign in once and access everything.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
auth.yourdomain.com (Auth Service)
|
|
12
|
+
↓ Shared Authentication
|
|
13
|
+
├── project1.yourdomain.com
|
|
14
|
+
├── project2.yourdomain.com
|
|
15
|
+
└── project3.yourdomain.com
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 2. Embedded Auth
|
|
19
|
+
Each application has its own isolated authentication.
|
|
4
20
|
|
|
5
21
|
## Features
|
|
6
22
|
|
|
@@ -13,7 +29,73 @@ Production-ready NestJS authentication module with 2026 security standards. Add
|
|
|
13
29
|
- 🎯 **Type-Safe**: Full TypeScript support
|
|
14
30
|
- 📦 **Zero Config**: Sensible defaults, fully customizable
|
|
15
31
|
|
|
16
|
-
## Quick Start
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### Portfolio SSO Setup (Recommended)
|
|
35
|
+
|
|
36
|
+
Deploy one auth service, use it across all your projects.
|
|
37
|
+
|
|
38
|
+
#### 1. Deploy Auth Service (One Time)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Clone and deploy packages/horizon-auth to auth.yourdomain.com
|
|
42
|
+
npm install
|
|
43
|
+
npm run build
|
|
44
|
+
npm run start:prod
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Environment Variables**:
|
|
48
|
+
```env
|
|
49
|
+
DATABASE_URL=postgresql://...
|
|
50
|
+
REDIS_HOST=your-redis-host
|
|
51
|
+
JWT_PRIVATE_KEY=<your-private-key>
|
|
52
|
+
JWT_PUBLIC_KEY=<your-public-key>
|
|
53
|
+
COOKIE_DOMAIN=.yourdomain.com
|
|
54
|
+
NODE_ENV=production
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### 2. Use in Your Projects
|
|
58
|
+
|
|
59
|
+
For each project:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install @ofeklabs/horizon-auth
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// app.module.ts
|
|
67
|
+
HorizonAuthModule.forRoot({
|
|
68
|
+
ssoMode: true,
|
|
69
|
+
authServiceUrl: process.env.AUTH_SERVICE_URL,
|
|
70
|
+
jwt: {
|
|
71
|
+
publicKey: process.env.JWT_PUBLIC_KEY,
|
|
72
|
+
},
|
|
73
|
+
cookie: {
|
|
74
|
+
domain: process.env.COOKIE_DOMAIN,
|
|
75
|
+
secure: process.env.NODE_ENV === 'production',
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```env
|
|
81
|
+
# .env
|
|
82
|
+
AUTH_SERVICE_URL=https://auth.yourdomain.com
|
|
83
|
+
JWT_PUBLIC_KEY=<paste-public-key>
|
|
84
|
+
COOKIE_DOMAIN=.yourdomain.com
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Deploy to:
|
|
88
|
+
- project1.yourdomain.com
|
|
89
|
+
- project2.yourdomain.com
|
|
90
|
+
- project3.yourdomain.com
|
|
91
|
+
|
|
92
|
+
Users sign in once, access all projects.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Embedded Auth Setup (Alternative)
|
|
97
|
+
|
|
98
|
+
Each application has its own authentication.
|
|
17
99
|
|
|
18
100
|
### 1. Install
|
|
19
101
|
|
|
@@ -50,10 +132,12 @@ import { join } from 'path';
|
|
|
50
132
|
redis: {
|
|
51
133
|
host: process.env.REDIS_HOST || 'localhost',
|
|
52
134
|
port: parseInt(process.env.REDIS_PORT) || 6379,
|
|
135
|
+
password: process.env.REDIS_PASSWORD,
|
|
53
136
|
},
|
|
54
137
|
jwt: {
|
|
55
|
-
|
|
56
|
-
|
|
138
|
+
// For production: use environment variables
|
|
139
|
+
privateKey: process.env.JWT_PRIVATE_KEY || readFileSync(join(__dirname, '../certs/private.pem'), 'utf8'),
|
|
140
|
+
publicKey: process.env.JWT_PUBLIC_KEY || readFileSync(join(__dirname, '../certs/public.pem'), 'utf8'),
|
|
57
141
|
},
|
|
58
142
|
}),
|
|
59
143
|
],
|
|
@@ -317,10 +401,136 @@ REDIS_HOST=localhost
|
|
|
317
401
|
REDIS_PORT=6379
|
|
318
402
|
REDIS_PASSWORD=your_redis_password
|
|
319
403
|
|
|
404
|
+
# JWT Keys (for production - use multiline env vars)
|
|
405
|
+
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"
|
|
406
|
+
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----"
|
|
407
|
+
|
|
320
408
|
# Application
|
|
321
409
|
NODE_ENV=production
|
|
410
|
+
PORT=3000
|
|
411
|
+
|
|
412
|
+
# Optional: SSO Mode
|
|
413
|
+
AUTH_SERVICE_URL=https://auth.ofeklabs.dev
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Production Deployment
|
|
417
|
+
|
|
418
|
+
### Portfolio SSO Architecture (Recommended)
|
|
419
|
+
|
|
420
|
+
Perfect for portfolios, demo sites, or related projects.
|
|
421
|
+
|
|
422
|
+
**Benefits**:
|
|
423
|
+
- One login for all projects
|
|
424
|
+
- Consistent user experience
|
|
425
|
+
- No duplicate auth code
|
|
426
|
+
- Single point of maintenance
|
|
427
|
+
- Professional appearance
|
|
428
|
+
|
|
429
|
+
**Setup**:
|
|
430
|
+
|
|
431
|
+
1. Deploy auth service to `auth.yourdomain.com`
|
|
432
|
+
2. Install package in each project
|
|
433
|
+
3. Configure SSO mode with `AUTH_SERVICE_URL`
|
|
434
|
+
4. Deploy projects to subdomains
|
|
435
|
+
|
|
436
|
+
See [PRODUCTION-DEPLOYMENT.md](./PRODUCTION-DEPLOYMENT.md) for complete guide.
|
|
437
|
+
|
|
438
|
+
### Deploy to Render
|
|
439
|
+
|
|
440
|
+
#### 1. Deploy Auth Service
|
|
441
|
+
|
|
442
|
+
Create a new Web Service on Render:
|
|
443
|
+
|
|
444
|
+
**Environment Variables**:
|
|
445
|
+
```env
|
|
446
|
+
DATABASE_URL=<your-postgres-url>
|
|
447
|
+
REDIS_HOST=<your-redis-host>
|
|
448
|
+
REDIS_PORT=6379
|
|
449
|
+
REDIS_PASSWORD=<your-redis-password>
|
|
450
|
+
JWT_PRIVATE_KEY=<paste-private-key-with-\n>
|
|
451
|
+
JWT_PUBLIC_KEY=<paste-public-key-with-\n>
|
|
452
|
+
NODE_ENV=production
|
|
453
|
+
COOKIE_DOMAIN=.ofeklabs.dev
|
|
454
|
+
COOKIE_SECURE=true
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Build Command**: `npm install && npm run build`
|
|
458
|
+
|
|
459
|
+
**Start Command**: `npm run start:prod`
|
|
460
|
+
|
|
461
|
+
#### 2. Deploy Your Apps (SSO Mode)
|
|
462
|
+
|
|
463
|
+
For each app (tasks, CRM, analytics):
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
// app.module.ts
|
|
467
|
+
HorizonAuthModule.forRoot({
|
|
468
|
+
ssoMode: true,
|
|
469
|
+
authServiceUrl: process.env.AUTH_SERVICE_URL || 'https://auth.ofeklabs.dev',
|
|
470
|
+
jwt: {
|
|
471
|
+
publicKey: process.env.JWT_PUBLIC_KEY,
|
|
472
|
+
},
|
|
473
|
+
cookie: {
|
|
474
|
+
domain: process.env.COOKIE_DOMAIN || '.ofeklabs.dev',
|
|
475
|
+
secure: process.env.NODE_ENV === 'production',
|
|
476
|
+
},
|
|
477
|
+
})
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Environment Variables**:
|
|
481
|
+
```env
|
|
482
|
+
AUTH_SERVICE_URL=https://auth.ofeklabs.dev
|
|
483
|
+
JWT_PUBLIC_KEY=<paste-public-key-with-\n>
|
|
484
|
+
COOKIE_DOMAIN=.ofeklabs.dev
|
|
485
|
+
NODE_ENV=production
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### 3. Configure Custom Domains
|
|
489
|
+
|
|
490
|
+
On Render, add custom domains:
|
|
491
|
+
- Auth service: `auth.ofeklabs.dev`
|
|
492
|
+
- Tasks app: `tasks.ofeklabs.dev`
|
|
493
|
+
- CRM app: `crm.ofeklabs.dev`
|
|
494
|
+
|
|
495
|
+
All apps will share authentication via cookies! 🎉
|
|
496
|
+
|
|
497
|
+
### Deploy to AWS/Docker
|
|
498
|
+
|
|
499
|
+
```dockerfile
|
|
500
|
+
FROM node:18-alpine
|
|
501
|
+
|
|
502
|
+
WORKDIR /app
|
|
503
|
+
|
|
504
|
+
COPY package*.json ./
|
|
505
|
+
RUN npm ci --only=production
|
|
506
|
+
|
|
507
|
+
COPY . .
|
|
508
|
+
RUN npm run build
|
|
509
|
+
|
|
510
|
+
EXPOSE 3000
|
|
511
|
+
|
|
512
|
+
CMD ["node", "dist/main"]
|
|
322
513
|
```
|
|
323
514
|
|
|
515
|
+
```bash
|
|
516
|
+
# Build and run
|
|
517
|
+
docker build -t horizon-auth .
|
|
518
|
+
docker run -p 3000:3000 \
|
|
519
|
+
-e DATABASE_URL="postgresql://..." \
|
|
520
|
+
-e REDIS_HOST="redis" \
|
|
521
|
+
-e JWT_PRIVATE_KEY="$(cat certs/private.pem)" \
|
|
522
|
+
-e JWT_PUBLIC_KEY="$(cat certs/public.pem)" \
|
|
523
|
+
horizon-auth
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Environment Variable Best Practices
|
|
527
|
+
|
|
528
|
+
1. **Never commit keys to Git**
|
|
529
|
+
2. **Use secrets manager** (AWS Secrets Manager, Render Secrets)
|
|
530
|
+
3. **Rotate keys periodically**
|
|
531
|
+
4. **Use different keys** for dev/staging/production
|
|
532
|
+
5. **Store keys as multiline strings** with `\n` for newlines
|
|
533
|
+
|
|
324
534
|
## Troubleshooting
|
|
325
535
|
|
|
326
536
|
### "Invalid or expired access token"
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
export interface HorizonAuthConfig {
|
|
2
|
-
|
|
2
|
+
ssoMode?: boolean;
|
|
3
|
+
authServiceUrl?: string;
|
|
4
|
+
database?: {
|
|
3
5
|
url: string;
|
|
4
6
|
};
|
|
5
|
-
redis
|
|
7
|
+
redis?: {
|
|
6
8
|
host: string;
|
|
7
9
|
port: number;
|
|
8
10
|
password?: string;
|
|
9
11
|
db?: number;
|
|
10
12
|
};
|
|
11
13
|
jwt: {
|
|
12
|
-
privateKey
|
|
14
|
+
privateKey?: string;
|
|
13
15
|
publicKey: string;
|
|
14
16
|
accessTokenExpiry?: string;
|
|
15
17
|
refreshTokenExpiry?: string;
|
|
@@ -17,6 +19,11 @@ export interface HorizonAuthConfig {
|
|
|
17
19
|
audience?: string;
|
|
18
20
|
kid?: string;
|
|
19
21
|
};
|
|
22
|
+
cookie?: {
|
|
23
|
+
domain?: string;
|
|
24
|
+
secure?: boolean;
|
|
25
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
26
|
+
};
|
|
20
27
|
multiTenant?: {
|
|
21
28
|
enabled: boolean;
|
|
22
29
|
tenantIdExtractor?: 'header' | 'subdomain' | 'custom';
|
|
@@ -31,6 +31,16 @@ let HorizonAuthModule = HorizonAuthModule_1 = class HorizonAuthModule {
|
|
|
31
31
|
useClass: jwt_auth_guard_1.JwtAuthGuard,
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
if (finalConfig.ssoMode) {
|
|
35
|
+
return {
|
|
36
|
+
module: HorizonAuthModule_1,
|
|
37
|
+
imports: [
|
|
38
|
+
auth_module_1.AuthModule,
|
|
39
|
+
],
|
|
40
|
+
providers,
|
|
41
|
+
exports: ['HORIZON_AUTH_CONFIG', auth_module_1.AuthModule],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
34
44
|
return {
|
|
35
45
|
module: HorizonAuthModule_1,
|
|
36
46
|
imports: [
|
|
@@ -63,14 +73,26 @@ let HorizonAuthModule = HorizonAuthModule_1 = class HorizonAuthModule {
|
|
|
63
73
|
};
|
|
64
74
|
}
|
|
65
75
|
static validateConfig(config) {
|
|
76
|
+
if (config.ssoMode) {
|
|
77
|
+
if (!config.jwt?.publicKey) {
|
|
78
|
+
throw new Error('HorizonAuth (SSO Mode): jwt.publicKey is required');
|
|
79
|
+
}
|
|
80
|
+
if (!config.authServiceUrl) {
|
|
81
|
+
throw new Error('HorizonAuth (SSO Mode): authServiceUrl is required');
|
|
82
|
+
}
|
|
83
|
+
if (!config.jwt.publicKey.includes('BEGIN')) {
|
|
84
|
+
throw new Error('HorizonAuth: JWT public key must be in PEM format');
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
66
88
|
if (!config.database?.url) {
|
|
67
|
-
throw new Error('HorizonAuth: database.url is required');
|
|
89
|
+
throw new Error('HorizonAuth: database.url is required (or enable ssoMode)');
|
|
68
90
|
}
|
|
69
91
|
if (!config.redis?.host || !config.redis?.port) {
|
|
70
|
-
throw new Error('HorizonAuth: redis.host and redis.port are required');
|
|
92
|
+
throw new Error('HorizonAuth: redis.host and redis.port are required (or enable ssoMode)');
|
|
71
93
|
}
|
|
72
94
|
if (!config.jwt?.privateKey || !config.jwt?.publicKey) {
|
|
73
|
-
throw new Error('HorizonAuth: jwt.privateKey and jwt.publicKey are required');
|
|
95
|
+
throw new Error('HorizonAuth: jwt.privateKey and jwt.publicKey are required (or enable ssoMode with only publicKey)');
|
|
74
96
|
}
|
|
75
97
|
if (!config.jwt.privateKey.includes('BEGIN') || !config.jwt.publicKey.includes('BEGIN')) {
|
|
76
98
|
throw new Error('HorizonAuth: JWT keys must be in PEM format');
|
|
@@ -79,6 +101,7 @@ let HorizonAuthModule = HorizonAuthModule_1 = class HorizonAuthModule {
|
|
|
79
101
|
static applyDefaults(config) {
|
|
80
102
|
return {
|
|
81
103
|
...config,
|
|
104
|
+
ssoMode: config.ssoMode || false,
|
|
82
105
|
jwt: {
|
|
83
106
|
...config.jwt,
|
|
84
107
|
accessTokenExpiry: config.jwt.accessTokenExpiry || '15m',
|
|
@@ -87,6 +110,11 @@ let HorizonAuthModule = HorizonAuthModule_1 = class HorizonAuthModule {
|
|
|
87
110
|
audience: config.jwt.audience || 'horizon-api',
|
|
88
111
|
kid: config.jwt.kid || 'horizon-auth-key-1',
|
|
89
112
|
},
|
|
113
|
+
cookie: {
|
|
114
|
+
domain: config.cookie?.domain || config.security?.cookieDomain,
|
|
115
|
+
secure: config.cookie?.secure ?? config.security?.cookieSecure ?? (process.env.NODE_ENV === 'production'),
|
|
116
|
+
sameSite: config.cookie?.sameSite || 'lax',
|
|
117
|
+
},
|
|
90
118
|
multiTenant: {
|
|
91
119
|
enabled: config.multiTenant?.enabled || false,
|
|
92
120
|
tenantIdExtractor: config.multiTenant?.tenantIdExtractor || 'header',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"horizon-auth.module.js","sourceRoot":"","sources":["../../src/lib/horizon-auth.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAyE;AACzE,uCAAyC;AAEzC,qDAAiD;AACjD,wDAAoD;AACpD,wDAAoD;AACpD,2DAAuD;AACvD,kEAA6D;AAItD,IAAM,iBAAiB,yBAAvB,MAAM,iBAAiB;IAM5B,MAAM,CAAC,OAAO,CAAC,MAAyB;QAEtC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAG5B,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAE/C,MAAM,SAAS,GAAe;YAC5B;gBACE,OAAO,EAAE,qBAAqB;gBAC9B,QAAQ,EAAE,WAAW;aACtB;SACF,CAAC;QAGF,IAAI,WAAW,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC;YAC9C,SAAS,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,6BAAY;aACvB,CAAC,CAAC;QACL,CAAC;
|
|
1
|
+
{"version":3,"file":"horizon-auth.module.js","sourceRoot":"","sources":["../../src/lib/horizon-auth.module.ts"],"names":[],"mappings":"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"}
|