mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-07-04 17:06:49 +00:00
Adds LDAP auth support
This commit is contained in:
@ -3,97 +3,244 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
h "npm/internal/api/http"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
c "npm/internal/api/context"
|
||||
h "npm/internal/api/http"
|
||||
"npm/internal/entity/auth"
|
||||
"npm/internal/entity/setting"
|
||||
"npm/internal/entity/user"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
njwt "npm/internal/jwt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type setAuthModel struct {
|
||||
// The json tags are required, as the change password form decodes into this object
|
||||
Type string `json:"type"`
|
||||
Secret string `json:"secret"`
|
||||
CurrentSecret string `json:"current_secret"`
|
||||
// tokenPayload is the structure we expect from a incoming login request
|
||||
type tokenPayload struct {
|
||||
Type string `json:"type"`
|
||||
Identity string `json:"identity"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
// SetAuth sets a auth method. This can be used for "me" and `2` for example
|
||||
// Route: POST /users/:userID/auth
|
||||
func SetAuth() func(http.ResponseWriter, *http.Request) {
|
||||
// GetAuthConfig is anonymous and returns the types of authentication
|
||||
// enabled for this site
|
||||
func GetAuthConfig() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
val, err := setting.GetAuthMethods()
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, nil)
|
||||
return
|
||||
} else if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, val)
|
||||
}
|
||||
}
|
||||
|
||||
// NewToken Also known as a Login, requesting a new token with credentials
|
||||
// Route: POST /auth
|
||||
func NewToken() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Read the bytes from the body
|
||||
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
|
||||
|
||||
var newAuth setAuthModel
|
||||
err := json.Unmarshal(bodyBytes, &newAuth)
|
||||
var payload tokenPayload
|
||||
err := json.Unmarshal(bodyBytes, &payload)
|
||||
if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
userID, isSelf, userIDErr := getUserIDFromRequest(r)
|
||||
if userIDErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, userIDErr.Error(), nil)
|
||||
// Check that this auth type is enabled
|
||||
if authMethods, err := setting.GetAuthMethods(); err == gorm.ErrRecordNotFound {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, nil)
|
||||
return
|
||||
} else if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
} else if !slices.Contains(authMethods, payload.Type) {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidAuthType.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Load user
|
||||
thisUser, thisUserErr := user.GetByID(userID)
|
||||
if thisUserErr == gorm.ErrRecordNotFound {
|
||||
h.NotFound(w, r)
|
||||
return
|
||||
} else if thisUserErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, thisUserErr.Error(), nil)
|
||||
return
|
||||
switch payload.Type {
|
||||
case "ldap":
|
||||
newTokenLDAP(w, r, payload)
|
||||
case "oidc":
|
||||
newTokenOIDC(w, r, payload)
|
||||
case "local":
|
||||
newTokenLocal(w, r, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTokenLocal(w http.ResponseWriter, r *http.Request, payload tokenPayload) {
|
||||
// Find user by email
|
||||
userObj, userErr := user.GetByEmail(payload.Identity)
|
||||
if userErr != nil {
|
||||
logger.Debug("%s: %s", errors.ErrInvalidLogin.Error(), userErr.Error())
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if userObj.IsDisabled {
|
||||
h.ResultErrorJSON(w, r, http.StatusUnauthorized, errors.ErrUserDisabled.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get Auth
|
||||
authObj, authErr := auth.GetByUserIDType(userObj.ID, payload.Type)
|
||||
if authErr != nil {
|
||||
logger.Debug("%s: %s", errors.ErrInvalidLogin.Error(), authErr.Error())
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify Auth
|
||||
validateErr := authObj.ValidateSecret(payload.Secret)
|
||||
if validateErr != nil {
|
||||
logger.Debug("%s: %s", errors.ErrInvalidLogin.Error(), validateErr.Error())
|
||||
// Sleep for 1 second to prevent brute force password guessing
|
||||
time.Sleep(time.Second)
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if response, err := njwt.Generate(&userObj, false); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
} else {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
|
||||
func newTokenLDAP(w http.ResponseWriter, r *http.Request, payload tokenPayload) {
|
||||
// Get LDAP settings
|
||||
ldapSettings, err := setting.GetLDAPSettings()
|
||||
if err != nil {
|
||||
logger.Error("LDAP settings not found", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Lets try to authenticate with LDAP
|
||||
ldapUser, err := auth.LDAPAuthenticate(payload.Identity, payload.Secret)
|
||||
if err != nil {
|
||||
logger.Error("LDAP Auth Error", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get Auth by identity
|
||||
authObj, authErr := auth.GetByIdenityType(ldapUser.Username, payload.Type)
|
||||
if authErr == gorm.ErrRecordNotFound {
|
||||
// Auth is not found for this identity. We can create it
|
||||
if !ldapSettings.AutoCreateUser {
|
||||
// LDAP Login was successful, but user does not have an auth record
|
||||
// and auto create is disabled. Showing account disabled error
|
||||
// for the time being
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrUserDisabled.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt to find user by email
|
||||
foundUser, err := user.GetByEmail(ldapUser.Email)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// User not found, create user
|
||||
foundUser, err = user.CreateFromLDAPUser(ldapUser)
|
||||
if err != nil {
|
||||
logger.Error("user.CreateFromLDAPUser", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
logger.Info("Created user from LDAP: %s, %s", ldapUser.Username, foundUser.Email)
|
||||
} else if err != nil {
|
||||
logger.Error("user.GetByEmail", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Create auth record and attach to this user
|
||||
authObj = auth.Model{
|
||||
UserID: foundUser.ID,
|
||||
Type: auth.TypeLDAP,
|
||||
Identity: ldapUser.Username,
|
||||
}
|
||||
if err := authObj.Save(); err != nil {
|
||||
logger.Error("auth.Save", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
logger.Info("Created LDAP auth for user: %s, %s", ldapUser.Username, foundUser.Email)
|
||||
} else if authErr != nil {
|
||||
logger.Error("auth.GetByIdenityType", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, authErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
userObj, userErr := user.GetByID(authObj.UserID)
|
||||
if userErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, userErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if userObj.IsDisabled {
|
||||
h.ResultErrorJSON(w, r, http.StatusUnauthorized, errors.ErrUserDisabled.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if response, err := njwt.Generate(&userObj, false); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
} else {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
|
||||
func newTokenOIDC(w http.ResponseWriter, r *http.Request, _ tokenPayload) {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "NOT YET SUPPORTED", nil)
|
||||
}
|
||||
|
||||
// RefreshToken an existing token by given them a new one with the same claims
|
||||
// Route: POST /auth/refresh
|
||||
func RefreshToken() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO: Use your own methods to verify an existing user is
|
||||
// able to refresh their token and then give them a new one
|
||||
userObj, _ := user.GetByEmail("jc@jc21.com")
|
||||
if response, err := njwt.Generate(&userObj, false); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
} else {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewSSEToken will generate and return a very short lived token for
|
||||
// use by the /sse/* endpoint. It requires an app token to generate this
|
||||
// Route: POST /auth/sse
|
||||
func NewSSEToken() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.Context().Value(c.UserIDCtxKey).(uint)
|
||||
|
||||
// Find user
|
||||
userObj, userErr := user.GetByID(userID)
|
||||
if userErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if userObj.IsDisabled {
|
||||
h.ResultErrorJSON(w, r, http.StatusUnauthorized, errors.ErrUserDisabled.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if response, err := njwt.Generate(&userObj, true); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
} else {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, response)
|
||||
}
|
||||
|
||||
if thisUser.IsSystem {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, "Cannot set password for system user", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Load existing auth for user
|
||||
userAuth, userAuthErr := auth.GetByUserIDType(userID, newAuth.Type)
|
||||
if userAuthErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, userAuthErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if isSelf {
|
||||
// confirm that the current_secret given is valid for the one stored in the database
|
||||
validateErr := userAuth.ValidateSecret(newAuth.CurrentSecret)
|
||||
if validateErr != nil {
|
||||
logger.Debug("%s: %s", "Password change: current password was incorrect", validateErr.Error())
|
||||
// Sleep for 1 second to prevent brute force password guessing
|
||||
time.Sleep(time.Second)
|
||||
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrCurrentPasswordInvalid.Error(), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if newAuth.Type == auth.TypePassword {
|
||||
err := userAuth.SetPassword(newAuth.Secret)
|
||||
if err != nil {
|
||||
logger.Error("SetPasswordError", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
if err = userAuth.Save(); err != nil {
|
||||
logger.Error("AuthSaveError", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "Unable to save Authentication for User", nil)
|
||||
return
|
||||
}
|
||||
|
||||
userAuth.Secret = ""
|
||||
|
||||
// todo: add to audit-log
|
||||
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, userAuth)
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,12 @@ func CreateSetting() func(http.ResponseWriter, *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the setting already exists
|
||||
if _, err := setting.GetByName(newSetting.Name); err == nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Setting with name '%s' already exists", newSetting.Name), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err = newSetting.Save(); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Setting: %s", err.Error()), nil)
|
||||
return
|
||||
@ -75,6 +81,7 @@ func CreateSetting() func(http.ResponseWriter, *http.Request) {
|
||||
|
||||
// UpdateSetting updates a setting
|
||||
// Route: PUT /settings/{name}
|
||||
// TODO: Add validation for the setting value, for system settings they should be validated against the setting name and type
|
||||
func UpdateSetting() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
settingName := chi.URLParam(r, "name")
|
||||
@ -85,13 +92,12 @@ func UpdateSetting() func(http.ResponseWriter, *http.Request) {
|
||||
h.NotFound(w, r)
|
||||
case nil:
|
||||
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
|
||||
err := json.Unmarshal(bodyBytes, &setting)
|
||||
if err != nil {
|
||||
if err := json.Unmarshal(bodyBytes, &setting); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err = setting.Save(); err != nil {
|
||||
if err := setting.Save(); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
@ -1,116 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
h "npm/internal/api/http"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"time"
|
||||
|
||||
c "npm/internal/api/context"
|
||||
"npm/internal/entity/auth"
|
||||
"npm/internal/entity/user"
|
||||
njwt "npm/internal/jwt"
|
||||
)
|
||||
|
||||
// tokenPayload is the structure we expect from a incoming login request
|
||||
type tokenPayload struct {
|
||||
Type string `json:"type"`
|
||||
Identity string `json:"identity"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
// NewToken Also known as a Login, requesting a new token with credentials
|
||||
// Route: POST /tokens
|
||||
func NewToken() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Read the bytes from the body
|
||||
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
|
||||
|
||||
var payload tokenPayload
|
||||
err := json.Unmarshal(bodyBytes, &payload)
|
||||
if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Find user
|
||||
userObj, userErr := user.GetByEmail(payload.Identity)
|
||||
if userErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if userObj.IsDisabled {
|
||||
h.ResultErrorJSON(w, r, http.StatusUnauthorized, errors.ErrUserDisabled.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get Auth
|
||||
authObj, authErr := auth.GetByUserIDType(userObj.ID, payload.Type)
|
||||
if authErr != nil {
|
||||
logger.Debug("%s: %s", errors.ErrInvalidLogin.Error(), authErr.Error())
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify Auth
|
||||
validateErr := authObj.ValidateSecret(payload.Secret)
|
||||
if validateErr != nil {
|
||||
logger.Debug("%s: %s", errors.ErrInvalidLogin.Error(), validateErr.Error())
|
||||
// Sleep for 1 second to prevent brute force password guessing
|
||||
time.Sleep(time.Second)
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if response, err := njwt.Generate(&userObj, false); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
} else {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RefreshToken an existing token by given them a new one with the same claims
|
||||
// Route: GET /tokens
|
||||
func RefreshToken() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO: Use your own methods to verify an existing user is
|
||||
// able to refresh their token and then give them a new one
|
||||
userObj, _ := user.GetByEmail("jc@jc21.com")
|
||||
if response, err := njwt.Generate(&userObj, false); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
} else {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewSSEToken will generate and return a very short lived token for
|
||||
// use by the /sse/* endpoint. It requires an app token to generate this
|
||||
// Route: POST /tokens/sse
|
||||
func NewSSEToken() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.Context().Value(c.UserIDCtxKey).(uint)
|
||||
|
||||
// Find user
|
||||
userObj, userErr := user.GetByID(userID)
|
||||
if userErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrInvalidLogin.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if userObj.IsDisabled {
|
||||
h.ResultErrorJSON(w, r, http.StatusUnauthorized, errors.ErrUserDisabled.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if response, err := njwt.Generate(&userObj, true); err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
|
||||
} else {
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
c "npm/internal/api/context"
|
||||
h "npm/internal/api/http"
|
||||
@ -17,6 +18,13 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type setAuthModel struct {
|
||||
// The json tags are required, as the change password form decodes into this object
|
||||
Type string `json:"type"`
|
||||
Secret string `json:"secret"`
|
||||
CurrentSecret string `json:"current_secret"`
|
||||
}
|
||||
|
||||
// GetUsers returns all users
|
||||
// Route: GET /users
|
||||
func GetUsers() func(http.ResponseWriter, *http.Request) {
|
||||
@ -188,7 +196,7 @@ func CreateUser() func(http.ResponseWriter, *http.Request) {
|
||||
// newUser has been saved, now save their auth
|
||||
if newUser.Auth.Secret != "" && newUser.Auth.ID == 0 {
|
||||
newUser.Auth.UserID = newUser.ID
|
||||
if newUser.Auth.Type == auth.TypePassword {
|
||||
if newUser.Auth.Type == auth.TypeLocal {
|
||||
err = newUser.Auth.SetPassword(newUser.Auth.Secret)
|
||||
if err != nil {
|
||||
logger.Error("SetPasswordError", err)
|
||||
@ -247,3 +255,79 @@ func getUserIDFromRequest(r *http.Request) (uint, bool, error) {
|
||||
}
|
||||
return userID, self, nil
|
||||
}
|
||||
|
||||
// SetAuth sets a auth method. This can be used for "me" and `2` for example
|
||||
// Route: POST /users/:userID/auth
|
||||
func SetAuth() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
|
||||
|
||||
var newAuth setAuthModel
|
||||
err := json.Unmarshal(bodyBytes, &newAuth)
|
||||
if err != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
userID, isSelf, userIDErr := getUserIDFromRequest(r)
|
||||
if userIDErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, userIDErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Load user
|
||||
thisUser, thisUserErr := user.GetByID(userID)
|
||||
if thisUserErr == gorm.ErrRecordNotFound {
|
||||
h.NotFound(w, r)
|
||||
return
|
||||
} else if thisUserErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, thisUserErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if thisUser.IsSystem {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, "Cannot set password for system user", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Load existing auth for user
|
||||
userAuth, userAuthErr := auth.GetByUserIDType(userID, newAuth.Type)
|
||||
if userAuthErr != nil {
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, userAuthErr.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if isSelf {
|
||||
// confirm that the current_secret given is valid for the one stored in the database
|
||||
validateErr := userAuth.ValidateSecret(newAuth.CurrentSecret)
|
||||
if validateErr != nil {
|
||||
logger.Debug("%s: %s", "Password change: current password was incorrect", validateErr.Error())
|
||||
// Sleep for 1 second to prevent brute force password guessing
|
||||
time.Sleep(time.Second)
|
||||
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrCurrentPasswordInvalid.Error(), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if newAuth.Type == auth.TypeLocal {
|
||||
err := userAuth.SetPassword(newAuth.Secret)
|
||||
if err != nil {
|
||||
logger.Error("SetPasswordError", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
if err = userAuth.Save(); err != nil {
|
||||
logger.Error("AuthSaveError", err)
|
||||
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "Unable to save Authentication for User", nil)
|
||||
return
|
||||
}
|
||||
|
||||
userAuth.Secret = ""
|
||||
|
||||
// todo: add to audit-log
|
||||
|
||||
h.ResultResponseJSON(w, r, http.StatusOK, userAuth)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user