Skip to content

PUT and GET APIs for projects, credentials and collections, with policies that decide from the caller's scope - #5239

Draft
stuartc wants to merge 17 commits into
mainfrom
stuart/con-331-con-334-credentials-collections
Draft

stuartc wants to merge 17 commits into
mainfrom
stuart/con-331-con-334-credentials-collections

Conversation

@stuartc

@stuartc stuartc commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Description

Adds idempotent PUT and GET endpoints for projects, credentials and collections, open to a person's token or the service account, and moves every decision about who may call them into the policies. It's the Lightning side of jig's apply for CON-329 (projects), CON-331 (credentials) and CON-334 (collections). The API contract is ADR 0024 in the jig repo. It replaces #5238, which this stacked on.

The new endpoints:

  • PUT /api/projects/:id creates a project under a client-chosen id or replaces its name, description and members; GET is open to the service account too.
  • PUT and GET /api/credentials/:id. A service account names the owner by email; a person can only name themselves. Project links are only ever added.
  • PUT and GET /api/projects/:project_id/collections/:id.
  • Service-account scopes projects:write, credentials:write and collections:write. Sending the same body twice changes nothing and writes no audit event.

How authorisation works now (see SERVICE_ACCOUNTS.md):

  • Authentication only proves who is calling. Lightning.Scope (replacing Lightning.Projects.Scope) describes the caller: actor, grants, instance role, MFA status and, once a project is involved, facts about it such as being scheduled for deletion.
  • Policies decide from the scope in one order: grant, then standing, then project state. So a missing grant is 403 insufficient_scope, no standing is 403, and only a caller who would otherwise be allowed hears 409 scheduled_for_deletion. That now includes /api/provision.
  • Controllers ask the policy by action name through the AuthorizeAction plug, before any body parse or lookup.
  • Writes record an actor (Lightning.Actor: a user or the service account) in the audit trail. Projects, credentials and collections are covered.

Commits

Each commit compiles on its own, so it reads well commit by commit:

  1. Service-account standing in Scope and ProjectUsers, and Lightning.Actor
  2. Projects API
  3. Credentials API
  4. Collections API
  5. Policies decide from the scope (most of the behaviour changes below land here)

Behaviour changes for existing callers

  1. A service-account token request with no scope is refused with 400 invalid_scope instead of getting every scope.
  2. GET /api/projects/:id refusals are 403, not 401.
  3. Updating a credential scheduled for deletion is refused for everyone (API PUT gives 409; the LiveView form from a stale page too). Scheduling one again is refused. "Delete now" and the purge still work.
  4. Repo-connection tokens get 403 insufficient_scope with a WWW-Authenticate header on the new APIs, and on their own project's older read routes (was 401).
  5. A credential response names a linked project only to a caller allowed :access_project on it. Show and PUT still list every link id.
  6. Credential audit events name whoever made the change, and a body-only change now writes an updated event.
  7. Path ids must be written-out UUIDs.
  8. update_collection no longer casts project_id, so the superuser edit modal can't move a collection between projects.

Validation steps

  1. mix test test/lightning_web/controllers/api test/lightning/policies test/lightning/scope_test.exs
  2. With SERVICE_ACCOUNT_PUBLIC_KEY set, get a token with scope=projects:write credentials:write collections:write, then PUT a project, a credential and a collection twice with the same body. Expect 201 then 200, and no second audit event.
  3. jig's scripts/e2e.sh against this branch runs the lot end to end.

Additional notes for the reviewer

Needs a deliberate yes:

  1. With credentials:write a service account can create a credential owned by any user and link it to any project (ADR 0022). The 409 id_taken on an owner mismatch is what stops it rewriting someone else's credential.
  2. A person naming any owner but themselves gets 403, even for an unknown email, so the endpoint can't be used to find accounts.
  3. API DELETE of a scheduled credential still deletes it at once, as today.

Follow-ups are in Linear: CON-357 (remaining policies onto Scope), CON-358 (kit's 409 message), CON-359 (contexts take a Scope, plus a Credo check).

No CHANGELOG entry yet; I'll write those just before merge.

AI Usage

Please disclose whether you've used AI anywhere in this PR (it's cool, we just want to know!):

  • I have used Claude Code
  • I have used another model
  • I have not used AI

You can read more details in our Responsible AI Policy

Pre-submission checklist

  • I have performed an AI review of my code (we recommend using /review with Claude Code)
  • I have implemented and tested all related authorization policies. (e.g., :owner, :admin, :editor, :viewer)
  • I have updated the changelog. Left out on purpose until just before merge.
  • I have ticked a box in "AI usage" in this PR

@github-project-automation github-project-automation Bot moved this to New Issues in Core Oct 9, 2026
@stuartc
stuartc added this pull request to stack #5240 October 9, 2026 19:38
@codecov

codecov Bot commented Oct 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.73684% with 12 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (stuart/con-329-projects@9d2dd33). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...tning_web/controllers/api/credential_controller.ex 91.6% 7 Missing ⚠️
...tning_web/controllers/api/collection_controller.ex 93.5% 3 Missing ⚠️
lib/lightning/collections.ex 85.7% 2 Missing ⚠️
Additional details and impacted files
@@                    Coverage Diff                    @@
##             stuart/con-329-projects   #5239   +/-   ##
=========================================================
  Coverage                           ?   91.5%           
=========================================================
  Files                              ?     467           
  Lines                              ?   23627           
  Branches                           ?       0           
=========================================================
  Hits                               ?   21613           
  Misses                             ?    2014           
  Partials                           ?       0           

☔ 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.

@stuartc
stuartc force-pushed the stuart/con-331-con-334-credentials-collections branch from bc9ce4c to a64781c Compare October 12, 2026 14:25
… add Lightning.Actor for the person or service account a context function records
…n or a service account with collections:write
@stuartc
stuartc force-pushed the stuart/con-331-con-334-credentials-collections branch from a64781c to 3a2c84f Compare October 12, 2026 14:33
@stuartc
stuartc removed this pull request from stack #5240 October 12, 2026 14:35
@stuartc stuartc changed the title PUT and GET APIs for credentials and collections, open to service accounts PUT and GET APIs for projects, credentials and collections, with policies that decide from the caller's scope Oct 12, 2026
@stuartc
stuartc changed the base branch from stuart/con-329-projects to main October 12, 2026 14:35
@stuartc stuartc closed this Oct 13, 2026
@stuartc
stuartc deleted the stuart/con-331-con-334-credentials-collections branch October 13, 2026 09:55
@github-project-automation github-project-automation Bot moved this from New Issues to Done in Core Oct 13, 2026
@stuartc
stuartc restored the stuart/con-331-con-334-credentials-collections branch October 13, 2026 09:55
@stuartc stuartc reopened this Oct 13, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant