archicore 0.2.1 → 0.2.3

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.
@@ -17,6 +17,27 @@ export declare class AuthService {
17
17
  private createDefaultAdmin;
18
18
  private saveUsers;
19
19
  private saveSessions;
20
+ private readonly BCRYPT_ROUNDS;
21
+ /**
22
+ * Hash password with bcrypt (async)
23
+ */
24
+ private hashPasswordAsync;
25
+ /**
26
+ * Legacy SHA256 hash for backward compatibility
27
+ */
28
+ private hashPasswordLegacy;
29
+ /**
30
+ * Verify password against hash (supports both bcrypt and legacy SHA256)
31
+ */
32
+ private verifyPassword;
33
+ /**
34
+ * Check if password needs rehashing (from legacy to bcrypt)
35
+ */
36
+ private needsRehash;
37
+ /**
38
+ * Sync hash for JSON fallback (still uses legacy for simplicity)
39
+ * @deprecated Use hashPasswordAsync for new passwords
40
+ */
20
41
  private hashPassword;
21
42
  private createEmptyUsage;
22
43
  private generateToken;
@@ -6,6 +6,7 @@
6
6
  import { randomUUID } from 'crypto';
7
7
  import { createHash } from 'crypto';
8
8
  import { readFile, writeFile, mkdir } from 'fs/promises';
9
+ import bcrypt from 'bcrypt';
9
10
  import { join } from 'path';
10
11
  import { TIER_LIMITS } from '../../types/user.js';
11
12
  import { db } from './database.js';
@@ -65,7 +66,7 @@ export class AuthService {
65
66
  const admin = {
66
67
  id: 'admin-' + randomUUID(),
67
68
  email: 'admin@archicore.io',
68
- username: 'Administrator',
69
+ username: 'ArchiCore Team',
69
70
  passwordHash: this.hashPassword('admin123'),
70
71
  tier: 'admin',
71
72
  provider: 'email',
@@ -85,9 +86,45 @@ export class AuthService {
85
86
  await writeFile(sessionsPath, JSON.stringify({ sessions: this.sessions }, null, 2));
86
87
  }
87
88
  // ========== HELPER METHODS ==========
88
- hashPassword(password) {
89
+ BCRYPT_ROUNDS = 12;
90
+ /**
91
+ * Hash password with bcrypt (async)
92
+ */
93
+ async hashPasswordAsync(password) {
94
+ return bcrypt.hash(password, this.BCRYPT_ROUNDS);
95
+ }
96
+ /**
97
+ * Legacy SHA256 hash for backward compatibility
98
+ */
99
+ hashPasswordLegacy(password) {
89
100
  return createHash('sha256').update(password + 'archicore-salt-2024').digest('hex');
90
101
  }
102
+ /**
103
+ * Verify password against hash (supports both bcrypt and legacy SHA256)
104
+ */
105
+ async verifyPassword(password, hash) {
106
+ // Check if it's a bcrypt hash (starts with $2b$ or $2a$)
107
+ if (hash.startsWith('$2')) {
108
+ return bcrypt.compare(password, hash);
109
+ }
110
+ // Legacy SHA256 hash
111
+ return this.hashPasswordLegacy(password) === hash;
112
+ }
113
+ /**
114
+ * Check if password needs rehashing (from legacy to bcrypt)
115
+ */
116
+ needsRehash(hash) {
117
+ return !hash.startsWith('$2');
118
+ }
119
+ /**
120
+ * Sync hash for JSON fallback (still uses legacy for simplicity)
121
+ * @deprecated Use hashPasswordAsync for new passwords
122
+ */
123
+ hashPassword(password) {
124
+ // For new registrations, we should use async version
125
+ // This is kept for backward compatibility with JSON storage
126
+ return this.hashPasswordLegacy(password);
127
+ }
91
128
  createEmptyUsage() {
92
129
  return {
93
130
  requestsToday: 0,
@@ -130,7 +167,7 @@ export class AuthService {
130
167
  return;
131
168
  const adminId = 'admin-' + randomUUID();
132
169
  await db.query(`INSERT INTO users (id, email, username, password_hash, tier, provider)
133
- VALUES ($1, $2, $3, $4, $5, $6)`, [adminId, 'admin@archicore.io', 'Administrator', this.hashPassword('admin123'), 'admin', 'email']);
170
+ VALUES ($1, $2, $3, $4, $5, $6)`, [adminId, 'admin@archicore.io', 'ArchiCore Team', this.hashPassword('admin123'), 'admin', 'email']);
134
171
  Logger.info('Default admin user created');
135
172
  }
136
173
  // ========== PUBLIC API ==========
@@ -149,9 +186,10 @@ export class AuthService {
149
186
  }
150
187
  const userId = 'user-' + randomUUID();
151
188
  const token = this.generateToken();
189
+ const passwordHash = await this.hashPasswordAsync(password);
152
190
  await db.transaction(async (client) => {
153
191
  await client.query(`INSERT INTO users (id, email, username, password_hash, tier, provider)
154
- VALUES ($1, $2, $3, $4, $5, $6)`, [userId, email.toLowerCase(), username, this.hashPassword(password), 'free', 'email']);
192
+ VALUES ($1, $2, $3, $4, $5, $6)`, [userId, email.toLowerCase(), username, passwordHash, 'free', 'email']);
155
193
  await client.query(`INSERT INTO sessions (token, user_id, expires_at)
156
194
  VALUES ($1, $2, $3)`, [token, userId, new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)]);
157
195
  });
@@ -203,10 +241,18 @@ export class AuthService {
203
241
  return { success: false, error: 'Invalid email or password' };
204
242
  }
205
243
  const row = result.rows[0];
206
- if (row.password_hash !== this.hashPassword(password)) {
244
+ // Verify password (supports both bcrypt and legacy SHA256)
245
+ const isValid = await this.verifyPassword(password, row.password_hash || '');
246
+ if (!isValid) {
207
247
  return { success: false, error: 'Invalid email or password' };
208
248
  }
209
249
  const token = this.generateToken();
250
+ // Rehash legacy passwords to bcrypt
251
+ if (row.password_hash && this.needsRehash(row.password_hash)) {
252
+ const newHash = await this.hashPasswordAsync(password);
253
+ await db.query('UPDATE users SET password_hash = $1 WHERE id = $2', [newHash, row.id]);
254
+ Logger.info(`Upgraded password hash for user ${row.id}`);
255
+ }
210
256
  await db.query('UPDATE users SET last_login_at = NOW() WHERE id = $1', [row.id]);
211
257
  await db.query('INSERT INTO sessions (token, user_id, expires_at) VALUES ($1, $2, $3)', [token, row.id, new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)]);
212
258
  const user = this.rowToUser(row);
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Encryption Service for ArchiCore
3
+ *
4
+ * AES-256-GCM encryption for sensitive data
5
+ */
6
+ declare class EncryptionService {
7
+ private key;
8
+ private initialized;
9
+ /**
10
+ * Initialize encryption with key from environment
11
+ */
12
+ init(): void;
13
+ /**
14
+ * Encrypt a string value
15
+ */
16
+ encrypt(plaintext: string): string;
17
+ /**
18
+ * Decrypt a string value
19
+ */
20
+ decrypt(ciphertext: string): string;
21
+ /**
22
+ * Hash a value (one-way, for comparison)
23
+ */
24
+ hash(value: string): string;
25
+ /**
26
+ * Check if a value is encrypted (starts with valid base64 of correct length)
27
+ */
28
+ isEncrypted(value: string): boolean;
29
+ /**
30
+ * Encrypt if not already encrypted
31
+ */
32
+ ensureEncrypted(value: string): string;
33
+ /**
34
+ * Mask sensitive data for logging (show only last 4 chars)
35
+ */
36
+ mask(value: string): string;
37
+ /**
38
+ * Generate a secure random token
39
+ */
40
+ generateToken(length?: number): string;
41
+ /**
42
+ * Generate a secure random secret
43
+ */
44
+ generateSecret(length?: number): string;
45
+ }
46
+ export declare const encryption: EncryptionService;
47
+ export {};
48
+ //# sourceMappingURL=encryption.d.ts.map
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Encryption Service for ArchiCore
3
+ *
4
+ * AES-256-GCM encryption for sensitive data
5
+ */
6
+ import crypto from 'crypto';
7
+ import { Logger } from '../../utils/logger.js';
8
+ const ALGORITHM = 'aes-256-gcm';
9
+ const IV_LENGTH = 16;
10
+ const AUTH_TAG_LENGTH = 16;
11
+ // const SALT_LENGTH = 32; // Reserved for future use
12
+ class EncryptionService {
13
+ key = null;
14
+ initialized = false;
15
+ /**
16
+ * Initialize encryption with key from environment
17
+ */
18
+ init() {
19
+ if (this.initialized)
20
+ return;
21
+ const encryptionKey = process.env.ENCRYPTION_KEY;
22
+ if (!encryptionKey) {
23
+ Logger.warn('ENCRYPTION_KEY not set - generating temporary key (NOT FOR PRODUCTION)');
24
+ // Generate a temporary key for development
25
+ this.key = crypto.randomBytes(32);
26
+ }
27
+ else {
28
+ // Derive key from password using PBKDF2
29
+ const salt = process.env.ENCRYPTION_SALT || 'archicore-default-salt';
30
+ this.key = crypto.pbkdf2Sync(encryptionKey, salt, 100000, 32, 'sha256');
31
+ }
32
+ this.initialized = true;
33
+ Logger.info('Encryption service initialized');
34
+ }
35
+ /**
36
+ * Encrypt a string value
37
+ */
38
+ encrypt(plaintext) {
39
+ if (!this.key) {
40
+ this.init();
41
+ }
42
+ try {
43
+ // Generate random IV
44
+ const iv = crypto.randomBytes(IV_LENGTH);
45
+ // Create cipher
46
+ const cipher = crypto.createCipheriv(ALGORITHM, this.key, iv);
47
+ // Encrypt
48
+ let encrypted = cipher.update(plaintext, 'utf8', 'hex');
49
+ encrypted += cipher.final('hex');
50
+ // Get auth tag
51
+ const authTag = cipher.getAuthTag();
52
+ // Combine: iv + authTag + encrypted
53
+ const combined = Buffer.concat([
54
+ iv,
55
+ authTag,
56
+ Buffer.from(encrypted, 'hex')
57
+ ]);
58
+ return combined.toString('base64');
59
+ }
60
+ catch (error) {
61
+ Logger.error('Encryption failed:', error);
62
+ throw new Error('Encryption failed');
63
+ }
64
+ }
65
+ /**
66
+ * Decrypt a string value
67
+ */
68
+ decrypt(ciphertext) {
69
+ if (!this.key) {
70
+ this.init();
71
+ }
72
+ try {
73
+ // Decode from base64
74
+ const combined = Buffer.from(ciphertext, 'base64');
75
+ // Extract components
76
+ const iv = combined.subarray(0, IV_LENGTH);
77
+ const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
78
+ const encrypted = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
79
+ // Create decipher
80
+ const decipher = crypto.createDecipheriv(ALGORITHM, this.key, iv);
81
+ decipher.setAuthTag(authTag);
82
+ // Decrypt
83
+ let decrypted = decipher.update(encrypted);
84
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
85
+ return decrypted.toString('utf8');
86
+ }
87
+ catch (error) {
88
+ Logger.error('Decryption failed:', error);
89
+ throw new Error('Decryption failed');
90
+ }
91
+ }
92
+ /**
93
+ * Hash a value (one-way, for comparison)
94
+ */
95
+ hash(value) {
96
+ const salt = process.env.ENCRYPTION_SALT || 'archicore-default-salt';
97
+ return crypto
98
+ .createHmac('sha256', salt)
99
+ .update(value)
100
+ .digest('hex');
101
+ }
102
+ /**
103
+ * Check if a value is encrypted (starts with valid base64 of correct length)
104
+ */
105
+ isEncrypted(value) {
106
+ try {
107
+ const decoded = Buffer.from(value, 'base64');
108
+ // Minimum length: IV (16) + AuthTag (16) + at least 1 byte encrypted
109
+ return decoded.length > IV_LENGTH + AUTH_TAG_LENGTH;
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ }
115
+ /**
116
+ * Encrypt if not already encrypted
117
+ */
118
+ ensureEncrypted(value) {
119
+ if (this.isEncrypted(value)) {
120
+ return value;
121
+ }
122
+ return this.encrypt(value);
123
+ }
124
+ /**
125
+ * Mask sensitive data for logging (show only last 4 chars)
126
+ */
127
+ mask(value) {
128
+ if (value.length <= 4) {
129
+ return '****';
130
+ }
131
+ return '*'.repeat(value.length - 4) + value.slice(-4);
132
+ }
133
+ /**
134
+ * Generate a secure random token
135
+ */
136
+ generateToken(length = 32) {
137
+ return crypto.randomBytes(length).toString('hex');
138
+ }
139
+ /**
140
+ * Generate a secure random secret
141
+ */
142
+ generateSecret(length = 64) {
143
+ return crypto.randomBytes(length).toString('base64url');
144
+ }
145
+ }
146
+ // Export singleton instance
147
+ export const encryption = new EncryptionService();
148
+ //# sourceMappingURL=encryption.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archicore",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "AI Software Architect - code analysis, impact prediction, semantic search",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -18,6 +18,8 @@
18
18
  },
19
19
  "scripts": {
20
20
  "build": "tsc",
21
+ "build:frontend": "node scripts/build-frontend.js",
22
+ "build:all": "npm run build && npm run build:frontend",
21
23
  "dev": "tsx watch src/index.ts",
22
24
  "start": "node dist/index.js",
23
25
  "server": "tsx src/server/index.ts",
@@ -93,7 +95,11 @@
93
95
  "@types/multer": "^2.0.0",
94
96
  "@types/node": "^22.10.2",
95
97
  "@types/pg": "^8.11.6",
98
+ "clean-css": "^5.3.3",
99
+ "html-minifier-terser": "^7.2.0",
100
+ "javascript-obfuscator": "^4.1.1",
96
101
  "tsx": "^4.19.2",
97
- "typescript": "^5.7.2"
102
+ "typescript": "^5.7.2",
103
+ "vite": "^5.4.11"
98
104
  }
99
105
  }