diff --git a/auth/README.md b/auth/README.md index 99f80257..4ffb3538 100644 --- a/auth/README.md +++ b/auth/README.md @@ -17,20 +17,25 @@ ### Lifecycle -``` -`/login` { login, password } -> token -`/login/$provider` { redirectUrl, tenant? } -> redirect -`/register` { email, username, password } -> token -`/logout` w/ optional `?session=id` + +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/$username` (or /users/me) -> user -Get/Post/Delete `/users/$username/logo` (or /users/me/logo) -> png +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).\ @@ -41,10 +46,15 @@ Put/Patch can edit custom claims (roles & permissons for example) if the user ha 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) -(can then use `/logout?session=id`) +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 diff --git a/auth/session.go b/auth/session.go index 4c9011f4..a0d539bd 100644 --- a/auth/session.go +++ b/auth/session.go @@ -1,12 +1,17 @@ package main import ( + "cmp" + "context" + "crypto/rand" + "encoding/base64" "maps" "net/http" "time" "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" + "github.com/zoriya/kyoo/keibi/dbc" ) type LoginDto struct { @@ -14,8 +19,30 @@ type LoginDto struct { Password string `json:"password" validate:"required"` } -func (h *Handler) createToken(c echo.Context, user *User) error { - return nil +func (h *Handler) createSession(c echo.Context, user *User) error { + ctx := context.Background() + + id := make([]byte, 64) + _, err := rand.Read(id) + if err != nil { + return err + } + + dev := cmp.Or(c.Param("device"), c.Request().Header.Get("User-Agent")) + device := &dev + if dev == "" { + device = nil + } + + session, err := h.db.CreateSession(ctx, dbc.CreateSessionParams{ + ID: base64.StdEncoding.EncodeToString(id), + UserID: user.ID, + Device: device, + }) + if err != nil { + return err + } + return c.JSON(201, session) } func (h *Handler) CreateJwt(c echo.Context, user *User) error { diff --git a/auth/sql/migrations/000003_sessions.down.sql b/auth/sql/migrations/000003_sessions.down.sql new file mode 100644 index 00000000..7a17cccd --- /dev/null +++ b/auth/sql/migrations/000003_sessions.down.sql @@ -0,0 +1,5 @@ +begin; + +drop table sessions; + +commit; diff --git a/auth/sql/migrations/000003_sessions.up.sql b/auth/sql/migrations/000003_sessions.up.sql new file mode 100644 index 00000000..ff9db051 --- /dev/null +++ b/auth/sql/migrations/000003_sessions.up.sql @@ -0,0 +1,11 @@ +begin; + +create table sessions( + id varchar(128) not null primary key, + user_id uuid not null references users(id) on delete cascade, + created_date timestampz not null default now()::timestampz, + last_used timestampz not null default now()::timestampz, + device varchar(1024) +); + +commit; diff --git a/auth/sql/queries/sessions.sql b/auth/sql/queries/sessions.sql new file mode 100644 index 00000000..38783702 --- /dev/null +++ b/auth/sql/queries/sessions.sql @@ -0,0 +1,40 @@ +-- name: GetUserFromSession :one +select + u.* +from + users as u + left join sessions as s on u.id = s.user_id +where + s.id = $1 +limit 1; + +-- name: TouchSession :exec +update + sessions +set + last_used = now()::timestampz +where + id = $1; + +-- name: GetUserSessions :many +select + * +from + sessions +where + user_id = $1 +order by + last_used; + +-- name: CreateSession :one +insert into sessions(id, user_id, device) + values ($1, $2, $3) +returning + *; + +-- name: DeleteSession :one +delete from sessions +where id = $1 +returning + *; + diff --git a/auth/users.go b/auth/users.go index 4f8ed0d0..fe1f9871 100644 --- a/auth/users.go +++ b/auth/users.go @@ -102,5 +102,5 @@ func (h *Handler) Register(c echo.Context) error { return echo.NewHTTPError(409, "Email or username already taken") } user := MapDbUser(&duser) - return h.createToken(c, &user) + return h.createSession(c, &user) }