mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
feat(mcp): allow minting and consenting to health:write connector tokens
Let a user opt a connector token into logging. The settings card gains a write toggle; the mint endpoint accepts a closed scope choice (read, or read_write) and builds the permission array explicitly — it can mint only [health:read] or [health:read, health:write], never a wildcard or any other grant. The OAuth flow grants health:write only when a client requests it, and the consent screen states in plain language that the app will be able to read AND log/write health data when write is requested; the token endpoint grants exactly the approved scopes and never widens. Read-only behaviour is unchanged. Add the English and German strings for the new toggle and warning.
This commit is contained in:
1 parent
6de61de010
commit
ddfe9170bd
11 files changed
+195
-19
No files matched your search
@@ -257,6 +257,7 @@ function McpTokensCard() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
const [newName, setNewName] = useState("");
|
||||
const [allowWrite, setAllowWrite] = useState(false);
|
||||
const [newToken, setNewToken] = useState<string | null>(null);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [tokenMsg, setTokenMsg] = useState<string | null>(null);
|
||||
@@ -277,12 +278,17 @@ function McpTokensCard() {
|
||||
const res = await apiFetchRaw("/api/mcp/tokens", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: newName.trim() }),
|
||||
body: JSON.stringify({
|
||||
name: newName.trim(),
|
||||
// Closed choice — read-only, or read + the audience-bound write scope.
|
||||
scope: allowWrite ? "read_write" : "read",
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (res.ok) {
|
||||
setNewToken(json.data.token);
|
||||
setNewName("");
|
||||
setAllowWrite(false);
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.mcpTokens() });
|
||||
} else {
|
||||
setTokenMsg(json.error || t("common.error"));
|
||||
@@ -336,6 +342,22 @@ function McpTokensCard() {
|
||||
{t("settings.mcp.scopeNote")}
|
||||
</p>
|
||||
|
||||
<div className="border-border bg-muted/30 space-y-2 rounded-lg border p-3">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<span className="text-sm font-medium">
|
||||
{t("settings.mcp.writeScopeToggleLabel")}
|
||||
</span>
|
||||
<Switch
|
||||
checked={allowWrite}
|
||||
onCheckedChange={setAllowWrite}
|
||||
aria-label={t("settings.mcp.writeScopeToggleLabel")}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-[11px] leading-relaxed">
|
||||
{t("settings.mcp.writeScopeNote")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleCreate} className="flex items-center gap-2">
|
||||
<Input
|
||||
value={newName}
|
||||
|
||||
Reference in new issue
Block a user