securepool 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.
Files changed (126) hide show
  1. package/.dockerignore +7 -0
  2. package/.env.example +20 -0
  3. package/ARCHITECTURE.md +279 -0
  4. package/DEPLOYMENT.md +441 -0
  5. package/README.md +283 -0
  6. package/SETUP.md +388 -0
  7. package/apps/demo-backend/Dockerfile +33 -0
  8. package/apps/demo-backend/package.json +19 -0
  9. package/apps/demo-backend/src/index.ts +71 -0
  10. package/apps/demo-backend/tsconfig.json +8 -0
  11. package/apps/demo-frontend/.env.example +2 -0
  12. package/apps/demo-frontend/README.md +73 -0
  13. package/apps/demo-frontend/eslint.config.js +23 -0
  14. package/apps/demo-frontend/index.html +13 -0
  15. package/apps/demo-frontend/package.json +24 -0
  16. package/apps/demo-frontend/public/favicon.svg +1 -0
  17. package/apps/demo-frontend/public/icons.svg +24 -0
  18. package/apps/demo-frontend/src/App.tsx +33 -0
  19. package/apps/demo-frontend/src/assets/hero.png +0 -0
  20. package/apps/demo-frontend/src/assets/vite.svg +1 -0
  21. package/apps/demo-frontend/src/components/AccountSwitcher.tsx +373 -0
  22. package/apps/demo-frontend/src/components/ChangePasswordModal.tsx +128 -0
  23. package/apps/demo-frontend/src/index.css +272 -0
  24. package/apps/demo-frontend/src/main.tsx +10 -0
  25. package/apps/demo-frontend/src/pages/DashboardPage.tsx +141 -0
  26. package/apps/demo-frontend/src/pages/ForgotPasswordPage.tsx +183 -0
  27. package/apps/demo-frontend/src/pages/LoginPage.tsx +158 -0
  28. package/apps/demo-frontend/src/pages/OtpLoginPage.tsx +114 -0
  29. package/apps/demo-frontend/src/pages/SignupPage.tsx +95 -0
  30. package/apps/demo-frontend/src/pages/VerifyEmailPage.tsx +84 -0
  31. package/apps/demo-frontend/tsconfig.app.json +28 -0
  32. package/apps/demo-frontend/tsconfig.json +7 -0
  33. package/apps/demo-frontend/tsconfig.node.json +26 -0
  34. package/apps/demo-frontend/vite.config.ts +15 -0
  35. package/docs/DATABASE_MONGODB.md +280 -0
  36. package/docs/DATABASE_SQL.md +472 -0
  37. package/package.json +21 -0
  38. package/packages/api/package.json +30 -0
  39. package/packages/api/src/createSecurePool.ts +113 -0
  40. package/packages/api/src/index.ts +8 -0
  41. package/packages/api/src/middleware/authMiddleware.ts +26 -0
  42. package/packages/api/src/middleware/authorize.ts +24 -0
  43. package/packages/api/src/middleware/rateLimiter.ts +25 -0
  44. package/packages/api/src/middleware/tenantMiddleware.ts +12 -0
  45. package/packages/api/src/routes/authRoutes.ts +229 -0
  46. package/packages/api/src/routes/sessionRoutes.ts +30 -0
  47. package/packages/api/src/swagger.ts +529 -0
  48. package/packages/api/tsconfig.json +8 -0
  49. package/packages/application/package.json +16 -0
  50. package/packages/application/src/index.ts +17 -0
  51. package/packages/application/src/interfaces/IAuditLogRepository.ts +6 -0
  52. package/packages/application/src/interfaces/IAuthPlugin.ts +4 -0
  53. package/packages/application/src/interfaces/IEmailService.ts +3 -0
  54. package/packages/application/src/interfaces/IGoogleAuthService.ts +3 -0
  55. package/packages/application/src/interfaces/IOtpRepository.ts +8 -0
  56. package/packages/application/src/interfaces/IOtpService.ts +4 -0
  57. package/packages/application/src/interfaces/IPasswordHasher.ts +4 -0
  58. package/packages/application/src/interfaces/IRoleRepository.ts +8 -0
  59. package/packages/application/src/interfaces/ISessionRepository.ts +8 -0
  60. package/packages/application/src/interfaces/ITokenRepository.ts +9 -0
  61. package/packages/application/src/interfaces/ITokenService.ts +5 -0
  62. package/packages/application/src/interfaces/IUserRepository.ts +8 -0
  63. package/packages/application/src/services/AuthService.ts +323 -0
  64. package/packages/application/src/services/RefreshTokenService.ts +53 -0
  65. package/packages/application/tsconfig.json +8 -0
  66. package/packages/core/package.json +13 -0
  67. package/packages/core/src/entities/AuditLog.ts +11 -0
  68. package/packages/core/src/entities/OtpCode.ts +10 -0
  69. package/packages/core/src/entities/RefreshToken.ts +9 -0
  70. package/packages/core/src/entities/Role.ts +6 -0
  71. package/packages/core/src/entities/Session.ts +10 -0
  72. package/packages/core/src/entities/Tenant.ts +7 -0
  73. package/packages/core/src/entities/User.ts +10 -0
  74. package/packages/core/src/entities/UserRole.ts +6 -0
  75. package/packages/core/src/enums/index.ts +22 -0
  76. package/packages/core/src/index.ts +10 -0
  77. package/packages/core/tsconfig.json +8 -0
  78. package/packages/infrastructure/package.json +24 -0
  79. package/packages/infrastructure/src/email/NodemailerEmailService.ts +55 -0
  80. package/packages/infrastructure/src/google/GoogleAuthServiceImpl.ts +28 -0
  81. package/packages/infrastructure/src/hashing/BcryptHasher.ts +18 -0
  82. package/packages/infrastructure/src/index.ts +6 -0
  83. package/packages/infrastructure/src/jwt/JwtTokenService.ts +32 -0
  84. package/packages/infrastructure/src/otp/OtpServiceImpl.ts +50 -0
  85. package/packages/infrastructure/tsconfig.json +8 -0
  86. package/packages/persistence/package.json +22 -0
  87. package/packages/persistence/prisma/schema.prisma +88 -0
  88. package/packages/persistence/src/factory.ts +48 -0
  89. package/packages/persistence/src/index.ts +30 -0
  90. package/packages/persistence/src/mongo/connection.ts +9 -0
  91. package/packages/persistence/src/mongo/models/AuditLogModel.ts +21 -0
  92. package/packages/persistence/src/mongo/models/OtpModel.ts +19 -0
  93. package/packages/persistence/src/mongo/models/RefreshTokenModel.ts +17 -0
  94. package/packages/persistence/src/mongo/models/RoleModel.ts +11 -0
  95. package/packages/persistence/src/mongo/models/SessionModel.ts +19 -0
  96. package/packages/persistence/src/mongo/models/UserModel.ts +21 -0
  97. package/packages/persistence/src/mongo/models/UserRoleModel.ts +15 -0
  98. package/packages/persistence/src/mongo/repositories/MongoAuditLogRepository.ts +29 -0
  99. package/packages/persistence/src/mongo/repositories/MongoOtpRepository.ts +34 -0
  100. package/packages/persistence/src/mongo/repositories/MongoRoleRepository.ts +32 -0
  101. package/packages/persistence/src/mongo/repositories/MongoSessionRepository.ts +29 -0
  102. package/packages/persistence/src/mongo/repositories/MongoTokenRepository.ts +34 -0
  103. package/packages/persistence/src/mongo/repositories/MongoUserRepository.ts +37 -0
  104. package/packages/persistence/src/prisma/repositories/PrismaAuditLogRepository.ts +37 -0
  105. package/packages/persistence/src/prisma/repositories/PrismaOtpRepository.ts +43 -0
  106. package/packages/persistence/src/prisma/repositories/PrismaRoleRepository.ts +36 -0
  107. package/packages/persistence/src/prisma/repositories/PrismaSessionRepository.ts +39 -0
  108. package/packages/persistence/src/prisma/repositories/PrismaTokenRepository.ts +50 -0
  109. package/packages/persistence/src/prisma/repositories/PrismaUserRepository.ts +45 -0
  110. package/packages/persistence/tsconfig.json +8 -0
  111. package/packages/react-sdk/package.json +23 -0
  112. package/packages/react-sdk/src/components/GoogleLoginButton.tsx +54 -0
  113. package/packages/react-sdk/src/components/LoginForm.tsx +67 -0
  114. package/packages/react-sdk/src/components/OTPVerification.tsx +104 -0
  115. package/packages/react-sdk/src/components/SessionList.tsx +64 -0
  116. package/packages/react-sdk/src/components/SignupForm.tsx +95 -0
  117. package/packages/react-sdk/src/context/AuthContext.ts +4 -0
  118. package/packages/react-sdk/src/context/SecurePoolProvider.tsx +492 -0
  119. package/packages/react-sdk/src/hooks/useAuth.ts +11 -0
  120. package/packages/react-sdk/src/index.ts +22 -0
  121. package/packages/react-sdk/src/types.ts +53 -0
  122. package/packages/react-sdk/tsconfig.json +12 -0
  123. package/scripts/setup.js +285 -0
  124. package/scripts/setup.sh +309 -0
  125. package/tsconfig.base.json +16 -0
  126. package/turbo.json +16 -0
@@ -0,0 +1,472 @@
1
+ # SecurePool — PostgreSQL / MySQL Setup Guide
2
+
3
+ This guide covers using SecurePool with **PostgreSQL** or **MySQL** via Prisma ORM.
4
+
5
+ ---
6
+
7
+ ## How It Works
8
+
9
+ SecurePool uses **Prisma** as the ORM for SQL databases. When you set `type: "postgres"`, the persistence layer uses Prisma Client to interact with your SQL database.
10
+
11
+ ```ts
12
+ createSecurePool({
13
+ database: {
14
+ type: "postgres",
15
+ url: "postgresql://user:password@localhost:5432/securepool"
16
+ },
17
+ ...
18
+ })
19
+ ```
20
+
21
+ Unlike MongoDB, SQL databases **require a migration step** to create tables before first use.
22
+
23
+ ---
24
+
25
+ ## PostgreSQL
26
+
27
+ ### Option 1: Local PostgreSQL (Development)
28
+
29
+ #### Install
30
+
31
+ ```bash
32
+ # macOS
33
+ brew install postgresql@16
34
+ brew services start postgresql@16
35
+ ```
36
+
37
+ #### Create Database and User
38
+
39
+ ```bash
40
+ # Connect as default user
41
+ psql postgres
42
+ ```
43
+
44
+ ```sql
45
+ -- Create database
46
+ CREATE DATABASE securepool;
47
+
48
+ -- Create user with password
49
+ CREATE USER securepool_user WITH PASSWORD 'SecurePool@123';
50
+
51
+ -- Grant privileges
52
+ GRANT ALL PRIVILEGES ON DATABASE securepool TO securepool_user;
53
+
54
+ -- Connect to the database and grant schema access
55
+ \c securepool
56
+ GRANT ALL ON SCHEMA public TO securepool_user;
57
+
58
+ \q
59
+ ```
60
+
61
+ #### Verify Connection
62
+
63
+ ```bash
64
+ psql "postgresql://securepool_user:SecurePool%40123@localhost:5432/securepool"
65
+ ```
66
+
67
+ #### .env Configuration
68
+
69
+ ```env
70
+ DB_TYPE=postgres
71
+ DB_URL=postgresql://securepool_user:SecurePool%40123@localhost:5432/securepool
72
+ ```
73
+
74
+ ---
75
+
76
+ ### Option 2: Cloud PostgreSQL (Production)
77
+
78
+ #### Supabase (Free tier — recommended)
79
+
80
+ 1. Go to https://supabase.com → Create new project
81
+ 2. Go to **Settings** → **Database** → **Connection string**
82
+ 3. Copy the URI (starts with `postgresql://`)
83
+
84
+ ```env
85
+ DB_TYPE=postgres
86
+ DB_URL=postgresql://postgres:your-password@db.xxxx.supabase.co:5432/postgres
87
+ ```
88
+
89
+ #### AWS RDS
90
+
91
+ 1. Create an RDS PostgreSQL instance
92
+ 2. Use the endpoint provided by AWS
93
+
94
+ ```env
95
+ DB_TYPE=postgres
96
+ DB_URL=postgresql://admin:password@your-instance.region.rds.amazonaws.com:5432/securepool
97
+ ```
98
+
99
+ #### Railway (built-in PostgreSQL)
100
+
101
+ 1. In your Railway project, click **"+ New"** → **"Database"** → **"PostgreSQL"**
102
+ 2. Railway auto-sets `DATABASE_URL`
103
+
104
+ ```env
105
+ DB_TYPE=postgres
106
+ DB_URL=${{ Postgres.DATABASE_URL }}
107
+ ```
108
+
109
+ #### Neon (serverless PostgreSQL)
110
+
111
+ 1. Go to https://neon.tech → Create project
112
+ 2. Copy the connection string
113
+
114
+ ```env
115
+ DB_TYPE=postgres
116
+ DB_URL=postgresql://user:pass@ep-xxx.region.aws.neon.tech/securepool?sslmode=require
117
+ ```
118
+
119
+ ---
120
+
121
+ ## MySQL
122
+
123
+ ### Change Prisma Schema Provider
124
+
125
+ By default, the Prisma schema is set to `postgresql`. To use MySQL, update the schema:
126
+
127
+ Edit `packages/persistence/prisma/schema.prisma`:
128
+
129
+ ```prisma
130
+ datasource db {
131
+ provider = "mysql"
132
+ url = env("DATABASE_URL")
133
+ }
134
+ ```
135
+
136
+ ### Install MySQL
137
+
138
+ ```bash
139
+ # macOS
140
+ brew install mysql
141
+ brew services start mysql
142
+ ```
143
+
144
+ ### Create Database and User
145
+
146
+ ```bash
147
+ mysql -u root
148
+ ```
149
+
150
+ ```sql
151
+ CREATE DATABASE securepool;
152
+ CREATE USER 'securepool_user'@'localhost' IDENTIFIED BY 'SecurePool@123';
153
+ GRANT ALL PRIVILEGES ON securepool.* TO 'securepool_user'@'localhost';
154
+ FLUSH PRIVILEGES;
155
+ EXIT;
156
+ ```
157
+
158
+ ### .env Configuration
159
+
160
+ ```env
161
+ DB_TYPE=postgres
162
+ DB_URL=mysql://securepool_user:SecurePool%40123@localhost:3306/securepool
163
+ ```
164
+
165
+ > Note: Even though it's MySQL, use `DB_TYPE=postgres` because SecurePool uses Prisma for all SQL databases. The `postgres` type tells SecurePool to use the Prisma code path (not MongoDB).
166
+
167
+ ### Cloud MySQL Options
168
+
169
+ | Provider | URL Format |
170
+ |---|---|
171
+ | PlanetScale | `mysql://user:pass@region.connect.psdb.cloud/securepool?sslaccept=strict` |
172
+ | AWS RDS MySQL | `mysql://admin:pass@instance.region.rds.amazonaws.com:3306/securepool` |
173
+ | DigitalOcean | `mysql://user:pass@db-mysql-xxx.ondigitalocean.com:25060/securepool?ssl-mode=REQUIRED` |
174
+
175
+ ---
176
+
177
+ ## Running Migrations (Required for SQL)
178
+
179
+ After setting your `DB_URL`, you must run Prisma migrations to create the tables.
180
+
181
+ ### First Time Setup
182
+
183
+ ```bash
184
+ cd packages/persistence
185
+
186
+ # Set the DATABASE_URL for Prisma
187
+ export DATABASE_URL="postgresql://securepool_user:SecurePool%40123@localhost:5432/securepool"
188
+
189
+ # Generate Prisma Client
190
+ npx prisma generate
191
+
192
+ # Create and run migration
193
+ npx prisma migrate dev --name init
194
+ ```
195
+
196
+ ### Production Migration
197
+
198
+ ```bash
199
+ export DATABASE_URL="postgresql://user:pass@production-host:5432/securepool"
200
+ npx prisma migrate deploy
201
+ ```
202
+
203
+ ### View Database in Prisma Studio (GUI)
204
+
205
+ ```bash
206
+ cd packages/persistence
207
+ export DATABASE_URL="postgresql://..."
208
+ npx prisma studio
209
+ ```
210
+
211
+ Opens a web UI at http://localhost:5555 to browse your data.
212
+
213
+ ---
214
+
215
+ ## Database Schema
216
+
217
+ Prisma creates these tables automatically during migration:
218
+
219
+ ### Tables
220
+
221
+ ```
222
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
223
+ │ users │ │ roles │ │ user_roles │
224
+ ├──────────────┤ ├──────────────┤ ├──────────────┤
225
+ │ id (PK) │ │ id (PK) │ │ userId (FK) │
226
+ │ tenantId │◄───┐│ name │◄───┐│ roleId (FK) │
227
+ │ email │ ││ │ │└──────────────┘
228
+ │ passwordHash │ │└──────────────┘ │
229
+ │ isVerified │ │ │
230
+ │ createdAt │ │┌──────────────┐ │
231
+ └──────────────┘ ││ sessions │ │
232
+ │├──────────────┤ │
233
+ ││ id (PK) │ │
234
+ ├│ userId (FK) │ │
235
+ ││ device │ │
236
+ ││ ip │ │
237
+ ││ createdAt │ │
238
+ ││ isActive │ │
239
+ │└──────────────┘ │
240
+ │ │
241
+ │┌──────────────┐ │
242
+ ││ audit_logs │ │
243
+ │├──────────────┤ │
244
+ ││ id (PK) │ │
245
+ └│ userId (FK) │ │
246
+ │ tenantId │ │
247
+ │ action │ │
248
+ │ ip │ │
249
+ │ metadata (JSON│ │
250
+ │ timestamp │ │
251
+ └──────────────┘ │
252
+
253
+ ┌──────────────┐ ┌──────────────┐ │
254
+ │refresh_tokens │ │ otp_codes │ │
255
+ ├──────────────┤ ├──────────────┤ │
256
+ │ id (PK) │ │ id (PK) │ │
257
+ │ userId │ │ userId │ │
258
+ │ tokenHash │ │ code │ │
259
+ │ expiresAt │ │ expiresAt │ │
260
+ │ isRevoked │ │ attempts │ │
261
+ └──────────────┘ └──────────────┘ │
262
+ ```
263
+
264
+ ### Column Details
265
+
266
+ **users**
267
+
268
+ | Column | Type | Constraints |
269
+ |---|---|---|
270
+ | id | UUID | Primary key, auto-generated |
271
+ | tenantId | String | Indexed |
272
+ | email | String | Unique per tenant (`email + tenantId`) |
273
+ | passwordHash | String? | Nullable (Google SSO users have no password) |
274
+ | isVerified | Boolean | Default: false |
275
+ | createdAt | DateTime | Default: now() |
276
+
277
+ **roles**
278
+
279
+ | Column | Type | Constraints |
280
+ |---|---|---|
281
+ | id | UUID | Primary key |
282
+ | name | String | Unique (e.g., "admin", "user") |
283
+
284
+ **user_roles** (junction table)
285
+
286
+ | Column | Type | Constraints |
287
+ |---|---|---|
288
+ | userId | UUID | FK → users.id, cascade delete |
289
+ | roleId | UUID | FK → roles.id, cascade delete |
290
+ | | | Composite PK: (userId, roleId) |
291
+
292
+ **refresh_tokens**
293
+
294
+ | Column | Type | Constraints |
295
+ |---|---|---|
296
+ | id | UUID | Primary key |
297
+ | userId | String | Indexed |
298
+ | tokenHash | String | Indexed (for fast lookup) |
299
+ | expiresAt | DateTime | Token expiry (30 days) |
300
+ | isRevoked | Boolean | Default: false |
301
+
302
+ **otp_codes**
303
+
304
+ | Column | Type | Constraints |
305
+ |---|---|---|
306
+ | id | UUID | Primary key |
307
+ | userId | String | Indexed |
308
+ | code | String | 6-digit code |
309
+ | expiresAt | DateTime | Code expiry (10 minutes) |
310
+ | attempts | Int | Default: 0 (max 3 attempts) |
311
+
312
+ **sessions**
313
+
314
+ | Column | Type | Constraints |
315
+ |---|---|---|
316
+ | id | UUID | Primary key |
317
+ | userId | String | FK → users.id, indexed |
318
+ | device | String | e.g., "Chrome on Mac OS" |
319
+ | ip | String | Client IP address |
320
+ | createdAt | DateTime | Default: now() |
321
+ | isActive | Boolean | Default: true |
322
+
323
+ **audit_logs**
324
+
325
+ | Column | Type | Constraints |
326
+ |---|---|---|
327
+ | id | UUID | Primary key |
328
+ | userId | String | FK → users.id, indexed |
329
+ | tenantId | String | Indexed |
330
+ | action | String | e.g., "LOGIN_SUCCESS", "REGISTER" |
331
+ | ip | String | Client IP |
332
+ | metadata | JSON | Additional data |
333
+ | timestamp | DateTime | Default: now() |
334
+
335
+ ---
336
+
337
+ ## PostgreSQL URL Format Reference
338
+
339
+ | Scenario | URL Format |
340
+ |---|---|
341
+ | Local | `postgresql://user:pass@localhost:5432/securepool` |
342
+ | With SSL | `postgresql://user:pass@host:5432/securepool?sslmode=require` |
343
+ | Supabase | `postgresql://postgres:pass@db.xxx.supabase.co:5432/postgres` |
344
+ | AWS RDS | `postgresql://admin:pass@instance.region.rds.amazonaws.com:5432/securepool` |
345
+ | Neon | `postgresql://user:pass@ep-xxx.neon.tech/securepool?sslmode=require` |
346
+ | Railway | Auto-set via `${{ Postgres.DATABASE_URL }}` |
347
+
348
+ ---
349
+
350
+ ## Switching from MongoDB to PostgreSQL
351
+
352
+ If you're already running SecurePool with MongoDB and want to switch:
353
+
354
+ ### Step 1: Update `.env`
355
+
356
+ ```env
357
+ # Before (MongoDB)
358
+ DB_TYPE=mongo
359
+ DB_URL=mongodb://localhost:27017/securepool
360
+
361
+ # After (PostgreSQL)
362
+ DB_TYPE=postgres
363
+ DB_URL=postgresql://securepool_user:SecurePool%40123@localhost:5432/securepool
364
+ ```
365
+
366
+ ### Step 2: Run Migrations
367
+
368
+ ```bash
369
+ cd packages/persistence
370
+ export DATABASE_URL="postgresql://securepool_user:SecurePool%40123@localhost:5432/securepool"
371
+ npx prisma migrate dev --name init
372
+ ```
373
+
374
+ ### Step 3: Restart Backend
375
+
376
+ ```bash
377
+ cd apps/demo-backend
378
+ npx ts-node src/index.ts
379
+ ```
380
+
381
+ That's it. No code changes needed. All auth features (login, OTP, sessions, etc.) work the same way.
382
+
383
+ ### Data Migration
384
+
385
+ SecurePool does **not** auto-migrate data between databases. If you need to move existing users from MongoDB to PostgreSQL, you'll need to export from Mongo and import to PostgreSQL manually.
386
+
387
+ ---
388
+
389
+ ## Backup & Restore
390
+
391
+ ### PostgreSQL
392
+
393
+ ```bash
394
+ # Backup
395
+ pg_dump "postgresql://user:pass@localhost:5432/securepool" > backup.sql
396
+
397
+ # Restore
398
+ psql "postgresql://user:pass@localhost:5432/securepool" < backup.sql
399
+ ```
400
+
401
+ ### MySQL
402
+
403
+ ```bash
404
+ # Backup
405
+ mysqldump -u securepool_user -p securepool > backup.sql
406
+
407
+ # Restore
408
+ mysql -u securepool_user -p securepool < backup.sql
409
+ ```
410
+
411
+ ---
412
+
413
+ ## Troubleshooting
414
+
415
+ ### "relation does not exist"
416
+
417
+ Migrations haven't been run:
418
+
419
+ ```bash
420
+ cd packages/persistence
421
+ npx prisma migrate dev
422
+ ```
423
+
424
+ ### "authentication failed for user"
425
+
426
+ Check your credentials and ensure the user has access to the database:
427
+
428
+ ```bash
429
+ psql "postgresql://user:pass@localhost:5432/securepool"
430
+ ```
431
+
432
+ ### "SSL required"
433
+
434
+ Cloud providers often require SSL. Add to your URL:
435
+
436
+ ```
437
+ ?sslmode=require
438
+ ```
439
+
440
+ ### "Prisma Client not generated"
441
+
442
+ ```bash
443
+ cd packages/persistence
444
+ npx prisma generate
445
+ ```
446
+
447
+ ### Reset database (delete all data)
448
+
449
+ ```bash
450
+ cd packages/persistence
451
+ npx prisma migrate reset
452
+ ```
453
+
454
+ > Warning: This deletes ALL data. Use only in development.
455
+
456
+ ---
457
+
458
+ ## Comparison: MongoDB vs PostgreSQL
459
+
460
+ | Feature | MongoDB | PostgreSQL |
461
+ |---|---|---|
462
+ | Setup complexity | Easier (no migrations) | Requires `prisma migrate` |
463
+ | Schema enforcement | Flexible (schemaless) | Strict (type-safe) |
464
+ | Joins | Manual (application-level) | Native SQL joins |
465
+ | Transactions | Supported (4.0+) | Full ACID support |
466
+ | JSON support | Native (BSON) | JSONB column type |
467
+ | Best for | Rapid prototyping, flexibility | Data integrity, complex queries |
468
+ | Free cloud | Atlas M0 (512MB) | Supabase (500MB), Neon (512MB) |
469
+ | ORM used | Mongoose | Prisma |
470
+ | Code changes needed | None | None |
471
+
472
+ Both are fully supported. Choose based on your team's preference and requirements.
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "securepool",
3
+ "version": "1.0.0",
4
+ "packageManager": "npm@11.6.2",
5
+ "workspaces": [
6
+ "packages/*",
7
+ "apps/*"
8
+ ],
9
+ "scripts": {
10
+ "setup": "node scripts/setup.js",
11
+ "build": "turbo run build",
12
+ "dev": "turbo run dev",
13
+ "clean": "turbo run clean",
14
+ "start:backend": "cd apps/demo-backend && npx ts-node src/index.ts",
15
+ "start:frontend": "cd apps/demo-frontend && npx vite"
16
+ },
17
+ "devDependencies": {
18
+ "turbo": "^2.0.0",
19
+ "typescript": "^5.5.0"
20
+ }
21
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@securepool/api",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "clean": "rm -rf dist"
9
+ },
10
+ "dependencies": {
11
+ "@securepool/core": "1.0.0",
12
+ "@securepool/application": "1.0.0",
13
+ "@securepool/infrastructure": "1.0.0",
14
+ "@securepool/persistence": "1.0.0",
15
+ "express": "^4.21.0",
16
+ "cors": "^2.8.5",
17
+ "express-rate-limit": "^7.4.0",
18
+ "helmet": "^7.1.0",
19
+ "ua-parser-js": "^1.0.38",
20
+ "dotenv": "^16.4.5",
21
+ "swagger-ui-express": "^5.0.1"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.5.0",
25
+ "@types/express": "^4.17.21",
26
+ "@types/cors": "^2.8.17",
27
+ "@types/ua-parser-js": "^0.7.39",
28
+ "@types/swagger-ui-express": "^4.1.6"
29
+ }
30
+ }
@@ -0,0 +1,113 @@
1
+ import express, { Express } from "express";
2
+ import cors from "cors";
3
+ import helmet from "helmet";
4
+ import { AuthService, RefreshTokenService } from "@securepool/application";
5
+ import { JwtTokenService, BcryptHasher, OtpServiceImpl, GoogleAuthServiceImpl, NodemailerEmailService } from "@securepool/infrastructure";
6
+ import { createRepositories } from "@securepool/persistence";
7
+ import { createAuthMiddleware } from "./middleware/authMiddleware";
8
+ import { createAuthorize } from "./middleware/authorize";
9
+ import { tenantMiddleware } from "./middleware/tenantMiddleware";
10
+ import { apiRateLimiter } from "./middleware/rateLimiter";
11
+ import { createAuthRoutes } from "./routes/authRoutes";
12
+ import { createSessionRoutes } from "./routes/sessionRoutes";
13
+ import { setupSwagger } from "./swagger";
14
+
15
+ export interface SecurePoolConfig {
16
+ database: {
17
+ type: "mongo" | "postgres";
18
+ url: string;
19
+ };
20
+ jwt: {
21
+ privateKey: string;
22
+ publicKey: string;
23
+ accessTokenExpirySeconds?: number;
24
+ };
25
+ google?: {
26
+ clientId: string;
27
+ };
28
+ otp?: {
29
+ maxAttempts?: number;
30
+ expiryMinutes?: number;
31
+ };
32
+ email?: {
33
+ host: string;
34
+ port: number;
35
+ secure: boolean;
36
+ user: string;
37
+ pass: string;
38
+ from: string;
39
+ };
40
+ security?: {
41
+ enableRateLimit?: boolean;
42
+ corsOrigins?: string | string[];
43
+ };
44
+ }
45
+
46
+ export async function createSecurePool(config: SecurePoolConfig) {
47
+ // Initialize repositories
48
+ const repos = await createRepositories(config.database);
49
+
50
+ // Initialize infrastructure services
51
+ const hasher = new BcryptHasher();
52
+ const tokenService = new JwtTokenService(config.jwt.privateKey, config.jwt.publicKey, config.jwt.accessTokenExpirySeconds);
53
+
54
+ const otpService = config.otp !== undefined
55
+ ? new OtpServiceImpl(repos.otpRepo, config.otp)
56
+ : new OtpServiceImpl(repos.otpRepo);
57
+
58
+ const googleAuthService = config.google
59
+ ? new GoogleAuthServiceImpl(config.google.clientId)
60
+ : undefined;
61
+
62
+ // Initialize email service
63
+ const emailService = config.email
64
+ ? new NodemailerEmailService({
65
+ host: config.email.host,
66
+ port: config.email.port,
67
+ secure: config.email.secure,
68
+ auth: { user: config.email.user, pass: config.email.pass },
69
+ from: config.email.from,
70
+ })
71
+ : undefined;
72
+
73
+ // Initialize application services
74
+ const authService = new AuthService(repos.userRepo, hasher, tokenService, repos.tokenRepo, otpService, repos.auditLogRepo, emailService);
75
+ const refreshTokenService = new RefreshTokenService(repos.tokenRepo, tokenService);
76
+
77
+ // Create Express app
78
+ const app: Express = express();
79
+ app.use(express.json());
80
+ app.use(helmet({
81
+ contentSecurityPolicy: false, // Allow Swagger UI to load
82
+ crossOriginEmbedderPolicy: false,
83
+ }));
84
+ app.use(cors({ origin: config.security?.corsOrigins || "*" }));
85
+
86
+ if (config.security?.enableRateLimit !== false) {
87
+ app.use(apiRateLimiter);
88
+ }
89
+
90
+ // Middleware factories
91
+ const authMiddleware = createAuthMiddleware(tokenService);
92
+ const authorize = createAuthorize(repos.roleRepo);
93
+
94
+ // Routes
95
+ app.use("/auth", tenantMiddleware, createAuthRoutes(authService, refreshTokenService, repos.sessionRepo, repos.auditLogRepo, tokenService, authMiddleware));
96
+ app.use("/sessions", authMiddleware, createSessionRoutes(repos.sessionRepo));
97
+
98
+ // Health check
99
+ app.get("/health", (_req, res) => { res.json({ status: "ok" }); });
100
+
101
+ // Swagger API docs
102
+ setupSwagger(app);
103
+
104
+ return {
105
+ app,
106
+ authService,
107
+ refreshTokenService,
108
+ authMiddleware,
109
+ authorize,
110
+ tokenService,
111
+ repositories: repos,
112
+ };
113
+ }
@@ -0,0 +1,8 @@
1
+ export { createSecurePool, SecurePoolConfig } from "./createSecurePool";
2
+ export { createAuthMiddleware, AuthenticatedRequest } from "./middleware/authMiddleware";
3
+ export { tenantMiddleware } from "./middleware/tenantMiddleware";
4
+ export { createAuthorize } from "./middleware/authorize";
5
+ export { loginRateLimiter, apiRateLimiter, otpRateLimiter } from "./middleware/rateLimiter";
6
+ export { createAuthRoutes } from "./routes/authRoutes";
7
+ export { createSessionRoutes } from "./routes/sessionRoutes";
8
+ export { setupSwagger } from "./swagger";
@@ -0,0 +1,26 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { ITokenService } from "@securepool/application";
3
+
4
+ export interface AuthenticatedRequest extends Request {
5
+ user?: { userId: string; tenantId: string };
6
+ tenantId?: string;
7
+ }
8
+
9
+ export function createAuthMiddleware(tokenService: ITokenService) {
10
+ return async (req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> => {
11
+ const authHeader = req.headers.authorization;
12
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
13
+ res.status(401).json({ error: "Missing or invalid authorization header" });
14
+ return;
15
+ }
16
+
17
+ const token = authHeader.split(" ")[1];
18
+ try {
19
+ const payload = await tokenService.verifyAccessToken(token);
20
+ req.user = { userId: payload.sub, tenantId: payload.tenantId };
21
+ next();
22
+ } catch {
23
+ res.status(401).json({ error: "Invalid or expired token" });
24
+ }
25
+ };
26
+ }
@@ -0,0 +1,24 @@
1
+ import { Response, NextFunction } from "express";
2
+ import { AuthenticatedRequest } from "./authMiddleware";
3
+ import { IRoleRepository } from "@securepool/application";
4
+
5
+ export function createAuthorize(roleRepo: IRoleRepository) {
6
+ return (allowedRoles: string[]) => {
7
+ return async (req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> => {
8
+ if (!req.user) {
9
+ res.status(401).json({ error: "Not authenticated" });
10
+ return;
11
+ }
12
+
13
+ const userRoles = await roleRepo.getUserRoles(req.user.userId);
14
+ const hasRole = userRoles.some(role => allowedRoles.includes(role.name));
15
+
16
+ if (!hasRole) {
17
+ res.status(403).json({ error: "Forbidden: insufficient permissions" });
18
+ return;
19
+ }
20
+
21
+ next();
22
+ };
23
+ };
24
+ }