From c498c583a97bb3a8cff6caf39f16e4f8d22bd89a Mon Sep 17 00:00:00 2001 From: donggyu Date: Thu, 27 Apr 2023 10:49:56 +0900 Subject: [PATCH 1/6] trivial fix: change wrong comment --- internal/keycloak/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/keycloak/config.go b/internal/keycloak/config.go index 9f8e5786..cb87c975 100644 --- a/internal/keycloak/config.go +++ b/internal/keycloak/config.go @@ -13,6 +13,6 @@ const ( DefaultClientSecret = "secret" AdminCliClientID = "admin-cli" accessTokenLifespan = 60 * 60 * 24 // 1 day - ssoSessionIdleTimeout = 60 * 60 * 8 // 2 hours + ssoSessionIdleTimeout = 60 * 60 * 8 // 8 hours ssoSessionMaxLifespan = 60 * 60 * 24 // 1 day ) From 0a824eab1f5afed2441043f45b6c4cbd06c7e499 Mon Sep 17 00:00:00 2001 From: donggyu Date: Thu, 27 Apr 2023 13:35:16 +0900 Subject: [PATCH 2/6] feature: reset user password by admin --- internal/delivery/http/user.go | 34 +++++++++++++++++++++++ internal/route/route.go | 1 + internal/usecase/user.go | 51 ++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/internal/delivery/http/user.go b/internal/delivery/http/user.go index b3ab2316..f537bb0f 100644 --- a/internal/delivery/http/user.go +++ b/internal/delivery/http/user.go @@ -18,6 +18,7 @@ type IUserHandler interface { Get(w http.ResponseWriter, r *http.Request) Delete(w http.ResponseWriter, r *http.Request) Update(w http.ResponseWriter, r *http.Request) + ResetPassword(w http.ResponseWriter, r *http.Request) GetMyProfile(w http.ResponseWriter, r *http.Request) UpdateMyProfile(w http.ResponseWriter, r *http.Request) @@ -294,6 +295,39 @@ func (u UserHandler) Update(w http.ResponseWriter, r *http.Request) { ResponseJSON(w, http.StatusOK, out) } +// ResetPassword godoc +// @Tags Users +// @Summary Reset user's password as temporary password by admin +// @Description Reset user's password as temporary password by admin and send email to user +// @Accept json +// @Produce json +// @Param organizationId path string true "organizationId" +// @Param accountId path string true "accountId" +// @Success 200 +// @Router /organizations/{organizationId}/users/{accountId}/reset-password [post] +// @Security JWT +func (u UserHandler) ResetPassword(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + accountId, ok := vars["accountId"] + if !ok { + ErrorJSON(w, httpErrors.NewBadRequestError(fmt.Errorf("accountId not found in path"))) + return + } + organizationId, ok := vars["organizationId"] + if !ok { + ErrorJSON(w, httpErrors.NewBadRequestError(fmt.Errorf("organizationId not found in path"))) + return + } + + err := u.usecase.ResetPasswordByAccountId(accountId, organizationId) + if err != nil { + ErrorJSON(w, err) + return + } + + ResponseJSON(w, http.StatusOK, nil) +} + // GetMyProfile godoc // @Tags My-profile // @Summary Get my profile detail diff --git a/internal/route/route.go b/internal/route/route.go index 2fa7fbd1..245ed817 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -83,6 +83,7 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, asset http.Handler, r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users", authMiddleware.Handle(http.HandlerFunc(userHandler.List))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Get))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Update))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}/reset-password", authMiddleware.Handle(http.HandlerFunc(userHandler.ResetPassword))).Methods(http.MethodPost) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Delete))).Methods(http.MethodDelete) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/my-profile", authMiddleware.Handle(http.HandlerFunc(userHandler.GetMyProfile))).Methods(http.MethodGet) diff --git a/internal/usecase/user.go b/internal/usecase/user.go index c788964a..0e120cb8 100644 --- a/internal/usecase/user.go +++ b/internal/usecase/user.go @@ -3,6 +3,7 @@ package usecase import ( "context" "fmt" + "github.com/openinfradev/tks-api/internal/aws/ses" "github.com/openinfradev/tks-api/internal/middleware/auth/request" "net/http" @@ -25,6 +26,8 @@ type IUserUsecase interface { List(ctx context.Context, organizationId string) (*[]domain.User, error) Get(userId uuid.UUID) (*domain.User, error) Update(ctx context.Context, userId uuid.UUID, user *domain.User) (*domain.User, error) + ResetPassword(userId uuid.UUID, organizationId string) error + ResetPasswordByAccountId(accountId string, organizationId string) error Delete(userId uuid.UUID, organizationId string) error GetByAccountId(ctx context.Context, accountId string, organizationId string) (*domain.User, error) GetByEmail(ctx context.Context, email string, organizationId string) (*domain.User, error) @@ -43,6 +46,54 @@ type UserUsecase struct { kc keycloak.IKeycloak } +func (u *UserUsecase) ResetPassword(userId uuid.UUID, organizationId string) error { + user, err := u.repo.GetByUuid(userId) + if err != nil { + return err + } + randomPassword := helper.GenerateRandomString(passwordLength) + + originUser, err := u.kc.GetUser(organizationId, user.AccountId) + if err != nil { + return err + } + originUser.Credentials = &[]gocloak.CredentialRepresentation{ + { + Type: gocloak.StringP("password"), + Value: gocloak.StringP(randomPassword), + Temporary: gocloak.BoolP(false), + }, + } + if err = u.kc.UpdateUser(organizationId, originUser); err != nil { + return httpErrors.NewInternalServerError(err) + } + + if user.Password, err = helper.HashPassword(randomPassword); err != nil { + return httpErrors.NewInternalServerError(err) + } + if err = u.repo.UpdatePassword(userId, organizationId, user.Password, true); err != nil { + return httpErrors.NewInternalServerError(err) + } + + if err = ses.SendEmailForTemporaryPassword(ses.Client, user.Email, randomPassword); err != nil { + return httpErrors.NewInternalServerError(err) + } + + return nil +} + +func (u *UserUsecase) ResetPasswordByAccountId(accountId string, organizationId string) error { + user, err := u.repo.Get(accountId, organizationId) + if err != nil { + if _, status := httpErrors.ErrorResponse(err); status == http.StatusNotFound { + return err + } + return httpErrors.NewInternalServerError(err) + } + userId, err := uuid.Parse(user.AccountId) + return u.ResetPassword(userId, organizationId) +} + func (u *UserUsecase) ValidateAccount(userId uuid.UUID, password string, organizationId string) error { user, err := u.repo.GetByUuid(userId) if err != nil { From 6be0e1c79fdf7e47081c848db99f5bd4031db32d Mon Sep 17 00:00:00 2001 From: donggyu Date: Thu, 27 Apr 2023 14:21:01 +0900 Subject: [PATCH 3/6] feature: renew password expired date --- internal/delivery/http/user.go | 30 ++++++++++++++++++- internal/route/route.go | 3 +- internal/usecase/user.go | 53 ++++++++++++++++++++++++++++------ 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/internal/delivery/http/user.go b/internal/delivery/http/user.go index f537bb0f..d08813bc 100644 --- a/internal/delivery/http/user.go +++ b/internal/delivery/http/user.go @@ -23,6 +23,7 @@ type IUserHandler interface { GetMyProfile(w http.ResponseWriter, r *http.Request) UpdateMyProfile(w http.ResponseWriter, r *http.Request) UpdateMyPassword(w http.ResponseWriter, r *http.Request) + RenewPasswordExpiredDate(w http.ResponseWriter, r *http.Request) DeleteMyProfile(w http.ResponseWriter, r *http.Request) CheckId(w http.ResponseWriter, r *http.Request) @@ -304,7 +305,7 @@ func (u UserHandler) Update(w http.ResponseWriter, r *http.Request) { // @Param organizationId path string true "organizationId" // @Param accountId path string true "accountId" // @Success 200 -// @Router /organizations/{organizationId}/users/{accountId}/reset-password [post] +// @Router /organizations/{organizationId}/users/{accountId}/reset-password [put] // @Security JWT func (u UserHandler) ResetPassword(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -470,6 +471,33 @@ func (u UserHandler) UpdateMyPassword(w http.ResponseWriter, r *http.Request) { ResponseJSON(w, http.StatusOK, nil) } +// RenewPasswordExpiredDate godoc +// @Tags My-profile +// @Summary Update user's password expired date to current date +// @Description Update user's password expired date to current date +// @Accept json +// @Produce json +// @Param organizationId path string true "organizationId" +// @Success 200 +// @Failure 400 {object} httpErrors.RestError +// @Router /organizations/{organizationId}/my-profile/next-password-change [put] +// @Security JWT +func (u UserHandler) RenewPasswordExpiredDate(w http.ResponseWriter, r *http.Request) { + requestUserInfo, ok := request.UserFrom(r.Context()) + if !ok { + ErrorJSON(w, httpErrors.NewInternalServerError(fmt.Errorf("user not found in request"))) + return + } + + err := u.usecase.RenewalPasswordExpiredTime(r.Context(), requestUserInfo.GetUserId()) + if err != nil { + ErrorJSON(w, err) + return + } + + ResponseJSON(w, http.StatusOK, nil) +} + // DeleteMyProfile godoc // @Tags My-profile // @Summary Delete myProfile diff --git a/internal/route/route.go b/internal/route/route.go index 245ed817..ab92ec42 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -83,12 +83,13 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, asset http.Handler, r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users", authMiddleware.Handle(http.HandlerFunc(userHandler.List))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Get))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Update))).Methods(http.MethodPut) - r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}/reset-password", authMiddleware.Handle(http.HandlerFunc(userHandler.ResetPassword))).Methods(http.MethodPost) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}/reset-password", authMiddleware.Handle(http.HandlerFunc(userHandler.ResetPassword))).Methods(http.MethodPut) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/{accountId}", authMiddleware.Handle(http.HandlerFunc(userHandler.Delete))).Methods(http.MethodDelete) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/my-profile", authMiddleware.Handle(http.HandlerFunc(userHandler.GetMyProfile))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/my-profile", authMiddleware.Handle(http.HandlerFunc(userHandler.UpdateMyProfile))).Methods(http.MethodPut) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/my-profile/password", authMiddleware.Handle(http.HandlerFunc(userHandler.UpdateMyPassword))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/my-profile/next-password-change", authMiddleware.Handle(http.HandlerFunc(userHandler.RenewPasswordExpiredDate))).Methods(http.MethodPut) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/my-profile", authMiddleware.Handle(http.HandlerFunc(userHandler.DeleteMyProfile))).Methods(http.MethodDelete) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/users/accountId/{accountId}/existence", authMiddleware.Handle(http.HandlerFunc(userHandler.CheckId))).Methods(http.MethodGet) diff --git a/internal/usecase/user.go b/internal/usecase/user.go index 0e120cb8..1a21d10f 100644 --- a/internal/usecase/user.go +++ b/internal/usecase/user.go @@ -26,7 +26,7 @@ type IUserUsecase interface { List(ctx context.Context, organizationId string) (*[]domain.User, error) Get(userId uuid.UUID) (*domain.User, error) Update(ctx context.Context, userId uuid.UUID, user *domain.User) (*domain.User, error) - ResetPassword(userId uuid.UUID, organizationId string) error + ResetPassword(userId uuid.UUID) error ResetPasswordByAccountId(accountId string, organizationId string) error Delete(userId uuid.UUID, organizationId string) error GetByAccountId(ctx context.Context, accountId string, organizationId string) (*domain.User, error) @@ -34,6 +34,8 @@ type IUserUsecase interface { UpdateByAccountId(ctx context.Context, accountId string, user *domain.User) (*domain.User, error) UpdatePasswordByAccountId(ctx context.Context, accountId string, originPassword string, newPassword string, organizationId string) error + RenewalPasswordExpiredTime(ctx context.Context, userId uuid.UUID) error + RenewalPasswordExpiredTimeByAccountId(ctx context.Context, accountId string, organizationId string) error DeleteByAccountId(ctx context.Context, accountId string, organizationId string) error ValidateAccount(userId uuid.UUID, password string, organizationId string) error ValidateAccountByAccountId(accountId string, password string, organizationId string) error @@ -46,17 +48,50 @@ type UserUsecase struct { kc keycloak.IKeycloak } -func (u *UserUsecase) ResetPassword(userId uuid.UUID, organizationId string) error { +func (u *UserUsecase) RenewalPasswordExpiredTime(ctx context.Context, userId uuid.UUID) error { user, err := u.repo.GetByUuid(userId) if err != nil { - return err + if _, status := httpErrors.ErrorResponse(err); status != http.StatusNotFound { + return httpErrors.NewBadRequestError(fmt.Errorf("user not found")) + } + return httpErrors.NewInternalServerError(err) } - randomPassword := helper.GenerateRandomString(passwordLength) - originUser, err := u.kc.GetUser(organizationId, user.AccountId) + err = u.repo.UpdatePassword(userId, user.Organization.ID, user.Password, false) + if err != nil { + log.Errorf("failed to update password expired time: %v", err) + return httpErrors.NewInternalServerError(err) + } + + return nil +} + +func (u *UserUsecase) RenewalPasswordExpiredTimeByAccountId(ctx context.Context, accountId string, organizationId string) error { + user, err := u.repo.Get(accountId, organizationId) + if err != nil { + if _, status := httpErrors.ErrorResponse(err); status != http.StatusNotFound { + return httpErrors.NewBadRequestError(fmt.Errorf("user not found")) + } + return httpErrors.NewInternalServerError(err) + } + userId, err := uuid.Parse(user.ID) + return u.RenewalPasswordExpiredTime(ctx, userId) +} + +func (u *UserUsecase) ResetPassword(userId uuid.UUID) error { + user, err := u.repo.GetByUuid(userId) if err != nil { return err } + originUser, err := u.kc.GetUser(user.Organization.ID, user.AccountId) + if err != nil { + if _, status := httpErrors.ErrorResponse(err); status == http.StatusNotFound { + return httpErrors.NewBadRequestError(fmt.Errorf("user not found")) + } + return httpErrors.NewInternalServerError(err) + } + + randomPassword := helper.GenerateRandomString(passwordLength) originUser.Credentials = &[]gocloak.CredentialRepresentation{ { Type: gocloak.StringP("password"), @@ -64,14 +99,14 @@ func (u *UserUsecase) ResetPassword(userId uuid.UUID, organizationId string) err Temporary: gocloak.BoolP(false), }, } - if err = u.kc.UpdateUser(organizationId, originUser); err != nil { + if err = u.kc.UpdateUser(user.Organization.ID, originUser); err != nil { return httpErrors.NewInternalServerError(err) } if user.Password, err = helper.HashPassword(randomPassword); err != nil { return httpErrors.NewInternalServerError(err) } - if err = u.repo.UpdatePassword(userId, organizationId, user.Password, true); err != nil { + if err = u.repo.UpdatePassword(userId, user.Organization.ID, user.Password, true); err != nil { return httpErrors.NewInternalServerError(err) } @@ -86,12 +121,12 @@ func (u *UserUsecase) ResetPasswordByAccountId(accountId string, organizationId user, err := u.repo.Get(accountId, organizationId) if err != nil { if _, status := httpErrors.ErrorResponse(err); status == http.StatusNotFound { - return err + return httpErrors.NewBadRequestError(fmt.Errorf("user not found")) } return httpErrors.NewInternalServerError(err) } userId, err := uuid.Parse(user.AccountId) - return u.ResetPassword(userId, organizationId) + return u.ResetPassword(userId) } func (u *UserUsecase) ValidateAccount(userId uuid.UUID, password string, organizationId string) error { From cda11a83610f83a7dc7329a8e567fcba83cfce9c Mon Sep 17 00:00:00 2001 From: donggyu Date: Thu, 27 Apr 2023 14:24:21 +0900 Subject: [PATCH 4/6] minor fix: change email contents --- internal/aws/ses/ses.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/aws/ses/ses.go b/internal/aws/ses/ses.go index 488d92e1..9be75772 100644 --- a/internal/aws/ses/ses.go +++ b/internal/aws/ses/ses.go @@ -84,8 +84,10 @@ func SendEmailForVerityIdentity(client *awsSes.Client, targetEmailAddress string func SendEmailForTemporaryPassword(client *awsSes.Client, targetEmailAddress string, randomPassword string) error { subject := "[TKS] 비밀번호 초기화" - body := "임시 비밀번호가 발급되었습니다.\n\n" + "임시 비밀번호는 [" + randomPassword + "]이며\n" + - "로그인 후 비밀번호를 변경하여 사용하십시요.\n\n" + "TKS를 이용해 주셔서 감사합니다.\nTKS Team 드림" + body := "임시 비밀번호가 발급되었습니다.\n" + + "로그인 후 비밀번호를 변경하여 사용하십시오.\n\n" + + "임시 비밀번호: " + randomPassword + "\n\n" + + "TKS를 이용해 주셔서 감사합니다.\nTKS Team 드림" input := &awsSes.SendEmailInput{ Destination: &types.Destination{ From c6d21e58986465a93ae4feaaad4aecd998954e6d Mon Sep 17 00:00:00 2001 From: donggyu Date: Thu, 27 Apr 2023 14:25:20 +0900 Subject: [PATCH 5/6] swagger docs --- api/swagger/docs.go | 81 ++++++++++++++++++++++++++++++++++++++++ api/swagger/swagger.json | 81 ++++++++++++++++++++++++++++++++++++++++ api/swagger/swagger.yaml | 52 ++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) diff --git a/api/swagger/docs.go b/api/swagger/docs.go index 5d747e14..67806dea 100644 --- a/api/swagger/docs.go +++ b/api/swagger/docs.go @@ -1854,6 +1854,46 @@ const docTemplate = `{ } } }, + "/organizations/{organizationId}/my-profile/next-password-change": { + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Update user's password expired date to current date", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "My-profile" + ], + "summary": "Update user's password expired date to current date", + "parameters": [ + { + "type": "string", + "description": "organizationId", + "name": "organizationId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httpErrors.RestError" + } + } + } + } + }, "/organizations/{organizationId}/my-profile/password": { "put": { "security": [ @@ -2503,6 +2543,47 @@ const docTemplate = `{ } } }, + "/organizations/{organizationId}/users/{accountId}/reset-password": { + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Reset user's password as temporary password by admin and send email to user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Reset user's password as temporary password by admin", + "parameters": [ + { + "type": "string", + "description": "organizationId", + "name": "organizationId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "accountId", + "name": "accountId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/stack-templates": { "get": { "security": [ diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index 5e1e7519..d726ac4d 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -1847,6 +1847,46 @@ } } }, + "/organizations/{organizationId}/my-profile/next-password-change": { + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Update user's password expired date to current date", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "My-profile" + ], + "summary": "Update user's password expired date to current date", + "parameters": [ + { + "type": "string", + "description": "organizationId", + "name": "organizationId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httpErrors.RestError" + } + } + } + } + }, "/organizations/{organizationId}/my-profile/password": { "put": { "security": [ @@ -2496,6 +2536,47 @@ } } }, + "/organizations/{organizationId}/users/{accountId}/reset-password": { + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Reset user's password as temporary password by admin and send email to user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Reset user's password as temporary password by admin", + "parameters": [ + { + "type": "string", + "description": "organizationId", + "name": "organizationId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "accountId", + "name": "accountId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/stack-templates": { "get": { "security": [ diff --git a/api/swagger/swagger.yaml b/api/swagger/swagger.yaml index a41bca63..809dba2d 100644 --- a/api/swagger/swagger.yaml +++ b/api/swagger/swagger.yaml @@ -2693,6 +2693,31 @@ paths: summary: Update my profile detail tags: - My-profile + /organizations/{organizationId}/my-profile/next-password-change: + put: + consumes: + - application/json + description: Update user's password expired date to current date + parameters: + - description: organizationId + in: path + name: organizationId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + "400": + description: Bad Request + schema: + $ref: '#/definitions/httpErrors.RestError' + security: + - JWT: [] + summary: Update user's password expired date to current date + tags: + - My-profile /organizations/{organizationId}/my-profile/password: put: consumes: @@ -3054,6 +3079,33 @@ paths: summary: Update user tags: - Users + /organizations/{organizationId}/users/{accountId}/reset-password: + put: + consumes: + - application/json + description: Reset user's password as temporary password by admin and send email + to user + parameters: + - description: organizationId + in: path + name: organizationId + required: true + type: string + - description: accountId + in: path + name: accountId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + security: + - JWT: [] + summary: Reset user's password as temporary password by admin + tags: + - Users /organizations/{organizationId}/users/accountId/{accountId}/existence: get: description: return true when accountId exists From dfa64269af1a4bd102e10a0d530577d97cbb4465 Mon Sep 17 00:00:00 2001 From: donggyu Date: Thu, 27 Apr 2023 15:19:41 +0900 Subject: [PATCH 6/6] minor fix: stablize api --- .../middleware/auth/authorizer/password.go | 16 ++++++++++++++-- internal/middleware/auth/authorizer/rbac.go | 6 ++++++ internal/usecase/user.go | 18 +++++++++++++----- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/internal/middleware/auth/authorizer/password.go b/internal/middleware/auth/authorizer/password.go index b9e9992d..f45678ba 100644 --- a/internal/middleware/auth/authorizer/password.go +++ b/internal/middleware/auth/authorizer/password.go @@ -30,8 +30,11 @@ func PasswordFilter(handler http.Handler, repo repository.Repository) http.Handl return } if helper.IsDurationExpired(storedUser.PasswordUpdatedAt, internal.PasswordExpiredDuration) { - allowedUrl := internal.API_PREFIX + internal.API_VERSION + "/organizations/" + requestUserInfo.GetOrganizationId() + "/my-profile" + "/password" - if !(r.URL.Path == allowedUrl && r.Method == http.MethodPut) { + allowedUrl := [2]string{ + internal.API_PREFIX + internal.API_VERSION + "/organizations/" + requestUserInfo.GetOrganizationId() + "/my-profile" + "/password", + internal.API_PREFIX + internal.API_VERSION + "/organizations/" + requestUserInfo.GetOrganizationId() + "/my-profile" + "/next-password-change", + } + if !(urlContains(allowedUrl, r.URL.Path) && r.Method == http.MethodPut) { internalHttp.ErrorJSON(w, httpErrors.NewForbiddenError(fmt.Errorf("password expired"))) return } @@ -39,3 +42,12 @@ func PasswordFilter(handler http.Handler, repo repository.Repository) http.Handl handler.ServeHTTP(w, r) }) } + +func urlContains(urls [2]string, url string) bool { + for _, u := range urls { + if u == url { + return true + } + } + return false +} diff --git a/internal/middleware/auth/authorizer/rbac.go b/internal/middleware/auth/authorizer/rbac.go index 0efba7cc..f3cf9521 100644 --- a/internal/middleware/auth/authorizer/rbac.go +++ b/internal/middleware/auth/authorizer/rbac.go @@ -22,6 +22,12 @@ func RBACFilter(handler http.Handler, repo repository.Repository) http.Handler { } role := requestUserInfo.GetRoleProjectMapping()[requestUserInfo.GetOrganizationId()] + // TODO: 추후 tks-admin role 수정 필요 + if role == "tks-admin" { + handler.ServeHTTP(w, r) + return + } + vars := mux.Vars(r) // Organization Filter if role == "admin" || role == "user" { diff --git a/internal/usecase/user.go b/internal/usecase/user.go index 1a21d10f..3c5a2771 100644 --- a/internal/usecase/user.go +++ b/internal/usecase/user.go @@ -75,15 +75,20 @@ func (u *UserUsecase) RenewalPasswordExpiredTimeByAccountId(ctx context.Context, return httpErrors.NewInternalServerError(err) } userId, err := uuid.Parse(user.ID) + if err != nil { + return httpErrors.NewInternalServerError(err) + } return u.RenewalPasswordExpiredTime(ctx, userId) } func (u *UserUsecase) ResetPassword(userId uuid.UUID) error { user, err := u.repo.GetByUuid(userId) if err != nil { - return err + if _, status := httpErrors.ErrorResponse(err); status == http.StatusNotFound { + return httpErrors.NewBadRequestError(fmt.Errorf("user not found")) + } } - originUser, err := u.kc.GetUser(user.Organization.ID, user.AccountId) + userInKeycloak, err := u.kc.GetUser(user.Organization.ID, user.AccountId) if err != nil { if _, status := httpErrors.ErrorResponse(err); status == http.StatusNotFound { return httpErrors.NewBadRequestError(fmt.Errorf("user not found")) @@ -92,14 +97,14 @@ func (u *UserUsecase) ResetPassword(userId uuid.UUID) error { } randomPassword := helper.GenerateRandomString(passwordLength) - originUser.Credentials = &[]gocloak.CredentialRepresentation{ + userInKeycloak.Credentials = &[]gocloak.CredentialRepresentation{ { Type: gocloak.StringP("password"), Value: gocloak.StringP(randomPassword), Temporary: gocloak.BoolP(false), }, } - if err = u.kc.UpdateUser(user.Organization.ID, originUser); err != nil { + if err = u.kc.UpdateUser(user.Organization.ID, userInKeycloak); err != nil { return httpErrors.NewInternalServerError(err) } @@ -125,7 +130,10 @@ func (u *UserUsecase) ResetPasswordByAccountId(accountId string, organizationId } return httpErrors.NewInternalServerError(err) } - userId, err := uuid.Parse(user.AccountId) + userId, err := uuid.Parse(user.ID) + if err != nil { + return httpErrors.NewInternalServerError(err) + } return u.ResetPassword(userId) }