Commit 4c25d50c authored by Lee Brown's avatar Lee Brown

fix where, auth use request arg

parent 0471af92
......@@ -55,7 +55,7 @@ func (p *Project) Find(ctx context.Context, w http.ResponseWriter, r *http.Reque
if err != nil {
return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
}
req.Where = &where
req.Where = where
req.Args = args
}
......
......@@ -42,6 +42,10 @@ func (c *Signup) Signup(ctx context.Context, w http.ResponseWriter, r *http.Requ
// Claims are optional as authentication is not required ATM for this method.
claims, _ := auth.ClaimsFromContext(ctx)
// Hack to allow custom validation to be handled by business logic package.
ctx = context.WithValue(ctx, signup.KeyTagUniqueEmail, true)
ctx = context.WithValue(ctx, signup.KeyTagUniqueName, true)
var req signup.SignupRequest
if err := web.Decode(ctx, r, &req); err != nil {
if _, ok := errors.Cause(err).(*weberror.Error); !ok {
......
......@@ -60,7 +60,7 @@ func (u *User) Find(ctx context.Context, w http.ResponseWriter, r *http.Request,
if err != nil {
return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
}
req.Where = &where
req.Where = where
req.Args = args
}
......@@ -442,7 +442,9 @@ func (u *User) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http
return err
}
tkn, err := user_auth.SwitchAccount(ctx, u.MasterDB, u.TokenGenerator, claims, params["account_id"], sessionTtl, v.Now)
tkn, err := user_auth.SwitchAccount(ctx, u.MasterDB, u.TokenGenerator, claims, user_auth.SwitchAccountRequest{
AccountID: params["account_id"],
}, sessionTtl, v.Now)
if err != nil {
cause := errors.Cause(err)
switch cause {
......@@ -486,10 +488,16 @@ func (u *User) Token(ctx context.Context, w http.ResponseWriter, r *http.Request
return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusUnauthorized))
}
accountID := r.URL.Query().Get("account_id")
// Optional to include scope.
scope := r.URL.Query().Get("scope")
tkn, err := user_auth.Authenticate(ctx, u.MasterDB, u.TokenGenerator, email, pass, sessionTtl, v.Now, scope)
tkn, err := user_auth.Authenticate(ctx, u.MasterDB, u.TokenGenerator, user_auth.AuthenticateRequest{
Email: email,
Password: pass,
AccountID: accountID,
}, sessionTtl, v.Now, scope)
if err != nil {
cause := errors.Cause(err)
switch cause {
......@@ -505,30 +513,5 @@ func (u *User) Token(ctx context.Context, w http.ResponseWriter, r *http.Request
}
}
accountID := r.URL.Query().Get("account_id")
if accountID != "" && accountID != tkn.AccountID {
claims, err := u.TokenGenerator.ParseClaims(tkn.AccessToken)
if err != nil {
return err
}
tkn, err = user_auth.SwitchAccount(ctx, u.MasterDB, u.TokenGenerator, claims, accountID, sessionTtl, v.Now)
if err != nil {
cause := errors.Cause(err)
switch cause {
case user_auth.ErrAuthenticationFailure:
return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusUnauthorized))
default:
_, ok := cause.(validator.ValidationErrors)
if ok {
return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
}
return errors.Wrap(err, "switch account")
}
}
}
return web.RespondJson(ctx, w, tkn, http.StatusOK)
}
......@@ -55,7 +55,7 @@ func (u *UserAccount) Find(ctx context.Context, w http.ResponseWriter, r *http.R
if err != nil {
return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
}
req.Where = &where
req.Where = where
req.Args = args
}
......
......@@ -104,7 +104,11 @@ func TestAccountCRUDAdmin(t *testing.T) {
"status": map[string]interface{}{
"value": "active",
"title": "Active",
"options": []map[string]interface{}{{"selected": false, "title": "[Active Pending Disabled]", "value": "[active pending disabled]"}},
"options": []map[string]interface{}{
{"selected": true, "title": "Active", "value": "active"},
{"selected": false, "title": "Pending", "value": "pending"},
{"selected": false, "title": "Disabled", "value": "disabled"},
},
},
"signup_user_id": &tr.Account.SignupUserID.String,
}
......@@ -324,7 +328,11 @@ func TestAccountCRUDUser(t *testing.T) {
"status": map[string]interface{}{
"value": "active",
"title": "Active",
"options": []map[string]interface{}{{"selected": false, "title": "[Active Pending Disabled]", "value": "[active pending disabled]"}},
"options": []map[string]interface{}{
{"selected": true, "title": "Active", "value": "active"},
{"selected": false, "title": "Pending", "value": "pending"},
{"selected": false, "title": "Disabled", "value": "disabled"},
},
},
"signup_user_id": &tr.Account.SignupUserID.String,
}
......
......@@ -79,7 +79,7 @@ func TestProjectCRUDAdmin(t *testing.T) {
"updated_at": web.NewTimeResponse(ctx, actual.UpdatedAt.Value),
"id": actual.ID,
"account_id": req.AccountID,
"status": web.NewEnumResponse(ctx, "active", project.ProjectStatus_Values),
"status": web.NewEnumResponse(ctx, "active", project.ProjectStatus_ValuesInterface()...),
"created_at": web.NewTimeResponse(ctx, actual.CreatedAt.Value),
"name": req.Name,
}
......
......@@ -56,7 +56,10 @@ func newMockSignup() mockSignup {
}
expires := time.Now().UTC().Sub(s.User.CreatedAt) + time.Hour
tkn, err := user_auth.Authenticate(tests.Context(), test.MasterDB, authenticator, req.User.Email, req.User.Password, expires, now)
tkn, err := user_auth.Authenticate(tests.Context(), test.MasterDB, authenticator, user_auth.AuthenticateRequest{
Email: req.User.Email,
Password: req.User.Password,
}, expires, now)
if err != nil {
panic(err)
}
......@@ -141,7 +144,11 @@ func TestSignup(t *testing.T) {
"status": map[string]interface{}{
"value": "active",
"title": "Active",
"options": []map[string]interface{}{{"selected": false, "title": "[Active Pending Disabled]", "value": "[active pending disabled]"}},
"options": []map[string]interface{}{
{"selected": true, "title": "Active", "value": "active"},
{"selected": false, "title": "Pending", "value": "pending"},
{"selected": false, "title": "Disabled", "value": "disabled"},
},
},
"signup_user_id": &actual.Account.SignupUserID,
},
......
......@@ -95,7 +95,10 @@ func testMain(m *testing.M) int {
}
expires := time.Now().UTC().Sub(signup1.User.CreatedAt) + time.Hour
adminTkn, err := user_auth.Authenticate(tests.Context(), test.MasterDB, authenticator, signupReq1.User.Email, signupReq1.User.Password, expires, now)
adminTkn, err := user_auth.Authenticate(tests.Context(), test.MasterDB, authenticator, user_auth.AuthenticateRequest{
Email: signupReq1.User.Email,
Password: signupReq1.User.Password,
}, expires, now)
if err != nil {
panic(err)
}
......@@ -146,7 +149,10 @@ func testMain(m *testing.M) int {
panic(err)
}
userTkn, err := user_auth.Authenticate(tests.Context(), test.MasterDB, authenticator, usr.Email, userReq.Password, expires, now)
userTkn, err := user_auth.Authenticate(tests.Context(), test.MasterDB, authenticator, user_auth.AuthenticateRequest{
Email: usr.Email,
Password: userReq.Password,
}, expires, now)
if err != nil {
panic(err)
}
......
......@@ -89,13 +89,18 @@ func TestUserAccountCRUDAdmin(t *testing.T) {
}
created = actual
var roles []interface{}
for _, r := range req.Roles {
roles = append(roles, r)
}
expectedMap := map[string]interface{}{
"updated_at": web.NewTimeResponse(ctx, actual.UpdatedAt.Value),
//"id": actual.ID,
"account_id": req.AccountID,
"user_id": req.UserID,
"status": web.NewEnumResponse(ctx, "active", user_account.UserAccountStatus_Values),
"roles": req.Roles,
"status": web.NewEnumResponse(ctx, "active", user_account.UserAccountStatus_ValuesInterface()...),
"roles": web.NewEnumMultiResponse(ctx, roles, user_account.UserAccountRole_ValuesInterface()...),
"created_at": web.NewTimeResponse(ctx, actual.CreatedAt.Value),
}
......
......@@ -1419,7 +1419,7 @@ func TestUserToken(t *testing.T) {
// Test user token with invalid email.
{
expectedStatus := http.StatusUnauthorized
expectedStatus := http.StatusBadRequest
rt := requestTest{
fmt.Sprintf("Token %d using invalid email", expectedStatus),
......@@ -1434,7 +1434,9 @@ func TestUserToken(t *testing.T) {
t.Logf("\tTest: %s - %s %s", rt.name, rt.method, rt.url)
r := httptest.NewRequest(rt.method, rt.url, nil)
r.SetBasicAuth("invalid email.com", "some random password")
invalidEmail := "invalid email.com"
r.SetBasicAuth(invalidEmail, "some random password")
w := httptest.NewRecorder()
r.Header.Set("Content-Type", web.MIMEApplicationJSONCharsetUTF8)
......@@ -1456,8 +1458,17 @@ func TestUserToken(t *testing.T) {
expected := weberror.ErrorResponse{
StatusCode: expectedStatus,
Error: http.StatusText(expectedStatus),
Details: user_auth.ErrAuthenticationFailure.Error(),
Error: "Field validation error",
Fields: []weberror.FieldError{
{
Field: "email",
Value: invalidEmail,
Tag: "email",
Error: "email must be a valid email address",
Display: "email must be a valid email address",
},
},
Details: actual.Details,
StackTrace: actual.StackTrace,
}
......
......@@ -110,9 +110,8 @@ func (h *Projects) Index(ctx context.Context, w http.ResponseWriter, r *http.Req
}
loadFunc := func(ctx context.Context, sorting string, fields []datatable.DisplayField) (resp [][]datatable.ColumnValue, err error) {
whereFilter := "account_id = ?"
res, err := project.Find(ctx, claims, h.MasterDB, project.ProjectFindRequest{
Where: &whereFilter,
Where: "account_id = ?",
Args: []interface{}{claims.Audience},
Order: strings.Split(sorting, ","),
})
......
......@@ -209,6 +209,8 @@ func (h *User) ResetPassword(ctx context.Context, w http.ResponseWriter, r *http
// ResetConfirm handles changing a users password after they have clicked on the link emailed.
func (h *User) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
resetHash := params["hash"]
ctxValues, err := webcontext.ContextValues(ctx)
if err != nil {
return err
......@@ -217,31 +219,36 @@ func (h *User) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http.
//
req := new(user.UserResetConfirmRequest)
data := make(map[string]interface{})
f := func() error {
f := func() (bool, error) {
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
return err
return false, err
}
decoder := schema.NewDecoder()
if err := decoder.Decode(req, r.PostForm); err != nil {
return err
return false, err
}
// Append the query param value to the request.
req.ResetHash = params["hash"]
req.ResetHash = resetHash
u, err := user.ResetConfirm(ctx, h.MasterDB, *req, h.SecretKey, ctxValues.Now)
if err != nil {
switch errors.Cause(err) {
case user.ErrResetExpired:
webcontext.SessionFlashError(ctx,
"Reset Expired",
"The reset has expired.")
return false, nil
default:
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return nil
return false, nil
} else {
return err
return false, err
}
}
}
......@@ -249,34 +256,51 @@ func (h *User) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http.
// Authenticated the user. Probably should use the default session TTL from UserLogin.
token, err := user_auth.Authenticate(ctx, h.MasterDB, h.Authenticator, u.Email, req.Password, time.Hour, ctxValues.Now)
if err != nil {
switch errors.Cause(err) {
case account.ErrForbidden:
return web.RespondError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden))
default:
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return nil
return false, nil
} else {
return err
}
return false, err
}
}
// Add the token to the users session.
err = handleSessionToken(ctx, h.MasterDB, w, r, token)
if err != nil {
return err
return false, err
}
// Redirect the user to the dashboard.
http.Redirect(w, r, "/", http.StatusFound)
return true, nil
}
return nil
_, err = user.ParseResetHash(ctx, h.SecretKey, resetHash, ctxValues.Now)
if err != nil {
switch errors.Cause(err) {
case user.ErrResetExpired:
webcontext.SessionFlashError(ctx,
"Reset Expired",
"The reset has expired.")
return false, nil
default:
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return false, nil
} else {
return false, err
}
}
}
if err := f(); err != nil {
return false, nil
}
end, err := f()
if err != nil {
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
} else if end {
return nil
}
data["form"] = req
......@@ -572,9 +596,8 @@ func (h *User) VirtualLogin(ctx context.Context, w http.ResponseWriter, r *http.
return nil
}
usrAccFilter := "account_id = ?"
usrAccs, err := user_account.Find(ctx, claims, h.MasterDB, user_account.UserAccountFindRequest{
Where: &usrAccFilter,
Where: "account_id = ?",
Args: []interface{}{claims.Audience},
})
if err != nil {
......@@ -597,9 +620,9 @@ func (h *User) VirtualLogin(ctx context.Context, w http.ResponseWriter, r *http.
userPhs = append(userPhs, "?")
}
usrFilter := fmt.Sprintf("id IN (%s)", strings.Join(userPhs, ", "))
users, err := user.Find(ctx, claims, h.MasterDB, user.UserFindRequest{
Where: &usrFilter,
Where: fmt.Sprintf("id IN (%s)",
strings.Join(userPhs, ", ")),
Args: userIDs,
})
if err != nil {
......
......@@ -52,12 +52,18 @@ func urlUsersUpdate(userID string) string {
return fmt.Sprintf("/users/%s/update", userID)
}
// UserLoginRequest extends the AuthenicateRequest with the RememberMe flag.
// UserCreateRequest extends the UserCreateRequest with a list of roles.
type UserCreateRequest struct {
user.UserCreateRequest
Roles user_account.UserAccountRoles `json:"roles" validate:"required,dive,oneof=admin user" enums:"admin,user" swaggertype:"array,string" example:"admin"`
}
// UserUpdateRequest extends the UserUpdateRequest with a list of roles.
type UserUpdateRequest struct {
user.UserUpdateRequest
Roles user_account.UserAccountRoles `json:"roles" validate:"required,dive,oneof=admin user" enums:"admin,user" swaggertype:"array,string" example:"admin"`
}
// Index handles listing all the users for the current account.
func (h *Users) Index(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
......@@ -198,8 +204,6 @@ func (h *Users) Create(ctx context.Context, w http.ResponseWriter, r *http.Reque
}
decoder := schema.NewDecoder()
decoder.IgnoreUnknownKeys(true)
if err := decoder.Decode(req, r.PostForm); err != nil {
return false, err
}
......@@ -279,11 +283,11 @@ func (h *Users) Create(ctx context.Context, w http.ResponseWriter, r *http.Reque
return err
}
var roleValues []interface{}
for _, v := range user_account.UserAccountRole_Values {
roleValues = append(roleValues, string(v))
var selectedRoles []interface{}
for _, r := range req.Roles {
selectedRoles = append(selectedRoles, r.String())
}
data["roles"] = web.NewEnumResponse(ctx, nil, roleValues...)
data["roles"] = web.NewEnumMultiResponse(ctx, selectedRoles, user_account.UserAccountRole_ValuesInterface()...)
data["form"] = req
......@@ -389,7 +393,7 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
}
//
req := new(user.UserUpdateRequest)
req := new(UserUpdateRequest)
data := make(map[string]interface{})
f := func() (bool, error) {
if r.Method == http.MethodPost {
......@@ -400,13 +404,45 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
decoder := schema.NewDecoder()
decoder.IgnoreUnknownKeys(true)
if err := decoder.Decode(req, r.PostForm); err != nil {
return false, err
}
req.ID = userID
err = user.Update(ctx, claims, h.MasterDB, *req, ctxValues.Now)
// Bypass the uniq check on email here for the moment, it will be caught before the user_account is
// created by user.Create.
ctx = context.WithValue(ctx, webcontext.KeyTagUnique, true)
// Validate the request.
err = webcontext.Validator().StructCtx(ctx, req)
if err != nil {
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return false, nil
} else {
return false, err
}
}
err = user.Update(ctx, claims, h.MasterDB, req.UserUpdateRequest, ctxValues.Now)
if err != nil {
switch errors.Cause(err) {
default:
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return false, nil
} else {
return false, err
}
}
}
if req.Roles != nil {
err = user_account.Update(ctx, claims, h.MasterDB, user_account.UserAccountUpdateRequest{
UserID: userID,
AccountID: claims.Audience,
Roles: &req.Roles,
}, ctxValues.Now)
if err != nil {
switch errors.Cause(err) {
default:
......@@ -418,6 +454,7 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
}
}
}
}
if r.PostForm.Get("Password") != "" {
pwdReq := new(user.UserUpdatePasswordRequest)
......@@ -469,11 +506,20 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
return err
}
usrAcc, err := user_account.Read(ctx, claims, h.MasterDB, user_account.UserAccountReadRequest{
UserID: userID,
AccountID: claims.Audience,
})
if err != nil {
return err
}
if req.ID == "" {
req.FirstName = &usr.FirstName
req.LastName = &usr.LastName
req.Email = &usr.Email
req.Timezone = usr.Timezone
req.Roles = usrAcc.Roles
}
data["user"] = usr.Response(ctx)
......@@ -483,9 +529,15 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
return err
}
var selectedRoles []interface{}
for _, r := range req.Roles {
selectedRoles = append(selectedRoles, r.String())
}
data["roles"] = web.NewEnumMultiResponse(ctx, selectedRoles, user_account.UserAccountRole_ValuesInterface()...)
data["form"] = req
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(user.UserUpdateRequest{})); ok {
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(UserUpdateRequest{})); ok {
data["userValidationDefaults"] = verr.(*weberror.Error)
}
......
......@@ -28,7 +28,6 @@
{{ template "validation-error" . }}
<form class="user" method="post" novalidate>
<input type="hidden" name="ResetHash" value="{{ $.form.ResetHash }}" />
<div class="form-group row">
<div class="col-sm-6 mb-3 mb-sm-0">
<input type="password" class="form-control form-control-user {{ ValidationFieldClass $.validationErrors "Password" }}" name="Password" value="{{ $.form.Password }}" placeholder="Password" required>
......
......@@ -8,78 +8,84 @@
<h1 class="h3 mb-0 text-gray-800">Update User</h1>
</div>
<form class="user" method="post" novalidate>
<div class="card shadow">
<div class="card-body">
<div class="row mb-2">
<div class="col-12">
<h4 class="card-title">User Details</h4>
</div>
</div>
<div class="row mb-2">
<div class="col-md-6">
<div class="form-group">
<label for="inputFirstName">First Name</label>
<input type="text" class="form-control {{ ValidationFieldClass $.validationErrors "FirstName" }}" placeholder="enter first name" name="FirstName" value="{{ .form.FirstName }}" required>
{{template "invalid-feedback" dict "validationDefaults" $.userValidationDefaults "validationErrors" $.validationErrors "fieldName" "FirstName" }}
<input type="text"
class="form-control {{ ValidationFieldClass $.validationErrors "UserUpdateRequest.FirstName" }}"
placeholder="enter first name" name="FirstName" value="{{ .form.FirstName }}" required>
{{template "invalid-feedback" dict "fieldName" "UserUpdateRequest.FirstName" "validationDefaults" $.userValidationDefaults "validationErrors" $.validationErrors }}
</div>
<div class="form-group">
<label for="inputLastName">Last Name</label>
<input type="text" class="form-control {{ ValidationFieldClass $.validationErrors "LastName" }}" placeholder="enter last name" name="LastName" value="{{ .form.LastName }}" required>
{{template "invalid-feedback" dict "validationDefaults" $.userValidationDefaults "validationErrors" $.validationErrors "fieldName" "LastName" }}
<input type="text"
class="form-control {{ ValidationFieldClass $.validationErrors "UserUpdateRequest.LastName" }}"
placeholder="enter last name" name="LastName" value="{{ .form.LastName }}" required>
{{template "invalid-feedback" dict "fieldName" "UserUpdateRequest.LastName" "validationDefaults" $.userValidationDefaults "validationErrors" $.validationErrors }}
</div>
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text" class="form-control {{ ValidationFieldClass $.validationErrors "Email" }}" placeholder="enter email" name="Email" value="{{ .form.Email }}" required>
{{template "invalid-feedback" dict "validationDefaults" $.userValidationDefaults "validationErrors" $.validationErrors "fieldName" "Email" }}
<input type="text"
class="form-control {{ ValidationFieldClass $.validationErrors "UserUpdateRequest.Email" }}"
placeholder="enter email" name="Email" value="{{ .form.Email }}" required>
{{template "invalid-feedback" dict "fieldName" "UserUpdateRequest.Email" "validationDefaults" $.userValidationDefaults "validationErrors" $.validationErrors }}
</div>
<div class="form-group">
<label for="inputRoles">Roles</label>
<span class="help-block "><small>- Select at least one role.</small></span>
{{ range $r := .roles }}
<div class="form-check">
<input class="form-check-input {{ ValidationFieldClass $.validationErrors "Roles" }}"
type="checkbox" name="Roles"
value="{{ $r.Value }}" id="inputRole{{ $r.Value }}"
{{ if $r.Selected }}checked="checked"{{ end }}>
<label class="form-check-label" for="inputRole{{ $r.Value }}">
{{ $r.Title }}
</label>
</div>
{{ end }}
{{template "invalid-feedback" dict "fieldName" "Roles" "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors }}
</div>
<div class="form-group">
<label for="inputTimezone">Timezone</label>
<select class="form-control {{ ValidationFieldClass $.validationErrors "Timezone" }}" name="Timezone">
<select class="form-control {{ ValidationFieldClass $.validationErrors "UserUpdateRequest.Timezone" }}" name="Timezone">
<option value="">Not set</option>
{{ range $idx, $t := .timezones }}
<option value="{{ $t }}" {{ if CmpString $t $.form.Timezone }}selected="selected"{{ end }}>{{ $t }}</option>
{{ end }}
</select>
{{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "Timezone" }}
{{template "invalid-feedback" dict "fieldName" "UserUpdateRequest.Timezone" "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors }}
</div>
</div>
</div>
<div class="row">
<div class="col">
<input id="btnSubmit" type="submit" name="action" value="Save" class="btn btn-primary"/>
<input id="btnSubmit" type="submit" value="Save" class="btn btn-primary"/>
<a href="/users/{{ .user.ID }}" class="ml-2 btn btn-secondary" >Cancel</a>
</div>
</div>
</div>
</div>
</form>
<form class="user" method="post" novalidate>
<div class="card mt-4">
<div class="card-body">
<div class="row mb-2">
<div class="col-12">
<h4 class="card-title">Change Password</h4>
<p><small><b>Optional</b>. You can change the users' password by specifying a new one below. Otherwise leave the fields empty.</small></p>
</div>
</div>
<div class="row mb-2">
<div class="col-md-6">
<div class="form-group">
......@@ -102,7 +108,6 @@
</div>
</div>
</div>
</form>
{{end}}
{{define "js"}}
......
......@@ -63,13 +63,13 @@
<small>Role</small><br/>
{{ if .userAccount }}
<b>
{{ range $r := .userAccount.Roles }}
{{ if eq $r "admin" }}
<span class="text-pink"><i class="far fa-kiss-wink-heart mr-1"></i>{{ $r }}</span>
{{ range $r := .userAccount.Roles }}{{ if $r.Selected }}
{{ if eq $r.Value "admin" }}
<span class="text-pink"><i class="far fa-kiss-wink-heart mr-1"></i>{{ $r.Title }}</span>
{{else}}
<span class="text-purple"><i class="far fa-user-circle mr-1"></i>{{ $r }}</span>
<span class="text-purple"><i class="far fa-user-circle mr-1"></i>{{ $r.Title }}</span>
{{end}}
{{ end }}
{{ end }}{{ end }}
</b>
{{ end }}
</p>
......
......@@ -153,8 +153,8 @@ func selectQuery() *sqlbuilder.SelectBuilder {
func Find(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req AccountFindRequest) (Accounts, error) {
query := selectQuery()
if req.Where != nil {
query.Where(query.And(*req.Where))
if req.Where != "" {
query.Where(query.And(req.Where))
}