Skip to content

feat: device authorization for the CLI - #5

Merged
soorya-u merged 8 commits into
mainfrom
feat/device-authorization
Jun 28, 2026
Merged

soorya-u merged 8 commits into
mainfrom
feat/device-authorization

Conversation

@soorya-u

@soorya-u soorya-u commented Jun 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds OAuth 2.0 Device Authorization (RFC 8628) so the cyrus CLI can sign in via the browser, plus the supporting server endpoints and web verification page.

Server

  • Enable better-auth deviceAuthorization (verification page → web app /auth/device) and the bearer plugin so the CLI authenticates with its session token.
  • Add the device_code drizzle model + WEB_APP_URL env var, and a migration creating the table.
  • Rename migrations to descriptive tags (db_init, device_authorization); route drizzle/auth scripts through dotenvx so they read apps/server/.env.

Web

  • deviceAuthorization client plugin + a /auth/device route where a signed-in user approves/denies a device by entering the code from their terminal (bounces through GitHub sign-in when needed).

CLI

  • login (device-code flow + polling), whoami, and logout commands.
  • Token persisted to a 0600 YAML file ($CYRUS_HOME/config.yml); sent as a bearer token on subsequent calls.
  • Bun-native color/print helper (no chalk), @t3-oss/env-core config, @/* path alias, commands under src/commands.

Verification

  • bun check (Biome) and bun check:types (turbo) both pass.
  • Full flow exercised end-to-end against a server instance: login → approve → whoami → logout.

Notes

  • The interactive GitHub OAuth approval step can't be automated here; the rest of the flow (token exchange, bearer get-session, logout) was verified directly.
  • noUnusedExpressions is disabled for apps/cli only, so the print tagged-template API (print.error\…``) passes lint.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added CLI authentication commands to log in, log out, and show the current signed-in account.
    • Introduced device-code sign-in, including a browser page to approve or deny a device request.
    • Updated the web app and CLI to use the new device authorization flow (and shifted the CLI away from server lifecycle management).
  • Bug Fixes

    • Improved token persistence and validation behavior.
    • Enhanced device-code polling and messaging for pending, slow-down, denied, and expired scenarios.

soorya-u and others added 2 commits June 28, 2026 18:59
Enable better-auth deviceAuthorization (verification page at the web app's
/auth/device) and the bearer plugin so the CLI can authenticate with the
session token. Add the device_code drizzle model, WEB_APP_URL env var, and a
migration creating the table. Rename migrations to descriptive tags and route
drizzle/auth scripts through dotenvx so they read apps/server/.env.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the deviceAuthorization client plugin and a /auth/device route where a
signed-in user enters the code from their terminal to approve or deny a
device, bouncing through GitHub sign-in first when needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cyrus Ready Ready Preview, Comment Jun 28, 2026 4:38pm

@coderabbitai

coderabbitai Bot commented Jun 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@soorya-u, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 28 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b4be174-2923-463c-8f86-c216fb51a7e6

📥 Commits

Reviewing files that changed from the base of the PR and between f215053 and ff72be5.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • mise.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • apps/cli/package.json
  • apps/cli/src/commands/auth/login.ts
  • apps/cli/src/commands/auth/logout.ts
  • apps/cli/src/commands/auth/whoami.ts
  • apps/cli/src/index.ts
  • apps/cli/src/lib/auth.ts
  • apps/cli/src/lib/env.ts
  • apps/cli/src/paths.ts
  • apps/cli/src/serve.ts
  • apps/cli/src/utils/store.ts
  • apps/cli/src/utils/style.ts
  • apps/cli/tsconfig.json
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/auth.device.tsx
  • biome.json
📝 Walkthrough

Walkthrough

Adds device authorization across the server, CLI, and web app. The server gains device auth plugins, a WEB_APP_URL env var, and device_code persistence. The CLI switches to auth commands with token storage. The web app adds /auth/device for approval.

Changes

OAuth Device Authorization Flow

Layer / File(s) Summary
Server schema and migration
apps/server/src/db/models/auth.ts, apps/server/src/db/models/index.ts, apps/server/src/db/migrations/...
Adds the device_code table model, SQL migration, snapshot, journal entry, and model re-export.
Server auth wiring
apps/server/src/auth/index.ts, apps/server/src/config/env.ts, apps/server/package.json
Adds WEB_APP_URL, registers deviceAuthorization and bearer, and wraps auth/database scripts with dotenvx run.
CLI foundation
apps/cli/package.json, apps/cli/tsconfig.json, apps/cli/src/lib/env.ts, apps/cli/src/utils/store.ts, apps/cli/src/utils/style.ts, apps/cli/src/lib/auth.ts, biome.json
Introduces CLI package/bin setup, typed env config, YAML token storage, ANSI helpers, CLI auth client config, and related lint/path settings.
CLI auth commands and entrypoint
apps/cli/src/commands/auth/login.ts, apps/cli/src/commands/auth/logout.ts, apps/cli/src/commands/auth/whoami.ts, apps/cli/src/index.ts
Adds device-code login, logout, and whoami commands, plus the new auth-only CLI entrypoint wiring.
Web device approval route
apps/web/src/lib/auth.ts, apps/web/src/routeTree.gen.ts, apps/web/src/routes/auth.device.tsx
Enables device auth in the web client, registers /auth/device, and implements the approval/deny page.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 A tiny code with a secret sigh,
Then browser hops to approve or deny.
The CLI keeps polling under moonlit snow,
While tokens tuck themselves away below.
Cyrus the rabbit taps, “We’re in!” with glee,
Device auth blooms across the tree 🌳

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: adding device authorization support for the CLI.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/device-authorization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (3)
biome.json (1)

29-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow this ignore to the generated migration metadata path.

!**/meta excludes every meta directory in the repo, not just apps/server/src/db/migrations/meta. That can silently remove unrelated source from linting/formatting later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@biome.json` at line 29, The ignore rule is too broad because `!**/meta`
unignores every `meta` directory in the repo instead of only the generated
migration metadata path. Update the Biome ignore entry to target the specific
migrations metadata location used by the server DB migrations, so only
`apps/server/src/db/migrations/meta` is excluded and unrelated `meta` folders
remain covered.
apps/cli/src/index.ts (1)

13-13: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use parseAsync() with these async Commander actions.

Commander documents that async action handlers should be paired with .parseAsync() rather than .parse(). (github.com)

Suggested change
-program.parse(Bun.argv);
+await program.parseAsync(Bun.argv);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cli/src/index.ts` at line 13, The CLI entrypoint is using program.parse
while it has async Commander actions, so update the program setup in index.ts to
use program.parseAsync instead. Locate the top-level command parsing call on the
Commander program instance and switch it to the async parsing API so async
action handlers are awaited correctly.
apps/cli/src/utils/style.ts (1)

21-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This printer API is what forced the broad noUnusedExpressions waiver.

Using print.dim\...`as an expression statement is whybiome.jsonnow disablessuspicious.noUnusedExpressionsforapps/cli/**. That turns off a useful bug-catching rule across the whole CLI. Prefer callable printers (print.dim("...")`) or scope the override down to the handful of files that truly need tagged templates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cli/src/utils/style.ts` around lines 21 - 42, The current printer API in
style.ts forces tagged-template usage, which is why noUnusedExpressions had to
be broadly waived. Update the printer/print API so the exported helpers like
print.dim, print.success, and print.error are callable functions instead of
tag-only template printers, and adjust any local uses in this module
accordingly. If template support must remain, keep it isolated and avoid
requiring the broad suspicious.noUnusedExpressions override in biome.json.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/cli/src/commands/auth/logout.ts`:
- Around line 5-12: The logout flow in logout() is treating readToken() like a
synchronous guard and also leaves clearToken() outside a failure-safe path.
Await readToken() before deciding whether to return early, and make sure
authClient.signOut() and clearToken() are handled so the local token is always
removed in a finally block even if signOut() fails. Keep the success message
only after the async cleanup completes successfully.

In `@apps/cli/src/commands/auth/whoami.ts`:
- Around line 11-15: In whoami’s session check, avoid calling clearToken() for
every authClient.getSession() error, since transient network/5xx failures should
not wipe a still-valid cached credential. Update the logic around
authClient.getSession() to only clear the token when the response clearly
indicates an invalid/unauthenticated session, and for other errors surface the
error while preserving the token; keep the existing not-logged-in handling in
the whoami command.

In `@apps/cli/src/index.ts`:
- Line 10: The `logout` command is wired to a broken handler in `logout` from
`apps/cli/src/commands/auth/logout.ts`; fix the token check and deletion flow so
it only signs out when a token exists. Update the `logout` function to await
`clearToken()` before returning, and verify the condition around `readToken()`
so it does not always take the sign-out path when no token is present.

In `@apps/cli/src/utils/store.ts`:
- Around line 19-27: The readToken function is collapsing non-ENOENT token-store
failures into a null token by using Result.tryPromise(...).unwrapOr("null"), and
YAML.parse can still throw on bad content. Update readToken in store.ts so only
a missing file returns null, while other file read or parse failures are
propagated as actionable errors; keep the logic around file.exists(),
file.text(), and YAML.parse, but distinguish ENOENT from corruption/read
failures instead of treating them as logged-out state.
- Around line 12-16: The saveToken flow currently writes the token file via
Bun.file.write and then fixes permissions afterward, which leaves a brief window
with unsafe defaults. Update saveToken in store.ts to use a Node fs-based
create/write path that opens or creates TOKEN_FILE_PATH with restrictive 0600
permissions from the start, while keeping the YAML.stringify({ token } satisfies
Stored) content generation intact.

In `@apps/server/package.json`:
- Line 13: The auth:generate script currently writes Better Auth output into the
same auth model file that also contains the custom deviceCode table, which risks
overwriting hand-written schema. Update the package.json script to generate auth
tables into a dedicated file, or move custom tables like deviceCode out of the
generated target, and then make sure src/db/models/index.ts still imports the
correct split model files via the auth/index-related symbols.

In `@apps/server/src/db/migrations/0001_device_authorization.sql`:
- Around line 1-12: The device_code table definition is missing uniqueness
enforcement for the device_code and user_code fields, which can allow duplicate
grants to be resolved incorrectly. Update the migration to add UNIQUE
constraints or unique indexes for these columns, and if expired rows must be
retained then use partial unique indexes instead. Keep the change within the
device_code table migration so the constraints are enforced when device polling
and approval look up grants.

In `@apps/server/src/db/migrations/meta/_journal.json`:
- Around line 9-17: Restore the original idx 0 migration tag in _journal.json so
the initial migration identity stays unchanged; update the journal entry back to
the existing tag used by the first migration and leave the subsequent
0001_device_authorization entry intact. Use the migration metadata in the
journal array (especially the first object’s tag field and idx 0) to locate and
revert only that identity, without changing the rest of the migration sequence.

In `@apps/server/src/db/models/auth.ts`:
- Around line 83-94: In deviceCode, add database constraints for the lookup keys
used by the auth flow: make userCode and deviceCode unique and indexed so web
approvals and CLI polling resolve a single record efficiently. Update the
pgTable("device_code", ...) definition to include UNIQUE/INDEX metadata for
these columns, using the existing deviceCode symbol and the related
userCode/deviceCode fields.

In `@apps/web/src/routes/auth.device.tsx`:
- Around line 79-101: Handle failures from the claim step in decide before the
handler exits, because authClient.$fetch("/device") can reject for
invalid/expired codes or network errors and leave the page stuck in busy state.
Wrap the claim-and-approve/deny flow in try/finally (or equivalent) so
setBusy(false) always runs, and catch the claim-step error to show a toast via
errorMessage instead of letting the exception escape. Use decide,
authClient.$fetch("/device"), and setBusy(false) as the key locations to update.

---

Nitpick comments:
In `@apps/cli/src/index.ts`:
- Line 13: The CLI entrypoint is using program.parse while it has async
Commander actions, so update the program setup in index.ts to use
program.parseAsync instead. Locate the top-level command parsing call on the
Commander program instance and switch it to the async parsing API so async
action handlers are awaited correctly.

In `@apps/cli/src/utils/style.ts`:
- Around line 21-42: The current printer API in style.ts forces tagged-template
usage, which is why noUnusedExpressions had to be broadly waived. Update the
printer/print API so the exported helpers like print.dim, print.success, and
print.error are callable functions instead of tag-only template printers, and
adjust any local uses in this module accordingly. If template support must
remain, keep it isolated and avoid requiring the broad
suspicious.noUnusedExpressions override in biome.json.

In `@biome.json`:
- Line 29: The ignore rule is too broad because `!**/meta` unignores every
`meta` directory in the repo instead of only the generated migration metadata
path. Update the Biome ignore entry to target the specific migrations metadata
location used by the server DB migrations, so only
`apps/server/src/db/migrations/meta` is excluded and unrelated `meta` folders
remain covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 46b31281-8708-4a9a-9977-fb340a931814

📥 Commits

Reviewing files that changed from the base of the PR and between f49aa41 and 98132ac.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • mise.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • apps/cli/package.json
  • apps/cli/src/commands/auth/login.ts
  • apps/cli/src/commands/auth/logout.ts
  • apps/cli/src/commands/auth/whoami.ts
  • apps/cli/src/index.ts
  • apps/cli/src/lib/auth.ts
  • apps/cli/src/lib/env.ts
  • apps/cli/src/paths.ts
  • apps/cli/src/serve.ts
  • apps/cli/src/utils/store.ts
  • apps/cli/src/utils/style.ts
  • apps/cli/tsconfig.json
  • apps/server/package.json
  • apps/server/src/auth/index.ts
  • apps/server/src/config/env.ts
  • apps/server/src/db/migrations/0000_db_init.sql
  • apps/server/src/db/migrations/0001_device_authorization.sql
  • apps/server/src/db/migrations/meta/0001_snapshot.json
  • apps/server/src/db/migrations/meta/_journal.json
  • apps/server/src/db/models/auth.ts
  • apps/server/src/db/models/index.ts
  • apps/web/src/lib/auth.ts
  • apps/web/src/routeTree.gen.ts
  • apps/web/src/routes/auth.device.tsx
  • biome.json
💤 Files with no reviewable changes (2)
  • apps/cli/src/serve.ts
  • apps/cli/src/paths.ts

Comment thread apps/cli/src/commands/auth/logout.ts
Comment thread apps/cli/src/commands/auth/whoami.ts
Comment thread apps/cli/src/index.ts
Comment thread apps/cli/src/utils/store.ts Outdated
Comment thread apps/cli/src/utils/store.ts Outdated
Comment thread apps/server/package.json
Comment thread apps/server/src/db/migrations/0001_device_authorization.sql
Comment thread apps/server/src/db/migrations/meta/_journal.json
Comment thread apps/server/src/db/models/auth.ts
Comment thread apps/web/src/routes/auth.device.tsx
soorya-u added a commit that referenced this pull request Jun 28, 2026
- logout: await readToken and always clear the local token in finally so a
  valid bearer token never lingers after a successful sign-out message
- whoami: only clear the token on an empty session, not on transient/server
  errors that would otherwise force an unnecessary relogin
- store: create the token file with 0600 atomically (Bun.write can't set the
  mode), and stop swallowing read/parse errors as a logged-out state — only a
  missing file maps to null

Addresses CodeRabbit review on #5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
soorya-u added a commit that referenced this pull request Jun 28, 2026
Wrap the claim/approve/deny calls so setBusy(false) always runs and a rejected
request shows a toast instead of throwing and leaving the buttons disabled.

Addresses CodeRabbit review on #5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/cli/src/utils/store.ts (1)

1-18: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Avoid in-place truncation when saving the token.

writeFile(TOKEN_FILE_PATH, ...) updates the live config in place. If the process dies mid-write, config.yml can be left partial/corrupt and later auth commands will fail until the file is repaired. Write to a 0600 temp file in the same directory and rename it into place after the write succeeds.

Suggested fix
-import { chmod, mkdir, writeFile } from "node:fs/promises";
+import { chmod, mkdir, rename, writeFile } from "node:fs/promises";
+import { randomUUID } from "node:crypto";
+import { join } from "node:path";

 export async function saveToken(token: string) {
-	await mkdir(dirname(TOKEN_FILE_PATH), { recursive: true, mode: 0o700 });
+	const dir = dirname(TOKEN_FILE_PATH);
+	await mkdir(dir, { recursive: true, mode: 0o700 });
 	const content = YAML.stringify({ token } satisfies Stored);
-	await writeFile(TOKEN_FILE_PATH, content, { mode: 0o600 });
+	const tempPath = join(dir, `.config.${randomUUID()}.tmp`);
+	await writeFile(tempPath, content, { mode: 0o600, flag: "wx" });
+	await rename(tempPath, TOKEN_FILE_PATH);
 	await chmod(TOKEN_FILE_PATH, 0o600);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cli/src/utils/store.ts` around lines 1 - 18, The saveToken flow in
store.ts currently writes directly to TOKEN_FILE_PATH via writeFile, which can
leave config.yml partial if the process dies mid-write. Update saveToken to
write the YAML content to a temporary 0600 file in the same directory first,
then rename it into TOKEN_FILE_PATH after the write succeeds; keep the existing
directory creation and permissions handling around TOKEN_FILE_PATH and the
saveToken helper.
♻️ Duplicate comments (1)
apps/cli/src/utils/store.ts (1)

27-28: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate the parsed YAML shape before returning it.

This cast does not enforce { token: string } at runtime. A valid YAML file like token: 123 or token: {} can cross the store boundary as a non-string value, which breaks the token contract instead of surfacing corruption.

Suggested fix
 	const content = await file.text();
-	return (YAML.parse(content) as Stored | null)?.token ?? null;
+	const parsed = YAML.parse(content);
+	if (
+		!parsed ||
+		typeof parsed !== "object" ||
+		!("token" in parsed) ||
+		typeof parsed.token !== "string"
+	) {
+		throw new Error(`Invalid token store format at ${TOKEN_FILE_PATH}`);
+	}
+	return parsed.token;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cli/src/utils/store.ts` around lines 27 - 28, The YAML parsing in
store.ts is only using a type cast, so invalid shapes like a non-string token
can slip through at runtime. Update the logic around the existing YAML.parse
call in the store read helper to explicitly validate that the parsed value is an
object with a string token before returning it, and otherwise return null or
handle corruption; use the store function that reads the token as the place to
enforce this contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/cli/src/utils/store.ts`:
- Around line 1-18: The saveToken flow in store.ts currently writes directly to
TOKEN_FILE_PATH via writeFile, which can leave config.yml partial if the process
dies mid-write. Update saveToken to write the YAML content to a temporary 0600
file in the same directory first, then rename it into TOKEN_FILE_PATH after the
write succeeds; keep the existing directory creation and permissions handling
around TOKEN_FILE_PATH and the saveToken helper.

---

Duplicate comments:
In `@apps/cli/src/utils/store.ts`:
- Around line 27-28: The YAML parsing in store.ts is only using a type cast, so
invalid shapes like a non-string token can slip through at runtime. Update the
logic around the existing YAML.parse call in the store read helper to explicitly
validate that the parsed value is an object with a string token before returning
it, and otherwise return null or handle corruption; use the store function that
reads the token as the place to enforce this contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1d740d86-0f45-442d-936f-fbd7181741b2

📥 Commits

Reviewing files that changed from the base of the PR and between 98132ac and f215053.

📒 Files selected for processing (4)
  • apps/cli/src/commands/auth/logout.ts
  • apps/cli/src/commands/auth/whoami.ts
  • apps/cli/src/utils/store.ts
  • apps/web/src/routes/auth.device.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/cli/src/commands/auth/logout.ts
  • apps/web/src/routes/auth.device.tsx

soorya-u and others added 6 commits June 28, 2026 22:07
Drive the OAuth 2.0 device flow from the CLI: login requests a device code,
polls for the token, and persists it (0600 file via Bun/YAML); whoami and
logout use it as a bearer token. Add a Bun-native color/print helper, env
config (@t3-oss/env-core), and the @/* path alias; organize commands under
src/commands. Disable noUnusedExpressions for apps/cli so print tagged
templates pass lint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- logout: await readToken and always clear the local token in finally so a
  valid bearer token never lingers after a successful sign-out message
- whoami: only clear the token on an empty session, not on transient/server
  errors that would otherwise force an unnecessary relogin
- store: create the token file with 0600 atomically (Bun.write can't set the
  mode), and stop swallowing read/parse errors as a logged-out state — only a
  missing file maps to null

Addresses CodeRabbit review on #5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap the claim/approve/deny calls so setBusy(false) always runs and a rejected
request shows a toast instead of throwing and leaving the buttons disabled.

Addresses CodeRabbit review on #5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the raw `$fetch("/device", …)` escape hatch with the generated typed
client method `authClient.device({ query: { user_code } })` — same GET request,
but type-checked and without a hardcoded path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AuthProvider expects the shared TanStack queryClient; wire it from the
QueryClientProvider via useQueryClient().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — ff72be58 Deployed Jun 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant