fix: update the permission for create and read the api keys

This commit is contained in:
2026-07-11 15:28:52 +07:00
parent 8cd10aefdc
commit 4ac342c5a2
15 changed files with 187 additions and 72 deletions
+6 -4
View File
@@ -35,7 +35,8 @@ export {
// API keys
export {
getApiKeys, getApiKeyById, createApiKey, updateApiKey, deleteApiKey, validateApiKey,
getApiKeys, getApiKeysByOwnerId, getApiKeyById, getApiKeyByIdAndOwnerId,
createApiKey, updateApiKey, deleteApiKey, validateApiKey,
} from "./repos/apiKeysRepo.js";
// Combos
@@ -84,7 +85,7 @@ export async function exportDb() {
providerConnections: db.all(`SELECT * FROM providerConnections`).map((r) => ({ ...parseJson(r.data, {}), id: r.id, provider: r.provider, authType: r.authType, name: r.name, email: r.email, priority: r.priority, isActive: r.isActive === 1, createdAt: r.createdAt, updatedAt: r.updatedAt })),
providerNodes: db.all(`SELECT * FROM providerNodes`).map((r) => ({ ...parseJson(r.data, {}), id: r.id, type: r.type, name: r.name, createdAt: r.createdAt, updatedAt: r.updatedAt })),
proxyPools: db.all(`SELECT * FROM proxyPools`).map((r) => ({ ...parseJson(r.data, {}), id: r.id, isActive: r.isActive === 1, testStatus: r.testStatus, createdAt: r.createdAt, updatedAt: r.updatedAt })),
apiKeys: db.all(`SELECT * FROM apiKeys`).map((r) => ({ id: r.id, key: r.key, name: r.name, machineId: r.machineId, isActive: r.isActive === 1, createdAt: r.createdAt })),
apiKeys: db.all(`SELECT * FROM apiKeys`).map((r) => ({ id: r.id, key: r.key, name: r.name, machineId: r.machineId, ownerId: r.ownerId, isActive: r.isActive === 1, createdAt: r.createdAt })),
combos: db.all(`SELECT * FROM combos`).map((r) => ({ id: r.id, name: r.name, kind: r.kind, models: parseJson(r.models, []), createdAt: r.createdAt, updatedAt: r.updatedAt })),
modelAliases: {},
customModels: [],
@@ -164,10 +165,11 @@ export async function importDb(payload) {
[id, isActive === false ? 0 : 1, testStatus || "unknown", stringifyJson(rest), createdAt || new Date().toISOString(), updatedAt || new Date().toISOString()]
);
}
const defaultKeyOwner = db.get(`SELECT id FROM users WHERE role = 'admin' ORDER BY createdAt ASC LIMIT 1`)?.id || null;
for (const k of payload.apiKeys || []) {
db.run(
`INSERT OR REPLACE INTO apiKeys(id, key, name, machineId, isActive, createdAt) VALUES(?, ?, ?, ?, ?, ?)`,
[k.id, k.key, k.name || null, k.machineId || null, k.isActive === false ? 0 : 1, k.createdAt || new Date().toISOString()]
`INSERT OR REPLACE INTO apiKeys(id, key, name, machineId, ownerId, isActive, createdAt) VALUES(?, ?, ?, ?, ?, ?, ?)`,
[k.id, k.key, k.name || null, k.machineId || null, k.ownerId || defaultKeyOwner, k.isActive === false ? 0 : 1, k.createdAt || new Date().toISOString()]
);
}
for (const c of payload.combos || []) {
+3 -2
View File
@@ -111,6 +111,7 @@ function syncSchemaFromTables(adapter) {
// ─── Legacy JSON import (one-time) ───────────────────────────────────────
function importLegacyMain(adapter, data) {
if (!data || typeof data !== "object") return;
const defaultKeyOwner = adapter.get(`SELECT id FROM users WHERE role = 'admin' ORDER BY createdAt ASC LIMIT 1`)?.id || null;
if (data.settings) {
adapter.run(`INSERT INTO settings(id, data) VALUES(1, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`, [stringifyJson(data.settings)]);
@@ -142,8 +143,8 @@ function importLegacyMain(adapter, data) {
importWithAssertion(adapter, "apiKeys", data.apiKeys || [], (k) => {
adapter.run(
`INSERT OR REPLACE INTO apiKeys(id, key, name, machineId, isActive, createdAt) VALUES(?, ?, ?, ?, ?, ?)`,
[k.id, k.key, k.name || null, k.machineId || null, k.isActive === false ? 0 : 1, k.createdAt || new Date().toISOString()]
`INSERT OR REPLACE INTO apiKeys(id, key, name, machineId, ownerId, isActive, createdAt) VALUES(?, ?, ?, ?, ?, ?, ?)`,
[k.id, k.key, k.name || null, k.machineId || null, k.ownerId || defaultKeyOwner, k.isActive === false ? 0 : 1, k.createdAt || new Date().toISOString()]
);
}, (k) => ({ id: k.id ?? null, name: k.name ?? null }));
@@ -0,0 +1,17 @@
// API keys are private to the dashboard account that created them. Keys from
// pre-multi-user installations are retained as keys owned by the first admin.
export default {
version: 3,
name: "api-key-owners",
up(db) {
const columns = db.all(`PRAGMA table_info(apiKeys)`);
if (!columns.some((column) => column.name === "ownerId")) {
db.exec(`ALTER TABLE apiKeys ADD COLUMN ownerId TEXT`);
}
const admin = db.get(`SELECT id FROM users WHERE role = 'admin' ORDER BY createdAt ASC LIMIT 1`);
if (admin) {
db.run(`UPDATE apiKeys SET ownerId = ? WHERE ownerId IS NULL OR ownerId = ''`, [admin.id]);
}
},
};
+2 -1
View File
@@ -3,8 +3,9 @@
// Versions MUST be unique and monotonically increasing.
import m001 from "./001-initial.js";
import m002 from "./002-users-table.js";
import m003 from "./003-api-key-owners.js";
export const MIGRATIONS = [m001, m002].sort((a, b) => a.version - b.version);
export const MIGRATIONS = [m001, m002, m003].sort((a, b) => a.version - b.version);
export function latestVersion() {
return MIGRATIONS.length ? MIGRATIONS[MIGRATIONS.length - 1].version : 0;
+19 -5
View File
@@ -8,6 +8,7 @@ function rowToKey(row) {
key: row.key,
name: row.name,
machineId: row.machineId,
ownerId: row.ownerId,
isActive: row.isActive === 1 || row.isActive === true,
createdAt: row.createdAt,
};
@@ -19,13 +20,25 @@ export async function getApiKeys() {
return rows.map(rowToKey);
}
export async function getApiKeysByOwnerId(ownerId) {
const db = await getAdapter();
const rows = db.all(`SELECT * FROM apiKeys WHERE ownerId = ? ORDER BY createdAt ASC`, [ownerId]);
return rows.map(rowToKey);
}
export async function getApiKeyById(id) {
const db = await getAdapter();
const row = db.get(`SELECT * FROM apiKeys WHERE id = ?`, [id]);
return rowToKey(row);
}
export async function createApiKey(name, machineId) {
export async function getApiKeyByIdAndOwnerId(id, ownerId) {
const db = await getAdapter();
const row = db.get(`SELECT * FROM apiKeys WHERE id = ? AND ownerId = ?`, [id, ownerId]);
return rowToKey(row);
}
export async function createApiKey(name, machineId, ownerId = null) {
if (!machineId) throw new Error("machineId is required");
const db = await getAdapter();
const { generateApiKeyWithMachine } = await import("@/shared/utils/apiKey");
@@ -35,12 +48,13 @@ export async function createApiKey(name, machineId) {
name,
key: result.key,
machineId,
ownerId,
isActive: true,
createdAt: new Date().toISOString(),
};
db.run(
`INSERT INTO apiKeys(id, key, name, machineId, isActive, createdAt) VALUES(?, ?, ?, ?, ?, ?)`,
[apiKey.id, apiKey.key, apiKey.name, apiKey.machineId, 1, apiKey.createdAt]
`INSERT INTO apiKeys(id, key, name, machineId, ownerId, isActive, createdAt) VALUES(?, ?, ?, ?, ?, ?, ?)`,
[apiKey.id, apiKey.key, apiKey.name, apiKey.machineId, apiKey.ownerId, 1, apiKey.createdAt]
);
return apiKey;
}
@@ -53,8 +67,8 @@ export async function updateApiKey(id, data) {
if (!row) return;
const merged = { ...rowToKey(row), ...data };
db.run(
`UPDATE apiKeys SET key = ?, name = ?, machineId = ?, isActive = ? WHERE id = ?`,
[merged.key, merged.name, merged.machineId, merged.isActive ? 1 : 0, id]
`UPDATE apiKeys SET key = ?, name = ?, machineId = ?, ownerId = ?, isActive = ? WHERE id = ?`,
[merged.key, merged.name, merged.machineId, merged.ownerId, merged.isActive ? 1 : 0, id]
);
result = merged;
});
+6 -2
View File
@@ -3,7 +3,7 @@
// pre-change safety backup in migrate.js: when the stored version is lower,
// one lightweight DB backup is taken before applying schema changes. Forgetting
// to bump only skips that backup — it does NOT break the additive auto-sync.
export const SCHEMA_VERSION = 2;
export const SCHEMA_VERSION = 3;
export const PRAGMA_SQL = `
PRAGMA journal_mode = WAL;
@@ -96,10 +96,14 @@ export const TABLES = {
key: "TEXT UNIQUE NOT NULL",
name: "TEXT",
machineId: "TEXT",
ownerId: "TEXT",
isActive: "INTEGER DEFAULT 1",
createdAt: "TEXT NOT NULL",
},
indexes: ["CREATE INDEX IF NOT EXISTS idx_ak_key ON apiKeys(key)"],
indexes: [
"CREATE INDEX IF NOT EXISTS idx_ak_key ON apiKeys(key)",
"CREATE INDEX IF NOT EXISTS idx_ak_owner ON apiKeys(ownerId)",
],
},
combos: {
columns: {
+2 -1
View File
@@ -12,7 +12,8 @@ export {
createProviderNode, updateProviderNode, deleteProviderNode,
getProxyPools, getProxyPoolById,
createProxyPool, updateProxyPool, deleteProxyPool,
getApiKeys, getApiKeyById, createApiKey, updateApiKey, deleteApiKey, validateApiKey,
getApiKeys, getApiKeysByOwnerId, getApiKeyById, getApiKeyByIdAndOwnerId,
createApiKey, updateApiKey, deleteApiKey, validateApiKey,
getCombos, getComboById, getComboByName,
createCombo, updateCombo, deleteCombo,
getModelAliases, setModelAlias, deleteModelAlias,