feat(cli): add pipefy auth login --device#154
Closed
adriannoes wants to merge 1 commit into
Closed
Conversation
b2200b2 to
fb637bb
Compare
gbrlcustodio
added a commit
that referenced
this pull request
May 30, 2026
Address PR #154 review findings by giving the OAuth wire shapes typed domain objects instead of raw dicts threaded through call sites. * Add `TokenResponse` (RFC 6749 §5.1) and `OAuthErrorResponse` (§5.2 / RFC 8628 §3.5) in a new `responses` module. `TokenResponse` replaces `dict[str, object]` across `exchange_code`, `poll_device_token`, and `store_session`; `OAuthErrorResponse` replaces three near-duplicate `_format_*_error` free functions across `flow.py` and `device.py`. * `StoredSession` now bundles a `TokenResponse` so the keychain blob reads from one source of truth. On-disk JSON keeps the legacy flat shape via destructure-on-write / rebuild-on-load, so existing keychain entries continue to load without forcing a re-login. * Move device-authorization-response parsing into `DeviceAuthorization.from_payload` and wrap the `expires_in` float coercion so a malformed IdP value surfaces as `LoginError` instead of a bare `ValueError`. * Restructure the device poll loop so the first request fires immediately. RFC 8628 §3.5 only requires the interval wait *between* retryable responses, not before the first poll. * Hard-error via `typer.BadParameter` when `--device` is combined with `--no-browser` or an explicit `--callback-timeout`. Matches the conflicting-flag precedent in `portal.py`. Test surface: 15 new tests (parser edge cases, poll-first timing, conflicting-flags CLI) plus migration of 394 existing tests to the new attribute-access shape. Whole workspace passes (2705/2705).
This was referenced May 30, 2026
61d3731 to
abad06c
Compare
f04f589 to
325d6f1
Compare
gbrlcustodio
added a commit
that referenced
this pull request
May 30, 2026
Implement OAuth 2.0 Device Authorization Grant (RFC 8628) for headless sign-in flows where the loopback callback dance isn't workable: CI runners, SSH-only boxes, dev containers without a system browser. * `pipefy_auth.device` runs the device-code flow end-to-end. The CLI prints the user code and verification URL (plus the `_complete` form when the IdP advertises it), then polls the token endpoint per RFC 8628 §3.5: clamp non-positive intervals to 5s, sleep only the remaining TTL before each request, and honor `slow_down` / `authorization_pending`. * PKCE is bound to the device grant. Realms that mandate PKCE on the OIDC client (observed: piporacle staging) reject the device-auth request without a `code_challenge`; sending it on the authorization request and the matching `code_verifier` on the token poll is safe on IdPs that don't enforce it. * `pipefy auth login --device` is the CLI entry point. `--device` combined with `--no-browser` or an explicit `--callback-timeout` hard-errors via `typer.BadParameter` — the device flow has no browser-callback dance to suppress and the deadline is governed by the IdP's `expires_in`. The `_DEFAULT_CALLBACK_TIMEOUT_S` constant exists so the explicit-default check compares by value. * `discovery` parses and SSRF-validates `device_authorization_endpoint` from the OIDC document; the docstring covers the device path alongside the browser dance and token exchange. * `docs/cli/auth.md` documents the device grant under "Headless / SSH", including the IdP requirement (`device_authorization_endpoint` in OIDC discovery). Built on the foundations from the prior two PRs: device.py uses the shared `TokenResponse` / `OAuthErrorResponse` wrappers from ``pipefy_auth.responses`` and the permissive `pipefy_infra.coerce` helpers throughout. Test surface: 27 tests covering the device polling state machine, TTL-aware sleep, RFC-mandated interval clamp, slow-down handling, PKCE binding, and the conflicting-flag CLI errors.
abad06c to
54eeeab
Compare
325d6f1 to
285500e
Compare
gbrlcustodio
added a commit
that referenced
this pull request
May 30, 2026
Implement OAuth 2.0 Device Authorization Grant (RFC 8628) for headless sign-in flows where the loopback callback dance isn't workable: CI runners, SSH-only boxes, dev containers without a system browser. * `pipefy_auth.device` runs the device-code flow end-to-end. The CLI prints the user code and verification URL (plus the `_complete` form when the IdP advertises it), then polls the token endpoint per RFC 8628 §3.5: clamp non-positive intervals to 5s, sleep only the remaining TTL before each request, and honor `slow_down` / `authorization_pending`. * PKCE is bound to the device grant. Realms that mandate PKCE on the OIDC client (observed: piporacle staging) reject the device-auth request without a `code_challenge`; sending it on the authorization request and the matching `code_verifier` on the token poll is safe on IdPs that don't enforce it. * `pipefy auth login --device` is the CLI entry point. `--device` combined with `--no-browser` or an explicit `--callback-timeout` hard-errors via `typer.BadParameter` — the device flow has no browser-callback dance to suppress and the deadline is governed by the IdP's `expires_in`. The `_DEFAULT_CALLBACK_TIMEOUT_S` constant exists so the explicit-default check compares by value. * `discovery` parses and SSRF-validates `device_authorization_endpoint` from the OIDC document; the docstring covers the device path alongside the browser dance and token exchange. * `docs/cli/auth.md` documents the device grant under "Headless / SSH", including the IdP requirement (`device_authorization_endpoint` in OIDC discovery). Built on the foundations from the prior two PRs: device.py uses the shared `TokenResponse` / `OAuthErrorResponse` wrappers from ``pipefy_auth.responses`` and the permissive `pipefy_infra.coerce` helpers throughout. Test surface: 27 tests covering the device polling state machine, TTL-aware sleep, RFC-mandated interval clamp, slow-down handling, PKCE binding, and the conflicting-flag CLI errors.
54eeeab to
9edc67d
Compare
285500e to
bb19834
Compare
gbrlcustodio
added a commit
that referenced
this pull request
May 30, 2026
Implement OAuth 2.0 Device Authorization Grant (RFC 8628) for headless sign-in flows where the loopback callback dance isn't workable: CI runners, SSH-only boxes, dev containers without a system browser. * `pipefy_auth.device` runs the device-code flow end-to-end. The CLI prints the user code and verification URL (plus the `_complete` form when the IdP advertises it), then polls the token endpoint per RFC 8628 §3.5: clamp non-positive intervals to 5s, sleep only the remaining TTL before each request, and honor `slow_down` / `authorization_pending`. * PKCE is bound to the device grant. Realms that mandate PKCE on the OIDC client (observed: piporacle staging) reject the device-auth request without a `code_challenge`; sending it on the authorization request and the matching `code_verifier` on the token poll is safe on IdPs that don't enforce it. * `pipefy auth login --device` is the CLI entry point. `--device` combined with `--no-browser` or an explicit `--callback-timeout` hard-errors via `typer.BadParameter` — the device flow has no browser-callback dance to suppress and the deadline is governed by the IdP's `expires_in`. The `_DEFAULT_CALLBACK_TIMEOUT_S` constant exists so the explicit-default check compares by value. * `discovery` parses and SSRF-validates `device_authorization_endpoint` from the OIDC document; the docstring covers the device path alongside the browser dance and token exchange. * `docs/cli/auth.md` documents the device grant under "Headless / SSH", including the IdP requirement (`device_authorization_endpoint` in OIDC discovery). Built on the foundations from the prior two PRs: device.py uses the shared `TokenResponse` / `OAuthErrorResponse` wrappers from ``pipefy_auth.responses`` and the permissive `pipefy_infra.coerce` helpers throughout. Test surface: 27 tests covering the device polling state machine, TTL-aware sleep, RFC-mandated interval clamp, slow-down handling, PKCE binding, and the conflicting-flag CLI errors.
9edc67d to
a69487c
Compare
bb19834 to
b3ab7c0
Compare
gbrlcustodio
added a commit
that referenced
this pull request
May 30, 2026
Implement OAuth 2.0 Device Authorization Grant (RFC 8628) for headless sign-in flows where the loopback callback dance isn't workable: CI runners, SSH-only boxes, dev containers without a system browser. * `pipefy_auth.device` runs the device-code flow end-to-end. The CLI prints the user code and verification URL (plus the `_complete` form when the IdP advertises it), then polls the token endpoint per RFC 8628 §3.5: clamp non-positive intervals to 5s, sleep only the remaining TTL before each request, and honor `slow_down` / `authorization_pending`. * PKCE is bound to the device grant. Realms that mandate PKCE on the OIDC client (observed: piporacle staging) reject the device-auth request without a `code_challenge`; sending it on the authorization request and the matching `code_verifier` on the token poll is safe on IdPs that don't enforce it. * `pipefy auth login --device` is the CLI entry point. `--device` combined with `--no-browser` or an explicit `--callback-timeout` hard-errors via `typer.BadParameter` — the device flow has no browser-callback dance to suppress and the deadline is governed by the IdP's `expires_in`. The `_DEFAULT_CALLBACK_TIMEOUT_S` constant exists so the explicit-default check compares by value. * `discovery` parses and SSRF-validates `device_authorization_endpoint` from the OIDC document; the docstring covers the device path alongside the browser dance and token exchange. * `docs/cli/auth.md` documents the device grant under "Headless / SSH", including the IdP requirement (`device_authorization_endpoint` in OIDC discovery). Built on the foundations from the prior two PRs: device.py uses the shared `TokenResponse` / `OAuthErrorResponse` wrappers from ``pipefy_auth.responses`` and the permissive `pipefy_infra.coerce` helpers throughout. Test surface: 27 tests covering the device polling state machine, TTL-aware sleep, RFC-mandated interval clamp, slow-down handling, PKCE binding, and the conflicting-flag CLI errors.
a69487c to
129658d
Compare
b3ab7c0 to
01e1cd9
Compare
Implement OAuth 2.0 Device Authorization Grant (RFC 8628) for headless sign-in flows where the loopback callback dance isn't workable: CI runners, SSH-only boxes, dev containers without a system browser. * `pipefy_auth.device` runs the device-code flow end-to-end. The CLI prints the user code and verification URL (plus the `_complete` form when the IdP advertises it), then polls the token endpoint per RFC 8628 §3.5: clamp non-positive intervals to 5s, sleep only the remaining TTL before each request, and honor `slow_down` / `authorization_pending`. * PKCE is bound to the device grant. Realms that mandate PKCE on the OIDC client (observed: piporacle staging) reject the device-auth request without a `code_challenge`; sending it on the authorization request and the matching `code_verifier` on the token poll is safe on IdPs that don't enforce it. * `pipefy auth login --device` is the CLI entry point. `--device` combined with `--no-browser` or an explicit `--callback-timeout` hard-errors via `typer.BadParameter` — the device flow has no browser-callback dance to suppress and the deadline is governed by the IdP's `expires_in`. The `_DEFAULT_CALLBACK_TIMEOUT_S` constant exists so the explicit-default check compares by value. * `discovery` parses and SSRF-validates `device_authorization_endpoint` from the OIDC document; the docstring covers the device path alongside the browser dance and token exchange. * `docs/cli/auth.md` documents the device grant under "Headless / SSH", including the IdP requirement (`device_authorization_endpoint` in OIDC discovery). Built on the foundations from the prior two PRs: device.py uses the shared `TokenResponse` / `OAuthErrorResponse` wrappers from ``pipefy_auth.responses`` and the permissive `pipefy_infra.coerce` helpers throughout. Test surface: 27 tests covering the device polling state machine, TTL-aware sleep, RFC-mandated interval clamp, slow-down handling, PKCE binding, and the conflicting-flag CLI errors.
129658d to
dee8399
Compare
gbrlcustodio
added a commit
that referenced
this pull request
May 31, 2026
Implement OAuth 2.0 Device Authorization Grant (RFC 8628) for headless sign-in flows where the loopback callback dance isn't workable: CI runners, SSH-only boxes, dev containers without a system browser. * `pipefy_auth.device` runs the device-code flow end-to-end. The CLI prints the user code and verification URL (plus the `_complete` form when the IdP advertises it), then polls the token endpoint per RFC 8628 §3.5: clamp non-positive intervals to 5s, sleep only the remaining TTL before each request, and honor `slow_down` / `authorization_pending`. * PKCE is bound to the device grant. Realms that mandate PKCE on the OIDC client (observed: piporacle staging) reject the device-auth request without a `code_challenge`; sending it on the authorization request and the matching `code_verifier` on the token poll is safe on IdPs that don't enforce it. * `pipefy auth login --device` is the CLI entry point. `--device` combined with `--no-browser` or an explicit `--callback-timeout` hard-errors via `typer.BadParameter` — the device flow has no browser-callback dance to suppress and the deadline is governed by the IdP's `expires_in`. The `_DEFAULT_CALLBACK_TIMEOUT_S` constant exists so the explicit-default check compares by value. * `discovery` parses and SSRF-validates `device_authorization_endpoint` from the OIDC document; the docstring covers the device path alongside the browser dance and token exchange. * `docs/cli/auth.md` documents the device grant under "Headless / SSH", including the IdP requirement (`device_authorization_endpoint` in OIDC discovery). Built on the foundations from the prior two PRs: device.py uses the shared `TokenResponse` / `OAuthErrorResponse` wrappers from ``pipefy_auth.responses`` and the permissive `pipefy_infra.coerce` helpers throughout. Test surface: 27 tests covering the device polling state machine, TTL-aware sleep, RFC-mandated interval clamp, slow-down handling, PKCE binding, and the conflicting-flag CLI errors.
4 tasks
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implement OAuth 2.0 Device Authorization Grant (RFC 8628) for
pipefy auth login --deviceso headless flows (CI runners, SSH-only boxes, dev containers without a system browser) can sign in. Closes #138.Scope
This PR is now scoped to the device login strategy only. The infra and OAuth response refactors that previously rode along have been carved out into two prerequisite PRs:
refactor/infra-coerce(permissive type-coercion helpers inpipefy_infra).refactor/auth-responses(TokenResponse/OAuthErrorResponsedomain wrappers).Once #257 and #258 land on
dev, this PR's base will be re-pointed todev.Changes
pipefy_auth/device.pyruns the device-code flow end-to-end. CLI prints the user code + verification URL (plus_completeform when the IdP advertises it), then polls per RFC 8628 §3.5: clamp non-positive intervals to 5s, sleep only remaining TTL before each request, honorslow_down/authorization_pending.code_challenge. Sending it on the auth request + matchingcode_verifieron the token poll is safe on IdPs that don't enforce it.pipefy auth login --deviceis the CLI entry point.--device + --no-browserand--device + --callback-timeout <explicit>both hard-error viatyper.BadParameter.discoveryparses and SSRF-validatesdevice_authorization_endpointfrom the OIDC document.docs/cli/auth.mddocuments the device grant under "Headless / SSH", including the IdP requirement.Built on the foundations from the prior two PRs:
device.pyusesTokenResponse/OAuthErrorResponsefrom #258 and thepipefy_infra.coercehelpers from #257.Test plan
packages/cli/tests/test_auth_device.pycover the polling state machine, TTL-aware sleep, RFC interval clamp, slow-down handling, PKCE binding, conflicting-flag CLI errors.uv run pytest packages/auth/tests/ packages/infra/tests/-- 206 passeduv run pytest packages/sdk/tests/ packages/cli/tests/-- 1188 passed, 30 skippeduv run pytest packages/mcp/tests/-- 1320 passed, 16 skipped