Commit 900cfcf7 authored by Lee Brown's avatar Lee Brown

fixed redirects to handle saving the session when set

parent 7909dc4c
......@@ -209,13 +209,8 @@ func (h *Account) Update(ctx context.Context, w http.ResponseWriter, r *http.Req
webcontext.SessionFlashSuccess(ctx,
"Account Updated",
"Account profile successfully updated.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, "/account", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/account", http.StatusFound)
}
acc, err := account.ReadByID(ctx, claims, h.MasterDB, claims.Audience)
......
......@@ -203,13 +203,8 @@ func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Re
webcontext.SessionFlashSuccess(ctx,
"Project Created",
"Project successfully created.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, urlProjectsView(usr.ID), http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, urlProjectsView(usr.ID), http.StatusFound)
}
return false, nil
......@@ -266,13 +261,8 @@ func (h *Projects) View(ctx context.Context, w http.ResponseWriter, r *http.Requ
webcontext.SessionFlashSuccess(ctx,
"Project Archive",
"Project successfully archive.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, urlProjectsIndex(), http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, urlProjectsIndex(), http.StatusFound)
}
}
......@@ -347,13 +337,8 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re
webcontext.SessionFlashSuccess(ctx,
"Project Updated",
"Project successfully updated.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, urlProjectsView(req.ID), http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, urlProjectsView(req.ID), http.StatusFound)
}
return false, nil
......
......@@ -40,12 +40,10 @@ func (h *Root) indexDashboard(ctx context.Context, w http.ResponseWriter, r *htt
// indexDefault loads the root index page when a user has no authentication.
func (u *Root) indexDefault(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
return u.Renderer.Render(ctx, w, r, tmplLayoutSite, "site-index.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, nil)
}
// indexDefault loads the root index page when a user has no authentication.
// SitePage loads the page with the layout for site instead of the app base.
func (u *Root) SitePage(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
var tmpName string
......@@ -63,18 +61,15 @@ func (u *Root) SitePage(ctx context.Context, w http.ResponseWriter, r *http.Requ
case "/legal/terms":
tmpName = "legal-terms.gohtml"
default:
http.Redirect(w, r, "/", http.StatusFound)
return nil
return web.Redirect(ctx, w, r, "/", http.StatusFound)
}
return u.Renderer.Render(ctx, w, r, tmplLayoutSite, tmpName, web.MIMETextHTMLCharsetUTF8, http.StatusOK, nil)
}
// IndexHtml redirects /index.html to the website root page.
func (u *Root) IndexHtml(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
http.Redirect(w, r, "/", http.StatusMovedPermanently)
return nil
return web.Redirect(ctx, w, r, "/", http.StatusMovedPermanently)
}
// RobotHandler returns a robots.txt response.
......
......@@ -86,14 +86,9 @@ func (h *Signup) Step1(ctx context.Context, w http.ResponseWriter, r *http.Reque
webcontext.SessionFlashSuccess(ctx,
"Thank you for Joining",
"You workflow will be a breeze starting today.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
// Redirect the user to the dashboard.
http.Redirect(w, r, "/", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/", http.StatusFound)
}
return false, nil
......@@ -103,6 +98,10 @@ func (h *Signup) Step1(ctx context.Context, w http.ResponseWriter, r *http.Reque
if err != nil {
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
} else if end {
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return err
}
return nil
}
......
......@@ -112,8 +112,7 @@ func (h *User) Login(ctx context.Context, w http.ResponseWriter, r *http.Request
}
// Redirect the user to the dashboard.
http.Redirect(w, r, redirectUri, http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, redirectUri, http.StatusFound)
}
return false, nil
......@@ -148,9 +147,7 @@ func (h *User) Logout(ctx context.Context, w http.ResponseWriter, r *http.Reques
}
// Redirect the user to the root page.
http.Redirect(w, r, "/", http.StatusFound)
return nil
return web.Redirect(ctx, w, r, "/", http.StatusFound)
}
// ResetPassword allows a user to perform forgot password.
......@@ -281,8 +278,7 @@ func (h *User) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http.
}
// Redirect the user to the dashboard.
http.Redirect(w, r, "/", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/", http.StatusFound)
}
_, err = user.ParseResetHash(ctx, h.SecretKey, resetHash, ctxValues.Now)
......@@ -432,13 +428,8 @@ func (h *User) Update(ctx context.Context, w http.ResponseWriter, r *http.Reques
webcontext.SessionFlashSuccess(ctx,
"Profile Updated",
"User profile successfully updated.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, "/user", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/user", http.StatusFound)
}
return false, nil
......@@ -584,16 +575,8 @@ func (h *User) VirtualLogin(ctx context.Context, w http.ResponseWriter, r *http.
fmt.Sprintf("You are now virtually logged into user %s.",
usr.Response(ctx).Name))
// Write the session to the client.
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
// Redirect the user to the dashboard with the new credentials.
http.Redirect(w, r, "/", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/", http.StatusFound)
}
return false, nil
......@@ -724,9 +707,7 @@ func (h *User) VirtualLogout(ctx context.Context, w http.ResponseWriter, r *http
}
// Redirect the user to the dashboard with the new credentials.
http.Redirect(w, r, "/", http.StatusFound)
return nil
return web.Redirect(ctx, w, r, "/", http.StatusFound)
}
// VirtualLogin handles switching the scope of the context to another user.
......@@ -800,16 +781,8 @@ func (h *User) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http
fmt.Sprintf("You are now logged into account %s.",
acc.Response(ctx).Name))
// Write the session to the client.
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
// Redirect the user to the dashboard with the new credentials.
http.Redirect(w, r, "/", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/", http.StatusFound)
}
return false, nil
......
......@@ -259,13 +259,8 @@ func (h *Users) Create(ctx context.Context, w http.ResponseWriter, r *http.Reque
webcontext.SessionFlashSuccess(ctx,
"User Created",
"User successfully created.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, urlUsersView(usr.ID), http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, urlUsersView(usr.ID), http.StatusFound)
}
return false, nil
......@@ -333,13 +328,8 @@ func (h *Users) View(ctx context.Context, w http.ResponseWriter, r *http.Request
webcontext.SessionFlashSuccess(ctx,
"User Archive",
"User successfully archive.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, urlUsersIndex(), http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, urlUsersIndex(), http.StatusFound)
}
}
......@@ -483,13 +473,8 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
webcontext.SessionFlashSuccess(ctx,
"User Updated",
"User successfully updated.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, urlUsersView(req.ID), http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, urlUsersView(req.ID), http.StatusFound)
}
return false, nil
......@@ -607,13 +592,7 @@ func (h *Users) Invite(ctx context.Context, w http.ResponseWriter, r *http.Reque
"No users were invited.")
}
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
http.Redirect(w, r, urlUsersIndex(), http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, urlUsersIndex(), http.StatusFound)
}
return false, nil
......@@ -652,7 +631,7 @@ func (h *Users) InviteAccept(ctx context.Context, w http.ResponseWriter, r *http
}
//
req := new(invite.AcceptInviteRequest)
req := new(invite.AcceptInviteUserRequest)
data := make(map[string]interface{})
f := func() (bool, error) {
......@@ -670,30 +649,33 @@ func (h *Users) InviteAccept(ctx context.Context, w http.ResponseWriter, r *http
// Append the query param value to the request.
req.InviteHash = inviteHash
hash, err := invite.AcceptInvite(ctx, h.MasterDB, *req, h.SecretKey, ctxValues.Now)
hash, err := invite.AcceptInviteUser(ctx, h.MasterDB, *req, h.SecretKey, ctxValues.Now)
if err != nil {
switch errors.Cause(err) {
case invite.ErrInviteExpired:
webcontext.SessionFlashError(ctx,
"Invite Expired",
"The invite has expired.")
return false, nil
case invite.ErrUserAccountActive:
webcontext.SessionFlashError(ctx,
"User already Active",
"The user already is already active for the account. Try to login or use forgot password.")
http.Redirect(w, r, "/user/login", http.StatusFound)
return true, nil
case invite.ErrInviteUserPasswordSet:
"The user is already is already active for the account. Try to login or use forgot password.")
return true, web.Redirect(ctx, w, r, "/user/login", http.StatusFound)
case invite.ErrNoPendingInvite:
webcontext.SessionFlashError(ctx,
"Invite already Accepted",
"Invite Accepted",
"The invite has already been accepted. Try to login or use forgot password.")
http.Redirect(w, r, "/user/login", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/user/login", http.StatusFound)
case user_account.ErrNotFound:
return false, err
case invite.ErrNoPendingInvite:
return false, err
default:
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
......@@ -732,36 +714,57 @@ func (h *Users) InviteAccept(ctx context.Context, w http.ResponseWriter, r *http
}
// Redirect the user to the dashboard.
http.Redirect(w, r, "/", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/", http.StatusFound)
}
hash, err := invite.ParseInviteHash(ctx, h.SecretKey, inviteHash, ctxValues.Now)
usrAcc, err := invite.AcceptInvite(ctx, h.MasterDB, invite.AcceptInviteRequest{
InviteHash: inviteHash,
}, h.SecretKey, ctxValues.Now)
if err != nil {
switch errors.Cause(err) {
case invite.ErrInviteExpired:
webcontext.SessionFlashError(ctx,
"Invite Expired",
"The invite has expired.")
return false, nil
case invite.ErrInviteUserPasswordSet:
return true, web.Redirect(ctx, w, r, "/user/login", http.StatusFound)
case invite.ErrUserAccountActive:
webcontext.SessionFlashError(ctx,
"Invite already Accepted",
"User already Active",
"The user is already is already active for the account. Try to login or use forgot password.")
return true, web.Redirect(ctx, w, r, "/user/login", http.StatusFound)
case invite.ErrNoPendingInvite:
webcontext.SessionFlashError(ctx,
"Invite Accepted",
"The invite has already been accepted. Try to login or use forgot password.")
http.Redirect(w, r, "/user/login", http.StatusFound)
return true, nil
return true, web.Redirect(ctx, w, r, "/user/login", http.StatusFound)
case user_account.ErrNotFound:
return false, err
default:
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return false, nil
} else {
return false, err
}
}
} else if usrAcc.Status == user_account.UserAccountStatus_Active {
webcontext.SessionFlashError(ctx,
"Invite Accepted",
"The invite has been accepted. Login to continue.")
return true, web.Redirect(ctx, w, r, "/user/login", http.StatusFound)
}
// Read user by ID with no claims.
usr, err := user.ReadByID(ctx, auth.Claims{}, h.MasterDB, hash.UserID)
usr, err := user.ReadByID(ctx, auth.Claims{}, h.MasterDB, usrAcc.UserID)
if err != nil {
return false, err
}
......@@ -791,7 +794,7 @@ func (h *Users) InviteAccept(ctx context.Context, w http.ResponseWriter, r *http
data["form"] = req
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(invite.AcceptInviteRequest{})); ok {
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(invite.AcceptInviteUserRequest{})); ok {
data["validationDefaults"] = verr.(*weberror.Error)
}
......
......@@ -55,13 +55,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.Options }}{{ 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>
......
......@@ -21,7 +21,7 @@
<label for="selectRoles">Roles</label>
<select class="form-control {{ ValidationFieldClass $.validationErrors "Roles" }}"
id="selectRoles" name="Roles" multiple="multiple">
{{ range $t := .roles }}
{{ range $t := .roles.Options }}
<option value="{{ $t.Value }}" {{ if $t.Selected }}selected="selected"{{ end }}>{{ $t.Title }}</option>
{{ end }}
</select>
......
......@@ -67,7 +67,7 @@
<small>Role</small><br/>
{{ if .userAccount }}
<b>
{{ range $r := .userAccount.Roles }}{{ if $r.Selected }}
{{ range $r := .userAccount.Roles.Options }}{{ 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}}
......
......@@ -117,12 +117,20 @@ func NewEnumResponse(ctx context.Context, value interface{}, options ...interfac
}
// EnumResponse is a response friendly format for displaying a multi select enum.
type EnumMultiResponse []EnumOption
type EnumMultiResponse struct {
Values []string `json:"values" example:"active_etc"`
Options []EnumOption `json:"options,omitempty"`
}
// NewEnumMultiResponse returns a display friendly format for a multi enum field.
func NewEnumMultiResponse(ctx context.Context, selected []interface{}, options ...interface{}) EnumMultiResponse {
var er EnumMultiResponse
for _, s := range selected {
selStr := fmt.Sprintf("%s", s)
er.Values = append(er.Values, selStr)
}
for _, opt := range options {
optStr := fmt.Sprintf("%s", opt)
opt := EnumOption{
......@@ -137,7 +145,7 @@ func NewEnumMultiResponse(ctx context.Context, selected []interface{}, options .
}
}
er = append(er, opt)
er.Options = append(er.Options, opt)
}
return er
......
......@@ -256,3 +256,16 @@ func StaticHandler(ctx context.Context, w http.ResponseWriter, r *http.Request,
return nil
}
// Redirect ensures the session is flushed to the browser before the redirect is issued.
func Redirect(ctx context.Context, w http.ResponseWriter, r *http.Request, url string, code int) error {
if sess := webcontext.ContextSession(ctx); sess != nil {
if err := sess.Save(r, w); err != nil {
return err
}
}
http.Redirect(w, r, url, code)
return nil
}
......@@ -26,9 +26,6 @@ var (
// ErrUserAccountActive occurs when the user already has an active user_account entry.
ErrUserAccountActive = errors.New("User already active.")
// ErrInviteUserPasswordSet occurs when the the reset hash exceeds the expiration.
ErrInviteUserPasswordSet = errors.New("User password set")
)
// SendUserInvites sends emails to the users inviting them to join an account.
......@@ -181,7 +178,7 @@ func SendUserInvites(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, r
}
// AcceptInvite updates the user using the provided invite hash.
func AcceptInvite(ctx context.Context, dbConn *sqlx.DB, req AcceptInviteRequest, secretKey string, now time.Time) (*InviteHash, error) {
func AcceptInvite(ctx context.Context, dbConn *sqlx.DB, req AcceptInviteRequest, secretKey string, now time.Time) (*user_account.UserAccount, error) {
span, ctx := tracer.StartSpanFromContext(ctx, "internal.user_account.invite.AcceptInvite")
defer span.Finish()
......@@ -193,7 +190,7 @@ func AcceptInvite(ctx context.Context, dbConn *sqlx.DB, req AcceptInviteRequest,
return nil, err
}
hash, err := ParseInviteHash(ctx, secretKey, req.InviteHash, now)
hash, err := ParseInviteHash(ctx, req.InviteHash, secretKey, now)
if err != nil {
return nil, err
}
......@@ -216,24 +213,86 @@ func AcceptInvite(ctx context.Context, dbConn *sqlx.DB, req AcceptInviteRequest,
AccountID: hash.AccountID,
})
if err != nil {
return nil, nil
return nil, err
}
// Ensure the entry has the status of invited.
if usrAcc.Status != user_account.UserAccountStatus_Invited {
// If the entry is already active
if usrAcc.Status == user_account.UserAccountStatus_Active {
return hash, errors.WithStack(ErrUserAccountActive)
return usrAcc, errors.WithStack(ErrUserAccountActive)
}
return nil, errors.WithStack(ErrNoPendingInvite)
return usrAcc, errors.WithStack(ErrNoPendingInvite)
}
// If the user already has a password set, then just update the user_account entry to status of active.
// The user will need to login and should not be auto-authenticated.
if len(u.PasswordHash) > 0 {
// Do not update the password for a user that already has a password set.
return nil, errors.WithStack(ErrInviteUserPasswordSet)
usrAcc.Status = user_account.UserAccountStatus_Active
err = user_account.Update(ctx, auth.Claims{}, dbConn, user_account.UserAccountUpdateRequest{
UserID: usrAcc.UserID,
AccountID: usrAcc.AccountID,
Status: &usrAcc.Status,
}, now)
if err != nil {
return nil, err
}
}
return usrAcc, nil
}
// AcceptInviteUser updates the user using the provided invite hash.
func AcceptInviteUser(ctx context.Context, dbConn *sqlx.DB, req AcceptInviteUserRequest, secretKey string, now time.Time) (*user_account.UserAccount, error) {
span, ctx := tracer.StartSpanFromContext(ctx, "internal.user_account.invite.AcceptInviteUser")
defer span.Finish()
v := webcontext.Validator()
// Validate the request.
err := v.StructCtx(ctx, req)
if err != nil {
return nil, err
}
hash, err := ParseInviteHash(ctx, req.InviteHash, secretKey, now)
if err != nil {
return nil, err
}
u, err := user.Read(ctx, auth.Claims{}, dbConn,
user.UserReadRequest{ID: hash.UserID, IncludeArchived: true})
if err != nil {
return nil, err
}
if u.ArchivedAt != nil && !u.ArchivedAt.Time.IsZero() {
err = user.Restore(ctx, auth.Claims{}, dbConn, user.UserRestoreRequest{ID: hash.UserID}, now)
if err != nil {
return nil, err
}
}
usrAcc, err := user_account.Read(ctx, auth.Claims{}, dbConn, user_account.UserAccountReadRequest{
UserID: hash.UserID,
AccountID: hash.AccountID,
})
if err != nil {
return nil, err
}
// Ensure the entry has the status of invited.
if usrAcc.Status != user_account.UserAccountStatus_Invited {
// If the entry is already active
if usrAcc.Status == user_account.UserAccountStatus_Active {
return usrAcc, errors.WithStack(ErrUserAccountActive)
}
return nil, errors.WithStack(ErrNoPendingInvite)
}
// These two calls, user.Update and user.UpdatePassword should probably be in a transaction!
// These three calls, user.Update, user.UpdatePassword, and user_account.Update
// should probably be in a transaction!
err = user.Update(ctx, auth.Claims{}, dbConn, user.UserUpdateRequest{
ID: hash.UserID,
Email: &req.Email,
......@@ -254,15 +313,15 @@ func AcceptInvite(ctx context.Context, dbConn *sqlx.DB, req AcceptInviteRequest,
return nil, err
}
activeStatus := user_account.UserAccountStatus_Active
usrAcc.Status = user_account.UserAccountStatus_Active
err = user_account.Update(ctx, auth.Claims{}, dbConn, user_account.UserAccountUpdateRequest{
UserID: usrAcc.UserID,
AccountID: usrAcc.AccountID,
Status: &activeStatus,
Status: &usrAcc.Status,
}, now)
if err != nil {
return nil, err
}
return hash, nil
return usrAcc, nil
}
......@@ -148,13 +148,13 @@ func TestSendUserInvites(t *testing.T) {
// Ensure validation is working by trying ResetConfirm with an empty request.
{
expectedErr := errors.New("Key: 'AcceptInviteRequest.invite_hash' Error:Field validation for 'invite_hash' failed on the 'required' tag\n" +
"Key: 'AcceptInviteRequest.email' Error:Field validation for 'email' failed on the 'required' tag\n" +
"Key: 'AcceptInviteRequest.first_name' Error:Field validation for 'first_name' failed on the 'required' tag\n" +
"Key: 'AcceptInviteRequest.last_name' Error:Field validation for 'last_name' failed on the 'required' tag\n" +
"Key: 'AcceptInviteRequest.password' Error:Field validation for 'password' failed on the 'required' tag\n" +
"Key: 'AcceptInviteRequest.password_confirm' Error:Field validation for 'password_confirm' failed on the 'required' tag")
_, err = AcceptInvite(ctx, test.MasterDB, AcceptInviteRequest{}, secretKey, now)
expectedErr := errors.New("Key: 'AcceptInviteUserRequest.invite_hash' Error:Field validation for 'invite_hash' failed on the 'required' tag\n" +
"Key: 'AcceptInviteUserRequest.email' Error:Field validation for 'email' failed on the 'required' tag\n" +
"Key: 'AcceptInviteUserRequest.first_name' Error:Field validation for 'first_name' failed on the 'required' tag\n" +
"Key: 'AcceptInviteUserRequest.last_name' Error:Field validation for 'last_name' failed on the 'required' tag\n" +
"Key: 'AcceptInviteUserRequest.password' Error:Field validation for 'password' failed on the 'required' tag\n" +
"Key: 'AcceptInviteUserRequest.password_confirm' Error:Field validation for 'password_confirm' failed on the 'required' tag")
_, err = AcceptInviteUser(ctx, test.MasterDB, AcceptInviteUserRequest{}, secretKey, now)
if err == nil {
t.Logf("\t\tWant: %+v", expectedErr)
t.Fatalf("\t%s\tResetConfirm failed.", tests.Failed)
......@@ -174,7 +174,7 @@ func TestSendUserInvites(t *testing.T) {
// Ensure the TTL is enforced.
{
newPass := uuid.NewRandom().String()
_, err = AcceptInvite(ctx, test.MasterDB, AcceptInviteRequest{
_, err = AcceptInviteUser(ctx, test.MasterDB, AcceptInviteUserRequest{
InviteHash: inviteHashes[0],
Email: inviteEmails[0],
FirstName: "Foo",
......@@ -194,7 +194,7 @@ func TestSendUserInvites(t *testing.T) {
for idx, inviteHash := range inviteHashes {
newPass := uuid.NewRandom().String()
hash, err := AcceptInvite(ctx, test.MasterDB, AcceptInviteRequest{
hash, err := AcceptInviteUser(ctx, test.MasterDB, AcceptInviteUserRequest{
InviteHash: inviteHash,
Email: inviteEmails[idx],
FirstName: "Foo",
......@@ -227,7 +227,7 @@ func TestSendUserInvites(t *testing.T) {
// Ensure the reset hash does not work after its used.
{
newPass := uuid.NewRandom().String()
_, err = AcceptInvite(ctx, test.MasterDB, AcceptInviteRequest{
_, err = AcceptInviteUser(ctx, test.MasterDB, AcceptInviteUserRequest{
InviteHash: inviteHashes[0],
Email: inviteEmails[0],
FirstName: "Foo",
......@@ -237,7 +237,7 @@ func TestSendUserInvites(t *testing.T) {
}, secretKey, now)
if errors.Cause(err) != ErrUserAccountActive {
t.Logf("\t\tGot : %+v", errors.Cause(err))
t.Logf("\t\tWant: %+v", ErrInviteUserPasswordSet)
t.Logf("\t\tWant: %+v", ErrUserAccountActive)
t.Fatalf("\t%s\tInviteAccept verify reuse failed.", tests.Failed)
}
t.Logf("\t%s\tInviteAccept verify reuse disabled ok.", tests.Success)
......
......@@ -32,6 +32,11 @@ type InviteHash struct {
// AcceptInviteRequest defines the fields need to complete an invite request.
type AcceptInviteRequest struct {
InviteHash string `json:"invite_hash" validate:"required" example:"d69bdef7-173f-4d29-b52c-3edc60baf6a2"`
}
// AcceptInviteUserRequest defines the fields need to complete an invite request.
type AcceptInviteUserRequest struct {
InviteHash string `json:"invite_hash" validate:"required" example:"d69bdef7-173f-4d29-b52c-3edc60baf6a2"`
Email string `json:"email" validate:"required,email" example:"[email protected]"`
FirstName string `json:"first_name" validate:"required" example:"Gabi"`
......@@ -67,12 +72,12 @@ func NewInviteHash(ctx context.Context, secretKey, userID, accountID, requestIp
}
// ParseInviteHash extracts the details encrypted in the hash string.
func ParseInviteHash(ctx context.Context, secretKey string, str string, now time.Time) (*InviteHash, error) {
func ParseInviteHash(ctx context.Context, encrypted, secretKey string, now time.Time) (*InviteHash, error) {
crypto, err := symcrypto.New(secretKey)
if err != nil {
return nil, errors.WithStack(err)
}
hashStr, err := crypto.Decrypt(str)
hashStr, err := crypto.Decrypt(encrypted)
if err != nil {