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
package/SETUP.md ADDED
@@ -0,0 +1,388 @@
1
+ # SecurePool - Local Setup Guide
2
+
3
+ ## Quick Start (One Command)
4
+
5
+ ```bash
6
+ cd securepool
7
+ npm run setup
8
+ ```
9
+
10
+ This interactive script handles everything:
11
+ - Checks prerequisites (Node.js, MongoDB, OpenSSL)
12
+ - Installs dependencies
13
+ - Generates RSA keys for JWT
14
+ - Sets up MongoDB (with or without authentication)
15
+ - Configures email for OTP (optional)
16
+ - Creates `.env` file
17
+ - Builds all packages
18
+ - Verifies the setup
19
+
20
+ After setup, start the servers:
21
+
22
+ ```bash
23
+ # Terminal 1 - Backend
24
+ npm run start:backend
25
+
26
+ # Terminal 2 - Frontend
27
+ npm run start:frontend
28
+ ```
29
+
30
+ Open:
31
+ - Frontend: http://localhost:5173
32
+ - API Docs: http://localhost:5001/docs
33
+ - Health Check: http://localhost:5001/health
34
+
35
+ ---
36
+
37
+ ## Manual Setup (Step by Step)
38
+
39
+ If you prefer manual setup, follow the steps below.
40
+
41
+ ### Prerequisites
42
+
43
+ | Tool | Version | Install |
44
+ |------|---------|---------|
45
+ | Node.js | 20+ | `brew install node` |
46
+ | MongoDB | 6+ | `brew install mongodb-community` |
47
+ | OpenSSL | any | Pre-installed on macOS |
48
+
49
+ ---
50
+
51
+ ### Step 1: Clone & Install
52
+
53
+ ```bash
54
+ cd securepool
55
+ npm install
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Step 2: Start MongoDB
61
+
62
+ **Option A: Without authentication (simple)**
63
+
64
+ ```bash
65
+ mongod --dbpath ~/mongodb-data
66
+ ```
67
+
68
+ **Option B: With authentication (production-like)**
69
+
70
+ ```bash
71
+ # Start MongoDB with auth
72
+ mongod --dbpath ~/mongodb-data --auth
73
+ ```
74
+
75
+ Then in a new terminal, create the database user:
76
+
77
+ ```bash
78
+ mongosh
79
+ ```
80
+
81
+ ```js
82
+ use securepool
83
+
84
+ db.createUser({
85
+ user: "securepool-user",
86
+ pwd: "SecurePool@123",
87
+ roles: [{ role: "readWrite", db: "securepool" }]
88
+ })
89
+
90
+ exit
91
+ ```
92
+
93
+ Verify it works:
94
+
95
+ ```bash
96
+ mongosh "mongodb://securepool-user:SecurePool%40123@localhost:27017/securepool?authSource=securepool"
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Step 3: Generate RSA Keys (for JWT)
102
+
103
+ ```bash
104
+ cd securepool
105
+ openssl genrsa -out private.pem 2048
106
+ openssl rsa -in private.pem -pubout -out public.pem
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Step 4: Create `.env` File
112
+
113
+ Create a `.env` file in the `securepool/` root:
114
+
115
+ ```env
116
+ # Database
117
+ DB_TYPE=mongo
118
+ DB_URL=mongodb://localhost:27017/securepool
119
+
120
+ # If using auth (Step 2 Option B):
121
+ # DB_URL=mongodb://securepool-user:SecurePool%40123@localhost:27017/securepool?authSource=securepool
122
+
123
+ # JWT (paths to RSA key files)
124
+ JWT_PRIVATE_KEY_PATH=./private.pem
125
+ JWT_PUBLIC_KEY_PATH=./public.pem
126
+
127
+ # Email (Gmail SMTP - optional, needed for OTP emails)
128
+ EMAIL_HOST=smtp.gmail.com
129
+ EMAIL_PORT=587
130
+ EMAIL_SECURE=false
131
+ EMAIL_USER=your-email@gmail.com
132
+ EMAIL_PASS=your-gmail-app-password
133
+ EMAIL_FROM=your-email@gmail.com
134
+
135
+ # Server
136
+ PORT=5001
137
+
138
+ # Security
139
+ RATE_LIMIT_ENABLED=true
140
+ CORS_ORIGINS=*
141
+ ```
142
+
143
+ ### Gmail App Password (required for email OTP)
144
+
145
+ 1. Go to https://myaccount.google.com/apppasswords
146
+ 2. Sign in (2FA must be enabled)
147
+ 3. Create app password for "Mail"
148
+ 4. Copy the 16-character password into `EMAIL_PASS`
149
+
150
+ If you skip email setup, OTP features won't send emails (but the rest works fine).
151
+
152
+ ---
153
+
154
+ ## Step 5: Build All Packages
155
+
156
+ ```bash
157
+ npm run build
158
+ ```
159
+
160
+ This builds all 6 packages using Turborepo:
161
+ - `@securepool/core`
162
+ - `@securepool/application`
163
+ - `@securepool/infrastructure`
164
+ - `@securepool/persistence`
165
+ - `@securepool/api`
166
+ - `@securepool/react-sdk`
167
+
168
+ ---
169
+
170
+ ## Step 6: Start Backend
171
+
172
+ ```bash
173
+ cd apps/demo-backend
174
+ npx ts-node src/index.ts
175
+ ```
176
+
177
+ You should see:
178
+
179
+ ```
180
+ SecurePool API running on port 5001
181
+ ```
182
+
183
+ Verify:
184
+
185
+ ```bash
186
+ curl http://localhost:5001/health
187
+ # → {"status":"ok"}
188
+ ```
189
+
190
+ ### API Documentation (Swagger UI)
191
+
192
+ Open in your browser:
193
+
194
+ ```
195
+ http://localhost:5001/docs
196
+ ```
197
+
198
+ This gives you an interactive API explorer where you can:
199
+
200
+ - See all endpoints with request/response schemas
201
+ - Try any API live by clicking "Try it out"
202
+ - Authenticate by clicking the **Authorize** button (top right) and pasting your JWT access token
203
+ - View the raw OpenAPI spec at `http://localhost:5001/docs.json`
204
+
205
+ **How to use authenticated endpoints in Swagger:**
206
+
207
+ 1. First call `POST /auth/login` with your email + password
208
+ 2. Copy the `accessToken` from the response
209
+ 3. Click the **Authorize** button (lock icon at top)
210
+ 4. Paste the token and click "Authorize"
211
+ 5. Now all authenticated endpoints (Sessions, Change Password) will work
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Step 7: Start Frontend
217
+
218
+ Open a **new terminal**:
219
+
220
+ ```bash
221
+ cd apps/demo-frontend
222
+ npx vite
223
+ ```
224
+
225
+ Opens at http://localhost:5173
226
+
227
+ ---
228
+
229
+ ## Step 8: Test the Full Flow
230
+
231
+ ### Register a new user
232
+
233
+ 1. Open http://localhost:5173/signup
234
+ 2. Enter email and password (min 8 chars)
235
+ 3. Check your email for the 6-digit OTP
236
+ 4. Enter OTP on the verification page
237
+ 5. You're logged in and redirected to the dashboard
238
+
239
+ ### Login with password
240
+
241
+ 1. Go to http://localhost:5173/login
242
+ 2. Enter email and password
243
+
244
+ ### Login with OTP
245
+
246
+ 1. Go to http://localhost:5173/otp-login
247
+ 2. Enter your email
248
+ 3. Check email for OTP code
249
+ 4. Enter the code
250
+
251
+ ### Forgot password
252
+
253
+ 1. Go to http://localhost:5173/forgot-password
254
+ 2. Enter your email
255
+ 3. Check email for OTP
256
+ 4. Enter OTP, then set new password
257
+
258
+ ### Change password
259
+
260
+ 1. Login and go to dashboard
261
+ 2. Click your avatar (top right)
262
+ 3. Click "Change password"
263
+ 4. Enter old password and new password
264
+
265
+ ### Switch accounts
266
+
267
+ 1. Login with one account
268
+ 2. Click avatar → "Add another account"
269
+ 3. Login with a different email
270
+ 4. Click avatar → click any stored account to switch
271
+
272
+ ---
273
+
274
+ ## API Endpoints
275
+
276
+ | Method | Endpoint | Auth | Description |
277
+ |--------|----------|------|-------------|
278
+ | GET | `/health` | No | Health check |
279
+ | POST | `/auth/register` | No | Register + send OTP |
280
+ | POST | `/auth/verify-email` | No | Verify OTP + create user |
281
+ | POST | `/auth/login` | No | Login with password |
282
+ | POST | `/auth/otp/request` | No | Request OTP for login |
283
+ | POST | `/auth/otp/verify` | No | Verify OTP + login |
284
+ | POST | `/auth/refresh` | No | Refresh token |
285
+ | POST | `/auth/google` | No | Google SSO login |
286
+ | POST | `/auth/forgot-password` | No | Send password reset OTP |
287
+ | POST | `/auth/reset-password` | No | Reset password with OTP |
288
+ | POST | `/auth/change-password` | Yes | Change password (old + new) |
289
+ | GET | `/sessions` | Yes | List active sessions |
290
+ | DELETE | `/sessions/:id` | Yes | Revoke a session |
291
+ | DELETE | `/sessions` | Yes | Revoke all sessions |
292
+
293
+ All `/auth/*` routes require the `x-tenant-id` header.
294
+
295
+ Authenticated routes require: `Authorization: Bearer <access_token>`
296
+
297
+ ### Example: Register + Login via curl
298
+
299
+ ```bash
300
+ # Register
301
+ curl -X POST http://localhost:5001/auth/register \
302
+ -H "Content-Type: application/json" \
303
+ -H "x-tenant-id: default" \
304
+ -d '{"email":"test@example.com","password":"MyPass@1234"}'
305
+
306
+ # Login
307
+ curl -X POST http://localhost:5001/auth/login \
308
+ -H "Content-Type: application/json" \
309
+ -H "x-tenant-id: default" \
310
+ -d '{"email":"test@example.com","password":"MyPass@1234"}'
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Project Structure
316
+
317
+ ```
318
+ securepool/
319
+ ├── packages/ # NPM library packages
320
+ │ ├── core/ # Entities, enums (zero dependencies)
321
+ │ ├── application/ # Interfaces, AuthService, business logic
322
+ │ ├── infrastructure/ # JWT, bcrypt, OTP, email (nodemailer)
323
+ │ ├── persistence/ # MongoDB (mongoose) + PostgreSQL (prisma)
324
+ │ ├── api/ # Express routes, middleware, createSecurePool()
325
+ │ └── react-sdk/ # SecurePoolProvider, useAuth, UI components
326
+
327
+ ├── apps/ # Runnable applications
328
+ │ ├── demo-backend/ # Express server using @securepool/api
329
+ │ └── demo-frontend/ # React app using @securepool/react-sdk
330
+
331
+ ├── package.json # Monorepo root (npm workspaces)
332
+ ├── tsconfig.base.json # Shared TypeScript config
333
+ ├── turbo.json # Turborepo build pipeline
334
+ ├── .env # Environment variables (not in git)
335
+ ├── private.pem # JWT private key (not in git)
336
+ └── public.pem # JWT public key (not in git)
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Common Issues
342
+
343
+ ### Port 5001 already in use
344
+
345
+ ```bash
346
+ lsof -ti:5001 | xargs kill -9
347
+ ```
348
+
349
+ ### Port 5000 used by AirPlay (macOS)
350
+
351
+ We use port 5001 to avoid this. Or disable AirPlay Receiver in System Settings.
352
+
353
+ ### MongoDB connection failed
354
+
355
+ Make sure MongoDB is running:
356
+
357
+ ```bash
358
+ mongosh --eval "db.runCommand({ ping: 1 })"
359
+ ```
360
+
361
+ ### OTP email not received
362
+
363
+ - Check `EMAIL_USER` and `EMAIL_PASS` in `.env`
364
+ - Gmail requires an App Password (not your regular password)
365
+ - Enable 2FA on your Google account first
366
+
367
+ ### Build errors after changing code
368
+
369
+ Rebuild all packages:
370
+
371
+ ```bash
372
+ npm run build
373
+ ```
374
+
375
+ Then restart the backend.
376
+
377
+ ---
378
+
379
+ ## Useful Commands
380
+
381
+ | Command | Description |
382
+ |---------|-------------|
383
+ | `npm run build` | Build all packages |
384
+ | `npm run clean` | Clean all dist folders |
385
+ | `npx turbo run build --filter=@securepool/api` | Build single package |
386
+ | `cd apps/demo-backend && npx ts-node src/index.ts` | Start backend |
387
+ | `cd apps/demo-frontend && npx vite` | Start frontend |
388
+ | `lsof -ti:5001 \| xargs kill -9` | Kill process on port |
@@ -0,0 +1,33 @@
1
+ FROM node:20-alpine AS builder
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy monorepo root files
6
+ COPY package.json package-lock.json tsconfig.base.json turbo.json ./
7
+
8
+ # Copy all packages and the backend app
9
+ COPY packages/ packages/
10
+ COPY apps/demo-backend/ apps/demo-backend/
11
+
12
+ # Install dependencies
13
+ RUN npm ci
14
+
15
+ # Build all packages
16
+ RUN npx turbo run build --filter=@securepool/*
17
+
18
+ # Build the backend app
19
+ RUN cd apps/demo-backend && npx tsc
20
+
21
+ # ---- Production stage ----
22
+ FROM node:20-alpine
23
+
24
+ WORKDIR /app
25
+
26
+ COPY --from=builder /app/package.json /app/package-lock.json ./
27
+ COPY --from=builder /app/packages/ packages/
28
+ COPY --from=builder /app/apps/demo-backend/ apps/demo-backend/
29
+ COPY --from=builder /app/node_modules/ node_modules/
30
+
31
+ EXPOSE 5001
32
+
33
+ CMD ["node", "apps/demo-backend/dist/index.js"]
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "demo-backend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "ts-node-dev --respawn src/index.ts",
7
+ "build": "tsc",
8
+ "start": "node dist/index.js"
9
+ },
10
+ "dependencies": {
11
+ "@securepool/api": "1.0.0",
12
+ "dotenv": "^16.4.5"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "^5.5.0",
16
+ "ts-node-dev": "^2.0.0",
17
+ "@types/node": "^20.14.0"
18
+ }
19
+ }
@@ -0,0 +1,71 @@
1
+ import dotenv from "dotenv";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import { createSecurePool } from "@securepool/api";
5
+
6
+ // Load .env (local dev: from monorepo root, production: from process.env)
7
+ dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
8
+ dotenv.config(); // also check current dir
9
+
10
+ function getJwtKeys(): { privateKey: string; publicKey: string } {
11
+ // Option 1: Keys passed directly as env vars (production)
12
+ if (process.env.JWT_PRIVATE_KEY && process.env.JWT_PUBLIC_KEY) {
13
+ return {
14
+ privateKey: process.env.JWT_PRIVATE_KEY.replace(/\\n/g, "\n"),
15
+ publicKey: process.env.JWT_PUBLIC_KEY.replace(/\\n/g, "\n"),
16
+ };
17
+ }
18
+
19
+ // Option 2: Keys from file paths (local dev)
20
+ const root = path.resolve(__dirname, "../../..");
21
+ const privateKeyPath = path.resolve(root, process.env.JWT_PRIVATE_KEY_PATH || "./private.pem");
22
+ const publicKeyPath = path.resolve(root, process.env.JWT_PUBLIC_KEY_PATH || "./public.pem");
23
+
24
+ if (fs.existsSync(privateKeyPath) && fs.existsSync(publicKeyPath)) {
25
+ return {
26
+ privateKey: fs.readFileSync(privateKeyPath, "utf-8"),
27
+ publicKey: fs.readFileSync(publicKeyPath, "utf-8"),
28
+ };
29
+ }
30
+
31
+ console.error("JWT keys not found. Set JWT_PRIVATE_KEY/JWT_PUBLIC_KEY env vars or provide .pem files.");
32
+ process.exit(1);
33
+ }
34
+
35
+ async function main() {
36
+ const { privateKey, publicKey } = getJwtKeys();
37
+ const port = parseInt(process.env.PORT || "5001", 10);
38
+
39
+ const { app } = await createSecurePool({
40
+ database: {
41
+ type: (process.env.DB_TYPE as "mongo" | "postgres") || "mongo",
42
+ url: process.env.DB_URL || "mongodb://localhost:27017/securepool",
43
+ },
44
+ jwt: {
45
+ privateKey,
46
+ publicKey,
47
+ },
48
+ email: process.env.EMAIL_USER ? {
49
+ host: process.env.EMAIL_HOST || "smtp.gmail.com",
50
+ port: parseInt(process.env.EMAIL_PORT || "587", 10),
51
+ secure: process.env.EMAIL_SECURE === "true",
52
+ user: process.env.EMAIL_USER,
53
+ pass: process.env.EMAIL_PASS || "",
54
+ from: process.env.EMAIL_FROM || process.env.EMAIL_USER,
55
+ } : undefined,
56
+ security: {
57
+ enableRateLimit: process.env.RATE_LIMIT_ENABLED !== "false",
58
+ corsOrigins: process.env.CORS_ORIGINS || "*",
59
+ },
60
+ });
61
+
62
+ app.listen(port, () => {
63
+ console.log(`SecurePool API running on port ${port}`);
64
+ console.log(`Api Docs: http://localhost:${port}/docs`);
65
+ });
66
+ }
67
+
68
+ main().catch((err) => {
69
+ console.error("Failed to start SecurePool:", err);
70
+ process.exit(1);
71
+ });
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,2 @@
1
+ VITE_API_URL=https://your-backend-url.railway.app
2
+ VITE_TENANT_ID=default
@@ -0,0 +1,73 @@
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ])
44
+ ```
45
+
46
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
+
48
+ ```js
49
+ // eslint.config.js
50
+ import reactX from 'eslint-plugin-react-x'
51
+ import reactDom from 'eslint-plugin-react-dom'
52
+
53
+ export default defineConfig([
54
+ globalIgnores(['dist']),
55
+ {
56
+ files: ['**/*.{ts,tsx}'],
57
+ extends: [
58
+ // Other configs...
59
+ // Enable lint rules for React
60
+ reactX.configs['recommended-typescript'],
61
+ // Enable lint rules for React DOM
62
+ reactDom.configs.recommended,
63
+ ],
64
+ languageOptions: {
65
+ parserOptions: {
66
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
+ tsconfigRootDir: import.meta.dirname,
68
+ },
69
+ // other options...
70
+ },
71
+ },
72
+ ])
73
+ ```
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs.flat.recommended,
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>demo-frontend</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "demo-frontend",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^19.2.4",
13
+ "react-dom": "^19.2.4",
14
+ "react-router-dom": "^7.6.0",
15
+ "@securepool/react-sdk": "1.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^19.2.14",
19
+ "@types/react-dom": "^19.2.3",
20
+ "@vitejs/plugin-react": "^6.0.1",
21
+ "typescript": "~5.9.3",
22
+ "vite": "^8.0.1"
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>