Skip to content

feat(oauth): First-party CLI client and per-client scope policy - #8029

Merged
khvn26 merged 10 commits into
mainfrom
feat/oauth-first-party-cli-client
Jul 16, 2026
Merged

feat(oauth): First-party CLI client and per-client scope policy#8029
khvn26 merged 10 commits into
mainfrom
feat/oauth-first-party-cli-client

Conversation

@khvn26

@khvn26 khvn26 commented Jul 16, 2026

Copy link
Copy Markdown
Member

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Contributes to Flagsmith/flagsmith-cli#42

In this PR, we prepare the OAuth implementation for the upcoming CLI with the following additions:

  1. A flagsmith-cli public OAuth app with a constant client_id for the CLI.
  2. A admin-api scope, to distinguish CLI grants from MCP. A custom FlagsmithScopes scopes backend makes sure admin-api is only allowed for the CLI.
  3. REFRESH_TOKEN_GRACE_PERIOD_SECONDS is set to 120 so a client that loses the response to a refresh-token rotation can retry with the previous token instead of being locked out.
  4. REFRESH_TOKEN_REUSE_PROTECTION is enabled: replaying a rotated-away refresh token after the grace period revokes the whole token family, cutting off a potential token thief.

How did you test this code?

Added new tests.

Create the well-known public flagsmith-cli OAuth application via data
migration, pinned to a fixed client_id with RFC 8252 loopback redirect
URIs so the CLI binary can hardcode it against any instance.

Add a management-api scope alongside mcp, and a scopes backend that
issues scopes per client class: the first-party CLI may be issued
management-api, DCR-registered third-party clients only mcp. Scopes
name API surfaces and act as a ceiling; RBAC still decides what the
authenticating user can do.

Set REFRESH_TOKEN_GRACE_PERIOD_SECONDS so clients can retry a token
refresh after a network failure during rotation.

beep boop
@khvn26
khvn26 requested a review from a team as a code owner July 16, 2026 14:05
@khvn26
khvn26 requested review from emyller and removed request for a team July 16, 2026 14:05
@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 16, 2026 3:12pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ignored Ignored Preview Jul 16, 2026 3:12pm
flagsmith-frontend-staging Ignored Ignored Preview Jul 16, 2026 3:12pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

OAuth2 configuration now supports refresh-token reuse protection, a grace period, mcp and management-api scopes, and a custom scope backend. New constants describe the Flagsmith CLI OAuth2 application and scope sets. A migration creates the CLI application without overwriting existing records. FlagsmithScopes filters available and default scopes by application identity. Tests cover migration behaviour, scope filtering, backend selection, and authorisation responses.

Estimated code review effort: 3 (Moderate) | ~25 minutes


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the api Issue related to the REST API label Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-e2e:pr-8029 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-e2e:pr-8029 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api-test:pr-8029 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api-test:pr-8029 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-frontend:pr-8029 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-frontend:pr-8029 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-api:pr-8029 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-8029 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-8029 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8029 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8029 Finished ✅ Results

@github-actions github-actions Bot added the feature New feature or request label Jul 16, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jul 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 412f7bd5-4636-4276-8f72-0cd5caf5a2ee

📥 Commits

Reviewing files that changed from the base of the PR and between 2ec1139 and 1494407.

📒 Files selected for processing (8)
  • api/app/settings/common.py
  • api/oauth2_metadata/constants.py
  • api/oauth2_metadata/migrations/0001_create_flagsmith_cli_application.py
  • api/oauth2_metadata/migrations/__init__.py
  • api/oauth2_metadata/scopes.py
  • api/tests/unit/oauth2_metadata/test_authorize_view.py
  • api/tests/unit/oauth2_metadata/test_migrations.py
  • api/tests/unit/oauth2_metadata/test_scopes.py

Comment thread api/app/settings/common.py
Comment thread api/oauth2_metadata/migrations/0001_create_flagsmith_cli_application.py Outdated
Comment thread api/oauth2_metadata/migrations/0001_create_flagsmith_cli_application.py Outdated
Comment thread api/oauth2_metadata/scopes.py
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  33.1 seconds
commit  1494407
info  🔄 Run: #18352 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  47.4 seconds
commit  1494407
info  🔄 Run: #18352 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  32.1 seconds
commit  1494407
info  🔄 Run: #18352 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  4 passed

Details

stats  4 tests across 4 suites
duration  37.7 seconds
commit  1494407
info  🔄 Run: #18352 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  44.6 seconds
commit  aa33ddc
info  🔄 Run: #18353 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  53.5 seconds
commit  aa33ddc
info  🔄 Run: #18353 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  32 seconds
commit  aa33ddc
info  🔄 Run: #18353 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  37 seconds
commit  aa33ddc
info  🔄 Run: #18353 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  40.6 seconds
commit  f43b649
info  🔄 Run: #18356 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  42.2 seconds
commit  f43b649
info  🔄 Run: #18356 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  4 passed

Details

stats  4 tests across 4 suites
duration  36.3 seconds
commit  f43b649
info  🔄 Run: #18356 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  39.9 seconds
commit  f43b649
info  🔄 Run: #18356 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  46.8 seconds
commit  e72355c
info  🔄 Run: #18358 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  6.1 seconds
commit  e72355c
info  🔄 Run: #18358 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  34.6 seconds
commit  e72355c
info  🔄 Run: #18358 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  7.7 seconds
commit  e72355c
info  🔄 Run: #18358 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  4.8 seconds
commit  8b7acdf
info  🔄 Run: #18362 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  41.2 seconds
commit  8b7acdf
info  🔄 Run: #18362 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  47.8 seconds
commit  8b7acdf
info  🔄 Run: #18362 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  6 passed

Details

stats  6 tests across 5 suites
duration  1 minute, 2 seconds
commit  8b7acdf
info  🔄 Run: #18362 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  39.9 seconds
commit  1b93aa6
info  🔄 Run: #18364 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  4 passed

Details

stats  4 tests across 4 suites
duration  7.2 seconds
commit  1b93aa6
info  🔄 Run: #18364 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

failed  1 failed

Details

stats  1 test across 1 suite
duration  11.8 seconds
commit  1b93aa6
info  📦 Artifacts: View test results and HTML report
🔄 Run: #18364 (attempt 1)

Failed tests

firefox › tests/onboarding-tests.pw.ts › Onboarding › New user connects via the single-page onboarding flow @oss

### Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  1 minute, 3 seconds
commit  1b93aa6
info  🔄 Run: #18364 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  42.4 seconds
commit  e4c7f0a
info  🔄 Run: #18366 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  44.5 seconds
commit  4380518
info  🔄 Run: #18367 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  45.3 seconds
commit  e4c7f0a
info  🔄 Run: #18366 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  8.1 seconds
commit  e4c7f0a
info  🔄 Run: #18366 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  32 seconds
commit  4380518
info  🔄 Run: #18367 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  5 passed

Details

stats  5 tests across 5 suites
duration  36.9 seconds
commit  4380518
info  🔄 Run: #18367 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  6.9 seconds
commit  e4c7f0a
info  🔄 Run: #18366 (attempt 1)

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

19 screenshots compared. See report for details.
View full report

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.64%. Comparing base (2ec1139) to head (4380518).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8029   +/-   ##
=======================================
  Coverage   98.64%   98.64%           
=======================================
  Files        1501     1506    +5     
  Lines       59474    59572   +98     
=======================================
+ Hits        58668    58766   +98     
  Misses        806      806           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The forward operation refuses to clobber an existing flagsmith-cli
application, but the reverse deleted it unconditionally, cascading to
any tokens issued through it. Make the reverse a noop; migration tests
now simulate a fresh install explicitly instead of relying on reverse
deletion.

beep boop
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jul 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/tests/unit/oauth2_metadata/test_migrations.py (1)

54-63: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Cover rollback and issued-token preservation.

This test only applies the migration forwards and checks two application fields. It never verifies that rolling back preserves a pre-existing application and its issued tokens, so a regression to destructive rollback could pass. Add a token and exercise the supported rollback path, asserting both records and the existing fields remain unchanged.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: beb45510-cb80-48a9-9e11-6a3152e95dfa

📥 Commits

Reviewing files that changed from the base of the PR and between aa33ddc and f43b649.

📒 Files selected for processing (2)
  • api/oauth2_metadata/migrations/0001_create_flagsmith_cli_application.py
  • api/tests/unit/oauth2_metadata/test_migrations.py

Importing runtime constants into the migration meant a future edit
would silently change what fresh installs create, and the lockstep
test would not detect the drift. Inline the values in the migration
and assert literals in its tests; constants.py keeps only the
client_id the runtime scope policy needs.

beep boop
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jul 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 90956103-1ae0-4ea8-80ed-95a60ec381eb

📥 Commits

Reviewing files that changed from the base of the PR and between f43b649 and e72355c.

📒 Files selected for processing (3)
  • api/oauth2_metadata/constants.py
  • api/oauth2_metadata/migrations/0001_create_flagsmith_cli_application.py
  • api/tests/unit/oauth2_metadata/test_migrations.py
💤 Files with no reviewable changes (1)
  • api/oauth2_metadata/constants.py

Comment thread api/tests/unit/oauth2_metadata/test_migrations.py
khvn26 added 2 commits July 16, 2026 15:43
Without it, rotation is only cosmetic: replaying a rotated-away
refresh token after the grace period merely fails, and a thief
holding the current token can keep rotating indefinitely. With reuse
protection, a stale replay revokes the whole token family, cutting
off both parties and forcing re-authentication.

beep boop
The preservation test only checked name and skip_authorization, and
the pre-existing row's other fields matched the migration defaults,
so an overwrite of those fields would have passed unnoticed. Seed
deliberately different values and assert all of them.

beep boop
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jul 16, 2026

@emyller emyller 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.

Non-blocking comments about code comments, and one possibly-blocking discussion about scope naming. Looks great otherwise!

Comment thread api/oauth2_metadata/constants.py Outdated
Comment thread api/tests/unit/oauth2_metadata/test_authorize_view.py Outdated
Comment thread api/tests/unit/oauth2_metadata/test_authorize_view.py Outdated
khvn26 added 2 commits July 16, 2026 15:59
Reversing URL names derives the tested path from the code under test,
so an accidental route change would go unnoticed. Assert the public
paths directly: /api/v1/oauth/authorize/ and /o/token/.

beep boop
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jul 16, 2026
@khvn26
khvn26 requested a review from a team as a code owner July 16, 2026 15:12
@khvn26
khvn26 requested review from bakirFS and removed request for a team July 16, 2026 15:12
@github-actions github-actions Bot added docs Documentation updates feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 16, 2026
@khvn26
khvn26 requested a review from emyller July 16, 2026 15:13
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jul 16, 2026

@emyller emyller 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.

LGTM!

@khvn26
khvn26 merged commit 4df9bfb into main Jul 16, 2026
35 checks passed
@khvn26
khvn26 deleted the feat/oauth-first-party-cli-client branch July 16, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants