Users with stored remoteStorage authorizations now jump straight to the connected apps dashboard from the services dashboard, instead of the storage info page.
The exact issuance and expiry times in the device rows' hover titles were
rendered in UTC, which reads as wrong to anyone not living there.
- Mark the dates with a <time> element carrying the absolute (UTC) timestamp,
rather than a custom data attribute whose name claimed to hold local time
- Rewrite those titles in the browser's own zone, keeping the server's UTC
rendering as the fallback for when JavaScript is unavailable
- Only convert values that carry an explicit offset, so a date-only <time> is
not read as UTC midnight and shifted onto the previous day
- Give the revoke button a little more room from the device name and dates
Part of #254
Revoking a device redirected away from the storage dashboard, which closed the
details modal and reloaded the page. Refreshing the affected parts instead
keeps the modal open and shows the change where it happened.
- Extract the card and the modal's access list into their own components, so
either can be re-rendered without touching the modal's open state, and share
the group summaries between the components
- Derive the app's DOM ids from its client_id (the grouping key) and the device
row ids from the authorization, through one helper used by both the templates
and the controller
- Revoke one of several devices as a Turbo Stream: remove the row and refresh
the card behind the modal and the access list
- Revoke an app's last device with a redirect, so the dashboard re-renders
itself and grouping, sorting and the empty state stay in one place
- Fade the removed row out, deferring Turbo's own removal until the animation
has ended
Part of #254
Tokens issued before user agents were captured cannot be attributed to a
single device, and the per-app tokens issued earlier could even be reused
from several devices at once.
- Add RemoteStorageAuthorization#device_label, falling back to
"Unknown device(s)" so the card and the details modal share one source
- Show a lower-bound device count with a "+" whenever an app has such a
token, so it reads as "could be more" instead of claiming a number we
cannot support
- Drop the now-redundant device-name wrapper from the details modal
Part of #254
Show the union of the app's authorizations' scopes in the modal's Access
section too, so it matches the card's summary, and cover the union
summary with a feature spec.
Part of #254
- One card per app (grouped by client_id) showing distinct device and
category counts; the whole card opens the details modal (with hover and
focus highlight) instead of a separate Details button
- Details modal: access list styled like the OAuth consent dialog via a
shared RsScopeListComponent, plus per-token device, connected date and
expiry, each with its own Revoke
- "folder" -> "category" in the card, modal, consent dialog and the
auth-created notification
- Make revoke a real DELETE: it was a GET link that Turbo 8's
prefetch-on-hover fired on its own, silently deleting authorizations.
Missing or foreign authorizations now return 404 instead of 500.
- Move "Launch app" to the app level
(GET /services/storage/apps/:client_id/launch); no token id in the URL
Part of #254
- Rework RsAuthComponent into a card that shows the app, its device, an
access summary and the permission status
- Group connected apps by app and device, so repeated authorizations for
the same app and device (e.g. after a browser update) are shown together,
each with its own revoke action
- Add RsAuthDetailsModalComponent with the full scope list, device,
connected tokens and permission expiry, plus Launch and Revoke actions
(built on the existing ModalComponent)
- Add RemoteStorageAuthorization#device_name (parsed from the user agent)
and #expired?
Part of #254
- Make authorization-code redemption atomic: claim the code with a guarded
delete inside the transaction that creates the authorization, so concurrent
exchanges can't both issue a token (RFC 6749 4.1.3)
- Compare the token request's client_id host to the code's stored client_id
(defense-in-depth alongside the exact redirect_uri match)
- Reject unknown response_type values with unsupported_response_type
- Validate the PKCE code_challenge (43-128 unreserved chars, S256) so a client
can't persist an over-long or malformed challenge
- Return invalid_request for missing token-endpoint parameters, and set
Cache-Control: no-store on error responses too
- Use the right separator when the redirect_uri already has a query string
- Look codes up through the active scope; drop the unused consume!; serialize
permissions as JSON
- Purge abandoned authorization codes via a recurring Solid Queue task
Part of #254
Implements the optional OAuth 2.0 Authorization Code grant with PKCE
(RFC 7636) for remoteStorage, as described in the remoteStorage spec
(section 10.1).
- Advertise the authorization and token endpoints plus the supported
code_challenge method (S256) in WebFinger, and bump the advertised
spec version to draft-dejong-remotestorage-27
- Add RsAuthorizationCode: a short-lived (5 min), single-use authorization
code stored only as a SHA256 digest
- Add RsOauthManager services for issuing and exchanging codes, including
S256 verification
- Add a cross-origin POST /rs/oauth/token endpoint that exchanges a code
(and code_verifier) for an access token, with CORS and no CSRF token
- Consent dialog now carries response_type / code_challenge through the
form and redirects with the code in the query string for the code flow
- Remove the existing-token reuse shortcut so every authorization results
in a distinct authorization (and token)
Part of #254
We use a custom auth method to pre-fill the username when reaching the
RS OAuth while signed out. However, it needs to redirect back to the RS
OAuth page after sign-in, and not to the root path.