feat(profile-B): OIDC profile+phone scopes, consent screen, claim emission (BUNYIP-140) #170
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/bunyip!170
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/bunyip-140-oidc-profile-scopes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Second slice of BUNYIP-103. Depends on BUNYIP-139's storage columns.
After this lands, bunyip emits given_name / family_name / phone_number
standard OIDC claims when an RP requests + the user consents to the
profile / phone scopes; the first time a (user, client) pair encounters
a new scope, a consent screen lists which fields each scope discloses.
Migrations:
profile+phoneto allowed_scopes of mokosh-apps,drillmark, lets-chat-psa (idempotent guarded by @>).
profile+phonefrom any existinguser_application_access.granted_scopes rows so existing users re-consent
the first time an RP requests these scopes after deploy. Without it, the
prior JIT path's grant-everything posture would have silently auto-granted
the new scopes for every existing user.
Bunyip-api:
add_scopes_to_grant unions new scopes into the row (upsert).
baseline-only grant of (openid, email, offline_access). Scopes outside
the baseline must reach granted_scopes via the consent flow.
redirects to bunyip-web /oauth2/consent with client_id + missing +
continue URL preserved in the query string.
the scopes into the user_application_access row.
scope set contains
profileAND the column is non-NULL; phone_numberon
phone. NULL columns serialize as ABSENT keys (not empty strings).IdTokenClaims.extra BTreeMap (BUNYIP-63 pattern).
Bunyip-web:
requested scope and the concrete fields it discloses. POST routes
Allow -> POST /v1/users/me/consents + redirect to continue URL;
Deny -> dashboard with flash. (Standard "Deny 302s the RP with
error=access_denied" is a follow-up.)
Runtime sqlx queries (not the
query!macro) so this slice does notneed a .sqlx/ regen.
#BUNYIP-140
#BUNYIP-103
Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com
Second slice of BUNYIP-103. Depends on BUNYIP-139's storage columns. After this lands, bunyip emits given_name / family_name / phone_number standard OIDC claims when an RP requests + the user consents to the profile / phone scopes; the first time a (user, client) pair encounters a new scope, a consent screen lists which fields each scope discloses. Migrations: - 20260618073849: add `profile` + `phone` to allowed_scopes of mokosh-apps, drillmark, lets-chat-psa (idempotent guarded by @>). - 20260618073850: retroactively strip `profile` + `phone` from any existing user_application_access.granted_scopes rows so existing users re-consent the first time an RP requests these scopes after deploy. Without it, the prior JIT path's grant-everything posture would have silently auto-granted the new scopes for every existing user. Bunyip-api: - OidcProvider::get_granted_scopes(user, client) reads the granted set; add_scopes_to_grant unions new scopes into the row (upsert). - /oauth2/authorize replaces the "grant every allowed_scope" JIT with a baseline-only grant of (openid, email, offline_access). Scopes outside the baseline must reach granted_scopes via the consent flow. - /oauth2/authorize gates on requested - granted; non-empty missing set redirects to bunyip-web /oauth2/consent with client_id + missing + continue URL preserved in the query string. - POST /v1/users/me/consents accepts { client_id, scopes } and unions the scopes into the user_application_access row. - /oauth2/userinfo emits given_name / family_name when the access token's scope set contains `profile` AND the column is non-NULL; phone_number on `phone`. NULL columns serialize as ABSENT keys (not empty strings). - id_token mint adds the same claims under the same gates via the existing IdTokenClaims.extra BTreeMap (BUNYIP-63 pattern). Bunyip-web: - New handlers::consent module renders the Allow / Deny card listing each requested scope and the concrete fields it discloses. POST routes Allow -> POST /v1/users/me/consents + redirect to continue URL; Deny -> dashboard with flash. (Standard "Deny 302s the RP with error=access_denied" is a follow-up.) - /oauth2/consent route wired in main.rs. Runtime sqlx queries (not the `query!` macro) so this slice does not need a .sqlx/ regen. #BUNYIP-140 #BUNYIP-103 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>