Skip to content

feat(fe): add attribute consent screen for explicit user approval#3771

Open
sea-snake wants to merge 16 commits intosea-snake/icrc3-text-typesfrom
sea-snake/attribute-consent
Open

feat(fe): add attribute consent screen for explicit user approval#3771
sea-snake wants to merge 16 commits intosea-snake/icrc3-text-typesfrom
sea-snake/attribute-consent

Conversation

@sea-snake
Copy link
Copy Markdown
Contributor

@sea-snake sea-snake commented Apr 10, 2026

Motivation

When apps request user attributes (email, name, etc.) via ICRC-3, there's no UI for users to review and approve what's being shared. The existing authorize flow was built for a single path (authenticate → delegate → close) and its isAuthenticating flag immediately hid all layout children, making it impossible to show a consent screen after delegation.

Changes

Architecture: request-driven authorize flow

Replaced the isAuthenticating boolean with a request-driven architecture:

  • New authorizeFlowComplete store — Layout shows children when false, redirect/close screen when true.
  • Idle timeout — Starts after each channel.send() response via a new "response" event on the Channel interface. Resets when new requests arrive. Fires when no more requests come in within the timeout window (500ms).
  • AuthorizationChannel handles both icrc34_delegation and ii-icrc3-attributes requests, resolving the auth UI for either.
  • authorizationStore.authorize() is now pure business logic — no UI state. AuthorizationContext has optional authRequest/requestId (only present when delegation was requested).
  • Channel interface — Added "response" event, implemented in both PostMessageChannel and LegacyChannel.

Attribute consent screen

  • New route /authorize/consent inside (panel) layout with checkboxes per attribute group.
  • Co-located utilities (consent/utils.ts) — parseAttributeKey, isImplicitConsentAttribute, needsConsentScreen, buildConsentGroups with email/verified_email merging.
  • Continue page — After delegation, checks for pending ICRC-3 attribute request and navigates to consent if needed. Skips delegation for attributes-only flow.
  • Consent page — Sends ICRC-3 attribute response after user approval. "Deny All" proceeds without non-implicit attributes.
  • Attribute labels use locale store for i18n.
  • Validates icrc95DerivationOrigin from attribute request params.

Deferred

  • OpenID flow consent redirect (needs dedicated e2e coverage).
  • Standalone attribute requests need further work for the auth flow without delegation context.

← Previous: #3770

- Scenario 2 (explicit): continue page checks for pending attribute
  requests and redirects to /authorize/consent. Authorization proceeds
  in parallel.
- Scenario 1 (OpenID): resume-openid-authorize checks needsConsentScreen
  before processing attributes. Redirects to consent page for non-implicit
  attributes.
- Scenario 3 (standalone): AuthorizationChannel listens for attribute
  requests without prior delegation and navigates to consent page.
- channelStore: add ii_attributes and ii-icrc3-attributes to scopes.
@sea-snake sea-snake requested a review from a team as a code owner April 10, 2026 15:45
Wire up both legacy (prepare_attributes/get_attributes) and ICRC-3
(prepare_icrc3_attributes/get_icrc3_attributes) response flows in
the consent page's handleContinue handler.
- Validate icrc95DerivationOrigin in attribute requests using the same
  validateDerivationOrigin used by the authorization store.
- Add icrc95DerivationOrigin to Icrc3AttributesParamsSchema.
- Consent page derives effectiveOrigin from derivationOrigin or channel
  origin, independent of authorizationContextStore.
- Clean up unused imports.
…ders

When a standalone ii-icrc3-attributes request arrives without a
delegation, resolve the authorizeChannel promise so the auth UI
renders. After the user authenticates, continue/+page.svelte picks
up the pending attribute request and redirects to consent.
The consent screen redirect in resume-openid-authorize was breaking
the existing ICRC-3 attribute e2e tests. The OpenID flow consent
integration will be added in a follow-up with proper e2e coverage.
…textStore crash

The standalone ii-icrc3-attributes listener was resolving the auth
channel promise, causing the authorize UI to render without an
authorization context set — crashing authorizationContextStore.
Standalone flow deferred to a follow-up.
addEventListener emits pending requests synchronously during the call.
Store the result in a variable and unsubscribe in the next microtask.
Replace the isAuthenticating flag (which blocked consent rendering) with
a request-driven architecture:

- New authorizeFlowComplete store: layout shows children when false,
  redirect screen when true.
- Idle timeout: starts after each channel.send() response via a new
  'response' event on the Channel interface. Resets when new requests
  arrive. Fires when no more requests come in within the timeout window.
- AuthorizationChannel handles both icrc34_delegation and
  ii-icrc3-attributes requests, resolving the auth UI for either.
- authorizationStore.authorize() is now pure business logic — no UI state.
- AuthorizationContext: authRequest and requestId are optional (only
  present when delegation was requested).
- Channel interface: added 'response' event, implemented in both
  PostMessageChannel and LegacyChannel.
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.

1 participant