diff --git a/cmd/web-api/handlers/project.go b/cmd/web-api/handlers/project.go index 7b49b35..4a0e09f 100644 --- a/cmd/web-api/handlers/project.go +++ b/cmd/web-api/handlers/project.go @@ -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 } diff --git a/cmd/web-api/handlers/signup.go b/cmd/web-api/handlers/signup.go index 49c3571..208ec43 100644 --- a/cmd/web-api/handlers/signup.go +++ b/cmd/web-api/handlers/signup.go @@ -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 { diff --git a/cmd/web-api/handlers/user.go b/cmd/web-api/handlers/user.go index 8457b1a..a695b9c 100644 --- a/cmd/web-api/handlers/user.go +++ b/cmd/web-api/handlers/user.go @@ -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) } diff --git a/cmd/web-api/handlers/user_account.go b/cmd/web-api/handlers/user_account.go index 849a52a..091fc2c 100644 --- a/cmd/web-api/handlers/user_account.go +++ b/cmd/web-api/handlers/user_account.go @@ -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 } diff --git a/cmd/web-api/tests/account_test.go b/cmd/web-api/tests/account_test.go index ffde8ed..918aa6c 100644 --- a/cmd/web-api/tests/account_test.go +++ b/cmd/web-api/tests/account_test.go @@ -102,9 +102,13 @@ func TestAccountCRUDAdmin(t *testing.T) { "address1": tr.Account.Address1, "city": tr.Account.City, "status": map[string]interface{}{ - "value": "active", - "title": "Active", - "options": []map[string]interface{}{{"selected": false, "title": "[Active Pending Disabled]", "value": "[active pending disabled]"}}, + "value": "active", + "title": "Active", + "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, } @@ -322,9 +326,13 @@ func TestAccountCRUDUser(t *testing.T) { "address1": tr.Account.Address1, "city": tr.Account.City, "status": map[string]interface{}{ - "value": "active", - "title": "Active", - "options": []map[string]interface{}{{"selected": false, "title": "[Active Pending Disabled]", "value": "[active pending disabled]"}}, + "value": "active", + "title": "Active", + "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, } diff --git a/cmd/web-api/tests/project_test.go b/cmd/web-api/tests/project_test.go index a42c749..fbefebd 100644 --- a/cmd/web-api/tests/project_test.go +++ b/cmd/web-api/tests/project_test.go @@ -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, } diff --git a/cmd/web-api/tests/signup_test.go b/cmd/web-api/tests/signup_test.go index 2456a03..686f2a7 100644 --- a/cmd/web-api/tests/signup_test.go +++ b/cmd/web-api/tests/signup_test.go @@ -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) } @@ -139,9 +142,13 @@ func TestSignup(t *testing.T) { "address1": req.Account.Address1, "city": req.Account.City, "status": map[string]interface{}{ - "value": "active", - "title": "Active", - "options": []map[string]interface{}{{"selected": false, "title": "[Active Pending Disabled]", "value": "[active pending disabled]"}}, + "value": "active", + "title": "Active", + "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, }, diff --git a/cmd/web-api/tests/tests_test.go b/cmd/web-api/tests/tests_test.go index 0edec3c..83608f5 100644 --- a/cmd/web-api/tests/tests_test.go +++ b/cmd/web-api/tests/tests_test.go @@ -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) } diff --git a/cmd/web-api/tests/user_account_test.go b/cmd/web-api/tests/user_account_test.go index 00f4ebb..e1c650c 100644 --- a/cmd/web-api/tests/user_account_test.go +++ b/cmd/web-api/tests/user_account_test.go @@ -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), } diff --git a/cmd/web-api/tests/user_test.go b/cmd/web-api/tests/user_test.go index 19c3902..d2778e6 100644 --- a/cmd/web-api/tests/user_test.go +++ b/cmd/web-api/tests/user_test.go @@ -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, } diff --git a/cmd/web-app/handlers/projects.go b/cmd/web-app/handlers/projects.go index 8dfa2d6..8aac2ba 100644 --- a/cmd/web-app/handlers/projects.go +++ b/cmd/web-app/handlers/projects.go @@ -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, ","), }) diff --git a/cmd/web-app/handlers/signup.go b/cmd/web-app/handlers/signup.go index 43a2a41..a25bd90 100644 --- a/cmd/web-app/handlers/signup.go +++ b/cmd/web-app/handlers/signup.go @@ -68,7 +68,10 @@ func (h *Signup) Step1(ctx context.Context, w http.ResponseWriter, r *http.Reque } // Authenticated the new user. - token, err := user_auth.Authenticate(ctx, h.MasterDB, h.Authenticator, req.User.Email, req.User.Password, time.Hour, ctxValues.Now) + token, err := user_auth.Authenticate(ctx, h.MasterDB, h.Authenticator, user_auth.AuthenticateRequest{ + Email: req.User.Email, + Password: req.User.Password, + }, time.Hour, ctxValues.Now) if err != nil { return false, err } diff --git a/cmd/web-app/handlers/user.go b/cmd/web-app/handlers/user.go index 2ad888d..119135a 100644 --- a/cmd/web-app/handlers/user.go +++ b/cmd/web-app/handlers/user.go @@ -76,7 +76,10 @@ func (h *User) Login(ctx context.Context, w http.ResponseWriter, r *http.Request } // Authenticated the user. - token, err := user_auth.Authenticate(ctx, h.MasterDB, h.Authenticator, req.Email, req.Password, sessionTTL, ctxValues.Now) + token, err := user_auth.Authenticate(ctx, h.MasterDB, h.Authenticator, user_auth.AuthenticateRequest{ + Email: req.Email, + Password: req.Password, + }, sessionTTL, ctxValues.Now) if err != nil { switch errors.Cause(err) { case user.ErrForbidden: @@ -213,6 +216,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 @@ -221,66 +226,91 @@ 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 } } } // 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) + token, err := user_auth.Authenticate(ctx, h.MasterDB, h.Authenticator, user_auth.AuthenticateRequest{ + Email: u.Email, + Password: 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 - } else { - return err - } + if verr, ok := weberror.NewValidationError(ctx, err); ok { + data["validationErrors"] = verr.(*weberror.Error) + return false, nil + } else { + 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 + } + } + } + + return false, nil } - if err := f(); err != 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 @@ -576,9 +606,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 { @@ -601,10 +630,10 @@ 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, - Args: userIDs, + Where: fmt.Sprintf("id IN (%s)", + strings.Join(userPhs, ", ")), + Args: userIDs, }) if err != nil { return err diff --git a/cmd/web-app/handlers/users.go b/cmd/web-app/handlers/users.go index 5cb347f..be36709 100644 --- a/cmd/web-app/handlers/users.go +++ b/cmd/web-app/handlers/users.go @@ -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 @@ -390,7 +394,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 { @@ -401,13 +405,27 @@ 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: @@ -420,6 +438,25 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque } } + 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: + if verr, ok := weberror.NewValidationError(ctx, err); ok { + data["validationErrors"] = verr.(*weberror.Error) + return false, nil + } else { + return false, err + } + } + } + } + if r.PostForm.Get("Password") != "" { pwdReq := new(user.UserUpdatePasswordRequest) @@ -470,11 +507,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) @@ -484,9 +530,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) } @@ -618,7 +670,7 @@ func (h *Users) InviteAccept(ctx context.Context, w http.ResponseWriter, r *http // Append the query param value to the request. req.InviteHash = inviteHash - userID, err := invite.AcceptInvite(ctx, h.MasterDB, *req, h.SecretKey, ctxValues.Now) + hash, err := invite.AcceptInvite(ctx, h.MasterDB, *req, h.SecretKey, ctxValues.Now) if err != nil { switch errors.Cause(err) { case invite.ErrInviteExpired: @@ -653,13 +705,17 @@ func (h *Users) InviteAccept(ctx context.Context, w http.ResponseWriter, r *http } // Load the user without any claims applied. - usr, err := user.ReadByID(ctx, auth.Claims{}, h.MasterDB, userID) + usr, err := user.ReadByID(ctx, auth.Claims{}, h.MasterDB, hash.UserID) if err != nil { return false, err } // Authenticated the user. Probably should use the default session TTL from UserLogin. - token, err := user_auth.Authenticate(ctx, h.MasterDB, h.Authenticator, usr.Email, req.Password, time.Hour, ctxValues.Now) + token, err := user_auth.Authenticate(ctx, h.MasterDB, h.Authenticator, user_auth.AuthenticateRequest{ + Email: usr.Email, + Password: req.Password, + AccountID: hash.AccountID, + }, time.Hour, ctxValues.Now) if err != nil { if verr, ok := weberror.NewValidationError(ctx, err); ok { data["validationErrors"] = verr.(*weberror.Error) diff --git a/cmd/web-app/templates/content/user-reset-confirm.gohtml b/cmd/web-app/templates/content/user-reset-confirm.gohtml index 471183b..c509e3c 100644 --- a/cmd/web-app/templates/content/user-reset-confirm.gohtml +++ b/cmd/web-app/templates/content/user-reset-confirm.gohtml @@ -28,16 +28,17 @@ {{ template "validation-error" . }}
-
- - {{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "Password" }} - + + {{template "invalid-feedback" dict "fieldName" "Password" "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors }}
- - {{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "PasswordConfirm" }} - + + {{template "invalid-feedback" dict "fieldName" "PasswordConfirm" "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors }}