# Keibi ## Features - Not an oauth provider/no login page (as in you don't redirect to this, you create your own auth page) - [Phantom tokens](https://curity.io/resources/learn/phantom-token-pattern/) - Session based tokens (valid for 30 days, reset after each use [configurable]) - Last online/last connection stored per user (and token) - Device used per session/token - Username/password login - OIDC (login via Google, Discord, Authentik, whatever) - Custom jwt claims (for your role/permissions handling or something else) - Guest handling (only if using `GUEST_CLAIMS`) - Api keys support - Optionally [Federated](#federated) ## Routes ### Lifecycle Login: `POST /session { login, password } -> token` `GET /login/$provider { redirectUrl, tenant? } -> redirect` Register: `POST /users { email, username, password } -> token` Logout `DELETE /session` w/ optional `?session=id` `/jwt` retrieve a jwt from an opaque token (also update last online value for session & user) ### Profiles ``` Get `/users` -> user[] Get/Put/Patch/Delete `/users/$id` (or /users/me) -> user Get/Post/Delete `/users/$id/logo` (or /users/me/logo) -> png ``` Put/Patch of a user can edit the password if the `oldPassword` value is set and valid (or the user has the `users.password` permission).\ Should require an otp from mail if no oldPassword exists (see todo). Put/Patch can edit custom claims (roles & permissons for example) if the user has the `users.claims` permission). Read others requires `users.read` permission.\ Write/Delete requires `users.write` permission (if it's not your account). POST /users is how you register. ### Sessions GET `/sessions` list all of your active sessions (and devices) POST `/sessions` is how you login Delete `/sessions` (or `/sessions/$id`) is how you logout GET `/users/$id/sessions` can be used by admins to list others session ### Api keys ``` Get `/apikeys` Post `/apikeys` {...claims} Create a new api keys with given claims ``` An api key can be used like an opaque token, calling /jwt with it will return a valid jwt with the claims you specified during the post request to create it. Creating an apikeys requires the `apikey.write` permission, reading them requires the `apikey.read` permission. ### OIDC ``` `/login/$provider` {redirectUrl, tenant?} `/logged/$provider` {code, state, error} (callback called automatically, don't call it manually) `/callback/$provider` {code, tenant?} (if called with the `Authorization` header, links account w/ provider else create a new account) (see diagram below) `/unlink/$provider` Remove provider from current account `/providers` -> provider[] ``` ```mermaid sequenceDiagram participant App participant Browser participant Kyoo participant Google App->>Kyoo: /login/google?redirectUrl=/user-logged Kyoo->>Browser: redirect auth.google.com?state=id=guid,url=/user-logged&redirectUrl=/logged/google Browser-->>Google: Access login page Google->>Browser: redirect /logged/google?code=abc&state=id=guid,url=/user-logged Browser-->>Kyoo: access /logged/google?code=abc&state=id=guid,url=/user-logged Kyoo->>App: redirect /user-logged?token=opaque&error= App->>Kyoo: /callback/google?token=opaque Kyoo->>Google: auth.google.com/token?code=abc Google->>Kyoo: jwt token Kyoo->>Google: auth.google.com/profile (w/ jwt) Google->>Kyoo: profile info Kyoo->>App: Token if user exist/was created ``` In the previous diagram, the code is stored by Kyoo and an opaque token is returned to the client to ensure only Kyoo's auth service can read the oauth code. ## Federated You can use another instance to login via oidc you have not configured. This allows an user to login/create a profile without having an api key for the oidc service. This won't allow you to retrive a provider's jwt token, you only get a profile with basic information from the provider. This can be usefull for self-hosted apps where you don't want to setup ~10 api keys just for login. ```mermaid sequenceDiagram participant App participant Browser participant Kyoo participant Hosted participant Google App->>Kyoo: /login/google?redirectUrl=/user-logged Kyoo->>Hosted: /providers Hosted->>Kyoo: has google = true Kyoo->>Browser: redirect hosted.com/login/google?redirectUrl=/user-logged&tenant=kyoo.com Browser-->>Hosted: access /login/google?redirectUrl=/user-logged&tenant=kyoo.com Hosted->>Browser: redirect auth.google.com?state=id=guid,url=/user-logged,tenant=kyoo.com&redirectUrl=/logged/google Browser-->>Google: Access login page Google->>Browser: redirect hosted.com/logged/google?code=abc&state=id=guid,url=/user-logged,tenant=kyoo.com Browser-->>Hosted: access /logged/google?code=abc&state=id=guid,url=/user-logged,tenant=kyoo.com Hosted->>App: redirect /user-logged?token=opaque&error= App->>Kyoo: /callback/google?token=opaque Kyoo->>Hosted: /callback/google?token=opaque&tenant=kyoo.com Hosted->>Google: auth.google.com/token?code=abc Google->>Hosted: jwt token Hosted->>Google: auth.google.com/profile (w/ jwt) Google->>Hosted: profile info Hosted->>Kyoo: profile info (without a jwt to access the provider) Kyoo->>App: Token if user exist/was created ``` The hosted service does not store any user data during this interaction. A `/login` requests temporally stores an id, the tenant & the redirectUrl to unsure the profile value is not stollen. This is then deleted after a `/callback` call (or on timeout). User profile or jwt is never stored. ## Permissions You might have noticed that some routes requires the user to have some permissions. Kyoo's auth uses the custom `permissions` claim for this. Your application is free to use this or any other way of handling permissions/roles. ## TODO - Reset/forget password - Login via qrcode/code from other device (useful for tv for example) - LDMA? - Mails