Skip to content

Add interactive login#64

Open
ramilamparo wants to merge 8 commits intomainfrom
dev/ram/interactive-login
Open

Add interactive login#64
ramilamparo wants to merge 8 commits intomainfrom
dev/ram/interactive-login

Conversation

@ramilamparo
Copy link
Copy Markdown
Collaborator

@ramilamparo ramilamparo commented Apr 8, 2026

Summary

Adds interactive qasphere auth commands for login, logout, and status — replacing the need to manually manage API keys and environment variables.

Login methods

  • Device Authorization Grant (RFC 8628) — default flow. Opens a browser to the QA Sphere verification page, displays a user code, and polls until the user approves. No secrets typed into the terminal.
  • API Key (--api-key flag) — prompts for team name and API key directly (hidden input).

Credential storage

Credentials are persisted in priority order:

  1. System keyring (@napi-rs/keyring) — preferred, used when the OS keyring daemon is available
  2. File fallback (~/.config/qasphere/credentials.json) — restricted permissions (0600), used when keyring is unavailable

Credential resolution

resolveCredentialSource() checks sources in priority order:

  1. QAS_TOKEN + QAS_URL environment variables
  2. .env file in cwd
  3. System keyring
  4. ~/.config/qasphere/credentials.json
  5. .qaspherecli file (searched up directory tree)

Auth subcommands

Command Description
qasphere auth login Device authorization flow (browser-based)
qasphere auth login --api-key Direct API key entry
qasphere auth status Shows current auth source and validates the API key
qasphere auth logout Clears keyring or file credentials; warns if env-based credentials remain

@claude

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

@claude
Copy link
Copy Markdown

claude bot commented Apr 8, 2026

Code Review: Add interactive login

  • Read review instructions
  • Gather full PR diff and context
  • Run specialized review subagents (code quality, security, performance, test coverage)
  • Aggregate findings and post review with inline comments
  • Clean up outdated bot comments and threads

Running specialized review subagents...

View job run

@claude
Copy link
Copy Markdown

claude bot commented Apr 8, 2026

Claude finished @ramilamparo's task in 5m 56s —— View job


Code Review: Add interactive login

Solid implementation of device authorization grant (RFC 8628) with keyring/file credential storage. The architecture is clean — well-separated API layer, credential management, and CLI commands. The test suite is comprehensive with good MSW-based coverage. A few issues worth addressing:


Security

1. Server-returned redirectUrl used as base URL without origin validationsrc/api/deviceAuth.ts:44-45

checkTenant() extracts .origin from the server-returned redirectUrl and uses it as the base URL for all subsequent authenticated requests (requestDeviceCode, pollDeviceToken). If the login service returned a crafted redirect (e.g., http://attacker.com/login), the device code exchange would happen against that server.

Fix: validate the origin — at minimum enforce https: protocol:

const origin = new URL(data.redirectUrl).origin
if (!origin.startsWith('https://')) {
    throw new Error(`Unexpected non-HTTPS tenant URL: ${origin}`)
}

2. CONFIG_DIR created with default umasksrc/utils/credentials.ts:63

mkdirSync(CONFIG_DIR, { recursive: true }) inherits the process umask, so the directory could be world-readable (0o755). Since it stores credentials:

mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })

Code Quality

3. Type-only imports should use import typesrc/commands/auth.ts:6,12

CredentialSource (line 6) and DeviceCodeResponse (line 12) are used only as type annotations. For consistency with the rest of the codebase (see env.ts which uses type StoredCredentials, type CredentialSource):

import { saveCredentials, clearCredentials, type CredentialSource } from '../utils/credentials'
// ...
import { checkTenant, requestDeviceCode, pollDeviceToken, type DeviceCodeResponse } from '../api/deviceAuth'

4. Duplicated User-Agent headersrc/api/deviceAuth.ts:38,52,64

The header is manually specified in all three exported functions. Bake it into createFetcher:

const createFetcher = (baseUrl: string) => {
    const fetcher = withHttpRetry(withJson(withBaseUrl(fetch, baseUrl)))
    return (url: string, init?: RequestInit) =>
        fetcher(url, {
            ...init,
            headers: { 'User-Agent': `qas-cli/${CLI_VERSION}`, ...init?.headers },
        })
}

Test Coverage Gaps

5. pollDeviceToken error during polling untestedsrc/commands/auth.ts:132-137

The catch block for pollDeviceToken throwing during the polling loop is never exercised. Add a test where /api/auth/device/token returns 500 and assert "Authorization failed".

6. Keyring setPassword failure fallback untestedsrc/utils/credentials.ts:54-59

There's no test for a keyring that loads successfully (module import + Entry construction work) but setPassword throws. This silent fallback to file is a distinct code path from "keyring unavailable."


@claude

This comment was marked as outdated.

claude[bot]

This comment was marked as outdated.

@claude

This comment was marked as outdated.

claude[bot]

This comment was marked as outdated.

@ramilamparo ramilamparo requested a review from a team April 9, 2026 13:40
@ramilamparo ramilamparo force-pushed the dev/ram/interactive-login branch from 8908deb to 6b05689 Compare April 9, 2026 14:10
@claude

This comment was marked as outdated.

claude[bot]

This comment was marked as outdated.

@claude

This comment was marked as outdated.

Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Clean implementation. The latest commits have addressed all prior review findings (tenantUrl origin extraction, DRY warning constant, type-only imports for CredentialSource/DeviceCodeResponse, middleware composition for User-Agent). The architecture is well-separated — credential storage with keyring/file fallback, composable fetch middlewares, and comprehensive E2E tests with proper isolation via vi.doMock/resetModules.

One minor import type nit in an inline comment — otherwise LGTM.

@claude
Copy link
Copy Markdown

claude bot commented Apr 9, 2026

Claude finished @ramilamparo's task in 7m 3s —— View job


Code Review: Add interactive login

  • Read review instructions
  • Gather full PR diff and context
  • Run specialized review subagents (code quality, security, performance, test coverage)
  • Aggregate findings and post review with inline comments
  • Clean up outdated bot comments and threads

Ran four specialized subagents (code quality, security, performance, test coverage) against the full diff. All prior review findings have been addressed in the latest commits — import type for Argv/CommandModule fixed, middleware composition refactored, type-only imports applied throughout. No new noteworthy issues found beyond what was already flagged in earlier reviews.

Minimized 5 outdated bot review summaries. Could not delete progress tracking comments due to insufficient permissions.

Verdict: LGTM — clean implementation, well-structured architecture, comprehensive test suite.

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