@standardagents/builder 0.17.2 → 0.18.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/dist/built-in-routes.js +527 -266
- package/dist/built-in-routes.js.map +1 -1
- package/dist/client/ApiKeysView.js +1 -1
- package/dist/client/CenteredContentView.js +1 -1
- package/dist/client/CompositionView.js +1 -1
- package/dist/client/ConfirmDialog.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/client/CopyButton.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/client/DataTable.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/client/JsonViewer.js +1 -1
- package/dist/client/LoginView.js +1 -1
- package/dist/client/MarketplaceView.js +1 -1
- package/dist/client/Modal.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/client/ModelModal.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/client/ModelsView.js +1 -1
- package/dist/client/PromptEditView.js +1 -1
- package/dist/client/PromptModal.js +1 -1
- package/dist/client/PromptsView.js +1 -1
- package/dist/client/ProvidersView.js +2 -2
- package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/client/ToolsView.js +1 -1
- package/dist/client/UsersView.js +1 -1
- package/dist/client/VariablesView.js +1 -1
- package/dist/client/assets/index.css +1 -1
- package/dist/client/index.js +4 -4
- package/dist/index.js +500 -52
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +92 -7
- package/dist/plugin.js.map +1 -1
- package/dist/runtime.d.ts +72 -4
- package/dist/runtime.js +408 -45
- package/dist/runtime.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -19696,8 +19696,10 @@ import { isThreadEndpoint } from "@standardagents/spec";
|
|
|
19696
19696
|
const PUBLIC_ROUTES = [
|
|
19697
19697
|
'/api/auth/bootstrap',
|
|
19698
19698
|
'/api/auth/login',
|
|
19699
|
-
'/api/auth/bootstrap',
|
|
19700
19699
|
'/api/auth/config',
|
|
19700
|
+
'/api/auth/platform-replica',
|
|
19701
|
+
'/api/auth/sa/start', // Login with Standard Agents (OAuth) \u2014 unauthenticated entry
|
|
19702
|
+
'/api/auth/sa/callback', // OAuth callback (sets the session cookie)
|
|
19701
19703
|
'/api/config',
|
|
19702
19704
|
'/api/auth/oauth/github',
|
|
19703
19705
|
'/api/auth/oauth/google',
|
|
@@ -19710,15 +19712,31 @@ const PUBLIC_ROUTES = [
|
|
|
19710
19712
|
'/api/hooks' // Hook metadata is safe to expose publicly
|
|
19711
19713
|
];
|
|
19712
19714
|
|
|
19715
|
+
// True when the platform deployed this instance (injects STANDARD_AGENTS_HOSTED).
|
|
19716
|
+
// Hosted instances are internet-reachable and multi-tenant, so the thread data
|
|
19717
|
+
// API and event/stream WebSockets must NOT be anonymously public the way they
|
|
19718
|
+
// are in single-user local dev \u2014 they require a session (admin) or API key (SDK).
|
|
19719
|
+
function isHostedInstance(env) {
|
|
19720
|
+
const value = env && env.STANDARD_AGENTS_HOSTED;
|
|
19721
|
+
if (typeof value === 'string') {
|
|
19722
|
+
const trimmed = value.trim().toLowerCase();
|
|
19723
|
+
return trimmed !== '' && trimmed !== '0' && trimmed !== 'false';
|
|
19724
|
+
}
|
|
19725
|
+
return Boolean(value);
|
|
19726
|
+
}
|
|
19727
|
+
|
|
19713
19728
|
// Check if a route is public (no auth required)
|
|
19714
|
-
function isPublicRoute(routePath) {
|
|
19729
|
+
function isPublicRoute(routePath, hosted) {
|
|
19715
19730
|
// Exact match for auth routes
|
|
19716
19731
|
if (PUBLIC_ROUTES.includes(routePath)) {
|
|
19717
19732
|
return true;
|
|
19718
19733
|
}
|
|
19719
19734
|
|
|
19720
|
-
// Thread routes are
|
|
19721
|
-
|
|
19735
|
+
// Thread routes (REST + message/log stream WebSockets) are public in local
|
|
19736
|
+
// single-user dev, but on a hosted deployment they require auth \u2014 requireAuth
|
|
19737
|
+
// accepts the admin's session (cookie or token) or the SDK's API key, so this
|
|
19738
|
+
// only blocks anonymous access to another tenant's threads/messages/files.
|
|
19739
|
+
if (!hosted && (routePath.startsWith('/api/threads/') || routePath === '/api/threads')) {
|
|
19722
19740
|
return true;
|
|
19723
19741
|
}
|
|
19724
19742
|
|
|
@@ -19727,16 +19745,25 @@ function isPublicRoute(routePath) {
|
|
|
19727
19745
|
return true;
|
|
19728
19746
|
}
|
|
19729
19747
|
|
|
19730
|
-
// Platform proxy routes handle their own auth.
|
|
19748
|
+
// Platform proxy routes handle their own auth in local dev only.
|
|
19749
|
+
if (hosted && (routePath.startsWith('/api/platform/') || routePath === '/api/platform')) {
|
|
19750
|
+
return false;
|
|
19751
|
+
}
|
|
19731
19752
|
if (routePath.startsWith('/api/platform/') || routePath === '/api/platform') {
|
|
19732
19753
|
return true;
|
|
19733
19754
|
}
|
|
19734
19755
|
|
|
19735
|
-
// Platform session proxy and auth bridge
|
|
19756
|
+
// Platform session proxy and auth bridge are local-dev helpers only.
|
|
19757
|
+
if (hosted && (routePath.startsWith('/api/platform-session/') || routePath === '/api/platform-session')) {
|
|
19758
|
+
return false;
|
|
19759
|
+
}
|
|
19736
19760
|
if (routePath.startsWith('/api/platform-session/') || routePath === '/api/platform-session') {
|
|
19737
19761
|
return true;
|
|
19738
19762
|
}
|
|
19739
19763
|
|
|
19764
|
+
if (hosted && (routePath.startsWith('/api/platform-auth/') || routePath === '/api/platform-auth')) {
|
|
19765
|
+
return false;
|
|
19766
|
+
}
|
|
19740
19767
|
if (routePath.startsWith('/api/platform-auth/') || routePath === '/api/platform-auth') {
|
|
19741
19768
|
return true;
|
|
19742
19769
|
}
|
|
@@ -19744,6 +19771,36 @@ function isPublicRoute(routePath) {
|
|
|
19744
19771
|
return false;
|
|
19745
19772
|
}
|
|
19746
19773
|
|
|
19774
|
+
function platformEndpoint(env) {
|
|
19775
|
+
const configured =
|
|
19776
|
+
env && (env.PLATFORM_ENDPOINT || env.STANDARD_AGENTS_PLATFORM_URL || env.PLATFORM_URL || env.STANDARD_AGENTS_PUBLIC_URL);
|
|
19777
|
+
if (typeof configured === 'string' && configured.trim()) {
|
|
19778
|
+
return configured.trim().replace(/\\/+$/, '');
|
|
19779
|
+
}
|
|
19780
|
+
return 'https://platform.standardagents.ai';
|
|
19781
|
+
}
|
|
19782
|
+
|
|
19783
|
+
function hostedInstanceRedirectId(request, env) {
|
|
19784
|
+
const configured = env && (env.STANDARD_AGENTS_PROJECT_ID || env.STANDARD_AGENTS_INSTANCE_ID || env.STANDARD_AGENTS_INSTANCE_SUBDOMAIN);
|
|
19785
|
+
if (typeof configured === 'string' && configured.trim()) {
|
|
19786
|
+
return configured.trim();
|
|
19787
|
+
}
|
|
19788
|
+
return new URL(request.url).hostname;
|
|
19789
|
+
}
|
|
19790
|
+
|
|
19791
|
+
function platformLoginUrl(request, env) {
|
|
19792
|
+
const requestUrl = new URL(request.url);
|
|
19793
|
+
const url = new URL('/login', platformEndpoint(env));
|
|
19794
|
+
url.searchParams.set('redirect', hostedInstanceRedirectId(request, env));
|
|
19795
|
+
url.searchParams.set('return_to', requestUrl.pathname + requestUrl.search || '/');
|
|
19796
|
+
return url.toString();
|
|
19797
|
+
}
|
|
19798
|
+
|
|
19799
|
+
function isHtmlNavigationRequest(request) {
|
|
19800
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') return false;
|
|
19801
|
+
return (request.headers.get('Accept') || '').includes('text/html');
|
|
19802
|
+
}
|
|
19803
|
+
|
|
19747
19804
|
// CORS headers for API responses
|
|
19748
19805
|
const CORS_HEADERS = {
|
|
19749
19806
|
"Access-Control-Allow-Origin": "*",
|
|
@@ -19820,7 +19877,7 @@ ${packedThreadRouteCode}
|
|
|
19820
19877
|
|
|
19821
19878
|
if (routeMatch) {
|
|
19822
19879
|
// Check if authentication is required for this route
|
|
19823
|
-
const publicRoute = isPublicRoute(routePath);
|
|
19880
|
+
const publicRoute = isPublicRoute(routePath, isHostedInstance(env));
|
|
19824
19881
|
const isApiRoute = routePath.startsWith('/api/');
|
|
19825
19882
|
|
|
19826
19883
|
let authContext = null;
|
|
@@ -19835,6 +19892,21 @@ ${packedThreadRouteCode}
|
|
|
19835
19892
|
}
|
|
19836
19893
|
|
|
19837
19894
|
authContext = authResult;
|
|
19895
|
+
|
|
19896
|
+
if (routePath.startsWith('/api/threads/')) {
|
|
19897
|
+
const threadId = routeMatch.params?.id || routeMatch.params?.threadId;
|
|
19898
|
+
if (threadId) {
|
|
19899
|
+
const agentBuilderId = env.AGENT_BUILDER.idFromName('singleton');
|
|
19900
|
+
const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
|
|
19901
|
+
const thread = await agentBuilder.getThread(threadId);
|
|
19902
|
+
if (!thread) {
|
|
19903
|
+
return addCorsHeaders(Response.json({ error: \`Thread not found: \${threadId}\` }, { status: 404 }));
|
|
19904
|
+
}
|
|
19905
|
+
if (authContext.user.role !== 'admin' && (thread.user_id === null || thread.user_id !== authContext.user.id)) {
|
|
19906
|
+
return addCorsHeaders(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }));
|
|
19907
|
+
}
|
|
19908
|
+
}
|
|
19909
|
+
}
|
|
19838
19910
|
}
|
|
19839
19911
|
|
|
19840
19912
|
let controller = await routeMatch.data();
|
|
@@ -19870,6 +19942,19 @@ ${packedThreadRouteCode}
|
|
|
19870
19942
|
});
|
|
19871
19943
|
}
|
|
19872
19944
|
|
|
19945
|
+
// Hosted browser navigations do not render a local login page. Redirect
|
|
19946
|
+
// anonymous users directly to the platform, where the instance membership is
|
|
19947
|
+
// resolved and returned as a signed handoff token.
|
|
19948
|
+
if (isHostedInstance(env) && isHtmlNavigationRequest(request)) {
|
|
19949
|
+
const authResult = await requireAuth(request, env);
|
|
19950
|
+
if (authResult instanceof Response) {
|
|
19951
|
+
return new Response(null, {
|
|
19952
|
+
status: 302,
|
|
19953
|
+
headers: { Location: platformLoginUrl(request, env) },
|
|
19954
|
+
});
|
|
19955
|
+
}
|
|
19956
|
+
}
|
|
19957
|
+
|
|
19873
19958
|
// Serve UI for all other routes (SPA fallback)
|
|
19874
19959
|
return serveUI(routePath, env);
|
|
19875
19960
|
}
|
|
@@ -20573,11 +20658,24 @@ async function hashToken(token) {
|
|
|
20573
20658
|
const hashArray = new Uint8Array(hashBuffer);
|
|
20574
20659
|
return Array.from(hashArray, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
20575
20660
|
}
|
|
20661
|
+
var SESSION_COOKIE_NAME = "agtuser_session";
|
|
20662
|
+
function readSessionCookie(request) {
|
|
20663
|
+
const header = request.headers.get("Cookie");
|
|
20664
|
+
if (!header) return null;
|
|
20665
|
+
for (const part of header.split(";")) {
|
|
20666
|
+
const eq = part.indexOf("=");
|
|
20667
|
+
if (eq === -1) continue;
|
|
20668
|
+
if (part.slice(0, eq).trim() === SESSION_COOKIE_NAME) {
|
|
20669
|
+
return decodeURIComponent(part.slice(eq + 1).trim()) || null;
|
|
20670
|
+
}
|
|
20671
|
+
}
|
|
20672
|
+
return null;
|
|
20673
|
+
}
|
|
20576
20674
|
function isValidUserToken(token) {
|
|
20577
20675
|
return token.startsWith("agtuser_") && token.length > 10;
|
|
20578
20676
|
}
|
|
20579
20677
|
function isValidApiKey(key) {
|
|
20580
|
-
return key.startsWith("agtbldr_") && key.length > 10;
|
|
20678
|
+
return key.startsWith("agtbldr_") && key.length > 10 || key.startsWith("sak_live_") && key.length > 10;
|
|
20581
20679
|
}
|
|
20582
20680
|
async function verifySignedToken(signedToken, encryptionKey) {
|
|
20583
20681
|
try {
|
|
@@ -20646,6 +20744,10 @@ function extractBearerToken(request) {
|
|
|
20646
20744
|
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
20647
20745
|
return authHeader.substring(7);
|
|
20648
20746
|
}
|
|
20747
|
+
const cookieToken = readSessionCookie(request);
|
|
20748
|
+
if (cookieToken) {
|
|
20749
|
+
return cookieToken;
|
|
20750
|
+
}
|
|
20649
20751
|
const isWebSocket = request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
20650
20752
|
if (isWebSocket) {
|
|
20651
20753
|
try {
|
|
@@ -20697,7 +20799,11 @@ async function authenticate(request, env) {
|
|
|
20697
20799
|
user: {
|
|
20698
20800
|
id: user.id,
|
|
20699
20801
|
username: user.username,
|
|
20700
|
-
role: user.role
|
|
20802
|
+
role: user.role,
|
|
20803
|
+
platform_user_id: user.platform_user_id ?? null,
|
|
20804
|
+
email: user.email ?? null,
|
|
20805
|
+
display_name: user.display_name ?? null,
|
|
20806
|
+
avatar_url: user.avatar_url ?? null
|
|
20701
20807
|
},
|
|
20702
20808
|
authType: "session"
|
|
20703
20809
|
};
|
|
@@ -20715,7 +20821,11 @@ async function authenticate(request, env) {
|
|
|
20715
20821
|
user: {
|
|
20716
20822
|
id: user.id,
|
|
20717
20823
|
username: user.username,
|
|
20718
|
-
role: user.role
|
|
20824
|
+
role: user.role,
|
|
20825
|
+
platform_user_id: user.platform_user_id ?? null,
|
|
20826
|
+
email: user.email ?? null,
|
|
20827
|
+
display_name: user.display_name ?? null,
|
|
20828
|
+
avatar_url: user.avatar_url ?? null
|
|
20719
20829
|
},
|
|
20720
20830
|
authType: "api_key"
|
|
20721
20831
|
};
|
|
@@ -23505,9 +23615,9 @@ var DurableThread = class extends DurableObject {
|
|
|
23505
23615
|
* Each migration is run in order, starting from the current version + 1.
|
|
23506
23616
|
*/
|
|
23507
23617
|
async runMigrations(fromVersion) {
|
|
23508
|
-
for (const
|
|
23509
|
-
if (
|
|
23510
|
-
await
|
|
23618
|
+
for (const migration38 of migrations) {
|
|
23619
|
+
if (migration38.version > fromVersion) {
|
|
23620
|
+
await migration38.up(this.ctx.storage.sql);
|
|
23511
23621
|
}
|
|
23512
23622
|
}
|
|
23513
23623
|
}
|
|
@@ -26942,9 +27052,38 @@ var migration36 = {
|
|
|
26942
27052
|
}
|
|
26943
27053
|
};
|
|
26944
27054
|
|
|
27055
|
+
// src/durable-objects/agentbuilder-migrations/0007_platform_identity_replica.ts
|
|
27056
|
+
var migration37 = {
|
|
27057
|
+
version: 7,
|
|
27058
|
+
async up(sql) {
|
|
27059
|
+
sql.exec(`ALTER TABLE users ADD COLUMN platform_user_id TEXT`);
|
|
27060
|
+
sql.exec(`ALTER TABLE users ADD COLUMN email TEXT`);
|
|
27061
|
+
sql.exec(`ALTER TABLE users ADD COLUMN display_name TEXT`);
|
|
27062
|
+
sql.exec(`ALTER TABLE users ADD COLUMN avatar_url TEXT`);
|
|
27063
|
+
sql.exec(`ALTER TABLE users ADD COLUMN instance_role TEXT NOT NULL DEFAULT 'admin' CHECK (instance_role IN ('admin', 'user'))`);
|
|
27064
|
+
sql.exec(`ALTER TABLE users ADD COLUMN source TEXT NOT NULL DEFAULT 'local' CHECK (source IN ('local', 'standard_agents'))`);
|
|
27065
|
+
sql.exec(`ALTER TABLE users ADD COLUMN replica_active INTEGER NOT NULL DEFAULT 1`);
|
|
27066
|
+
sql.exec(`ALTER TABLE users ADD COLUMN replica_updated_at INTEGER`);
|
|
27067
|
+
sql.exec(`ALTER TABLE users ADD COLUMN deleted_at INTEGER`);
|
|
27068
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_platform_user_id ON users(platform_user_id)`);
|
|
27069
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_replica_active ON users(replica_active)`);
|
|
27070
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_instance_role ON users(instance_role)`);
|
|
27071
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN platform_key_id TEXT`);
|
|
27072
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN scope TEXT`);
|
|
27073
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN source TEXT NOT NULL DEFAULT 'local' CHECK (source IN ('local', 'standard_agents'))`);
|
|
27074
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN replica_active INTEGER NOT NULL DEFAULT 1`);
|
|
27075
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN replica_updated_at INTEGER`);
|
|
27076
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_platform_key_id ON api_keys(platform_key_id)`);
|
|
27077
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_replica_active ON api_keys(replica_active)`);
|
|
27078
|
+
sql.exec(`
|
|
27079
|
+
INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '7')
|
|
27080
|
+
`);
|
|
27081
|
+
}
|
|
27082
|
+
};
|
|
27083
|
+
|
|
26945
27084
|
// src/durable-objects/agentbuilder-migrations/index.ts
|
|
26946
|
-
var migrations2 = [migration31, migration32, migration33, migration34, migration35, migration36];
|
|
26947
|
-
var LATEST_SCHEMA_VERSION2 =
|
|
27085
|
+
var migrations2 = [migration31, migration32, migration33, migration34, migration35, migration36, migration37];
|
|
27086
|
+
var LATEST_SCHEMA_VERSION2 = 7;
|
|
26948
27087
|
|
|
26949
27088
|
// src/utils/crypto.ts
|
|
26950
27089
|
var CryptoUtil = class {
|
|
@@ -27568,9 +27707,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
27568
27707
|
}
|
|
27569
27708
|
}
|
|
27570
27709
|
async runMigrations(fromVersion) {
|
|
27571
|
-
for (const
|
|
27572
|
-
if (
|
|
27573
|
-
await
|
|
27710
|
+
for (const migration38 of migrations2) {
|
|
27711
|
+
if (migration38.version > fromVersion) {
|
|
27712
|
+
await migration38.up(this.ctx.storage.sql);
|
|
27574
27713
|
}
|
|
27575
27714
|
}
|
|
27576
27715
|
}
|
|
@@ -28603,27 +28742,54 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28603
28742
|
// ============================================================
|
|
28604
28743
|
// User Authentication Methods
|
|
28605
28744
|
// ============================================================
|
|
28745
|
+
normalizeReplicaUsername(input, fallback) {
|
|
28746
|
+
const normalized = (input || fallback).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
|
|
28747
|
+
return normalized || fallback;
|
|
28748
|
+
}
|
|
28749
|
+
async availableReplicaUsername(candidate, platformUserId) {
|
|
28750
|
+
const existing = await this.ctx.storage.sql.exec(
|
|
28751
|
+
`SELECT id, platform_user_id FROM users WHERE username = ? LIMIT 1`,
|
|
28752
|
+
candidate
|
|
28753
|
+
);
|
|
28754
|
+
const row = existing.toArray()[0];
|
|
28755
|
+
if (!row || row.platform_user_id === platformUserId) {
|
|
28756
|
+
return candidate;
|
|
28757
|
+
}
|
|
28758
|
+
const suffix = platformUserId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "user";
|
|
28759
|
+
const trimmed = candidate.slice(0, Math.max(1, 50 - suffix.length - 1)).replace(/-+$/g, "");
|
|
28760
|
+
return `${trimmed || "sa"}-${suffix}`;
|
|
28761
|
+
}
|
|
28762
|
+
userFromRow(row) {
|
|
28763
|
+
return {
|
|
28764
|
+
id: row.id,
|
|
28765
|
+
username: row.username,
|
|
28766
|
+
password_hash: row.password_hash,
|
|
28767
|
+
role: row.role === "user" ? "user" : "admin",
|
|
28768
|
+
platform_user_id: row.platform_user_id ?? null,
|
|
28769
|
+
email: row.email ?? null,
|
|
28770
|
+
display_name: row.display_name ?? null,
|
|
28771
|
+
avatar_url: row.avatar_url ?? null,
|
|
28772
|
+
source: row.source ?? "local",
|
|
28773
|
+
replica_active: row.replica_active ?? 1,
|
|
28774
|
+
created_at: row.created_at,
|
|
28775
|
+
updated_at: row.updated_at
|
|
28776
|
+
};
|
|
28777
|
+
}
|
|
28606
28778
|
/**
|
|
28607
28779
|
* Get a user by username.
|
|
28608
28780
|
*/
|
|
28609
28781
|
async getUserByUsername(username) {
|
|
28610
28782
|
await this.ensureMigrated();
|
|
28611
28783
|
const cursor = await this.ctx.storage.sql.exec(
|
|
28612
|
-
`SELECT id, username, password_hash,
|
|
28613
|
-
|
|
28784
|
+
`SELECT id, username, password_hash, COALESCE(instance_role, role) AS role,
|
|
28785
|
+
platform_user_id, email, display_name, avatar_url, source, replica_active,
|
|
28786
|
+
created_at, updated_at
|
|
28787
|
+
FROM users WHERE username = ? AND deleted_at IS NULL AND replica_active != 0`,
|
|
28614
28788
|
username
|
|
28615
28789
|
);
|
|
28616
28790
|
const rows = cursor.toArray();
|
|
28617
28791
|
if (rows.length === 0) return null;
|
|
28618
|
-
|
|
28619
|
-
return {
|
|
28620
|
-
id: row.id,
|
|
28621
|
-
username: row.username,
|
|
28622
|
-
password_hash: row.password_hash,
|
|
28623
|
-
role: row.role,
|
|
28624
|
-
created_at: row.created_at,
|
|
28625
|
-
updated_at: row.updated_at
|
|
28626
|
-
};
|
|
28792
|
+
return this.userFromRow(rows[0]);
|
|
28627
28793
|
}
|
|
28628
28794
|
/**
|
|
28629
28795
|
* Get a user by ID.
|
|
@@ -28631,21 +28797,15 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28631
28797
|
async getUserById(id) {
|
|
28632
28798
|
await this.ensureMigrated();
|
|
28633
28799
|
const cursor = await this.ctx.storage.sql.exec(
|
|
28634
|
-
`SELECT id, username, password_hash,
|
|
28635
|
-
|
|
28800
|
+
`SELECT id, username, password_hash, COALESCE(instance_role, role) AS role,
|
|
28801
|
+
platform_user_id, email, display_name, avatar_url, source, replica_active,
|
|
28802
|
+
created_at, updated_at
|
|
28803
|
+
FROM users WHERE id = ? AND deleted_at IS NULL AND replica_active != 0`,
|
|
28636
28804
|
id
|
|
28637
28805
|
);
|
|
28638
28806
|
const rows = cursor.toArray();
|
|
28639
28807
|
if (rows.length === 0) return null;
|
|
28640
|
-
|
|
28641
|
-
return {
|
|
28642
|
-
id: row.id,
|
|
28643
|
-
username: row.username,
|
|
28644
|
-
password_hash: row.password_hash,
|
|
28645
|
-
role: row.role,
|
|
28646
|
-
created_at: row.created_at,
|
|
28647
|
-
updated_at: row.updated_at
|
|
28648
|
-
};
|
|
28808
|
+
return this.userFromRow(rows[0]);
|
|
28649
28809
|
}
|
|
28650
28810
|
/**
|
|
28651
28811
|
* Create a new user.
|
|
@@ -28655,12 +28815,23 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28655
28815
|
const id = crypto.randomUUID();
|
|
28656
28816
|
const now = Math.floor(Date.now() / 1e3);
|
|
28657
28817
|
await this.ctx.storage.sql.exec(
|
|
28658
|
-
`INSERT INTO users (
|
|
28659
|
-
|
|
28818
|
+
`INSERT INTO users (
|
|
28819
|
+
id, username, password_hash, role, instance_role, platform_user_id,
|
|
28820
|
+
email, display_name, avatar_url, source, replica_active,
|
|
28821
|
+
replica_updated_at, created_at, updated_at
|
|
28822
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
28660
28823
|
id,
|
|
28661
28824
|
params.username,
|
|
28662
28825
|
params.password_hash,
|
|
28826
|
+
"admin",
|
|
28663
28827
|
params.role || "admin",
|
|
28828
|
+
params.platform_user_id ?? null,
|
|
28829
|
+
params.email ?? null,
|
|
28830
|
+
params.display_name ?? null,
|
|
28831
|
+
params.avatar_url ?? null,
|
|
28832
|
+
params.source ?? "local",
|
|
28833
|
+
1,
|
|
28834
|
+
params.source === "standard_agents" ? now : null,
|
|
28664
28835
|
now,
|
|
28665
28836
|
now
|
|
28666
28837
|
);
|
|
@@ -28669,6 +28840,12 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28669
28840
|
username: params.username,
|
|
28670
28841
|
password_hash: params.password_hash,
|
|
28671
28842
|
role: params.role || "admin",
|
|
28843
|
+
platform_user_id: params.platform_user_id ?? null,
|
|
28844
|
+
email: params.email ?? null,
|
|
28845
|
+
display_name: params.display_name ?? null,
|
|
28846
|
+
avatar_url: params.avatar_url ?? null,
|
|
28847
|
+
source: params.source ?? "local",
|
|
28848
|
+
replica_active: 1,
|
|
28672
28849
|
created_at: now,
|
|
28673
28850
|
updated_at: now
|
|
28674
28851
|
};
|
|
@@ -28688,11 +28865,24 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28688
28865
|
*/
|
|
28689
28866
|
async listUsers() {
|
|
28690
28867
|
await this.ensureMigrated();
|
|
28691
|
-
const cursor = await this.ctx.storage.sql.exec(
|
|
28868
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
28869
|
+
`SELECT id, username, COALESCE(instance_role, role) AS role,
|
|
28870
|
+
platform_user_id, email, display_name, avatar_url, source, replica_active,
|
|
28871
|
+
created_at, updated_at
|
|
28872
|
+
FROM users
|
|
28873
|
+
WHERE deleted_at IS NULL
|
|
28874
|
+
ORDER BY created_at DESC`
|
|
28875
|
+
);
|
|
28692
28876
|
return cursor.toArray().map((row) => ({
|
|
28693
28877
|
id: row.id,
|
|
28694
28878
|
username: row.username,
|
|
28695
|
-
role: row.role,
|
|
28879
|
+
role: row.role === "user" ? "user" : "admin",
|
|
28880
|
+
platform_user_id: row.platform_user_id ?? null,
|
|
28881
|
+
email: row.email ?? null,
|
|
28882
|
+
display_name: row.display_name ?? null,
|
|
28883
|
+
avatar_url: row.avatar_url ?? null,
|
|
28884
|
+
source: row.source ?? "local",
|
|
28885
|
+
replica_active: row.replica_active ?? 1,
|
|
28696
28886
|
created_at: row.created_at,
|
|
28697
28887
|
updated_at: row.updated_at
|
|
28698
28888
|
}));
|
|
@@ -28716,14 +28906,42 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28716
28906
|
values.push(params.password_hash);
|
|
28717
28907
|
}
|
|
28718
28908
|
if (params.role !== void 0) {
|
|
28719
|
-
updates.push("
|
|
28909
|
+
updates.push("instance_role = ?");
|
|
28720
28910
|
values.push(params.role);
|
|
28721
28911
|
}
|
|
28912
|
+
if (params.platform_user_id !== void 0) {
|
|
28913
|
+
updates.push("platform_user_id = ?");
|
|
28914
|
+
values.push(params.platform_user_id);
|
|
28915
|
+
}
|
|
28916
|
+
if (params.email !== void 0) {
|
|
28917
|
+
updates.push("email = ?");
|
|
28918
|
+
values.push(params.email);
|
|
28919
|
+
}
|
|
28920
|
+
if (params.display_name !== void 0) {
|
|
28921
|
+
updates.push("display_name = ?");
|
|
28922
|
+
values.push(params.display_name);
|
|
28923
|
+
}
|
|
28924
|
+
if (params.avatar_url !== void 0) {
|
|
28925
|
+
updates.push("avatar_url = ?");
|
|
28926
|
+
values.push(params.avatar_url);
|
|
28927
|
+
}
|
|
28928
|
+
if (params.source !== void 0) {
|
|
28929
|
+
updates.push("source = ?");
|
|
28930
|
+
values.push(params.source);
|
|
28931
|
+
}
|
|
28932
|
+
if (params.replica_active !== void 0) {
|
|
28933
|
+
updates.push("replica_active = ?");
|
|
28934
|
+
values.push(params.replica_active);
|
|
28935
|
+
}
|
|
28722
28936
|
if (updates.length === 0) {
|
|
28723
28937
|
return existing;
|
|
28724
28938
|
}
|
|
28725
28939
|
updates.push("updated_at = ?");
|
|
28726
28940
|
values.push(now);
|
|
28941
|
+
if (params.source === "standard_agents" || params.replica_active !== void 0) {
|
|
28942
|
+
updates.push("replica_updated_at = ?");
|
|
28943
|
+
values.push(now);
|
|
28944
|
+
}
|
|
28727
28945
|
values.push(id);
|
|
28728
28946
|
await this.ctx.storage.sql.exec(
|
|
28729
28947
|
`UPDATE users SET ${updates.join(", ")} WHERE id = ?`,
|
|
@@ -28744,9 +28962,223 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28744
28962
|
`DELETE FROM api_keys WHERE user_id = ?`,
|
|
28745
28963
|
id
|
|
28746
28964
|
);
|
|
28747
|
-
await this.ctx.storage.sql.exec(
|
|
28965
|
+
await this.ctx.storage.sql.exec(
|
|
28966
|
+
`UPDATE users SET deleted_at = ?, replica_active = 0 WHERE id = ?`,
|
|
28967
|
+
Math.floor(Date.now() / 1e3),
|
|
28968
|
+
id
|
|
28969
|
+
);
|
|
28748
28970
|
return true;
|
|
28749
28971
|
}
|
|
28972
|
+
/**
|
|
28973
|
+
* Upsert a platform-replicated identity and return the local user row.
|
|
28974
|
+
*/
|
|
28975
|
+
async upsertPlatformReplicaUser(replica) {
|
|
28976
|
+
await this.ensureMigrated();
|
|
28977
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
28978
|
+
const fallback = this.normalizeReplicaUsername(
|
|
28979
|
+
replica.email?.split("@")[0] ?? null,
|
|
28980
|
+
`sa-${replica.platform_user_id.slice(0, 12)}`
|
|
28981
|
+
);
|
|
28982
|
+
const username = this.normalizeReplicaUsername(
|
|
28983
|
+
replica.username ?? replica.display_name ?? replica.email ?? null,
|
|
28984
|
+
fallback
|
|
28985
|
+
);
|
|
28986
|
+
const existingCursor = await this.ctx.storage.sql.exec(
|
|
28987
|
+
`SELECT id FROM users WHERE platform_user_id = ? LIMIT 1`,
|
|
28988
|
+
replica.platform_user_id
|
|
28989
|
+
);
|
|
28990
|
+
const existing = existingCursor.toArray()[0];
|
|
28991
|
+
const availableUsername = await this.availableReplicaUsername(username, replica.platform_user_id);
|
|
28992
|
+
if (existing) {
|
|
28993
|
+
await this.ctx.storage.sql.exec(
|
|
28994
|
+
`UPDATE users SET
|
|
28995
|
+
username = ?,
|
|
28996
|
+
instance_role = ?,
|
|
28997
|
+
email = ?,
|
|
28998
|
+
display_name = ?,
|
|
28999
|
+
avatar_url = ?,
|
|
29000
|
+
source = 'standard_agents',
|
|
29001
|
+
replica_active = 1,
|
|
29002
|
+
replica_updated_at = ?,
|
|
29003
|
+
deleted_at = NULL,
|
|
29004
|
+
updated_at = ?
|
|
29005
|
+
WHERE id = ?`,
|
|
29006
|
+
availableUsername,
|
|
29007
|
+
replica.role,
|
|
29008
|
+
replica.email ?? null,
|
|
29009
|
+
replica.display_name ?? null,
|
|
29010
|
+
replica.avatar_url ?? null,
|
|
29011
|
+
now,
|
|
29012
|
+
now,
|
|
29013
|
+
existing.id
|
|
29014
|
+
);
|
|
29015
|
+
const updated = await this.getUserById(existing.id);
|
|
29016
|
+
if (!updated) {
|
|
29017
|
+
throw new Error("Failed to load replicated user after update");
|
|
29018
|
+
}
|
|
29019
|
+
return updated;
|
|
29020
|
+
}
|
|
29021
|
+
return this.createUser({
|
|
29022
|
+
username: availableUsername,
|
|
29023
|
+
password_hash: `platform-replica:${crypto.randomUUID()}`,
|
|
29024
|
+
role: replica.role,
|
|
29025
|
+
platform_user_id: replica.platform_user_id,
|
|
29026
|
+
email: replica.email ?? null,
|
|
29027
|
+
display_name: replica.display_name ?? null,
|
|
29028
|
+
avatar_url: replica.avatar_url ?? null,
|
|
29029
|
+
source: "standard_agents"
|
|
29030
|
+
});
|
|
29031
|
+
}
|
|
29032
|
+
/**
|
|
29033
|
+
* Apply a full platform read-replica snapshot for this instance.
|
|
29034
|
+
*/
|
|
29035
|
+
async applyPlatformReplicaSnapshot(snapshot) {
|
|
29036
|
+
await this.ensureMigrated();
|
|
29037
|
+
const activeUserIds = /* @__PURE__ */ new Set();
|
|
29038
|
+
for (const replicaUser of snapshot.users) {
|
|
29039
|
+
const user = await this.upsertPlatformReplicaUser(replicaUser);
|
|
29040
|
+
activeUserIds.add(user.id);
|
|
29041
|
+
}
|
|
29042
|
+
const activeList = Array.from(activeUserIds);
|
|
29043
|
+
if (activeList.length > 0) {
|
|
29044
|
+
const placeholders = activeList.map(() => "?").join(", ");
|
|
29045
|
+
const inactive = await this.ctx.storage.sql.exec(
|
|
29046
|
+
`SELECT id FROM users
|
|
29047
|
+
WHERE source = 'standard_agents'
|
|
29048
|
+
AND replica_active != 0
|
|
29049
|
+
AND id NOT IN (${placeholders})`,
|
|
29050
|
+
...activeList
|
|
29051
|
+
);
|
|
29052
|
+
for (const row of inactive.toArray()) {
|
|
29053
|
+
await this.ctx.storage.sql.exec(`DELETE FROM sessions WHERE user_id = ?`, row.id);
|
|
29054
|
+
await this.ctx.storage.sql.exec(
|
|
29055
|
+
`UPDATE users SET replica_active = 0, deleted_at = ?, replica_updated_at = ?, updated_at = ? WHERE id = ?`,
|
|
29056
|
+
Math.floor(Date.now() / 1e3),
|
|
29057
|
+
Math.floor(Date.now() / 1e3),
|
|
29058
|
+
Math.floor(Date.now() / 1e3),
|
|
29059
|
+
row.id
|
|
29060
|
+
);
|
|
29061
|
+
}
|
|
29062
|
+
} else {
|
|
29063
|
+
const inactive = await this.ctx.storage.sql.exec(
|
|
29064
|
+
`SELECT id FROM users WHERE source = 'standard_agents' AND replica_active != 0`
|
|
29065
|
+
);
|
|
29066
|
+
for (const row of inactive.toArray()) {
|
|
29067
|
+
await this.ctx.storage.sql.exec(`DELETE FROM sessions WHERE user_id = ?`, row.id);
|
|
29068
|
+
}
|
|
29069
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
29070
|
+
await this.ctx.storage.sql.exec(
|
|
29071
|
+
`UPDATE users SET replica_active = 0, deleted_at = ?, replica_updated_at = ?, updated_at = ?
|
|
29072
|
+
WHERE source = 'standard_agents'`,
|
|
29073
|
+
now2,
|
|
29074
|
+
now2,
|
|
29075
|
+
now2
|
|
29076
|
+
);
|
|
29077
|
+
}
|
|
29078
|
+
const activePlatformKeyIds = /* @__PURE__ */ new Set();
|
|
29079
|
+
const keys = snapshot.api_keys ?? [];
|
|
29080
|
+
for (const key of keys) {
|
|
29081
|
+
const user = key.user_platform_id ? await this.getUserByPlatformUserId(key.user_platform_id) : await this.getFirstReplicaAdminUser();
|
|
29082
|
+
const keyUser = user ?? await this.getFirstReplicaAdminUser();
|
|
29083
|
+
if (!keyUser) continue;
|
|
29084
|
+
activePlatformKeyIds.add(key.id);
|
|
29085
|
+
await this.upsertPlatformReplicaApiKey(key, keyUser.id);
|
|
29086
|
+
}
|
|
29087
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
29088
|
+
if (activePlatformKeyIds.size > 0) {
|
|
29089
|
+
const ids = Array.from(activePlatformKeyIds);
|
|
29090
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
29091
|
+
await this.ctx.storage.sql.exec(
|
|
29092
|
+
`UPDATE api_keys SET replica_active = 0, replica_updated_at = ?
|
|
29093
|
+
WHERE source = 'standard_agents' AND platform_key_id NOT IN (${placeholders})`,
|
|
29094
|
+
now,
|
|
29095
|
+
...ids
|
|
29096
|
+
);
|
|
29097
|
+
} else {
|
|
29098
|
+
await this.ctx.storage.sql.exec(
|
|
29099
|
+
`UPDATE api_keys SET replica_active = 0, replica_updated_at = ?
|
|
29100
|
+
WHERE source = 'standard_agents'`,
|
|
29101
|
+
now
|
|
29102
|
+
);
|
|
29103
|
+
}
|
|
29104
|
+
return { users: activeUserIds.size, api_keys: activePlatformKeyIds.size };
|
|
29105
|
+
}
|
|
29106
|
+
async getUserByPlatformUserId(platformUserId) {
|
|
29107
|
+
await this.ensureMigrated();
|
|
29108
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
29109
|
+
`SELECT id FROM users
|
|
29110
|
+
WHERE platform_user_id = ? AND deleted_at IS NULL AND replica_active != 0
|
|
29111
|
+
LIMIT 1`,
|
|
29112
|
+
platformUserId
|
|
29113
|
+
);
|
|
29114
|
+
const row = cursor.toArray()[0];
|
|
29115
|
+
return row ? this.getUserById(row.id) : null;
|
|
29116
|
+
}
|
|
29117
|
+
async getFirstReplicaAdminUser() {
|
|
29118
|
+
await this.ensureMigrated();
|
|
29119
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
29120
|
+
`SELECT id FROM users
|
|
29121
|
+
WHERE source = 'standard_agents'
|
|
29122
|
+
AND instance_role = 'admin'
|
|
29123
|
+
AND deleted_at IS NULL
|
|
29124
|
+
AND replica_active != 0
|
|
29125
|
+
ORDER BY created_at ASC
|
|
29126
|
+
LIMIT 1`
|
|
29127
|
+
);
|
|
29128
|
+
const row = cursor.toArray()[0];
|
|
29129
|
+
return row ? this.getUserById(row.id) : null;
|
|
29130
|
+
}
|
|
29131
|
+
async upsertPlatformReplicaApiKey(key, userId) {
|
|
29132
|
+
await this.ensureMigrated();
|
|
29133
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
29134
|
+
const existing = await this.ctx.storage.sql.exec(
|
|
29135
|
+
`SELECT id FROM api_keys WHERE platform_key_id = ? LIMIT 1`,
|
|
29136
|
+
key.id
|
|
29137
|
+
);
|
|
29138
|
+
const row = existing.toArray()[0];
|
|
29139
|
+
const name = key.name || `Standard Agents ${key.key_prefix}`;
|
|
29140
|
+
const lastFive = key.last_five || key.key_prefix.slice(-5);
|
|
29141
|
+
if (row) {
|
|
29142
|
+
await this.ctx.storage.sql.exec(
|
|
29143
|
+
`UPDATE api_keys SET
|
|
29144
|
+
name = ?,
|
|
29145
|
+
key_hash = ?,
|
|
29146
|
+
key_prefix = ?,
|
|
29147
|
+
last_five = ?,
|
|
29148
|
+
user_id = ?,
|
|
29149
|
+
scope = ?,
|
|
29150
|
+
source = 'standard_agents',
|
|
29151
|
+
replica_active = 1,
|
|
29152
|
+
replica_updated_at = ?
|
|
29153
|
+
WHERE id = ?`,
|
|
29154
|
+
name,
|
|
29155
|
+
key.key_hash,
|
|
29156
|
+
key.key_prefix,
|
|
29157
|
+
lastFive,
|
|
29158
|
+
userId,
|
|
29159
|
+
key.scope ?? null,
|
|
29160
|
+
now,
|
|
29161
|
+
row.id
|
|
29162
|
+
);
|
|
29163
|
+
return;
|
|
29164
|
+
}
|
|
29165
|
+
await this.ctx.storage.sql.exec(
|
|
29166
|
+
`INSERT INTO api_keys (
|
|
29167
|
+
id, name, key_hash, key_prefix, last_five, user_id, created_at,
|
|
29168
|
+
platform_key_id, scope, source, replica_active, replica_updated_at
|
|
29169
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'standard_agents', 1, ?)`,
|
|
29170
|
+
`platform:${key.id}`,
|
|
29171
|
+
name,
|
|
29172
|
+
key.key_hash,
|
|
29173
|
+
key.key_prefix,
|
|
29174
|
+
lastFive,
|
|
29175
|
+
userId,
|
|
29176
|
+
key.created_at ?? now,
|
|
29177
|
+
key.id,
|
|
29178
|
+
key.scope ?? null,
|
|
29179
|
+
now
|
|
29180
|
+
);
|
|
29181
|
+
}
|
|
28750
29182
|
// ============================================================
|
|
28751
29183
|
// Session Methods
|
|
28752
29184
|
// ============================================================
|
|
@@ -28775,8 +29207,13 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28775
29207
|
await this.ensureMigrated();
|
|
28776
29208
|
const now = Math.floor(Date.now() / 1e3);
|
|
28777
29209
|
const cursor = await this.ctx.storage.sql.exec(
|
|
28778
|
-
`SELECT user_id, expires_at
|
|
28779
|
-
|
|
29210
|
+
`SELECT sessions.user_id, sessions.expires_at
|
|
29211
|
+
FROM sessions
|
|
29212
|
+
JOIN users ON users.id = sessions.user_id
|
|
29213
|
+
WHERE sessions.token_hash = ?
|
|
29214
|
+
AND sessions.expires_at > ?
|
|
29215
|
+
AND users.deleted_at IS NULL
|
|
29216
|
+
AND users.replica_active != 0`,
|
|
28780
29217
|
tokenHash,
|
|
28781
29218
|
now
|
|
28782
29219
|
);
|
|
@@ -28833,14 +29270,23 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28833
29270
|
*/
|
|
28834
29271
|
async validateApiKey(keyHash) {
|
|
28835
29272
|
await this.ensureMigrated();
|
|
28836
|
-
const cursor = await this.ctx.storage.sql.exec(
|
|
29273
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
29274
|
+
`SELECT api_keys.id, api_keys.user_id
|
|
29275
|
+
FROM api_keys
|
|
29276
|
+
JOIN users ON users.id = api_keys.user_id
|
|
29277
|
+
WHERE api_keys.key_hash = ?
|
|
29278
|
+
AND api_keys.replica_active != 0
|
|
29279
|
+
AND users.deleted_at IS NULL
|
|
29280
|
+
AND users.replica_active != 0`,
|
|
29281
|
+
keyHash
|
|
29282
|
+
);
|
|
28837
29283
|
const rows = cursor.toArray();
|
|
28838
29284
|
if (rows.length === 0) {
|
|
28839
29285
|
return null;
|
|
28840
29286
|
}
|
|
28841
29287
|
const now = Math.floor(Date.now() / 1e3);
|
|
28842
29288
|
await this.ctx.storage.sql.exec(
|
|
28843
|
-
`UPDATE api_keys SET last_used_at = ? WHERE key_hash =
|
|
29289
|
+
`UPDATE api_keys SET last_used_at = ? WHERE key_hash = ? AND replica_active != 0`,
|
|
28844
29290
|
now,
|
|
28845
29291
|
keyHash
|
|
28846
29292
|
);
|
|
@@ -28852,8 +29298,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28852
29298
|
async listApiKeys(userId) {
|
|
28853
29299
|
await this.ensureMigrated();
|
|
28854
29300
|
const cursor = await this.ctx.storage.sql.exec(
|
|
28855
|
-
`SELECT id, name, key_prefix, last_five, created_at, last_used_at
|
|
28856
|
-
FROM api_keys
|
|
29301
|
+
`SELECT id, name, key_prefix, last_five, source, created_at, last_used_at
|
|
29302
|
+
FROM api_keys
|
|
29303
|
+
WHERE user_id = ? AND replica_active != 0
|
|
29304
|
+
ORDER BY created_at DESC`,
|
|
28857
29305
|
userId
|
|
28858
29306
|
);
|
|
28859
29307
|
return cursor.toArray();
|