@africode/core 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
- package/LICENSE +623 -0
- package/README.md +442 -0
- package/bin/africode.js +73 -0
- package/bin/africode.js.1758507140 +343 -0
- package/bin/cli.ts +83 -0
- package/bin/create-africode.js +158 -0
- package/bin/scaffold.ts +219 -0
- package/components/accordion.js +183 -0
- package/components/alert.js +131 -0
- package/components/auth.js +172 -0
- package/components/avatar.js +117 -0
- package/components/badge.js +104 -0
- package/components/base.d.ts +139 -0
- package/components/base.js +184 -0
- package/components/button.js +164 -0
- package/components/card.js +137 -0
- package/components/cultural-card.js +243 -0
- package/components/divider.js +83 -0
- package/components/dropdown.js +171 -0
- package/components/error-boundary.js +155 -0
- package/components/form.js +131 -0
- package/components/grid.js +273 -0
- package/components/hero.js +138 -0
- package/components/icon.js +36 -0
- package/components/index.js +57 -0
- package/components/input.js +256 -0
- package/components/kanga-card.js +185 -0
- package/components/language-switcher.js +108 -0
- package/components/loader.js +80 -0
- package/components/modal.js +262 -0
- package/components/motion.js +84 -0
- package/components/navbar.js +236 -0
- package/components/pattern-showcase.js +225 -0
- package/components/progress.js +134 -0
- package/components/react.js +111 -0
- package/components/section.js +54 -0
- package/components/select.js +322 -0
- package/components/sidebar.js +180 -0
- package/components/skeleton.js +85 -0
- package/components/table.js +181 -0
- package/components/tabs.js +202 -0
- package/components/theme-toggle.js +82 -0
- package/components/toast.js +139 -0
- package/components/tooltip.js +167 -0
- package/core/a2ui-schema-manager.js +344 -0
- package/core/a2ui.js +431 -0
- package/core/bun-runtime.js +799 -0
- package/core/cli/commands/add.js +23 -0
- package/core/cli/commands/audit.js +58 -0
- package/core/cli/commands/build.js +137 -0
- package/core/cli/commands/create-plugin.js +241 -0
- package/core/cli/commands/dev.js +228 -0
- package/core/cli/commands/lint.js +23 -0
- package/core/cli/commands/test.js +34 -0
- package/core/cli/migrator.js +71 -0
- package/core/cli/ui.js +46 -0
- package/core/compliance.js +628 -0
- package/core/config.js +263 -0
- package/core/db-advanced.js +481 -0
- package/core/db.js +284 -0
- package/core/enhanced-hmr.js +404 -0
- package/core/errors.js +222 -0
- package/core/file-router.js +290 -0
- package/core/heartbeat.js +64 -0
- package/core/hmr-client.js +204 -0
- package/core/hmr.js +196 -0
- package/core/html.d.ts +116 -0
- package/core/html.js +160 -0
- package/core/hydration.js +52 -0
- package/core/lipa-namba-journey.js +572 -0
- package/core/motion.js +106 -0
- package/core/nida-cig-middleware.js +455 -0
- package/core/patterns.d.ts +124 -0
- package/core/patterns.js +833 -0
- package/core/plugins/index.js +312 -0
- package/core/router.js +387 -0
- package/core/sdk-client.js +62 -0
- package/core/sdk.d.ts +133 -0
- package/core/sdk.js +123 -0
- package/core/seo.js +76 -0
- package/core/server/auth-endpoints.js +339 -0
- package/core/server/auth.js +180 -0
- package/core/server/csrf.js +206 -0
- package/core/server/db.js +39 -0
- package/core/server/middleware.js +324 -0
- package/core/server/rate-limit.js +238 -0
- package/core/server/render.js +69 -0
- package/core/server/router.js +120 -0
- package/core/shim.js +28 -0
- package/core/state.d.ts +86 -0
- package/core/state.js +242 -0
- package/core/store.d.ts +122 -0
- package/core/store.js +61 -0
- package/core/validation.d.ts +233 -0
- package/core/validation.js +590 -0
- package/core/websocket.js +639 -0
- package/dist/africode.js +2905 -0
- package/dist/africode.js.map +61 -0
- package/dist/build-info.json +23 -0
- package/dist/components.js +2888 -0
- package/dist/components.js.map +58 -0
- package/dist/styles/africanity.css +322 -0
- package/dist/styles/typography.css +141 -0
- package/docs/IDE-Guide.md +50 -0
- package/package.json +110 -0
- package/src/index.ts +196 -0
- package/styles/africanity.css +322 -0
- package/styles/typography.css +141 -0
- package/templates/starter/.env.example +15 -0
- package/templates/starter/africode.config.js +40 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/pages/index.html +46 -0
- package/templates/starter/src/pages/index.js +32 -0
- package/templates/starter/src/styles/main.css +4 -0
- package/templates/starter-3d/.env.example +7 -0
- package/templates/starter-3d/africode.config.js +29 -0
- package/templates/starter-3d/components/af-model-viewer.js +125 -0
- package/templates/starter-3d/package.json +15 -0
- package/templates/starter-3d/src/pages/index.html +46 -0
- package/templates/starter-3d/src/pages/index.js +50 -0
- package/templates/starter-3d/src/styles/main.css +4 -0
- package/templates/starter-react/.env.example +15 -0
- package/templates/starter-react/africode.config.js +40 -0
- package/templates/starter-react/package.json +16 -0
- package/templates/starter-react/src/pages/index.html +46 -0
- package/templates/starter-react/src/pages/index.js +68 -0
- package/templates/starter-react/src/styles/main.css +4 -0
- package/templates/starter-tailwind/.env.example +15 -0
- package/templates/starter-tailwind/africode.config.js +40 -0
- package/templates/starter-tailwind/package.json +20 -0
- package/templates/starter-tailwind/src/pages/index.html +46 -0
- package/templates/starter-tailwind/src/pages/index.js +37 -0
- package/templates/starter-tailwind/src/styles/main.css +4 -0
- package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
- package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Authentication Endpoints
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - POST /api/auth/register - Create new user account
|
|
6
|
+
* - POST /api/auth/login - Authenticate user
|
|
7
|
+
* - POST /api/auth/logout - Destroy session
|
|
8
|
+
* - GET /api/auth/me - Get current user
|
|
9
|
+
* - POST /api/auth/refresh - Refresh session
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { login, register, createSessionCookie, generateSessionId, createSession } from './auth.js';
|
|
13
|
+
import { Session, User } from '../db.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract session from request cookies
|
|
17
|
+
*/
|
|
18
|
+
function getSessionFromRequest(request) {
|
|
19
|
+
const cookieHeader = request.headers.get('Cookie');
|
|
20
|
+
if (!cookieHeader) return null;
|
|
21
|
+
|
|
22
|
+
const match = cookieHeader.match(/session_id=([^;]+)/);
|
|
23
|
+
return match ? match[1] : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get current user from session
|
|
28
|
+
*/
|
|
29
|
+
function getCurrentUser(sessionId) {
|
|
30
|
+
if (!sessionId) return null;
|
|
31
|
+
|
|
32
|
+
const session = Session.findById(sessionId);
|
|
33
|
+
if (!session) return null;
|
|
34
|
+
|
|
35
|
+
// Check if session is expired
|
|
36
|
+
if (new Date(session.expires_at) < new Date()) {
|
|
37
|
+
Session.delete(sessionId);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const user = User.findById(session.user_id);
|
|
42
|
+
return user ? {
|
|
43
|
+
id: user.id,
|
|
44
|
+
email: user.email,
|
|
45
|
+
username: user.username,
|
|
46
|
+
full_name: user.full_name,
|
|
47
|
+
created_at: user.created_at
|
|
48
|
+
} : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* POST /api/auth/register
|
|
53
|
+
* Create a new user account
|
|
54
|
+
*
|
|
55
|
+
* Request body:
|
|
56
|
+
* {
|
|
57
|
+
* email: "user@example.com",
|
|
58
|
+
* username: "johndoe",
|
|
59
|
+
* password: "SecurePassword123!",
|
|
60
|
+
* full_name: "John Doe"
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
export async function handleRegister(request) {
|
|
64
|
+
try {
|
|
65
|
+
const body = await request.json();
|
|
66
|
+
const { email, username, password, full_name } = body;
|
|
67
|
+
|
|
68
|
+
// Validation
|
|
69
|
+
if (!email || !email.includes('@')) {
|
|
70
|
+
return new Response(JSON.stringify({ error: 'Invalid email' }), {
|
|
71
|
+
status: 400,
|
|
72
|
+
headers: { 'Content-Type': 'application/json' }
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!username || username.length < 3) {
|
|
77
|
+
return new Response(JSON.stringify({ error: 'Username must be at least 3 characters' }), {
|
|
78
|
+
status: 400,
|
|
79
|
+
headers: { 'Content-Type': 'application/json' }
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!password || password.length < 8) {
|
|
84
|
+
return new Response(JSON.stringify({ error: 'Password must be at least 8 characters' }), {
|
|
85
|
+
status: 400,
|
|
86
|
+
headers: { 'Content-Type': 'application/json' }
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!full_name) {
|
|
91
|
+
return new Response(JSON.stringify({ error: 'Full name is required' }), {
|
|
92
|
+
status: 400,
|
|
93
|
+
headers: { 'Content-Type': 'application/json' }
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Register user
|
|
98
|
+
const result = await register({
|
|
99
|
+
email,
|
|
100
|
+
username,
|
|
101
|
+
password,
|
|
102
|
+
full_name
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (result.error) {
|
|
106
|
+
return new Response(JSON.stringify({ error: result.error }), {
|
|
107
|
+
status: 409,
|
|
108
|
+
headers: { 'Content-Type': 'application/json' }
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Success
|
|
113
|
+
return new Response(JSON.stringify({
|
|
114
|
+
success: true,
|
|
115
|
+
user: result.user,
|
|
116
|
+
message: 'Account created successfully'
|
|
117
|
+
}), {
|
|
118
|
+
status: 201,
|
|
119
|
+
headers: {
|
|
120
|
+
'Content-Type': 'application/json',
|
|
121
|
+
'Set-Cookie': createSessionCookie(result.sessionId, true)
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('[Auth] Register error:', error);
|
|
126
|
+
return new Response(JSON.stringify({ error: 'Registration failed' }), {
|
|
127
|
+
status: 500,
|
|
128
|
+
headers: { 'Content-Type': 'application/json' }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* POST /api/auth/login
|
|
135
|
+
* Authenticate user with email and password
|
|
136
|
+
*
|
|
137
|
+
* Request body:
|
|
138
|
+
* {
|
|
139
|
+
* email: "user@example.com",
|
|
140
|
+
* password: "SecurePassword123!"
|
|
141
|
+
* }
|
|
142
|
+
*/
|
|
143
|
+
export async function handleLogin(request) {
|
|
144
|
+
try {
|
|
145
|
+
const body = await request.json();
|
|
146
|
+
const { email, password } = body;
|
|
147
|
+
|
|
148
|
+
// Validation
|
|
149
|
+
if (!email || !password) {
|
|
150
|
+
return new Response(JSON.stringify({ error: 'Email and password required' }), {
|
|
151
|
+
status: 400,
|
|
152
|
+
headers: { 'Content-Type': 'application/json' }
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Authenticate
|
|
157
|
+
const result = await login(email, password);
|
|
158
|
+
|
|
159
|
+
if (result.error) {
|
|
160
|
+
return new Response(JSON.stringify({ error: result.error }), {
|
|
161
|
+
status: 401,
|
|
162
|
+
headers: { 'Content-Type': 'application/json' }
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Success
|
|
167
|
+
return new Response(JSON.stringify({
|
|
168
|
+
success: true,
|
|
169
|
+
user: result.user,
|
|
170
|
+
message: 'Logged in successfully'
|
|
171
|
+
}), {
|
|
172
|
+
status: 200,
|
|
173
|
+
headers: {
|
|
174
|
+
'Content-Type': 'application/json',
|
|
175
|
+
'Set-Cookie': createSessionCookie(result.sessionId, true)
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('[Auth] Login error:', error);
|
|
180
|
+
return new Response(JSON.stringify({ error: 'Login failed' }), {
|
|
181
|
+
status: 500,
|
|
182
|
+
headers: { 'Content-Type': 'application/json' }
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* POST /api/auth/logout
|
|
189
|
+
* Destroy the current session
|
|
190
|
+
*/
|
|
191
|
+
export async function handleLogout(request) {
|
|
192
|
+
try {
|
|
193
|
+
const sessionId = getSessionFromRequest(request);
|
|
194
|
+
|
|
195
|
+
if (sessionId) {
|
|
196
|
+
Session.delete(sessionId);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return new Response(JSON.stringify({
|
|
200
|
+
success: true,
|
|
201
|
+
message: 'Logged out successfully'
|
|
202
|
+
}), {
|
|
203
|
+
status: 200,
|
|
204
|
+
headers: {
|
|
205
|
+
'Content-Type': 'application/json',
|
|
206
|
+
'Set-Cookie': 'session_id=; HttpOnly; Path=/; Max-Age=0; SameSite=Strict'
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('[Auth] Logout error:', error);
|
|
211
|
+
return new Response(JSON.stringify({ error: 'Logout failed' }), {
|
|
212
|
+
status: 500,
|
|
213
|
+
headers: { 'Content-Type': 'application/json' }
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* GET /api/auth/me
|
|
220
|
+
* Get current authenticated user
|
|
221
|
+
*/
|
|
222
|
+
export async function handleGetMe(request) {
|
|
223
|
+
try {
|
|
224
|
+
const sessionId = getSessionFromRequest(request);
|
|
225
|
+
const user = getCurrentUser(sessionId);
|
|
226
|
+
|
|
227
|
+
if (!user) {
|
|
228
|
+
return new Response(JSON.stringify({ error: 'Not authenticated' }), {
|
|
229
|
+
status: 401,
|
|
230
|
+
headers: { 'Content-Type': 'application/json' }
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return new Response(JSON.stringify({
|
|
235
|
+
success: true,
|
|
236
|
+
user
|
|
237
|
+
}), {
|
|
238
|
+
status: 200,
|
|
239
|
+
headers: { 'Content-Type': 'application/json' }
|
|
240
|
+
});
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error('[Auth] GetMe error:', error);
|
|
243
|
+
return new Response(JSON.stringify({ error: 'Failed to get user' }), {
|
|
244
|
+
status: 500,
|
|
245
|
+
headers: { 'Content-Type': 'application/json' }
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* POST /api/auth/refresh
|
|
252
|
+
* Refresh session expiry
|
|
253
|
+
*/
|
|
254
|
+
export async function handleRefresh(request) {
|
|
255
|
+
try {
|
|
256
|
+
const sessionId = getSessionFromRequest(request);
|
|
257
|
+
const user = getCurrentUser(sessionId);
|
|
258
|
+
|
|
259
|
+
if (!user) {
|
|
260
|
+
return new Response(JSON.stringify({ error: 'Not authenticated' }), {
|
|
261
|
+
status: 401,
|
|
262
|
+
headers: { 'Content-Type': 'application/json' }
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Update session expiry
|
|
267
|
+
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
268
|
+
Session.update(sessionId, { expires_at: expiresAt.toISOString() });
|
|
269
|
+
|
|
270
|
+
return new Response(JSON.stringify({
|
|
271
|
+
success: true,
|
|
272
|
+
user,
|
|
273
|
+
message: 'Session refreshed'
|
|
274
|
+
}), {
|
|
275
|
+
status: 200,
|
|
276
|
+
headers: {
|
|
277
|
+
'Content-Type': 'application/json',
|
|
278
|
+
'Set-Cookie': createSessionCookie(sessionId, true)
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('[Auth] Refresh error:', error);
|
|
283
|
+
return new Response(JSON.stringify({ error: 'Refresh failed' }), {
|
|
284
|
+
status: 500,
|
|
285
|
+
headers: { 'Content-Type': 'application/json' }
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Route auth endpoints
|
|
292
|
+
*/
|
|
293
|
+
export async function handleAuthRequest(req, pathname) {
|
|
294
|
+
// POST /api/auth/register
|
|
295
|
+
if (pathname === '/api/auth/register' && req.method === 'POST') {
|
|
296
|
+
return await handleRegister(req);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// POST /api/auth/login
|
|
300
|
+
if (pathname === '/api/auth/login' && req.method === 'POST') {
|
|
301
|
+
return await handleLogin(req);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// POST /api/auth/logout
|
|
305
|
+
if (pathname === '/api/auth/logout' && req.method === 'POST') {
|
|
306
|
+
return await handleLogout(req);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// GET /api/auth/me
|
|
310
|
+
if (pathname === '/api/auth/me' && req.method === 'GET') {
|
|
311
|
+
return await handleGetMe(req);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// POST /api/auth/refresh
|
|
315
|
+
if (pathname === '/api/auth/refresh' && req.method === 'POST') {
|
|
316
|
+
return await handleRefresh(req);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Middleware to require authentication
|
|
324
|
+
*/
|
|
325
|
+
export function createAuthRequired(req) {
|
|
326
|
+
const sessionId = getSessionFromRequest(req);
|
|
327
|
+
const user = getCurrentUser(sessionId);
|
|
328
|
+
|
|
329
|
+
if (!user) {
|
|
330
|
+
return new Response(JSON.stringify({ error: 'Authentication required' }), {
|
|
331
|
+
status: 401,
|
|
332
|
+
headers: { 'Content-Type': 'application/json' }
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return user;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export { getCurrentUser, getSessionFromRequest };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Authentication System
|
|
3
|
+
* Session-based auth with SQLite storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { User, Session } from '../db.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hash a password using Bun's built-in crypto
|
|
10
|
+
*/
|
|
11
|
+
export async function hashPassword(password) {
|
|
12
|
+
return await Bun.password.hash(password, {
|
|
13
|
+
algorithm: 'argon2id',
|
|
14
|
+
memoryCost: 65536, // 64 MB
|
|
15
|
+
timeCost: 3,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Verify a password against its hash
|
|
21
|
+
*/
|
|
22
|
+
export async function verifyPassword(password, hash) {
|
|
23
|
+
return await Bun.password.verify(password, hash);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a secure session ID
|
|
28
|
+
*/
|
|
29
|
+
export function generateSessionId() {
|
|
30
|
+
return crypto.randomUUID();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a session that expires in 7 days
|
|
35
|
+
*/
|
|
36
|
+
export function createSessionExpiry() {
|
|
37
|
+
return new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a new session record for a user
|
|
42
|
+
*/
|
|
43
|
+
export function createSession(userId) {
|
|
44
|
+
const sessionId = generateSessionId();
|
|
45
|
+
const expiresAt = createSessionExpiry();
|
|
46
|
+
|
|
47
|
+
Session.create({
|
|
48
|
+
id: sessionId,
|
|
49
|
+
user_id: userId,
|
|
50
|
+
expires_at: expiresAt.toISOString()
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return sessionId;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build a session cookie header string
|
|
58
|
+
*/
|
|
59
|
+
export function createSessionCookie(sessionId, secure = false) {
|
|
60
|
+
return `session_id=${sessionId}; HttpOnly; Path=/; Max-Age=604800; SameSite=Strict${secure ? '; Secure' : ''}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Login user with email and password
|
|
65
|
+
*/
|
|
66
|
+
export async function login(email, password) {
|
|
67
|
+
const user = User.findByEmail(email);
|
|
68
|
+
if (!user) {
|
|
69
|
+
return { error: 'Invalid credentials' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const isValid = await verifyPassword(password, user.password_hash);
|
|
73
|
+
if (!isValid) {
|
|
74
|
+
return { error: 'Invalid credentials' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sessionId = createSession(user.id);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
sessionId,
|
|
81
|
+
user: {
|
|
82
|
+
id: user.id,
|
|
83
|
+
email: user.email,
|
|
84
|
+
username: user.username,
|
|
85
|
+
full_name: user.full_name
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Register a new user
|
|
92
|
+
*/
|
|
93
|
+
export async function register(userData) {
|
|
94
|
+
const { email, username, password, full_name } = userData;
|
|
95
|
+
|
|
96
|
+
// Check if user already exists
|
|
97
|
+
if (User.findByEmail(email)) {
|
|
98
|
+
return { error: 'Email already registered' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (User.findByUsername(username)) {
|
|
102
|
+
return { error: 'Username already taken' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const password_hash = await hashPassword(password);
|
|
107
|
+
const userId = User.create({
|
|
108
|
+
email,
|
|
109
|
+
username,
|
|
110
|
+
password_hash,
|
|
111
|
+
full_name
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const sessionId = createSession(userId);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
sessionId,
|
|
119
|
+
user: {
|
|
120
|
+
id: userId,
|
|
121
|
+
email,
|
|
122
|
+
username,
|
|
123
|
+
full_name
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('Registration error:', error);
|
|
128
|
+
return { error: 'Registration failed' };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get user from session
|
|
134
|
+
*/
|
|
135
|
+
export async function getSessionUser(request) {
|
|
136
|
+
const cookieHeader = request.headers.get('Cookie');
|
|
137
|
+
if (!cookieHeader) {return null;}
|
|
138
|
+
|
|
139
|
+
const match = cookieHeader.match(/session_id=([^;]+)/);
|
|
140
|
+
if (!match) {return null;}
|
|
141
|
+
|
|
142
|
+
const sessionId = match[1];
|
|
143
|
+
const session = Session.findById(sessionId);
|
|
144
|
+
|
|
145
|
+
return session ? {
|
|
146
|
+
id: session.user_id,
|
|
147
|
+
email: session.email,
|
|
148
|
+
username: session.username
|
|
149
|
+
} : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Logout by deleting session
|
|
154
|
+
*/
|
|
155
|
+
export async function logout(request) {
|
|
156
|
+
const cookieHeader = request.headers.get('Cookie');
|
|
157
|
+
if (!cookieHeader) {return { success: true };}
|
|
158
|
+
|
|
159
|
+
const match = cookieHeader.match(/session_id=([^;]+)/);
|
|
160
|
+
if (!match) {return { success: true };}
|
|
161
|
+
|
|
162
|
+
const sessionId = match[1];
|
|
163
|
+
Session.delete(sessionId);
|
|
164
|
+
|
|
165
|
+
return { success: true };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Middleware to require authentication
|
|
170
|
+
*/
|
|
171
|
+
export async function requireAuth(request) {
|
|
172
|
+
const user = await getSessionUser(request);
|
|
173
|
+
if (!user) {
|
|
174
|
+
return new Response(JSON.stringify({ error: 'Authentication required' }), {
|
|
175
|
+
status: 401,
|
|
176
|
+
headers: { 'Content-Type': 'application/json' }
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return user;
|
|
180
|
+
}
|