# 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 `/keys`
Post `/keys` {...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.

### Presigned urls

```
POST `/presign` { for: [{ url|prefix, verb }], duration } -> { signature, for, expireAt }
```

`/presign` returns a signature valid for the given requests. Each `for` rule matches either an exact `url` or (if `prefix` is set instead) any request path starting with that prefix, restricted to the given `verb` (all matched against the request path).

Append the returned signature as a `x-presign=$signature` query parameter to any request allowed by `for`, and the auth middleware (`/jwt`) will verify the signature and issue a jwt carrying the claims you had when you created it ; without needing a session token or an api key.

### 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

