feat(login): appen kan logge ind og bære sin session som bearer
Første trin mod en iOS-app. Appen pakker den eksisterende flade ind og henter den fra sig selv (capacitor://localhost), så den deler aldrig origin med serveren og får aldrig cookien tilbage. Den logger derfor ind som en browser, med adgangskode og evt. kode, men får sin session i svaret i stedet for i en cookie og sender den som `Authorization: Bearer`. Det er med vilje en SESSION og ikke et API-token: den står i listen over enheder under det navn, appen giver sig selv, kan logges ud derfra, og den når 2FA og nøgler, som et token med vilje ikke gør. - `POST /auth/login` tager `client: "app"` og `device_name`. Svaret får `session_token` og ingen cookie, også mens `totp_required` er sand. - En app-session glider: hver brug flytter udløbet frem, op til `VERDANDE_APP_SESSION_TTL` (180 dage) ubrugt. En browser beholder sin faste levetid. En halvfærdig login glider ikke; den beholder sine ti minutter. - En bearer, der ikke er et API-token, slås op som session, men kun hvis den blev udstedt til appen. En browsers cookie-værdi som header må ikke blive en anden vej forbi SameSite og CSRF-låsen. - CSRF-låsen genkender appens origin. Det var prøven, der fandt det: selve login-kaldet har intet token endnu og blev afvist som cross-site. En hjemmeside kan ikke skrive `Origin`-headeren, og en app-login sætter ingen cookie, så der er ingen session for en forfalsket anmodning at ride på. Passkeys er udenfor i første omgang: RP-ID er bundet til domænet og kræver apple-app-site-association på hver instans. Migration 0037: sessions.client, device_name, idle_ttl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
95e02b567b
commit
4ef050ae04
@@ -120,6 +120,19 @@ paths:
|
||||
properties:
|
||||
email: { type: string, format: email }
|
||||
password: { type: string }
|
||||
client:
|
||||
type: string
|
||||
enum: [web, app]
|
||||
description: |
|
||||
`app` for the iOS app. Its session comes back as
|
||||
`session_token` instead of a cookie and slides — every use
|
||||
moves its end forward, up to `VERDANDE_APP_SESSION_TTL`
|
||||
(180 days) unused. Left out, it is a browser login.
|
||||
device_name:
|
||||
type: string
|
||||
maxLength: 100
|
||||
description: Required with `client: app`; shown in the list of sessions.
|
||||
example: Kristians iPhone
|
||||
responses:
|
||||
"200":
|
||||
description: |
|
||||
@@ -132,7 +145,14 @@ paths:
|
||||
properties:
|
||||
user: { $ref: "#/components/schemas/User" }
|
||||
totp_required: { type: boolean }
|
||||
session_token:
|
||||
type: string
|
||||
description: |
|
||||
An app login only, also while `totp_required` — the app
|
||||
sends it to `/auth/login/totp` too. Sent from then on as
|
||||
`Authorization: Bearer`. A browser never receives it.
|
||||
"401": { $ref: "#/components/responses/Unauthorized" }
|
||||
"422": { $ref: "#/components/responses/Validation" }
|
||||
"429": { $ref: "#/components/responses/RateLimited" }
|
||||
|
||||
/api/v1/auth/login/totp:
|
||||
@@ -4439,6 +4459,10 @@ components:
|
||||
A personal API token, `vrd_…`. Carries its owner's permissions exactly —
|
||||
there is no separate service identity — and is refused on `/tokens`.
|
||||
|
||||
Or the iOS app's session, the `session_token` from an app login. That
|
||||
one is a session and reaches everything a browser session does. A
|
||||
browser's own session is never accepted here.
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: The body could not be read.
|
||||
@@ -4973,6 +4997,12 @@ components:
|
||||
it so anything the summary gets wrong is still visible.
|
||||
example: Chrome på macOS
|
||||
user_agent: { type: string }
|
||||
client:
|
||||
type: string
|
||||
enum: [web, app]
|
||||
description: |
|
||||
`app` for the iOS app, whose `device` is the name it gave itself and
|
||||
whose `expires_at` moves forward each time it is used.
|
||||
ip: { type: string }
|
||||
created_at: { type: string, format: date-time }
|
||||
last_seen_at: { type: string, format: date-time }
|
||||
|
||||
Reference in New Issue
Block a user