myaidev-method 0.2.8 → 0.2.10
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/.claude/agents/wordpress-admin.md +271 -0
- package/.env.example +0 -1
- package/PACKAGE_FIXES_SUMMARY.md +319 -0
- package/PAYLOADCMS_AUTH_UPDATE.md +248 -0
- package/USER_GUIDE.md +260 -0
- package/bin/cli.js +70 -0
- package/dist/server/.tsbuildinfo +1 -0
- package/dist/server/auth/controllers/AuthController.d.ts +34 -0
- package/dist/server/auth/controllers/AuthController.d.ts.map +1 -0
- package/dist/server/auth/controllers/AuthController.js +43 -0
- package/dist/server/auth/controllers/AuthController.js.map +1 -0
- package/dist/server/auth/example-usage.d.ts +53 -0
- package/dist/server/auth/example-usage.d.ts.map +1 -0
- package/dist/server/auth/example-usage.js +129 -0
- package/dist/server/auth/example-usage.js.map +1 -0
- package/dist/server/auth/index.d.ts +11 -0
- package/dist/server/auth/index.d.ts.map +1 -0
- package/dist/server/auth/index.js +15 -0
- package/dist/server/auth/index.js.map +1 -0
- package/dist/server/auth/layers.d.ts +19 -0
- package/dist/server/auth/layers.d.ts.map +1 -0
- package/dist/server/auth/layers.js +33 -0
- package/dist/server/auth/layers.js.map +1 -0
- package/dist/server/auth/middleware/authMiddleware.d.ts +24 -0
- package/dist/server/auth/middleware/authMiddleware.d.ts.map +1 -0
- package/dist/server/auth/middleware/authMiddleware.js +65 -0
- package/dist/server/auth/middleware/authMiddleware.js.map +1 -0
- package/dist/server/auth/routes/authRoutes.d.ts +11 -0
- package/dist/server/auth/routes/authRoutes.d.ts.map +1 -0
- package/dist/server/auth/routes/authRoutes.js +213 -0
- package/dist/server/auth/routes/authRoutes.js.map +1 -0
- package/dist/server/auth/services/AuditLogService.d.ts +21 -0
- package/dist/server/auth/services/AuditLogService.d.ts.map +1 -0
- package/dist/server/auth/services/AuditLogService.js +28 -0
- package/dist/server/auth/services/AuditLogService.js.map +1 -0
- package/dist/server/auth/services/AuthService.d.ts +27 -0
- package/dist/server/auth/services/AuthService.d.ts.map +1 -0
- package/dist/server/auth/services/AuthService.js +246 -0
- package/dist/server/auth/services/AuthService.js.map +1 -0
- package/dist/server/auth/services/PasswordService.d.ts +12 -0
- package/dist/server/auth/services/PasswordService.d.ts.map +1 -0
- package/dist/server/auth/services/PasswordService.js +31 -0
- package/dist/server/auth/services/PasswordService.js.map +1 -0
- package/dist/server/auth/services/SessionRepository.d.ts +24 -0
- package/dist/server/auth/services/SessionRepository.d.ts.map +1 -0
- package/dist/server/auth/services/SessionRepository.js +101 -0
- package/dist/server/auth/services/SessionRepository.js.map +1 -0
- package/dist/server/auth/services/TokenService.d.ts +12 -0
- package/dist/server/auth/services/TokenService.d.ts.map +1 -0
- package/dist/server/auth/services/TokenService.js +86 -0
- package/dist/server/auth/services/TokenService.js.map +1 -0
- package/dist/server/auth/services/UserRepository.d.ts +23 -0
- package/dist/server/auth/services/UserRepository.d.ts.map +1 -0
- package/dist/server/auth/services/UserRepository.js +168 -0
- package/dist/server/auth/services/UserRepository.js.map +1 -0
- package/dist/server/auth/services/example.d.ts +26 -0
- package/dist/server/auth/services/example.d.ts.map +1 -0
- package/dist/server/auth/services/example.js +221 -0
- package/dist/server/auth/services/example.js.map +1 -0
- package/dist/server/auth/services/index.d.ts +6 -0
- package/dist/server/auth/services/index.d.ts.map +1 -0
- package/dist/server/auth/services/index.js +7 -0
- package/dist/server/auth/services/index.js.map +1 -0
- package/dist/server/database/db.d.ts +28 -0
- package/dist/server/database/db.d.ts.map +1 -0
- package/dist/server/database/db.js +91 -0
- package/dist/server/database/db.js.map +1 -0
- package/dist/server/database/schema.sql +95 -0
- package/dist/server/hono/app.d.ts +10 -0
- package/dist/server/hono/app.d.ts.map +1 -0
- package/dist/server/hono/app.js +26 -0
- package/dist/server/hono/app.js.map +1 -0
- package/dist/server/hono/routes.d.ts +12 -0
- package/dist/server/hono/routes.d.ts.map +1 -0
- package/dist/server/hono/routes.js +40 -0
- package/dist/server/hono/routes.js.map +1 -0
- package/dist/server/main.d.ts +2 -0
- package/dist/server/main.d.ts.map +1 -0
- package/dist/server/main.js +94 -0
- package/dist/server/main.js.map +1 -0
- package/dist/server/user-management/DirectoryService.d.ts +62 -0
- package/dist/server/user-management/DirectoryService.d.ts.map +1 -0
- package/dist/server/user-management/DirectoryService.js +201 -0
- package/dist/server/user-management/DirectoryService.js.map +1 -0
- package/dist/server/user-management/LinuxUserService.d.ts +71 -0
- package/dist/server/user-management/LinuxUserService.d.ts.map +1 -0
- package/dist/server/user-management/LinuxUserService.js +192 -0
- package/dist/server/user-management/LinuxUserService.js.map +1 -0
- package/dist/server/user-management/QuotaService.d.ts +59 -0
- package/dist/server/user-management/QuotaService.d.ts.map +1 -0
- package/dist/server/user-management/QuotaService.js +148 -0
- package/dist/server/user-management/QuotaService.js.map +1 -0
- package/dist/server/user-management/UserManagementService.d.ts +74 -0
- package/dist/server/user-management/UserManagementService.d.ts.map +1 -0
- package/dist/server/user-management/UserManagementService.js +122 -0
- package/dist/server/user-management/UserManagementService.js.map +1 -0
- package/dist/server/user-management/index.d.ts +26 -0
- package/dist/server/user-management/index.d.ts.map +1 -0
- package/dist/server/user-management/index.js +26 -0
- package/dist/server/user-management/index.js.map +1 -0
- package/dist/server/user-management/layers.d.ts +27 -0
- package/dist/server/user-management/layers.d.ts.map +1 -0
- package/dist/server/user-management/layers.js +37 -0
- package/dist/server/user-management/layers.js.map +1 -0
- package/dist/shared/types.d.ts +94 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +32 -0
- package/dist/shared/types.js.map +1 -0
- package/package.json +25 -5
- package/src/lib/payloadcms-utils.js +5 -12
- package/src/server/auth/ARCHITECTURE.md +575 -0
- package/src/server/auth/IMPLEMENTATION_SUMMARY.md +287 -0
- package/src/server/auth/QUICK_START.md +283 -0
- package/src/server/auth/README.md +290 -0
- package/src/server/auth/controllers/AuthController.ts +129 -0
- package/src/server/auth/example-usage.ts +159 -0
- package/src/server/auth/index.ts +19 -0
- package/src/server/auth/layers.ts +57 -0
- package/src/server/auth/middleware/authMiddleware.ts +118 -0
- package/src/server/auth/routes/authRoutes.ts +319 -0
- package/src/server/auth/services/AuditLogService.ts +81 -0
- package/src/server/auth/services/AuthService.ts +408 -0
- package/src/server/auth/services/IMPLEMENTATION_SUMMARY.md +404 -0
- package/src/server/auth/services/PasswordService.ts +85 -0
- package/src/server/auth/services/README.md +361 -0
- package/src/server/auth/services/SessionRepository.ts +227 -0
- package/src/server/auth/services/TokenService.ts +174 -0
- package/src/server/auth/services/UserRepository.ts +318 -0
- package/src/server/auth/services/example.ts +346 -0
- package/src/server/auth/services/index.ts +6 -0
- package/src/server/database/db.ts +161 -0
- package/src/server/database/schema.sql +95 -0
- package/src/server/hono/app.ts +41 -0
- package/src/server/main.ts +115 -0
- package/src/server/user-management/DirectoryService.ts +348 -0
- package/src/server/user-management/LinuxUserService.ts +338 -0
- package/src/server/user-management/QuotaService.ts +256 -0
- package/src/server/user-management/README.md +333 -0
- package/src/server/user-management/UserManagementService.ts +335 -0
- package/src/server/user-management/index.ts +26 -0
- package/src/server/user-management/layers.ts +51 -0
- package/src/shared/types.ts +111 -0
- package/src/templates/claude/agents/coolify-deploy.md +50 -50
- package/src/templates/claude/agents/payloadcms-publish.md +46 -18
- package/src/templates/codex/commands/myai-astro-publish.md +8 -2
- package/src/templates/codex/commands/myai-content-writer.md +8 -2
- package/src/templates/codex/commands/myai-coolify-deploy.md +8 -2
- package/src/templates/codex/commands/myai-dev-architect.md +8 -2
- package/src/templates/codex/commands/myai-dev-code.md +8 -2
- package/src/templates/codex/commands/myai-dev-docs.md +8 -2
- package/src/templates/codex/commands/myai-dev-review.md +8 -2
- package/src/templates/codex/commands/myai-dev-test.md +8 -2
- package/src/templates/codex/commands/myai-docusaurus-publish.md +8 -2
- package/src/templates/codex/commands/myai-mintlify-publish.md +8 -2
- package/src/templates/codex/commands/myai-payloadcms-publish.md +17 -3
- package/src/templates/codex/commands/myai-sparc-workflow.md +8 -2
- package/src/templates/codex/commands/myai-wordpress-admin.md +8 -2
- package/src/templates/codex/commands/myai-wordpress-publish.md +8 -2
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
# Authentication Architecture
|
|
2
|
+
|
|
3
|
+
## System Overview
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
7
|
+
│ HTTP Layer (Hono) │
|
|
8
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
9
|
+
│ │
|
|
10
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
11
|
+
│ │ Register │ │ Login │ │ Logout │ │
|
|
12
|
+
│ │ POST /register│ │ POST /login │ │POST /logout │ │
|
|
13
|
+
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
|
14
|
+
│ │ │ │ │
|
|
15
|
+
│ │ │ │ ┌──────────────┐│
|
|
16
|
+
│ │ │ │ │ Me ││
|
|
17
|
+
│ │ │ │ │ GET /me ││
|
|
18
|
+
│ │ │ │ └──────┬───────┘│
|
|
19
|
+
│ │ │ │ │ │
|
|
20
|
+
│ └──────────────────┴──────────────────┴─────────┘ │
|
|
21
|
+
│ │ │
|
|
22
|
+
└──────────────────────────────┼────────────────────────────────────┘
|
|
23
|
+
│
|
|
24
|
+
┌──────────▼──────────┐
|
|
25
|
+
│ authMiddleware │
|
|
26
|
+
│ Token Extraction │
|
|
27
|
+
│ Token Verification │
|
|
28
|
+
│ Context Injection │
|
|
29
|
+
└──────────┬──────────┘
|
|
30
|
+
│
|
|
31
|
+
┌──────────────────────────────┼────────────────────────────────────┐
|
|
32
|
+
│ Effect-TS Service Layer │
|
|
33
|
+
├──────────────────────────────┼────────────────────────────────────┤
|
|
34
|
+
│ │ │
|
|
35
|
+
│ ┌──────────▼──────────┐ │
|
|
36
|
+
│ │ AuthService │ │
|
|
37
|
+
│ │ - register() │ │
|
|
38
|
+
│ │ - login() │ │
|
|
39
|
+
│ │ - logout() │ │
|
|
40
|
+
│ │ - verifyToken() │ │
|
|
41
|
+
│ └─────────┬───────────┘ │
|
|
42
|
+
│ │ │
|
|
43
|
+
│ ┌────────────────────┼────────────────────┐ │
|
|
44
|
+
│ │ │ │ │
|
|
45
|
+
│ ┌──────▼─────┐ ┌────────▼────────┐ ┌──────▼──────┐ │
|
|
46
|
+
│ │ Password │ │ TokenService │ │ Audit │ │
|
|
47
|
+
│ │ Service │ │ - generate() │ │ Service │ │
|
|
48
|
+
│ │ - hash() │ │ - verify() │ │ - log() │ │
|
|
49
|
+
│ │ - verify()│ │ - hashToken() │ └─────────────┘ │
|
|
50
|
+
│ └────────────┘ └─────────────────┘ │
|
|
51
|
+
│ │
|
|
52
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
53
|
+
│ │ User │ │ Session │ │
|
|
54
|
+
│ │ Repository │ │ Repository │ │
|
|
55
|
+
│ │ - create() │ │ - create() │ │
|
|
56
|
+
│ │ - findBy*() │ │ - findBy*() │ │
|
|
57
|
+
│ │ - update() │ │ - revoke() │ │
|
|
58
|
+
│ └──────┬───────┘ └──────┬───────┘ │
|
|
59
|
+
│ │ │ │
|
|
60
|
+
└─────────┼───────────────────────────┼─────────────────────────────┘
|
|
61
|
+
│ │
|
|
62
|
+
┌─────────▼───────────────────────────▼─────────────────────────────┐
|
|
63
|
+
│ Database Layer (SQLite) │
|
|
64
|
+
├────────────────────────────────────────────────────────────────────┤
|
|
65
|
+
│ │
|
|
66
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
67
|
+
│ │ users │ │ sessions │ │ audit_logs │ │
|
|
68
|
+
│ │ │ │ │ │ │ │
|
|
69
|
+
│ │ • id │ │ • id │ │ • id │ │
|
|
70
|
+
│ │ • username │ │ • user_id │ │ • user_id │ │
|
|
71
|
+
│ │ • email │ │ • token_hash│ │ • action │ │
|
|
72
|
+
│ │ • password │ │ • ip_addr │ │ • ip_addr │ │
|
|
73
|
+
│ │ • linux_usr │ │ • user_agent│ │ • details │ │
|
|
74
|
+
│ │ • failed │ │ • expires │ │ • timestamp │ │
|
|
75
|
+
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
76
|
+
│ │
|
|
77
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Request Flow
|
|
81
|
+
|
|
82
|
+
### Registration Flow
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Client Request
|
|
86
|
+
│
|
|
87
|
+
│ POST /api/auth/register
|
|
88
|
+
│ { username, email, password }
|
|
89
|
+
│
|
|
90
|
+
▼
|
|
91
|
+
┌───────────────────────────────────┐
|
|
92
|
+
│ authRoutes.post() │
|
|
93
|
+
│ 1. Extract request body │
|
|
94
|
+
│ 2. Extract IP, user agent │
|
|
95
|
+
│ 3. Call AuthService.register() │
|
|
96
|
+
└───────────────┬───────────────────┘
|
|
97
|
+
│
|
|
98
|
+
▼
|
|
99
|
+
┌───────────────────────────────────┐
|
|
100
|
+
│ AuthService.register() │
|
|
101
|
+
│ 1. Validate username │
|
|
102
|
+
│ 2. Validate email │
|
|
103
|
+
│ 3. Validate password strength │
|
|
104
|
+
│ 4. Check duplicates │
|
|
105
|
+
│ 5. Hash password (bcrypt) │
|
|
106
|
+
│ 6. Generate linux_username │
|
|
107
|
+
│ 7. Create user in DB │
|
|
108
|
+
│ 8. Log audit event │
|
|
109
|
+
└───────────────┬───────────────────┘
|
|
110
|
+
│
|
|
111
|
+
▼
|
|
112
|
+
┌───────────────────────────────────┐
|
|
113
|
+
│ UserRepository.create() │
|
|
114
|
+
│ 1. Generate UUID │
|
|
115
|
+
│ 2. Insert into users table │
|
|
116
|
+
│ 3. Return user object │
|
|
117
|
+
└───────────────┬───────────────────┘
|
|
118
|
+
│
|
|
119
|
+
▼
|
|
120
|
+
201 Created
|
|
121
|
+
{ user: { id, username, email } }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Login Flow
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
Client Request
|
|
128
|
+
│
|
|
129
|
+
│ POST /api/auth/login
|
|
130
|
+
│ { email, password }
|
|
131
|
+
│
|
|
132
|
+
▼
|
|
133
|
+
┌───────────────────────────────────┐
|
|
134
|
+
│ authRoutes.post() │
|
|
135
|
+
│ 1. Extract request body │
|
|
136
|
+
│ 2. Extract IP, user agent │
|
|
137
|
+
│ 3. Call AuthService.login() │
|
|
138
|
+
└───────────────┬───────────────────┘
|
|
139
|
+
│
|
|
140
|
+
▼
|
|
141
|
+
┌───────────────────────────────────┐
|
|
142
|
+
│ AuthService.login() │
|
|
143
|
+
│ 1. Find user by email │
|
|
144
|
+
│ 2. Check account lockout │
|
|
145
|
+
│ 3. Verify password │
|
|
146
|
+
│ 4. Reset/increment failed tries │
|
|
147
|
+
│ 5. Update last login time │
|
|
148
|
+
│ 6. Create session │
|
|
149
|
+
│ 7. Generate JWT token │
|
|
150
|
+
│ 8. Log audit event │
|
|
151
|
+
└───────────────┬───────────────────┘
|
|
152
|
+
│
|
|
153
|
+
▼
|
|
154
|
+
┌───────────────────────────────────┐
|
|
155
|
+
│ TokenService.generateToken() │
|
|
156
|
+
│ 1. Create JWT payload │
|
|
157
|
+
│ 2. Sign with RS256 │
|
|
158
|
+
│ 3. Return token string │
|
|
159
|
+
└───────────────┬───────────────────┘
|
|
160
|
+
│
|
|
161
|
+
▼
|
|
162
|
+
┌───────────────────────────────────┐
|
|
163
|
+
│ SessionRepository.create() │
|
|
164
|
+
│ 1. Generate session ID │
|
|
165
|
+
│ 2. Hash token with SHA-256 │
|
|
166
|
+
│ 3. Insert into sessions table │
|
|
167
|
+
│ 4. Return session object │
|
|
168
|
+
└───────────────┬───────────────────┘
|
|
169
|
+
│
|
|
170
|
+
▼
|
|
171
|
+
200 OK + Set-Cookie
|
|
172
|
+
{ user: {...}, token: "..." }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Protected Route Flow
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
Client Request
|
|
179
|
+
│
|
|
180
|
+
│ GET /api/auth/me
|
|
181
|
+
│ Authorization: Bearer <token>
|
|
182
|
+
│ OR Cookie: auth_token=<token>
|
|
183
|
+
│
|
|
184
|
+
▼
|
|
185
|
+
┌───────────────────────────────────┐
|
|
186
|
+
│ authMiddleware() │
|
|
187
|
+
│ 1. Extract token from header │
|
|
188
|
+
│ or cookie │
|
|
189
|
+
│ 2. Call AuthService.verify() │
|
|
190
|
+
└───────────────┬───────────────────┘
|
|
191
|
+
│
|
|
192
|
+
▼
|
|
193
|
+
┌───────────────────────────────────┐
|
|
194
|
+
│ AuthService.verifyToken() │
|
|
195
|
+
│ 1. Verify JWT signature │
|
|
196
|
+
│ 2. Check expiration │
|
|
197
|
+
│ 3. Hash token │
|
|
198
|
+
│ 4. Find session by hash │
|
|
199
|
+
│ 5. Check session not revoked │
|
|
200
|
+
│ 6. Find user by ID │
|
|
201
|
+
│ 7. Check user is active │
|
|
202
|
+
└───────────────┬───────────────────┘
|
|
203
|
+
│
|
|
204
|
+
▼
|
|
205
|
+
┌───────────────────────────────────┐
|
|
206
|
+
│ Context Injection │
|
|
207
|
+
│ c.set('user', user) │
|
|
208
|
+
│ c.set('session', session) │
|
|
209
|
+
└───────────────┬───────────────────┘
|
|
210
|
+
│
|
|
211
|
+
▼
|
|
212
|
+
┌───────────────────────────────────┐
|
|
213
|
+
│ Route Handler │
|
|
214
|
+
│ const user = c.get('user') │
|
|
215
|
+
│ const session = c.get('session') │
|
|
216
|
+
│ return c.json({ user }) │
|
|
217
|
+
└───────────────┬───────────────────┘
|
|
218
|
+
│
|
|
219
|
+
▼
|
|
220
|
+
200 OK
|
|
221
|
+
{ user: {...} }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Logout Flow
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
Client Request
|
|
228
|
+
│
|
|
229
|
+
│ POST /api/auth/logout
|
|
230
|
+
│ Cookie: auth_token=<token>
|
|
231
|
+
│
|
|
232
|
+
▼
|
|
233
|
+
┌───────────────────────────────────┐
|
|
234
|
+
│ authMiddleware() │
|
|
235
|
+
│ 1. Verify token │
|
|
236
|
+
│ 2. Inject user & session │
|
|
237
|
+
└───────────────┬───────────────────┘
|
|
238
|
+
│
|
|
239
|
+
▼
|
|
240
|
+
┌───────────────────────────────────┐
|
|
241
|
+
│ authRoutes.post() │
|
|
242
|
+
│ 1. Get session from context │
|
|
243
|
+
│ 2. Call AuthService.logout() │
|
|
244
|
+
└───────────────┬───────────────────┘
|
|
245
|
+
│
|
|
246
|
+
▼
|
|
247
|
+
┌───────────────────────────────────┐
|
|
248
|
+
│ AuthService.logout() │
|
|
249
|
+
│ 1. Revoke session │
|
|
250
|
+
│ 2. Log audit event │
|
|
251
|
+
└───────────────┬───────────────────┘
|
|
252
|
+
│
|
|
253
|
+
▼
|
|
254
|
+
┌───────────────────────────────────┐
|
|
255
|
+
│ SessionRepository.revoke() │
|
|
256
|
+
│ 1. Set is_revoked = true │
|
|
257
|
+
│ 2. Set revoked_at = now │
|
|
258
|
+
└───────────────┬───────────────────┘
|
|
259
|
+
│
|
|
260
|
+
▼
|
|
261
|
+
200 OK + Clear Cookie
|
|
262
|
+
{ message: "Logged out" }
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Effect-TS Dependency Graph
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
269
|
+
│ AppLayer │
|
|
270
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
271
|
+
│
|
|
272
|
+
│ Layer.mergeAll()
|
|
273
|
+
┌──────────────┼──────────────┐
|
|
274
|
+
│ │ │
|
|
275
|
+
┌───────────▼───────┐ ┌───▼──────┐ ┌───▼─────────┐
|
|
276
|
+
│ DatabaseService │ │Password │ │ Token │
|
|
277
|
+
│ .Live │ │Service │ │ Service │
|
|
278
|
+
└───────────┬───────┘ │ .Live │ │ .Live │
|
|
279
|
+
│ └──────────┘ └─────────────┘
|
|
280
|
+
│
|
|
281
|
+
│ Layer.provideMerge()
|
|
282
|
+
│
|
|
283
|
+
┌───────────▼───────────────────────────────┐
|
|
284
|
+
│ UserRepository.Live │
|
|
285
|
+
│ SessionRepository.Live │
|
|
286
|
+
│ AuditLogService.Live │
|
|
287
|
+
└───────────┬───────────────────────────────┘
|
|
288
|
+
│
|
|
289
|
+
│ Layer.provideMerge()
|
|
290
|
+
│
|
|
291
|
+
┌───────────▼───────────────────────────────┐
|
|
292
|
+
│ AuthService.Live │
|
|
293
|
+
│ │
|
|
294
|
+
│ Provides: │
|
|
295
|
+
│ - register() │
|
|
296
|
+
│ - login() │
|
|
297
|
+
│ - logout() │
|
|
298
|
+
│ - verifyToken() │
|
|
299
|
+
└────────────────────────────────────────────┘
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Security Architecture
|
|
303
|
+
|
|
304
|
+
### Password Security
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
Plain Password
|
|
308
|
+
│
|
|
309
|
+
│ validatePasswordStrength()
|
|
310
|
+
│ • Min 8 chars
|
|
311
|
+
│ • Uppercase + Lowercase
|
|
312
|
+
│ • Number required
|
|
313
|
+
│
|
|
314
|
+
▼
|
|
315
|
+
bcrypt.hash()
|
|
316
|
+
│ salt_rounds=12
|
|
317
|
+
│
|
|
318
|
+
▼
|
|
319
|
+
Password Hash
|
|
320
|
+
│ stored in DB
|
|
321
|
+
│ never logged
|
|
322
|
+
│ never transmitted
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Token Security
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
User Login
|
|
329
|
+
│
|
|
330
|
+
▼
|
|
331
|
+
Generate JWT
|
|
332
|
+
│ RS256 Algorithm
|
|
333
|
+
│ Private Key Signing
|
|
334
|
+
│ 7 Day Expiry
|
|
335
|
+
│
|
|
336
|
+
├─────────────────┬──────────────────┐
|
|
337
|
+
│ │ │
|
|
338
|
+
▼ ▼ ▼
|
|
339
|
+
JWT Token SHA-256 Hash Session
|
|
340
|
+
│ stored in DB Record
|
|
341
|
+
│ │
|
|
342
|
+
│ │
|
|
343
|
+
▼ ▼
|
|
344
|
+
Sent to Client Linked to Token Hash
|
|
345
|
+
(httpOnly cookie
|
|
346
|
+
or Bearer token)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Session Validation
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
Incoming Request
|
|
353
|
+
│
|
|
354
|
+
▼
|
|
355
|
+
Extract Token
|
|
356
|
+
│
|
|
357
|
+
├─── From Authorization Header
|
|
358
|
+
│ "Bearer <token>"
|
|
359
|
+
│
|
|
360
|
+
└─── From Cookie
|
|
361
|
+
"auth_token=<token>"
|
|
362
|
+
│
|
|
363
|
+
▼
|
|
364
|
+
Verify JWT Signature
|
|
365
|
+
│ • Check RS256 signature
|
|
366
|
+
│ • Verify not expired
|
|
367
|
+
│ • Validate payload
|
|
368
|
+
│
|
|
369
|
+
▼
|
|
370
|
+
Hash Token (SHA-256)
|
|
371
|
+
│
|
|
372
|
+
▼
|
|
373
|
+
Find Session by Hash
|
|
374
|
+
│
|
|
375
|
+
├─── Session not found? → 401
|
|
376
|
+
│
|
|
377
|
+
├─── Session expired? → 401
|
|
378
|
+
│
|
|
379
|
+
└─── Session revoked? → 401
|
|
380
|
+
│
|
|
381
|
+
▼
|
|
382
|
+
Find User by ID
|
|
383
|
+
│
|
|
384
|
+
└─── User inactive? → 401
|
|
385
|
+
│
|
|
386
|
+
▼
|
|
387
|
+
Request Authorized
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Account Lockout
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
Login Attempt
|
|
394
|
+
│
|
|
395
|
+
▼
|
|
396
|
+
Check Failed Attempts
|
|
397
|
+
│
|
|
398
|
+
├─── >= 5 attempts?
|
|
399
|
+
│ │
|
|
400
|
+
│ ▼
|
|
401
|
+
│ Check Lockout Time
|
|
402
|
+
│ │
|
|
403
|
+
│ ├─── Within 15 min? → LOCKED
|
|
404
|
+
│ │ Return 401
|
|
405
|
+
│ │
|
|
406
|
+
│ └─── After 15 min? → UNLOCKED
|
|
407
|
+
│ Continue login
|
|
408
|
+
│
|
|
409
|
+
▼
|
|
410
|
+
Verify Password
|
|
411
|
+
│
|
|
412
|
+
├─── Invalid?
|
|
413
|
+
│ │
|
|
414
|
+
│ ▼
|
|
415
|
+
│ Increment Failed Attempts
|
|
416
|
+
│ Return 401
|
|
417
|
+
│
|
|
418
|
+
└─── Valid?
|
|
419
|
+
│
|
|
420
|
+
▼
|
|
421
|
+
Reset Failed Attempts
|
|
422
|
+
Create Session
|
|
423
|
+
Return 200 + Token
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Data Flow Patterns
|
|
427
|
+
|
|
428
|
+
### Effect.gen Pattern
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
// Every operation uses Effect.gen
|
|
432
|
+
const operation = Effect.gen(function* () {
|
|
433
|
+
// 1. Acquire dependencies
|
|
434
|
+
const service = yield* ServiceTag;
|
|
435
|
+
|
|
436
|
+
// 2. Execute operations with yield*
|
|
437
|
+
const result = yield* service.method();
|
|
438
|
+
|
|
439
|
+
// 3. Handle errors with typed failures
|
|
440
|
+
if (errorCondition) {
|
|
441
|
+
return yield* Effect.fail(
|
|
442
|
+
new TypedError("code", "message")
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// 4. Return success value
|
|
447
|
+
return result;
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Layer Composition
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// Services declare dependencies
|
|
455
|
+
export class MyService extends Context.Tag("MyService")<
|
|
456
|
+
MyService,
|
|
457
|
+
Interface
|
|
458
|
+
>() {
|
|
459
|
+
static Live = Layer.effect(
|
|
460
|
+
this,
|
|
461
|
+
Effect.gen(function* () {
|
|
462
|
+
// Acquire dependencies
|
|
463
|
+
const dep1 = yield* Dependency1;
|
|
464
|
+
const dep2 = yield* Dependency2;
|
|
465
|
+
|
|
466
|
+
// Provide implementation
|
|
467
|
+
return { method: () => Effect.succeed(value) };
|
|
468
|
+
})
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Compose into application layer
|
|
473
|
+
export const AppLayer = Layer.mergeAll(
|
|
474
|
+
BaseDep1.Live,
|
|
475
|
+
BaseDep2.Live
|
|
476
|
+
).pipe(
|
|
477
|
+
Layer.provideMerge(ServiceA.Live),
|
|
478
|
+
Layer.provideMerge(ServiceB.Live),
|
|
479
|
+
Layer.provideMerge(MyService.Live)
|
|
480
|
+
);
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Error Handling
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
// HTTP Layer
|
|
487
|
+
try {
|
|
488
|
+
const result = await Effect.runPromise(
|
|
489
|
+
Effect.provide(operation, AppLayer)
|
|
490
|
+
);
|
|
491
|
+
return c.json(result, 200);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (error && typeof error === "object" && "_tag" in error) {
|
|
494
|
+
const typedError = error as { _tag: string; message: string };
|
|
495
|
+
|
|
496
|
+
switch (typedError._tag) {
|
|
497
|
+
case "AuthError":
|
|
498
|
+
return c.json({ error: typedError.message }, 401);
|
|
499
|
+
case "ValidationError":
|
|
500
|
+
return c.json({ error: typedError.message }, 400);
|
|
501
|
+
case "DatabaseError":
|
|
502
|
+
return c.json({ error: "Internal error" }, 500);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return c.json({ error: "Unexpected error" }, 500);
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Type Safety
|
|
510
|
+
|
|
511
|
+
### No Type Casting
|
|
512
|
+
```typescript
|
|
513
|
+
// ❌ NEVER use 'as' type casting
|
|
514
|
+
const user = data as User;
|
|
515
|
+
|
|
516
|
+
// ✅ ALWAYS validate and construct
|
|
517
|
+
const user: User = {
|
|
518
|
+
id: row.id,
|
|
519
|
+
username: row.username,
|
|
520
|
+
email: row.email,
|
|
521
|
+
// ... explicit mapping
|
|
522
|
+
};
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Typed Errors
|
|
526
|
+
```typescript
|
|
527
|
+
// Define error classes
|
|
528
|
+
export class AuthError {
|
|
529
|
+
readonly _tag = "AuthError";
|
|
530
|
+
constructor(
|
|
531
|
+
readonly code: string,
|
|
532
|
+
readonly message: string,
|
|
533
|
+
readonly cause?: unknown
|
|
534
|
+
) {}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Use in Effect
|
|
538
|
+
Effect.gen(function* () {
|
|
539
|
+
if (invalid) {
|
|
540
|
+
return yield* Effect.fail(
|
|
541
|
+
new AuthError("INVALID", "Error message")
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Effect Return Types
|
|
548
|
+
```typescript
|
|
549
|
+
// Method signatures specify all error types
|
|
550
|
+
readonly login: (
|
|
551
|
+
email: string,
|
|
552
|
+
password: string
|
|
553
|
+
) => Effect.Effect<
|
|
554
|
+
{ user: User; token: string; session: Session },
|
|
555
|
+
AuthError | DatabaseError
|
|
556
|
+
>;
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
## Performance Considerations
|
|
560
|
+
|
|
561
|
+
### Caching Strategy
|
|
562
|
+
- JWT keys cached in memory (keyPair variable)
|
|
563
|
+
- Database connection pooling via DatabaseService
|
|
564
|
+
- Session lookup by token hash (indexed)
|
|
565
|
+
- User lookup by email/username (indexed)
|
|
566
|
+
|
|
567
|
+
### Concurrency
|
|
568
|
+
- Independent operations in routes can be parallelized
|
|
569
|
+
- Effect composition enables optimal concurrency
|
|
570
|
+
- Database operations are sequential by necessity
|
|
571
|
+
|
|
572
|
+
### Token Hashing
|
|
573
|
+
- SHA-256 for fast session lookup
|
|
574
|
+
- bcrypt (12 rounds) for password security
|
|
575
|
+
- Balance between security and performance
|