1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-08 23:56:37 +02:00

800 lines
22 KiB
Go
Raw Normal View History

2019-08-04 23:24:30 -08:00
package handlers
import (
"context"
"fmt"
"geeks-accelerator/oss/saas-starter-kit/internal/geonames"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
2019-08-05 01:13:03 -08:00
"geeks-accelerator/oss/saas-starter-kit/internal/platform/datatable"
2019-08-04 23:24:30 -08:00
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
project_routes "geeks-accelerator/oss/saas-starter-kit/internal/project-routes"
"geeks-accelerator/oss/saas-starter-kit/internal/user"
"geeks-accelerator/oss/saas-starter-kit/internal/user_account"
2019-08-05 13:33:18 -08:00
"geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite"
2019-08-05 14:32:45 -08:00
"geeks-accelerator/oss/saas-starter-kit/internal/user_auth"
2019-08-05 13:33:18 -08:00
"github.com/dustin/go-humanize/english"
2019-08-04 23:24:30 -08:00
"github.com/gorilla/schema"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis"
2019-08-05 13:33:18 -08:00
"net/http"
"strings"
2019-08-05 14:32:45 -08:00
"time"
2019-08-04 23:24:30 -08:00
)
// Users represents the Users API method handler set.
type Users struct {
MasterDB *sqlx.DB
2019-08-05 01:13:03 -08:00
Redis *redis.Client
2019-08-04 23:24:30 -08:00
Renderer web.Renderer
Authenticator *auth.Authenticator
ProjectRoutes project_routes.ProjectRoutes
NotifyEmail notify.Email
SecretKey string
}
2019-08-05 01:13:03 -08:00
func urlUsersIndex() string {
return fmt.Sprintf("/users")
}
func urlUsersCreate() string {
return fmt.Sprintf("/users/create")
}
func urlUsersView(userID string) string {
2019-08-04 23:24:30 -08:00
return fmt.Sprintf("/users/%s", userID)
}
2019-08-05 01:13:03 -08:00
func urlUsersUpdate(userID string) string {
return fmt.Sprintf("/users/%s/update", userID)
}
2019-08-05 17:12:28 -08:00
// UserCreateRequest extends the UserCreateRequest with a list of roles.
2019-08-05 01:13:03 -08:00
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"`
}
2019-08-05 17:12:28 -08:00
// 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"`
}
2019-08-04 23:24:30 -08:00
// 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 {
claims, err := auth.ClaimsFromContext(ctx)
if err != nil {
return err
}
2019-08-05 14:32:45 -08:00
statusOpts := web.NewEnumResponse(ctx, nil, user_account.UserAccountStatus_ValuesInterface()...)
2019-08-04 23:24:30 -08:00
statusFilterItems := []datatable.FilterOptionItem{}
for _, opt := range statusOpts.Options {
statusFilterItems = append(statusFilterItems, datatable.FilterOptionItem{
Display: opt.Title,
Value: opt.Value,
})
}
fields := []datatable.DisplayField{
datatable.DisplayField{Field: "id", Title: "ID", Visible: false, Searchable: true, Orderable: true, Filterable: false},
datatable.DisplayField{Field: "name", Title: "User", Visible: true, Searchable: true, Orderable: true, Filterable: true, FilterPlaceholder: "filter Name"},
datatable.DisplayField{Field: "status", Title: "Status", Visible: true, Searchable: true, Orderable: true, Filterable: true, FilterPlaceholder: "All Statuses", FilterItems: statusFilterItems},
datatable.DisplayField{Field: "updated_at", Title: "Last Updated", Visible: true, Searchable: true, Orderable: true, Filterable: false},
datatable.DisplayField{Field: "created_at", Title: "Created", Visible: true, Searchable: true, Orderable: true, Filterable: false},
}
mapFunc := func(q *user_account.User, cols []datatable.DisplayField) (resp []datatable.ColumnValue, err error) {
for i := 0; i < len(cols); i++ {
col := cols[i]
var v datatable.ColumnValue
switch col.Field {
case "id":
v.Value = fmt.Sprintf("%d", q.ID)
case "name":
v.Value = q.Name
2019-08-05 01:13:03 -08:00
v.Formatted = fmt.Sprintf("<a href='%s'>%s</a>", urlUsersView(q.ID), v.Value)
case "status":
v.Value = q.Status.String()
var subStatusClass string
var subStatusIcon string
switch q.Status {
case user_account.UserAccountStatus_Active:
subStatusClass = "text-green"
subStatusIcon = "far fa-dot-circle"
case user_account.UserAccountStatus_Invited:
subStatusClass = "text-blue"
subStatusIcon = "far fa-unicorn"
case user_account.UserAccountStatus_Disabled:
subStatusClass = "text-orange"
subStatusIcon = "far fa-circle"
}
v.Formatted = fmt.Sprintf("<span class='cell-font-status %s'><i class='%s'></i>%s</span>", subStatusClass, subStatusIcon, web.EnumValueTitle(v.Value))
2019-08-04 23:24:30 -08:00
case "created_at":
dt := web.NewTimeResponse(ctx, q.CreatedAt)
v.Value = dt.Local
v.Formatted = fmt.Sprintf("<span class='cell-font-date'>%s</span>", v.Value)
case "updated_at":
dt := web.NewTimeResponse(ctx, q.UpdatedAt)
v.Value = dt.Local
v.Formatted = fmt.Sprintf("<span class='cell-font-date'>%s</span>", v.Value)
default:
return resp, errors.Errorf("Failed to map value for %s.", col.Field)
}
resp = append(resp, v)
}
return resp, nil
}
loadFunc := func(ctx context.Context, sorting string, fields []datatable.DisplayField) (resp [][]datatable.ColumnValue, err error) {
res, err := user_account.UserFindByAccount(ctx, claims, h.MasterDB, user_account.UserFindByAccountRequest{
2019-08-05 01:13:03 -08:00
AccountID: claims.Audience,
Order: strings.Split(sorting, ","),
2019-08-04 23:24:30 -08:00
})
if err != nil {
return resp, err
}
for _, a := range res {
l, err := mapFunc(a, fields)
if err != nil {
return resp, errors.Wrapf(err, "Failed to map user for display.")
}
resp = append(resp, l)
}
return resp, nil
}
dt, err := datatable.New(ctx, w, r, h.Redis, fields, loadFunc)
if err != nil {
return err
}
if dt.HasCache() {
return nil
}
if ok, err := dt.Render(); ok {
if err != nil {
return err
}
return nil
}
data := map[string]interface{}{
2019-08-05 01:13:03 -08:00
"datatable": dt.Response(),
"urlUsersCreate": urlUsersCreate(),
2019-08-04 23:24:30 -08:00
}
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-index.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
}
2019-08-05 01:13:03 -08:00
// Create handles creating a new user for the account.
func (h *Users) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
ctxValues, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
2019-08-04 23:24:30 -08:00
2019-08-05 01:13:03 -08:00
claims, err := auth.ClaimsFromContext(ctx)
if err != nil {
return err
}
//
req := new(UserCreateRequest)
2019-08-04 23:24:30 -08:00
data := make(map[string]interface{})
2019-08-05 01:13:03 -08:00
f := func() (bool, error) {
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
return false, err
}
2019-08-04 23:24:30 -08:00
2019-08-05 01:13:03 -08:00
decoder := schema.NewDecoder()
if err := decoder.Decode(req, r.PostForm); err != nil {
return false, err
}
2019-08-04 23:24:30 -08:00
2019-08-05 01:13:03 -08:00
// 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)
2019-08-04 23:24:30 -08:00
2019-08-05 01:13:03 -08:00
// 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
}
}
2019-08-04 23:24:30 -08:00
2019-08-05 01:13:03 -08:00
usr, err := user.Create(ctx, claims, h.MasterDB, req.UserCreateRequest, 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
}
}
}
uaStatus := user_account.UserAccountStatus_Active
_, err = user_account.Create(ctx, claims, h.MasterDB, user_account.UserAccountCreateRequest{
UserID: usr.ID,
AccountID: claims.Audience,
Roles: req.Roles,
Status: &uaStatus,
}, 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
}
}
}
// Display a success message to the user.
webcontext.SessionFlashSuccess(ctx,
"User Created",
"User successfully created.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
2019-08-04 23:24:30 -08:00
}
2019-08-05 01:13:03 -08:00
http.Redirect(w, r, urlUsersView(usr.ID), http.StatusFound)
return true, nil
2019-08-04 23:24:30 -08:00
}
2019-08-05 01:13:03 -08:00
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 {
2019-08-04 23:24:30 -08:00
return nil
}
2019-08-05 01:13:03 -08:00
data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB)
if err != nil {
return err
}
2019-08-05 17:12:28 -08:00
var selectedRoles []interface{}
for _, r := range req.Roles {
selectedRoles = append(selectedRoles, r.String())
2019-08-05 01:13:03 -08:00
}
2019-08-05 17:12:28 -08:00
data["roles"] = web.NewEnumMultiResponse(ctx, selectedRoles, user_account.UserAccountRole_ValuesInterface()...)
2019-08-05 01:13:03 -08:00
data["form"] = req
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(UserCreateRequest{})); ok {
data["validationDefaults"] = verr.(*weberror.Error)
}
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-create.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
}
// View handles displaying a user.
func (h *Users) View(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
userID := params["user_id"]
ctxValues, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
claims, err := auth.ClaimsFromContext(ctx)
if err != nil {
return err
}
data := make(map[string]interface{})
f := func() (bool, error) {
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
return false, err
}
switch r.PostForm.Get("action") {
case "archive":
err = user.Archive(ctx, claims, h.MasterDB, user.UserArchiveRequest{
ID: userID,
}, ctxValues.Now)
if err != nil {
return false, err
}
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 false, nil
}
end, err := f()
if err != nil {
2019-08-04 23:24:30 -08:00
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
2019-08-05 01:13:03 -08:00
} else if end {
return nil
}
usr, err := user.ReadByID(ctx, claims, h.MasterDB, userID)
if err != nil {
return err
2019-08-04 23:24:30 -08:00
}
2019-08-05 01:13:03 -08:00
data["user"] = usr.Response(ctx)
usrAccs, err := user_account.FindByUserID(ctx, claims, h.MasterDB, userID, false)
if err != nil {
return err
}
for _, usrAcc := range usrAccs {
if usrAcc.AccountID == claims.Audience {
data["userAccount"] = usrAcc.Response(ctx)
break
}
}
data["urlUsersUpdate"] = urlUsersUpdate(userID)
data["urlUserVirtualLogin"] = urlUserVirtualLogin(userID)
2019-08-05 01:13:03 -08:00
2019-08-04 23:24:30 -08:00
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-view.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
}
// Update handles updating a user for the account.
func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
2019-08-05 01:13:03 -08:00
userID := params["user_id"]
2019-08-04 23:24:30 -08:00
ctxValues, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
claims, err := auth.ClaimsFromContext(ctx)
if err != nil {
return err
}
//
2019-08-05 17:12:28 -08:00
req := new(UserUpdateRequest)
2019-08-04 23:24:30 -08:00
data := make(map[string]interface{})
f := func() (bool, error) {
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
return false, err
}
decoder := schema.NewDecoder()
decoder.IgnoreUnknownKeys(true)
if err := decoder.Decode(req, r.PostForm); err != nil {
return false, err
}
2019-08-05 01:13:03 -08:00
req.ID = userID
2019-08-04 23:24:30 -08:00
2019-08-05 17:12:28 -08:00
// 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)
2019-08-04 23:24:30 -08:00
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
}
}
}
2019-08-05 17:12:28 -08:00
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
}
}
}
}
2019-08-04 23:24:30 -08:00
if r.PostForm.Get("Password") != "" {
pwdReq := new(user.UserUpdatePasswordRequest)
if err := decoder.Decode(pwdReq, r.PostForm); err != nil {
return false, err
}
2019-08-05 01:13:03 -08:00
pwdReq.ID = userID
2019-08-04 23:24:30 -08:00
err = user.UpdatePassword(ctx, claims, h.MasterDB, *pwdReq, 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
}
}
}
}
// Display a success message to the user.
webcontext.SessionFlashSuccess(ctx,
"User Updated",
"User successfully updated.")
err = webcontext.ContextSession(ctx).Save(r, w)
if err != nil {
return false, err
}
2019-08-05 01:13:03 -08:00
http.Redirect(w, r, urlUsersView(req.ID), http.StatusFound)
2019-08-04 23:24:30 -08:00
return true, 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
}
2019-08-05 01:13:03 -08:00
usr, err := user.ReadByID(ctx, claims, h.MasterDB, userID)
2019-08-04 23:24:30 -08:00
if err != nil {
return err
}
2019-08-05 17:12:28 -08:00
usrAcc, err := user_account.Read(ctx, claims, h.MasterDB, user_account.UserAccountReadRequest{
UserID: userID,
AccountID: claims.Audience,
})
if err != nil {
return err
}
2019-08-04 23:24:30 -08:00
if req.ID == "" {
req.FirstName = &usr.FirstName
req.LastName = &usr.LastName
req.Email = &usr.Email
2019-08-05 14:32:45 -08:00
req.Timezone = usr.Timezone
2019-08-05 17:12:28 -08:00
req.Roles = usrAcc.Roles
2019-08-04 23:24:30 -08:00
}
data["user"] = usr.Response(ctx)
data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB)
if err != nil {
return err
}
2019-08-05 17:12:28 -08:00
var selectedRoles []interface{}
for _, r := range req.Roles {
selectedRoles = append(selectedRoles, r.String())
}
data["roles"] = web.NewEnumMultiResponse(ctx, selectedRoles, user_account.UserAccountRole_ValuesInterface()...)
2019-08-04 23:24:30 -08:00
data["form"] = req
2019-08-05 17:12:28 -08:00
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(UserUpdateRequest{})); ok {
2019-08-04 23:24:30 -08:00
data["userValidationDefaults"] = verr.(*weberror.Error)
}
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(user.UserUpdatePasswordRequest{})); ok {
data["passwordValidationDefaults"] = verr.(*weberror.Error)
}
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-update.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
}
2019-08-05 13:27:23 -08:00
// Invite handles sending invites for users to the account.
func (h *Users) Invite(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
ctxValues, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
claims, err := auth.ClaimsFromContext(ctx)
if err != nil {
return err
}
//
req := new(invite.SendUserInvitesRequest)
data := make(map[string]interface{})
f := func() (bool, error) {
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
return false, err
}
decoder := schema.NewDecoder()
if err := decoder.Decode(req, r.PostForm); err != nil {
return false, err
}
req.UserID = claims.Subject
req.AccountID = claims.Audience
res, err := invite.SendUserInvites(ctx, claims, h.MasterDB, h.ProjectRoutes.UserInviteAccept, h.NotifyEmail, *req, h.SecretKey, 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
}
}
}
// Display a success message to the user.
inviteCnt := len(res)
if inviteCnt > 0 {
webcontext.SessionFlashSuccess(ctx,
fmt.Sprintf("%s Invited", english.PluralWord(inviteCnt, "User", "")),
fmt.Sprintf("%s successfully invited. %s been sent to them to join your account.",
english.Plural(inviteCnt, "user", ""),
english.PluralWord(inviteCnt, "An email has", "Emails have")))
} else {
webcontext.SessionFlashWarning(ctx,
"Users not Invited",
"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 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
}
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(invite.SendUserInvitesRequest{})); ok {
data["validationDefaults"] = verr.(*weberror.Error)
}
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-invite.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
}
// Invite handles sending invites for users to the account.
func (h *Users) InviteAccept(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
inviteHash := params["hash"]
ctxValues, err := webcontext.ContextValues(ctx)
if err != nil {
return err
}
//
req := new(invite.AcceptInviteRequest)
data := make(map[string]interface{})
f := func() (bool, error) {
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
return false, err
}
decoder := schema.NewDecoder()
if err := decoder.Decode(req, r.PostForm); err != nil {
return false, err
}
// Append the query param value to the request.
req.InviteHash = inviteHash
2019-08-05 17:23:56 -08:00
hash, err := invite.AcceptInvite(ctx, h.MasterDB, *req, h.SecretKey, ctxValues.Now)
2019-08-05 13:27:23 -08:00
if err != nil {
switch errors.Cause(err) {
2019-08-05 14:32:45 -08:00
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:
webcontext.SessionFlashError(ctx,
"Invite already 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
case user_account.ErrNotFound:
return false, err
case invite.ErrNoPendingInvite:
return false, err
2019-08-05 13:27:23 -08:00
default:
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return false, nil
} else {
return false, err
}
}
}
2019-08-05 14:32:45 -08:00
// Load the user without any claims applied.
2019-08-05 17:23:56 -08:00
usr, err := user.ReadByID(ctx, auth.Claims{}, h.MasterDB, hash.UserID)
2019-08-05 14:32:45 -08:00
if err != nil {
return false, err
}
2019-08-05 13:27:23 -08:00
// Authenticated the user. Probably should use the default session TTL from UserLogin.
2019-08-05 17:23:56 -08:00
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)
2019-08-05 13:27:23 -08:00
if err != nil {
2019-08-05 14:32:45 -08:00
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return false, nil
} else {
return false, err
2019-08-05 13:27:23 -08:00
}
}
// Add the token to the users session.
err = handleSessionToken(ctx, h.MasterDB, w, r, token)
if err != nil {
return false, err
}
// Redirect the user to the dashboard.
http.Redirect(w, r, "/", http.StatusFound)
return true, nil
}
2019-08-05 14:32:45 -08:00
hash, err := invite.ParseInviteHash(ctx, h.SecretKey, inviteHash, ctxValues.Now)
2019-08-05 13:27:23 -08:00
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:
webcontext.SessionFlashError(ctx,
"Invite already 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
default:
2019-08-05 14:32:45 -08:00
if verr, ok := weberror.NewValidationError(ctx, err); ok {
data["validationErrors"] = verr.(*weberror.Error)
return false, nil
} else {
return false, err
}
2019-08-05 13:27:23 -08:00
}
}
// Read user by ID with no claims.
usr, err := user.ReadByID(ctx, auth.Claims{}, h.MasterDB, hash.UserID)
if err != nil {
return false, err
}
data["user"] = usr.Response(ctx)
if req.Email == "" {
req.FirstName = usr.FirstName
req.LastName = usr.LastName
req.Email = usr.Email
2019-08-05 14:32:45 -08:00
req.Timezone = usr.Timezone
2019-08-05 13:27:23 -08:00
}
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
}
2019-08-05 14:32:45 -08:00
data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB)
if err != nil {
return err
}
2019-08-05 13:27:23 -08:00
data["form"] = req
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(invite.AcceptInviteRequest{})); ok {
data["validationDefaults"] = verr.(*weberror.Error)
}
2019-08-05 14:32:45 -08:00
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-invite-accept.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
2019-08-05 13:27:23 -08:00
}