myaidev-method 0.2.8 → 0.2.9
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 +36 -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/payloadcms-publish.md +34 -14
- 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,290 @@
|
|
|
1
|
+
# Authentication System
|
|
2
|
+
|
|
3
|
+
Effect-TS based authentication system for MyAIDev Method web server.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
### Services Layer
|
|
8
|
+
|
|
9
|
+
**AuthService** - High-level authentication orchestration
|
|
10
|
+
- `register()` - User registration with validation
|
|
11
|
+
- `login()` - User authentication with session creation
|
|
12
|
+
- `logout()` - Session revocation
|
|
13
|
+
- `verifyToken()` - JWT verification and session validation
|
|
14
|
+
|
|
15
|
+
**PasswordService** - Password hashing and validation
|
|
16
|
+
- `hash()` - bcrypt password hashing (12 rounds)
|
|
17
|
+
- `verify()` - Password verification
|
|
18
|
+
- `validatePasswordStrength()` - Password strength requirements
|
|
19
|
+
|
|
20
|
+
**TokenService** - JWT token management
|
|
21
|
+
- `generateToken()` - Create signed JWT (RS256)
|
|
22
|
+
- `verifyToken()` - Verify and decode JWT
|
|
23
|
+
- `hashToken()` - SHA-256 token hashing
|
|
24
|
+
|
|
25
|
+
**UserRepository** - User data persistence
|
|
26
|
+
- `create()` - Create new user
|
|
27
|
+
- `findById()` - Find user by ID
|
|
28
|
+
- `findByEmail()` - Find user by email
|
|
29
|
+
- `findByUsername()` - Find user by username
|
|
30
|
+
- `update()` - Update user data
|
|
31
|
+
- `incrementFailedLogins()` - Track failed login attempts
|
|
32
|
+
- `resetFailedLogins()` - Reset failed login counter
|
|
33
|
+
|
|
34
|
+
**SessionRepository** - Session management
|
|
35
|
+
- `create()` - Create new session
|
|
36
|
+
- `findById()` - Find session by ID
|
|
37
|
+
- `findByTokenHash()` - Find session by token hash
|
|
38
|
+
- `revoke()` - Revoke single session
|
|
39
|
+
- `revokeAllForUser()` - Revoke all user sessions
|
|
40
|
+
- `deleteExpired()` - Clean up expired sessions
|
|
41
|
+
|
|
42
|
+
**AuditLogService** - Security audit logging
|
|
43
|
+
- `log()` - Record security events
|
|
44
|
+
|
|
45
|
+
### Middleware
|
|
46
|
+
|
|
47
|
+
**authMiddleware** - Hono middleware for route protection
|
|
48
|
+
- Extracts token from `Authorization: Bearer <token>` or `auth_token` cookie
|
|
49
|
+
- Verifies token and session validity
|
|
50
|
+
- Injects `user` and `session` into Hono context
|
|
51
|
+
- Returns 401 for authentication failures
|
|
52
|
+
|
|
53
|
+
### Routes
|
|
54
|
+
|
|
55
|
+
**POST /api/auth/register**
|
|
56
|
+
```typescript
|
|
57
|
+
Request: { username: string; email: string; password: string }
|
|
58
|
+
Response: { user: { id, username, email, emailVerified } }
|
|
59
|
+
Status: 201 Created | 400 Validation Error | 500 Server Error
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**POST /api/auth/login**
|
|
63
|
+
```typescript
|
|
64
|
+
Request: { email: string; password: string }
|
|
65
|
+
Response: { user: { id, username, email, emailVerified }, token: string }
|
|
66
|
+
Cookie: auth_token (httpOnly, secure, sameSite=strict)
|
|
67
|
+
Status: 200 OK | 401 Unauthorized | 500 Server Error
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**POST /api/auth/logout** (requires auth)
|
|
71
|
+
```typescript
|
|
72
|
+
Response: { message: "Logged out successfully" }
|
|
73
|
+
Status: 200 OK | 401 Unauthorized | 500 Server Error
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**GET /api/auth/me** (requires auth)
|
|
77
|
+
```typescript
|
|
78
|
+
Response: { user: { id, username, email, emailVerified, createdAt, lastLoginAt } }
|
|
79
|
+
Status: 200 OK | 401 Unauthorized | 500 Server Error
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Security Features
|
|
83
|
+
|
|
84
|
+
### Password Requirements
|
|
85
|
+
- Minimum 8 characters
|
|
86
|
+
- At least 1 uppercase letter
|
|
87
|
+
- At least 1 lowercase letter
|
|
88
|
+
- At least 1 number
|
|
89
|
+
- Hashed with bcrypt (12 rounds)
|
|
90
|
+
|
|
91
|
+
### Account Lockout
|
|
92
|
+
- 5 failed login attempts triggers lockout
|
|
93
|
+
- 15 minute lockout duration
|
|
94
|
+
- Automatic unlock after duration expires
|
|
95
|
+
- Failed attempts reset on successful login
|
|
96
|
+
|
|
97
|
+
### Session Management
|
|
98
|
+
- JWT tokens with RS256 signing
|
|
99
|
+
- 7 day token expiration
|
|
100
|
+
- Session stored with token hash (SHA-256)
|
|
101
|
+
- Session validation on every request
|
|
102
|
+
- Revocable sessions
|
|
103
|
+
|
|
104
|
+
### Cookie Configuration
|
|
105
|
+
```typescript
|
|
106
|
+
{
|
|
107
|
+
httpOnly: true,
|
|
108
|
+
secure: process.env.NODE_ENV === "production",
|
|
109
|
+
sameSite: "strict",
|
|
110
|
+
maxAge: 7 * 24 * 60 * 60, // 7 days
|
|
111
|
+
path: "/"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Linux Username Generation
|
|
116
|
+
- Sanitized from user's username
|
|
117
|
+
- Lowercase alphanumeric + underscores
|
|
118
|
+
- Must start with letter
|
|
119
|
+
- Max 32 characters
|
|
120
|
+
- Guaranteed unique with counter suffix if needed
|
|
121
|
+
|
|
122
|
+
## Usage Example
|
|
123
|
+
|
|
124
|
+
### Integration with Hono
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { Hono } from "hono";
|
|
128
|
+
import { authRouter, authMiddleware } from "./auth/index.js";
|
|
129
|
+
|
|
130
|
+
const app = new Hono();
|
|
131
|
+
|
|
132
|
+
// Public routes
|
|
133
|
+
app.route("/api/auth", authRouter);
|
|
134
|
+
|
|
135
|
+
// Protected routes
|
|
136
|
+
app.get("/api/protected", authMiddleware, (c) => {
|
|
137
|
+
const user = c.get("user");
|
|
138
|
+
const session = c.get("session");
|
|
139
|
+
return c.json({ message: `Hello ${user.username}` });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export default app;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Direct Service Usage
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { Effect } from "effect";
|
|
149
|
+
import { AppLayer } from "./auth/middleware/authMiddleware.js";
|
|
150
|
+
import { AuthService } from "./auth/services/AuthService.js";
|
|
151
|
+
|
|
152
|
+
const registerUser = Effect.gen(function* () {
|
|
153
|
+
const authService = yield* AuthService;
|
|
154
|
+
const user = yield* authService.register(
|
|
155
|
+
"testuser",
|
|
156
|
+
"test@example.com",
|
|
157
|
+
"SecurePass123",
|
|
158
|
+
"127.0.0.1",
|
|
159
|
+
"Mozilla/5.0"
|
|
160
|
+
);
|
|
161
|
+
return user;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const user = await Effect.runPromise(
|
|
165
|
+
Effect.provide(registerUser, AppLayer)
|
|
166
|
+
);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Error Handling
|
|
170
|
+
|
|
171
|
+
All services use typed Effect errors:
|
|
172
|
+
|
|
173
|
+
- **AuthError** - Authentication failures (401)
|
|
174
|
+
- `INVALID_CREDENTIALS` - Wrong email/password
|
|
175
|
+
- `ACCOUNT_LOCKED` - Too many failed attempts
|
|
176
|
+
- `TOKEN_EXPIRED` - Session expired
|
|
177
|
+
- `SESSION_REVOKED` - Session invalidated
|
|
178
|
+
- `USER_INACTIVE` - Account disabled
|
|
179
|
+
|
|
180
|
+
- **ValidationError** - Input validation failures (400)
|
|
181
|
+
- Field-specific error messages
|
|
182
|
+
- Password strength requirements
|
|
183
|
+
- Email format validation
|
|
184
|
+
|
|
185
|
+
- **DatabaseError** - Database operation failures (500)
|
|
186
|
+
- Generic database errors
|
|
187
|
+
- Connection issues
|
|
188
|
+
|
|
189
|
+
## Audit Logging
|
|
190
|
+
|
|
191
|
+
All authentication events are logged:
|
|
192
|
+
- `USER_REGISTERED` - New user registration
|
|
193
|
+
- `USER_LOGIN` - Successful login
|
|
194
|
+
- `USER_LOGOUT` - User logout
|
|
195
|
+
- `LOGIN_FAILED` - Failed login attempt
|
|
196
|
+
- `ACCOUNT_LOCKED` - Account locked notification
|
|
197
|
+
|
|
198
|
+
Audit logs include:
|
|
199
|
+
- User ID
|
|
200
|
+
- Action type
|
|
201
|
+
- Resource type and ID
|
|
202
|
+
- IP address
|
|
203
|
+
- User agent
|
|
204
|
+
- Timestamp
|
|
205
|
+
- Additional details
|
|
206
|
+
|
|
207
|
+
## Testing
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { describe, it, expect } from "vitest";
|
|
211
|
+
import { Effect } from "effect";
|
|
212
|
+
import { AppLayer } from "./middleware/authMiddleware.js";
|
|
213
|
+
import { AuthService } from "./services/AuthService.js";
|
|
214
|
+
|
|
215
|
+
describe("AuthService", () => {
|
|
216
|
+
it("should register a new user", async () => {
|
|
217
|
+
const registerEffect = Effect.gen(function* () {
|
|
218
|
+
const authService = yield* AuthService;
|
|
219
|
+
return yield* authService.register(
|
|
220
|
+
"testuser",
|
|
221
|
+
"test@example.com",
|
|
222
|
+
"SecurePass123"
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const user = await Effect.runPromise(
|
|
227
|
+
Effect.provide(registerEffect, AppLayer)
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
expect(user.username).toBe("testuser");
|
|
231
|
+
expect(user.email).toBe("test@example.com");
|
|
232
|
+
expect(user.passwordHash).toBeTruthy();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Dependencies
|
|
238
|
+
|
|
239
|
+
- `effect` - Effect-TS for composable error handling
|
|
240
|
+
- `hono` - Web framework
|
|
241
|
+
- `bcrypt` - Password hashing
|
|
242
|
+
- `jose` - JWT handling (RS256)
|
|
243
|
+
- `node:crypto` - Token hashing and UUID generation
|
|
244
|
+
|
|
245
|
+
## Design Patterns
|
|
246
|
+
|
|
247
|
+
### Effect-TS Context.Tag Pattern
|
|
248
|
+
All services use `Context.Tag` for dependency injection:
|
|
249
|
+
```typescript
|
|
250
|
+
export class ServiceName extends Context.Tag("ServiceName")<
|
|
251
|
+
ServiceName,
|
|
252
|
+
ServiceInterface
|
|
253
|
+
>() {
|
|
254
|
+
static Live = Layer.effect(this, Effect.gen(function* () {
|
|
255
|
+
// Dependencies
|
|
256
|
+
const dep = yield* DependencyService;
|
|
257
|
+
|
|
258
|
+
// Implementation
|
|
259
|
+
return { method: () => Effect.succeed(result) };
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Layer Composition
|
|
265
|
+
Dependencies are composed into a single `AppLayer`:
|
|
266
|
+
```typescript
|
|
267
|
+
export const AppLayer = Layer.mergeAll(
|
|
268
|
+
DatabaseService.Live,
|
|
269
|
+
PasswordService.Live,
|
|
270
|
+
TokenService.Live
|
|
271
|
+
).pipe(
|
|
272
|
+
Layer.provideMerge(UserRepository.Live),
|
|
273
|
+
Layer.provideMerge(SessionRepository.Live),
|
|
274
|
+
Layer.provideMerge(AuditLogService.Live),
|
|
275
|
+
Layer.provideMerge(AuthService.Live)
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Effect.gen for Composition
|
|
280
|
+
All business logic uses `Effect.gen` with `yield*`:
|
|
281
|
+
```typescript
|
|
282
|
+
const operation = Effect.gen(function* () {
|
|
283
|
+
const service = yield* Service;
|
|
284
|
+
const result = yield* service.method();
|
|
285
|
+
return result;
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### No Type Casting
|
|
290
|
+
All type conversions are explicit and safe - no `as` type assertions.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { AuthService } from "../services/AuthService.js";
|
|
3
|
+
import {
|
|
4
|
+
AuthError,
|
|
5
|
+
AuthResponse,
|
|
6
|
+
DatabaseError,
|
|
7
|
+
LoginRequest,
|
|
8
|
+
RegisterRequest,
|
|
9
|
+
User,
|
|
10
|
+
ValidationError,
|
|
11
|
+
} from "../../../shared/types.js";
|
|
12
|
+
|
|
13
|
+
export interface RegisterResult {
|
|
14
|
+
user: {
|
|
15
|
+
id: string;
|
|
16
|
+
username: string;
|
|
17
|
+
email: string;
|
|
18
|
+
emailVerified: boolean;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LoginResult extends AuthResponse {}
|
|
23
|
+
|
|
24
|
+
export interface MeResult {
|
|
25
|
+
user: {
|
|
26
|
+
id: string;
|
|
27
|
+
username: string;
|
|
28
|
+
email: string;
|
|
29
|
+
emailVerified: boolean;
|
|
30
|
+
createdAt: number;
|
|
31
|
+
lastLoginAt: number | null;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class AuthController extends Context.Tag("AuthController")<
|
|
36
|
+
AuthController,
|
|
37
|
+
{
|
|
38
|
+
readonly register: (
|
|
39
|
+
data: RegisterRequest,
|
|
40
|
+
ipAddress?: string | null,
|
|
41
|
+
userAgent?: string | null
|
|
42
|
+
) => Effect.Effect<RegisterResult, AuthError | ValidationError | DatabaseError>;
|
|
43
|
+
readonly login: (
|
|
44
|
+
data: LoginRequest,
|
|
45
|
+
ipAddress?: string | null,
|
|
46
|
+
userAgent?: string | null
|
|
47
|
+
) => Effect.Effect<LoginResult, AuthError | DatabaseError>;
|
|
48
|
+
readonly logout: (
|
|
49
|
+
sessionId: string,
|
|
50
|
+
userId: string
|
|
51
|
+
) => Effect.Effect<void, DatabaseError>;
|
|
52
|
+
readonly me: (user: User) => Effect.Effect<MeResult, never>;
|
|
53
|
+
}
|
|
54
|
+
>() {
|
|
55
|
+
static Live = Layer.effect(
|
|
56
|
+
this,
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
const authService = yield* AuthService;
|
|
59
|
+
|
|
60
|
+
const register = (
|
|
61
|
+
data: RegisterRequest,
|
|
62
|
+
ipAddress?: string | null,
|
|
63
|
+
userAgent?: string | null
|
|
64
|
+
): Effect.Effect<RegisterResult, AuthError | ValidationError | DatabaseError> =>
|
|
65
|
+
Effect.gen(function* () {
|
|
66
|
+
const user = yield* authService.register(
|
|
67
|
+
data.username,
|
|
68
|
+
data.email,
|
|
69
|
+
data.password,
|
|
70
|
+
ipAddress,
|
|
71
|
+
userAgent
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
user: {
|
|
76
|
+
id: user.id,
|
|
77
|
+
username: user.username,
|
|
78
|
+
email: user.email,
|
|
79
|
+
emailVerified: user.emailVerified,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const login = (
|
|
85
|
+
data: LoginRequest,
|
|
86
|
+
ipAddress?: string | null,
|
|
87
|
+
userAgent?: string | null
|
|
88
|
+
): Effect.Effect<LoginResult, AuthError | DatabaseError> =>
|
|
89
|
+
Effect.gen(function* () {
|
|
90
|
+
const result = yield* authService.login(
|
|
91
|
+
data.email,
|
|
92
|
+
data.password,
|
|
93
|
+
ipAddress,
|
|
94
|
+
userAgent
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
user: {
|
|
99
|
+
id: result.user.id,
|
|
100
|
+
username: result.user.username,
|
|
101
|
+
email: result.user.email,
|
|
102
|
+
emailVerified: result.user.emailVerified,
|
|
103
|
+
},
|
|
104
|
+
token: result.token,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const logout = (
|
|
109
|
+
sessionId: string,
|
|
110
|
+
userId: string
|
|
111
|
+
): Effect.Effect<void, DatabaseError> =>
|
|
112
|
+
authService.logout(sessionId, userId);
|
|
113
|
+
|
|
114
|
+
const me = (user: User): Effect.Effect<MeResult, never> =>
|
|
115
|
+
Effect.succeed({
|
|
116
|
+
user: {
|
|
117
|
+
id: user.id,
|
|
118
|
+
username: user.username,
|
|
119
|
+
email: user.email,
|
|
120
|
+
emailVerified: user.emailVerified,
|
|
121
|
+
createdAt: user.createdAt,
|
|
122
|
+
lastLoginAt: user.lastLoginAt,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return { register, login, logout, me };
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example usage of the authentication system
|
|
3
|
+
* This file demonstrates how to integrate the auth system with Hono
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
import { Layer } from "effect";
|
|
8
|
+
import { createAuthRoutes, createAuthMiddleware } from "./index.js";
|
|
9
|
+
import { PasswordService } from "./services/PasswordService.js";
|
|
10
|
+
import { TokenService } from "./services/TokenService.js";
|
|
11
|
+
import { UserRepository } from "./services/UserRepository.js";
|
|
12
|
+
import { SessionRepository } from "./services/SessionRepository.js";
|
|
13
|
+
import { AuditLogService } from "./services/AuditLogService.js";
|
|
14
|
+
import { AuthService } from "./services/AuthService.js";
|
|
15
|
+
import { DatabaseService } from "../database/db.js";
|
|
16
|
+
|
|
17
|
+
// Build the complete application layer with all services
|
|
18
|
+
const DbLayer = DatabaseService.Live({
|
|
19
|
+
path: process.env["DB_PATH"] || "./auth.db",
|
|
20
|
+
timeout: 5000,
|
|
21
|
+
verbose: process.env["NODE_ENV"] === "development",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const IndependentServices = Layer.mergeAll(
|
|
25
|
+
PasswordService.Live,
|
|
26
|
+
TokenService.Live,
|
|
27
|
+
AuditLogService.Live
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const Repositories = Layer.mergeAll(
|
|
31
|
+
UserRepository.Live,
|
|
32
|
+
SessionRepository.Live
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const Auth = AuthService.Live;
|
|
36
|
+
|
|
37
|
+
const AppLayer = Layer.mergeAll(
|
|
38
|
+
IndependentServices,
|
|
39
|
+
Repositories,
|
|
40
|
+
Auth
|
|
41
|
+
).pipe(Layer.provide(DbLayer));
|
|
42
|
+
|
|
43
|
+
// Create main app
|
|
44
|
+
const app = new Hono();
|
|
45
|
+
|
|
46
|
+
// Create middleware and routes with AppLayer
|
|
47
|
+
const authMiddleware = createAuthMiddleware(AppLayer);
|
|
48
|
+
const authRouter = createAuthRoutes(AppLayer, authMiddleware);
|
|
49
|
+
|
|
50
|
+
// Mount authentication routes (public)
|
|
51
|
+
app.route("/api/auth", authRouter);
|
|
52
|
+
|
|
53
|
+
// Protected route example - requires authentication
|
|
54
|
+
app.get("/api/protected/profile", authMiddleware, (c) => {
|
|
55
|
+
const user = c.get("user");
|
|
56
|
+
const session = c.get("session");
|
|
57
|
+
|
|
58
|
+
return c.json({
|
|
59
|
+
message: "This is a protected endpoint",
|
|
60
|
+
user: {
|
|
61
|
+
id: user.id,
|
|
62
|
+
username: user.username,
|
|
63
|
+
email: user.email,
|
|
64
|
+
},
|
|
65
|
+
session: {
|
|
66
|
+
id: session.id,
|
|
67
|
+
createdAt: session.createdAt,
|
|
68
|
+
expiresAt: session.expiresAt,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Protected route example - user data
|
|
74
|
+
app.get("/api/protected/data", authMiddleware, (c) => {
|
|
75
|
+
const user = c.get("user");
|
|
76
|
+
|
|
77
|
+
return c.json({
|
|
78
|
+
data: {
|
|
79
|
+
userId: user.id,
|
|
80
|
+
username: user.username,
|
|
81
|
+
linuxUsername: user.linuxUsername,
|
|
82
|
+
isActive: user.isActive,
|
|
83
|
+
emailVerified: user.emailVerified,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Admin-only route example
|
|
89
|
+
app.get("/api/protected/admin", authMiddleware, (c) => {
|
|
90
|
+
const user = c.get("user");
|
|
91
|
+
|
|
92
|
+
// Add your own admin check logic here
|
|
93
|
+
// For example, check if user has admin role
|
|
94
|
+
if (user.email !== "admin@example.com") {
|
|
95
|
+
return c.json({ error: "FORBIDDEN", message: "Admin access required" }, 403);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return c.json({
|
|
99
|
+
message: "Welcome to admin panel",
|
|
100
|
+
adminData: "Sensitive information",
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Health check endpoint (public)
|
|
105
|
+
app.get("/health", (c) => {
|
|
106
|
+
return c.json({ status: "ok", timestamp: Date.now() });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Start server
|
|
110
|
+
const port = process.env["PORT"] || 3000;
|
|
111
|
+
console.log(`Server starting on port ${port}`);
|
|
112
|
+
|
|
113
|
+
export default app;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* To run this example:
|
|
117
|
+
*
|
|
118
|
+
* 1. Install dependencies:
|
|
119
|
+
* npm install hono effect bcrypt jose
|
|
120
|
+
*
|
|
121
|
+
* 2. Set up environment:
|
|
122
|
+
* export NODE_ENV=development
|
|
123
|
+
* export PORT=3000
|
|
124
|
+
*
|
|
125
|
+
* 3. Initialize database:
|
|
126
|
+
* node --import tsx/esm src/server/database/init-db.ts
|
|
127
|
+
*
|
|
128
|
+
* 4. Run the server:
|
|
129
|
+
* node --import tsx/esm src/server/auth/example-usage.ts
|
|
130
|
+
*
|
|
131
|
+
* API Usage Examples:
|
|
132
|
+
*
|
|
133
|
+
* Register:
|
|
134
|
+
* curl -X POST http://localhost:3000/api/auth/register \
|
|
135
|
+
* -H "Content-Type: application/json" \
|
|
136
|
+
* -d '{"username":"testuser","email":"test@example.com","password":"SecurePass123"}'
|
|
137
|
+
*
|
|
138
|
+
* Login:
|
|
139
|
+
* curl -X POST http://localhost:3000/api/auth/login \
|
|
140
|
+
* -H "Content-Type: application/json" \
|
|
141
|
+
* -d '{"email":"test@example.com","password":"SecurePass123"}' \
|
|
142
|
+
* -c cookies.txt
|
|
143
|
+
*
|
|
144
|
+
* Access protected endpoint (with cookie):
|
|
145
|
+
* curl http://localhost:3000/api/protected/profile \
|
|
146
|
+
* -b cookies.txt
|
|
147
|
+
*
|
|
148
|
+
* Access protected endpoint (with Bearer token):
|
|
149
|
+
* curl http://localhost:3000/api/protected/profile \
|
|
150
|
+
* -H "Authorization: Bearer <token>"
|
|
151
|
+
*
|
|
152
|
+
* Get current user:
|
|
153
|
+
* curl http://localhost:3000/api/auth/me \
|
|
154
|
+
* -b cookies.txt
|
|
155
|
+
*
|
|
156
|
+
* Logout:
|
|
157
|
+
* curl -X POST http://localhost:3000/api/auth/logout \
|
|
158
|
+
* -b cookies.txt
|
|
159
|
+
*/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Authentication module exports
|
|
2
|
+
|
|
3
|
+
// Services
|
|
4
|
+
export { AuthService } from "./services/AuthService.js";
|
|
5
|
+
export { PasswordService } from "./services/PasswordService.js";
|
|
6
|
+
export { TokenService } from "./services/TokenService.js";
|
|
7
|
+
export { UserRepository } from "./services/UserRepository.js";
|
|
8
|
+
export { SessionRepository } from "./services/SessionRepository.js";
|
|
9
|
+
export { AuditLogService } from "./services/AuditLogService.js";
|
|
10
|
+
|
|
11
|
+
// Controllers
|
|
12
|
+
export { AuthController } from "./controllers/AuthController.js";
|
|
13
|
+
|
|
14
|
+
// Middleware
|
|
15
|
+
export { createAuthMiddleware } from "./middleware/authMiddleware.js";
|
|
16
|
+
export type { AppRuntimeContext } from "./middleware/authMiddleware.js";
|
|
17
|
+
|
|
18
|
+
// Routes
|
|
19
|
+
export { createAuthRoutes } from "./routes/authRoutes.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { PasswordService } from "./services/PasswordService.js";
|
|
3
|
+
import { TokenService } from "./services/TokenService.js";
|
|
4
|
+
import { UserRepository } from "./services/UserRepository.js";
|
|
5
|
+
import { SessionRepository } from "./services/SessionRepository.js";
|
|
6
|
+
import { AuditLogService } from "./services/AuditLogService.js";
|
|
7
|
+
import { AuthService } from "./services/AuthService.js";
|
|
8
|
+
import { DatabaseService } from "../database/db.js";
|
|
9
|
+
import { UserManagementLayer } from "../user-management/layers.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates the complete application layer with all auth services
|
|
13
|
+
* @param dbConfig Database configuration
|
|
14
|
+
* @returns Complete layer with all services including DatabaseService
|
|
15
|
+
*/
|
|
16
|
+
export const createAppLayer = (dbConfig: { path: string; timeout?: number; verbose?: boolean }) => {
|
|
17
|
+
// Base database layer
|
|
18
|
+
const DbLayer = DatabaseService.Live(dbConfig);
|
|
19
|
+
|
|
20
|
+
// Service layers that don't require DatabaseService
|
|
21
|
+
const IndependentServices = Layer.mergeAll(
|
|
22
|
+
PasswordService.Live,
|
|
23
|
+
TokenService.Live
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Services and repositories that require DatabaseService
|
|
27
|
+
const DbDependentServices = Layer.mergeAll(
|
|
28
|
+
AuditLogService.Live,
|
|
29
|
+
UserRepository.Live,
|
|
30
|
+
SessionRepository.Live
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Provide DatabaseService to dependent services
|
|
34
|
+
const DbDependentWithDb = Layer.provide(DbDependentServices, DbLayer);
|
|
35
|
+
|
|
36
|
+
// Merge all services and database
|
|
37
|
+
const BaseServices = Layer.mergeAll(
|
|
38
|
+
IndependentServices,
|
|
39
|
+
DbDependentWithDb,
|
|
40
|
+
DbLayer
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Add user management layer
|
|
44
|
+
const ServicesWithUserManagement = Layer.mergeAll(
|
|
45
|
+
BaseServices,
|
|
46
|
+
UserManagementLayer
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Provide all dependencies to AuthService
|
|
50
|
+
const AuthWithDeps = Layer.provide(AuthService.Live, ServicesWithUserManagement);
|
|
51
|
+
|
|
52
|
+
// Complete application layer - merge everything
|
|
53
|
+
return Layer.mergeAll(
|
|
54
|
+
ServicesWithUserManagement,
|
|
55
|
+
AuthWithDeps
|
|
56
|
+
);
|
|
57
|
+
};
|