feat(FN-2521): add remote auth handoff endpoints and login URL flow

- Add remote auth token primitives plus login-url generation for short-lived phone auth handoff
- Expose public handoff API endpoint and wire remote auth handling into dashboard server routes
- Tighten remote access settings update typing to satisfy typecheck and preserve API isolation behavior
- Add comprehensive dashboard tests for remote-auth helpers, route behavior, and server integration
- Document the remote login-url and phone auth handoff contract in architecture docs
This commit is contained in:
Fusion
2026-04-26 04:38:33 -07:00
committed by gsxdsm
parent 8a261ae052
commit a4c219f89b
7 changed files with 843 additions and 69 deletions

View File

@@ -47,6 +47,7 @@ import { ChatManager } from "./chat.js";
import { stopAllDevServers } from "./dev-server-routes.js";
import type { SkillsAdapter } from "./skills-adapter.js";
import { createAuthMiddleware, authenticateUpgradeRequest, getDaemonToken } from "./auth-middleware.js";
import { validateRemoteAuthToken } from "./remote-auth.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -895,6 +896,50 @@ export function createServer(store: TaskStore, options?: ServerOptions): ReturnT
});
});
app.get("/remote-login", async (req, res) => {
const remoteToken = typeof req.query.rt === "string" ? req.query.rt : undefined;
let settings: Awaited<ReturnType<typeof store.getSettings>>;
try {
settings = await store.getSettings();
} catch {
res.status(401).json({ error: "Unauthorized", code: "remote_token_invalid" });
return;
}
const remoteAccess = settings.remoteAccess;
if (!remoteAccess) {
res.status(401).json({ error: "Unauthorized", code: "remote_token_invalid" });
return;
}
const result = validateRemoteAuthToken(remoteToken, remoteAccess);
if (result.status !== "valid") {
const codeByStatus: Record<string, string> = {
missing: "remote_token_missing",
expired: "remote_token_expired",
invalid: "remote_token_invalid",
disabled: "remote_token_invalid",
};
res.status(401).json({
error: "Unauthorized",
code: codeByStatus[result.status] ?? "remote_token_invalid",
});
return;
}
const daemonTokenForRedirect = getDaemonToken(options);
if (daemonTokenForRedirect) {
const redirectUrl = new URL("/", `${req.protocol}://${req.get("host")}`);
redirectUrl.searchParams.set("token", daemonTokenForRedirect);
res.redirect(302, redirectUrl.pathname + redirectUrl.search);
return;
}
res.redirect(302, "/");
});
// REST API
app.use("/api", createApiRoutes(store, {
...options,