feat(frontends,security,api,ui): a frontend can hold a token scope of its own
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
||||
LimitsResponse,
|
||||
MemoryFile,
|
||||
MemoryTree,
|
||||
ScopeRow,
|
||||
SearchResponse,
|
||||
SessionsResponse,
|
||||
TokenRow,
|
||||
@@ -351,6 +352,10 @@ export class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
scopes(): Promise<{ scopes: ScopeRow[] }> {
|
||||
return this.get("/api/scopes");
|
||||
}
|
||||
|
||||
createToken(
|
||||
name: string,
|
||||
scope: string
|
||||
|
||||
@@ -132,6 +132,7 @@ export interface FrontendInfo {
|
||||
kinds: Kind[];
|
||||
name: string;
|
||||
path: string | null;
|
||||
scope: string | null;
|
||||
type: string;
|
||||
url: string | null;
|
||||
}
|
||||
@@ -245,6 +246,11 @@ export interface MemoryFile {
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ScopeRow {
|
||||
frontend: string | null;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export interface TokenRow {
|
||||
created_at: string;
|
||||
id: number;
|
||||
|
||||
@@ -117,7 +117,17 @@
|
||||
};
|
||||
default:
|
||||
return {
|
||||
endpoints: base ? [{ hint: "", label: "base", url: base }] : [],
|
||||
endpoints: base
|
||||
? [
|
||||
{
|
||||
hint: fe.scope
|
||||
? `POST the frontend's own payload; bearer with scope ${fe.scope}`
|
||||
: "",
|
||||
label: "base",
|
||||
url: base,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
frontend: fe,
|
||||
note: "",
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import CopyIcon from "@lucide/svelte/icons/copy";
|
||||
import PlusIcon from "@lucide/svelte/icons/plus";
|
||||
import { toast } from "svelte-sonner";
|
||||
import type { TokenRow } from "$lib/api/types";
|
||||
import type { ScopeRow, TokenRow } from "$lib/api/types";
|
||||
import EmptyState from "$lib/components/empty-state.svelte";
|
||||
import ErrorNote from "$lib/components/error-note.svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
@@ -15,13 +15,23 @@
|
||||
import { session } from "$lib/session.svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
const SCOPES = [
|
||||
{ hint: "every frontend", value: "*" },
|
||||
{ hint: "conversations API and SSE (panel, plugin)", value: "api" },
|
||||
{ hint: "Anthropic /v1/messages", value: "messages" },
|
||||
{ hint: "MCP server", value: "mcp" },
|
||||
{ hint: "tokens and audit over the API", value: "admin" },
|
||||
];
|
||||
const HINTS: Record<string, string> = {
|
||||
"*": "every frontend",
|
||||
admin: "tokens and audit over the API",
|
||||
api: "conversations API and SSE (panel, plugin)",
|
||||
mcp: "MCP server",
|
||||
messages: "Anthropic /v1/messages",
|
||||
};
|
||||
|
||||
let scopes = $state<ScopeRow[]>([]);
|
||||
const options = $derived(
|
||||
scopes.map((row) => ({
|
||||
hint: row.frontend
|
||||
? `the ${row.frontend} frontend`
|
||||
: (HINTS[row.scope] ?? ""),
|
||||
value: row.scope,
|
||||
}))
|
||||
);
|
||||
|
||||
let tokens = $state<TokenRow[] | null>(null);
|
||||
let includeRevoked = $state(false);
|
||||
@@ -49,6 +59,21 @@
|
||||
load(includeRevoked);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const { client } = session;
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
client
|
||||
.scopes()
|
||||
.then(({ scopes: rows }) => {
|
||||
scopes = rows;
|
||||
})
|
||||
.catch(() => {
|
||||
scopes = [];
|
||||
});
|
||||
});
|
||||
|
||||
async function create() {
|
||||
const { client } = session;
|
||||
if (!client) {
|
||||
@@ -254,7 +279,7 @@
|
||||
>
|
||||
<Select.Trigger class="w-full">{scope}</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each SCOPES as option (option.value)}
|
||||
{#each options as option (option.value)}
|
||||
<Select.Item label={option.value} value={option.value}>
|
||||
<span class="flex flex-col">
|
||||
<span>{option.value}</span>
|
||||
|
||||
Reference in New Issue
Block a user