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.
- package/.dockerignore +7 -0
- package/.env.example +20 -0
- package/ARCHITECTURE.md +279 -0
- package/DEPLOYMENT.md +441 -0
- package/README.md +283 -0
- package/SETUP.md +388 -0
- package/apps/demo-backend/Dockerfile +33 -0
- package/apps/demo-backend/package.json +19 -0
- package/apps/demo-backend/src/index.ts +71 -0
- package/apps/demo-backend/tsconfig.json +8 -0
- package/apps/demo-frontend/.env.example +2 -0
- package/apps/demo-frontend/README.md +73 -0
- package/apps/demo-frontend/eslint.config.js +23 -0
- package/apps/demo-frontend/index.html +13 -0
- package/apps/demo-frontend/package.json +24 -0
- package/apps/demo-frontend/public/favicon.svg +1 -0
- package/apps/demo-frontend/public/icons.svg +24 -0
- package/apps/demo-frontend/src/App.tsx +33 -0
- package/apps/demo-frontend/src/assets/hero.png +0 -0
- package/apps/demo-frontend/src/assets/vite.svg +1 -0
- package/apps/demo-frontend/src/components/AccountSwitcher.tsx +373 -0
- package/apps/demo-frontend/src/components/ChangePasswordModal.tsx +128 -0
- package/apps/demo-frontend/src/index.css +272 -0
- package/apps/demo-frontend/src/main.tsx +10 -0
- package/apps/demo-frontend/src/pages/DashboardPage.tsx +141 -0
- package/apps/demo-frontend/src/pages/ForgotPasswordPage.tsx +183 -0
- package/apps/demo-frontend/src/pages/LoginPage.tsx +158 -0
- package/apps/demo-frontend/src/pages/OtpLoginPage.tsx +114 -0
- package/apps/demo-frontend/src/pages/SignupPage.tsx +95 -0
- package/apps/demo-frontend/src/pages/VerifyEmailPage.tsx +84 -0
- package/apps/demo-frontend/tsconfig.app.json +28 -0
- package/apps/demo-frontend/tsconfig.json +7 -0
- package/apps/demo-frontend/tsconfig.node.json +26 -0
- package/apps/demo-frontend/vite.config.ts +15 -0
- package/docs/DATABASE_MONGODB.md +280 -0
- package/docs/DATABASE_SQL.md +472 -0
- package/package.json +21 -0
- package/packages/api/package.json +30 -0
- package/packages/api/src/createSecurePool.ts +113 -0
- package/packages/api/src/index.ts +8 -0
- package/packages/api/src/middleware/authMiddleware.ts +26 -0
- package/packages/api/src/middleware/authorize.ts +24 -0
- package/packages/api/src/middleware/rateLimiter.ts +25 -0
- package/packages/api/src/middleware/tenantMiddleware.ts +12 -0
- package/packages/api/src/routes/authRoutes.ts +229 -0
- package/packages/api/src/routes/sessionRoutes.ts +30 -0
- package/packages/api/src/swagger.ts +529 -0
- package/packages/api/tsconfig.json +8 -0
- package/packages/application/package.json +16 -0
- package/packages/application/src/index.ts +17 -0
- package/packages/application/src/interfaces/IAuditLogRepository.ts +6 -0
- package/packages/application/src/interfaces/IAuthPlugin.ts +4 -0
- package/packages/application/src/interfaces/IEmailService.ts +3 -0
- package/packages/application/src/interfaces/IGoogleAuthService.ts +3 -0
- package/packages/application/src/interfaces/IOtpRepository.ts +8 -0
- package/packages/application/src/interfaces/IOtpService.ts +4 -0
- package/packages/application/src/interfaces/IPasswordHasher.ts +4 -0
- package/packages/application/src/interfaces/IRoleRepository.ts +8 -0
- package/packages/application/src/interfaces/ISessionRepository.ts +8 -0
- package/packages/application/src/interfaces/ITokenRepository.ts +9 -0
- package/packages/application/src/interfaces/ITokenService.ts +5 -0
- package/packages/application/src/interfaces/IUserRepository.ts +8 -0
- package/packages/application/src/services/AuthService.ts +323 -0
- package/packages/application/src/services/RefreshTokenService.ts +53 -0
- package/packages/application/tsconfig.json +8 -0
- package/packages/core/package.json +13 -0
- package/packages/core/src/entities/AuditLog.ts +11 -0
- package/packages/core/src/entities/OtpCode.ts +10 -0
- package/packages/core/src/entities/RefreshToken.ts +9 -0
- package/packages/core/src/entities/Role.ts +6 -0
- package/packages/core/src/entities/Session.ts +10 -0
- package/packages/core/src/entities/Tenant.ts +7 -0
- package/packages/core/src/entities/User.ts +10 -0
- package/packages/core/src/entities/UserRole.ts +6 -0
- package/packages/core/src/enums/index.ts +22 -0
- package/packages/core/src/index.ts +10 -0
- package/packages/core/tsconfig.json +8 -0
- package/packages/infrastructure/package.json +24 -0
- package/packages/infrastructure/src/email/NodemailerEmailService.ts +55 -0
- package/packages/infrastructure/src/google/GoogleAuthServiceImpl.ts +28 -0
- package/packages/infrastructure/src/hashing/BcryptHasher.ts +18 -0
- package/packages/infrastructure/src/index.ts +6 -0
- package/packages/infrastructure/src/jwt/JwtTokenService.ts +32 -0
- package/packages/infrastructure/src/otp/OtpServiceImpl.ts +50 -0
- package/packages/infrastructure/tsconfig.json +8 -0
- package/packages/persistence/package.json +22 -0
- package/packages/persistence/prisma/schema.prisma +88 -0
- package/packages/persistence/src/factory.ts +48 -0
- package/packages/persistence/src/index.ts +30 -0
- package/packages/persistence/src/mongo/connection.ts +9 -0
- package/packages/persistence/src/mongo/models/AuditLogModel.ts +21 -0
- package/packages/persistence/src/mongo/models/OtpModel.ts +19 -0
- package/packages/persistence/src/mongo/models/RefreshTokenModel.ts +17 -0
- package/packages/persistence/src/mongo/models/RoleModel.ts +11 -0
- package/packages/persistence/src/mongo/models/SessionModel.ts +19 -0
- package/packages/persistence/src/mongo/models/UserModel.ts +21 -0
- package/packages/persistence/src/mongo/models/UserRoleModel.ts +15 -0
- package/packages/persistence/src/mongo/repositories/MongoAuditLogRepository.ts +29 -0
- package/packages/persistence/src/mongo/repositories/MongoOtpRepository.ts +34 -0
- package/packages/persistence/src/mongo/repositories/MongoRoleRepository.ts +32 -0
- package/packages/persistence/src/mongo/repositories/MongoSessionRepository.ts +29 -0
- package/packages/persistence/src/mongo/repositories/MongoTokenRepository.ts +34 -0
- package/packages/persistence/src/mongo/repositories/MongoUserRepository.ts +37 -0
- package/packages/persistence/src/prisma/repositories/PrismaAuditLogRepository.ts +37 -0
- package/packages/persistence/src/prisma/repositories/PrismaOtpRepository.ts +43 -0
- package/packages/persistence/src/prisma/repositories/PrismaRoleRepository.ts +36 -0
- package/packages/persistence/src/prisma/repositories/PrismaSessionRepository.ts +39 -0
- package/packages/persistence/src/prisma/repositories/PrismaTokenRepository.ts +50 -0
- package/packages/persistence/src/prisma/repositories/PrismaUserRepository.ts +45 -0
- package/packages/persistence/tsconfig.json +8 -0
- package/packages/react-sdk/package.json +23 -0
- package/packages/react-sdk/src/components/GoogleLoginButton.tsx +54 -0
- package/packages/react-sdk/src/components/LoginForm.tsx +67 -0
- package/packages/react-sdk/src/components/OTPVerification.tsx +104 -0
- package/packages/react-sdk/src/components/SessionList.tsx +64 -0
- package/packages/react-sdk/src/components/SignupForm.tsx +95 -0
- package/packages/react-sdk/src/context/AuthContext.ts +4 -0
- package/packages/react-sdk/src/context/SecurePoolProvider.tsx +492 -0
- package/packages/react-sdk/src/hooks/useAuth.ts +11 -0
- package/packages/react-sdk/src/index.ts +22 -0
- package/packages/react-sdk/src/types.ts +53 -0
- package/packages/react-sdk/tsconfig.json +12 -0
- package/scripts/setup.js +285 -0
- package/scripts/setup.sh +309 -0
- package/tsconfig.base.json +16 -0
- 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
|
+
}
|