diff --git a/.github/workflows/auth-hurl.yml b/.github/workflows/auth-hurl.yml index 6ecbed1c..061e3743 100644 --- a/.github/workflows/auth-hurl.yml +++ b/.github/workflows/auth-hurl.yml @@ -52,6 +52,7 @@ jobs: hurl --error-format long --variable host=http://localhost:4568 tests/* env: POSTGRES_SERVER: localhost + FIRST_USER_CLAIMS: '{"permissions": ["users.read"]}' - name: Show logs working-directory: ./auth diff --git a/auth/dbc/users.sql.go b/auth/dbc/users.sql.go index 17e2e903..ecc89fbb 100644 --- a/auth/dbc/users.sql.go +++ b/auth/dbc/users.sql.go @@ -175,10 +175,18 @@ select from users as u left join oidc_handle as h on u.pk = h.user_pk -where - u.id = $1 +where ($1::boolean + and u.id = $2) + or (not $1 + and u.username = $3) ` +type GetUserParams struct { + UseId bool `json:"useId"` + Id uuid.UUID `json:"id"` + Username string `json:"username"` +} + type GetUserRow struct { User User `json:"user"` Provider *string `json:"provider"` @@ -187,8 +195,8 @@ type GetUserRow struct { ProfileUrl *string `json:"profileUrl"` } -func (q *Queries) GetUser(ctx context.Context, id uuid.UUID) ([]GetUserRow, error) { - rows, err := q.db.Query(ctx, getUser, id) +func (q *Queries) GetUser(ctx context.Context, arg GetUserParams) ([]GetUserRow, error) { + rows, err := q.db.Query(ctx, getUser, arg.UseId, arg.Id, arg.Username) if err != nil { return nil, err } diff --git a/auth/sql/queries/users.sql b/auth/sql/queries/users.sql index 1f1711dd..8bc00feb 100644 --- a/auth/sql/queries/users.sql +++ b/auth/sql/queries/users.sql @@ -28,8 +28,10 @@ select from users as u left join oidc_handle as h on u.pk = h.user_pk -where - u.id = $1; +where (@use_id::boolean + and u.id = @id) + or (not @use_id + and u.username = @username); -- name: GetUserByLogin :one select diff --git a/auth/tests/users.hurl b/auth/tests/users.hurl index 8e840a12..0a8da0fa 100644 --- a/auth/tests/users.hurl +++ b/auth/tests/users.hurl @@ -15,6 +15,15 @@ HTTP 200 [Captures] jwt: jsonpath "$.token" +GET {{host}}/users/me +Authorization: Bearer {{jwt}} +HTTP 200 +[Captures] +userId: jsonpath "$.id" +[Asserts] +# this should be defined in the `FIRST_USER_CLAIMS='{"permissions": ["users.read"]}'` env var +jsonpath "$.claims.permissions" contains "users.read" + # Duplicates usernames POST {{host}}/users @@ -35,6 +44,26 @@ POST {{host}}/users } HTTP 409 +# Cannot get non-existing user +GET {{host}}/users/dont-exist +Authorization: Bearer {{jwt}} +HTTP 404 + +# Can get user by id +GET {{host}}/users/{{userId}} +Authorization: Bearer {{jwt}} +HTTP 200 +[Asserts] +jsonpath "$.username" == "user-1" + +# Can get user by username +GET {{host}}/users/user-1 +Authorization: Bearer {{jwt}} +HTTP 200 +[Asserts] +jsonpath "$.id" == {{userId}} +jsonpath "$.username" == "user-1" + DELETE {{host}}/users/me Authorization: Bearer {{jwt}} diff --git a/auth/users.go b/auth/users.go index e0bfdb79..7aef41e4 100644 --- a/auth/users.go +++ b/auth/users.go @@ -94,7 +94,7 @@ func MapOidc(oidc *dbc.GetUserRow) OidcHandle { // @Failure 422 {object} KError "Invalid after id" // @Router /users [get] func (h *Handler) ListUsers(c echo.Context) error { - err := CheckPermissions(c, []string{"user.read"}) + err := CheckPermissions(c, []string{"users.read"}) if err != nil { return err } @@ -139,19 +139,24 @@ func (h *Handler) ListUsers(c echo.Context) error { // @Failure 422 {object} KError "Invalid id (not a uuid)" // @Router /users/{id} [get] func (h *Handler) GetUser(c echo.Context) error { - err := CheckPermissions(c, []string{"user.read"}) + err := CheckPermissions(c, []string{"users.read"}) if err != nil { return err } - id, err := uuid.Parse(c.Param("id")) - if err != nil { - return echo.NewHTTPError(http.StatusUnprocessableEntity, "Invalid id") - } - dbuser, err := h.db.GetUser(context.Background(), id) + id := c.Param("id") + uid, err := uuid.Parse(c.Param("id")) + dbuser, err := h.db.GetUser(context.Background(), dbc.GetUserParams{ + UseId: err == nil, + Id: uid, + Username: id, + }) if err != nil { return err } + if len(dbuser) == 0 { + return echo.NewHTTPError(404, fmt.Sprintf("No user found with id or username: '%s'.", id)) + } user := MapDbUser(&dbuser[0].User) for _, oidc := range dbuser { @@ -177,7 +182,10 @@ func (h *Handler) GetMe(c echo.Context) error { if err != nil { return err } - dbuser, err := h.db.GetUser(context.Background(), id) + dbuser, err := h.db.GetUser(context.Background(), dbc.GetUserParams{ + UseId: true, + Id: id, + }) if err != nil { return err } @@ -246,7 +254,7 @@ func (h *Handler) Register(c echo.Context) error { // @Failure 404 {object} KError "Invalid user id" // @Router /users/{id} [delete] func (h *Handler) DeleteUser(c echo.Context) error { - err := CheckPermissions(c, []string{"user.delete"}) + err := CheckPermissions(c, []string{"users.delete"}) if err != nil { return err } @@ -348,7 +356,7 @@ func (h *Handler) EditSelf(c echo.Context) error { // @Success 422 {object} KError "Invalid body" // @Router /users/{id} [patch] func (h *Handler) EditUser(c echo.Context) error { - err := CheckPermissions(c, []string{"user.write"}) + err := CheckPermissions(c, []string{"users.write"}) if err != nil { return err } diff --git a/auth/utils.go b/auth/utils.go index 70bca2fb..e607cbee 100644 --- a/auth/utils.go +++ b/auth/utils.go @@ -68,11 +68,21 @@ func CheckPermissions(c echo.Context, perms []string) error { if !ok { return echo.NewHTTPError(403, fmt.Sprintf("Missing permissions: %s.", ", ")) } - permissions, ok := permissions_claims.([]string) + fmt.Printf("%v\n", permissions_claims) + fmt.Printf("%t\n", permissions_claims) + permissions_int, ok := permissions_claims.([]any) if !ok { return echo.NewHTTPError(403, "Invalid permission claim.") } + permissions := make([]string, len(permissions_int)) + for i, perm := range permissions_int { + permissions[i], ok = perm.(string) + if !ok { + return echo.NewHTTPError(403, "Invalid permission claim.") + } + } + missing := make([]string, 0) for _, perm := range perms { if !slices.Contains(permissions, perm) {