AuthClient dev-console + AuthServer deviceFlow via k8s/auth-dev.yaml; otel server accepts its own URL as an extra audience (OTEL_OIDC_AUDIENCE). Vite dev-only device-flow sign-in, scripts/dev-auth.mjs helper, AGENTS.md notes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
94 lines
3.6 KiB
JavaScript
94 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
// Terminal helper for agent/local development against the protected API.
|
|
// Device grant (RFC 8628) against the go-auth issuer. The refresh token is
|
|
// kept in ~/.go-auth/dev/<client>.json (0700 dir, 0600 file). Never commit it.
|
|
//
|
|
// node scripts/dev-auth.mjs login approve once in a browser
|
|
// node scripts/dev-auth.mjs token print a fresh access token
|
|
// node scripts/dev-auth.mjs whoami print the token claims
|
|
// node scripts/dev-auth.mjs forget delete the stored refresh token
|
|
//
|
|
// Env: AUTH_DEV_BASE (issuer, default https://users.k6n.net),
|
|
// AUTH_DEVICE_CLIENT_ID (default dev-console),
|
|
// GO_AUTH_TOKEN (explicit token escape hatch; `token` prints it as-is).
|
|
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
import { homedir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const BASE = (process.env.AUTH_DEV_BASE ?? "https://users.k6n.net").replace(/\/$/, "");
|
|
const CLIENT = process.env.AUTH_DEVICE_CLIENT_ID ?? "dev-console";
|
|
const DIR = join(homedir(), ".go-auth", "dev");
|
|
const FILE = join(DIR, `${CLIENT}.json`);
|
|
const GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
|
|
const post = (path, body) =>
|
|
fetch(`${BASE}/api/auth${path}`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
async function save(refresh) {
|
|
await mkdir(DIR, { recursive: true, mode: 0o700 });
|
|
await chmod(DIR, 0o700);
|
|
const tmp = `${FILE}.tmp`;
|
|
await writeFile(tmp, JSON.stringify({ refresh_token: refresh }), { mode: 0o600 });
|
|
await rename(tmp, FILE);
|
|
}
|
|
|
|
async function login() {
|
|
const start = await post("/device", { client_id: CLIENT });
|
|
if (!start.ok) throw new Error(`device start failed: HTTP ${start.status}`);
|
|
const d = await start.json();
|
|
console.error(`Approve in a browser: ${d.verification_uri_complete ?? d.verification_uri}\nCode: ${d.user_code}`);
|
|
let interval = (d.interval ?? 5) * 1000;
|
|
const deadline = Date.now() + d.expires_in * 1000;
|
|
while (Date.now() < deadline) {
|
|
await new Promise((r) => setTimeout(r, interval));
|
|
const t = await post("/device/token", { grant_type: GRANT, device_code: d.device_code, client_id: CLIENT });
|
|
const body = await t.json().catch(() => ({}));
|
|
if (t.ok) {
|
|
await save(body.refresh_token);
|
|
console.error("Signed in; refresh token stored.");
|
|
return;
|
|
}
|
|
if (body.error === "slow_down") interval += 5000;
|
|
else if (body.error !== "authorization_pending") throw new Error(body.error ?? `HTTP ${t.status}`);
|
|
}
|
|
throw new Error("device code expired");
|
|
}
|
|
|
|
async function token() {
|
|
if (process.env.GO_AUTH_TOKEN) return process.env.GO_AUTH_TOKEN;
|
|
let stored;
|
|
try {
|
|
stored = JSON.parse(await readFile(FILE, "utf8"));
|
|
} catch {
|
|
throw new Error("not signed in: run `node scripts/dev-auth.mjs login`");
|
|
}
|
|
const res = await post("/device/refresh", { client_id: CLIENT, refresh_token: stored.refresh_token });
|
|
if (!res.ok) throw new Error(`refresh failed (HTTP ${res.status}); run login again`);
|
|
const body = await res.json();
|
|
if (body.refresh_token) await save(body.refresh_token);
|
|
return body.access_token;
|
|
}
|
|
|
|
const cmds = {
|
|
login,
|
|
token: async () => console.log(await token()),
|
|
whoami: async () => {
|
|
const t = await token();
|
|
console.log(JSON.stringify(JSON.parse(Buffer.from(t.split(".")[1], "base64url").toString()), null, 2));
|
|
},
|
|
forget: () => rm(FILE, { force: true }),
|
|
};
|
|
const cmd = cmds[process.argv[2]];
|
|
if (!cmd) {
|
|
console.error("usage: dev-auth.mjs login|token|whoami|forget");
|
|
process.exit(2);
|
|
}
|
|
cmd().catch((e) => {
|
|
console.error(e.message);
|
|
process.exit(1);
|
|
});
|