apteva 0.4.57 → 0.7.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/README.md +216 -54
- package/cli.js +35 -0
- package/install.js +92 -0
- package/package.json +12 -79
- package/LICENSE +0 -63
- package/bin/apteva.js +0 -196
- package/dist/ActivityPage.kxzzb4yc.js +0 -3
- package/dist/ApiDocsPage.zq998hbm.js +0 -4
- package/dist/App.55rea8mn.js +0 -61
- package/dist/App.5ywb23z4.js +0 -53
- package/dist/App.6thds120.js +0 -4
- package/dist/App.9tctxzqm.js +0 -8
- package/dist/App.a8r8ttaz.js +0 -4
- package/dist/App.agsv5bje.js +0 -4
- package/dist/App.cepapqmx.js +0 -4
- package/dist/App.dp041gb3.js +0 -221
- package/dist/App.fds72zb5.js +0 -4
- package/dist/App.fg9qj2dq.js +0 -4
- package/dist/App.ndfejbm9.js +0 -4
- package/dist/App.nxmfmq1h.js +0 -13
- package/dist/App.qdfyt8ba.js +0 -4
- package/dist/App.x2d0ygt6.js +0 -4
- package/dist/App.yt9p4nr3.js +0 -20
- package/dist/App.zn4mw16t.js +0 -1
- package/dist/ConnectionsPage.8r96ryw7.js +0 -3
- package/dist/McpPage.3cwh0gnd.js +0 -3
- package/dist/SettingsPage.ykgdh5ev.js +0 -3
- package/dist/SkillsPage.4np1s65b.js +0 -3
- package/dist/TasksPage.4g08t7p6.js +0 -3
- package/dist/TelemetryPage.72w9pwcp.js +0 -3
- package/dist/TestsPage.z4fk3r7r.js +0 -3
- package/dist/ThreadsPage.63tcajeh.js +0 -3
- package/dist/apteva-kit.css +0 -1
- package/dist/icon.png +0 -0
- package/dist/index.html +0 -16
- package/dist/styles.css +0 -1
- package/scripts/postinstall.mjs +0 -102
- package/src/auth/index.ts +0 -394
- package/src/auth/middleware.ts +0 -213
- package/src/binary.ts +0 -536
- package/src/channels/index.ts +0 -40
- package/src/channels/telegram.ts +0 -311
- package/src/crypto.ts +0 -301
- package/src/db-tests.ts +0 -174
- package/src/db.ts +0 -3133
- package/src/integrations/agentdojo.ts +0 -559
- package/src/integrations/composio.ts +0 -437
- package/src/integrations/index.ts +0 -87
- package/src/integrations/skillsmp.ts +0 -318
- package/src/mcp-client.ts +0 -605
- package/src/mcp-handler.ts +0 -394
- package/src/mcp-platform.ts +0 -2403
- package/src/openapi.ts +0 -2410
- package/src/providers.ts +0 -597
- package/src/routes/api/agent-utils.ts +0 -890
- package/src/routes/api/agents.ts +0 -916
- package/src/routes/api/api-keys.ts +0 -95
- package/src/routes/api/channels.ts +0 -182
- package/src/routes/api/helpers.ts +0 -12
- package/src/routes/api/integrations.ts +0 -639
- package/src/routes/api/mcp.ts +0 -574
- package/src/routes/api/meta-agent.ts +0 -195
- package/src/routes/api/projects.ts +0 -112
- package/src/routes/api/providers.ts +0 -424
- package/src/routes/api/skills.ts +0 -537
- package/src/routes/api/system.ts +0 -333
- package/src/routes/api/telemetry.ts +0 -203
- package/src/routes/api/tests.ts +0 -148
- package/src/routes/api/triggers.ts +0 -518
- package/src/routes/api/users.ts +0 -148
- package/src/routes/api/webhooks.ts +0 -171
- package/src/routes/api.ts +0 -53
- package/src/routes/auth.ts +0 -251
- package/src/routes/share.ts +0 -86
- package/src/routes/static.ts +0 -131
- package/src/server.ts +0 -642
- package/src/test-runner.ts +0 -598
- package/src/triggers/agentdojo.ts +0 -253
- package/src/triggers/composio.ts +0 -264
- package/src/triggers/index.ts +0 -71
- package/src/tui/AgentList.tsx +0 -145
- package/src/tui/App.tsx +0 -102
- package/src/tui/Login.tsx +0 -104
- package/src/tui/api.ts +0 -72
- package/src/tui/index.tsx +0 -7
- package/src/web/App.tsx +0 -455
- package/src/web/components/activity/ActivityPage.tsx +0 -314
- package/src/web/components/activity/index.ts +0 -1
- package/src/web/components/agents/AgentCard.tsx +0 -189
- package/src/web/components/agents/AgentPanel.tsx +0 -2244
- package/src/web/components/agents/AgentsView.tsx +0 -180
- package/src/web/components/agents/CreateAgentModal.tsx +0 -475
- package/src/web/components/agents/index.ts +0 -4
- package/src/web/components/api/ApiDocsPage.tsx +0 -842
- package/src/web/components/auth/CreateAccountStep.tsx +0 -176
- package/src/web/components/auth/LoginPage.tsx +0 -91
- package/src/web/components/auth/index.ts +0 -2
- package/src/web/components/common/Icons.tsx +0 -250
- package/src/web/components/common/LoadingSpinner.tsx +0 -44
- package/src/web/components/common/Modal.tsx +0 -199
- package/src/web/components/common/Select.tsx +0 -97
- package/src/web/components/common/index.ts +0 -20
- package/src/web/components/connections/ConnectionsPage.tsx +0 -54
- package/src/web/components/connections/IntegrationsTab.tsx +0 -170
- package/src/web/components/connections/OverviewTab.tsx +0 -137
- package/src/web/components/connections/TriggersTab.tsx +0 -1346
- package/src/web/components/dashboard/Dashboard.tsx +0 -572
- package/src/web/components/dashboard/index.ts +0 -1
- package/src/web/components/index.ts +0 -21
- package/src/web/components/layout/ErrorBanner.tsx +0 -18
- package/src/web/components/layout/Header.tsx +0 -332
- package/src/web/components/layout/Sidebar.tsx +0 -231
- package/src/web/components/layout/index.ts +0 -3
- package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
- package/src/web/components/mcp/McpPage.tsx +0 -2515
- package/src/web/components/mcp/index.ts +0 -1
- package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
- package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
- package/src/web/components/onboarding/index.ts +0 -1
- package/src/web/components/settings/SettingsPage.tsx +0 -2776
- package/src/web/components/settings/index.ts +0 -1
- package/src/web/components/skills/SkillsPage.tsx +0 -1200
- package/src/web/components/tasks/TasksPage.tsx +0 -1116
- package/src/web/components/tasks/index.ts +0 -1
- package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
- package/src/web/components/tests/TestsPage.tsx +0 -594
- package/src/web/components/threads/ThreadsPage.tsx +0 -315
- package/src/web/context/AuthContext.tsx +0 -242
- package/src/web/context/ProjectContext.tsx +0 -214
- package/src/web/context/TelemetryContext.tsx +0 -299
- package/src/web/context/ThemeContext.tsx +0 -90
- package/src/web/context/UIModeContext.tsx +0 -49
- package/src/web/context/index.ts +0 -12
- package/src/web/hooks/index.ts +0 -3
- package/src/web/hooks/useAgents.ts +0 -115
- package/src/web/hooks/useOnboarding.ts +0 -20
- package/src/web/hooks/useProviders.ts +0 -75
- package/src/web/icon.png +0 -0
- package/src/web/index.html +0 -16
- package/src/web/styles.css +0 -118
- package/src/web/themes.ts +0 -162
- package/src/web/types.ts +0 -298
package/src/auth/index.ts
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
import { createHmac, randomBytes } from "crypto";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
5
|
-
import { UserDB, SessionDB, generateId, type User } from "../db";
|
|
6
|
-
|
|
7
|
-
// ============ Configuration ============
|
|
8
|
-
|
|
9
|
-
const ACCESS_TOKEN_EXPIRY = 15 * 60; // 15 minutes in seconds
|
|
10
|
-
const REFRESH_TOKEN_EXPIRY = 7 * 24 * 60 * 60; // 7 days in seconds
|
|
11
|
-
|
|
12
|
-
// ============ Secret Key Management ============
|
|
13
|
-
|
|
14
|
-
let authSecret: string | null = null;
|
|
15
|
-
|
|
16
|
-
function getAuthSecretPath(): string {
|
|
17
|
-
const homeDir = process.env.DATA_DIR || join(homedir(), ".apteva");
|
|
18
|
-
return join(homeDir, "auth.key");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getAuthSecret(): string {
|
|
22
|
-
if (authSecret) return authSecret;
|
|
23
|
-
|
|
24
|
-
if (process.env.AUTH_SECRET) {
|
|
25
|
-
authSecret = process.env.AUTH_SECRET;
|
|
26
|
-
return authSecret;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const secretPath = getAuthSecretPath();
|
|
30
|
-
if (existsSync(secretPath)) {
|
|
31
|
-
authSecret = readFileSync(secretPath, "utf-8").trim();
|
|
32
|
-
return authSecret;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
authSecret = randomBytes(64).toString("hex");
|
|
36
|
-
|
|
37
|
-
const dir = process.env.DATA_DIR || join(homedir(), ".apteva");
|
|
38
|
-
if (!existsSync(dir)) {
|
|
39
|
-
mkdirSync(dir, { recursive: true });
|
|
40
|
-
}
|
|
41
|
-
writeFileSync(secretPath, authSecret, { mode: 0o600 });
|
|
42
|
-
|
|
43
|
-
return authSecret;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ============ Password Hashing ============
|
|
47
|
-
|
|
48
|
-
export async function hashPassword(password: string): Promise<string> {
|
|
49
|
-
return await Bun.password.hash(password, {
|
|
50
|
-
algorithm: "bcrypt",
|
|
51
|
-
cost: 12,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
56
|
-
try {
|
|
57
|
-
return await Bun.password.verify(password, hash);
|
|
58
|
-
} catch {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ============ Password Validation ============
|
|
64
|
-
|
|
65
|
-
export interface PasswordValidation {
|
|
66
|
-
valid: boolean;
|
|
67
|
-
errors: string[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function validatePassword(password: string): PasswordValidation {
|
|
71
|
-
const errors: string[] = [];
|
|
72
|
-
|
|
73
|
-
if (password.length < 8) {
|
|
74
|
-
errors.push("Password must be at least 8 characters");
|
|
75
|
-
}
|
|
76
|
-
if (!/[a-z]/.test(password)) {
|
|
77
|
-
errors.push("Password must contain a lowercase letter");
|
|
78
|
-
}
|
|
79
|
-
if (!/[A-Z]/.test(password)) {
|
|
80
|
-
errors.push("Password must contain an uppercase letter");
|
|
81
|
-
}
|
|
82
|
-
if (!/[0-9]/.test(password)) {
|
|
83
|
-
errors.push("Password must contain a number");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return { valid: errors.length === 0, errors };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ============ JWT Token Handling ============
|
|
90
|
-
|
|
91
|
-
interface TokenPayload {
|
|
92
|
-
userId: string;
|
|
93
|
-
username: string;
|
|
94
|
-
role: string;
|
|
95
|
-
type: "access" | "refresh";
|
|
96
|
-
iat: number;
|
|
97
|
-
exp: number;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function base64UrlEncode(data: string): string {
|
|
101
|
-
return Buffer.from(data)
|
|
102
|
-
.toString("base64")
|
|
103
|
-
.replace(/\+/g, "-")
|
|
104
|
-
.replace(/\//g, "_")
|
|
105
|
-
.replace(/=/g, "");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function base64UrlDecode(data: string): string {
|
|
109
|
-
const padded = data + "=".repeat((4 - (data.length % 4)) % 4);
|
|
110
|
-
return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function createToken(payload: Omit<TokenPayload, "iat" | "exp">, expiresIn: number): string {
|
|
114
|
-
const secret = getAuthSecret();
|
|
115
|
-
const now = Math.floor(Date.now() / 1000);
|
|
116
|
-
|
|
117
|
-
const fullPayload: TokenPayload = {
|
|
118
|
-
...payload,
|
|
119
|
-
iat: now,
|
|
120
|
-
exp: now + expiresIn,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const header = { alg: "HS256", typ: "JWT" };
|
|
124
|
-
const headerEncoded = base64UrlEncode(JSON.stringify(header));
|
|
125
|
-
const payloadEncoded = base64UrlEncode(JSON.stringify(fullPayload));
|
|
126
|
-
|
|
127
|
-
const signature = createHmac("sha256", secret)
|
|
128
|
-
.update(`${headerEncoded}.${payloadEncoded}`)
|
|
129
|
-
.digest("base64")
|
|
130
|
-
.replace(/\+/g, "-")
|
|
131
|
-
.replace(/\//g, "_")
|
|
132
|
-
.replace(/=/g, "");
|
|
133
|
-
|
|
134
|
-
return `${headerEncoded}.${payloadEncoded}.${signature}`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function verifyToken(token: string): TokenPayload | null {
|
|
138
|
-
try {
|
|
139
|
-
const secret = getAuthSecret();
|
|
140
|
-
const [headerEncoded, payloadEncoded, signature] = token.split(".");
|
|
141
|
-
|
|
142
|
-
if (!headerEncoded || !payloadEncoded || !signature) {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const expectedSignature = createHmac("sha256", secret)
|
|
147
|
-
.update(`${headerEncoded}.${payloadEncoded}`)
|
|
148
|
-
.digest("base64")
|
|
149
|
-
.replace(/\+/g, "-")
|
|
150
|
-
.replace(/\//g, "_")
|
|
151
|
-
.replace(/=/g, "");
|
|
152
|
-
|
|
153
|
-
if (signature !== expectedSignature) {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const payload = JSON.parse(base64UrlDecode(payloadEncoded)) as TokenPayload;
|
|
158
|
-
const now = Math.floor(Date.now() / 1000);
|
|
159
|
-
|
|
160
|
-
if (payload.exp < now) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return payload;
|
|
165
|
-
} catch {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ============ Token Generation ============
|
|
171
|
-
|
|
172
|
-
export function generateAccessToken(user: User): string {
|
|
173
|
-
return createToken(
|
|
174
|
-
{ userId: user.id, username: user.username, role: user.role, type: "access" },
|
|
175
|
-
ACCESS_TOKEN_EXPIRY
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function generateRefreshToken(user: User): string {
|
|
180
|
-
return createToken(
|
|
181
|
-
{ userId: user.id, username: user.username, role: user.role, type: "refresh" },
|
|
182
|
-
REFRESH_TOKEN_EXPIRY
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function verifyAccessToken(token: string): TokenPayload | null {
|
|
187
|
-
const payload = verifyToken(token);
|
|
188
|
-
if (!payload || payload.type !== "access") {
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
return payload;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export function verifyRefreshToken(token: string): TokenPayload | null {
|
|
195
|
-
const payload = verifyToken(token);
|
|
196
|
-
if (!payload || payload.type !== "refresh") {
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
return payload;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ============ Session Management ============
|
|
203
|
-
|
|
204
|
-
export function hashToken(token: string): string {
|
|
205
|
-
return createHmac("sha256", getAuthSecret()).update(token).digest("hex");
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export interface AuthTokens {
|
|
209
|
-
accessToken: string;
|
|
210
|
-
refreshToken: string;
|
|
211
|
-
expiresIn: number;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export async function createSession(user: User): Promise<AuthTokens> {
|
|
215
|
-
const accessToken = generateAccessToken(user);
|
|
216
|
-
const refreshToken = generateRefreshToken(user);
|
|
217
|
-
const refreshTokenHash = hashToken(refreshToken);
|
|
218
|
-
|
|
219
|
-
const expiresAt = new Date();
|
|
220
|
-
expiresAt.setSeconds(expiresAt.getSeconds() + REFRESH_TOKEN_EXPIRY);
|
|
221
|
-
|
|
222
|
-
SessionDB.create({
|
|
223
|
-
user_id: user.id,
|
|
224
|
-
refresh_token_hash: refreshTokenHash,
|
|
225
|
-
expires_at: expiresAt.toISOString(),
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
UserDB.updateLastLogin(user.id);
|
|
229
|
-
|
|
230
|
-
return { accessToken, refreshToken, expiresIn: ACCESS_TOKEN_EXPIRY };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export async function refreshSession(refreshToken: string): Promise<AuthTokens | null> {
|
|
234
|
-
const payload = verifyRefreshToken(refreshToken);
|
|
235
|
-
if (!payload) {
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const tokenHash = hashToken(refreshToken);
|
|
240
|
-
const session = SessionDB.findByTokenHash(tokenHash);
|
|
241
|
-
if (!session) {
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (new Date(session.expires_at) < new Date()) {
|
|
246
|
-
SessionDB.delete(session.id);
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const user = UserDB.findById(payload.userId);
|
|
251
|
-
if (!user) {
|
|
252
|
-
SessionDB.delete(session.id);
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
SessionDB.delete(session.id);
|
|
257
|
-
return createSession(user);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export function invalidateSession(refreshToken: string): boolean {
|
|
261
|
-
const tokenHash = hashToken(refreshToken);
|
|
262
|
-
return SessionDB.deleteByTokenHash(tokenHash);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export function invalidateAllSessions(userId: string): number {
|
|
266
|
-
return SessionDB.deleteByUser(userId);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// ============ Auth Check ============
|
|
270
|
-
|
|
271
|
-
export interface AuthStatus {
|
|
272
|
-
hasUsers: boolean;
|
|
273
|
-
authenticated: boolean;
|
|
274
|
-
isDev: boolean;
|
|
275
|
-
user?: { id: string; username: string; role: string };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Determine if we're in dev mode - used by frontend for dev-only features
|
|
279
|
-
function checkIsDev(): boolean {
|
|
280
|
-
return process.env.NODE_ENV !== "production";
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export function getAuthStatus(accessToken?: string): AuthStatus {
|
|
284
|
-
const hasUsers = UserDB.hasUsers();
|
|
285
|
-
const isDev = checkIsDev();
|
|
286
|
-
|
|
287
|
-
if (!accessToken) {
|
|
288
|
-
return { hasUsers, authenticated: false, isDev };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const payload = verifyAccessToken(accessToken);
|
|
292
|
-
if (!payload) {
|
|
293
|
-
return { hasUsers, authenticated: false, isDev };
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const user = UserDB.findById(payload.userId);
|
|
297
|
-
if (!user) {
|
|
298
|
-
return { hasUsers, authenticated: false, isDev };
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return {
|
|
302
|
-
hasUsers,
|
|
303
|
-
authenticated: true,
|
|
304
|
-
isDev,
|
|
305
|
-
user: { id: user.id, username: user.username, role: user.role },
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// ============ User Creation ============
|
|
310
|
-
|
|
311
|
-
export interface CreateUserResult {
|
|
312
|
-
success: boolean;
|
|
313
|
-
user?: User;
|
|
314
|
-
error?: string;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export async function createUser(data: {
|
|
318
|
-
username: string;
|
|
319
|
-
password: string;
|
|
320
|
-
email?: string;
|
|
321
|
-
role?: "admin" | "user";
|
|
322
|
-
}): Promise<CreateUserResult> {
|
|
323
|
-
const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/;
|
|
324
|
-
if (!usernameRegex.test(data.username)) {
|
|
325
|
-
return { success: false, error: "Username must be 3-20 characters (letters, numbers, underscore)" };
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const existing = UserDB.findByUsername(data.username);
|
|
329
|
-
if (existing) {
|
|
330
|
-
return { success: false, error: "Username already taken" };
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (data.email) {
|
|
334
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
335
|
-
if (!emailRegex.test(data.email)) {
|
|
336
|
-
return { success: false, error: "Invalid email format" };
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const passwordValidation = validatePassword(data.password);
|
|
341
|
-
if (!passwordValidation.valid) {
|
|
342
|
-
return { success: false, error: passwordValidation.errors.join(". ") };
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const passwordHash = await hashPassword(data.password);
|
|
346
|
-
|
|
347
|
-
const user = UserDB.create({
|
|
348
|
-
username: data.username,
|
|
349
|
-
password_hash: passwordHash,
|
|
350
|
-
email: data.email || null,
|
|
351
|
-
role: data.role || "user",
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
return { success: true, user };
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// ============ Login ============
|
|
358
|
-
|
|
359
|
-
export interface LoginResult {
|
|
360
|
-
success: boolean;
|
|
361
|
-
tokens?: AuthTokens;
|
|
362
|
-
user?: { id: string; username: string; role: string };
|
|
363
|
-
error?: string;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export async function login(username: string, password: string): Promise<LoginResult> {
|
|
367
|
-
const user = UserDB.findByUsername(username);
|
|
368
|
-
if (!user) {
|
|
369
|
-
return { success: false, error: "Invalid username or password" };
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const valid = await verifyPassword(password, user.password_hash);
|
|
373
|
-
if (!valid) {
|
|
374
|
-
return { success: false, error: "Invalid username or password" };
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const tokens = await createSession(user);
|
|
378
|
-
|
|
379
|
-
return {
|
|
380
|
-
success: true,
|
|
381
|
-
tokens,
|
|
382
|
-
user: { id: user.id, username: user.username, role: user.role },
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// ============ Cleanup ============
|
|
387
|
-
|
|
388
|
-
export function cleanupExpiredSessions(): number {
|
|
389
|
-
return SessionDB.deleteExpired();
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// ============ Exports ============
|
|
393
|
-
|
|
394
|
-
export { ACCESS_TOKEN_EXPIRY, REFRESH_TOKEN_EXPIRY };
|
package/src/auth/middleware.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { verifyAccessToken, getAuthStatus } from "./index";
|
|
2
|
-
import { UserDB, ApiKeyDB, type User } from "../db";
|
|
3
|
-
|
|
4
|
-
// Extend Request type to include user
|
|
5
|
-
declare global {
|
|
6
|
-
interface Request {
|
|
7
|
-
user?: User;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Helper to create JSON response
|
|
12
|
-
function json(data: unknown, status = 200): Response {
|
|
13
|
-
return new Response(JSON.stringify(data), {
|
|
14
|
-
status,
|
|
15
|
-
headers: { "Content-Type": "application/json" },
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Public paths that don't require authentication
|
|
20
|
-
const PUBLIC_PATHS = [
|
|
21
|
-
"/api/auth/check",
|
|
22
|
-
"/api/auth/login",
|
|
23
|
-
"/api/auth/refresh",
|
|
24
|
-
"/api/health",
|
|
25
|
-
"/api/features", // Feature flags needed before auth
|
|
26
|
-
"/api/telemetry", // Agents POST telemetry here
|
|
27
|
-
"/api/telemetry/stream", // SSE doesn't support auth headers
|
|
28
|
-
"/api/mcp/platform", // Built-in MCP server for agent communication
|
|
29
|
-
"/api/webhooks/composio", // Composio trigger webhooks (HMAC-verified)
|
|
30
|
-
"/api/webhooks/agentdojo", // AgentDojo trigger webhooks (HMAC-verified)
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
// Regex patterns for public paths (for dynamic segments)
|
|
34
|
-
const PUBLIC_PATTERNS = [
|
|
35
|
-
/^\/api\/mcp\/servers\/[^/]+\/mcp$/, // Local MCP server JSON-RPC endpoints
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// Path prefixes that don't require authentication (for agent communication)
|
|
39
|
-
const PUBLIC_PREFIXES = [
|
|
40
|
-
"/api/agents/", // Agent chat API needs to work without frontend auth
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
// Paths that only work when no users exist
|
|
44
|
-
const SETUP_PATHS = [
|
|
45
|
-
"/api/onboarding/user",
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
// Admin-only paths
|
|
49
|
-
const ADMIN_PATHS = [
|
|
50
|
-
"/api/users",
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
export interface AuthContext {
|
|
54
|
-
user: User | null;
|
|
55
|
-
isAuthenticated: boolean;
|
|
56
|
-
isAdmin: boolean;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Auth middleware for protecting API routes
|
|
61
|
-
* Returns null if request should proceed, or a Response to return immediately
|
|
62
|
-
*/
|
|
63
|
-
export async function authMiddleware(req: Request, path: string): Promise<{ response?: Response; context: AuthContext }> {
|
|
64
|
-
const context: AuthContext = {
|
|
65
|
-
user: null,
|
|
66
|
-
isAuthenticated: false,
|
|
67
|
-
isAdmin: false,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Check if auth is disabled
|
|
71
|
-
if (process.env.AUTH_ENABLED === "false") {
|
|
72
|
-
return { context };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Public paths - no auth needed
|
|
76
|
-
if (PUBLIC_PATHS.some(p => path === p || path.startsWith(p + "/"))) {
|
|
77
|
-
return { context };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Public patterns - no auth needed (dynamic path segments)
|
|
81
|
-
if (PUBLIC_PATTERNS.some(p => p.test(path))) {
|
|
82
|
-
return { context };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Public prefixes - no auth needed (for agent communication)
|
|
86
|
-
if (PUBLIC_PREFIXES.some(p => path.startsWith(p))) {
|
|
87
|
-
return { context };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Setup paths - only allowed when no users exist
|
|
91
|
-
if (SETUP_PATHS.some(p => path === p || path.startsWith(p + "/"))) {
|
|
92
|
-
const hasUsers = UserDB.hasUsers();
|
|
93
|
-
if (!hasUsers) {
|
|
94
|
-
return { context }; // Allow - no users yet
|
|
95
|
-
}
|
|
96
|
-
// Users exist - require auth for these paths
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Check for API key authentication (X-API-Key header or Bearer apt_... token)
|
|
100
|
-
const apiKeyHeader = req.headers.get("X-API-Key");
|
|
101
|
-
const authHeader = req.headers.get("Authorization");
|
|
102
|
-
const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
103
|
-
|
|
104
|
-
const apiKeyValue = apiKeyHeader || (bearerToken?.startsWith("apt_") ? bearerToken : null);
|
|
105
|
-
|
|
106
|
-
if (apiKeyValue) {
|
|
107
|
-
// API key auth
|
|
108
|
-
const result = ApiKeyDB.validate(apiKeyValue);
|
|
109
|
-
if (!result) {
|
|
110
|
-
return {
|
|
111
|
-
response: json({ error: "Invalid or expired API key", code: "INVALID_API_KEY" }, 401),
|
|
112
|
-
context,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
context.user = result.user;
|
|
117
|
-
context.isAuthenticated = true;
|
|
118
|
-
context.isAdmin = result.user.role === "admin";
|
|
119
|
-
} else if (bearerToken) {
|
|
120
|
-
// JWT auth
|
|
121
|
-
const payload = verifyAccessToken(bearerToken);
|
|
122
|
-
|
|
123
|
-
if (!payload) {
|
|
124
|
-
return {
|
|
125
|
-
response: json({ error: "Invalid or expired token", code: "INVALID_TOKEN" }, 401),
|
|
126
|
-
context,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const user = UserDB.findById(payload.userId);
|
|
131
|
-
if (!user) {
|
|
132
|
-
return {
|
|
133
|
-
response: json({ error: "User not found", code: "USER_NOT_FOUND" }, 401),
|
|
134
|
-
context,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
context.user = user;
|
|
139
|
-
context.isAuthenticated = true;
|
|
140
|
-
context.isAdmin = user.role === "admin";
|
|
141
|
-
} else {
|
|
142
|
-
return {
|
|
143
|
-
response: json({ error: "Unauthorized", code: "NO_TOKEN" }, 401),
|
|
144
|
-
context,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Check admin-only paths
|
|
149
|
-
if (ADMIN_PATHS.some(p => path === p || path.startsWith(p + "/"))) {
|
|
150
|
-
if (!context.isAdmin) {
|
|
151
|
-
return {
|
|
152
|
-
response: json({ error: "Admin access required", code: "FORBIDDEN" }, 403),
|
|
153
|
-
context,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return { context };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Helper to check if a path requires authentication
|
|
163
|
-
*/
|
|
164
|
-
export function requiresAuth(path: string): boolean {
|
|
165
|
-
if (process.env.AUTH_ENABLED === "false") {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (PUBLIC_PATHS.some(p => path === p || path.startsWith(p + "/"))) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Helper to extract token from request
|
|
178
|
-
*/
|
|
179
|
-
export function getTokenFromRequest(req: Request): string | null {
|
|
180
|
-
const authHeader = req.headers.get("Authorization");
|
|
181
|
-
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
return authHeader.slice(7);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Helper to extract refresh token from cookie
|
|
189
|
-
*/
|
|
190
|
-
export function getRefreshTokenFromCookie(req: Request): string | null {
|
|
191
|
-
const cookie = req.headers.get("Cookie");
|
|
192
|
-
if (!cookie) return null;
|
|
193
|
-
|
|
194
|
-
const match = cookie.match(/(?:^|;\s*)apteva_session=([^;]+)/);
|
|
195
|
-
return match ? match[1] : null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Create Set-Cookie header for refresh token
|
|
200
|
-
* Note: Secure flag only added when HTTPS is explicitly enabled (not just production mode)
|
|
201
|
-
* This allows Docker deployments to work on localhost without HTTPS
|
|
202
|
-
*/
|
|
203
|
-
export function createRefreshTokenCookie(token: string, maxAge: number): string {
|
|
204
|
-
const secure = process.env.HTTPS_ENABLED === "true" ? "; Secure" : "";
|
|
205
|
-
return `apteva_session=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${maxAge}${secure}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Create Set-Cookie header to clear refresh token
|
|
210
|
-
*/
|
|
211
|
-
export function clearRefreshTokenCookie(): string {
|
|
212
|
-
return "apteva_session=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0";
|
|
213
|
-
}
|