From 73e3c6c64bda055d6bf445e910a54f8308955d9e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 25 Jul 2024 15:10:57 +0200 Subject: [PATCH 01/36] Add auth spec --- auth/README.md | 141 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 auth/README.md diff --git a/auth/README.md b/auth/README.md new file mode 100644 index 00000000..49dd18c7 --- /dev/null +++ b/auth/README.md @@ -0,0 +1,141 @@ +# Auth + +## 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) +- Api keys support +- Optionally [Federated](#federated) + +## Routes + +### Lifecycle + +``` +`/login` { login, password } -> token +`/login/$provider` { redirectUrl, tenant? } -> redirect +`/register` { email, username, password } -> token +`/logout` 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 +``` + +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). + +### Sessions + +Get `/sessions` list all of your active sessions (and devices) +(can then use `/logout?session=id`) + +### Api keys + +``` +Get `/apikeys` +Post `/apikeys` {...nlaims} 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.create` 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 + From 629660bb7936ca64a611fba11dfd91262cc3bdd0 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 24 Aug 2024 00:43:58 +0200 Subject: [PATCH 02/36] Init user database --- auth/.gitignore | 2 ++ auth/go.mod | 11 ++++++ auth/go.sum | 16 +++++++++ auth/sql/migrations/000001_users.down.sql | 0 auth/sql/migrations/000001_users.up.sql | 15 ++++++++ auth/sql/queries/users.sql | 44 +++++++++++++++++++++++ auth/sqlc.yaml | 9 +++++ shell.nix | 1 + 8 files changed, 98 insertions(+) create mode 100644 auth/.gitignore create mode 100644 auth/go.mod create mode 100644 auth/go.sum create mode 100644 auth/sql/migrations/000001_users.down.sql create mode 100644 auth/sql/migrations/000001_users.up.sql create mode 100644 auth/sql/queries/users.sql create mode 100644 auth/sqlc.yaml diff --git a/auth/.gitignore b/auth/.gitignore new file mode 100644 index 00000000..3995d2c4 --- /dev/null +++ b/auth/.gitignore @@ -0,0 +1,2 @@ +# generated via sqlc +db/ diff --git a/auth/go.mod b/auth/go.mod new file mode 100644 index 00000000..b6749baf --- /dev/null +++ b/auth/go.mod @@ -0,0 +1,11 @@ +module github.com/zoriya/kyoo/keibi + +go 1.22.5 + +require ( + github.com/golang-migrate/migrate/v4 v4.17.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + go.uber.org/atomic v1.7.0 // indirect +) diff --git a/auth/go.sum b/auth/go.sum new file mode 100644 index 00000000..9cf65ecc --- /dev/null +++ b/auth/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= +github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/auth/sql/migrations/000001_users.down.sql b/auth/sql/migrations/000001_users.down.sql new file mode 100644 index 00000000..e69de29b diff --git a/auth/sql/migrations/000001_users.up.sql b/auth/sql/migrations/000001_users.up.sql new file mode 100644 index 00000000..8ebb52b9 --- /dev/null +++ b/auth/sql/migrations/000001_users.up.sql @@ -0,0 +1,15 @@ +begin; + +create table users( + id uuid primary key, + username varchar(256) not null unique, + email varchar(320) not null unique, + password text, + external_handle jsonb not null, + claims jsonb not null, + + created_date timestampz not null default now()::timestampz, + last_seen timestampz not null default now()::timestampz +); + +commit; diff --git a/auth/sql/queries/users.sql b/auth/sql/queries/users.sql new file mode 100644 index 00000000..13c73d15 --- /dev/null +++ b/auth/sql/queries/users.sql @@ -0,0 +1,44 @@ +-- name: GetAllUsers :many +select + * +from + users +order by + created_date +limit $1; + +-- name: GetUser :one +select + * +from + users +where + id = $1 +limit 1; + +-- name: CreateUser :one +insert into users(username, email, password, external_handle, claims) + values ($1, $2, $3, $4, $5) +returning + *; + +-- name: UpdateUser :one +update + users +set + username = $2, + email = $3, + password = $4, + external_handle = $5, + claims = $6 +where + id = $1 +returning + *; + +-- name: DeleteUser :one +delete from users +where id = $1 +returning + *; + diff --git a/auth/sqlc.yaml b/auth/sqlc.yaml new file mode 100644 index 00000000..190e70ae --- /dev/null +++ b/auth/sqlc.yaml @@ -0,0 +1,9 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "sql/queries" + schema: "sql/migrations" + gen: + go: + package: "db" + out: "db" diff --git a/shell.nix b/shell.nix index 0e91c3ba..1e4ced1b 100644 --- a/shell.nix +++ b/shell.nix @@ -38,6 +38,7 @@ in biome kubernetes-helm go-migrate + sqlc ]; DOTNET_ROOT = "${dotnet}"; From 606332ba6f372342942eb8e1275f17df935cb394 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 24 Aug 2024 02:22:03 +0200 Subject: [PATCH 03/36] Add list users route & split oidc to a new table --- auth/.env.example | 13 +++ auth/.gitignore | 2 +- auth/README.md | 2 +- auth/api/main.go | 97 +++++++++++++++++++ auth/api/users.go | 63 ++++++++++++ auth/go.mod | 35 ++++++- auth/go.sum | 112 +++++++++++++++++++--- auth/sql/migrations/000001_users.down.sql | 6 ++ auth/sql/migrations/000001_users.up.sql | 18 +++- auth/sql/queries/users.sql | 38 +++++--- auth/sqlc.yaml | 10 +- 11 files changed, 360 insertions(+), 36 deletions(-) create mode 100644 auth/.env.example create mode 100644 auth/api/main.go create mode 100644 auth/api/users.go diff --git a/auth/.env.example b/auth/.env.example new file mode 100644 index 00000000..012ba22f --- /dev/null +++ b/auth/.env.example @@ -0,0 +1,13 @@ +# vi: ft=sh +# shellcheck disable=SC2034 + +# Database things +POSTGRES_USER= +POSTGRES_PASSWORD= +POSTGRES_DB= +POSTGRES_SERVER= +POSTGRES_PORT=5432 +# Default is keibi, you can specify "disabled" to use the default search_path of the user. +# If this is not "disabled", the schema will be created (if it does not exists) and +# the search_path of the user will be ignored (only the schema specified will be used). +POSTGRES_SCHEMA=keibi diff --git a/auth/.gitignore b/auth/.gitignore index 3995d2c4..98fa9777 100644 --- a/auth/.gitignore +++ b/auth/.gitignore @@ -1,2 +1,2 @@ # generated via sqlc -db/ +dbc/ diff --git a/auth/README.md b/auth/README.md index 49dd18c7..99f80257 100644 --- a/auth/README.md +++ b/auth/README.md @@ -1,4 +1,4 @@ -# Auth +# Keibi ## Features diff --git a/auth/api/main.go b/auth/api/main.go new file mode 100644 index 00000000..94c3c2fb --- /dev/null +++ b/auth/api/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "database/sql" + "fmt" + "net/http" + "net/url" + "os" + + "github.com/zoriya/kyoo/keibi/dbc" + + "github.com/golang-migrate/migrate" + "github.com/golang-migrate/migrate/database/postgres" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +func ErrorHandler(err error, c echo.Context) { + code := http.StatusInternalServerError + var message string + if he, ok := err.(*echo.HTTPError); ok { + code = he.Code + message = fmt.Sprint(he.Message) + } else { + c.Logger().Error(err) + message = "Internal server error" + } + c.JSON(code, struct { + Errors []string `json:"errors"` + }{Errors: []string{message}}) +} + +func OpenDatabase() (*sql.DB, error) { + con := fmt.Sprintf( + "postgresql://%v:%v@%v:%v/%v?application_name=gocoder&sslmode=disable", + url.QueryEscape(os.Getenv("POSTGRES_USER")), + url.QueryEscape(os.Getenv("POSTGRES_PASSWORD")), + url.QueryEscape(os.Getenv("POSTGRES_SERVER")), + url.QueryEscape(os.Getenv("POSTGRES_PORT")), + url.QueryEscape(os.Getenv("POSTGRES_DB")), + ) + schema := os.Getenv("POSTGRES_SCHEMA") + if schema == "" { + schema = "keibi" + } + if schema != "disabled" { + con = fmt.Sprintf("%s&search_path=%s", con, url.QueryEscape(schema)) + } + db, err := sql.Open("postgres", con) + if err != nil { + fmt.Printf("Could not connect to database, check your env variables!") + return nil, err + } + + if schema != "disabled" { + _, err = db.Exec(fmt.Sprintf("create schema if not exists %s", schema)) + if err != nil { + return nil, err + } + } + + driver, err := postgres.WithInstance(db, &postgres.Config{}) + if err != nil { + return nil, err + } + m, err := migrate.NewWithDatabaseInstance("file://sql/migrations", "postgres", driver) + if err != nil { + return nil, err + } + m.Up() + + return db, nil +} + +type Handler struct { + db *dbc.Queries +} + +func main() { + e := echo.New() + e.Use(middleware.Logger()) + e.HTTPErrorHandler = ErrorHandler + + db, err := OpenDatabase(); + if err != nil { + e.Logger.Fatal(err) + return + } + + h := Handler{ + db: dbc.New(db), + } + + e.GET("/users", h.ListUsers) + + e.Logger.Fatal(e.Start(":4568")) +} diff --git a/auth/api/users.go b/auth/api/users.go new file mode 100644 index 00000000..f4e9eebf --- /dev/null +++ b/auth/api/users.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "github.com/zoriya/kyoo/keibi/dbc" +) + +type User struct { + ID uuid.UUID `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + CreatedDate time.Time `json:"createdDate"` + LastSeen time.Time `json:"lastSeen"` + Oidc map[string]OidcHandle `json:"oidc,omitempty"` +} + +type OidcHandle struct { + Id string `json:"id"` + Username string `json:"username"` + ProfileUrl *string `json:"profileUrl"` +} + +func (h *Handler) ListUsers(c echo.Context) error { + ctx := context.Background() + limit := int32(20) + id := c.Param("afterId") + + var users []dbc.User + var err error + if id == "" { + users, err = h.db.GetAllUsers(ctx, limit) + } else { + uid, uerr := uuid.Parse(id) + if uerr != nil { + return echo.NewHTTPError(400, "Invalid `afterId` parameter, uuid was expected") + } + users, err = h.db.GetAllUsersAfter(ctx, dbc.GetAllUsersAfterParams{ + Limit: limit, + AfterID: uid, + }) + } + + if err != nil { + return err + } + + var ret []User + for _, user := range users { + ret = append(ret, User{ + ID: user.ID, + Username: user.Username, + Email: user.Email, + CreatedDate: user.CreatedDate, + LastSeen: user.LastSeen, + Oidc: nil, + }) + } + return c.JSON(200, ret) +} diff --git a/auth/go.mod b/auth/go.mod index b6749baf..db1c5925 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -3,9 +3,34 @@ module github.com/zoriya/kyoo/keibi go 1.22.5 require ( - github.com/golang-migrate/migrate/v4 v4.17.1 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - go.uber.org/atomic v1.7.0 // indirect + github.com/golang-migrate/migrate v3.5.4+incompatible + github.com/google/uuid v1.6.0 + github.com/labstack/echo/v4 v4.12.0 +) + +require ( + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.2+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect ) diff --git a/auth/go.sum b/auth/go.sum index 9cf65ecc..c497b92c 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -1,16 +1,104 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= -github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= +github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= +github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/auth/sql/migrations/000001_users.down.sql b/auth/sql/migrations/000001_users.down.sql index e69de29b..d70106fc 100644 --- a/auth/sql/migrations/000001_users.down.sql +++ b/auth/sql/migrations/000001_users.down.sql @@ -0,0 +1,6 @@ +begin; + +drop table oidc_handle; +drop table users; + +commit; diff --git a/auth/sql/migrations/000001_users.up.sql b/auth/sql/migrations/000001_users.up.sql index 8ebb52b9..2f6689a5 100644 --- a/auth/sql/migrations/000001_users.up.sql +++ b/auth/sql/migrations/000001_users.up.sql @@ -1,15 +1,29 @@ begin; create table users( - id uuid primary key, + id uuid not null primary key, username varchar(256) not null unique, email varchar(320) not null unique, password text, - external_handle jsonb not null, claims jsonb not null, created_date timestampz not null default now()::timestampz, last_seen timestampz not null default now()::timestampz ); +create table oidc_handle( + user_id uuid not null references users(id) on delete cascade, + provider varchar(256) not null, + + id text not null, + username varchar(256) not null, + profile_url text, + + access_token text, + refresh_token text, + expire_at timestampz, + + constraint oidc_handle_pk primary key (user_id, provider) +); + commit; diff --git a/auth/sql/queries/users.sql b/auth/sql/queries/users.sql index 13c73d15..8c463e14 100644 --- a/auth/sql/queries/users.sql +++ b/auth/sql/queries/users.sql @@ -1,24 +1,37 @@ -- name: GetAllUsers :many select - * + u.*, from - users + users as u order by - created_date + id limit $1; --- name: GetUser :one +-- name: GetAllUsersAfter :many select * from users +where + id >= sqlc.arg(after_id) +order by + id +limit $1; + +-- name: GetUser :one +select + sqlc.embed(users), + sql.embed(oidc_handle) +from + users as u + left join oidc_handle as h on u.id = h.user_id where id = $1 limit 1; -- name: CreateUser :one -insert into users(username, email, password, external_handle, claims) - values ($1, $2, $3, $4, $5) +insert into users(username, email, password, claims) + values (?, ?, ?, ?) returning *; @@ -26,19 +39,18 @@ returning update users set - username = $2, - email = $3, - password = $4, - external_handle = $5, - claims = $6 + username = ?, + email = ?, + password = ?, + claims = ? where - id = $1 + id = ? returning *; -- name: DeleteUser :one delete from users -where id = $1 +where id = ? returning *; diff --git a/auth/sqlc.yaml b/auth/sqlc.yaml index 190e70ae..9c7022c1 100644 --- a/auth/sqlc.yaml +++ b/auth/sqlc.yaml @@ -5,5 +5,11 @@ sql: schema: "sql/migrations" gen: go: - package: "db" - out: "db" + package: "dbc" + out: "dbc" + overrides: + - db_type: "timestampz" + go_type: + import: "time" + type: "Time" + From 6b55ae6395f2fbd0f4df28b07bf7bd9ea16c2496 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 24 Aug 2024 19:17:24 +0200 Subject: [PATCH 04/36] Fix queries --- auth/sql/queries/users.sql | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/auth/sql/queries/users.sql b/auth/sql/queries/users.sql index 8c463e14..0d25d81b 100644 --- a/auth/sql/queries/users.sql +++ b/auth/sql/queries/users.sql @@ -1,8 +1,8 @@ -- name: GetAllUsers :many select - u.*, + * from - users as u + users order by id limit $1; @@ -20,18 +20,18 @@ limit $1; -- name: GetUser :one select - sqlc.embed(users), - sql.embed(oidc_handle) + sqlc.embed(u), + sqlc.embed(h) from users as u left join oidc_handle as h on u.id = h.user_id where - id = $1 + u.id = $1 limit 1; -- name: CreateUser :one insert into users(username, email, password, claims) - values (?, ?, ?, ?) + values ($1, $2, $3, $4) returning *; @@ -39,18 +39,18 @@ returning update users set - username = ?, - email = ?, - password = ?, - claims = ? + username = $2, + email = $3, + password = $4, + claims = $5 where - id = ? + id = $1 returning *; -- name: DeleteUser :one delete from users -where id = ? +where id = $1 returning *; From d820a1b149dab44f7c84e6b9a6b8d6c4cfcdb28f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 24 Aug 2024 19:17:48 +0200 Subject: [PATCH 05/36] Setup validators --- auth/{api => }/main.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) rename auth/{api => }/main.go (83%) diff --git a/auth/api/main.go b/auth/main.go similarity index 83% rename from auth/api/main.go rename to auth/main.go index 94c3c2fb..aa2ecf5a 100644 --- a/auth/api/main.go +++ b/auth/main.go @@ -9,6 +9,7 @@ import ( "github.com/zoriya/kyoo/keibi/dbc" + "github.com/go-playground/validator/v10" "github.com/golang-migrate/migrate" "github.com/golang-migrate/migrate/database/postgres" "github.com/labstack/echo/v4" @@ -30,6 +31,17 @@ func ErrorHandler(err error, c echo.Context) { }{Errors: []string{message}}) } +type Validator struct { + validator *validator.Validate +} + +func (v *Validator) Validate(i interface{}) error { + if err := v.validator.Struct(i); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + return nil +} + func OpenDatabase() (*sql.DB, error) { con := fmt.Sprintf( "postgresql://%v:%v@%v:%v/%v?application_name=gocoder&sslmode=disable", @@ -79,9 +91,10 @@ type Handler struct { func main() { e := echo.New() e.Use(middleware.Logger()) + e.Validator = &Validator{validator: validator.New(validator.WithRequiredStructEnabled())} e.HTTPErrorHandler = ErrorHandler - db, err := OpenDatabase(); + db, err := OpenDatabase() if err != nil { e.Logger.Fatal(err) return From b20c6c30fff0a1934d35a86e4096d09c78bcce44 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 24 Aug 2024 19:18:11 +0200 Subject: [PATCH 06/36] Move users.go --- auth/{api => }/users.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename auth/{api => }/users.go (100%) diff --git a/auth/api/users.go b/auth/users.go similarity index 100% rename from auth/api/users.go rename to auth/users.go From db08bb12c8b879670bde11e78e4de384e326eebe Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 24 Aug 2024 19:18:31 +0200 Subject: [PATCH 07/36] Register wip --- auth/auth.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ auth/go.mod | 8 ++++++++ auth/go.sum | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 auth/auth.go diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 00000000..2cb184da --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "database/sql" + "net/http" + + "github.com/alexedwards/argon2id" + "github.com/labstack/echo/v4" + "github.com/zoriya/kyoo/keibi/dbc" +) + +type LoginDto struct { + Login string `json:"login" validate:"required"` + Password string `json:"password" validate:"required"` +} + +type RegisterDto struct { + Username string `json:"username" validate:"required"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required"` +} + +func (h *Handler) Register(c echo.Context) error { + var req RegisterDto + err := c.Bind(&req) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + if err = c.Validate(&req); err != nil { + return err + } + + pass, err := argon2id.CreateHash(req.Password, argon2id.DefaultParams) + if err != nil { + return echo.NewHTTPError(400, "Invalid password") + } + + user, err := h.db.CreateUser(context.Background(), dbc.CreateUserParams{ + Username: req.Username, + Email: req.Email, + Password: sql.NullString{}, + ExternalHandle: []byte{}, + Claims: []byte{}, + }) + if err != nil { + return echo.NewHTTPError(409, "Email or username already taken") + } + return h.CreateToken(c, &user) +} diff --git a/auth/go.mod b/auth/go.mod index db1c5925..3ac26341 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -3,19 +3,26 @@ module github.com/zoriya/kyoo/keibi go 1.22.5 require ( + github.com/alexedwards/argon2id v1.0.0 github.com/golang-migrate/migrate v3.5.4+incompatible github.com/google/uuid v1.6.0 github.com/labstack/echo/v4 v4.12.0 ) +require github.com/gabriel-vasile/mimetype v1.4.3 // indirect + require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.2+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.0 github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/labstack/gommon v0.4.2 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -33,4 +40,5 @@ require ( golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect ) diff --git a/auth/go.sum b/auth/go.sum index c497b92c..d1ffff95 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -1,5 +1,7 @@ github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w= +github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -12,10 +14,20 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -30,6 +42,8 @@ github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+k github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -55,6 +69,7 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= @@ -66,28 +81,54 @@ go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+M golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -96,6 +137,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From d2856037165f2101133acb4a336c6ef260d77822 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 27 Aug 2024 00:16:33 +0200 Subject: [PATCH 08/36] Register wip --- auth/auth.go | 31 ++++++++++++++++++++-------- auth/config.go | 6 ++++++ auth/go.mod | 13 +++++++++++- auth/go.sum | 36 +++++++++++++++++++++++++++++++-- auth/main.go | 55 ++++++++++++++++++++++++++++++++++---------------- auth/sqlc.yaml | 8 ++++++++ auth/users.go | 20 ++++++++++-------- 7 files changed, 133 insertions(+), 36 deletions(-) create mode 100644 auth/config.go diff --git a/auth/auth.go b/auth/auth.go index 2cb184da..b1f4f4f2 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -2,10 +2,10 @@ package main import ( "context" - "database/sql" "net/http" "github.com/alexedwards/argon2id" + "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" "github.com/zoriya/kyoo/keibi/dbc" ) @@ -36,15 +36,30 @@ func (h *Handler) Register(c echo.Context) error { return echo.NewHTTPError(400, "Invalid password") } - user, err := h.db.CreateUser(context.Background(), dbc.CreateUserParams{ - Username: req.Username, - Email: req.Email, - Password: sql.NullString{}, - ExternalHandle: []byte{}, - Claims: []byte{}, + duser, err := h.db.CreateUser(context.Background(), dbc.CreateUserParams{ + Username: req.Username, + Email: req.Email, + Password: &pass, + // TODO: Use configured value. + Claims: []byte{}, }) if err != nil { return echo.NewHTTPError(409, "Email or username already taken") } - return h.CreateToken(c, &user) + user := MapDbUser(&duser) + return createToken(c, &user) +} + +func createToken(c echo.Context, user *User) error { + claims := &jwt.RegisteredClaims{ + Subject: user.ID.String(), + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + t, err := token.SignedString(h.jwtSecret) + if err != nil { + return err + } + return c.JSON(http.StatusOK, echo.Map{ + "token": t, + }) } diff --git a/auth/config.go b/auth/config.go new file mode 100644 index 00000000..541807c7 --- /dev/null +++ b/auth/config.go @@ -0,0 +1,6 @@ +package main + +type Configuration struct { + JwtSecret string + DefaultClaims []byte +} diff --git a/auth/go.mod b/auth/go.mod index 3ac26341..385eee02 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -6,21 +6,30 @@ require ( github.com/alexedwards/argon2id v1.0.0 github.com/golang-migrate/migrate v3.5.4+incompatible github.com/google/uuid v1.6.0 + github.com/jackc/pgx/v5 v5.6.0 github.com/labstack/echo/v4 v4.12.0 ) -require github.com/gabriel-vasile/mimetype v1.4.3 // indirect +require github.com/golang-jwt/jwt/v5 v5.2.1 // indirect require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.2+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang-migrate/migrate/v4 v4.17.1 + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect @@ -35,8 +44,10 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.22.0 // indirect golang.org/x/net v0.24.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/auth/go.sum b/auth/go.sum index d1ffff95..ea825295 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -1,7 +1,9 @@ github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w= github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -20,22 +22,41 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= +github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= +github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= @@ -61,6 +82,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -78,6 +102,8 @@ go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6b go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -104,6 +130,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -143,5 +171,9 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/auth/main.go b/auth/main.go index aa2ecf5a..d7878baf 100644 --- a/auth/main.go +++ b/auth/main.go @@ -1,17 +1,22 @@ package main import ( - "database/sql" + "context" + "errors" "fmt" "net/http" - "net/url" "os" + "strconv" "github.com/zoriya/kyoo/keibi/dbc" "github.com/go-playground/validator/v10" - "github.com/golang-migrate/migrate" - "github.com/golang-migrate/migrate/database/postgres" + "github.com/golang-migrate/migrate/v4" + pgxd "github.com/golang-migrate/migrate/v4/database/pgx/v5" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/stdlib" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) @@ -42,36 +47,51 @@ func (v *Validator) Validate(i interface{}) error { return nil } -func OpenDatabase() (*sql.DB, error) { - con := fmt.Sprintf( - "postgresql://%v:%v@%v:%v/%v?application_name=gocoder&sslmode=disable", - url.QueryEscape(os.Getenv("POSTGRES_USER")), - url.QueryEscape(os.Getenv("POSTGRES_PASSWORD")), - url.QueryEscape(os.Getenv("POSTGRES_SERVER")), - url.QueryEscape(os.Getenv("POSTGRES_PORT")), - url.QueryEscape(os.Getenv("POSTGRES_DB")), - ) +func OpenDatabase() (*pgxpool.Pool, error) { + ctx := context.Background() + + port, err := strconv.ParseUint(os.Getenv("POSTGRES_PORT"), 10, 16) + if err != nil { + return nil, errors.New("invalid postgres port specified") + } + conf := pgxpool.Config{ + ConnConfig: &pgx.ConnConfig{ + Config: pgconn.Config{ + Host: os.Getenv("POSTGRES_SERVER"), + Port: uint16(port), + Database: os.Getenv("POSTGRES_DB"), + User: os.Getenv("POSTGRES_USER"), + Password: os.Getenv("POSTGRES_PASSWORD"), + TLSConfig: nil, + RuntimeParams: map[string]string{ + "application_name": "keibi", + }, + }, + }, + } schema := os.Getenv("POSTGRES_SCHEMA") if schema == "" { schema = "keibi" } if schema != "disabled" { - con = fmt.Sprintf("%s&search_path=%s", con, url.QueryEscape(schema)) + conf.ConnConfig.Config.RuntimeParams["search_path"] = schema } - db, err := sql.Open("postgres", con) + + db, err := pgxpool.NewWithConfig(ctx, &conf) if err != nil { fmt.Printf("Could not connect to database, check your env variables!") return nil, err } + defer db.Close() if schema != "disabled" { - _, err = db.Exec(fmt.Sprintf("create schema if not exists %s", schema)) + _, err = db.Exec(ctx, fmt.Sprintf("create schema if not exists %s", schema)) if err != nil { return nil, err } } - driver, err := postgres.WithInstance(db, &postgres.Config{}) + driver, err := pgxd.WithInstance(stdlib.OpenDBFromPool(db), &pgxd.Config{}) if err != nil { return nil, err } @@ -86,6 +106,7 @@ func OpenDatabase() (*sql.DB, error) { type Handler struct { db *dbc.Queries + config *Configuration } func main() { diff --git a/auth/sqlc.yaml b/auth/sqlc.yaml index 9c7022c1..e15d172e 100644 --- a/auth/sqlc.yaml +++ b/auth/sqlc.yaml @@ -6,10 +6,18 @@ sql: gen: go: package: "dbc" + sql_package: "pgx/v5" out: "dbc" + emit_pointers_for_null_types: true + emit_json_tags: true overrides: - db_type: "timestampz" go_type: import: "time" type: "Time" + - db_type: "uuid" + go_type: + import: "github.com/google/uuid" + type: "UUID" + diff --git a/auth/users.go b/auth/users.go index f4e9eebf..02138620 100644 --- a/auth/users.go +++ b/auth/users.go @@ -24,6 +24,17 @@ type OidcHandle struct { ProfileUrl *string `json:"profileUrl"` } +func MapDbUser(user *dbc.User) User { + return User{ + ID: user.ID, + Username: user.Username, + Email: user.Email, + CreatedDate: user.CreatedDate, + LastSeen: user.LastSeen, + Oidc: nil, + } +} + func (h *Handler) ListUsers(c echo.Context) error { ctx := context.Background() limit := int32(20) @@ -50,14 +61,7 @@ func (h *Handler) ListUsers(c echo.Context) error { var ret []User for _, user := range users { - ret = append(ret, User{ - ID: user.ID, - Username: user.Username, - Email: user.Email, - CreatedDate: user.CreatedDate, - LastSeen: user.LastSeen, - Oidc: nil, - }) + ret = append(ret, MapDbUser(&user)) } return c.JSON(200, ret) } From dfc411e5f623edd909887bf3c006edb7779f5548 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 27 Aug 2024 00:47:58 +0200 Subject: [PATCH 09/36] Add configuration table --- .gitignore | 3 +- auth/auth.go | 9 ++-- auth/config.go | 50 +++++++++++++++++++++- auth/main.go | 6 +++ auth/sql/migrations/000002_config.down.sql | 5 +++ auth/sql/migrations/000002_config.up.sql | 8 ++++ auth/sql/queries/config.sql | 21 +++++++++ 7 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 auth/sql/migrations/000002_config.down.sql create mode 100644 auth/sql/migrations/000002_config.up.sql create mode 100644 auth/sql/queries/config.sql diff --git a/.gitignore b/.gitignore index 6ed2917f..6738538d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ log.html output.xml report.html chart/charts -chart/Chart.lock \ No newline at end of file +chart/Chart.lock +tmp diff --git a/auth/auth.go b/auth/auth.go index b1f4f4f2..e98eb087 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -40,22 +40,21 @@ func (h *Handler) Register(c echo.Context) error { Username: req.Username, Email: req.Email, Password: &pass, - // TODO: Use configured value. - Claims: []byte{}, + Claims: h.config.DefaultClaims, }) if err != nil { return echo.NewHTTPError(409, "Email or username already taken") } user := MapDbUser(&duser) - return createToken(c, &user) + return h.createToken(c, &user) } -func createToken(c echo.Context, user *User) error { +func (h *Handler) createToken(c echo.Context, user *User) error { claims := &jwt.RegisteredClaims{ Subject: user.ID.String(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - t, err := token.SignedString(h.jwtSecret) + t, err := token.SignedString(h.config.JwtSecret) if err != nil { return err } diff --git a/auth/config.go b/auth/config.go index 541807c7..a8e16ae4 100644 --- a/auth/config.go +++ b/auth/config.go @@ -1,6 +1,54 @@ package main +import ( + "context" + "crypto/rand" + "encoding/base64" + + "github.com/zoriya/kyoo/keibi/dbc" +) + type Configuration struct { - JwtSecret string + JwtSecret []byte DefaultClaims []byte } + +const ( + JwtSecret = "jwt_secret" +) + +func LoadConfiguration(db *dbc.Queries) (*Configuration, error) { + ctx := context.Background() + confs, err := db.LoadConfig(ctx) + if err != nil { + return nil, err + } + + ret := Configuration{} + + for _, conf := range confs { + switch conf.Key { + case JwtSecret: + secret, err := base64.StdEncoding.DecodeString(conf.Value) + if err != nil { + return nil, err + } + ret.JwtSecret = secret + } + } + + if ret.JwtSecret == nil { + ret.JwtSecret = make([]byte, 128) + rand.Read(ret.JwtSecret) + + _, err := db.SaveConfig(ctx, dbc.SaveConfigParams{ + Key: JwtSecret, + Value: base64.StdEncoding.EncodeToString(ret.JwtSecret), + }) + if err != nil { + return nil, err + } + } + + return &ret, nil +} diff --git a/auth/main.go b/auth/main.go index d7878baf..05aa37f2 100644 --- a/auth/main.go +++ b/auth/main.go @@ -124,6 +124,12 @@ func main() { h := Handler{ db: dbc.New(db), } + conf, err := LoadConfiguration(h.db) + if err != nil { + e.Logger.Fatal("Could not load configuration: %v", err) + return + } + h.config = conf e.GET("/users", h.ListUsers) diff --git a/auth/sql/migrations/000002_config.down.sql b/auth/sql/migrations/000002_config.down.sql new file mode 100644 index 00000000..2fd15f61 --- /dev/null +++ b/auth/sql/migrations/000002_config.down.sql @@ -0,0 +1,5 @@ +begin; + +drop table config; + +commit; diff --git a/auth/sql/migrations/000002_config.up.sql b/auth/sql/migrations/000002_config.up.sql new file mode 100644 index 00000000..7d9e4b72 --- /dev/null +++ b/auth/sql/migrations/000002_config.up.sql @@ -0,0 +1,8 @@ +begin; + +create table config( + key varchar(256) not null primary key, + value text not null +); + +commit; diff --git a/auth/sql/queries/config.sql b/auth/sql/queries/config.sql new file mode 100644 index 00000000..3f3db9d5 --- /dev/null +++ b/auth/sql/queries/config.sql @@ -0,0 +1,21 @@ +-- name: LoadConfig :many +select + * +from + config; + +-- name: SaveConfig :one +insert into config(key, value) + values ($1, $2) +on conflict (key) + do update set + value = excluded.value + returning + *; + +-- name: DeleteConfig :one +delete from config +where key = $1 +returning + *; + From 306dbbd024a07f2a6fb5b282c45d924d4ba18893 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 28 Aug 2024 18:06:18 +0200 Subject: [PATCH 10/36] Create register --- auth/auth.go | 64 ------------------------------------------------- auth/config.go | 4 +++- auth/main.go | 1 + auth/session.go | 39 ++++++++++++++++++++++++++++++ auth/sqlc.yaml | 5 ++++ auth/users.go | 39 ++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 65 deletions(-) delete mode 100644 auth/auth.go create mode 100644 auth/session.go diff --git a/auth/auth.go b/auth/auth.go deleted file mode 100644 index e98eb087..00000000 --- a/auth/auth.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "context" - "net/http" - - "github.com/alexedwards/argon2id" - "github.com/golang-jwt/jwt/v5" - "github.com/labstack/echo/v4" - "github.com/zoriya/kyoo/keibi/dbc" -) - -type LoginDto struct { - Login string `json:"login" validate:"required"` - Password string `json:"password" validate:"required"` -} - -type RegisterDto struct { - Username string `json:"username" validate:"required"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required"` -} - -func (h *Handler) Register(c echo.Context) error { - var req RegisterDto - err := c.Bind(&req) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - if err = c.Validate(&req); err != nil { - return err - } - - pass, err := argon2id.CreateHash(req.Password, argon2id.DefaultParams) - if err != nil { - return echo.NewHTTPError(400, "Invalid password") - } - - duser, err := h.db.CreateUser(context.Background(), dbc.CreateUserParams{ - Username: req.Username, - Email: req.Email, - Password: &pass, - Claims: h.config.DefaultClaims, - }) - if err != nil { - return echo.NewHTTPError(409, "Email or username already taken") - } - user := MapDbUser(&duser) - return h.createToken(c, &user) -} - -func (h *Handler) createToken(c echo.Context, user *User) error { - claims := &jwt.RegisteredClaims{ - Subject: user.ID.String(), - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - t, err := token.SignedString(h.config.JwtSecret) - if err != nil { - return err - } - return c.JSON(http.StatusOK, echo.Map{ - "token": t, - }) -} diff --git a/auth/config.go b/auth/config.go index a8e16ae4..dc0cec14 100644 --- a/auth/config.go +++ b/auth/config.go @@ -5,12 +5,14 @@ import ( "crypto/rand" "encoding/base64" + "github.com/golang-jwt/jwt/v5" "github.com/zoriya/kyoo/keibi/dbc" ) type Configuration struct { JwtSecret []byte - DefaultClaims []byte + Issuer string + DefaultClaims jwt.MapClaims } const ( diff --git a/auth/main.go b/auth/main.go index 05aa37f2..2f1ce240 100644 --- a/auth/main.go +++ b/auth/main.go @@ -132,6 +132,7 @@ func main() { h.config = conf e.GET("/users", h.ListUsers) + e.POST("/users", h.Register) e.Logger.Fatal(e.Start(":4568")) } diff --git a/auth/session.go b/auth/session.go new file mode 100644 index 00000000..4c9011f4 --- /dev/null +++ b/auth/session.go @@ -0,0 +1,39 @@ +package main + +import ( + "maps" + "net/http" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/labstack/echo/v4" +) + +type LoginDto struct { + Login string `json:"login" validate:"required"` + Password string `json:"password" validate:"required"` +} + +func (h *Handler) createToken(c echo.Context, user *User) error { + return nil +} + +func (h *Handler) CreateJwt(c echo.Context, user *User) error { + claims := maps.Clone(user.Claims) + claims["sub"] = user.ID.String() + claims["iss"] = h.config.Issuer + claims["exp"] = &jwt.NumericDate{ + Time: time.Now().UTC().Add(time.Hour), + } + claims["iss"] = &jwt.NumericDate{ + Time: time.Now().UTC(), + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + t, err := token.SignedString(h.config.JwtSecret) + if err != nil { + return err + } + return c.JSON(http.StatusOK, echo.Map{ + "token": t, + }) +} diff --git a/auth/sqlc.yaml b/auth/sqlc.yaml index e15d172e..75049429 100644 --- a/auth/sqlc.yaml +++ b/auth/sqlc.yaml @@ -19,5 +19,10 @@ sql: go_type: import: "github.com/google/uuid" type: "UUID" + - column: "users.claims" + go_type: + import: "github.com/golang-jwt/jwt/v5" + package: "jwt" + type: "MapClaims" diff --git a/auth/users.go b/auth/users.go index 02138620..4f8ed0d0 100644 --- a/auth/users.go +++ b/auth/users.go @@ -2,8 +2,11 @@ package main import ( "context" + "net/http" "time" + "github.com/alexedwards/argon2id" + "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/zoriya/kyoo/keibi/dbc" @@ -15,6 +18,7 @@ type User struct { Email string `json:"email"` CreatedDate time.Time `json:"createdDate"` LastSeen time.Time `json:"lastSeen"` + Claims jwt.MapClaims `json:"claims"` Oidc map[string]OidcHandle `json:"oidc,omitempty"` } @@ -24,6 +28,12 @@ type OidcHandle struct { ProfileUrl *string `json:"profileUrl"` } +type RegisterDto struct { + Username string `json:"username" validate:"required"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required"` +} + func MapDbUser(user *dbc.User) User { return User{ ID: user.ID, @@ -31,6 +41,7 @@ func MapDbUser(user *dbc.User) User { Email: user.Email, CreatedDate: user.CreatedDate, LastSeen: user.LastSeen, + Claims: user.Claims, Oidc: nil, } } @@ -65,3 +76,31 @@ func (h *Handler) ListUsers(c echo.Context) error { } return c.JSON(200, ret) } + +func (h *Handler) Register(c echo.Context) error { + var req RegisterDto + err := c.Bind(&req) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + if err = c.Validate(&req); err != nil { + return err + } + + pass, err := argon2id.CreateHash(req.Password, argon2id.DefaultParams) + if err != nil { + return echo.NewHTTPError(400, "Invalid password") + } + + duser, err := h.db.CreateUser(context.Background(), dbc.CreateUserParams{ + Username: req.Username, + Email: req.Email, + Password: &pass, + Claims: h.config.DefaultClaims, + }) + if err != nil { + return echo.NewHTTPError(409, "Email or username already taken") + } + user := MapDbUser(&duser) + return h.createToken(c, &user) +} From 8a2fb36cb0cce9e47163dd85e1556f6524a0a544 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 29 Aug 2024 16:32:35 +0200 Subject: [PATCH 11/36] Setup sessions --- auth/README.md | 30 ++++++++++----- auth/session.go | 31 ++++++++++++++- auth/sql/migrations/000003_sessions.down.sql | 5 +++ auth/sql/migrations/000003_sessions.up.sql | 11 ++++++ auth/sql/queries/sessions.sql | 40 ++++++++++++++++++++ auth/users.go | 2 +- 6 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 auth/sql/migrations/000003_sessions.down.sql create mode 100644 auth/sql/migrations/000003_sessions.up.sql create mode 100644 auth/sql/queries/sessions.sql 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) } From cf1e7497e26e823207a8a608f4370b2082bb50da Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 30 Aug 2024 00:50:40 +0200 Subject: [PATCH 12/36] Add swagger and problem details --- auth/.gitignore | 2 + auth/.swaggo | 1 + auth/go.mod | 36 ++++++++++-------- auth/go.sum | 99 +++++++++++++++++++++++++++++-------------------- auth/main.go | 31 +++++++++++++--- auth/users.go | 26 +++++++++++-- shell.nix | 1 + 7 files changed, 132 insertions(+), 64 deletions(-) create mode 100644 auth/.swaggo diff --git a/auth/.gitignore b/auth/.gitignore index 98fa9777..cb4266fd 100644 --- a/auth/.gitignore +++ b/auth/.gitignore @@ -1,2 +1,4 @@ # generated via sqlc dbc/ +# genereated via swag +docs/ diff --git a/auth/.swaggo b/auth/.swaggo new file mode 100644 index 00000000..df6d17f2 --- /dev/null +++ b/auth/.swaggo @@ -0,0 +1 @@ +replace jwt.MapClaims map[string]string diff --git a/auth/go.mod b/auth/go.mod index 385eee02..e3f53464 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -4,24 +4,28 @@ go 1.22.5 require ( github.com/alexedwards/argon2id v1.0.0 - github.com/golang-migrate/migrate v3.5.4+incompatible + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.6.0 github.com/labstack/echo/v4 v4.12.0 + github.com/otaxhu/problem v0.2.0 + github.com/swaggo/echo-swagger v1.4.1 ) -require github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - require ( + github.com/KyleBanks/depth v1.2.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.2+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-migrate/migrate/v4 v4.17.1 github.com/hashicorp/errwrap v1.1.0 // indirect @@ -30,26 +34,28 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lib/pq v1.10.9 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/swaggo/files/v2 v2.0.1 // indirect + github.com/swaggo/swag v1.16.3 github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect - gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + golang.org/x/tools v0.24.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/auth/go.sum b/auth/go.sum index ea825295..3cc58b45 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -1,11 +1,16 @@ -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w= github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg= +github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= @@ -18,16 +23,26 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= -github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -36,8 +51,6 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= -github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -57,16 +70,20 @@ github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -74,25 +91,37 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/otaxhu/problem v0.2.0 h1:cxVSlWHPi0zn1Mvl3/SVwySnnxfpHslENU1MvouSEME= +github.com/otaxhu/problem v0.2.0/go.mod h1:bp1KCPkRRBORbIg4a/p/Sa+FuFuMHVg+iEjnWL/LMKA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= +github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk= +github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= @@ -105,36 +134,27 @@ go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+M go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -144,8 +164,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -157,23 +177,22 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/auth/main.go b/auth/main.go index 2f1ce240..6ef50dd9 100644 --- a/auth/main.go +++ b/auth/main.go @@ -8,7 +8,9 @@ import ( "os" "strconv" + "github.com/otaxhu/problem" "github.com/zoriya/kyoo/keibi/dbc" + _ "github.com/zoriya/kyoo/keibi/docs" "github.com/go-playground/validator/v10" "github.com/golang-migrate/migrate/v4" @@ -19,21 +21,25 @@ import ( "github.com/jackc/pgx/v5/stdlib" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "github.com/swaggo/echo-swagger" ) func ErrorHandler(err error, c echo.Context) { code := http.StatusInternalServerError var message string + if he, ok := err.(*echo.HTTPError); ok { code = he.Code message = fmt.Sprint(he.Message) } else { c.Logger().Error(err) - message = "Internal server error" } - c.JSON(code, struct { - Errors []string `json:"errors"` - }{Errors: []string{message}}) + + ret := problem.NewDefault(code) + if message != "" { + ret.Detail = message + } + c.JSON(code, ret) } type Validator struct { @@ -74,7 +80,7 @@ func OpenDatabase() (*pgxpool.Pool, error) { schema = "keibi" } if schema != "disabled" { - conf.ConnConfig.Config.RuntimeParams["search_path"] = schema + conf.ConnConfig.RuntimeParams["search_path"] = schema } db, err := pgxpool.NewWithConfig(ctx, &conf) @@ -105,10 +111,22 @@ func OpenDatabase() (*pgxpool.Pool, error) { } type Handler struct { - db *dbc.Queries + db *dbc.Queries config *Configuration } +// @title Keibi - Kyoo's auth +// @version 1.0 +// @description Auth system made for kyoo. + +// @contact.name Repository +// @contact.url https://github.com/zoriya/kyoo + +// @license.name GPL-3.0 +// @license.url https://www.gnu.org/licenses/gpl-3.0.en.html + +// @host kyoo.zoriya.dev +// @BasePath /auth func main() { e := echo.New() e.Use(middleware.Logger()) @@ -133,6 +151,7 @@ func main() { e.GET("/users", h.ListUsers) e.POST("/users", h.Register) + e.GET("/swagger/*", echoSwagger.WrapHandler) e.Logger.Fatal(e.Start(":4568")) } diff --git a/auth/users.go b/auth/users.go index fe1f9871..75a07705 100644 --- a/auth/users.go +++ b/auth/users.go @@ -15,7 +15,7 @@ import ( type User struct { ID uuid.UUID `json:"id"` Username string `json:"username"` - Email string `json:"email"` + Email string `json:"email" format:"email"` CreatedDate time.Time `json:"createdDate"` LastSeen time.Time `json:"lastSeen"` Claims jwt.MapClaims `json:"claims"` @@ -25,12 +25,12 @@ type User struct { type OidcHandle struct { Id string `json:"id"` Username string `json:"username"` - ProfileUrl *string `json:"profileUrl"` + ProfileUrl *string `json:"profileUrl" format:"url"` } type RegisterDto struct { Username string `json:"username" validate:"required"` - Email string `json:"email" validate:"required,email"` + Email string `json:"email" validate:"required,email" format:"email"` Password string `json:"password" validate:"required"` } @@ -46,6 +46,15 @@ func MapDbUser(user *dbc.User) User { } } +// @Summary List all users +// @Description List all users existing in this instance. +// @Tags users +// @Accept json +// @Produce json +// @Param afterId query uuid false "used for pagination." +// @Success 200 {object} User[] +// @Failure 400 {object} problem.Problem +// @Router /users [get] func (h *Handler) ListUsers(c echo.Context) error { ctx := context.Background() limit := int32(20) @@ -74,9 +83,20 @@ func (h *Handler) ListUsers(c echo.Context) error { for _, user := range users { ret = append(ret, MapDbUser(&user)) } + // TODO: switch to a Page return c.JSON(200, ret) } +// @Summary Register +// @Description Register as a new user and open a session for it +// @Tags users +// @Accept json +// @Produce json +// @Param device query uuid false "The device the created session will be used on" +// @Param user body RegisterDto false "Registration informations" +// @Success 201 {object} dbc.Session +// @Failure 400 {object} problem.Problem +// @Router /users [post] func (h *Handler) Register(c echo.Context) error { var req RegisterDto err := c.Bind(&req) diff --git a/shell.nix b/shell.nix index 1e4ced1b..fa182ed2 100644 --- a/shell.nix +++ b/shell.nix @@ -39,6 +39,7 @@ in kubernetes-helm go-migrate sqlc + go-swag ]; DOTNET_ROOT = "${dotnet}"; From 0c64d9b15db2f7f0256555529da70521bac117b5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 30 Aug 2024 01:05:03 +0200 Subject: [PATCH 13/36] Add dockerfile --- auth/.dockerignore | 12 ++++++++++++ auth/Dockerfile | 23 +++++++++++++++++++++++ auth/Dockerfile.dev | 16 ++++++++++++++++ docker-compose.dev.yml | 21 +++++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 auth/.dockerignore create mode 100644 auth/Dockerfile create mode 100644 auth/Dockerfile.dev diff --git a/auth/.dockerignore b/auth/.dockerignore new file mode 100644 index 00000000..72546c82 --- /dev/null +++ b/auth/.dockerignore @@ -0,0 +1,12 @@ +Dockerfile* +*.md +.dockerignore +.gitignore +.env* + +# generated via sqlc +dbc/ +# genereated via swag +docs/ + +# vim: ft=gitignore diff --git a/auth/Dockerfile b/auth/Dockerfile new file mode 100644 index 00000000..2142fb98 --- /dev/null +++ b/auth/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.23 AS build +WORKDIR /app + +RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest +RUN go install github.com/swaggo/swag/cmd/swag@latest + +COPY go.mod go.sum ./ +RUN go mod download + +COPY sqlc.yaml sql ./ +RUN sqlc generate + +COPY . . +RUN swag init --parseDependency +RUN CGO_ENABLED=0 GOOS=linux go build -o /keibi + +FROM gcr.io/distroless/base-debian11 +WORKDIR / +EXPOSE 4568 +USER nonroot:nonroot + +COPY --from=build /keibi /keibi +CMD ["/keibi"] diff --git a/auth/Dockerfile.dev b/auth/Dockerfile.dev new file mode 100644 index 00000000..ab73945b --- /dev/null +++ b/auth/Dockerfile.dev @@ -0,0 +1,16 @@ +FROM golang:1.23 AS build +WORKDIR /app + +RUN go install github.com/bokwoon95/wgo@latest +RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest +RUN go install github.com/swaggo/swag/cmd/swag@latest + +COPY go.mod go.sum ./ +RUN go mod download + +# COPY sqlc.yaml ./ +# COPY sql/ ./ +# RUN sqlc generate + +EXPOSE 4568 +CMD ["wgo", "run", "-race", "."] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index cca0f2e0..6c07f455 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -84,6 +84,27 @@ services: - "traefik.enable=true" - "traefik.http.routers.front.rule=PathPrefix(`/`)" + auth: + build: + context: ./auth + dockerfile: Dockerfile.dev + restart: on-failure + depends_on: + postgres: + condition: service_healthy + ports: + - "4568:4568" + env_file: + - ./.env + volumes: + - ./auth:/app + labels: + - "traefik.enable=true" + - "traefik.http.routers.auth.rule=PathPrefix(`/auth/`)" + - "traefik.http.routers.auth.middlewares=auth-sp" + - "traefik.http.middlewares.auth-sp.stripprefix.prefixes=/auth" + - "traefik.http.middlewares.auth-sp.stripprefix.forceSlash=false" + scanner: build: ./scanner restart: on-failure From dc41880ca7ff6262201276f3904642ffa6b0a374 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Aug 2024 17:13:15 +0200 Subject: [PATCH 14/36] Fix pgx configuration --- auth/main.go | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/auth/main.go b/auth/main.go index 6ef50dd9..8f08f13d 100644 --- a/auth/main.go +++ b/auth/main.go @@ -15,8 +15,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/golang-migrate/migrate/v4" pgxd "github.com/golang-migrate/migrate/v4/database/pgx/v5" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" + _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" "github.com/labstack/echo/v4" @@ -60,30 +59,26 @@ func OpenDatabase() (*pgxpool.Pool, error) { if err != nil { return nil, errors.New("invalid postgres port specified") } - conf := pgxpool.Config{ - ConnConfig: &pgx.ConnConfig{ - Config: pgconn.Config{ - Host: os.Getenv("POSTGRES_SERVER"), - Port: uint16(port), - Database: os.Getenv("POSTGRES_DB"), - User: os.Getenv("POSTGRES_USER"), - Password: os.Getenv("POSTGRES_PASSWORD"), - TLSConfig: nil, - RuntimeParams: map[string]string{ - "application_name": "keibi", - }, - }, - }, + + config, _ := pgxpool.ParseConfig("") + config.ConnConfig.Host = os.Getenv("POSTGRES_SERVER") + config.ConnConfig.Port = uint16(port) + config.ConnConfig.Database = os.Getenv("POSTGRES_DB") + config.ConnConfig.User = os.Getenv("POSTGRES_USER") + config.ConnConfig.Password = os.Getenv("POSTGRES_PASSWORD") + config.ConnConfig.TLSConfig = nil + config.ConnConfig.RuntimeParams = map[string]string{ + "application_name": "keibi", } schema := os.Getenv("POSTGRES_SCHEMA") if schema == "" { schema = "keibi" } if schema != "disabled" { - conf.ConnConfig.RuntimeParams["search_path"] = schema + config.ConnConfig.RuntimeParams["search_path"] = schema } - db, err := pgxpool.NewWithConfig(ctx, &conf) + db, err := pgxpool.NewWithConfig(ctx, config) if err != nil { fmt.Printf("Could not connect to database, check your env variables!") return nil, err @@ -97,7 +92,10 @@ func OpenDatabase() (*pgxpool.Pool, error) { } } - driver, err := pgxd.WithInstance(stdlib.OpenDBFromPool(db), &pgxd.Config{}) + dbi := stdlib.OpenDBFromPool(db) + defer dbi.Close() + + driver, err := pgxd.WithInstance(dbi, &pgxd.Config{}) if err != nil { return nil, err } @@ -107,7 +105,7 @@ func OpenDatabase() (*pgxpool.Pool, error) { } m.Up() - return db, nil + return db, err } type Handler struct { @@ -135,7 +133,7 @@ func main() { db, err := OpenDatabase() if err != nil { - e.Logger.Fatal(err) + e.Logger.Fatal("Could not open databse: %v", err) return } From dd1580b8199ca68d5338d875a6368bafd95908ac Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Aug 2024 17:13:33 +0200 Subject: [PATCH 15/36] Fix timestamptz typo --- auth/sql/migrations/000001_users.up.sql | 6 +++--- auth/sql/migrations/000003_sessions.up.sql | 4 ++-- auth/sql/queries/sessions.sql | 2 +- auth/sqlc.yaml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/auth/sql/migrations/000001_users.up.sql b/auth/sql/migrations/000001_users.up.sql index 2f6689a5..25358905 100644 --- a/auth/sql/migrations/000001_users.up.sql +++ b/auth/sql/migrations/000001_users.up.sql @@ -7,8 +7,8 @@ create table users( password text, claims jsonb not null, - created_date timestampz not null default now()::timestampz, - last_seen timestampz not null default now()::timestampz + created_date timestamptz not null default now()::timestamptz, + last_seen timestamptz not null default now()::timestamptz ); create table oidc_handle( @@ -21,7 +21,7 @@ create table oidc_handle( access_token text, refresh_token text, - expire_at timestampz, + expire_at timestamptz, constraint oidc_handle_pk primary key (user_id, provider) ); diff --git a/auth/sql/migrations/000003_sessions.up.sql b/auth/sql/migrations/000003_sessions.up.sql index ff9db051..aae74520 100644 --- a/auth/sql/migrations/000003_sessions.up.sql +++ b/auth/sql/migrations/000003_sessions.up.sql @@ -3,8 +3,8 @@ 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, + created_date timestamptz not null default now()::timestamptz, + last_used timestamptz not null default now()::timestamptz, device varchar(1024) ); diff --git a/auth/sql/queries/sessions.sql b/auth/sql/queries/sessions.sql index 38783702..2b6c1d48 100644 --- a/auth/sql/queries/sessions.sql +++ b/auth/sql/queries/sessions.sql @@ -12,7 +12,7 @@ limit 1; update sessions set - last_used = now()::timestampz + last_used = now()::timestamptz where id = $1; diff --git a/auth/sqlc.yaml b/auth/sqlc.yaml index 75049429..440f0e06 100644 --- a/auth/sqlc.yaml +++ b/auth/sqlc.yaml @@ -11,7 +11,7 @@ sql: emit_pointers_for_null_types: true emit_json_tags: true overrides: - - db_type: "timestampz" + - db_type: "timestamptz" go_type: import: "time" type: "Time" From 4685f76cad62bd62f91c6ce5f8e651e1b4a539aa Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Aug 2024 17:13:40 +0200 Subject: [PATCH 16/36] Go mod tidy --- auth/go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth/go.sum b/auth/go.sum index 3cc58b45..7ec9b54b 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -82,6 +82,8 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= From b340958348c0fbb8464ec4f963722bac2c8394f5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 2 Sep 2024 14:32:27 +0200 Subject: [PATCH 17/36] Add login route --- auth/.swaggo | 1 + auth/main.go | 8 +++++-- auth/session.go | 43 ++++++++++++++++++++++++++++++++++++++ auth/sql/queries/users.sql | 13 ++++++++++-- auth/users.go | 9 ++++---- 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/auth/.swaggo b/auth/.swaggo index df6d17f2..3372ddb7 100644 --- a/auth/.swaggo +++ b/auth/.swaggo @@ -1 +1,2 @@ replace jwt.MapClaims map[string]string +replace uuid.UUID string diff --git a/auth/main.go b/auth/main.go index 8f08f13d..e21ba402 100644 --- a/auth/main.go +++ b/auth/main.go @@ -83,7 +83,6 @@ func OpenDatabase() (*pgxpool.Pool, error) { fmt.Printf("Could not connect to database, check your env variables!") return nil, err } - defer db.Close() if schema != "disabled" { _, err = db.Exec(ctx, fmt.Sprintf("create schema if not exists %s", schema)) @@ -92,6 +91,7 @@ func OpenDatabase() (*pgxpool.Pool, error) { } } + fmt.Println("Migrating database") dbi := stdlib.OpenDBFromPool(db) defer dbi.Close() @@ -104,8 +104,9 @@ func OpenDatabase() (*pgxpool.Pool, error) { return nil, err } m.Up() + fmt.Println("Migrating finished") - return db, err + return db, nil } type Handler struct { @@ -149,6 +150,9 @@ func main() { e.GET("/users", h.ListUsers) e.POST("/users", h.Register) + + e.POST("/session", h.Login) + e.GET("/swagger/*", echoSwagger.WrapHandler) e.Logger.Fatal(e.Start(":4568")) diff --git a/auth/session.go b/auth/session.go index a0d539bd..ae064fe8 100644 --- a/auth/session.go +++ b/auth/session.go @@ -9,6 +9,7 @@ import ( "net/http" "time" + "github.com/alexedwards/argon2id" "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" "github.com/zoriya/kyoo/keibi/dbc" @@ -19,6 +20,48 @@ type LoginDto struct { Password string `json:"password" validate:"required"` } +// @Summary Login +// @Description Login to your account and open a session +// @Tags sessions +// @Accept json +// @Produce json +// @Param device query uuid false "The device the created session will be used on" +// @Param user body LoginDto false "Account informations" +// @Success 201 {object} dbc.Session +// @Failure 400 {object} problem.Problem "Invalid login body" +// @Failure 400 {object} problem.Problem "Invalid password" +// @Failure 404 {object} problem.Problem "Account does not exists" +// @Router /sessions [post] +func (h *Handler) Login(c echo.Context) error { + var req LoginDto + err := c.Bind(&req) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + if err = c.Validate(&req); err != nil { + return err + } + + dbuser, err := h.db.GetUserByLogin(context.Background(), req.Login) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, "No account exists with the specified email or username.") + } + if dbuser.Password == nil { + return echo.NewHTTPError(http.StatusBadRequest, "Can't login with password, this account was created with OIDC.") + } + + match, err := argon2id.ComparePasswordAndHash(req.Password, *dbuser.Password) + if err != nil { + return err + } + if !match { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid password") + } + + user := MapDbUser(&dbuser) + return h.createSession(c, &user) +} + func (h *Handler) createSession(c echo.Context, user *User) error { ctx := context.Background() diff --git a/auth/sql/queries/users.sql b/auth/sql/queries/users.sql index 0d25d81b..ffdd37ef 100644 --- a/auth/sql/queries/users.sql +++ b/auth/sql/queries/users.sql @@ -18,7 +18,7 @@ order by id limit $1; --- name: GetUser :one +-- name: GetUser :many select sqlc.embed(u), sqlc.embed(h) @@ -26,7 +26,16 @@ from users as u left join oidc_handle as h on u.id = h.user_id where - u.id = $1 + u.id = $1; + +-- name: GetUserByLogin :one +select + * +from + users +where + email = sqlc.arg(login) + or username = sqlc.arg(login) limit 1; -- name: CreateUser :one diff --git a/auth/users.go b/auth/users.go index 75a07705..6035372f 100644 --- a/auth/users.go +++ b/auth/users.go @@ -29,7 +29,7 @@ type OidcHandle struct { } type RegisterDto struct { - Username string `json:"username" validate:"required"` + Username string `json:"username" validate:"required,excludes=@"` Email string `json:"email" validate:"required,email" format:"email"` Password string `json:"password" validate:"required"` } @@ -51,7 +51,7 @@ func MapDbUser(user *dbc.User) User { // @Tags users // @Accept json // @Produce json -// @Param afterId query uuid false "used for pagination." +// @Param afterId query string false "used for pagination." Format(uuid) // @Success 200 {object} User[] // @Failure 400 {object} problem.Problem // @Router /users [get] @@ -95,7 +95,8 @@ func (h *Handler) ListUsers(c echo.Context) error { // @Param device query uuid false "The device the created session will be used on" // @Param user body RegisterDto false "Registration informations" // @Success 201 {object} dbc.Session -// @Failure 400 {object} problem.Problem +// @Failure 400 {object} problem.Problem "Invalid register body" +// @Success 409 {object} problem.Problem "Duplicated email or username" // @Router /users [post] func (h *Handler) Register(c echo.Context) error { var req RegisterDto @@ -109,7 +110,7 @@ func (h *Handler) Register(c echo.Context) error { pass, err := argon2id.CreateHash(req.Password, argon2id.DefaultParams) if err != nil { - return echo.NewHTTPError(400, "Invalid password") + return err } duser, err := h.db.CreateUser(context.Background(), dbc.CreateUserParams{ From 7b08afde068ce66d8a82dadeda811146559a2847 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 2 Sep 2024 14:54:59 +0200 Subject: [PATCH 18/36] Use a proper id for sessions, leave token as a separate field --- auth/session.go | 4 ++-- auth/sql/migrations/000003_sessions.up.sql | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/auth/session.go b/auth/session.go index ae064fe8..93075857 100644 --- a/auth/session.go +++ b/auth/session.go @@ -78,8 +78,8 @@ func (h *Handler) createSession(c echo.Context, user *User) error { } session, err := h.db.CreateSession(ctx, dbc.CreateSessionParams{ - ID: base64.StdEncoding.EncodeToString(id), - UserID: user.ID, + Token: base64.StdEncoding.EncodeToString(id), + UserID: user.Id, Device: device, }) if err != nil { diff --git a/auth/sql/migrations/000003_sessions.up.sql b/auth/sql/migrations/000003_sessions.up.sql index aae74520..f9383ed9 100644 --- a/auth/sql/migrations/000003_sessions.up.sql +++ b/auth/sql/migrations/000003_sessions.up.sql @@ -1,7 +1,8 @@ begin; create table sessions( - id varchar(128) not null primary key, + id uuid not null primary key, + token varchar(128) not null unique, user_id uuid not null references users(id) on delete cascade, created_date timestamptz not null default now()::timestamptz, last_used timestamptz not null default now()::timestamptz, From 95da0184a0ed9e042ae314357e773d3c1178a144 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 2 Sep 2024 14:55:22 +0200 Subject: [PATCH 19/36] Cleanup return codes and add docs comment for swagger --- auth/session.go | 32 +++++++++++++++++++++++--------- auth/sql/queries/sessions.sql | 12 +++++++++--- auth/users.go | 19 ++++++++++++++++--- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/auth/session.go b/auth/session.go index 93075857..40294e3c 100644 --- a/auth/session.go +++ b/auth/session.go @@ -16,7 +16,9 @@ import ( ) type LoginDto struct { + // Either the email or the username. Login string `json:"login" validate:"required"` + // Password of the account. Password string `json:"password" validate:"required"` } @@ -25,12 +27,13 @@ type LoginDto struct { // @Tags sessions // @Accept json // @Produce json -// @Param device query uuid false "The device the created session will be used on" -// @Param user body LoginDto false "Account informations" -// @Success 201 {object} dbc.Session -// @Failure 400 {object} problem.Problem "Invalid login body" -// @Failure 400 {object} problem.Problem "Invalid password" -// @Failure 404 {object} problem.Problem "Account does not exists" +// @Param device query string false "The device the created session will be used on" +// @Param login body LoginDto false "Account informations" +// @Success 201 {object} dbc.Session +// @Failure 400 {object} problem.Problem "Invalid login body" +// @Failure 403 {object} problem.Problem "Invalid password" +// @Failure 404 {object} problem.Problem "Account does not exists" +// @Failure 422 {object} problem.Problem "User does not have a password (registered via oidc, please login via oidc)" // @Router /sessions [post] func (h *Handler) Login(c echo.Context) error { var req LoginDto @@ -47,7 +50,7 @@ func (h *Handler) Login(c echo.Context) error { return echo.NewHTTPError(http.StatusNotFound, "No account exists with the specified email or username.") } if dbuser.Password == nil { - return echo.NewHTTPError(http.StatusBadRequest, "Can't login with password, this account was created with OIDC.") + return echo.NewHTTPError(http.StatusUnprocessableEntity, "Can't login with password, this account was created with OIDC.") } match, err := argon2id.ComparePasswordAndHash(req.Password, *dbuser.Password) @@ -55,7 +58,7 @@ func (h *Handler) Login(c echo.Context) error { return err } if !match { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid password") + return echo.NewHTTPError(http.StatusForbidden, "Invalid password") } user := MapDbUser(&dbuser) @@ -88,9 +91,20 @@ func (h *Handler) createSession(c echo.Context, user *User) error { return c.JSON(201, session) } +// @Summary Get JWT +// @Description Convert a session token to a short lived JWT. +// @Tags sessions +// @Accept json +// @Produce json +// @Param user body LoginDto false "Account informations" +// @Success 200 {object} dbc.Session +// @Failure 400 {object} problem.Problem "Invalid login body" +// @Failure 400 {object} problem.Problem "Invalid password" +// @Failure 404 {object} problem.Problem "Account does not exists" +// @Router /jwt [get] func (h *Handler) CreateJwt(c echo.Context, user *User) error { claims := maps.Clone(user.Claims) - claims["sub"] = user.ID.String() + claims["sub"] = user.Id.String() claims["iss"] = h.config.Issuer claims["exp"] = &jwt.NumericDate{ Time: time.Now().UTC().Add(time.Hour), diff --git a/auth/sql/queries/sessions.sql b/auth/sql/queries/sessions.sql index 2b6c1d48..5cfa829c 100644 --- a/auth/sql/queries/sessions.sql +++ b/auth/sql/queries/sessions.sql @@ -1,11 +1,11 @@ --- name: GetUserFromSession :one +-- name: GetUserFromToken :one select u.* from users as u left join sessions as s on u.id = s.user_id where - s.id = $1 + s.token = $1 limit 1; -- name: TouchSession :exec @@ -27,7 +27,7 @@ order by last_used; -- name: CreateSession :one -insert into sessions(id, user_id, device) +insert into sessions(token, user_id, device) values ($1, $2, $3) returning *; @@ -38,3 +38,9 @@ where id = $1 returning *; +-- name: DeleteSessionByToken :one +delete from sessions +where token = $1 +returning + *; + diff --git a/auth/users.go b/auth/users.go index 6035372f..fc4512b1 100644 --- a/auth/users.go +++ b/auth/users.go @@ -13,30 +13,43 @@ import ( ) type User struct { - ID uuid.UUID `json:"id"` + // Id of the user. + Id uuid.UUID `json:"id"` + // Username of the user. Can be used as a login. Username string `json:"username"` + // Email of the user. Can be used as a login. Email string `json:"email" format:"email"` + // When was this account created? CreatedDate time.Time `json:"createdDate"` + // When was the last time this account made any authorized request? LastSeen time.Time `json:"lastSeen"` + // List of custom claims JWT created via get /jwt will have Claims jwt.MapClaims `json:"claims"` + // List of other login method available for this user. Access tokens wont be returned here. Oidc map[string]OidcHandle `json:"oidc,omitempty"` } type OidcHandle struct { + // Id of this oidc handle. Id string `json:"id"` + // Username of the user on the external service. Username string `json:"username"` + // Link to the profile of the user on the external service. Null if unknown or irrelevant. ProfileUrl *string `json:"profileUrl" format:"url"` } type RegisterDto struct { + // Username of the new account, can't contain @ signs. Can be used for login. Username string `json:"username" validate:"required,excludes=@"` + // Valid email that could be used for forgotten password requests. Can be used for login. Email string `json:"email" validate:"required,email" format:"email"` + // Password to use. Password string `json:"password" validate:"required"` } func MapDbUser(user *dbc.User) User { return User{ - ID: user.ID, + Id: user.ID, Username: user.Username, Email: user.Email, CreatedDate: user.CreatedDate, @@ -53,7 +66,7 @@ func MapDbUser(user *dbc.User) User { // @Produce json // @Param afterId query string false "used for pagination." Format(uuid) // @Success 200 {object} User[] -// @Failure 400 {object} problem.Problem +// @Failure 400 {object} problem.Problem "Invalid after id" // @Router /users [get] func (h *Handler) ListUsers(c echo.Context) error { ctx := context.Background() From e197062f64793ec5bed9327720b59b529b13f0d9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 2 Sep 2024 16:24:03 +0200 Subject: [PATCH 20/36] Add /jwt route --- auth/config.go | 2 ++ auth/main.go | 5 ++++ auth/session.go | 52 +++++++++++++++++++++++++---------- auth/sql/queries/sessions.sql | 4 +-- auth/sql/queries/users.sql | 8 ++++++ auth/sqlc.yaml | 7 +++++ auth/users.go | 4 +-- 7 files changed, 64 insertions(+), 18 deletions(-) diff --git a/auth/config.go b/auth/config.go index dc0cec14..aa777216 100644 --- a/auth/config.go +++ b/auth/config.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "encoding/base64" + "time" "github.com/golang-jwt/jwt/v5" "github.com/zoriya/kyoo/keibi/dbc" @@ -13,6 +14,7 @@ type Configuration struct { JwtSecret []byte Issuer string DefaultClaims jwt.MapClaims + ExpirationDelay time.Duration } const ( diff --git a/auth/main.go b/auth/main.go index e21ba402..e3032b29 100644 --- a/auth/main.go +++ b/auth/main.go @@ -126,6 +126,10 @@ type Handler struct { // @host kyoo.zoriya.dev // @BasePath /auth + +// @securityDefinitions.apiKey Token +// @in header +// @name Authorization func main() { e := echo.New() e.Use(middleware.Logger()) @@ -151,6 +155,7 @@ func main() { e.GET("/users", h.ListUsers) e.POST("/users", h.Register) + e.GET("/jwt", h.CreateJwt) e.POST("/session", h.Login) e.GET("/swagger/*", echoSwagger.WrapHandler) diff --git a/auth/session.go b/auth/session.go index 40294e3c..6e9a265f 100644 --- a/auth/session.go +++ b/auth/session.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "maps" "net/http" + "strings" "time" "github.com/alexedwards/argon2id" @@ -17,7 +18,7 @@ import ( type LoginDto struct { // Either the email or the username. - Login string `json:"login" validate:"required"` + Login string `json:"login" validate:"required"` // Password of the account. Password string `json:"password" validate:"required"` } @@ -82,7 +83,7 @@ func (h *Handler) createSession(c echo.Context, user *User) error { session, err := h.db.CreateSession(ctx, dbc.CreateSessionParams{ Token: base64.StdEncoding.EncodeToString(id), - UserID: user.Id, + UserId: user.Id, Device: device, }) if err != nil { @@ -91,20 +92,43 @@ func (h *Handler) createSession(c echo.Context, user *User) error { return c.JSON(201, session) } +type Jwt struct { + // The jwt token you can use for all authorized call to either keibi or other services. + Token string `json:"token"` +} + // @Summary Get JWT // @Description Convert a session token to a short lived JWT. // @Tags sessions // @Accept json // @Produce json -// @Param user body LoginDto false "Account informations" -// @Success 200 {object} dbc.Session -// @Failure 400 {object} problem.Problem "Invalid login body" -// @Failure 400 {object} problem.Problem "Invalid password" -// @Failure 404 {object} problem.Problem "Account does not exists" +// @Security Token +// @Success 200 {object} Jwt +// @Failure 401 {object} problem.Problem "Missing session token" +// @Failure 403 {object} problem.Problem "Invalid session token (or expired)" // @Router /jwt [get] -func (h *Handler) CreateJwt(c echo.Context, user *User) error { - claims := maps.Clone(user.Claims) - claims["sub"] = user.Id.String() +func (h *Handler) CreateJwt(c echo.Context) error { + auth := c.Request().Header.Get("Authorization") + if !strings.HasPrefix(auth, "Bearer ") { + return echo.NewHTTPError(http.StatusUnauthorized, "Missing session token") + } + token := auth[len("Bearer "):] + + session, err := h.db.GetUserFromToken(context.Background(), token) + if err != nil { + return echo.NewHTTPError(http.StatusForbidden, "Invalid token") + } + if session.LastUsed.Add(h.config.ExpirationDelay).Compare(time.Now().UTC()) < 0 { + return echo.NewHTTPError(http.StatusForbidden, "Token has expired") + } + + go func() { + h.db.TouchSession(context.Background(), session.Id) + h.db.TouchUser(context.Background(), session.User.Id) + }() + + claims := maps.Clone(session.User.Claims) + claims["sub"] = session.User.Id.String() claims["iss"] = h.config.Issuer claims["exp"] = &jwt.NumericDate{ Time: time.Now().UTC().Add(time.Hour), @@ -112,12 +136,12 @@ func (h *Handler) CreateJwt(c echo.Context, user *User) error { claims["iss"] = &jwt.NumericDate{ Time: time.Now().UTC(), } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - t, err := token.SignedString(h.config.JwtSecret) + jwt := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + t, err := jwt.SignedString(h.config.JwtSecret) if err != nil { return err } - return c.JSON(http.StatusOK, echo.Map{ - "token": t, + return c.JSON(http.StatusOK, Jwt{ + Token: t, }) } diff --git a/auth/sql/queries/sessions.sql b/auth/sql/queries/sessions.sql index 5cfa829c..11006f6a 100644 --- a/auth/sql/queries/sessions.sql +++ b/auth/sql/queries/sessions.sql @@ -1,9 +1,9 @@ -- name: GetUserFromToken :one select - u.* + s.id, s.last_used, sqlc.embed(u) from users as u - left join sessions as s on u.id = s.user_id + inner join sessions as s on u.id = s.user_id where s.token = $1 limit 1; diff --git a/auth/sql/queries/users.sql b/auth/sql/queries/users.sql index ffdd37ef..b08f1787 100644 --- a/auth/sql/queries/users.sql +++ b/auth/sql/queries/users.sql @@ -38,6 +38,14 @@ where or username = sqlc.arg(login) limit 1; +-- name: TouchUser :exec +update + users +set + last_used = now()::timestamptz +where + id = $1; + -- name: CreateUser :one insert into users(username, email, password, claims) values ($1, $2, $3, $4) diff --git a/auth/sqlc.yaml b/auth/sqlc.yaml index 440f0e06..8a7af618 100644 --- a/auth/sqlc.yaml +++ b/auth/sqlc.yaml @@ -10,11 +10,18 @@ sql: out: "dbc" emit_pointers_for_null_types: true emit_json_tags: true + initialisms: [] overrides: - db_type: "timestamptz" go_type: import: "time" type: "Time" + - db_type: "timestamptz" + nullable: true + go_type: + import: "time" + type: "Time" + pointer: true - db_type: "uuid" go_type: import: "github.com/google/uuid" diff --git a/auth/users.go b/auth/users.go index fc4512b1..f54487fe 100644 --- a/auth/users.go +++ b/auth/users.go @@ -49,7 +49,7 @@ type RegisterDto struct { func MapDbUser(user *dbc.User) User { return User{ - Id: user.ID, + Id: user.Id, Username: user.Username, Email: user.Email, CreatedDate: user.CreatedDate, @@ -84,7 +84,7 @@ func (h *Handler) ListUsers(c echo.Context) error { } users, err = h.db.GetAllUsersAfter(ctx, dbc.GetAllUsersAfterParams{ Limit: limit, - AfterID: uid, + AfterId: uid, }) } From caa4cf4c8dbdde54c5a0db292e3b89b9cd64b8ad Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 3 Sep 2024 14:23:52 +0200 Subject: [PATCH 21/36] Switch to asymetric keys for jwt signing --- auth/config.go | 41 ++++++++++++++++++++++++++++------------- auth/session.go | 6 +++--- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/auth/config.go b/auth/config.go index aa777216..1a509030 100644 --- a/auth/config.go +++ b/auth/config.go @@ -3,7 +3,9 @@ package main import ( "context" "crypto/rand" - "encoding/base64" + "crypto/rsa" + "crypto/x509" + "encoding/pem" "time" "github.com/golang-jwt/jwt/v5" @@ -11,14 +13,15 @@ import ( ) type Configuration struct { - JwtSecret []byte - Issuer string - DefaultClaims jwt.MapClaims + JwtPrivateKey *rsa.PrivateKey + JwtPublicKey *rsa.PublicKey + Issuer string + DefaultClaims jwt.MapClaims ExpirationDelay time.Duration } const ( - JwtSecret = "jwt_secret" + JwtPrivateKey = "jwt_private_key" ) func LoadConfiguration(db *dbc.Queries) (*Configuration, error) { @@ -32,22 +35,34 @@ func LoadConfiguration(db *dbc.Queries) (*Configuration, error) { for _, conf := range confs { switch conf.Key { - case JwtSecret: - secret, err := base64.StdEncoding.DecodeString(conf.Value) + case JwtPrivateKey: + block, _ := pem.Decode([]byte(conf.Value)) + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } - ret.JwtSecret = secret + ret.JwtPrivateKey = key + ret.JwtPublicKey = &key.PublicKey } } - if ret.JwtSecret == nil { - ret.JwtSecret = make([]byte, 128) - rand.Read(ret.JwtSecret) + if ret.JwtPrivateKey == nil { + ret.JwtPrivateKey, err = rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, err + } + ret.JwtPublicKey = &ret.JwtPrivateKey.PublicKey + + pemd := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(ret.JwtPrivateKey), + }, + ) _, err := db.SaveConfig(ctx, dbc.SaveConfigParams{ - Key: JwtSecret, - Value: base64.StdEncoding.EncodeToString(ret.JwtSecret), + Key: JwtPrivateKey, + Value: string(pemd), }) if err != nil { return nil, err diff --git a/auth/session.go b/auth/session.go index 6e9a265f..3a91ee86 100644 --- a/auth/session.go +++ b/auth/session.go @@ -100,7 +100,6 @@ type Jwt struct { // @Summary Get JWT // @Description Convert a session token to a short lived JWT. // @Tags sessions -// @Accept json // @Produce json // @Security Token // @Success 200 {object} Jwt @@ -129,6 +128,7 @@ func (h *Handler) CreateJwt(c echo.Context) error { claims := maps.Clone(session.User.Claims) claims["sub"] = session.User.Id.String() + claims["sid"] = session.Id.String() claims["iss"] = h.config.Issuer claims["exp"] = &jwt.NumericDate{ Time: time.Now().UTC().Add(time.Hour), @@ -136,8 +136,8 @@ func (h *Handler) CreateJwt(c echo.Context) error { claims["iss"] = &jwt.NumericDate{ Time: time.Now().UTC(), } - jwt := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - t, err := jwt.SignedString(h.config.JwtSecret) + jwt := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + t, err := jwt.SignedString(h.config.JwtPrivateKey) if err != nil { return err } From 3c73196f87e78f9c213089497337771695888d1b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 3 Sep 2024 16:05:35 +0200 Subject: [PATCH 22/36] Add logout and get /users/me --- auth/go.mod | 1 + auth/go.sum | 66 ++++++++++++++++++++++---------- auth/main.go | 17 ++++++++- auth/session.go | 63 ++++++++++++++++++++++++++++++ auth/sql/queries/sessions.sql | 11 ++---- auth/sqlc.yaml | 1 + auth/users.go | 72 ++++++++++++++++++++++++++++++----- 7 files changed, 192 insertions(+), 39 deletions(-) diff --git a/auth/go.mod b/auth/go.mod index e3f53464..a926db35 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -35,6 +35,7 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/labstack/echo-jwt/v4 v4.2.0 github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect diff --git a/auth/go.sum b/auth/go.sum index 7ec9b54b..6233c759 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -1,16 +1,17 @@ -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= +github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w= github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg= -github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= @@ -37,12 +38,12 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -51,6 +52,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= +github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -72,10 +75,11 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c= +github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -93,10 +97,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -107,8 +107,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -124,6 +122,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= @@ -135,28 +135,45 @@ go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt3 go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -166,6 +183,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -179,20 +198,27 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/auth/main.go b/auth/main.go index e3032b29..2ecd0d65 100644 --- a/auth/main.go +++ b/auth/main.go @@ -18,6 +18,7 @@ import ( _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" + "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/swaggo/echo-swagger" @@ -130,6 +131,10 @@ type Handler struct { // @securityDefinitions.apiKey Token // @in header // @name Authorization + +// @securityDefinitions.apiKey Jwt +// @in header +// @name Authorization func main() { e := echo.New() e.Use(middleware.Logger()) @@ -152,11 +157,19 @@ func main() { } h.config = conf - e.GET("/users", h.ListUsers) + r := e.Group("") + r.Use(echojwt.WithConfig(echojwt.Config{ + SigningKey: h.config.JwtPublicKey, + })) + + r.GET("/users", h.ListUsers) e.POST("/users", h.Register) + e.POST("/sessions", h.Login) + r.DELETE("/sessions", h.Logout) + r.DELETE("/sessions/:id", h.Logout) + e.GET("/jwt", h.CreateJwt) - e.POST("/session", h.Login) e.GET("/swagger/*", echoSwagger.WrapHandler) diff --git a/auth/session.go b/auth/session.go index 3a91ee86..5df466a0 100644 --- a/auth/session.go +++ b/auth/session.go @@ -12,10 +12,31 @@ import ( "github.com/alexedwards/argon2id" "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/zoriya/kyoo/keibi/dbc" ) +type Session struct { + // Unique id of this session. Can be used for calls to DELETE + Id uuid.UUID `json:"id"` + // When was the session first opened + CreatedDate time.Time `json:"createdDate"` + // Last date this session was used to access a service. + LastUsed time.Time `json:"lastUsed"` + // Device that created the session. + Device *string `json:"device"` +} + +func MapSession(ses *dbc.Session) Session { + return Session{ + Id: ses.Id, + CreatedDate: ses.CreatedDate, + LastUsed: ses.LastUsed, + Device: ses.Device, + } +} + type LoginDto struct { // Either the email or the username. Login string `json:"login" validate:"required"` @@ -145,3 +166,45 @@ func (h *Handler) CreateJwt(c echo.Context) error { Token: t, }) } + +// @Summary Logout +// @Description Delete a session and logout +// @Tags sessions +// @Produce json +// @Security Jwt +// @Param id path string true "The id of the session to delete" Format(uuid) +// @Success 200 {object} Session +// @Failure 400 {object} problem.Problem "Invalid session id" +// @Failure 401 {object} problem.Problem "Missing jwt token" +// @Failure 403 {object} problem.Problem "Invalid jwt token (or expired)" +// @Failure 404 {object} problem.Problem "Session not found with specified id (if not using the /current route)" +// @Router /sessions/{id} [delete] +// @Router /sessions/current [delete] +func (h *Handler) Logout(c echo.Context) error { + uid, err := GetCurrentUserId(c) + if err != nil { + return err + } + + session := c.Param("id") + if session == "" { + sid, ok := c.Get("user").(*jwt.Token).Claims.(jwt.MapClaims)["sid"] + if !ok { + return echo.NewHTTPError(400, "Missing session id") + } + session = sid.(string) + } + sid, err := uuid.Parse(session) + if err != nil { + return echo.NewHTTPError(400, "Invalid session id") + } + + ret, err := h.db.DeleteSession(context.Background(), dbc.DeleteSessionParams{ + Id: sid, + UserId: uid, + }) + if err != nil { + return echo.NewHTTPError(404, "Session not found with specified id") + } + return c.JSON(200, MapSession(&ret)) +} diff --git a/auth/sql/queries/sessions.sql b/auth/sql/queries/sessions.sql index 11006f6a..a8e9a285 100644 --- a/auth/sql/queries/sessions.sql +++ b/auth/sql/queries/sessions.sql @@ -1,6 +1,8 @@ -- name: GetUserFromToken :one select - s.id, s.last_used, sqlc.embed(u) + s.id, + s.last_used, + sqlc.embed(u) from users as u inner join sessions as s on u.id = s.user_id @@ -35,12 +37,7 @@ returning -- name: DeleteSession :one delete from sessions where id = $1 -returning - *; - --- name: DeleteSessionByToken :one -delete from sessions -where token = $1 + and user_id = $2 returning *; diff --git a/auth/sqlc.yaml b/auth/sqlc.yaml index 8a7af618..3bc35e05 100644 --- a/auth/sqlc.yaml +++ b/auth/sqlc.yaml @@ -10,6 +10,7 @@ sql: out: "dbc" emit_pointers_for_null_types: true emit_json_tags: true + json_tags_case_style: camel initialisms: [] overrides: - db_type: "timestamptz" diff --git a/auth/users.go b/auth/users.go index f54487fe..53e2e505 100644 --- a/auth/users.go +++ b/auth/users.go @@ -14,26 +14,26 @@ import ( type User struct { // Id of the user. - Id uuid.UUID `json:"id"` + Id uuid.UUID `json:"id"` // Username of the user. Can be used as a login. - Username string `json:"username"` + Username string `json:"username"` // Email of the user. Can be used as a login. - Email string `json:"email" format:"email"` + Email string `json:"email" format:"email"` // When was this account created? - CreatedDate time.Time `json:"createdDate"` + CreatedDate time.Time `json:"createdDate"` // When was the last time this account made any authorized request? - LastSeen time.Time `json:"lastSeen"` + LastSeen time.Time `json:"lastSeen"` // List of custom claims JWT created via get /jwt will have - Claims jwt.MapClaims `json:"claims"` + Claims jwt.MapClaims `json:"claims"` // List of other login method available for this user. Access tokens wont be returned here. - Oidc map[string]OidcHandle `json:"oidc,omitempty"` + Oidc map[string]OidcHandle `json:"oidc,omitempty"` } type OidcHandle struct { // Id of this oidc handle. - Id string `json:"id"` + Id string `json:"id"` // Username of the user on the external service. - Username string `json:"username"` + Username string `json:"username"` // Link to the profile of the user on the external service. Null if unknown or irrelevant. ProfileUrl *string `json:"profileUrl" format:"url"` } @@ -42,7 +42,7 @@ type RegisterDto struct { // Username of the new account, can't contain @ signs. Can be used for login. Username string `json:"username" validate:"required,excludes=@"` // Valid email that could be used for forgotten password requests. Can be used for login. - Email string `json:"email" validate:"required,email" format:"email"` + Email string `json:"email" validate:"required,email" format:"email"` // Password to use. Password string `json:"password" validate:"required"` } @@ -59,11 +59,20 @@ func MapDbUser(user *dbc.User) User { } } +func MapOidc(oidc *dbc.OidcHandle) OidcHandle { + return OidcHandle{ + Id: oidc.Id, + Username: oidc.Username, + ProfileUrl: oidc.ProfileUrl, + } +} + // @Summary List all users // @Description List all users existing in this instance. // @Tags users // @Accept json // @Produce json +// @Security Jwt[users.read] // @Param afterId query string false "used for pagination." Format(uuid) // @Success 200 {object} User[] // @Failure 400 {object} problem.Problem "Invalid after id" @@ -100,6 +109,49 @@ func (h *Handler) ListUsers(c echo.Context) error { return c.JSON(200, ret) } +// @Summary Get me +// @Description Get informations about the currently connected user +// @Tags users +// @Produce json +// @Security Jwt +// @Success 200 {object} User +// @Failure 401 {object} problem.Problem "Missing jwt token" +// @Failure 403 {object} problem.Problem "Invalid jwt token (or expired)" +// @Router /users/me [get] +func (h *Handler) GetMe(c echo.Context) error { + id, err := GetCurrentUserId(c) + if err != nil { + return err + } + dbuser, err := h.db.GetUser(context.Background(), id) + if err != nil { + return err + } + + user := MapDbUser(&dbuser[0].User) + for _, oidc := range dbuser { + user.Oidc[oidc.OidcHandle.Provider] = MapOidc(&oidc.OidcHandle) + } + + return c.JSON(200, user) +} + +func GetCurrentUserId(c echo.Context) (uuid.UUID, error) { + user := c.Get("user").(*jwt.Token) + if user == nil { + return uuid.UUID{}, echo.NewHTTPError(401, "Unauthorized") + } + sub, err := user.Claims.GetSubject() + if err != nil { + return uuid.UUID{}, echo.NewHTTPError(403, "Could not retrive subject") + } + ret, err := uuid.Parse(sub) + if err != nil { + return uuid.UUID{}, echo.NewHTTPError(403, "Invalid id") + } + return ret, nil +} + // @Summary Register // @Description Register as a new user and open a session for it // @Tags users From 1b192c7d05de465d91ca34632e1ec1ee1b795b17 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 3 Sep 2024 17:24:06 +0200 Subject: [PATCH 23/36] Check permissions --- auth/main.go | 2 ++ auth/{session.go => sessions.go} | 0 auth/users.go | 54 +++++++++++++++++++--------- auth/utils.go | 62 ++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 17 deletions(-) rename auth/{session.go => sessions.go} (100%) create mode 100644 auth/utils.go diff --git a/auth/main.go b/auth/main.go index 2ecd0d65..16943aed 100644 --- a/auth/main.go +++ b/auth/main.go @@ -163,6 +163,8 @@ func main() { })) r.GET("/users", h.ListUsers) + r.GET("/users/:id", h.GetUser) + r.GET("/users/me", h.GetMe) e.POST("/users", h.Register) e.POST("/sessions", h.Login) diff --git a/auth/session.go b/auth/sessions.go similarity index 100% rename from auth/session.go rename to auth/sessions.go diff --git a/auth/users.go b/auth/users.go index 53e2e505..08e2f3eb 100644 --- a/auth/users.go +++ b/auth/users.go @@ -78,12 +78,16 @@ func MapOidc(oidc *dbc.OidcHandle) OidcHandle { // @Failure 400 {object} problem.Problem "Invalid after id" // @Router /users [get] func (h *Handler) ListUsers(c echo.Context) error { + err := CheckPermission(c, []string{"user.read"}) + if err != nil { + return err + } + ctx := context.Background() limit := int32(20) id := c.Param("afterId") var users []dbc.User - var err error if id == "" { users, err = h.db.GetAllUsers(ctx, limit) } else { @@ -109,6 +113,38 @@ func (h *Handler) ListUsers(c echo.Context) error { return c.JSON(200, ret) } +// @Summary Get user +// @Description Get informations about a user from it's id +// @Tags users +// @Produce json +// @Security Jwt[users.read] +// @Param id path string true "The id of the user" Format(uuid) +// @Success 200 {object} User +// @Failure 404 {object} problem.Problem "No user with the given id found" +// @Router /users/{id} [get] +func (h *Handler) GetUser(c echo.Context) error { + err := CheckPermission(c, []string{"user.read"}) + if err != nil { + return err + } + + id, err := uuid.Parse(c.Param("id")) + if err != nil { + return echo.NewHTTPError(400, "Invalid id") + } + dbuser, err := h.db.GetUser(context.Background(), id) + if err != nil { + return err + } + + user := MapDbUser(&dbuser[0].User) + for _, oidc := range dbuser { + user.Oidc[oidc.OidcHandle.Provider] = MapOidc(&oidc.OidcHandle) + } + + return c.JSON(200, user) +} + // @Summary Get me // @Description Get informations about the currently connected user // @Tags users @@ -136,22 +172,6 @@ func (h *Handler) GetMe(c echo.Context) error { return c.JSON(200, user) } -func GetCurrentUserId(c echo.Context) (uuid.UUID, error) { - user := c.Get("user").(*jwt.Token) - if user == nil { - return uuid.UUID{}, echo.NewHTTPError(401, "Unauthorized") - } - sub, err := user.Claims.GetSubject() - if err != nil { - return uuid.UUID{}, echo.NewHTTPError(403, "Could not retrive subject") - } - ret, err := uuid.Parse(sub) - if err != nil { - return uuid.UUID{}, echo.NewHTTPError(403, "Invalid id") - } - return ret, nil -} - // @Summary Register // @Description Register as a new user and open a session for it // @Tags users diff --git a/auth/utils.go b/auth/utils.go new file mode 100644 index 00000000..c9e0fa0a --- /dev/null +++ b/auth/utils.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "slices" + "strings" + + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/labstack/echo/v4" +) + +func GetCurrentUserId(c echo.Context) (uuid.UUID, error) { + user := c.Get("user").(*jwt.Token) + if user == nil { + return uuid.UUID{}, echo.NewHTTPError(401, "Unauthorized") + } + sub, err := user.Claims.GetSubject() + if err != nil { + return uuid.UUID{}, echo.NewHTTPError(403, "Could not retrive subject") + } + ret, err := uuid.Parse(sub) + if err != nil { + return uuid.UUID{}, echo.NewHTTPError(403, "Invalid id") + } + return ret, nil +} + +func CheckPremissions(c echo.Context, perms []string) error { + token, ok := c.Get("user").(*jwt.Token) + if !ok { + return echo.NewHTTPError(401, "Not logged in") + } + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return echo.NewHTTPError(403, "Could not retrieve claims") + } + + permissions_claims, ok := claims["permissions"] + if !ok { + return echo.NewHTTPError(403, fmt.Sprintf("Missing permissions: %s.", ", ")) + } + permissions, ok := permissions_claims.([]string) + if !ok { + return echo.NewHTTPError(403, "Invalid permission claim.") + } + + missing := make([]string, 0) + for _, perm := range perms { + if !slices.Contains(permissions, perm) { + missing = append(missing, perm) + } + } + + if len(missing) != 0 { + return echo.NewHTTPError( + 403, + fmt.Sprintf("Missing permissions: %s.", strings.Join(missing, ", ")), + ) + } + return nil +} From 9b2f6eadc77fb9f26f3212438acfbe53db75e633 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 4 Sep 2024 16:27:49 +0200 Subject: [PATCH 24/36] Add delete user routes --- auth/users.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- auth/utils.go | 2 +- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/auth/users.go b/auth/users.go index 08e2f3eb..88b7c39f 100644 --- a/auth/users.go +++ b/auth/users.go @@ -78,7 +78,7 @@ func MapOidc(oidc *dbc.OidcHandle) OidcHandle { // @Failure 400 {object} problem.Problem "Invalid after id" // @Router /users [get] func (h *Handler) ListUsers(c echo.Context) error { - err := CheckPermission(c, []string{"user.read"}) + err := CheckPermissions(c, []string{"user.read"}) if err != nil { return err } @@ -123,7 +123,7 @@ func (h *Handler) ListUsers(c echo.Context) error { // @Failure 404 {object} problem.Problem "No user with the given id found" // @Router /users/{id} [get] func (h *Handler) GetUser(c echo.Context) error { - err := CheckPermission(c, []string{"user.read"}) + err := CheckPermissions(c, []string{"user.read"}) if err != nil { return err } @@ -177,7 +177,7 @@ func (h *Handler) GetMe(c echo.Context) error { // @Tags users // @Accept json // @Produce json -// @Param device query uuid false "The device the created session will be used on" +// @Param device query string false "The device the created session will be used on" // @Param user body RegisterDto false "Registration informations" // @Success 201 {object} dbc.Session // @Failure 400 {object} problem.Problem "Invalid register body" @@ -210,3 +210,48 @@ func (h *Handler) Register(c echo.Context) error { user := MapDbUser(&duser) return h.createSession(c, &user) } + +// @Summary Delete user +// @Description Delete an account and all it's sessions. +// @Tags users +// @Accept json +// @Produce json +// @Security Jwt[users.delete] +// @Param id path string false "User id of the user to delete" Format(uuid) +// @Success 200 {object} User +// @Failure 404 {object} problem.Problem "Invalid id format" +// @Failure 404 {object} problem.Problem "Invalid user id" +// @Router /users/{id} [delete] +func (h *Handler) DeleteUser(c echo.Context) error { + uid, err := uuid.Parse(c.Param("id")) + if err != nil { + return echo.NewHTTPError(400, "Invalid id given: not an uuid") + } + + ret, err := h.db.DeleteUser(context.Background(), uid) + if err != nil { + return echo.NewHTTPError(404, "No user found with given id") + } + return c.JSON(200, MapDbUser(&ret)) +} + +// @Summary Delete self +// @Description Delete your account and all your sessions +// @Tags users +// @Accept json +// @Produce json +// @Security Jwt +// @Success 200 {object} User +// @Router /users/me [delete] +func (h *Handler) DeleteSelf(c echo.Context) error { + uid, err := GetCurrentUserId(c) + if err != nil { + return err + } + + ret, err := h.db.DeleteUser(context.Background(), uid) + if err != nil { + return echo.NewHTTPError(403, "Invalid token, user already deleted.") + } + return c.JSON(200, MapDbUser(&ret)) +} diff --git a/auth/utils.go b/auth/utils.go index c9e0fa0a..c5040341 100644 --- a/auth/utils.go +++ b/auth/utils.go @@ -26,7 +26,7 @@ func GetCurrentUserId(c echo.Context) (uuid.UUID, error) { return ret, nil } -func CheckPremissions(c echo.Context, perms []string) error { +func CheckPermissions(c echo.Context, perms []string) error { token, ok := c.Get("user").(*jwt.Token) if !ok { return echo.NewHTTPError(401, "Not logged in") From 10d8c121b3e0b75d9a32484cfd54a67044186eef Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 4 Sep 2024 16:36:07 +0200 Subject: [PATCH 25/36] Add /info to retrieve public key --- auth/jwt.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ auth/main.go | 3 ++ auth/sessions.go | 56 ----------------------------- 3 files changed, 95 insertions(+), 56 deletions(-) create mode 100644 auth/jwt.go diff --git a/auth/jwt.go b/auth/jwt.go new file mode 100644 index 00000000..91aacec4 --- /dev/null +++ b/auth/jwt.go @@ -0,0 +1,92 @@ +package main + +import ( + "context" + "crypto/x509" + "encoding/pem" + "maps" + "net/http" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/labstack/echo/v4" +) + +type Jwt struct { + // The jwt token you can use for all authorized call to either keibi or other services. + Token string `json:"token"` +} + +type Info struct { + // The public key used to sign jwt tokens. It can be used by your services to check if the jwt is valid. + PublicKey string `json:"publicKey"` +} + +// @Summary Get JWT +// @Description Convert a session token to a short lived JWT. +// @Tags jwt +// @Produce json +// @Security Token +// @Success 200 {object} Jwt +// @Failure 401 {object} problem.Problem "Missing session token" +// @Failure 403 {object} problem.Problem "Invalid session token (or expired)" +// @Router /jwt [get] +func (h *Handler) CreateJwt(c echo.Context) error { + auth := c.Request().Header.Get("Authorization") + if !strings.HasPrefix(auth, "Bearer ") { + return echo.NewHTTPError(http.StatusUnauthorized, "Missing session token") + } + token := auth[len("Bearer "):] + + session, err := h.db.GetUserFromToken(context.Background(), token) + if err != nil { + return echo.NewHTTPError(http.StatusForbidden, "Invalid token") + } + if session.LastUsed.Add(h.config.ExpirationDelay).Compare(time.Now().UTC()) < 0 { + return echo.NewHTTPError(http.StatusForbidden, "Token has expired") + } + + go func() { + h.db.TouchSession(context.Background(), session.Id) + h.db.TouchUser(context.Background(), session.User.Id) + }() + + claims := maps.Clone(session.User.Claims) + claims["sub"] = session.User.Id.String() + claims["sid"] = session.Id.String() + claims["iss"] = h.config.Issuer + claims["exp"] = &jwt.NumericDate{ + Time: time.Now().UTC().Add(time.Hour), + } + claims["iss"] = &jwt.NumericDate{ + Time: time.Now().UTC(), + } + jwt := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + t, err := jwt.SignedString(h.config.JwtPrivateKey) + if err != nil { + return err + } + return c.JSON(http.StatusOK, Jwt{ + Token: t, + }) +} + +// @Summary Info +// @Description Get info like the public key used to sign the jwts. +// @Tags jwt +// @Produce json +// @Success 200 {object} Info +// @Router /info [get] +func (h *Handler) GetInfo(c echo.Context) error { + key := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: x509.MarshalPKCS1PublicKey(h.config.JwtPublicKey), + }, + ) + + return c.JSON(200, Info{ + PublicKey: string(key), + }) +} diff --git a/auth/main.go b/auth/main.go index 16943aed..e4713d74 100644 --- a/auth/main.go +++ b/auth/main.go @@ -165,6 +165,8 @@ func main() { r.GET("/users", h.ListUsers) r.GET("/users/:id", h.GetUser) r.GET("/users/me", h.GetMe) + r.DELETE("/users/:id", h.DeleteUser) + r.DELETE("/users/me", h.DeleteSelf) e.POST("/users", h.Register) e.POST("/sessions", h.Login) @@ -172,6 +174,7 @@ func main() { r.DELETE("/sessions/:id", h.Logout) e.GET("/jwt", h.CreateJwt) + e.GET("/info", h.GetInfo) e.GET("/swagger/*", echoSwagger.WrapHandler) diff --git a/auth/sessions.go b/auth/sessions.go index 5df466a0..5f61bb5d 100644 --- a/auth/sessions.go +++ b/auth/sessions.go @@ -5,9 +5,7 @@ import ( "context" "crypto/rand" "encoding/base64" - "maps" "net/http" - "strings" "time" "github.com/alexedwards/argon2id" @@ -113,60 +111,6 @@ func (h *Handler) createSession(c echo.Context, user *User) error { return c.JSON(201, session) } -type Jwt struct { - // The jwt token you can use for all authorized call to either keibi or other services. - Token string `json:"token"` -} - -// @Summary Get JWT -// @Description Convert a session token to a short lived JWT. -// @Tags sessions -// @Produce json -// @Security Token -// @Success 200 {object} Jwt -// @Failure 401 {object} problem.Problem "Missing session token" -// @Failure 403 {object} problem.Problem "Invalid session token (or expired)" -// @Router /jwt [get] -func (h *Handler) CreateJwt(c echo.Context) error { - auth := c.Request().Header.Get("Authorization") - if !strings.HasPrefix(auth, "Bearer ") { - return echo.NewHTTPError(http.StatusUnauthorized, "Missing session token") - } - token := auth[len("Bearer "):] - - session, err := h.db.GetUserFromToken(context.Background(), token) - if err != nil { - return echo.NewHTTPError(http.StatusForbidden, "Invalid token") - } - if session.LastUsed.Add(h.config.ExpirationDelay).Compare(time.Now().UTC()) < 0 { - return echo.NewHTTPError(http.StatusForbidden, "Token has expired") - } - - go func() { - h.db.TouchSession(context.Background(), session.Id) - h.db.TouchUser(context.Background(), session.User.Id) - }() - - claims := maps.Clone(session.User.Claims) - claims["sub"] = session.User.Id.String() - claims["sid"] = session.Id.String() - claims["iss"] = h.config.Issuer - claims["exp"] = &jwt.NumericDate{ - Time: time.Now().UTC().Add(time.Hour), - } - claims["iss"] = &jwt.NumericDate{ - Time: time.Now().UTC(), - } - jwt := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - t, err := jwt.SignedString(h.config.JwtPrivateKey) - if err != nil { - return err - } - return c.JSON(http.StatusOK, Jwt{ - Token: t, - }) -} - // @Summary Logout // @Description Delete a session and logout // @Tags sessions From 8b1cf612097afa182dec580b1ed9cda5c3007824 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 5 Sep 2024 17:59:00 +0200 Subject: [PATCH 26/36] Add robot tests for auth system --- auth/robot/auth.resource | 43 ++++++++++++++++++++++++++++++ auth/robot/sessions.robot | 29 ++++++++++++++++++++ auth/robot/users.robot | 33 +++++++++++++++++++++++ back/tests/robot/auth/auth.robot | 1 - pyproject.toml | 45 ++++++++++++++++++++++++++++++++ shell.nix | 1 + 6 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 auth/robot/auth.resource create mode 100644 auth/robot/sessions.robot create mode 100644 auth/robot/users.robot create mode 100644 pyproject.toml diff --git a/auth/robot/auth.resource b/auth/robot/auth.resource new file mode 100644 index 00000000..0c36d6f2 --- /dev/null +++ b/auth/robot/auth.resource @@ -0,0 +1,43 @@ +*** Settings *** +Documentation Common things to handle rest requests + +Library REST http://localhost:8901/api + + +*** Keywords *** +Login + [Documentation] Shortcut to login with the given username for future requests + [Arguments] ${username} + &{res}= POST /sessions {"username": "${username}", "password": "password-${username}"} + Output + Integer response status 201 + String response body access_token + ConvertToJwt ${res.body.token} + +Register + [Documentation] Shortcut to register with the given username for future requests + [Arguments] ${username} + &{res}= POST + ... /users + ... {"username": "${username}", "password": "password-${username}", "email": "${username}@zoriya.dev"} + Output + Integer response status 201 + String response body token + ConvertToJwt ${res.body.token} + +ConvertToJwt + [Documentation] Convert a session token to a jwt and set it in the header + [Arguments] ${token} + Set Headers {"Authorization": "Bearer ${token}"} + ${res}= GET /jwt + Output + Integer response status 200 + String response body token + Set Headers {"Authorization": "Bearer ${res.token}"} + +Logout + [Documentation] Logout the current user, only the local client is affected. + ${res}= DELETE /sessions/current + Output + Integer response status 200 + Set Headers {"Authorization": ""} diff --git a/auth/robot/sessions.robot b/auth/robot/sessions.robot new file mode 100644 index 00000000..b695f39f --- /dev/null +++ b/auth/robot/sessions.robot @@ -0,0 +1,29 @@ +*** Settings *** +Documentation Tests of the /sessions route. + +Resource ../auth.resource + + +*** Test Cases *** +Bad Account + [Documentation] Login fails if user does not exist + POST /sessions {"login": "i-don-t-exist", "password": "pass"} + Output + Integer response status 403 + +Login + [Documentation] Create a new user and login in it + Register login-user + ${res}= GET /users/me + Output + Integer response status 200 + String response body username login-user + + Logout + Login login-user + ${me}= Get /users/me + Output + Output ${me} + Should Be Equal As Strings ${res["body"]} ${me["body"]} + + [Teardown] DELETE /auth/me diff --git a/auth/robot/users.robot b/auth/robot/users.robot new file mode 100644 index 00000000..3d51bd93 --- /dev/null +++ b/auth/robot/users.robot @@ -0,0 +1,33 @@ +*** Settings *** +Documentation Tests of the /users route. +... Ensures that the user can authenticate on kyoo. + +Resource ../auth.resource + + +*** Test Cases *** +Me cant be accessed without an account + Get /users/me + Output + Integer response status 401 + +Register + [Documentation] Create a new user and login in it + Register user-1 + [Teardown] DELETE /users/me + +Register Duplicates + [Documentation] If two users tries to register with the same username, it fails + Register user-duplicate + # We can't use the `Register` keyword because it assert for success + POST /auth/register {"username": "user-duplicate", "password": "pass", "email": "mail@zoriya.dev"} + Output + Integer response status 409 + [Teardown] DELETE /users/me + +Delete Account + [Documentation] Check if a user can delete it's account + Register I-should-be-deleted + DELETE /users/me + Output + Integer response status 200 diff --git a/back/tests/robot/auth/auth.robot b/back/tests/robot/auth/auth.robot index 6b361ddd..02b7617b 100644 --- a/back/tests/robot/auth/auth.robot +++ b/back/tests/robot/auth/auth.robot @@ -80,4 +80,3 @@ Login Should Be Equal As Strings ${res["body"]} ${me["body"]} [Teardown] DELETE /auth/me - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..4ba4057b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[tool.robotidy] +diff = false +overwrite = true +verbose = false +separator = "space" +spacecount = 2 +line_length = 120 +lineseparator = "native" +skip_gitignore = true +ignore_git_dir = true +configure = [ + "AddMissingEnd:enabled=True", + "NormalizeSeparators:enabled=True", + "DiscardEmptySections:enabled=True", + "MergeAndOrderSections:enabled=True", + "RemoveEmptySettings:enabled=True", + "ReplaceEmptyValues:enabled=True", + "ReplaceWithVAR:enabled=False", + "NormalizeAssignments:enabled=True", + "GenerateDocumentation:enabled=False", + "OrderSettings:enabled=True", + "OrderSettingsSection:enabled=True", + "NormalizeTags:enabled=True", + "OrderTags:enabled=False", + "RenameVariables:enabled=False", + "IndentNestedKeywords:enabled=False", + "AlignSettingsSection:enabled=True", + "AlignVariablesSection:enabled=True", + "AlignTemplatedTestCases:enabled=False", + "AlignTestCasesSection:enabled=False", + "AlignKeywordsSection:enabled=False", + "NormalizeNewLines:enabled=True", + "NormalizeSectionHeaderName:enabled=True", + "NormalizeSettingName:enabled=True", + "ReplaceRunKeywordIf:enabled=True", + "SplitTooLongLine:enabled=True", + "SmartSortKeywords:enabled=False", + "RenameTestCases:enabled=False", + "RenameKeywords:enabled=False", + "ReplaceReturns:enabled=True", + "ReplaceBreakContinue:enabled=True", + "InlineIf:enabled=True", + "Translate:enabled=False", + "NormalizeComments:enabled=True", +] diff --git a/shell.nix b/shell.nix index fa182ed2..d665739e 100644 --- a/shell.nix +++ b/shell.nix @@ -40,6 +40,7 @@ in go-migrate sqlc go-swag + robotframework-tidy ]; DOTNET_ROOT = "${dotnet}"; From 662457d0f185351f35522e3516a25af55a1b9db2 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 11 Sep 2024 12:05:06 +0200 Subject: [PATCH 27/36] Robot tests wip --- .github/workflows/robot.yml | 21 +++++++++++-------- auth/Dockerfile | 3 ++- auth/main.go | 5 +++-- auth/robot/auth.resource | 2 +- auth/robot/sessions.robot | 2 +- auth/robot/users.robot | 2 +- docker-compose.build.yml | 17 +++++++++++++++ .../requirements.txt => requirements.txt | 0 shell.nix | 3 +++ 9 files changed, 40 insertions(+), 15 deletions(-) rename back/tests/robot/requirements.txt => requirements.txt (100%) diff --git a/.github/workflows/robot.yml b/.github/workflows/robot.yml index 044d5e3a..02b485e5 100644 --- a/.github/workflows/robot.yml +++ b/.github/workflows/robot.yml @@ -14,11 +14,14 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Pull images - run: | - cp .env.example .env - docker compose version - docker compose pull + - name: Robot cache + uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + + - name: Install robot + run: pip install -r requirements.txt - name: Docker cache uses: satackey/action-docker-layer-caching@v0.0.11 @@ -26,18 +29,18 @@ jobs: - name: Start the service run: | - docker compose up -d back postgres traefik meilisearch --wait + cp .env.example .env + docker compose -f docker-compose.build.yml up -d auth postgres traefik --wait - name: Perform healthchecks run: | docker compose ps -a docker compose logs - wget --retry-connrefused --retry-on-http-error=502 http://localhost:8901/api/health || (docker compose logs && exit 1) + # wget --retry-connrefused --retry-on-http-error=502 http://localhost:8901/api/health || (docker compose logs && exit 1) - name: Run robot tests run: | - pip install -r back/tests/robot/requirements.txt - robot -d out back/tests/robot/ + robot -d out $(find -type d -name robot) - name: Show logs if: failure() diff --git a/auth/Dockerfile b/auth/Dockerfile index 2142fb98..5c1a1b93 100644 --- a/auth/Dockerfile +++ b/auth/Dockerfile @@ -7,7 +7,8 @@ RUN go install github.com/swaggo/swag/cmd/swag@latest COPY go.mod go.sum ./ RUN go mod download -COPY sqlc.yaml sql ./ +COPY sqlc.yaml ./ +COPY sql ./sql RUN sqlc generate COPY . . diff --git a/auth/main.go b/auth/main.go index e4713d74..abb0f712 100644 --- a/auth/main.go +++ b/auth/main.go @@ -79,6 +79,7 @@ func OpenDatabase() (*pgxpool.Pool, error) { config.ConnConfig.RuntimeParams["search_path"] = schema } + fmt.Printf("Connecting to database with %v\n", config) db, err := pgxpool.NewWithConfig(ctx, config) if err != nil { fmt.Printf("Could not connect to database, check your env variables!") @@ -143,7 +144,7 @@ func main() { db, err := OpenDatabase() if err != nil { - e.Logger.Fatal("Could not open databse: %v", err) + e.Logger.Fatal("Could not open databse: ", err) return } @@ -152,7 +153,7 @@ func main() { } conf, err := LoadConfiguration(h.db) if err != nil { - e.Logger.Fatal("Could not load configuration: %v", err) + e.Logger.Fatal("Could not load configuration: ", err) return } h.config = conf diff --git a/auth/robot/auth.resource b/auth/robot/auth.resource index 0c36d6f2..32f284a0 100644 --- a/auth/robot/auth.resource +++ b/auth/robot/auth.resource @@ -1,7 +1,7 @@ *** Settings *** Documentation Common things to handle rest requests -Library REST http://localhost:8901/api +Library REST http://localhost:8901/auth *** Keywords *** diff --git a/auth/robot/sessions.robot b/auth/robot/sessions.robot index b695f39f..d5b880d1 100644 --- a/auth/robot/sessions.robot +++ b/auth/robot/sessions.robot @@ -1,7 +1,7 @@ *** Settings *** Documentation Tests of the /sessions route. -Resource ../auth.resource +Resource ./auth.resource *** Test Cases *** diff --git a/auth/robot/users.robot b/auth/robot/users.robot index 3d51bd93..96d8b3f9 100644 --- a/auth/robot/users.robot +++ b/auth/robot/users.robot @@ -2,7 +2,7 @@ Documentation Tests of the /users route. ... Ensures that the user can authenticate on kyoo. -Resource ../auth.resource +Resource ./auth.resource *** Test Cases *** diff --git a/docker-compose.build.yml b/docker-compose.build.yml index a883166c..a5114db5 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -60,6 +60,23 @@ services: - "traefik.enable=true" - "traefik.http.routers.front.rule=PathPrefix(`/`)" + auth: + build: ./auth + restart: on-failure + depends_on: + postgres: + condition: service_healthy + env_file: + - ./.env + labels: + - "traefik.enable=true" + - "traefik.http.routers.auth.rule=PathPrefix(`/auth/`)" + - "traefik.http.routers.auth.middlewares=auth-sp" + - "traefik.http.middlewares.auth-sp.stripprefix.prefixes=/auth" + - "traefik.http.middlewares.auth-sp.stripprefix.forceSlash=false" + profiles: + - "v5" + scanner: build: ./scanner restart: on-failure diff --git a/back/tests/robot/requirements.txt b/requirements.txt similarity index 100% rename from back/tests/robot/requirements.txt rename to requirements.txt diff --git a/shell.nix b/shell.nix index d665739e..3d413798 100644 --- a/shell.nix +++ b/shell.nix @@ -11,6 +11,9 @@ dataclasses-json msgspec langcodes + + robotframework + # restinstance needs to be packaged ]); dotnet = with pkgs.dotnetCorePackages; combinePackages [ From 2b493e6d163f94d3201dc461cbef9429a6e2dd96 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 18:05:20 +0200 Subject: [PATCH 28/36] Add default config values --- auth/config.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/auth/config.go b/auth/config.go index 1a509030..927a4827 100644 --- a/auth/config.go +++ b/auth/config.go @@ -20,6 +20,12 @@ type Configuration struct { ExpirationDelay time.Duration } +var DefaultConfig = Configuration{ + Issuer: "kyoo", + DefaultClaims: make(jwt.MapClaims), + ExpirationDelay: 30 * 24 * time.Hour, +} + const ( JwtPrivateKey = "jwt_private_key" ) @@ -31,7 +37,7 @@ func LoadConfiguration(db *dbc.Queries) (*Configuration, error) { return nil, err } - ret := Configuration{} + ret := DefaultConfig for _, conf := range confs { switch conf.Key { From 200087f2f65d25e43e089e5ee9645c45e1deb6fd Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 18:05:36 +0200 Subject: [PATCH 29/36] Fix jwt signing verification --- auth/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/main.go b/auth/main.go index abb0f712..d0a1369b 100644 --- a/auth/main.go +++ b/auth/main.go @@ -79,7 +79,6 @@ func OpenDatabase() (*pgxpool.Pool, error) { config.ConnConfig.RuntimeParams["search_path"] = schema } - fmt.Printf("Connecting to database with %v\n", config) db, err := pgxpool.NewWithConfig(ctx, config) if err != nil { fmt.Printf("Could not connect to database, check your env variables!") @@ -160,6 +159,7 @@ func main() { r := e.Group("") r.Use(echojwt.WithConfig(echojwt.Config{ + SigningMethod: "RS256", SigningKey: h.config.JwtPublicKey, })) From a2df0ae305a5e26a14ce2ec1aa8f7d371b9c07cd Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 18:06:31 +0200 Subject: [PATCH 30/36] Move pk to int autogen and uuid as handle --- auth/sql/migrations/000001_users.up.sql | 7 ++++--- auth/sql/migrations/000003_sessions.up.sql | 5 +++-- auth/sql/queries/sessions.sql | 20 +++++++++++--------- auth/sql/queries/users.sql | 7 +++++-- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/auth/sql/migrations/000001_users.up.sql b/auth/sql/migrations/000001_users.up.sql index 25358905..7076151d 100644 --- a/auth/sql/migrations/000001_users.up.sql +++ b/auth/sql/migrations/000001_users.up.sql @@ -1,7 +1,8 @@ begin; create table users( - id uuid not null primary key, + pk serial primary key, + id uuid not null default gen_random_uuid(), username varchar(256) not null unique, email varchar(320) not null unique, password text, @@ -12,7 +13,7 @@ create table users( ); create table oidc_handle( - user_id uuid not null references users(id) on delete cascade, + user_pk integer not null references users(pk) on delete cascade, provider varchar(256) not null, id text not null, @@ -23,7 +24,7 @@ create table oidc_handle( refresh_token text, expire_at timestamptz, - constraint oidc_handle_pk primary key (user_id, provider) + constraint oidc_handle_pk primary key (user_pk, provider) ); commit; diff --git a/auth/sql/migrations/000003_sessions.up.sql b/auth/sql/migrations/000003_sessions.up.sql index f9383ed9..61e11291 100644 --- a/auth/sql/migrations/000003_sessions.up.sql +++ b/auth/sql/migrations/000003_sessions.up.sql @@ -1,9 +1,10 @@ begin; create table sessions( - id uuid not null primary key, + pk serial primary key, + id uuid not null default gen_random_uuid(), token varchar(128) not null unique, - user_id uuid not null references users(id) on delete cascade, + user_pk integer not null references users(pk) on delete cascade, created_date timestamptz not null default now()::timestamptz, last_used timestamptz not null default now()::timestamptz, device varchar(1024) diff --git a/auth/sql/queries/sessions.sql b/auth/sql/queries/sessions.sql index a8e9a285..82127d80 100644 --- a/auth/sql/queries/sessions.sql +++ b/auth/sql/queries/sessions.sql @@ -5,7 +5,7 @@ select sqlc.embed(u) from users as u - inner join sessions as s on u.id = s.user_id + inner join sessions as s on u.pk = s.user_pk where s.token = $1 limit 1; @@ -20,24 +20,26 @@ where -- name: GetUserSessions :many select - * + s.* from - sessions + sessions as s + inner join users as u on u.pk = s.user_pk where - user_id = $1 + u.pk = $1 order by last_used; -- name: CreateSession :one -insert into sessions(token, user_id, device) +insert into sessions(token, user_pk, device) values ($1, $2, $3) returning *; -- name: DeleteSession :one -delete from sessions -where id = $1 - and user_id = $2 +delete from sessions as s using users as u +where s.user_pk = u.pk + and s.id = $1 + and u.id = sqlc.arg(user_id) returning - *; + s.*; diff --git a/auth/sql/queries/users.sql b/auth/sql/queries/users.sql index b08f1787..c57315ec 100644 --- a/auth/sql/queries/users.sql +++ b/auth/sql/queries/users.sql @@ -21,10 +21,13 @@ limit $1; -- name: GetUser :many select sqlc.embed(u), - sqlc.embed(h) + h.provider, + h.id, + h.username, + h.profile_url from users as u - left join oidc_handle as h on u.id = h.user_id + left join oidc_handle as h on u.pk = h.user_pk where u.id = $1; From 79b685ea8ab157afccc482047eb3da21a0698f99 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 18:07:07 +0200 Subject: [PATCH 31/36] Add proper error handling and fix del /sessions/current --- auth/go.mod | 2 +- auth/go.sum | 2 ++ auth/sessions.go | 9 ++++++--- auth/users.go | 31 +++++++++++++++++++++++-------- auth/utils.go | 11 +++++++++++ 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/auth/go.mod b/auth/go.mod index a926db35..8cd5a8d3 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -30,7 +30,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.17.1 github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect diff --git a/auth/go.sum b/auth/go.sum index 6233c759..6d6184f1 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -65,6 +65,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= diff --git a/auth/sessions.go b/auth/sessions.go index 5f61bb5d..1092f5fa 100644 --- a/auth/sessions.go +++ b/auth/sessions.go @@ -11,6 +11,7 @@ import ( "github.com/alexedwards/argon2id" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" "github.com/zoriya/kyoo/keibi/dbc" ) @@ -102,7 +103,7 @@ func (h *Handler) createSession(c echo.Context, user *User) error { session, err := h.db.CreateSession(ctx, dbc.CreateSessionParams{ Token: base64.StdEncoding.EncodeToString(id), - UserId: user.Id, + UserPk: user.Pk, Device: device, }) if err != nil { @@ -131,7 +132,7 @@ func (h *Handler) Logout(c echo.Context) error { } session := c.Param("id") - if session == "" { + if session == "current" { sid, ok := c.Get("user").(*jwt.Token).Claims.(jwt.MapClaims)["sid"] if !ok { return echo.NewHTTPError(400, "Missing session id") @@ -147,8 +148,10 @@ func (h *Handler) Logout(c echo.Context) error { Id: sid, UserId: uid, }) - if err != nil { + if err == pgx.ErrNoRows { return echo.NewHTTPError(404, "Session not found with specified id") + } else if err != nil { + return err } return c.JSON(200, MapSession(&ret)) } diff --git a/auth/users.go b/auth/users.go index 88b7c39f..31ee799c 100644 --- a/auth/users.go +++ b/auth/users.go @@ -8,11 +8,15 @@ import ( "github.com/alexedwards/argon2id" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + "github.com/jackc/pgerrcode" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" "github.com/zoriya/kyoo/keibi/dbc" ) type User struct { + // Primary key in database + Pk int32 `json:"-"` // Id of the user. Id uuid.UUID `json:"id"` // Username of the user. Can be used as a login. @@ -49,6 +53,7 @@ type RegisterDto struct { func MapDbUser(user *dbc.User) User { return User{ + Pk: user.Pk, Id: user.Id, Username: user.Username, Email: user.Email, @@ -59,10 +64,10 @@ func MapDbUser(user *dbc.User) User { } } -func MapOidc(oidc *dbc.OidcHandle) OidcHandle { +func MapOidc(oidc *dbc.GetUserRow) OidcHandle { return OidcHandle{ - Id: oidc.Id, - Username: oidc.Username, + Id: *oidc.Id, + Username: *oidc.Username, ProfileUrl: oidc.ProfileUrl, } } @@ -139,7 +144,9 @@ func (h *Handler) GetUser(c echo.Context) error { user := MapDbUser(&dbuser[0].User) for _, oidc := range dbuser { - user.Oidc[oidc.OidcHandle.Provider] = MapOidc(&oidc.OidcHandle) + if oidc.Provider != nil { + user.Oidc[*oidc.Provider] = MapOidc(&oidc) + } } return c.JSON(200, user) @@ -166,7 +173,9 @@ func (h *Handler) GetMe(c echo.Context) error { user := MapDbUser(&dbuser[0].User) for _, oidc := range dbuser { - user.Oidc[oidc.OidcHandle.Provider] = MapOidc(&oidc.OidcHandle) + if oidc.Provider != nil { + user.Oidc[*oidc.Provider] = MapOidc(&oidc) + } } return c.JSON(200, user) @@ -204,8 +213,10 @@ func (h *Handler) Register(c echo.Context) error { Password: &pass, Claims: h.config.DefaultClaims, }) - if err != nil { + if ErrIs(err, pgerrcode.UniqueViolation) { return echo.NewHTTPError(409, "Email or username already taken") + } else if err != nil { + return err } user := MapDbUser(&duser) return h.createSession(c, &user) @@ -229,8 +240,10 @@ func (h *Handler) DeleteUser(c echo.Context) error { } ret, err := h.db.DeleteUser(context.Background(), uid) - if err != nil { + if err == pgx.ErrNoRows { return echo.NewHTTPError(404, "No user found with given id") + } else if err != nil { + return err } return c.JSON(200, MapDbUser(&ret)) } @@ -250,8 +263,10 @@ func (h *Handler) DeleteSelf(c echo.Context) error { } ret, err := h.db.DeleteUser(context.Background(), uid) - if err != nil { + if err == pgx.ErrNoRows { return echo.NewHTTPError(403, "Invalid token, user already deleted.") + } else if err != nil { + return err } return c.JSON(200, MapDbUser(&ret)) } diff --git a/auth/utils.go b/auth/utils.go index c5040341..38753bc3 100644 --- a/auth/utils.go +++ b/auth/utils.go @@ -1,12 +1,14 @@ package main import ( + "errors" "fmt" "slices" "strings" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgconn" "github.com/labstack/echo/v4" ) @@ -60,3 +62,12 @@ func CheckPermissions(c echo.Context, perms []string) error { } return nil } + +func ErrIs(err error, code string) bool { + var pgerr *pgconn.PgError + + if !errors.As(err, &pgerr) { + return false + } + return pgerr.Code == code +} From 49fc88667a2e929cee341a0a43597456f3ac276b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 18:07:20 +0200 Subject: [PATCH 32/36] Fix robot tests --- auth/robot/auth.resource | 8 ++++---- auth/robot/sessions.robot | 11 +++++++++-- auth/robot/users.robot | 2 +- shell.nix | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/auth/robot/auth.resource b/auth/robot/auth.resource index 32f284a0..51298261 100644 --- a/auth/robot/auth.resource +++ b/auth/robot/auth.resource @@ -8,10 +8,10 @@ Library REST http://localhost:8901/auth Login [Documentation] Shortcut to login with the given username for future requests [Arguments] ${username} - &{res}= POST /sessions {"username": "${username}", "password": "password-${username}"} + &{res}= POST /sessions {"login": "${username}", "password": "password-${username}"} Output Integer response status 201 - String response body access_token + String response body token ConvertToJwt ${res.body.token} Register @@ -29,11 +29,11 @@ ConvertToJwt [Documentation] Convert a session token to a jwt and set it in the header [Arguments] ${token} Set Headers {"Authorization": "Bearer ${token}"} - ${res}= GET /jwt + &{res}= GET /jwt Output Integer response status 200 String response body token - Set Headers {"Authorization": "Bearer ${res.token}"} + Set Headers {"Authorization": "Bearer ${res.body.token}"} Logout [Documentation] Logout the current user, only the local client is affected. diff --git a/auth/robot/sessions.robot b/auth/robot/sessions.robot index d5b880d1..6f6b7f1b 100644 --- a/auth/robot/sessions.robot +++ b/auth/robot/sessions.robot @@ -9,7 +9,15 @@ Bad Account [Documentation] Login fails if user does not exist POST /sessions {"login": "i-don-t-exist", "password": "pass"} Output + Integer response status 404 + +Invalid password + [Documentation] Login fails if password is invalid + Register invalid-password-user + POST /sessions {"login": "invalid-password-user", "password": "pass"} + Output Integer response status 403 + [Teardown] DELETE /users/me Login [Documentation] Create a new user and login in it @@ -18,7 +26,6 @@ Login Output Integer response status 200 String response body username login-user - Logout Login login-user ${me}= Get /users/me @@ -26,4 +33,4 @@ Login Output ${me} Should Be Equal As Strings ${res["body"]} ${me["body"]} - [Teardown] DELETE /auth/me + [Teardown] DELETE /users/me diff --git a/auth/robot/users.robot b/auth/robot/users.robot index 96d8b3f9..603fd1ec 100644 --- a/auth/robot/users.robot +++ b/auth/robot/users.robot @@ -20,7 +20,7 @@ Register Duplicates [Documentation] If two users tries to register with the same username, it fails Register user-duplicate # We can't use the `Register` keyword because it assert for success - POST /auth/register {"username": "user-duplicate", "password": "pass", "email": "mail@zoriya.dev"} + POST /users {"username": "user-duplicate", "password": "pass", "email": "mail@zoriya.dev"} Output Integer response status 409 [Teardown] DELETE /users/me diff --git a/shell.nix b/shell.nix index 3d413798..51dcdd54 100644 --- a/shell.nix +++ b/shell.nix @@ -12,7 +12,7 @@ msgspec langcodes - robotframework + # robotframework # restinstance needs to be packaged ]); dotnet = with pkgs.dotnetCorePackages; From e706f8974fe2529440b07b6e1f3ae3081581877e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 18:07:48 +0200 Subject: [PATCH 33/36] Fix public url in .env.example --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index f15219f1..f81f9357 100644 --- a/.env.example +++ b/.env.example @@ -47,7 +47,7 @@ TVDB_PIN= # The url you can use to reach your kyoo instance. This is used during oidc to redirect users to your instance. -PUBLIC_URL=http://localhost:5000 +PUBLIC_URL=http://localhost:8901 # Use a builtin oidc service (google, discord, trakt, or simkl): # When you create a client_id, secret combo you may be asked for a redirect url. You need to specify https://YOUR-PUBLIC-URL/api/auth/logged/YOUR-SERVICE-NAME From 0d82a1349f3c933e12a2df56660da9eb6c42e3f8 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 18:33:48 +0200 Subject: [PATCH 34/36] Move old robot tests to make them not run with ci --- back/tests/{robot => }/.gitignore | 0 back/tests/{robot => }/auth/auth.robot | 0 back/tests/{robot => }/pyproject.toml | 0 back/tests/{robot => }/rest.resource | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename back/tests/{robot => }/.gitignore (100%) rename back/tests/{robot => }/auth/auth.robot (100%) rename back/tests/{robot => }/pyproject.toml (100%) rename back/tests/{robot => }/rest.resource (100%) diff --git a/back/tests/robot/.gitignore b/back/tests/.gitignore similarity index 100% rename from back/tests/robot/.gitignore rename to back/tests/.gitignore diff --git a/back/tests/robot/auth/auth.robot b/back/tests/auth/auth.robot similarity index 100% rename from back/tests/robot/auth/auth.robot rename to back/tests/auth/auth.robot diff --git a/back/tests/robot/pyproject.toml b/back/tests/pyproject.toml similarity index 100% rename from back/tests/robot/pyproject.toml rename to back/tests/pyproject.toml diff --git a/back/tests/robot/rest.resource b/back/tests/rest.resource similarity index 100% rename from back/tests/robot/rest.resource rename to back/tests/rest.resource From 719d1609a870931f60873119ffc2f8b4698baa60 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 18:37:34 +0200 Subject: [PATCH 35/36] Run robot tests with v5 profile --- .github/workflows/robot.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/robot.yml b/.github/workflows/robot.yml index 02b485e5..76941d6a 100644 --- a/.github/workflows/robot.yml +++ b/.github/workflows/robot.yml @@ -20,8 +20,7 @@ jobs: python-version: '3.9' cache: 'pip' - - name: Install robot - run: pip install -r requirements.txt + - run: pip install -r requirements.txt - name: Docker cache uses: satackey/action-docker-layer-caching@v0.0.11 @@ -30,7 +29,7 @@ jobs: - name: Start the service run: | cp .env.example .env - docker compose -f docker-compose.build.yml up -d auth postgres traefik --wait + docker compose --profile v5 -f docker-compose.build.yml up -d auth postgres traefik --wait - name: Perform healthchecks run: | From 8fd57da2944e2c2dbf1c92b1710443b2b19c0417 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 19 Oct 2024 19:06:43 +0200 Subject: [PATCH 36/36] Fix keibi dockerfile & ci --- .github/workflows/robot.yml | 2 +- auth/Dockerfile | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/robot.yml b/.github/workflows/robot.yml index 76941d6a..ca630329 100644 --- a/.github/workflows/robot.yml +++ b/.github/workflows/robot.yml @@ -29,7 +29,7 @@ jobs: - name: Start the service run: | cp .env.example .env - docker compose --profile v5 -f docker-compose.build.yml up -d auth postgres traefik --wait + docker compose --profile v5 -f docker-compose.build.yml up -d auth postgres traefik --wait --build - name: Perform healthchecks run: | diff --git a/auth/Dockerfile b/auth/Dockerfile index 5c1a1b93..798b7db4 100644 --- a/auth/Dockerfile +++ b/auth/Dockerfile @@ -16,9 +16,10 @@ RUN swag init --parseDependency RUN CGO_ENABLED=0 GOOS=linux go build -o /keibi FROM gcr.io/distroless/base-debian11 -WORKDIR / +WORKDIR /app EXPOSE 4568 USER nonroot:nonroot -COPY --from=build /keibi /keibi -CMD ["/keibi"] +COPY --from=build /keibi /app/keibi +COPY sql ./sql +CMD ["/app/keibi"]