Skip to content

feat(auth): the device authorization grant, client side - #1805

Draft
sileht wants to merge 1 commit into
devs/sileht/mrgfy-8703-cli-auth-commands/let-caller-read-endpoint-s-rejection-body--9339bdfbfrom
devs/sileht/mrgfy-8703-cli-auth-commands/device-authz-grant-client-side--c7a625b9
Draft

feat(auth): the device authorization grant, client side#1805
sileht wants to merge 1 commit into
devs/sileht/mrgfy-8703-cli-auth-commands/let-caller-read-endpoint-s-rejection-body--9339bdfbfrom
devs/sileht/mrgfy-8703-cli-auth-commands/device-authz-grant-client-side--c7a625b9

Conversation

@sileht

@sileht sileht commented Sep 4, 2026

Copy link
Copy Markdown
Member

A new mergify-auth crate holding the RFC 8628 client: open a grant,
poll until its owner approves in a browser, revoke the result.

A device grant rather than a browser redirect because the CLI runs where
there is no browser to redirect to and no port to listen on -- over SSH,
in a container, on a build machine. The user reads a code off the
terminal and approves it wherever they already have a Mergify session.

Three things the implementation takes from the server rather than
assuming:

  • The verification URL. A self-hosted deployment answers with its own
    dashboard, so a client that hardcoded dashboard.mergify.com would
    send those users to a page that knows nothing about their grant.
  • The refusal text. The access_denied you get at the 20-token cap
    names the cap and the fix; printing the error code instead would drop
    the only part the user can act on.
  • The cadence -- within limits. interval and expires_in come from
    whatever host --api-url names, so both are clamped: a server
    answering interval: 86400 would otherwise hang auth login for a
    day and look like a bug here. Ours sends 5 and 600.

slow_down adds five seconds per RFC 8628 §3.5, and the poll loop keeps
its own deadline as a backstop against a deployment that answers
authorization_pending forever -- the server's own expiry is the
expired_token branch and comes first in practice.

The client carries no bearer token: the three endpoints are
unauthenticated, and revoke already puts the secret in the form body.
A test pins that, since sending it twice would be easy to do by
accident.

refresh_token is declared on the token response so the field cannot
break deserialization the day it appears, and nothing reads it: the
Mergify API issues none, and using one would need a refresh grant that
does not exist server-side either.

Not wired to the CLI yet.

Fixes MRGFY-8703

Depends-On: #1804

@sileht

sileht commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

This pull request is part of a Mergify stack:

# Pull Request Link
1 refactor(core): split Mergify and GitHub token resolution #1802
2 feat(core): store the Mergify credential in the OS keychain #1803
3 feat(core): let a caller read an endpoint's rejection body #1804
4 feat(auth): the device authorization grant, client side #1805 👈
5 feat(cli): mergify auth login, logout and status #1806
6 feat(core): use the stored credential, deprecate GITHUB_TOKEN #1807
7 fix(core): never send a Mergify user token to GitHub #1808

@mergify

mergify Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 3 of 7 protections blocking · waiting on 👀 reviews and ⛓️ dependency

Protection Waiting on
🔴 ⛓️ Depends-On Requirements ⛓️ dependency
🔴 👀 Review Requirements 👀 reviews
🔴 🔎 Reviews 👀 reviews
🟢 🤖 Continuous Integration
🟢 Enforce conventional commit
🟢 📕 PR description
🟢 🚦 Auto-queue

🔴 ⛓️ Depends-On Requirements

Waiting for

This rule is failing.

Requirement based on the presence of Depends-On in the body of the pull request

🔴 👀 Review Requirements

Waiting for

  • #approved-reviews-by>=2
This rule is failing.
  • any of:
    • #approved-reviews-by>=2
    • author = dependabot[bot]
    • author = mergify-ci-bot
    • author = renovate[bot]

🔴 🔎 Reviews

Waiting for

  • #review-requested = 0
This rule is failing.
  • #review-requested = 0
  • #changes-requested-reviews-by = 0
  • #review-threads-unresolved = 0

Show 4 satisfied protections

🟢 🤖 Continuous Integration

  • all of:
    • check-success=ci-gate

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|internal|docs|style|refactor|perf|test|build|ci|chore|revert|ui)(?:\(.+\))?!?:

🟢 📕 PR description

  • body ~= (?ms:.{48,})

🟢 🚦 Auto-queue

When all merge protections are satisfied, this pull request will be queued automatically.

@mergify
mergify Bot requested a review from a team September 4, 2026 23:26
A new `mergify-auth` crate holding the RFC 8628 client: open a grant,
poll until its owner approves in a browser, revoke the result.

A device grant rather than a browser redirect because the CLI runs where
there is no browser to redirect to and no port to listen on -- over SSH,
in a container, on a build machine. The user reads a code off the
terminal and approves it wherever they already have a Mergify session.

Three things the implementation takes from the server rather than
assuming:

- The verification URL. A self-hosted deployment answers with its own
  dashboard, so a client that hardcoded `dashboard.mergify.com` would
  send those users to a page that knows nothing about their grant.
- The refusal text. The `access_denied` you get at the 20-token cap
  names the cap and the fix; printing the error code instead would drop
  the only part the user can act on.
- The cadence -- within limits. `interval` and `expires_in` come from
  whatever host `--api-url` names, so both are clamped: a server
  answering `interval: 86400` would otherwise hang `auth login` for a
  day and look like a bug here. Ours sends 5 and 600.

`slow_down` adds five seconds per RFC 8628 §3.5, and the poll loop keeps
its own deadline as a backstop against a deployment that answers
`authorization_pending` forever -- the server's own expiry is the
`expired_token` branch and comes first in practice.

The client carries no bearer token: the three endpoints are
unauthenticated, and `revoke` already puts the secret in the form body.
A test pins that, since sending it twice would be easy to do by
accident.

`refresh_token` is declared on the token response so the field cannot
break deserialization the day it appears, and nothing reads it: the
Mergify API issues none, and using one would need a refresh grant that
does not exist server-side either.

Not wired to the CLI yet.

Fixes MRGFY-8703

Change-Id: Ic7a625b9e8f54a692af48d9b95907334eac3d5e5
@sileht
sileht force-pushed the devs/sileht/mrgfy-8703-cli-auth-commands/let-caller-read-endpoint-s-rejection-body--9339bdfb branch from 3c5d1dd to 9ca0419 Compare September 4, 2026 23:40
@sileht
sileht force-pushed the devs/sileht/mrgfy-8703-cli-auth-commands/device-authz-grant-client-side--c7a625b9 branch from bc88a36 to 048166f Compare September 4, 2026 23:40
@sileht

sileht commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Revision history

# Type Changes Reason Date
1 initial bc88a36 2026-09-04 23:40 UTC
2 rebase bc88a36 → 048166f (rebase only) 2026-09-04 23:40 UTC

@mergify
mergify Bot had a problem deploying to Mergify Merge Protections September 4, 2026 23:41 Failure
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