@blueforge-studio/fauth

forge-auth CLI — sign in with the device flow and inspect your forge-auth identity

Informations sur le paquet

Latest Version0.4.0
Licensenone
Keywords-
Repository-
Author-
Homepage-
Bugs-

Versions

VersionPubliée
0.4.02026-10-13
0.3.02026-10-13
0.2.02026-09-25

Chaîne d'approvisionnement par version

VersionSigSBOMAtt
0.4.0
0.3.0
0.2.0

Cliquez sur une version pour voir ses détails.

Chaîne d'approvisionnement

Provenance

Publié par
web:14
Quand
2026-10-13T11:06:02Z
sha256 (calculé par le registre)
45eb560c1e123858f0ff2290caa2de7e6d6b54a9659eb858bb3a1ce1474756d3

Observé par le registre au moment de la publication.

Déclaré par la compilation

L'éditeur n'a rien déclaré sur sa compilation.

Pièces jointes

Signature
aucune
SBOM
aucune

Dépendances

Chargement du graphe de dépendances...

README

fauth — forge-auth CLI

fauth is the forge-auth member of the federation CLI family, alongside fgit (forge-git), forge (forge-control) and packr-cli. It signs in with forge-auth's device flow and tells you who you are, so you no longer need to read the production database to find your own user id.

fauth login                                # device flow for app "fauth": open the URL, enter the code
fauth whoami                               # user id, email, name, org memberships
fauth whoami --json
fauth orgs list | show <idOrSlug> | members <idOrSlug>
fauth orgs create --name "Acme" --slug acme --yes
fauth logout                               # revokes the key on the server, deletes the local file

Commands

CommandWhat it does
fauth login [--app-id <id or slug>]Runs the device flow (POST /api/auth/device, then polls /api/auth/device/token). The app defaults to the slug fauth (FAUTH_APP_ID overrides). Prints the verification URL and code, waits for browser approval, then stores the minted key.
fauth login --admin --email <e> [--app <slug>] [--days <n>] [--json]Agentic login with no browser and no prompts; see Agentic login.
fauth admin device-approve <code> --as <email or id> [--json]Approves someone else's waiting device login (a host with no browser) as that existing user, using the admin key (same sources as --admin). The waiting login then gets its key, which carries the user's own scopes.
fauth admin service-account-delete <id> [--json]Deletes a service account by id with the admin key (same sources as --admin). The server answers 204 even for an unknown id, so a repeat is a no-op; it does not say whether the id existed.
fauth admin apps create --name <n> --slug <s> [--redirect <url>]... [--webhook-url <u>] [--show-secret] [--json]Registers an app with the admin key. Idempotent: if the slug exists it prints the existing app (id included) and exits 0, changing nothing (a differing --redirect set only prints a note). The generated webhook secret is never printed unless --show-secret.
fauth admin apps show <slug> [--json]Prints an app's non-secret fields (id, slug, name, enabled, redirect URLs, webhook URL) so consumers can set e.g. FORGE_AUTH_APP_ID without hand-copying. Exits 2 for an unknown slug.
fauth admin mint-key --email <e> --app <idOrSlug> --scopes <a,b> [--days N] [--name n] (--print-key | --out <file>) [--json]Admin-mints a user-bound key with explicit scopes (the server rejects unknown ones; admin mint may grant any catalogue scope). Unlike login --admin it does not touch your stored login. The key is shown once: --print-key writes only the key to stdout (summary on stderr), --out writes a new mode-0600 file and refuses to overwrite.
fauth login --key-stdinStores an existing user-bound fk_… key read from stdin (for example fauth login --key-stdin < file). The key is checked before it is saved, and the command refuses to read from a terminal.
fauth logoutRevokes the stored key on the server (DELETE /api/v1/me/key), then deletes the local file. If revocation fails, the file is still deleted and a warning says the key may still be valid.
fauth whoami [--json]User id, email, name, and org memberships (org id, slug, name, role) plus the default org.
fauth orgs list [--json]The organizations you belong to.
fauth orgs show <idOrSlug> [--json]One organization you belong to (for non-members it answers exactly as if the org didn't exist).
fauth orgs members <idOrSlug> [--json]Its members (user id, email, name, role).
fauth orgs create --name <n> --slug <s> --yes [--json]Creates an org with you as owner. Without --yes, prints what would be created and exits 2. A taken slug exits 1.

Global option: --base-url <url> (env FAUTH_BASE_URL), default https://forge-auth.blueforge.studio. The URL must use https; plain http is allowed only for localhost.

Exit codes

CodeMeaning
0ok
1error (network, server, unexpected response, credential file I/O, slug taken, org not found)
2usage (bad flags or arguments, unknown app, base-URL mismatch, orgs create without --yes)
3not logged in (no credential, or the server rejected it)

Agentic login

Use this when an agent or script needs to be the operator without a browser. It follows the same pattern as fgit and packr: a secret in ~/.forge-secrets, one command, no TTY. The device flow is still the path for humans.

fauth login --admin --email you@example.com            # app "fauth", key valid for 30 days
fauth login --admin --email you@example.com --days 7 --json
FAUTH_EMAIL=you@example.com fauth login --admin --app fauth

Steps:

  1. Reads the forge-auth admin API key (the server's FORGE_GIT_AUTH_ADMIN_KEY), trying these sources in order:

    1. the FAUTH_ADMIN_KEY env var
    2. ~/.forge-secrets/forge-auth-admin.key, which must be mode 0600. fauth refuses a group- or world-readable file and prints a chmod 600 hint.
    3. on macOS, the keychain item forge-auth-admin-key (security find-generic-password -s forge-auth-admin-key -w)

    The key is never read from argv.

  2. Calls POST /api/admin/users/keys with { email, app, expiresInDays }. The server looks up the existing user by email (case-insensitive; it never creates users) and mints a key tied to that user and app, carrying the user's own scope set (user.scopes), as a device-flow key would. It uses the same code path as the device flow, so every /api/v1 route accepts the key the same way.

  3. Checks the new key with GET /api/v1/me. If that fails, fauth revokes the key and saves nothing.

  4. Saves the key to ~/.forge-secrets/forge-auth.json (0600) in the same form as the device flow, plus expiresAt, then prints whoami (or --json).

The email comes from --email, otherwise FAUTH_EMAIL, otherwise the command exits 2. --days accepts 1–90 and defaults to 30.

Security. The admin key can mint a key for any user. Keep it only in ~/.forge-secrets/forge-auth-admin.key (mode 0600) or the keychain. Never commit it, paste it into a chat, or pass it as a command-line argument. fauth never prints the admin key or the minted key. Each mint writes an audit line on the server (forge_auth.admin.user_key_minted: target user, app, key id, expiry; never the key), and fauth logout revokes the minted key.

Storing the admin key once, without it touching argv or shell history:

pbpaste > ~/.forge-secrets/forge-auth-admin.key && chmod 600 ~/.forge-secrets/forge-auth-admin.key
# or: security add-generic-password -s forge-auth-admin-key -a "$USER" -w   (prompts for the value)

Credentials

  • The credential is stored in ~/.forge-secrets/forge-auth.json with mode 0600, inside a 0700 directory. It is written to a temp file and then renamed into place. FORGE_SECRETS_DIR overrides the directory.
  • The file holds { baseUrl, token, appId, userId, email, savedAt }. The token is never printed.
  • A stored key is only ever sent to the baseUrl it was issued by. If --base-url names a different server, the command exits with 2 and makes no request.
  • FAUTH_TOKEN (env) takes precedence over the file. This matches how FORGE_GIT_TOKEN works for fgit.
  • Secrets never go in argv. The key is sent only in a JSON body (/api/v1/users/resolve) or an Authorization: Bearer header, never in a URL.
  • If the file is group- or world-readable, fauth prints a warning.

Before first login: register the fauth app (once)

forge-auth's device flow issues its key for a registered app (POST /api/auth/device { appId }). appId can be the app's id or its slug, and an unknown app returns 404 APP_NOT_FOUND. fauth login defaults to the slug fauth, so an admin registers that app once (safe to repeat):

fauth admin apps create --name "fauth CLI" --slug fauth
fauth admin apps show fauth        # prints the app_… id

Until then, pass any existing app id or slug with --app-id. The key is bound to its user, and the /api/v1 routes don't care which app it came from.

Server endpoints

The /api/v1 routes authenticate with Authorization: Bearer fk_…, and the admin mint uses X-Admin-Api-Key. The key must be user-bound (an app/admin key with no user gets 401) and unexpired (expires_at is honored). api_key rows have no scopes yet, so there is no scope check: a user-bound key acts as its user for these self-service routes. When key scopes land, the routes map to the scopes in the table. Every route is rate-limited in memory per key.

EndpointScope (future)LimitAnswers
GET /api/v1/meread:user120/min{ user: {id,email,name}, memberships, activeOrganizationId, organizationsEnabled, membershipsTruncated }; flag off → memberships: []
DELETE /api/v1/me/keywrite:user10/minrevokes exactly the presented key → { revoked }
GET /api/v1/orgsread:organization120/minthe caller's memberships (same claims as get-session)
POST /api/v1/orgs {name, slug}write:organization5/min201 via auth.api.createOrganization (caller = owner); 409 SLUG_TAKEN; 403 for guests or at the org limit; 404 ORGANIZATIONS_DISABLED when the flag is off
POST /api/admin/users/keys {email, app?, name?, expiresInDays?, scopes?}X-Admin-Api-Key (not a user key)20/min201 { key, keyId, userId, email, appId, expiresAt, scopes } (the key is shown once; scopes omitted → the user's row); 404 USER_NOT_FOUND/APP_NOT_FOUND, 403 APP_DISABLED, 400 bad input, 401 bad admin key. Audited.
GET /api/admin/apps, POST /api/admin/apps {name, slug, redirectUrls?, webhookUrl?, includeSecret?}X-Admin-Api-Keylist apps; 201 { app } (webhook secret only with includeSecret: true); 400 on a duplicate slug.
POST /api/admin/device/approve {code, user}X-Admin-Api-Key20/min200 { ok, userId, email, appId }; 404 USER_NOT_FOUND/DEVICE_REQUEST_NOT_FOUND, 409 ALREADY_APPROVED, 400, 401. Audited.
GET /api/v1/orgs/:idOrSlugread:organization120/minorg + members, members only; a non-member gets the same 404 as a missing org

If a server predates these routes, whoami falls back to POST /api/v1/users/resolve (id + email only), and the orgs commands report that the server needs upgrading. An admin user lookup (fauth users find) is not built yet.

Build, install, publish

This directory is not a pnpm workspace member. commander and esbuild are devDependencies of the forge-auth root, and the bundle includes everything, so the published package has no runtime dependencies.

# from the forge-auth repo root
pnpm install
pnpm fauth:bundle                     # → packages/fauth/dist/fauth.mjs
pnpm fauth whoami                     # or run from source via tsx

# install the local build globally (before it is on packr)
cd packages/fauth && npm pack --pack-destination /tmp && npm install -g /tmp/blueforge-studio-fauth-$(node -p "require('./package.json').version").tgz

To publish to packr (operator only; not automated yet):

pnpm fauth:bundle
cd packages/fauth
npm view @blueforge-studio/fauth@$(node -p "require('./package.json').version") --registry https://api.packr.blueforge.studio/ version \
  && echo "already published — bump the version first"      # packr answers 409 to an overwrite
npm publish --registry https://api.packr.blueforge.studio/   # auth: PACKR_TOKEN via ~/.npmrc
# consumers: npm install -g @blueforge-studio/fauth --registry https://api.packr.blueforge.studio/

Never probe packr with curl -X PUT, because an empty PUT creates an empty version. The same rule is documented in fgit's scripts/publish-fgit.sh.