From 31d545530bba26d4c30f59bb45a2fe3f3312be6d Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Apr 2025 15:42:38 +0200 Subject: [PATCH] Add edit password --- auth/dbc/sessions.sql.go | 17 ++++++++ auth/docs/docs.go | 74 ++++++++++++++++++++++++++++++++++- auth/docs/swagger.json | 74 ++++++++++++++++++++++++++++++++++- auth/main.go | 1 + auth/sql/queries/sessions.sql | 5 +++ auth/users.go | 58 +++++++++++++++++++++++++++ auth/utils.go | 26 ++++++++++++ 7 files changed, 253 insertions(+), 2 deletions(-) diff --git a/auth/dbc/sessions.sql.go b/auth/dbc/sessions.sql.go index 02509fd9..212ab91f 100644 --- a/auth/dbc/sessions.sql.go +++ b/auth/dbc/sessions.sql.go @@ -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) diff --git a/auth/docs/docs.go b/auth/docs/docs.go index a7f7d070..2b9f7a65 100644 --- a/auth/docs/docs.go +++ b/auth/docs/docs.go @@ -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": { diff --git a/auth/docs/swagger.json b/auth/docs/swagger.json index e3d338e2..367b80ea 100644 --- a/auth/docs/swagger.json +++ b/auth/docs/swagger.json @@ -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": { diff --git a/auth/main.go b/auth/main.go index 13a0ca48..d976c675 100644 --- a/auth/main.go +++ b/auth/main.go @@ -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) diff --git a/auth/sql/queries/sessions.sql b/auth/sql/queries/sessions.sql index 82127d80..b665848f 100644 --- a/auth/sql/queries/sessions.sql +++ b/auth/sql/queries/sessions.sql @@ -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; diff --git a/auth/users.go b/auth/users.go index 542d6ea0..46e5fcad 100644 --- a/auth/users.go +++ b/auth/users.go @@ -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) +} diff --git a/auth/utils.go b/auth/utils.go index 20b8c377..70bca2fb 100644 --- a/auth/utils.go +++ b/auth/utils.go @@ -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 {