archicore 0.3.1 → 0.3.2

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 (42) hide show
  1. package/README.md +48 -4
  2. package/dist/cli/commands/interactive.js +83 -23
  3. package/dist/cli/commands/projects.js +3 -3
  4. package/dist/cli/ui/prompt.d.ts +4 -0
  5. package/dist/cli/ui/prompt.js +22 -0
  6. package/dist/cli/utils/config.js +2 -2
  7. package/dist/cli/utils/upload-utils.js +65 -18
  8. package/dist/code-index/ast-parser.d.ts +4 -0
  9. package/dist/code-index/ast-parser.js +42 -0
  10. package/dist/code-index/index.d.ts +21 -1
  11. package/dist/code-index/index.js +45 -1
  12. package/dist/code-index/source-map-extractor.d.ts +71 -0
  13. package/dist/code-index/source-map-extractor.js +194 -0
  14. package/dist/gitlab/gitlab-service.d.ts +162 -0
  15. package/dist/gitlab/gitlab-service.js +652 -0
  16. package/dist/gitlab/index.d.ts +8 -0
  17. package/dist/gitlab/index.js +8 -0
  18. package/dist/server/config/passport.d.ts +14 -0
  19. package/dist/server/config/passport.js +86 -0
  20. package/dist/server/index.js +52 -10
  21. package/dist/server/middleware/api-auth.d.ts +2 -2
  22. package/dist/server/middleware/api-auth.js +21 -2
  23. package/dist/server/middleware/csrf.d.ts +23 -0
  24. package/dist/server/middleware/csrf.js +96 -0
  25. package/dist/server/routes/auth.d.ts +2 -2
  26. package/dist/server/routes/auth.js +204 -5
  27. package/dist/server/routes/device-auth.js +2 -2
  28. package/dist/server/routes/gitlab.d.ts +12 -0
  29. package/dist/server/routes/gitlab.js +528 -0
  30. package/dist/server/routes/oauth.d.ts +6 -0
  31. package/dist/server/routes/oauth.js +198 -0
  32. package/dist/server/services/audit-service.d.ts +1 -1
  33. package/dist/server/services/auth-service.d.ts +13 -1
  34. package/dist/server/services/auth-service.js +108 -7
  35. package/dist/server/services/email-service.d.ts +63 -0
  36. package/dist/server/services/email-service.js +586 -0
  37. package/dist/server/utils/disposable-email-domains.d.ts +14 -0
  38. package/dist/server/utils/disposable-email-domains.js +192 -0
  39. package/dist/types/api.d.ts +98 -0
  40. package/dist/types/gitlab.d.ts +245 -0
  41. package/dist/types/gitlab.js +11 -0
  42. package/package.json +1 -1
@@ -5,16 +5,94 @@ import { Router } from 'express';
5
5
  import { AuthService } from '../services/auth-service.js';
6
6
  import { auditService } from '../services/audit-service.js';
7
7
  import { Logger } from '../../utils/logger.js';
8
+ import { emailService } from '../services/email-service.js';
9
+ import { isDisposableEmail, getDisposableEmailError } from '../utils/disposable-email-domains.js';
10
+ import { cache } from '../services/cache.js';
8
11
  export const authRouter = Router();
9
12
  const authService = AuthService.getInstance();
10
- // Middleware to check authentication
13
+ // Cookie configuration for auth tokens
14
+ // Note: sameSite 'lax' allows cookies on top-level navigations (redirects from OAuth, links)
15
+ // while still protecting against CSRF on POST requests
16
+ const COOKIE_OPTIONS = {
17
+ httpOnly: true,
18
+ secure: process.env.NODE_ENV === 'production',
19
+ sameSite: 'lax',
20
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
21
+ path: '/'
22
+ };
23
+ // Helper to set auth cookie
24
+ function setAuthCookie(res, token) {
25
+ res.cookie('archicore_token', token, COOKIE_OPTIONS);
26
+ }
27
+ // Helper to clear auth cookie
28
+ function clearAuthCookie(res) {
29
+ res.clearCookie('archicore_token', { path: '/' });
30
+ }
31
+ // ========== Email Verification Code Storage (Redis with fallback) ==========
32
+ const VERIFICATION_PREFIX = 'verify:';
33
+ const VERIFICATION_TTL = 10 * 60; // 10 minutes in seconds
34
+ // Fallback in-memory storage when Redis is unavailable
35
+ const verificationCodes = new Map();
36
+ // Get verification code from Redis or fallback
37
+ async function getVerificationCode(email) {
38
+ const key = `${VERIFICATION_PREFIX}${email}`;
39
+ // Try Redis first
40
+ const cached = await cache.get(key);
41
+ if (cached)
42
+ return cached;
43
+ // Fallback to memory
44
+ const memCached = verificationCodes.get(email);
45
+ if (memCached && memCached.expiresAt > Date.now()) {
46
+ return memCached;
47
+ }
48
+ // Cleanup expired
49
+ if (memCached)
50
+ verificationCodes.delete(email);
51
+ return null;
52
+ }
53
+ // Set verification code in Redis or fallback
54
+ async function setVerificationCode(email, data) {
55
+ const key = `${VERIFICATION_PREFIX}${email}`;
56
+ // Store in Redis with TTL
57
+ await cache.set(key, data, { ttl: VERIFICATION_TTL });
58
+ // Also store in memory as fallback
59
+ verificationCodes.set(email, data);
60
+ }
61
+ // Delete verification code
62
+ async function deleteVerificationCode(email) {
63
+ const key = `${VERIFICATION_PREFIX}${email}`;
64
+ await cache.del(key);
65
+ verificationCodes.delete(email);
66
+ }
67
+ // Generate 6-digit verification code
68
+ function generateVerificationCode() {
69
+ return Math.floor(100000 + Math.random() * 900000).toString();
70
+ }
71
+ // Cleanup expired memory codes
72
+ setInterval(() => {
73
+ const now = Date.now();
74
+ for (const [email, data] of verificationCodes.entries()) {
75
+ if (data.expiresAt < now) {
76
+ verificationCodes.delete(email);
77
+ }
78
+ }
79
+ }, 60 * 1000); // Clean up every minute
80
+ // Middleware to check authentication (supports both Bearer token and httpOnly cookie)
11
81
  export async function authMiddleware(req, res, next) {
12
82
  const authHeader = req.headers.authorization;
13
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
83
+ const cookieToken = req.cookies?.archicore_token;
84
+ // Try Bearer token first, then cookie
85
+ let token = null;
86
+ if (authHeader && authHeader.startsWith('Bearer ')) {
87
+ token = authHeader.substring(7);
88
+ }
89
+ else if (cookieToken) {
90
+ token = cookieToken;
91
+ }
92
+ if (!token) {
14
93
  res.status(401).json({ error: 'No token provided' });
15
94
  return;
16
95
  }
17
- const token = authHeader.substring(7);
18
96
  const user = await authService.validateToken(token);
19
97
  if (!user) {
20
98
  res.status(401).json({ error: 'Invalid or expired token' });
@@ -31,9 +109,96 @@ export async function adminMiddleware(req, res, next) {
31
109
  }
32
110
  next();
33
111
  }
112
+ /**
113
+ * POST /api/auth/send-verification-code
114
+ * Send email verification code
115
+ */
116
+ authRouter.post('/send-verification-code', async (req, res) => {
117
+ try {
118
+ const { email } = req.body;
119
+ if (!email || typeof email !== 'string') {
120
+ res.status(400).json({ success: false, error: 'Valid email is required' });
121
+ return;
122
+ }
123
+ // Validate email format
124
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
125
+ if (!emailRegex.test(email)) {
126
+ res.status(400).json({ success: false, error: 'Invalid email format' });
127
+ return;
128
+ }
129
+ // Check for disposable/temporary email
130
+ if (isDisposableEmail(email)) {
131
+ res.status(400).json({ success: false, error: getDisposableEmailError() });
132
+ return;
133
+ }
134
+ // Check if email already registered
135
+ // This prevents enumeration but allows re-verification
136
+ const normalizedEmail = email.toLowerCase().trim();
137
+ // Generate verification code
138
+ const code = generateVerificationCode();
139
+ const expiresAt = Date.now() + 10 * 60 * 1000; // 10 minutes
140
+ // Store code in Redis (with memory fallback)
141
+ await setVerificationCode(normalizedEmail, {
142
+ email: normalizedEmail,
143
+ code,
144
+ expiresAt,
145
+ verified: false
146
+ });
147
+ // Send email
148
+ const emailSent = await emailService.sendVerificationCode(normalizedEmail, code);
149
+ if (!emailSent) {
150
+ Logger.warn(`[Auth] Failed to send verification code to ${normalizedEmail}`);
151
+ res.status(500).json({ success: false, error: 'Failed to send verification code' });
152
+ return;
153
+ }
154
+ Logger.info(`[Auth] Verification code sent to ${normalizedEmail}`);
155
+ res.json({ success: true, message: 'Verification code sent to your email' });
156
+ }
157
+ catch (error) {
158
+ Logger.error('[Auth] Send verification code error:', error);
159
+ res.status(500).json({ success: false, error: 'Failed to send verification code' });
160
+ }
161
+ });
162
+ /**
163
+ * POST /api/auth/verify-email
164
+ * Verify email with code
165
+ */
166
+ authRouter.post('/verify-email', async (req, res) => {
167
+ try {
168
+ const { email, code } = req.body;
169
+ if (!email || !code) {
170
+ res.status(400).json({ success: false, error: 'Email and code are required' });
171
+ return;
172
+ }
173
+ const normalizedEmail = email.toLowerCase().trim();
174
+ const storedData = await getVerificationCode(normalizedEmail);
175
+ if (!storedData) {
176
+ res.status(400).json({ success: false, error: 'No verification code found. Please request a new one.' });
177
+ return;
178
+ }
179
+ if (storedData.expiresAt < Date.now()) {
180
+ await deleteVerificationCode(normalizedEmail);
181
+ res.status(400).json({ success: false, error: 'Verification code expired. Please request a new one.' });
182
+ return;
183
+ }
184
+ if (storedData.code !== code.trim()) {
185
+ res.status(400).json({ success: false, error: 'Invalid verification code' });
186
+ return;
187
+ }
188
+ // Mark as verified and update in Redis
189
+ storedData.verified = true;
190
+ await setVerificationCode(normalizedEmail, storedData);
191
+ Logger.info(`[Auth] Email verified: ${normalizedEmail}`);
192
+ res.json({ success: true, message: 'Email verified successfully' });
193
+ }
194
+ catch (error) {
195
+ Logger.error('[Auth] Verify email error:', error);
196
+ res.status(500).json({ success: false, error: 'Failed to verify email' });
197
+ }
198
+ });
34
199
  /**
35
200
  * POST /api/auth/register
36
- * Register new user
201
+ * Register new user (requires verified email)
37
202
  */
38
203
  authRouter.post('/register', async (req, res) => {
39
204
  const ip = req.ip || req.headers['x-forwarded-for'] || 'unknown';
@@ -44,11 +209,32 @@ authRouter.post('/register', async (req, res) => {
44
209
  res.status(400).json({ success: false, error: 'Email, username, and password are required' });
45
210
  return;
46
211
  }
212
+ // Check email verification - MANDATORY
213
+ const normalizedEmail = email.toLowerCase().trim();
214
+ const verification = await getVerificationCode(normalizedEmail);
215
+ if (!verification) {
216
+ res.status(400).json({ success: false, error: 'Please verify your email first. Request a verification code to continue.' });
217
+ return;
218
+ }
219
+ if (!verification.verified) {
220
+ res.status(400).json({ success: false, error: 'Email not verified. Please enter the verification code sent to your email.' });
221
+ return;
222
+ }
223
+ if (verification.expiresAt < Date.now()) {
224
+ await deleteVerificationCode(normalizedEmail);
225
+ res.status(400).json({ success: false, error: 'Verification expired. Please request a new verification code.' });
226
+ return;
227
+ }
47
228
  if (password.length < 8) {
48
229
  res.status(400).json({ success: false, error: 'Password must be at least 8 characters' });
49
230
  return;
50
231
  }
51
232
  const result = await authService.register(email, username, password);
233
+ // Clear verification code after successful registration
234
+ if (result.success) {
235
+ await deleteVerificationCode(normalizedEmail);
236
+ Logger.info(`[Auth] Verification code cleared for ${normalizedEmail} after successful registration`);
237
+ }
52
238
  // Audit log
53
239
  if (result.success && result.user) {
54
240
  await auditService.log({
@@ -60,6 +246,10 @@ authRouter.post('/register', async (req, res) => {
60
246
  details: { email }
61
247
  });
62
248
  }
249
+ // Set httpOnly cookie if registration successful
250
+ if (result.success && result.token) {
251
+ setAuthCookie(res, result.token);
252
+ }
63
253
  res.json(result);
64
254
  }
65
255
  catch (error) {
@@ -92,6 +282,10 @@ authRouter.post('/login', async (req, res) => {
92
282
  success: result.success,
93
283
  errorMessage: result.success ? undefined : result.error
94
284
  });
285
+ // Set httpOnly cookie if login successful
286
+ if (result.success && result.token) {
287
+ setAuthCookie(res, result.token);
288
+ }
95
289
  res.json(result);
96
290
  }
97
291
  catch (error) {
@@ -116,7 +310,10 @@ authRouter.post('/logout', authMiddleware, async (req, res) => {
116
310
  const ip = req.ip || req.headers['x-forwarded-for'] || 'unknown';
117
311
  const userAgent = req.headers['user-agent'];
118
312
  try {
119
- const token = req.headers.authorization?.substring(7);
313
+ // Get token from header or cookie
314
+ const headerToken = req.headers.authorization?.substring(7);
315
+ const cookieToken = req.cookies?.archicore_token;
316
+ const token = headerToken || cookieToken;
120
317
  if (token) {
121
318
  await authService.logout(token);
122
319
  }
@@ -128,6 +325,8 @@ authRouter.post('/logout', authMiddleware, async (req, res) => {
128
325
  ip,
129
326
  userAgent
130
327
  });
328
+ // Clear the auth cookie
329
+ clearAuthCookie(res);
131
330
  res.json({ success: true });
132
331
  }
133
332
  catch (error) {
@@ -58,8 +58,8 @@ router.post('/code', (_req, res) => {
58
58
  if (!serverUrl) {
59
59
  const host = process.env.HOST || 'localhost';
60
60
  const port = process.env.PORT || 3000;
61
- // If HOST is 0.0.0.0 (bind to all interfaces), use the server's actual IP or fallback to request origin
62
- const effectiveHost = host === '0.0.0.0' ? (process.env.SERVER_IP || '194.156.66.251') : host;
61
+ // If HOST is 0.0.0.0 (bind to all interfaces), use localhost for fallback
62
+ const effectiveHost = host === '0.0.0.0' ? 'localhost' : host;
63
63
  serverUrl = `http://${effectiveHost}:${port}`;
64
64
  }
65
65
  res.json({
@@ -0,0 +1,12 @@
1
+ /**
2
+ * GitLab Integration Routes for ArchiCore
3
+ *
4
+ * Provides API endpoints for:
5
+ * - Managing GitLab instances (add, remove, list)
6
+ * - Listing and connecting repositories
7
+ * - Branch selection
8
+ * - Webhooks for auto-analysis
9
+ */
10
+ export declare const gitlabRouter: import("express-serve-static-core").Router;
11
+ export default gitlabRouter;
12
+ //# sourceMappingURL=gitlab.d.ts.map