mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-07 10:40:42 +01:00
#### Description - Moves the users API off the legacy `AdminAccess` gate onto `CheckResources` + `ResourceDef`s; `me` and anonymous password flows stay `OpenAccess`. - Invite checks `role:attach` per requested role; an empty role list resolves to no link, so the sibling def skips the check. - Migration `131_add_user_tuples` backfills admin `user` and `factor-password` tuples for existing orgs. #### Issues closed by this PR Closes: SigNoz/keystone-pod#38
492 lines
12 KiB
Go
492 lines
12 KiB
Go
package impluser
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
"github.com/SigNoz/signoz/pkg/http/binding"
|
|
"github.com/SigNoz/signoz/pkg/http/render"
|
|
root "github.com/SigNoz/signoz/pkg/modules/user"
|
|
"github.com/SigNoz/signoz/pkg/types"
|
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
type handler struct {
|
|
setter root.Setter
|
|
getter root.Getter
|
|
}
|
|
|
|
func NewHandler(setter root.Setter, getter root.Getter) root.Handler {
|
|
return &handler{setter: setter, getter: getter}
|
|
}
|
|
|
|
func (handler *handler) CreateUser(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(authtypes.PostableUser)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
user, err := types.NewUser(req.DisplayName, req.Email, valuer.MustNewUUID(claims.OrgID), types.UserStatusPendingInvite)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
roleIDs := make([]valuer.UUID, 0, len(req.UserRoles))
|
|
for _, role := range req.UserRoles {
|
|
roleIDs = append(roleIDs, role.ID)
|
|
}
|
|
|
|
user, err = handler.setter.CreatePendingInviteUser(ctx, valuer.MustNewUUID(claims.IdentityID()), valuer.MustNewEmail(claims.Email), req.FrontendBaseUrl, user, root.WithRoleIDs(roleIDs))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusCreated, types.Identifiable{ID: user.ID})
|
|
}
|
|
|
|
func (handler *handler) GetUser(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
userID := mux.Vars(r)["id"]
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
user, err := handler.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID))
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
userRoles, err := handler.getter.GetRolesByUserID(ctx, user.ID)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
userWithRoles := &authtypes.UserWithRoles{
|
|
User: user,
|
|
UserRoles: userRoles,
|
|
}
|
|
|
|
render.Success(w, http.StatusOK, userWithRoles)
|
|
}
|
|
|
|
func (handler *handler) GetMyUserDeprecated(w http.ResponseWriter, r *http.Request) {
|
|
render.Error(w, errors.New(errors.TypeUnsupported, types.ErrCodeUserDeprecated,
|
|
"the v1 user API is deprecated; instead, get your user with GET /api/v2/users/me"))
|
|
}
|
|
|
|
func (handler *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
user, err := handler.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
userRoles, err := handler.getter.GetRolesByUserID(ctx, user.ID)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
userWithRoles := &authtypes.UserWithRoles{
|
|
User: user,
|
|
UserRoles: userRoles,
|
|
}
|
|
|
|
render.Success(w, http.StatusOK, userWithRoles)
|
|
}
|
|
|
|
func (handler *handler) UpdateMyUser(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
updatableUser := new(types.UpdatableUser)
|
|
if err := json.NewDecoder(r.Body).Decode(&updatableUser); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
_, err = handler.setter.UpdateUser(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), updatableUser)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
users, err := handler.getter.ListUsersByOrgID(ctx, valuer.MustNewUUID(claims.OrgID))
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusOK, users)
|
|
}
|
|
|
|
func (handler *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
userID := mux.Vars(r)["id"]
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
updatableUser := new(types.UpdatableUser)
|
|
if err := json.NewDecoder(r.Body).Decode(&updatableUser); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
_, err = handler.setter.UpdateUser(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID), updatableUser)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
id := mux.Vars(r)["id"]
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
if err := handler.setter.DeleteUser(ctx, valuer.MustNewUUID(claims.OrgID), id, claims.IdentityID()); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) GetResetPasswordToken(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
userID, err := valuer.NewUUID(mux.Vars(r)["id"])
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
token, err := handler.getter.GetResetPasswordTokenByOrgIDAndUserID(ctx, valuer.MustNewUUID(claims.OrgID), userID)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusOK, token)
|
|
}
|
|
|
|
func (handler *handler) CreateResetPasswordToken(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
userID, err := valuer.NewUUID(mux.Vars(r)["id"])
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
user, err := handler.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), userID)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
token, err := handler.setter.GetOrCreateResetPasswordToken(ctx, user.ID)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusCreated, token)
|
|
}
|
|
|
|
func (handler *handler) VerifyResetPasswordToken(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
req := new(types.PostableVerifyResetPasswordToken)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
err := handler.getter.VerifyResetPasswordToken(ctx, req.Token)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
req := new(types.PostableResetPassword)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
err := handler.setter.UpdatePasswordByResetPasswordToken(ctx, req.Token, req.Password)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
var req types.ChangePasswordRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
err = handler.setter.UpdatePassword(ctx, valuer.MustNewUUID(claims.UserID), req.OldPassword, req.NewPassword)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
req := new(types.PostableForgotPassword)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
err := handler.setter.ForgotPassword(ctx, req.OrgID, req.Email, req.FrontendBaseURL)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) GetRolesByUserID(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
userID := mux.Vars(r)["id"]
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
user, err := handler.getter.GetUserByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(userID))
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
userRoles, err := handler.getter.GetRolesByUserID(ctx, user.ID)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
roles := make([]*authtypes.Role, len(userRoles))
|
|
for idx, userRole := range userRoles {
|
|
roles[idx] = userRole.Role
|
|
}
|
|
|
|
render.Success(w, http.StatusOK, roles)
|
|
}
|
|
|
|
func (handler *handler) GetUsersByRoleID(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
roleID := mux.Vars(r)["id"]
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
users, err := handler.getter.GetUsersByOrgIDAndRoleID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(roleID))
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusOK, users)
|
|
}
|
|
|
|
func (handler *handler) CreateUserRole(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
req := new(authtypes.PostableUserRole)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
userRole, err := handler.setter.AddUserRoleByRoleID(ctx, valuer.MustNewUUID(claims.OrgID), req.UserID, req.RoleID)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusCreated, types.Identifiable{ID: userRole.ID})
|
|
}
|
|
|
|
func (handler *handler) GetUserRole(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
userRole, err := handler.getter.GetUserRoleByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusOK, userRole)
|
|
}
|
|
|
|
func (handler *handler) DeleteUserRole(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
userRole, err := handler.getter.GetUserRoleByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
if err := handler.setter.RemoveUserRole(ctx, valuer.MustNewUUID(claims.OrgID), userRole.UserID, userRole.RoleID); err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
|
|
render.Success(w, http.StatusNoContent, nil)
|
|
}
|