Skip to content

Add the OAuth2 password grant for tokens without a refresh token (Datto RMM) - #1075

Merged
helpcodeai merged 2 commits into
mainfrom
helpcodeai/datto-password-grant
Oct 13, 2026
Merged

helpcodeai merged 2 commits into
mainfrom
helpcodeai/datto-password-grant

Conversation

@helpcodeai

Copy link
Copy Markdown
Contributor

Summary

Datto RMM issues 100-hour tokens with no refresh token. A connector set up with the authorization-code flow therefore stopped working after 100 hours, and could only be revived by authorizing again. This adds the OAuth2 password grant (RFC 6749 §4.3): the connector asks the token URL again with the stored username and password when the token is about to expire or the API answers 401. The grant can now be chosen in the connector forms. Part 1 of the design in #979.

Changes

  • OAuth2TokenService: grant: 'password' sends username / password (Datto: API key / API secret) with the client as HTTP Basic by default (public-client:public), in the body with client_secret_post, or client_id only for a public client. Scopes typed in the form (scopes) are now sent for the machine grants too.
  • New oauth-grant.util.ts (isMachineGrant, canRenewToken). REST and GraphQL engines retry a 401 when the grant can renew by itself; before, the retry required a stored refresh token, so client_credentials connectors never retried either.
  • GET/PATCH /api/connectors/:id/oauth-config take and return grant; username / password are write-only (hasUsername / hasPassword). The authorization-code flow is stored as no grant, as every authorized connector has today.
  • Setup status and catalog setup kind treat the password grant like client_credentials (no browser authorization).
  • UI: "Grant type" selector in the create and edit forms, username and password fields for the password grant, no Authorization URL / PKCE / "Authorize with Provider" for machine grants.
  • docs/connectors/rest.md: the Datto example used the authorization-code flow, which is exactly the setup that breaks after 100 hours; replaced with the password grant.

Type

  • Bug fix
  • New feature

Testing

  • Added/updated unit tests
  • Tested manually (describe steps below)

Commands and results:

  • packages/backend: npx tsc --noEmit -p tsconfig.json clean; npm test 552 suites / 14188 tests passed; npm run lint 0 errors (two unrelated files rewritten by --fix were reverted).
  • packages/frontend: npx tsc --noEmit -p tsconfig.json clean; eslint on the changed files clean; npx playwright test tests/e2e/connector-oauth-grant.spec.ts tests/e2e/connector-oauth1-edit.spec.ts tests/e2e/connector-oauth-complete.spec.ts 9 passed.
  • Local baseline note: in this worktree npm ci hoisted react-dom 19.2.7 to the root next to react 19.3.0, so next dev refused to start for Playwright; react-dom was aligned in node_modules for the run only.
  • Not tested against a live Datto tenant (no credentials).

Cloud and self-hosted behave the same: the grant is per connector, no deployment-mode branch.

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • I have updated documentation (if applicable)
  • All existing tests pass (npm test)
  • This PR has a descriptive title

Related Issues

Relates to #979

Datto RMM issues 100-hour tokens with no refresh token, so a connector
set up with the authorization-code flow stopped working after 100 hours.
The password grant asks the token URL again with the stored username and
password. The grant can now be chosen in the connector forms, and the
REST and GraphQL engines retry a 401 for machine grants too.
@helpcodeai
helpcodeai enabled auto-merge (squash) October 13, 2026 13:32
@helpcodeai
helpcodeai merged commit 2193b64 into main Oct 13, 2026
13 checks passed
@helpcodeai
helpcodeai deleted the helpcodeai/datto-password-grant branch October 13, 2026 13:37
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant