feat(auth): add remembered local login to the frontend
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import { apiFetch } from "./client";
|
||||
import {
|
||||
captureAuthOperation,
|
||||
isAuthOperationCurrent,
|
||||
requireAuthOperationPrecondition,
|
||||
type AuthOperationPrecondition,
|
||||
} from "../auth/authOperation";
|
||||
import { getSettings } from "./settings";
|
||||
import { getWorkspace, listWorkspaces } from "./workspaces";
|
||||
import {
|
||||
@@ -6,18 +12,20 @@ import {
|
||||
workspacePreferences, type WorkspacePreference,
|
||||
} from "../workspaces/drafts";
|
||||
import type {
|
||||
Principal, ResumeSessionResult, SessionScope, SessionSummary, SessionDocument, UiResponse,
|
||||
AuthenticatedUser, ResumeSessionResult, SessionScope, SessionSummary, SessionDocument, UiResponse,
|
||||
} from "./types";
|
||||
|
||||
type NewSessionInput = { question: string; name?: string } & Partial<WorkspacePreference>;
|
||||
|
||||
async function selectedPreferences(): Promise<WorkspacePreference> {
|
||||
async function selectedPreferences(precondition?: AuthOperationPrecondition): Promise<WorkspacePreference> {
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
const saved = workspacePreferences.load();
|
||||
if (saved.workspaceId && saved.provider && saved.model && saved.thinking) return saved;
|
||||
|
||||
// A direct new-session entry point can run before the composer has mounted. Seed current
|
||||
// application memory from the installation defaults once, then keep choices ephemeral.
|
||||
const legacy = await getSettings();
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
return workspacePreferences.save({
|
||||
workspaceId: saved.workspaceId ?? legacy.workspace,
|
||||
provider: saved.provider ?? legacy.provider,
|
||||
@@ -44,8 +52,9 @@ function reconcileWorkspacePolicy(preferences: WorkspacePreference, allowed: rea
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureWorkspaceSelectionPolicy(): Promise<WorkspacePreference> {
|
||||
async function ensureWorkspaceSelectionPolicy(precondition?: AuthOperationPrecondition): Promise<WorkspacePreference> {
|
||||
while (true) {
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
const preferences = workspacePreferences.load();
|
||||
const workspaceId = preferences.workspaceId;
|
||||
if (!workspaceId) return preferences;
|
||||
@@ -54,10 +63,12 @@ async function ensureWorkspaceSelectionPolicy(): Promise<WorkspacePreference> {
|
||||
try {
|
||||
workspaces = await listWorkspaces();
|
||||
} catch {
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
workspacePolicyGate.rejectSummary(workspaceId);
|
||||
throw new WorkspaceSelectionError(WORKSPACE_SUMMARY_ERROR);
|
||||
}
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
const workspace = workspaces.find((candidate) => candidate.id === workspaceId);
|
||||
if (!workspace) {
|
||||
@@ -69,18 +80,22 @@ async function ensureWorkspaceSelectionPolicy(): Promise<WorkspacePreference> {
|
||||
try {
|
||||
installationDefault = (await getSettings()).workspace;
|
||||
} catch {
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
workspacePolicyGate.rejectSummary(workspaceId);
|
||||
throw new WorkspaceSelectionError(WORKSPACE_SUMMARY_ERROR);
|
||||
}
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
const replacement = workspaces.find(
|
||||
(candidate) => candidate.id === installationDefault && candidate.revision,
|
||||
) ?? workspaces.find((candidate) => candidate.revision);
|
||||
if (!replacement) {
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
workspacePolicyGate.reject(workspaceId);
|
||||
throw new WorkspaceSelectionError(WORKSPACE_POLICY_ERROR);
|
||||
}
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
workspacePreferences.save({ ...preferences, workspaceId: replacement.id });
|
||||
continue;
|
||||
}
|
||||
@@ -98,16 +113,19 @@ async function ensureWorkspaceSelectionPolicy(): Promise<WorkspacePreference> {
|
||||
(selection) => ({ kind: "selection" as const, selection }),
|
||||
),
|
||||
]);
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
if (outcome.kind === "selection") {
|
||||
if (outcome.selection.workspaceId !== workspaceId) continue;
|
||||
return outcome.selection;
|
||||
}
|
||||
if (outcome.kind === "error") {
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
workspacePolicyGate.reject(workspaceId);
|
||||
throw new WorkspaceSelectionError(WORKSPACE_POLICY_ERROR);
|
||||
}
|
||||
const { record } = outcome;
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
let selection: WorkspacePreference;
|
||||
try {
|
||||
@@ -117,17 +135,31 @@ async function ensureWorkspaceSelectionPolicy(): Promise<WorkspacePreference> {
|
||||
record.workspace.llm_policy.default,
|
||||
);
|
||||
} catch (error) {
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
workspacePolicyGate.reject(workspaceId);
|
||||
throw error;
|
||||
}
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
workspacePolicyGate.resolve(workspaceId);
|
||||
requireAuthOperationPrecondition(precondition);
|
||||
if (workspacePreferences.load().workspaceId === workspaceId) return selection;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSession(i: NewSessionInput) {
|
||||
await selectedPreferences();
|
||||
const selection = await ensureWorkspaceSelectionPolicy();
|
||||
export async function createSession(i: NewSessionInput, precondition?: AuthOperationPrecondition) {
|
||||
const initiatingOperation = captureAuthOperation();
|
||||
const effectivePrecondition = precondition ?? (initiatingOperation ? {
|
||||
operation: initiatingOperation,
|
||||
isCurrent: () => isAuthOperationCurrent(initiatingOperation, {
|
||||
sessionId: null,
|
||||
disposalEpoch: initiatingOperation.disposalEpoch,
|
||||
}),
|
||||
} : undefined);
|
||||
requireAuthOperationPrecondition(effectivePrecondition);
|
||||
await selectedPreferences(effectivePrecondition);
|
||||
requireAuthOperationPrecondition(effectivePrecondition);
|
||||
const selection = await ensureWorkspaceSelectionPolicy(effectivePrecondition);
|
||||
requireAuthOperationPrecondition(effectivePrecondition);
|
||||
return apiFetch<{ id: string }>("/sessions", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ...i, ...selection }),
|
||||
@@ -138,7 +170,8 @@ export async function createSession(i: NewSessionInput) {
|
||||
export const prewarmRuntime = () =>
|
||||
apiFetch<void>("/runtime/prewarm", { method: "POST" });
|
||||
|
||||
export const getMe = () => apiFetch<Principal>("/me");
|
||||
/** Compatibility export for existing shell tests/callers; AuthGate uses the validated auth API. */
|
||||
export const getMe = () => apiFetch<AuthenticatedUser>("/me");
|
||||
|
||||
export const listSessions = (scope: SessionScope = "mine") =>
|
||||
apiFetch<SessionSummary[]>(`/sessions?scope=${scope}`);
|
||||
|
||||
Reference in New Issue
Block a user