Add edit password

This commit is contained in:
Zoe Roux 2025-04-05 15:42:38 +02:00
parent dbe8e319c8
commit 31d545530b
No known key found for this signature in database
7 changed files with 253 additions and 2 deletions

View File

@ -12,6 +12,23 @@ import (
"github.com/google/uuid"
)
const clearOtherSessions = `-- name: ClearOtherSessions :exec
delete from sessions as s using users as u
where s.user_pk = u.pk
and s.id != $1
and u.id = $2
`
type ClearOtherSessionsParams struct {
SessionId uuid.UUID `json:"sessionId"`
UserId uuid.UUID `json:"userId"`
}
func (q *Queries) ClearOtherSessions(ctx context.Context, arg ClearOtherSessionsParams) error {
_, err := q.db.Exec(ctx, clearOtherSessions, arg.SessionId, arg.UserId)
return err
}
const createSession = `-- name: CreateSession :one
insert into sessions(token, user_pk, device)
values ($1, $2, $3)

View File

@ -417,6 +417,60 @@ const docTemplate = `{
"schema": {
"$ref": "#/definitions/main.KError"
}
},
"422": {
"description": "Invalid body",
"schema": {
"$ref": "#/definitions/main.KError"
}
}
}
}
},
"/users/me/password": {
"patch": {
"security": [
{
"Jwt": []
}
],
"description": "Edit your password",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Edit password",
"parameters": [
{
"type": "boolean",
"default": true,
"description": "Invalidate other sessions",
"name": "invalidate",
"in": "query"
},
{
"description": "New password",
"name": "user",
"in": "body",
"schema": {
"$ref": "#/definitions/main.EditPasswordDto"
}
}
],
"responses": {
"204": {
"description": "No Content"
},
"422": {
"description": "Invalid body",
"schema": {
"$ref": "#/definitions/main.KError"
}
}
}
}
@ -535,7 +589,7 @@ const docTemplate = `{
{
"type": "string",
"format": "uuid",
"description": "User id of the user to delete",
"description": "User id of the user to edit",
"name": "id",
"in": "path"
},
@ -560,12 +614,30 @@ const docTemplate = `{
"schema": {
"$ref": "#/definitions/main.KError"
}
},
"422": {
"description": "Invalid body",
"schema": {
"$ref": "#/definitions/main.KError"
}
}
}
}
}
},
"definitions": {
"main.EditPasswordDto": {
"type": "object",
"required": [
"password"
],
"properties": {
"password": {
"type": "string",
"example": "password1234"
}
}
},
"main.EditUserDto": {
"type": "object",
"properties": {

View File

@ -411,6 +411,60 @@
"schema": {
"$ref": "#/definitions/main.KError"
}
},
"422": {
"description": "Invalid body",
"schema": {
"$ref": "#/definitions/main.KError"
}
}
}
}
},
"/users/me/password": {
"patch": {
"security": [
{
"Jwt": []
}
],
"description": "Edit your password",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Edit password",
"parameters": [
{
"type": "boolean",
"default": true,
"description": "Invalidate other sessions",
"name": "invalidate",
"in": "query"
},
{
"description": "New password",
"name": "user",
"in": "body",
"schema": {
"$ref": "#/definitions/main.EditPasswordDto"
}
}
],
"responses": {
"204": {
"description": "No Content"
},
"422": {
"description": "Invalid body",
"schema": {
"$ref": "#/definitions/main.KError"
}
}
}
}
@ -529,7 +583,7 @@
{
"type": "string",
"format": "uuid",
"description": "User id of the user to delete",
"description": "User id of the user to edit",
"name": "id",
"in": "path"
},
@ -554,12 +608,30 @@
"schema": {
"$ref": "#/definitions/main.KError"
}
},
"422": {
"description": "Invalid body",
"schema": {
"$ref": "#/definitions/main.KError"
}
}
}
}
}
},
"definitions": {
"main.EditPasswordDto": {
"type": "object",
"required": [
"password"
],
"properties": {
"password": {
"type": "string",
"example": "password1234"
}
}
},
"main.EditUserDto": {
"type": "object",
"properties": {

View File

@ -217,6 +217,7 @@ func main() {
r.DELETE("/users/me", h.DeleteSelf)
r.PATCH("/users/:id", h.EditUser)
r.PATCH("/users/me", h.EditSelf)
r.PATCH("/users/me/password", h.ChangePassword)
g.POST("/users", h.Register)
g.POST("/sessions", h.Login)

View File

@ -43,3 +43,8 @@ where s.user_pk = u.pk
returning
s.*;
-- name: ClearOtherSessions :exec
delete from sessions as s using users as u
where s.user_pk = u.pk
and s.id != @session_id
and u.id = @user_id;

View File

@ -58,6 +58,10 @@ type EditUserDto struct {
Claims jwt.MapClaims `json:"claims,omitempty" example:"preferOriginal: true"`
}
type EditPasswordDto struct {
Password string `json:"password" validate:"required" example:"password1234"`
}
func MapDbUser(user *dbc.User) User {
return User{
Pk: user.Pk,
@ -293,6 +297,7 @@ func (h *Handler) DeleteSelf(c echo.Context) error {
// @Param user body EditUserDto false "Edited user info"
// @Success 200 {object} User
// @Success 403 {object} KError "You can't edit a protected claim"
// @Success 422 {object} KError "Invalid body"
// @Router /users/me [patch]
func (h *Handler) EditSelf(c echo.Context) error {
var req EditUserDto
@ -340,6 +345,7 @@ func (h *Handler) EditSelf(c echo.Context) error {
// @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"
// @Success 422 {object} KError "Invalid body"
// @Router /users/{id} [patch]
func (h *Handler) EditUser(c echo.Context) error {
err := CheckPermissions(c, []string{"user.write"})
@ -375,3 +381,55 @@ func (h *Handler) EditUser(c echo.Context) error {
return c.JSON(200, MapDbUser(&ret))
}
// @Summary Edit password
// @Description Edit your password
// @Tags users
// @Accept json
// @Produce json
// @Security Jwt
// @Param invalidate query bool false "Invalidate other sessions" default(true)
// @Param user body EditPasswordDto false "New password"
// @Success 204
// @Success 422 {object} KError "Invalid body"
// @Router /users/me/password [patch]
func (h *Handler) ChangePassword(c echo.Context) error {
uid, err := GetCurrentUserId(c)
if err != nil {
return err
}
sid, err := GetCurrentSessionId(c)
if err != nil {
return err
}
var req EditPasswordDto
err = c.Bind(&req)
if err != nil {
return echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error())
}
if err = c.Validate(&req); err != nil {
return err
}
_, err = h.db.UpdateUser(context.Background(), dbc.UpdateUserParams{
Id: uid,
Password: &req.Password,
})
if err == pgx.ErrNoRows {
return echo.NewHTTPError(http.StatusNotFound, "Invalid token, user not found")
} else if err != nil {
return err
}
err = h.db.ClearOtherSessions(context.Background(), dbc.ClearOtherSessionsParams{
SessionId: sid,
UserId: uid,
})
if err != nil {
return err
}
return c.NoContent(http.StatusNoContent)
}

View File

@ -28,6 +28,32 @@ func GetCurrentUserId(c echo.Context) (uuid.UUID, error) {
return ret, nil
}
func GetCurrentSessionId(c echo.Context) (uuid.UUID, error) {
user := c.Get("user").(*jwt.Token)
if user == nil {
return uuid.UUID{}, echo.NewHTTPError(401, "Unauthorized")
}
claims, ok := user.Claims.(jwt.MapClaims)
if !ok {
return uuid.UUID{}, echo.NewHTTPError(403, "Could not retrieve claims")
}
sid, ok := claims["sid"]
if !ok {
return uuid.UUID{}, echo.NewHTTPError(403, "Could not retrieve session")
}
sid_str, ok := sid.(string)
if !ok {
return uuid.UUID{}, echo.NewHTTPError(403, "Invalid session id claim.")
}
ret, err := uuid.Parse(sid_str)
if err != nil {
return uuid.UUID{}, echo.NewHTTPError(403, "Invalid id")
}
return ret, nil
}
func CheckPermissions(c echo.Context, perms []string) error {
token, ok := c.Get("user").(*jwt.Token)
if !ok {