@opentrust/dashboard 7.3.21 → 7.3.22
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/dist/index.js +8 -6
- package/dist/middleware/session-auth.js +42 -14
- package/dist/routes/auth.js +91 -89
- package/dist/routes/settings.js +43 -3
- package/package.json +3 -3
- package/public/assets/index-05zO5oa1.js +398 -0
- package/public/assets/index-05zO5oa1.js.map +1 -0
- package/public/assets/{index-Djrsw7WU.css → index-GJYqt73Q.css} +1 -1
- package/public/index.html +2 -2
- package/public/assets/index-a6XIM1PE.js +0 -378
- package/public/assets/index-a6XIM1PE.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { join, dirname } from "node:path";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { sessionAuth } from "./middleware/session-auth.js";
|
|
9
9
|
import { authRouter } from "./routes/auth.js";
|
|
10
|
-
import { settingsRouter } from "./routes/settings.js";
|
|
10
|
+
import { settingsRouter, ensureSystemApiKey } from "./routes/settings.js";
|
|
11
11
|
import { agentsRouter } from "./routes/agents.js";
|
|
12
12
|
import { scannersRouter } from "./routes/scanners.js";
|
|
13
13
|
import { policiesRouter } from "./routes/policies.js";
|
|
@@ -82,13 +82,15 @@ app.use("/api/commands", commandsRouter);
|
|
|
82
82
|
app.use("/api/hosts", hostsRouter);
|
|
83
83
|
app.use("/api/system", systemRouter);
|
|
84
84
|
app.use(errorHandler);
|
|
85
|
-
app.listen(PORT, () => {
|
|
85
|
+
app.listen(PORT, async () => {
|
|
86
86
|
console.log(`OpenTrust API running on port ${PORT}`);
|
|
87
87
|
console.log(`DashboardMode: ${DASHBOARD_MODE}`);
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
console.log(`Auth: username/password login (default: admin/admin)`);
|
|
89
|
+
try {
|
|
90
|
+
const systemKey = await ensureSystemApiKey();
|
|
91
|
+
console.log(`System API Key: ${systemKey.slice(0, 12)}...${systemKey.slice(-4)}`);
|
|
90
92
|
}
|
|
91
|
-
|
|
92
|
-
console.
|
|
93
|
+
catch {
|
|
94
|
+
console.warn("Warning: failed to initialize system API key");
|
|
93
95
|
}
|
|
94
96
|
});
|
|
@@ -1,27 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
import { db, settingsQueries } from "@opentrust/db";
|
|
2
2
|
const CORE_URL = process.env.OG_CORE_URL || "http://localhost:53666";
|
|
3
3
|
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const DEV_API_KEY = "sk-og-dev";
|
|
4
|
+
const KEY_CACHE_TTL_MS = 60_000; // 1 minute
|
|
5
|
+
const DEFAULT_TENANT_ID = "default";
|
|
7
6
|
const sessionCache = new Map();
|
|
7
|
+
let cachedSystemKey = null;
|
|
8
|
+
let cachedSystemKeyAt = 0;
|
|
9
|
+
async function getSystemApiKey() {
|
|
10
|
+
if (cachedSystemKey && Date.now() - cachedSystemKeyAt < KEY_CACHE_TTL_MS) {
|
|
11
|
+
return cachedSystemKey;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const settings = settingsQueries(db);
|
|
15
|
+
const key = await settings.get("system_api_key");
|
|
16
|
+
cachedSystemKey = key || null;
|
|
17
|
+
cachedSystemKeyAt = Date.now();
|
|
18
|
+
return cachedSystemKey;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return cachedSystemKey;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
8
24
|
/**
|
|
9
25
|
* Session authentication middleware for OpenTrust.
|
|
10
26
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
27
|
+
* Supports two authentication modes:
|
|
28
|
+
* 1. sk-ot-* keys: validated directly against the system_api_key in settings DB
|
|
29
|
+
* 2. sk-og-* keys: validated against Core (backward compatible)
|
|
14
30
|
*/
|
|
15
31
|
export async function sessionAuth(req, res, next) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
res.
|
|
20
|
-
|
|
32
|
+
const apiKey = req.headers.authorization?.replace("Bearer ", "")
|
|
33
|
+
|| req.headers["x-api-key"];
|
|
34
|
+
if (!apiKey) {
|
|
35
|
+
res.status(401).json({ success: false, error: "Not authenticated" });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// sk-ot-* keys: validate against system_api_key stored in Dashboard settings
|
|
39
|
+
if (apiKey.startsWith("sk-ot-")) {
|
|
40
|
+
const systemKey = await getSystemApiKey();
|
|
41
|
+
if (systemKey && apiKey === systemKey) {
|
|
42
|
+
res.locals.tenantId = DEFAULT_TENANT_ID;
|
|
43
|
+
res.locals.userEmail = "admin@opentrust";
|
|
44
|
+
res.locals.coreApiKey = apiKey;
|
|
45
|
+
next();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
res.status(401).json({ success: false, error: "Invalid system API key" });
|
|
21
49
|
return;
|
|
22
50
|
}
|
|
23
|
-
|
|
24
|
-
if (!apiKey
|
|
51
|
+
// sk-og-* keys: validate against Core (backward compatible)
|
|
52
|
+
if (!apiKey.startsWith("sk-og-")) {
|
|
25
53
|
res.status(401).json({ success: false, error: "Not authenticated" });
|
|
26
54
|
return;
|
|
27
55
|
}
|
package/dist/routes/auth.js
CHANGED
|
@@ -1,125 +1,127 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { db, settingsQueries } from "@opentrust/db";
|
|
4
|
+
import { ensureSystemApiKey } from "./settings.js";
|
|
2
5
|
export const authRouter = Router();
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
email: DEV_EMAIL,
|
|
9
|
-
agentId: "dev-agent",
|
|
10
|
-
name: "Dev Agent",
|
|
11
|
-
quotaTotal: 999999,
|
|
12
|
-
quotaUsed: 0,
|
|
13
|
-
quotaRemaining: 999999,
|
|
14
|
-
agents: [],
|
|
15
|
-
};
|
|
16
|
-
async function fetchCoreAccount(apiKey) {
|
|
17
|
-
try {
|
|
18
|
-
const res = await fetch(`${CORE_URL}/api/v1/account`, {
|
|
19
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
20
|
-
});
|
|
21
|
-
if (!res.ok)
|
|
22
|
-
return null;
|
|
23
|
-
return res.json();
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
6
|
+
const DEFAULT_USERNAME = "admin";
|
|
7
|
+
const DEFAULT_PASSWORD = "admin";
|
|
8
|
+
const settings = settingsQueries(db);
|
|
9
|
+
function hashPassword(password) {
|
|
10
|
+
return createHash("sha256").update(password).digest("hex");
|
|
28
11
|
}
|
|
29
|
-
async function
|
|
12
|
+
async function ensureAdminCredentials() {
|
|
13
|
+
const existing = await settings.get("admin_password_hash");
|
|
14
|
+
if (existing)
|
|
15
|
+
return;
|
|
16
|
+
await settings.set("admin_username", DEFAULT_USERNAME);
|
|
17
|
+
await settings.set("admin_password_hash", hashPassword(DEFAULT_PASSWORD));
|
|
18
|
+
}
|
|
19
|
+
authRouter.get("/config", async (_req, res) => {
|
|
30
20
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
});
|
|
34
|
-
if (!res.ok)
|
|
35
|
-
return null;
|
|
36
|
-
return res.json();
|
|
21
|
+
const pwHash = await settings.get("admin_password_hash");
|
|
22
|
+
const isDefault = pwHash === hashPassword(DEFAULT_PASSWORD);
|
|
23
|
+
res.json({ defaultPassword: isDefault });
|
|
37
24
|
}
|
|
38
25
|
catch {
|
|
39
|
-
|
|
26
|
+
res.json({ defaultPassword: true });
|
|
40
27
|
}
|
|
41
|
-
}
|
|
42
|
-
authRouter.get("/config", (_req, res) => {
|
|
43
|
-
res.json({ devMode: IS_DEV_MODE });
|
|
44
28
|
});
|
|
45
29
|
authRouter.post("/login", async (req, res, next) => {
|
|
46
|
-
if (IS_DEV_MODE) {
|
|
47
|
-
res.json(DEV_RESPONSE);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
30
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
31
|
+
await ensureAdminCredentials();
|
|
32
|
+
const { username, password } = req.body;
|
|
33
|
+
if (!username || !password) {
|
|
34
|
+
res.status(400).json({ success: false, error: "Username and password required" });
|
|
54
35
|
return;
|
|
55
36
|
}
|
|
56
|
-
|
|
57
|
-
|
|
37
|
+
const storedUsername = await settings.get("admin_username") || DEFAULT_USERNAME;
|
|
38
|
+
const storedHash = await settings.get("admin_password_hash") || hashPassword(DEFAULT_PASSWORD);
|
|
39
|
+
if (username !== storedUsername || hashPassword(password) !== storedHash) {
|
|
40
|
+
res.status(401).json({ success: false, error: "Invalid username or password" });
|
|
58
41
|
return;
|
|
59
42
|
}
|
|
60
|
-
const
|
|
61
|
-
if (!account?.success) {
|
|
62
|
-
res.status(401).json({ success: false, error: "Invalid API key" });
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (!account.email) {
|
|
66
|
-
res.status(403).json({ success: false, error: "Agent not yet activated. Complete email verification first." });
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (account.email.toLowerCase() !== email.toLowerCase()) {
|
|
70
|
-
res.status(401).json({ success: false, error: "Email does not match this API key" });
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const accounts = await fetchCoreAccounts(apiKey);
|
|
74
|
-
const agents = accounts?.agents ?? [];
|
|
43
|
+
const apiKey = await ensureSystemApiKey();
|
|
75
44
|
res.json({
|
|
76
45
|
success: true,
|
|
77
|
-
email:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
quotaTotal: account.quotaTotal,
|
|
81
|
-
quotaUsed: account.quotaUsed,
|
|
82
|
-
quotaRemaining: account.quotaRemaining,
|
|
83
|
-
agents,
|
|
46
|
+
email: `${username}@opentrust`,
|
|
47
|
+
name: username,
|
|
48
|
+
apiKey,
|
|
84
49
|
});
|
|
85
50
|
}
|
|
86
51
|
catch (err) {
|
|
87
52
|
next(err);
|
|
88
53
|
}
|
|
89
54
|
});
|
|
90
|
-
authRouter.
|
|
91
|
-
if (IS_DEV_MODE) {
|
|
92
|
-
res.json(DEV_RESPONSE);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
55
|
+
authRouter.post("/change-password", async (req, res, next) => {
|
|
95
56
|
try {
|
|
96
|
-
const
|
|
97
|
-
|
|
57
|
+
const token = req.headers.authorization?.replace("Bearer ", "");
|
|
58
|
+
const systemKey = await ensureSystemApiKey();
|
|
59
|
+
if (!token || token !== systemKey) {
|
|
98
60
|
res.status(401).json({ success: false, error: "Not authenticated" });
|
|
99
61
|
return;
|
|
100
62
|
}
|
|
101
|
-
const
|
|
102
|
-
if (!
|
|
103
|
-
res.status(
|
|
63
|
+
const { currentPassword, newPassword } = req.body;
|
|
64
|
+
if (!currentPassword || !newPassword) {
|
|
65
|
+
res.status(400).json({ success: false, error: "Current and new password required" });
|
|
104
66
|
return;
|
|
105
67
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
68
|
+
if (newPassword.length < 4) {
|
|
69
|
+
res.status(400).json({ success: false, error: "Password must be at least 4 characters" });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const storedHash = await settings.get("admin_password_hash") || hashPassword(DEFAULT_PASSWORD);
|
|
73
|
+
if (hashPassword(currentPassword) !== storedHash) {
|
|
74
|
+
res.status(401).json({ success: false, error: "Current password is incorrect" });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
await settings.set("admin_password_hash", hashPassword(newPassword));
|
|
78
|
+
res.json({ success: true });
|
|
118
79
|
}
|
|
119
80
|
catch (err) {
|
|
120
81
|
next(err);
|
|
121
82
|
}
|
|
122
83
|
});
|
|
84
|
+
authRouter.get("/me", async (req, res) => {
|
|
85
|
+
const token = req.headers.authorization?.replace("Bearer ", "")
|
|
86
|
+
|| req.headers["x-api-key"];
|
|
87
|
+
if (!token) {
|
|
88
|
+
res.status(401).json({ success: false, error: "Not authenticated" });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Validate sk-ot-* system key
|
|
92
|
+
if (token.startsWith("sk-ot-")) {
|
|
93
|
+
const systemKey = await ensureSystemApiKey();
|
|
94
|
+
if (token === systemKey) {
|
|
95
|
+
const username = await settings.get("admin_username") || DEFAULT_USERNAME;
|
|
96
|
+
res.json({ success: true, email: `${username}@opentrust`, name: username });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
res.status(401).json({ success: false, error: "Invalid system API key" });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Backward compat: sk-og-* keys via Core
|
|
103
|
+
if (token.startsWith("sk-og-")) {
|
|
104
|
+
try {
|
|
105
|
+
const coreUrl = process.env.OG_CORE_URL || "http://localhost:53666";
|
|
106
|
+
const coreRes = await fetch(`${coreUrl}/api/v1/account`, {
|
|
107
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
108
|
+
});
|
|
109
|
+
if (!coreRes.ok) {
|
|
110
|
+
res.status(401).json({ success: false, error: "Invalid API key" });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const data = await coreRes.json();
|
|
114
|
+
if (data.success && data.email) {
|
|
115
|
+
res.json({ success: true, email: data.email, name: data.name || data.email });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
res.status(401).json({ success: false, error: "Invalid or inactive API key" });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
res.status(401).json({ success: false, error: "Not authenticated" });
|
|
124
|
+
});
|
|
123
125
|
authRouter.post("/logout", (_req, res) => {
|
|
124
126
|
res.json({ success: true });
|
|
125
127
|
});
|
package/dist/routes/settings.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
2
3
|
import { db, settingsQueries } from "@opentrust/db";
|
|
3
4
|
import { maskSecret } from "@opentrust/shared";
|
|
4
5
|
import { checkCoreHealth } from "../services/core-client.js";
|
|
5
6
|
const settings = settingsQueries(db);
|
|
6
7
|
export const settingsRouter = Router();
|
|
8
|
+
const SENSITIVE_KEYS = ["og_core_key", "session_token", "system_api_key"];
|
|
9
|
+
export function generateSystemApiKey() {
|
|
10
|
+
return `sk-ot-${randomBytes(16).toString("hex")}`;
|
|
11
|
+
}
|
|
12
|
+
export async function ensureSystemApiKey() {
|
|
13
|
+
const existing = await settings.get("system_api_key");
|
|
14
|
+
if (existing)
|
|
15
|
+
return existing;
|
|
16
|
+
const key = generateSystemApiKey();
|
|
17
|
+
await settings.set("system_api_key", key);
|
|
18
|
+
return key;
|
|
19
|
+
}
|
|
7
20
|
// GET /api/settings
|
|
8
21
|
settingsRouter.get("/", async (_req, res, next) => {
|
|
9
22
|
try {
|
|
10
23
|
const all = await settings.getAll();
|
|
11
|
-
// Mask sensitive values
|
|
12
24
|
const masked = {};
|
|
13
25
|
for (const [key, value] of Object.entries(all)) {
|
|
14
|
-
if (key
|
|
26
|
+
if (SENSITIVE_KEYS.includes(key)) {
|
|
15
27
|
masked[key] = maskSecret(value);
|
|
16
28
|
}
|
|
17
29
|
else {
|
|
@@ -32,8 +44,8 @@ settingsRouter.put("/", async (req, res, next) => {
|
|
|
32
44
|
res.status(400).json({ success: false, error: "Request body must be a key-value object" });
|
|
33
45
|
return;
|
|
34
46
|
}
|
|
35
|
-
// Prevent overwriting session_token via this endpoint
|
|
36
47
|
delete updates.session_token;
|
|
48
|
+
delete updates.system_api_key;
|
|
37
49
|
for (const [key, value] of Object.entries(updates)) {
|
|
38
50
|
await settings.set(key, value);
|
|
39
51
|
}
|
|
@@ -43,6 +55,34 @@ settingsRouter.put("/", async (req, res, next) => {
|
|
|
43
55
|
next(err);
|
|
44
56
|
}
|
|
45
57
|
});
|
|
58
|
+
// GET /api/settings/api-key — reveal or masked system API key
|
|
59
|
+
settingsRouter.get("/api-key", async (req, res, next) => {
|
|
60
|
+
try {
|
|
61
|
+
const key = await ensureSystemApiKey();
|
|
62
|
+
const reveal = req.query.reveal === "true";
|
|
63
|
+
res.json({
|
|
64
|
+
success: true,
|
|
65
|
+
data: {
|
|
66
|
+
key: reveal ? key : maskSecret(key),
|
|
67
|
+
masked: !reveal,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
next(err);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// POST /api/settings/generate-key — regenerate system API key
|
|
76
|
+
settingsRouter.post("/generate-key", async (_req, res, next) => {
|
|
77
|
+
try {
|
|
78
|
+
const key = generateSystemApiKey();
|
|
79
|
+
await settings.set("system_api_key", key);
|
|
80
|
+
res.json({ success: true, data: { key } });
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
next(err);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
46
86
|
// POST /api/settings/test-connection
|
|
47
87
|
settingsRouter.post("/test-connection", async (_req, res, next) => {
|
|
48
88
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentrust/dashboard",
|
|
3
|
-
"version": "7.3.
|
|
3
|
+
"version": "7.3.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenTrust Dashboard — management panel for AI Agent security (API + embedded web)",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"morgan": "^1.10.0",
|
|
20
20
|
"nodemailer": "^8.0.1",
|
|
21
21
|
"zod": "^3.23.0",
|
|
22
|
-
"@opentrust/shared": "7.3.
|
|
23
|
-
"@opentrust/db": "7.3.
|
|
22
|
+
"@opentrust/shared": "7.3.22",
|
|
23
|
+
"@opentrust/db": "7.3.22"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/cors": "^2.8.17",
|