Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 142 additions & 15 deletions docs/_static/env-vars/auth-guest_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ OpenCloud account. When a share is created for a user of type
that token exchanges it for a signed session cookie that authenticates the
guest.

When the session or the link token is no longer valid, the guest can *renew*:
the service issues a new link token together with a one-time PIN. The guest
then either redeems the new link or exchanges the PIN for a fresh session.

It is disabled by default. Set `OC_ENABLE_GUEST_LINKS=true` to enable the guest
links feature and start the service.

Expand All @@ -17,19 +21,24 @@ links feature and start the service.

* [Overview](#overview)
* [Guest links flow](#guest-links-flow)
* [Renewal and PIN flow](#renewal-and-pin-flow)
* [Token lifecycle](#token-lifecycle)
* [Endpoints](#endpoints)
* [`POST .../guestLinks/verify/token`](#post-guestlinksverifytoken)
* [`POST .../guestLinks/renew`](#post-guestlinksrenew)
* [`POST .../guestLinks/verify/pin`](#post-guestlinksverifypin)
* [Configuration](#configuration)

## Overview

- **Consumes** the share lifecycle events `ShareCreated`, `ShareRemoved` and
`ShareExpired`.
- **Publishes** the `GuestTokenCreated` event carrying the guest link token,
so the link can be delivered to the guest.
- Exposes an unauthenticated endpoint that redeems the token and sets a
session cookie.
- Stores only hashes of the token and deletes the stored record when the share
is removed or expires.
- **Publishes** the `GuestTokenCreated` event (initial token) and the
`GuestTokenRenewed` event (refresh, carrying the new token and the PIN).
- Exposes unauthenticated endpoints to redeem a token, renew it and verify a
PIN; redeeming a token or verifying a PIN sets a session cookie.
- Stores only hashes of the token secret and of the PIN and deletes the stored
record when the share is removed or expires.

## Guest links flow

Expand All @@ -53,7 +62,7 @@ sequenceDiagram
Reva->>-Redeem: Share
Note right of Redeem: Validate Share, Mark Token used
Redeem->>-Web: Set Cookie, return shareid
Note right of Web: HTTP only cookie with signed JWT (JWT lifetime 24h)
Note right of Web: HTTP only cookie with signed JWT (24h by default)
Web->>+Proxy: "/graph/me/drives/sharedWithMe"
Proxy->>+Reva: validate token extracted from JWT
Note right of Reva: Sign Reva Token for Guest User
Expand All @@ -78,27 +87,145 @@ sequenceDiagram
Proxy->>-Web: PROPFIND Response
```

## Renewal and PIN flow

A guest session (signed JWT) is valid for 24 hours by default
(`AUTH_GUEST_JWT_TTL`); a link token is single-use and valid for 30 minutes.
Renewal starts from a *failed* attempt:

- an authenticated request made with an expired session is rejected with `401`;
the browser still holds the (expired) session cookie;
- `redeem` on a link that was already used answers `409`, and on an expired
link `401`; the client still holds the link token.

`renew` requires the share id (`permissionId`) and is authorized by either the
(possibly expired) session cookie or the previous link token, whichever the
client has; the credential must belong to the given share. It does not create a
session. It requires an existing token record for the share, validates the
share, generates a new link token and a 6-digit PIN (both valid for 30 minutes),
overwrites the stored token and PIN hashes (invalidating the previous link and
PIN), resets the redeemed flag so the new link can be redeemed, and publishes
`GuestTokenRenewed`. It answers `200`. This works whether the record is unused,
expired or already redeemed (the guest already redeemed an earlier link); if
publishing fails, the previous record is restored.

To turn the new credentials into a session the guest either redeems the link
(`verify/token`) or enters the PIN (`verify/pin`); both return a session
cookie and the `permissionId`. `verify/pin` does not consume the link token.

```mermaid
sequenceDiagram
autonumber
actor User as Guest user
participant Web as Web client
participant Renew as Renew endpoint
participant Redeem as Redeem endpoint
participant Pin as Verify PIN endpoint
participant Bus as Event bus

Note over Web: Session expired / previous link used or expired
Web->>+Renew: POST renew { permissionId } (+ session cookie or link token)
Note right of Renew: Validate share, generate token + PIN, store hashes
Renew->>Bus: publish GuestTokenRenewed
Note right of Bus: External consumer delivers token + PIN (out of scope)
Renew->>-Web: 200 OK

alt Guest opens the new link
User->>Web: Open link
Web->>+Redeem: POST redeem { token }
Note right of Redeem: Validate token + share, mark used
Redeem->>-Web: Set Cookie, return permissionId
else Guest enters the PIN
User->>Web: Enter PIN
Web->>+Pin: POST verify/pin { pin, permissionId }
Note right of Pin: Validate share, consume PIN (single-use)
Pin->>-Web: Set Cookie, return permissionId
end
Note over Web: HTTP only cookie, signed JWT (24h default)
```

## Token lifecycle

1. **Issue** — on the consumed `ShareCreated` event, where the grantee is a
guest, the service generates a random secret and stores a record keyed by
the hash of the share id. It then publishes the `GuestTokenCreated` event
with the token.
2. **Redeem** — the guest posts the token to
`POST /graph/v1beta1/extensions/org.libregraph/guestLinks/redeem`.
The service validates the token and the share, marks the token as used
and returns a signed JWT session token in a cookie plus the share's
`permissionId` in the response body. Tokens are single-use.
3. **Cleanup** — on the consumed `ShareRemoved` or `ShareExpired` event, the
stored record is deleted.
`POST /graph/v1beta1/extensions/org.libregraph/guestLinks/verify/token`.
The service validates the token and the share, marks the token as used and
returns a signed JWT session token in a cookie plus the share's
`permissionId` in the response body. Tokens are single-use and valid for
30 minutes; the session cookie lives for 24 hours by default
(`AUTH_GUEST_JWT_TTL`).
3. **Renew** — the guest posts the `permissionId` to
`POST /graph/v1beta1/extensions/org.libregraph/guestLinks/renew`, authorized
by either the previous link token (`{ "token": "<token>" }`) or the (possibly
expired) session cookie, which must both belong to that share. An existing
record is required; otherwise the request fails with `tokenNotFound`. The
service validates the share, generates a new link token and a 6-digit PIN
(both valid for 30 minutes), overwrites the stored token and PIN hashes
(invalidating the previous link and PIN) and resets the redeemed flag so the
new link can be redeemed, then publishes the `GuestTokenRenewed` event. This
works whether the record is unused, expired or already redeemed. If
publishing fails, the previous record is restored.
4. **Verify PIN** — the guest posts the PIN and the `permissionId` to
`POST /graph/v1beta1/extensions/org.libregraph/guestLinks/verify/pin`.
The service validates the record and the share, compares the PIN against the
stored argon2id hash and, on success, clears the PIN (single-use) and
returns a signed JWT session token in a cookie plus the share's
`permissionId`. Verifying the PIN does not consume the magic link.

### Endpoints

All error responses have the body
`{ "errorType": "<type>", "message": "<msg>", "permissionId": "<share-id>" }`.

#### `POST .../guestLinks/verify/token`

- **Request body:** `{ "token": "<token>" }`
- **Success:** `200`, session cookie,
`{ "permissionId": "<share-id>" }`
- **Errors:** `400 invalidRequest`, `401 tokenInvalid`,
`401 tokenExpired`, `404 tokenNotFound`, `409 tokenAlreadyRedeemed`,
`404 shareNotFound`, `410 shareExpired`, `500 internalError`

#### `POST .../guestLinks/renew`

- **Request body:**
`{ "permissionId": "<share-id>", "token": "<previous link token>" }`
(`token` optional when the session cookie is sent)
- **Authorization:** the share id is required; the body link token or the
session cookie must belong to it, the link token takes precedence
- **Success:** `200` (no session cookie; the new link/PIN are delivered out
of band)
- **Errors:** `400 invalidRequest`, `401 tokenInvalid`, `401 sessionInvalid`,
`404 tokenNotFound`, `404 shareNotFound`, `410 shareExpired`,
`409 tokenAlreadyRedeemed`, `503 serviceUnavailable`, `500 internalError`

#### `POST .../guestLinks/verify/pin`

- **Request body:** `{ "pin": "<pin>", "permissionId": "<share-id>" }`
- **Success:** `200`, session cookie,
`{ "permissionId": "<share-id>" }`
- **Errors:** `400 invalidRequest`, `401 pinInvalid`, `401 pinExpired`,
`404 shareNotFound`, `410 shareExpired`, `500 internalError`

## Configuration

The service is configured via `AUTH_GUEST_*` environment variables or a
`auth-guest.yaml` file.

To run only the HTTP part, set `AUTH_GUEST_EVENTS_DISABLED=true`. To run only
the event consumer, set `AUTH_GUEST_HTTP_DISABLED=true`.
Both transports are enabled by default: the service consumes and publishes
events and serves the HTTP endpoints. Either transport can be disabled:

- `AUTH_GUEST_EVENTS_DISABLED=true` — the service does not consume events
(no tokens are created or cleaned up) and `renew` cannot publish its event,
so it responds with `503 serviceUnavailable`.
- `AUTH_GUEST_HTTP_DISABLED=true` — the service does not serve the HTTP
endpoints (`verify/token`, `renew`, `verify/pin`).

The token and PIN lifetimes are fixed (30 minutes each) and are not
configurable.

Relevant options:

Expand Down
2 changes: 1 addition & 1 deletion docs/_static/env-vars/frontend_configvars.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

2026-10-13-00-11-11
2026-10-14-00-06-59

## Deprecation Notice

Expand Down
Loading
Loading