Skip to content

feat: service accounts - #361

Open
yuvrajjsingh0 wants to merge 1 commit into
mainfrom
service-accounts-1
Open

feat: service accounts#361
yuvrajjsingh0 wants to merge 1 commit into
mainfrom
service-accounts-1

Conversation

@yuvrajjsingh0

@yuvrajjsingh0 yuvrajjsingh0 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added organisation service account management in the dashboard, including creating, listing, deleting, and rotating credentials.
    • Service account credentials can now be copied or downloaded as JSON.
    • Token handling now supports service accounts alongside regular user credentials.
  • Bug Fixes

    • Regular user management now excludes service accounts from the standard user list.
    • Added validation to prevent service-account addresses from being added as normal organisation users.

@semanticdiff-com

semanticdiff-com Bot commented Jul 6, 2026

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e708f9d2-61ad-4ef4-92f1-ec8f17b54e8e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR adds organisation service accounts: a new database table and migrations, Diesel schema/model additions, AuthN provider support (base and Keycloak) for creating/deleting service-account users, backend API endpoints for create/list/delete/rotate, token issuance changes to resolve service-account credentials, and dashboard UI for managing service accounts.

Changes

Service Accounts

Layer / File(s) Summary
Database schema and migrations
airborne_server/migrations/20260410120000_add_service_accounts/*, airborne_server/src/utils/db/schema.rs, airborne_server/src/utils/db/models.rs
Adds hyperotaserver.service_accounts table with uniqueness constraints and an org index, plus matching Diesel schema table and ServiceAccountEntry model.
AuthN provider service-account support
airborne_server/src/provider/authn.rs, airborne_server/src/provider/authn/keycloak.rs
Base provider rejects service-account operations; Keycloak provider implements supports_service_accounts, create_service_account_user, and delete_user.
Service account API endpoints
airborne_server/src/service_account.rs, airborne_server/src/service_account/types.rs, airborne_server/src/main.rs, airborne_server/src/organisation/user.rs
Adds create/list/delete/rotate handlers, name/email validation, DTOs, /service-accounts route wiring, and blocks adding service-account emails as regular users.
Token issuance resolution
airborne_server/src/token.rs
Resolves refresh token from either user credentials or service accounts table before refreshing access tokens.
Dashboard service accounts UI
airborne_dashboard/app/dashboard/[orgId]/users/page.tsx, airborne_dashboard/next.config.mjs
Adds credential display, service accounts section with SWR-backed CRUD dialogs, permission flags, filtering of regular users, and API rewrite support.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ServiceAccountRoute
  participant AuthNProvider
  participant Database
  participant AuthZProvider
  Client->>ServiceAccountRoute: POST /service-accounts
  ServiceAccountRoute->>AuthNProvider: create_service_account_user(username, email, password)
  AuthNProvider-->>ServiceAccountRoute: UserToken (refresh token as client_secret)
  ServiceAccountRoute->>Database: insert ServiceAccountEntry
  ServiceAccountRoute->>AuthZProvider: add org membership with role
  ServiceAccountRoute-->>Client: CreateServiceAccountResponse
Loading
sequenceDiagram
  participant TokenEndpoint
  participant UserCredentialsTable
  participant ServiceAccountsTable
  participant AuthNProvider
  TokenEndpoint->>UserCredentialsTable: lookup by client_id
  alt found
    TokenEndpoint->>TokenEndpoint: decrypt stored refresh token with client_secret
  else not found
    TokenEndpoint->>ServiceAccountsTable: lookup by client_id
    alt found
      TokenEndpoint->>TokenEndpoint: use client_secret as refresh token
    else not found
      TokenEndpoint-->>TokenEndpoint: return Unauthorized
    end
  end
  TokenEndpoint->>AuthNProvider: refresh_access_token(refresh_token)
Loading

Possibly related PRs

  • juspay/airborne#293: Both PRs modify token issuance in airborne_server/src/token.rs and Keycloak AuthN handling, affecting refresh/access token resolution.

Suggested reviewers: JamesGeorg

Poem

A rabbit hops with keys in paw,
New accounts born by service law,
Rotate, delete, create with glee,
Tokens flow both A and B,
Hop hop hurray, the org's set free! 🐇🔑

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding service account support across the app and server.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch service-accounts-1

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.

Comment thread airborne_server/src/service_account.rs Dismissed

@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: 9

🧹 Nitpick comments (2)
airborne_server/src/service_account.rs (1)

278-284: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

rotate_service_account reuses the "create" authz action.

Tagging rotate as action = "create" (same as the actual create endpoint) makes the two operations indistinguishable in any RBAC policy or audit trail keyed on action name. A dedicated action (e.g. "rotate") would allow finer-grained policy/audit control later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@airborne_server/src/service_account.rs` around lines 278 - 284, The rotate
endpoint is currently using the same authz action as service-account creation,
which makes rotation indistinguishable in policy and audit flows. Update the
authz annotation on rotate_service_account to use a dedicated action name such
as "rotate" instead of reusing "create", keeping the resource and role settings
intact so RBAC and audit logs can differentiate the operations.
airborne_server/src/token.rs (1)

296-342: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Credential-resolution logic is dense; consider extracting a helper.

The PAT-vs-service-account resolution block mixes two DB round trips, decryption, and error branching inline inside issue_token. Extracting it into a small helper (e.g. resolve_refresh_token(pool, client_id, client_secret)) would make it independently unit-testable and easier to reason about, especially as more credential sources may be added later. Note also that both failure paths correctly return the same generic "Invalid credentials" message, avoiding a credential-type oracle — good practice to keep intact in any refactor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@airborne_server/src/token.rs` around lines 296 - 342, The refresh-token
resolution logic inside issue_token is doing too much inline, mixing PAT lookup,
service-account fallback, decryption, and error mapping. Extract this block into
a helper such as resolve_refresh_token(state.db_pool, client_id, client_secret)
that encapsulates the two DB checks and decrypt_string flow, and keep the
existing generic Unauthorized("Invalid credentials") behavior unchanged for both
failure paths. Use the existing identifiers issue_token, decrypt_string,
user_credentials_table, and service_accounts_table to preserve current behavior
while making the logic easier to test and extend.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@airborne_dashboard/app/dashboard/`[orgId]/users/page.tsx:
- Around line 206-215: Prevent duplicate rotations in handleRotate by guarding
the rotate action so it can only run once at a time; since apiFetch to
/service-accounts/.../rotate is non-idempotent, a second click can create a
stale secret. Update the users/page.tsx handler around handleRotate (and any
related rotate button wiring) to disable the trigger while the request is in
flight or to early-return when a rotation is already pending, and make sure the
UI state is reset in both success and failure paths.
- Around line 169-171: Scope the service-account SWR fetch in the users page so
it only runs when the current user has read access, not just create access, and
make the cache key include org/token context instead of the static
"/service-accounts" string. Update the useSWR call in the service-account
section (and the similar usages referenced elsewhere in the page) so the GET is
gated by the read permission path and the key changes when org or token changes,
preventing stale cross-org metadata reuse and the avoidable 403 toast for
create-only users.
- Around line 306-315: The service-account Role select is hard-coded to
admin/write/read, which prevents assigning custom org roles from the UI. Update
the role picker inside the users page component in page.tsx to use the same
dynamically loaded org role list already used for user management, and bind the
options to the existing role state and change handler rather than static
SelectItem entries. Ensure the service-account creation flow reuses the org
roles source of truth so any custom role available in the organization can be
selected.
- Around line 122-132: Add accessible names to the icon-only copy actions in the
user details UI: the two Button components in the users page that call
copyToClipboard for clientId and clientSecret need aria-labels so screen readers
can distinguish them. Update the Button props in the component that renders the
Client ID and Client Secret rows, using labels that clearly describe each copy
action.

In `@airborne_server/src/organisation/user.rs`:
- Around line 72-79: The user email check in the user creation flow hardcodes
the service-account domain, which duplicates the source of truth. Update the
validation in the user handling logic to use the shared
SERVICE_ACCOUNT_EMAIL_DOMAIN from service_account.rs (make it pub(crate) if
needed) instead of the literal string, so the check stays consistent if the
domain changes.

In `@airborne_server/src/provider/authn/keycloak.rs`:
- Around line 174-232: The create_service_account_user flow leaves a Keycloak
user behind if password_login_common(..., Some("offline_access")) fails after
admin.realm_users_post succeeds. Update create_service_account_user to clean up
the newly created user on any post-creation login error, using the same
KeycloakAdmin instance and the created username as the rollback target. Keep the
existing find_user_by_username duplicate check and ensure the rollback happens
before returning the error so retries do not get blocked by an orphaned Keycloak
identity.

In `@airborne_server/src/service_account.rs`:
- Around line 312-322: The rotate flow in service_account.rs deletes the
existing OIDC user before confirming the replacement can be created, which can
leave the service account without any backing identity if
create_service_account_user fails. Update the rotate sequence in the service
account rotation logic to create the new credentials first, verify success via
authn_provider.create_service_account_user, and only then call
authn_provider.delete_user for the old account so the replacement window is
minimized.
- Around line 99-174: create_service_account has a partial-failure gap across
create_service_account_user, the DB insert, and add_organisation_user: if a
later step fails, the OIDC user is left orphaned. Update the
create_service_account flow to add compensation/rollback after failures from the
DB insert or authz step, using the existing authn_provider and authz_provider
calls plus the generated email/name/client_uid to identify and clean up the
created service account. Ensure the rollback path is best-effort and preserves
the current error propagation from create_service_account.
- Around line 245-255: The best-effort revocation in service_account.rs is
swallowing failures from remove_organisation_user and delete_user, so add error
logging around those calls in the service account deletion flow. Update the
deletion logic near the authz_provider.authn_provider calls to capture and log
any returned errors with enough context (organisation, entry.email, entry.name,
and auth.sub) while keeping the best-effort behavior. Use the existing delete
path in the service account handler as the reference point so operators can see
when identity-provider cleanup failed before the local row is removed.

---

Nitpick comments:
In `@airborne_server/src/service_account.rs`:
- Around line 278-284: The rotate endpoint is currently using the same authz
action as service-account creation, which makes rotation indistinguishable in
policy and audit flows. Update the authz annotation on rotate_service_account to
use a dedicated action name such as "rotate" instead of reusing "create",
keeping the resource and role settings intact so RBAC and audit logs can
differentiate the operations.

In `@airborne_server/src/token.rs`:
- Around line 296-342: The refresh-token resolution logic inside issue_token is
doing too much inline, mixing PAT lookup, service-account fallback, decryption,
and error mapping. Extract this block into a helper such as
resolve_refresh_token(state.db_pool, client_id, client_secret) that encapsulates
the two DB checks and decrypt_string flow, and keep the existing generic
Unauthorized("Invalid credentials") behavior unchanged for both failure paths.
Use the existing identifiers issue_token, decrypt_string,
user_credentials_table, and service_accounts_table to preserve current behavior
while making the logic easier to test and extend.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef4eec4f-9770-4e32-9230-822f7334f71b

📥 Commits

Reviewing files that changed from the base of the PR and between 34d7ecc and 6706030.

📒 Files selected for processing (13)
  • airborne_dashboard/app/dashboard/[orgId]/users/page.tsx
  • airborne_dashboard/next.config.mjs
  • airborne_server/migrations/20260410120000_add_service_accounts/down.sql
  • airborne_server/migrations/20260410120000_add_service_accounts/up.sql
  • airborne_server/src/main.rs
  • airborne_server/src/organisation/user.rs
  • airborne_server/src/provider/authn.rs
  • airborne_server/src/provider/authn/keycloak.rs
  • airborne_server/src/service_account.rs
  • airborne_server/src/service_account/types.rs
  • airborne_server/src/token.rs
  • airborne_server/src/utils/db/models.rs
  • airborne_server/src/utils/db/schema.rs

Comment thread airborne_dashboard/app/dashboard/[orgId]/users/page.tsx Outdated
Comment thread airborne_dashboard/app/dashboard/[orgId]/users/page.tsx Outdated
Comment thread airborne_dashboard/app/dashboard/[orgId]/users/page.tsx
Comment thread airborne_dashboard/app/dashboard/[orgId]/users/page.tsx
Comment thread airborne_server/src/organisation/user.rs
Comment thread airborne_server/src/provider/authn/keycloak.rs
Comment thread airborne_server/src/service_account.rs
Comment thread airborne_server/src/service_account.rs Outdated
Comment thread airborne_server/src/service_account.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants