mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-23 17:52:36 -04:00
Add edit user/settings route
This commit is contained in:
parent
a903d88a66
commit
dbe8e319c8
@ -93,3 +93,10 @@ RABBITMQ_HOST=rabbitmq
|
||||
RABBITMQ_PORT=5672
|
||||
RABBITMQ_DEFAULT_USER=kyoo
|
||||
RABBITMQ_DEFAULT_PASS=aohohunuhouhuhhoahothonseuhaoensuthoaentsuhha
|
||||
|
||||
|
||||
# v5 stuff, does absolutely nothing on master (aka: you can delete this)
|
||||
EXTRA_CLAIMS='{"permissions": [], "verified": false}'
|
||||
FIRST_USER_CLAIMS='{"permissions": ["user.read", "users.write", "users.delete"], "verified": true}'
|
||||
GUEST_CLAIMS='{"permissions": []}'
|
||||
PROTECTED_CLAIMS="permissions,verified"
|
||||
|
5
.github/workflows/auth-hurl.yml
vendored
5
.github/workflows/auth-hurl.yml
vendored
@ -57,8 +57,3 @@ jobs:
|
||||
working-directory: ./auth
|
||||
run: cat logs
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: results
|
||||
path: auth/out
|
||||
|
||||
|
@ -14,6 +14,11 @@ EXTRA_CLAIMS='{}'
|
||||
FIRST_USER_CLAIMS='{}'
|
||||
# If this is not empty, calls to `/jwt` without an `Authorization` header will still create a jwt (with `null` in `sub`)
|
||||
GUEST_CLAIMS=""
|
||||
# Comma separated list of claims that users without the `user.write` permissions should NOT be able to edit
|
||||
# (if you don't specify this an user could make themself administrator for example)
|
||||
# PS: `permissions` is always a protected claim since keibi uses it for user.read/user.write
|
||||
PROTECTED_CLAIMS="permissions"
|
||||
|
||||
# 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:8901
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"encoding/pem"
|
||||
"maps"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
@ -22,12 +23,14 @@ type Configuration struct {
|
||||
DefaultClaims jwt.MapClaims
|
||||
FirstUserClaims jwt.MapClaims
|
||||
GuestClaims jwt.MapClaims
|
||||
ProtectedClaims []string
|
||||
ExpirationDelay time.Duration
|
||||
}
|
||||
|
||||
var DefaultConfig = Configuration{
|
||||
DefaultClaims: make(jwt.MapClaims),
|
||||
FirstUserClaims: make(jwt.MapClaims),
|
||||
ProtectedClaims: []string{"permissions"},
|
||||
ExpirationDelay: 30 * 24 * time.Hour,
|
||||
}
|
||||
|
||||
@ -64,6 +67,9 @@ func LoadConfiguration(db *dbc.Queries) (*Configuration, error) {
|
||||
}
|
||||
}
|
||||
|
||||
protected := strings.Split(os.Getenv("PROTECTED_CLAIMS"), ",")
|
||||
ret.ProtectedClaims = append(ret.ProtectedClaims, protected...)
|
||||
|
||||
rsa_pk_path := os.Getenv("RSA_PRIVATE_KEY_PATH")
|
||||
if rsa_pk_path != "" {
|
||||
privateKeyData, err := os.ReadFile(rsa_pk_path)
|
||||
|
@ -265,10 +265,10 @@ const updateUser = `-- name: UpdateUser :one
|
||||
update
|
||||
users
|
||||
set
|
||||
username = $2,
|
||||
email = $3,
|
||||
password = $4,
|
||||
claims = $5
|
||||
username = coalesce($2, username),
|
||||
email = coalesce($3, email),
|
||||
password = coalesce($4, password),
|
||||
claims = coalesce($5, claims)
|
||||
where
|
||||
id = $1
|
||||
returning
|
||||
@ -277,8 +277,8 @@ returning
|
||||
|
||||
type UpdateUserParams struct {
|
||||
Id uuid.UUID `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Username *string `json:"username"`
|
||||
Email *string `json:"email"`
|
||||
Password *string `json:"password"`
|
||||
Claims jwt.MapClaims `json:"claims"`
|
||||
}
|
||||
|
@ -377,6 +377,48 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"Jwt": []
|
||||
}
|
||||
],
|
||||
"description": "Edit your account's info",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "Edit self",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Edited user info",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.EditUserDto"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.User"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "You can't edit a protected claim",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.KError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/{id}": {
|
||||
@ -469,10 +511,83 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"Jwt": [
|
||||
"users.write"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Edit an account info or permissions",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "Edit user",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "User id of the user to delete",
|
||||
"name": "id",
|
||||
"in": "path"
|
||||
},
|
||||
{
|
||||
"description": "Edited user info",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.EditUserDto"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.User"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "You don't have permissions to edit another account",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.KError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"main.EditUserDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"claims": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"preferOriginal": " true"
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"example": "kyoo@zoriya.dev"
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"example": "zoriya"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main.JwkSet": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -371,6 +371,48 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"Jwt": []
|
||||
}
|
||||
],
|
||||
"description": "Edit your account's info",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "Edit self",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Edited user info",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.EditUserDto"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.User"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "You can't edit a protected claim",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.KError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/{id}": {
|
||||
@ -463,10 +505,83 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"Jwt": [
|
||||
"users.write"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Edit an account info or permissions",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"summary": "Edit user",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "User id of the user to delete",
|
||||
"name": "id",
|
||||
"in": "path"
|
||||
},
|
||||
{
|
||||
"description": "Edited user info",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.EditUserDto"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.User"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "You don't have permissions to edit another account",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.KError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"main.EditUserDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"claims": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"preferOriginal": " true"
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"example": "kyoo@zoriya.dev"
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"example": "zoriya"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main.JwkSet": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -215,6 +215,8 @@ func main() {
|
||||
r.GET("/users/me", h.GetMe)
|
||||
r.DELETE("/users/:id", h.DeleteUser)
|
||||
r.DELETE("/users/me", h.DeleteSelf)
|
||||
r.PATCH("/users/:id", h.EditUser)
|
||||
r.PATCH("/users/me", h.EditSelf)
|
||||
g.POST("/users", h.Register)
|
||||
|
||||
g.POST("/sessions", h.Login)
|
||||
|
@ -125,7 +125,7 @@ func (h *Handler) createSession(c echo.Context, user *User) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(201, session)
|
||||
return c.JSON(201, MapSessionToken(&session))
|
||||
}
|
||||
|
||||
// @Summary Logout
|
||||
|
@ -67,10 +67,10 @@ returning
|
||||
update
|
||||
users
|
||||
set
|
||||
username = $2,
|
||||
email = $3,
|
||||
password = $4,
|
||||
claims = $5
|
||||
username = coalesce(sqlc.narg(username), username),
|
||||
email = coalesce(sqlc.narg(email), email),
|
||||
password = coalesce(sqlc.narg(password), password),
|
||||
claims = coalesce(sqlc.narg(claims), claims)
|
||||
where
|
||||
id = $1
|
||||
returning
|
||||
|
104
auth/users.go
104
auth/users.go
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@ -51,6 +52,12 @@ type RegisterDto struct {
|
||||
Password string `json:"password" validate:"required" example:"password1234"`
|
||||
}
|
||||
|
||||
type EditUserDto struct {
|
||||
Username *string `json:"username,omitempty" validate:"excludes=@" example:"zoriya"`
|
||||
Email *string `json:"email,omitempty" validate:"email" example:"kyoo@zoriya.dev"`
|
||||
Claims jwt.MapClaims `json:"claims,omitempty" example:"preferOriginal: true"`
|
||||
}
|
||||
|
||||
func MapDbUser(user *dbc.User) User {
|
||||
return User{
|
||||
Pk: user.Pk,
|
||||
@ -235,6 +242,11 @@ 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"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uid, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(400, "Invalid id given: not an uuid")
|
||||
@ -271,3 +283,95 @@ func (h *Handler) DeleteSelf(c echo.Context) error {
|
||||
}
|
||||
return c.JSON(200, MapDbUser(&ret))
|
||||
}
|
||||
|
||||
// @Summary Edit self
|
||||
// @Description Edit your account's info
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Jwt
|
||||
// @Param user body EditUserDto false "Edited user info"
|
||||
// @Success 200 {object} User
|
||||
// @Success 403 {object} KError "You can't edit a protected claim"
|
||||
// @Router /users/me [patch]
|
||||
func (h *Handler) EditSelf(c echo.Context) error {
|
||||
var req EditUserDto
|
||||
err := c.Bind(&req)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if err = c.Validate(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, key := range h.config.ProtectedClaims {
|
||||
if _, contains := req.Claims[key]; contains {
|
||||
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("Can't edit protected claim: '%s'.", key))
|
||||
}
|
||||
}
|
||||
|
||||
uid, err := GetCurrentUserId(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret, err := h.db.UpdateUser(context.Background(), dbc.UpdateUserParams{
|
||||
Id: uid,
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Claims: req.Claims,
|
||||
})
|
||||
if err == pgx.ErrNoRows {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Invalid token, user not found.")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(200, MapDbUser(&ret))
|
||||
}
|
||||
|
||||
// @Summary Edit user
|
||||
// @Description Edit an account info or permissions
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Jwt[users.write]
|
||||
// @Param id path string false "User id of the user to edit" Format(uuid)
|
||||
// @Param user body EditUserDto false "Edited user info"
|
||||
// @Success 200 {object} User
|
||||
// @Success 403 {object} KError "You don't have permissions to edit another account"
|
||||
// @Router /users/{id} [patch]
|
||||
func (h *Handler) EditUser(c echo.Context) error {
|
||||
err := CheckPermissions(c, []string{"user.write"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uid, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(400, "Invalid id given: not an uuid")
|
||||
}
|
||||
|
||||
var req EditUserDto
|
||||
err = c.Bind(&req)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error())
|
||||
}
|
||||
if err = c.Validate(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret, err := h.db.UpdateUser(context.Background(), dbc.UpdateUserParams{
|
||||
Id: uid,
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Claims: req.Claims,
|
||||
})
|
||||
if err == pgx.ErrNoRows {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Invalid user id, user not found")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(200, MapDbUser(&ret))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user