mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 18:30:39 +09:00
Add browser-based board CLI auth flow
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
1376fc8f44
commit
37c2c4acc4
31 changed files with 13299 additions and 19 deletions
114
cli/src/commands/client/auth.ts
Normal file
114
cli/src/commands/client/auth.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import type { Command } from "commander";
|
||||
import {
|
||||
getStoredBoardCredential,
|
||||
loginBoardCli,
|
||||
removeStoredBoardCredential,
|
||||
revokeStoredBoardCredential,
|
||||
} from "../../client/board-auth.js";
|
||||
import {
|
||||
addCommonClientOptions,
|
||||
handleCommandError,
|
||||
printOutput,
|
||||
resolveCommandContext,
|
||||
type BaseClientOptions,
|
||||
} from "./common.js";
|
||||
|
||||
interface AuthLoginOptions extends BaseClientOptions {
|
||||
instanceAdmin?: boolean;
|
||||
}
|
||||
|
||||
interface AuthLogoutOptions extends BaseClientOptions {}
|
||||
interface AuthWhoamiOptions extends BaseClientOptions {}
|
||||
|
||||
export function registerClientAuthCommands(auth: Command): void {
|
||||
addCommonClientOptions(
|
||||
auth
|
||||
.command("login")
|
||||
.description("Authenticate the CLI for board-user access")
|
||||
.option("-C, --company-id <id>", "Request access for a specific company")
|
||||
.option("--instance-admin", "Request instance-admin approval instead of plain board access", false)
|
||||
.action(async (opts: AuthLoginOptions) => {
|
||||
try {
|
||||
const ctx = resolveCommandContext(opts);
|
||||
const login = await loginBoardCli({
|
||||
apiBase: ctx.api.apiBase,
|
||||
requestedAccess: opts.instanceAdmin ? "instance_admin_required" : "board",
|
||||
requestedCompanyId: ctx.companyId ?? null,
|
||||
command: "paperclipai auth login",
|
||||
});
|
||||
printOutput(
|
||||
{
|
||||
ok: true,
|
||||
apiBase: ctx.api.apiBase,
|
||||
userId: login.userId ?? null,
|
||||
approvalUrl: login.approvalUrl,
|
||||
},
|
||||
{ json: ctx.json },
|
||||
);
|
||||
} catch (err) {
|
||||
handleCommandError(err);
|
||||
}
|
||||
}),
|
||||
{ includeCompany: true },
|
||||
);
|
||||
|
||||
addCommonClientOptions(
|
||||
auth
|
||||
.command("logout")
|
||||
.description("Remove the stored board-user credential for this API base")
|
||||
.action(async (opts: AuthLogoutOptions) => {
|
||||
try {
|
||||
const ctx = resolveCommandContext(opts);
|
||||
const credential = getStoredBoardCredential(ctx.api.apiBase);
|
||||
if (!credential) {
|
||||
printOutput({ ok: true, apiBase: ctx.api.apiBase, revoked: false, removedLocalCredential: false }, { json: ctx.json });
|
||||
return;
|
||||
}
|
||||
let revoked = false;
|
||||
try {
|
||||
await revokeStoredBoardCredential({
|
||||
apiBase: ctx.api.apiBase,
|
||||
token: credential.token,
|
||||
});
|
||||
revoked = true;
|
||||
} catch {
|
||||
// Remove the local credential even if the server-side revoke fails.
|
||||
}
|
||||
const removedLocalCredential = removeStoredBoardCredential(ctx.api.apiBase);
|
||||
printOutput(
|
||||
{
|
||||
ok: true,
|
||||
apiBase: ctx.api.apiBase,
|
||||
revoked,
|
||||
removedLocalCredential,
|
||||
},
|
||||
{ json: ctx.json },
|
||||
);
|
||||
} catch (err) {
|
||||
handleCommandError(err);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
addCommonClientOptions(
|
||||
auth
|
||||
.command("whoami")
|
||||
.description("Show the current board-user identity for this API base")
|
||||
.action(async (opts: AuthWhoamiOptions) => {
|
||||
try {
|
||||
const ctx = resolveCommandContext(opts);
|
||||
const me = await ctx.api.get<{
|
||||
user: { id: string; name: string; email: string } | null;
|
||||
userId: string;
|
||||
isInstanceAdmin: boolean;
|
||||
companyIds: string[];
|
||||
source: string;
|
||||
keyId: string | null;
|
||||
}>("/api/cli-auth/me");
|
||||
printOutput(me, { json: ctx.json });
|
||||
} catch (err) {
|
||||
handleCommandError(err);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue