@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.
Files changed (136) hide show
  1. package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
  2. package/LICENSE +623 -0
  3. package/README.md +442 -0
  4. package/bin/africode.js +73 -0
  5. package/bin/africode.js.1758507140 +343 -0
  6. package/bin/cli.ts +83 -0
  7. package/bin/create-africode.js +158 -0
  8. package/bin/scaffold.ts +219 -0
  9. package/components/accordion.js +183 -0
  10. package/components/alert.js +131 -0
  11. package/components/auth.js +172 -0
  12. package/components/avatar.js +117 -0
  13. package/components/badge.js +104 -0
  14. package/components/base.d.ts +139 -0
  15. package/components/base.js +184 -0
  16. package/components/button.js +164 -0
  17. package/components/card.js +137 -0
  18. package/components/cultural-card.js +243 -0
  19. package/components/divider.js +83 -0
  20. package/components/dropdown.js +171 -0
  21. package/components/error-boundary.js +155 -0
  22. package/components/form.js +131 -0
  23. package/components/grid.js +273 -0
  24. package/components/hero.js +138 -0
  25. package/components/icon.js +36 -0
  26. package/components/index.js +57 -0
  27. package/components/input.js +256 -0
  28. package/components/kanga-card.js +185 -0
  29. package/components/language-switcher.js +108 -0
  30. package/components/loader.js +80 -0
  31. package/components/modal.js +262 -0
  32. package/components/motion.js +84 -0
  33. package/components/navbar.js +236 -0
  34. package/components/pattern-showcase.js +225 -0
  35. package/components/progress.js +134 -0
  36. package/components/react.js +111 -0
  37. package/components/section.js +54 -0
  38. package/components/select.js +322 -0
  39. package/components/sidebar.js +180 -0
  40. package/components/skeleton.js +85 -0
  41. package/components/table.js +181 -0
  42. package/components/tabs.js +202 -0
  43. package/components/theme-toggle.js +82 -0
  44. package/components/toast.js +139 -0
  45. package/components/tooltip.js +167 -0
  46. package/core/a2ui-schema-manager.js +344 -0
  47. package/core/a2ui.js +431 -0
  48. package/core/bun-runtime.js +799 -0
  49. package/core/cli/commands/add.js +23 -0
  50. package/core/cli/commands/audit.js +58 -0
  51. package/core/cli/commands/build.js +137 -0
  52. package/core/cli/commands/create-plugin.js +241 -0
  53. package/core/cli/commands/dev.js +228 -0
  54. package/core/cli/commands/lint.js +23 -0
  55. package/core/cli/commands/test.js +34 -0
  56. package/core/cli/migrator.js +71 -0
  57. package/core/cli/ui.js +46 -0
  58. package/core/compliance.js +628 -0
  59. package/core/config.js +263 -0
  60. package/core/db-advanced.js +481 -0
  61. package/core/db.js +284 -0
  62. package/core/enhanced-hmr.js +404 -0
  63. package/core/errors.js +222 -0
  64. package/core/file-router.js +290 -0
  65. package/core/heartbeat.js +64 -0
  66. package/core/hmr-client.js +204 -0
  67. package/core/hmr.js +196 -0
  68. package/core/html.d.ts +116 -0
  69. package/core/html.js +160 -0
  70. package/core/hydration.js +52 -0
  71. package/core/lipa-namba-journey.js +572 -0
  72. package/core/motion.js +106 -0
  73. package/core/nida-cig-middleware.js +455 -0
  74. package/core/patterns.d.ts +124 -0
  75. package/core/patterns.js +833 -0
  76. package/core/plugins/index.js +312 -0
  77. package/core/router.js +387 -0
  78. package/core/sdk-client.js +62 -0
  79. package/core/sdk.d.ts +133 -0
  80. package/core/sdk.js +123 -0
  81. package/core/seo.js +76 -0
  82. package/core/server/auth-endpoints.js +339 -0
  83. package/core/server/auth.js +180 -0
  84. package/core/server/csrf.js +206 -0
  85. package/core/server/db.js +39 -0
  86. package/core/server/middleware.js +324 -0
  87. package/core/server/rate-limit.js +238 -0
  88. package/core/server/render.js +69 -0
  89. package/core/server/router.js +120 -0
  90. package/core/shim.js +28 -0
  91. package/core/state.d.ts +86 -0
  92. package/core/state.js +242 -0
  93. package/core/store.d.ts +122 -0
  94. package/core/store.js +61 -0
  95. package/core/validation.d.ts +233 -0
  96. package/core/validation.js +590 -0
  97. package/core/websocket.js +639 -0
  98. package/dist/africode.js +2905 -0
  99. package/dist/africode.js.map +61 -0
  100. package/dist/build-info.json +23 -0
  101. package/dist/components.js +2888 -0
  102. package/dist/components.js.map +58 -0
  103. package/dist/styles/africanity.css +322 -0
  104. package/dist/styles/typography.css +141 -0
  105. package/docs/IDE-Guide.md +50 -0
  106. package/package.json +110 -0
  107. package/src/index.ts +196 -0
  108. package/styles/africanity.css +322 -0
  109. package/styles/typography.css +141 -0
  110. package/templates/starter/.env.example +15 -0
  111. package/templates/starter/africode.config.js +40 -0
  112. package/templates/starter/package.json +14 -0
  113. package/templates/starter/src/pages/index.html +46 -0
  114. package/templates/starter/src/pages/index.js +32 -0
  115. package/templates/starter/src/styles/main.css +4 -0
  116. package/templates/starter-3d/.env.example +7 -0
  117. package/templates/starter-3d/africode.config.js +29 -0
  118. package/templates/starter-3d/components/af-model-viewer.js +125 -0
  119. package/templates/starter-3d/package.json +15 -0
  120. package/templates/starter-3d/src/pages/index.html +46 -0
  121. package/templates/starter-3d/src/pages/index.js +50 -0
  122. package/templates/starter-3d/src/styles/main.css +4 -0
  123. package/templates/starter-react/.env.example +15 -0
  124. package/templates/starter-react/africode.config.js +40 -0
  125. package/templates/starter-react/package.json +16 -0
  126. package/templates/starter-react/src/pages/index.html +46 -0
  127. package/templates/starter-react/src/pages/index.js +68 -0
  128. package/templates/starter-react/src/styles/main.css +4 -0
  129. package/templates/starter-tailwind/.env.example +15 -0
  130. package/templates/starter-tailwind/africode.config.js +40 -0
  131. package/templates/starter-tailwind/package.json +20 -0
  132. package/templates/starter-tailwind/src/pages/index.html +46 -0
  133. package/templates/starter-tailwind/src/pages/index.js +37 -0
  134. package/templates/starter-tailwind/src/styles/main.css +4 -0
  135. package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
  136. 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
+ }