Skip to content

feat: add verify-login command for headless AuthKit loop verification#190

Closed
nicknisi wants to merge 1 commit into
nicknisi/env-provision-login-safetyfrom
nicknisi/verify-login
Closed

feat: add verify-login command for headless AuthKit loop verification#190
nicknisi wants to merge 1 commit into
nicknisi/env-provision-login-safetyfrom
nicknisi/verify-login

Conversation

@nicknisi

@nicknisi nicknisi commented Jul 8, 2026

Copy link
Copy Markdown
Member

What

workos verify-login proves the AuthKit login loop works end-to-end without a browser: it creates a throwaway user, authenticates via the SDK password grant (authenticateWithPassword), asserts the returned tokens, and deletes the user in a finally block so cleanup happens even on failure. Human and --json output; exit codes follow the CLI's gh-style convention.

Production refusal is unconditional: the command refuses to run when the active environment is production-type or the API key has the sk_live_ prefix, checked before any SDK call. There is deliberately no override flag — the command creates and deletes real users.

Why

From the friction log (Part 2, full-agent run): the agent could scaffold the whole integration but had no way to verify the post-login flow headlessly, so "did login actually work?" required a human with a browser. The SDK password grant makes this buildable today with zero backend work.

Testing

  • Test-first with 13 spec cases in verify-login.spec.ts (mocked SDK): happy path, token assertion failure, cleanup-on-failure, production refusal both ways, JSON mode.
  • Full gate green (2,310 tests at the top of the stack).
  • Live end-to-end smoke test against the real API confirmed the create → authenticate → cleanup loop works.

Stack 6/7 (akshay-friction-log): merge after #189, then rebase onto main.

Add `workos verify-login`, a top-level resource command that proves the
AuthKit login loop end-to-end against the active environment with no browser
and no email/OTP retrieval, closing the last agent-oriented gap in the
friction log (an autonomous agent could not self-verify the post-login flow).

The command runs a full round-trip against the bundled @workos-inc/node SDK:
createUser (throwaway, emailVerified) -> authenticateWithPassword -> assert
access + refresh tokens -> deleteUser in a finally block so cleanup always
runs, even on auth failure. A failed cleanup is surfaced machine-readably
(orphanedUserId + userCleanedUp:false) rather than swallowed, and does not
change the exit code.

Two unconditional safety rules (no override flag): production refusal on both
env.type === 'production' and an sk_live_ key prefix, before any SDK call; and
always-attempt cleanup. Output has human (checklist + verdict) and JSON
(flat verdict object) modes driven by the global --json flag and non-TTY
auto-detection. Auth-required exits 4 via resolveApiKey; refusal / missing
client id emit the standard error envelope and exit 1; a failed verification
emits the verdict with success:false and exits 1.

The --method magic-auth stretch is not shipped: its gate (empirical staging
confirmation that createMagicAuth returns the code in the API response) cannot
be exercised in this headless run, so per the spec the flag is dropped and
password grant remains the only shipped method.

Resolves friction-log goal 10.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Open in Devin Review

Comment thread src/bin.ts
Comment on lines +600 to +640
.command(
'verify-login',
'Verify the AuthKit login loop end-to-end against the active environment (creates and deletes a throwaway user)',
(yargs) =>
yargs.options({
...insecureStorageOption,
'api-key': {
type: 'string' as const,
describe:
'WorkOS API key (overrides environment config). Format: sk_test_* (production keys are refused)',
},
'client-id': {
type: 'string' as const,
describe: 'WorkOS client ID (overrides the active environment)',
},
method: {
type: 'string' as const,
choices: ['password'] as const,
default: 'password',
describe: 'Authentication method to verify',
},
}),
async (argv) => {
await applyInsecureStorage(argv.insecureStorage as boolean | undefined);
const { resolveApiKey, resolveApiBaseUrl } = await import('./lib/api-key.js');
const { getActiveEnvironment } = await import('./lib/config-store.js');
const { runVerifyLogin } = await import('./commands/verify-login.js');

const apiKey = resolveApiKey({ apiKey: argv.apiKey as string | undefined }); // exits 4 if none
const activeEnv = getActiveEnvironment();

await runVerifyLogin({
apiKey,
clientId: (argv.clientId as string | undefined) ?? activeEnv?.clientId,
baseUrl: resolveApiBaseUrl(),
envType: activeEnv?.type ?? null,
envName: activeEnv?.name,
method: argv.method as VerifyLoginMethod,
});
},
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 verify-login is not excluded from the unclaimed-environment warning middleware

The maybeWarnUnclaimed middleware in src/bin.ts:316-332 has an exclusion list for commands like auth, doctor, env, install, etc. The new verify-login command is NOT in this list, meaning it will show the unclaimed-environment warning when applicable. This seems correct — verify-login is a management command that needs a properly configured (claimed) environment to function, so the warning is useful. However, if the intent is for verify-login to work against unclaimed environments (since it has its own envType handling), the warning could be confusing noise.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a headless verify-login command for checking the AuthKit login loop. The main changes are:

  • A new CLI command that creates a throwaway user and authenticates it with the password grant.
  • Production refusal before SDK calls using the active environment type and sk_live_ API key prefix.
  • Token presence checks, JSON/human output, and cleanup in a finally block.
  • Tests for success, failure, cleanup, production refusal, missing client IDs, and JSON output.
  • Structured help JSON updates for the new command.

Confidence Score: 5/5

Safe to merge with minimal risk.

The changed flow is covered by focused tests, refuses production before SDK calls, avoids logging generated passwords or returned tokens, and performs cleanup in finally.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the narrowed verify-login spec run and confirmed exit code 0 with 42/42 tests passing, including happy path, error handling, cleanup, production-refusal, and JSON cases.
  • Verified the build completed successfully with pnpm build exiting 0 after pnpm tsc and postbuild chmod/copy steps.
  • Inspected the verify-login CLI help output; node dist/bin.js verify-login --help exited 0 and displayed the verify-login help surface.
  • Inspected the JSON help output for verify-login; node dist/bin.js verify-login --help --json exited 0 and returned JSON listing options such as insecure-storage, api-key, client-id, and method.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/bin.ts Registers the new verify-login command, resolves API key/environment context, and dynamically loads the handler.
src/commands/verify-login.ts Adds the headless AuthKit login verification flow with production refusal, token checks, structured output, and cleanup in finally.
src/commands/verify-login.spec.ts Adds mocked SDK coverage for success, failures, cleanup behavior, production refusal, missing client IDs, and JSON output.
src/utils/help-json.ts Adds verify-login to the structured help schema with its options.
src/utils/help-json.spec.ts Updates help JSON expectations to include the new command.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User as CLI user
participant Bin as src/bin.ts
participant Verify as runVerifyLogin
participant SDK as WorkOS SDK

User->>Bin: workos verify-login [options]
Bin->>Bin: resolve API key, base URL, active env
Bin->>Verify: apiKey, clientId, envType, envName, method
Verify->>Verify: refuse production env or sk_live_ key
Verify->>Verify: generate throwaway email + password
Verify->>SDK: createUser(email, password, emailVerified)
SDK-->>Verify: user.id
Verify->>SDK: authenticateWithPassword(clientId, email, password)
SDK-->>Verify: accessToken, refreshToken
Verify->>Verify: assert token presence
Verify->>SDK: deleteUser(user.id)
SDK-->>Verify: cleanup result
Verify-->>User: human or JSON verdict + exit code
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User as CLI user
participant Bin as src/bin.ts
participant Verify as runVerifyLogin
participant SDK as WorkOS SDK

User->>Bin: workos verify-login [options]
Bin->>Bin: resolve API key, base URL, active env
Bin->>Verify: apiKey, clientId, envType, envName, method
Verify->>Verify: refuse production env or sk_live_ key
Verify->>Verify: generate throwaway email + password
Verify->>SDK: createUser(email, password, emailVerified)
SDK-->>Verify: user.id
Verify->>SDK: authenticateWithPassword(clientId, email, password)
SDK-->>Verify: accessToken, refreshToken
Verify->>Verify: assert token presence
Verify->>SDK: deleteUser(user.id)
SDK-->>Verify: cleanup result
Verify-->>User: human or JSON verdict + exit code
Loading

Reviews (1): Last reviewed commit: "feat: add verify-login command for headl..." | Re-trigger Greptile

@nicknisi

nicknisi commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Closing in favor of a single combined PR from nicknisi/akshay.

@nicknisi nicknisi closed this Jul 8, 2026
@nicknisi nicknisi deleted the nicknisi/verify-login branch July 8, 2026 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant