Skip to content

feat: Add connection test command for SSO login flow#177

Open
jonatascastro12 wants to merge 5 commits into
mainfrom
devin/1781191038-connection-test
Open

feat: Add connection test command for SSO login flow#177
jonatascastro12 wants to merge 5 commits into
mainfrom
devin/1781191038-connection-test

Conversation

@jonatascastro12

Copy link
Copy Markdown
Contributor

Summary

Adds workos connection test <id> — an end-to-end SSO smoke test for a connection, run entirely from the CLI:

  1. Registers http://localhost:<port>/callback as an environment redirect URI automatically via the existing POST /user_management/redirect_uris wrapper (client.redirectUris.add). If registration fails (e.g. insufficient permissions), it prints the URI and asks the user to add it in the Dashboard before continuing (errors out in agent/CI mode instead of prompting).
  2. Builds an SSO authorize URL with sso.getAuthorizationUrl({ clientId, redirectUri, connection, state }), where clientId resolves from --client-idWORKOS_CLIENT_ID → the active environment's stored client ID.
  3. Starts a localhost HTTP callback server, opens the URL in the browser (--no-open to disable), waits for the IdP redirect (default 300s timeout, --timeout to override), validates state, exchanges the code via sso.getProfileAndToken, and prints the authenticated profile.

Flags: --client-id, --port (default 4807), --timeout, --open/--no-open, plus the usual --api-key/--json.

JSON mode outputs { connectionId, redirectUri, redirectUriRegistered, authorizationUrl, profile }.

Also registered in bin.ts and the help-json.ts command registry, with spec coverage for the happy path, flag overrides, IdP error callbacks, state mismatch, registration fallback prompts, agent-mode behavior, and JSON output.

Test plan

  • pnpm test (2102 passed), pnpm typecheck, pnpm lint, pnpm format:check, pnpm build all pass
  • New unit tests in src/commands/connection.spec.ts mock the HTTP server, browser open, and SDK to exercise the full callback flow

Link to Devin session: https://app.devin.ai/sessions/68e059345eaa4022851eb4f324d9c361
Requested by: @jonatascastro12

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor
Original prompt from jonatas

https://github.com/workos/cli/blob/main/src/commands/connection.ts

Can you create a command to test a connection?

It should first:

  • ask the customer to add a redirect URI that points to the CLI (a localhost on some port perhaps) [unless we already have a endpoint that adds a redirect URI in the environment, so we could automatically add it]
  • create an SSO authorize URL with the default application client ID and redirect_uri that redirects to the one suggested above

@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown

Greptile Summary

Adds workos connection test <id> — a full SSO smoke-test command that starts a localhost callback server, registers the redirect URI automatically, opens the authorization URL in a browser (human mode only), validates the OAuth state parameter, exchanges the code for a profile, and prints the result. All issues flagged in previous review rounds have been resolved (state-mismatch handling, listener attachment ordering, agent-mode browser open guard).

  • Callback server registers the request handler via waitForCallback before server.listen is called, eliminating the previous race window; the listener only settles on a matching state for both success and IdP-error responses (per RFC 6749 §4.1.2.1).
  • Redirect-URI registration failure falls back to a manual-confirmation prompt in human mode and errors immediately in agent/CI mode.
  • Ten unit tests cover happy path, flag overrides, IdP errors, state-mismatch retry, agent-mode error, manual-URI fallback, cancellation, and JSON output.

Confidence Score: 5/5

Safe to merge — the new command is additive, well-tested, and all previously reported issues have been fixed.

The implementation correctly handles state validation, server lifecycle, redirect-URI registration fallback, and interaction-mode guards. All issues from prior review rounds are addressed, and the ten new unit tests cover every major code path including the retry-on-wrong-state behaviour.

No files require special attention.

Important Files Changed

Filename Overview
src/commands/connection.ts Core implementation of runConnectionTest; state validation, server lifecycle, and interaction-mode guards are all correct. Timeout error surfaces as unknown_error code rather than a dedicated timeout code when piped to JSON consumers.
src/commands/connection.spec.ts Comprehensive test coverage added for all new runConnectionTest paths including state-mismatch retry, agent-mode error, manual-URI prompt, and JSON output; existing tests unaffected.
src/bin.ts New test <id> subcommand correctly wired with all flags (client-id, port, timeout, open) and proper argument mapping.
src/utils/help-json.ts Command registry updated to include the new test subcommand schema with all options; defaults match bin.ts.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI as CLI (runConnectionTest)
    participant WorkOS as WorkOS API
    participant Server as Localhost :4807
    participant Browser as Browser
    participant IdP as Identity Provider

    CLI->>WorkOS: getConnection(id)
    WorkOS-->>CLI: connection info

    CLI->>WorkOS: redirectUris.add(http://localhost:4807/callback)
    alt Registration succeeds
        WorkOS-->>CLI: "{ success, alreadyExists }"
    else Registration fails + human mode
        CLI->>CLI: clack.confirm (manual add prompt)
    else Registration fails + agent/CI mode
        CLI->>CLI: exitWithError
    end

    CLI->>CLI: "crypto.randomUUID() -> state"
    CLI->>WorkOS: sso.getAuthorizationUrl
    WorkOS-->>CLI: authorizationUrl

    CLI->>Server: attach request handler
    CLI->>Server: server.listen(4807)
    Server-->>CLI: listening

    opt "isHumanMode() and open != false"
        CLI->>Browser: open(authorizationUrl)
    end

    Browser->>WorkOS: GET /sso/authorize
    WorkOS->>IdP: SAML/OIDC request
    IdP-->>Browser: authenticate user
    Browser->>Server: "GET /callback?code=...&state=..."

    alt state mismatch
        Server-->>Browser: 400 keep listening
    else IdP error with matching state
        Server-->>Browser: 400 SSO test failed
        CLI->>CLI: exitWithError(sso_test_failed)
    else success with matching state
        Server-->>Browser: 200 SSO test successful
        CLI->>WorkOS: sso.getProfileAndToken
        WorkOS-->>CLI: profile
        CLI->>CLI: print profile / outputJson
    end

    CLI->>Server: server.close()
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 CLI as CLI (runConnectionTest)
    participant WorkOS as WorkOS API
    participant Server as Localhost :4807
    participant Browser as Browser
    participant IdP as Identity Provider

    CLI->>WorkOS: getConnection(id)
    WorkOS-->>CLI: connection info

    CLI->>WorkOS: redirectUris.add(http://localhost:4807/callback)
    alt Registration succeeds
        WorkOS-->>CLI: "{ success, alreadyExists }"
    else Registration fails + human mode
        CLI->>CLI: clack.confirm (manual add prompt)
    else Registration fails + agent/CI mode
        CLI->>CLI: exitWithError
    end

    CLI->>CLI: "crypto.randomUUID() -> state"
    CLI->>WorkOS: sso.getAuthorizationUrl
    WorkOS-->>CLI: authorizationUrl

    CLI->>Server: attach request handler
    CLI->>Server: server.listen(4807)
    Server-->>CLI: listening

    opt "isHumanMode() and open != false"
        CLI->>Browser: open(authorizationUrl)
    end

    Browser->>WorkOS: GET /sso/authorize
    WorkOS->>IdP: SAML/OIDC request
    IdP-->>Browser: authenticate user
    Browser->>Server: "GET /callback?code=...&state=..."

    alt state mismatch
        Server-->>Browser: 400 keep listening
    else IdP error with matching state
        Server-->>Browser: 400 SSO test failed
        CLI->>CLI: exitWithError(sso_test_failed)
    else success with matching state
        Server-->>Browser: 200 SSO test successful
        CLI->>WorkOS: sso.getProfileAndToken
        WorkOS-->>CLI: profile
        CLI->>CLI: print profile / outputJson
    end

    CLI->>Server: server.close()
Loading

Reviews (5): Last reviewed commit: "fix: Skip browser auto-open outside huma..." | Re-trigger Greptile

Comment thread src/commands/connection.ts
Comment thread src/commands/connection.ts
Comment thread src/commands/connection.ts
Comment thread src/commands/connection.ts
jonatascastro12 and others added 2 commits June 11, 2026 15:31
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Comment thread src/commands/connection.ts Outdated
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Comment thread src/commands/connection.ts Outdated
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

❌ Cannot revive Devin session - the session is too old. Please start a new session instead.

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