diff --git a/documentation/access-requests.md b/documentation/access-requests.md index c43179e..d0ebf7b 100644 --- a/documentation/access-requests.md +++ b/documentation/access-requests.md @@ -93,6 +93,49 @@ curl --location 'http://localhost:4000/uma/policies' --header 'Authorization: We 3. Verify that the request appears in Alice's **Accepted** tab. 4. Verify that the request appears in Bob's **Accepted** tab. +## Deep-linking to a specific access request (Grant Access page) + +External clients can redirect a resource owner directly to the **Grant Access** page with a specific incoming request pre-filtered, so they immediately see the one they need to act on. + +### How it works + +Append: + +- a `request` query parameter whose value is the URL-encoded UID of the access request +- an optional `returnUrl` query parameter whose value is the URL-encoded callback URL of the originating client + +``` +/access-grants/?request=&returnUrl= +``` + +**Example:** + +``` +http://localhost:5173/access-grants/?request=http%3A%2F%2Fexample.org%2Fc432df06-9bae-4a20-a21d-0e30833552b0 +``` + +```text +http://localhost:5173/access-grants/?request=http%3A%2F%2Fexample.org%2Fc432df06-9bae-4a20-a21d-0e30833552b0&returnUrl=https%3A%2F%2Fclient.example%2Fgrant-callback +``` + +When the parameter is present the page will: + +1. Show a **purple filter banner** at the top: *"Active filter: showing only the request you were directed to."* +2. Display **only the matching request**, with the Accept / Deny buttons available as normal. +3. Provide a **"Show all requests"** button inside the banner to clear the filter and return to the full grouped view (Requested / Accepted / Denied). + +If the UID in the parameter does not match any known request, a *"The requested access request could not be found."* message is shown instead. + +When `returnUrl` is provided, clicking **Accept** or **Deny** redirects the user to that URL only in the filtered deep-link flow (the `request` query is active and matches the acted item). The redirect appends: + +- `request=` +- `decision=accepted` or `decision=denied` + +### Usage notes + +- The filter is applied purely client-side via the Vue Router query parameter; no server changes are required. +- Clicking **"Show all requests"** removes the `request` parameter from the URL via `router.replace`, so the browser history is not polluted with the filtered URL. + ## Known bugs and limitations * **UI State Sync:** When you update a policy on a selected resource, the interface does not visually refresh until you manually deselect and reselect that resource. diff --git a/loama/src/components/access-grants/AccessGrant.vue b/loama/src/components/access-grants/AccessGrant.vue index 9c64f36..d6362ee 100644 --- a/loama/src/components/access-grants/AccessGrant.vue +++ b/loama/src/components/access-grants/AccessGrant.vue @@ -1,14 +1,49 @@ @@ -190,4 +269,42 @@ button.deny:hover { text-align: center; padding: 1rem; } + +.filter-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + background-color: color-mix(in srgb, var(--solid-purple) 10%, white); + border: 1.5px solid var(--solid-purple); + border-radius: var(--base-corner); + padding: 0.75rem 1.25rem; + font-size: calc(var(--base-unit) * 1.75); + color: var(--solid-purple); +} + +.clear-filter-button { + background: none; + border: 1.5px solid var(--solid-purple); + color: var(--solid-purple); + border-radius: var(--base-corner); + padding: 0.4rem 0.9rem; + font-weight: 600; + font-size: calc(var(--base-unit) * 1.75); + cursor: pointer; + white-space: nowrap; + transition: background-color 0.2s ease, color 0.2s ease; +} + +.clear-filter-button:hover { + background-color: var(--solid-purple); + color: white; +} + +.access-request-item.highlighted { + border-radius: var(--base-corner); + outline: 2.5px solid var(--solid-purple); + outline-offset: 2px; + background-color: color-mix(in srgb, var(--solid-purple) 5%, white); +}