1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 05:09:42 +02:00
documize/domain/user/endpoint.go

852 lines
22 KiB
Go
Raw Normal View History

2017-07-26 10:50:26 +01:00
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package user
import (
2017-07-26 20:03:23 +01:00
"database/sql"
2018-03-01 10:58:55 +00:00
"encoding/csv"
2017-07-26 20:03:23 +01:00
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
2018-02-22 18:14:12 +00:00
"strings"
2017-07-26 20:03:23 +01:00
2017-07-26 10:50:26 +01:00
"github.com/documize/community/core/env"
2017-07-26 20:03:23 +01:00
"github.com/documize/community/core/event"
"github.com/documize/community/core/request"
"github.com/documize/community/core/response"
"github.com/documize/community/core/secrets"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid"
2017-07-26 10:50:26 +01:00
"github.com/documize/community/domain"
2017-08-02 15:26:31 +01:00
"github.com/documize/community/domain/mail"
2017-08-27 16:39:09 +01:00
"github.com/documize/community/domain/organization"
2017-07-26 20:03:23 +01:00
"github.com/documize/community/model/account"
"github.com/documize/community/model/audit"
2018-03-01 19:14:27 +00:00
"github.com/documize/community/model/group"
2017-07-26 20:03:23 +01:00
"github.com/documize/community/model/user"
2017-07-26 10:50:26 +01:00
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
2017-07-26 20:03:23 +01:00
Store *domain.Store
2017-07-26 10:50:26 +01:00
}
2017-07-26 20:03:23 +01:00
// Add is the endpoint that enables an administrator to add a new user for their orgaisation.
func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
method := "user.Add"
2017-07-26 10:50:26 +01:00
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() {
response.WriteBadLicense(w)
}
2017-07-26 20:03:23 +01:00
if !ctx.Administrator {
2017-07-26 10:50:26 +01:00
response.WriteForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
userModel := user.User{}
2017-07-26 10:50:26 +01:00
err = json.Unmarshal(body, &userModel)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
// data validation
userModel.Email = strings.ToLower(strings.TrimSpace(userModel.Email))
userModel.Firstname = strings.TrimSpace(userModel.Firstname)
userModel.Lastname = strings.TrimSpace(userModel.Lastname)
userModel.Password = strings.TrimSpace(userModel.Password)
if len(userModel.Email) == 0 {
response.WriteMissingDataError(w, method, "email")
return
}
if len(userModel.Firstname) == 0 {
response.WriteMissingDataError(w, method, "firsrtname")
return
}
if len(userModel.Lastname) == 0 {
response.WriteMissingDataError(w, method, "lastname")
return
}
userModel.Initials = stringutil.MakeInitials(userModel.Firstname, userModel.Lastname)
requestedPassword := secrets.GenerateRandomPassword()
userModel.Salt = secrets.GenerateSalt()
userModel.Password = secrets.GeneratePassword(requestedPassword, userModel.Salt)
userModel.LastVersion = ctx.AppVersion
2017-07-26 10:50:26 +01:00
// only create account if not dupe
addUser := true
addAccount := true
var userID string
2017-07-26 20:03:23 +01:00
userDupe, err := h.Store.User.GetByEmail(ctx, userModel.Email)
2017-07-26 10:50:26 +01:00
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
if userModel.Email == userDupe.Email {
addUser = false
userID = userDupe.RefID
h.Runtime.Log.Info("Dupe user found, will not add " + userModel.Email)
}
2017-07-26 20:03:23 +01:00
ctx.Transaction, err = h.Runtime.Db.Beginx()
2017-07-26 10:50:26 +01:00
if err != nil {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
if addUser {
userID = uniqueid.Generate()
userModel.RefID = userID
2017-07-26 20:03:23 +01:00
err = h.Store.User.Add(ctx, userModel)
2017-07-26 10:50:26 +01:00
if err != nil {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
h.Runtime.Log.Info("Adding user")
} else {
2017-07-26 20:03:23 +01:00
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &userDupe)
2017-07-26 10:50:26 +01:00
for _, a := range userDupe.Accounts {
2017-07-26 20:03:23 +01:00
if a.OrgID == ctx.OrgID {
2017-07-26 10:50:26 +01:00
addAccount = false
h.Runtime.Log.Info("Dupe account found, will not add")
break
}
}
}
// set up user account for the org
if addAccount {
2017-07-26 20:03:23 +01:00
var a account.Account
2017-07-26 10:50:26 +01:00
a.RefID = uniqueid.Generate()
a.UserID = userID
2017-07-26 20:03:23 +01:00
a.OrgID = ctx.OrgID
2017-07-26 10:50:26 +01:00
a.Editor = true
a.Admin = false
a.Active = true
2017-07-26 20:03:23 +01:00
err = h.Store.Account.Add(ctx, a)
2017-07-26 10:50:26 +01:00
if err != nil {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
}
ctx.Transaction.Commit()
2017-07-26 10:50:26 +01:00
if addUser {
event.Handler().Publish(string(event.TypeAddUser))
2017-07-26 20:03:23 +01:00
h.Store.Audit.Record(ctx, audit.EventTypeUserAdd)
2017-07-26 10:50:26 +01:00
}
if addAccount {
event.Handler().Publish(string(event.TypeAddAccount))
2017-07-26 20:03:23 +01:00
h.Store.Audit.Record(ctx, audit.EventTypeAccountAdd)
2017-07-26 10:50:26 +01:00
}
// If we did not add user or give them access (account) then we error back
if !addUser && !addAccount {
response.WriteDuplicateError(w, method, "user")
return
}
// Invite new user
2017-07-26 20:03:23 +01:00
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
2017-07-26 10:50:26 +01:00
if err != nil {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
// Prepare invitation email (that contains SSO link)
if addUser && addAccount {
size := len(requestedPassword)
2017-07-26 20:03:23 +01:00
auth := fmt.Sprintf("%s:%s:%s", ctx.AppURL, userModel.Email, requestedPassword[:size])
2017-07-26 10:50:26 +01:00
encrypted := secrets.EncodeBase64([]byte(auth))
2017-07-26 20:03:23 +01:00
url := fmt.Sprintf("%s/%s", ctx.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted)))
2017-08-02 15:26:31 +01:00
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, ctx.AppURL))
2017-07-26 10:50:26 +01:00
} else {
2017-08-02 15:26:31 +01:00
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.InviteExistingUser(userModel.Email, inviter.Fullname(), ctx.GetAppURL(""))
2017-07-26 10:50:26 +01:00
h.Runtime.Log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email))
}
response.WriteJSON(w, userModel)
}
// GetOrganizationUsers is the endpoint that allows administrators to view the users in their organisation.
func (h *Handler) GetOrganizationUsers(w http.ResponseWriter, r *http.Request) {
2017-07-26 20:03:23 +01:00
method := "user.GetOrganizationUsers"
ctx := domain.GetRequestContext(r)
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
if !ctx.Administrator {
2017-07-26 10:50:26 +01:00
response.WriteForbiddenError(w)
return
}
2018-03-01 19:14:27 +00:00
filter := request.Query(r, "filter")
2017-07-26 20:03:23 +01:00
active, err := strconv.ParseBool(request.Query(r, "active"))
2017-07-26 10:50:26 +01:00
if err != nil {
active = false
}
2017-07-26 20:03:23 +01:00
u := []user.User{}
2017-07-26 10:50:26 +01:00
if active {
2017-07-26 20:03:23 +01:00
u, err = h.Store.User.GetActiveUsersForOrganization(ctx)
2017-07-26 10:50:26 +01:00
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
} else {
2018-03-01 19:14:27 +00:00
u, err = h.Store.User.GetUsersForOrganization(ctx, filter)
2017-07-26 10:50:26 +01:00
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
}
2018-03-01 19:14:27 +00:00
// prefetch all group membership records
groups, err := h.Store.Group.GetMembers(ctx)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
2017-07-26 10:50:26 +01:00
}
2018-03-01 19:14:27 +00:00
// for each user...
2017-07-26 10:50:26 +01:00
for i := range u {
2018-03-01 19:14:27 +00:00
// 1. attach user accounts
2017-07-26 20:03:23 +01:00
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &u[i])
2018-03-01 19:14:27 +00:00
// 2. attach user groups
u[i].Groups = []group.Record{}
for j := range groups {
if groups[j].UserID == u[i].RefID {
u[i].Groups = append(u[i].Groups, groups[j])
}
}
2017-07-26 10:50:26 +01:00
}
response.WriteJSON(w, u)
}
// GetSpaceUsers returns every user within a given space
func (h *Handler) GetSpaceUsers(w http.ResponseWriter, r *http.Request) {
method := "user.GetSpaceUsers"
2017-07-26 20:03:23 +01:00
ctx := domain.GetRequestContext(r)
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
var u []user.User
2017-07-26 10:50:26 +01:00
var err error
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
2017-07-26 10:50:26 +01:00
return
}
// Get user account as we need to know if user can see all users.
account, err := h.Store.Account.GetUserAccount(ctx, ctx.UserID)
2017-07-26 10:50:26 +01:00
if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-10-03 17:52:03 -04:00
// account.users == false means we restrict viewing to just space users
if account.Users {
// can see all users
2017-07-26 20:03:23 +01:00
u, err = h.Store.User.GetActiveUsersForOrganization(ctx)
if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err)
return
}
} else {
// send back existing space users
u, err = h.Store.User.GetSpaceUsers(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err)
return
}
2017-07-26 10:50:26 +01:00
}
if len(u) == 0 {
2017-07-26 20:03:23 +01:00
u = []user.User{}
2017-07-26 10:50:26 +01:00
}
response.WriteJSON(w, u)
}
2017-07-26 20:03:23 +01:00
// Get returns user specified by ID
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
method := "user.Get"
ctx := domain.GetRequestContext(r)
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
userID := request.Param(r, "userID")
2017-07-26 10:50:26 +01:00
if len(userID) == 0 {
response.WriteMissingDataError(w, method, "userId")
return
}
2017-07-26 20:03:23 +01:00
if userID != ctx.UserID {
2017-07-26 10:50:26 +01:00
response.WriteBadRequestError(w, method, "userId mismatch")
return
}
2017-07-26 20:03:23 +01:00
u, err := GetSecuredUser(ctx, *h.Store, ctx.OrgID, userID)
2017-07-26 10:50:26 +01:00
if err == sql.ErrNoRows {
2017-07-26 20:03:23 +01:00
response.WriteNotFoundError(w, method, ctx.UserID)
2017-07-26 10:50:26 +01:00
return
}
if err != nil {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
response.WriteJSON(w, u)
}
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
// Delete is the endpoint to delete a user specified by userID, the caller must be an Administrator.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "user.Delete"
ctx := domain.GetRequestContext(r)
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
userID := request.Param(r, "userID")
2017-07-26 10:50:26 +01:00
if len(userID) == 0 {
2017-07-26 20:03:23 +01:00
response.WriteMissingDataError(w, method, "userId")
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
if userID == ctx.UserID {
2017-07-26 10:50:26 +01:00
response.WriteBadRequestError(w, method, "cannot delete self")
return
}
var err error
2017-07-26 20:03:23 +01:00
ctx.Transaction, err = h.Runtime.Db.Beginx()
2017-07-26 10:50:26 +01:00
if err != nil {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
err = h.Store.User.DeactiveUser(ctx, userID)
2017-07-26 10:50:26 +01:00
if err != nil {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-09-15 11:08:05 +01:00
// remove all associated roles for this user
2017-09-18 17:53:42 +01:00
_, err = h.Store.Permission.DeleteUserPermissions(ctx, userID)
2017-07-26 10:50:26 +01:00
if err != nil {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
ctx.Transaction.Commit()
2017-07-26 20:03:23 +01:00
h.Store.Audit.Record(ctx, audit.EventTypeUserDelete)
2017-07-26 10:50:26 +01:00
event.Handler().Publish(string(event.TypeRemoveUser))
2017-07-26 20:03:23 +01:00
response.WriteEmpty(w)
2017-07-26 10:50:26 +01:00
}
2017-07-26 20:03:23 +01:00
// Update is the endpoint to update user information for the given userID.
2017-07-26 10:50:26 +01:00
// Note that unless they have admin privildges, a user can only update their own information.
// Also, only admins can update user roles in organisations.
2017-07-26 20:03:23 +01:00
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
method := "user.Update"
ctx := domain.GetRequestContext(r)
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
userID := request.Param(r, "userID")
2017-07-26 10:50:26 +01:00
if len(userID) == 0 {
response.WriteBadRequestError(w, method, "user id must be numeric")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
2017-07-26 20:03:23 +01:00
response.WriteBadRequestError(w, method, err.Error())
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
u := user.User{}
2017-07-26 10:50:26 +01:00
err = json.Unmarshal(body, &u)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
// can only update your own account unless you are an admin
2017-07-26 20:03:23 +01:00
if ctx.UserID != userID && !ctx.Administrator {
2017-07-26 10:50:26 +01:00
response.WriteForbiddenError(w)
return
}
// can only update your own account unless you are an admin
if len(u.Email) == 0 {
response.WriteMissingDataError(w, method, "email")
return
}
2017-07-26 20:03:23 +01:00
ctx.Transaction, err = h.Runtime.Db.Beginx()
2017-07-26 10:50:26 +01:00
if err != nil {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
u.RefID = userID
u.Initials = stringutil.MakeInitials(u.Firstname, u.Lastname)
2017-07-26 20:03:23 +01:00
err = h.Store.User.UpdateUser(ctx, u)
2017-07-26 10:50:26 +01:00
if err != nil {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
// Now we update user roles for this organization.
// That means we have to first find their account record
// for this organization.
2017-07-26 20:03:23 +01:00
a, err := h.Store.Account.GetUserAccount(ctx, userID)
2017-07-26 10:50:26 +01:00
if err != nil {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
a.Editor = u.Editor
a.Admin = u.Admin
a.Active = u.Active
a.Users = u.ViewUsers
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
err = h.Store.Account.UpdateAccount(ctx, a)
2017-07-26 10:50:26 +01:00
if err != nil {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
ctx.Transaction.Commit()
2017-07-26 10:50:26 +01:00
h.Store.Audit.Record(ctx, audit.EventTypeUserUpdate)
2017-07-26 20:03:23 +01:00
response.WriteEmpty(w)
2017-07-26 10:50:26 +01:00
}
2017-07-26 20:03:23 +01:00
// ChangePassword accepts password change from within the app.
func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
method := "user.ChangePassword"
ctx := domain.GetRequestContext(r)
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
userID := request.Param(r, "userID")
2017-07-26 10:50:26 +01:00
if len(userID) == 0 {
response.WriteMissingDataError(w, method, "user id")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
newPassword := string(body)
// can only update your own account unless you are an admin
if !ctx.Administrator && userID != ctx.UserID {
2017-07-26 10:50:26 +01:00
response.WriteForbiddenError(w)
return
}
2017-07-26 20:03:23 +01:00
ctx.Transaction, err = h.Runtime.Db.Beginx()
2017-07-26 10:50:26 +01:00
if err != nil {
response.WriteServerError(w, method, err)
return
}
2017-07-26 20:03:23 +01:00
u, err := h.Store.User.Get(ctx, userID)
2017-07-26 10:50:26 +01:00
if err != nil {
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
u.Salt = secrets.GenerateSalt()
2017-07-26 20:03:23 +01:00
err = h.Store.User.UpdateUserPassword(ctx, userID, u.Salt, secrets.GeneratePassword(newPassword, u.Salt))
2017-07-26 10:50:26 +01:00
if err != nil {
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
ctx.Transaction.Commit()
2017-07-26 20:03:23 +01:00
2017-07-26 10:50:26 +01:00
response.WriteEmpty(w)
}
2017-07-26 20:03:23 +01:00
// ForgotPassword initiates the change password procedure.
2017-07-26 10:50:26 +01:00
// Generates a reset token and sends email to the user.
// User has to click link in email and then provide a new password.
2017-07-26 20:03:23 +01:00
func (h *Handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
method := "user.ForgotPassword"
ctx := domain.GetRequestContext(r)
2017-08-27 16:39:09 +01:00
ctx.Subdomain = organization.GetSubdomainFromHost(r)
2017-07-26 10:50:26 +01:00
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "cannot ready payload")
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
u := new(user.User)
2017-07-26 10:50:26 +01:00
err = json.Unmarshal(body, &u)
if err != nil {
response.WriteBadRequestError(w, method, "JSON body")
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
ctx.Transaction, err = h.Runtime.Db.Beginx()
2017-07-26 10:50:26 +01:00
if err != nil {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
token := secrets.GenerateSalt()
2017-07-26 20:03:23 +01:00
err = h.Store.User.ForgotUserPassword(ctx, u.Email, token)
2017-07-26 10:50:26 +01:00
if err != nil && err != sql.ErrNoRows {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
if err == sql.ErrNoRows {
ctx.Transaction.Rollback()
2017-07-26 20:03:23 +01:00
h.Runtime.Log.Info(fmt.Sprintf("User %s not found for password reset process", u.Email))
response.WriteEmpty(w)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
ctx.Transaction.Commit()
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
appURL := ctx.GetAppURL(fmt.Sprintf("auth/reset/%s", token))
2017-08-02 15:26:31 +01:00
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.PasswordReset(u.Email, appURL)
2017-07-26 10:50:26 +01:00
response.WriteEmpty(w)
}
2017-07-26 20:03:23 +01:00
// ResetPassword stores the newly chosen password for the user.
func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
2018-03-01 10:58:55 +00:00
method := "user.ResetPassword"
2017-07-26 20:03:23 +01:00
ctx := domain.GetRequestContext(r)
2017-08-27 16:39:09 +01:00
ctx.Subdomain = organization.GetSubdomainFromHost(r)
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
token := request.Param(r, "token")
2017-07-26 10:50:26 +01:00
if len(token) == 0 {
response.WriteMissingDataError(w, method, "missing token")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "JSON body")
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
newPassword := string(body)
2017-07-26 20:03:23 +01:00
ctx.Transaction, err = h.Runtime.Db.Beginx()
2017-07-26 10:50:26 +01:00
if err != nil {
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
u, err := h.Store.User.GetByToken(ctx, token)
2017-07-26 10:50:26 +01:00
if err != nil {
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
u.Salt = secrets.GenerateSalt()
2017-07-26 10:50:26 +01:00
2017-07-26 20:03:23 +01:00
err = h.Store.User.UpdateUserPassword(ctx, u.RefID, u.Salt, secrets.GeneratePassword(newPassword, u.Salt))
2017-07-26 10:50:26 +01:00
if err != nil {
2017-07-26 20:03:23 +01:00
ctx.Transaction.Rollback()
2017-07-26 10:50:26 +01:00
response.WriteServerError(w, method, err)
2017-08-03 10:00:24 +01:00
h.Runtime.Log.Error(method, err)
2017-07-26 10:50:26 +01:00
return
}
2017-07-26 20:03:23 +01:00
ctx.Transaction.Commit()
2017-07-26 10:50:26 +01:00
h.Store.Audit.Record(ctx, audit.EventTypeUserPasswordReset)
2017-07-26 10:50:26 +01:00
response.WriteEmpty(w)
}
2018-02-28 14:55:36 +00:00
// MatchUsers returns users where provided text
// matches firstname, lastname, email
func (h *Handler) MatchUsers(w http.ResponseWriter, r *http.Request) {
method := "user.MatchUsers"
ctx := domain.GetRequestContext(r)
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "text")
h.Runtime.Log.Error(method, err)
return
}
searchText := string(body)
u, err := h.Store.User.MatchUsers(ctx, searchText, 100)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, u)
}
2018-03-01 10:58:55 +00:00
// BulkImport imports comma-delimited list of users:
// firstname, lastname, email
func (h *Handler) BulkImport(w http.ResponseWriter, r *http.Request) {
method := "user.BulkImport"
ctx := domain.GetRequestContext(r)
if !ctx.Administrator {
response.WriteForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "text")
h.Runtime.Log.Error(method, err)
return
}
usersList := string(body)
cr := csv.NewReader(strings.NewReader(usersList))
cr.TrimLeadingSpace = true
cr.FieldsPerRecord = 3
records, err := cr.ReadAll()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
for _, v := range records {
userModel := user.User{}
userModel.Firstname = strings.TrimSpace(v[0])
userModel.Lastname = strings.TrimSpace(v[1])
userModel.Email = strings.ToLower(strings.TrimSpace(v[2]))
if len(userModel.Email) == 0 || len(userModel.Firstname) == 0 || len(userModel.Lastname) == 0 {
h.Runtime.Log.Info(method + " missing firstname, lastname, or email")
continue
}
userModel.Initials = stringutil.MakeInitials(userModel.Firstname, userModel.Lastname)
requestedPassword := secrets.GenerateRandomPassword()
userModel.Salt = secrets.GenerateSalt()
userModel.Password = secrets.GeneratePassword(requestedPassword, userModel.Salt)
// only create account if not dupe
addUser := true
addAccount := true
var userID string
userDupe, err := h.Store.User.GetByEmail(ctx, userModel.Email)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error(method, err)
continue
}
if userModel.Email == userDupe.Email {
addUser = false
userID = userDupe.RefID
h.Runtime.Log.Info("Dupe user found, will not add " + userModel.Email)
}
if addUser {
userID = uniqueid.Generate()
userModel.RefID = userID
err = h.Store.User.Add(ctx, userModel)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Runtime.Log.Info("Adding user")
} else {
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &userDupe)
for _, a := range userDupe.Accounts {
if a.OrgID == ctx.OrgID {
addAccount = false
h.Runtime.Log.Info("Dupe account found, will not add")
break
}
}
}
// set up user account for the org
if addAccount {
var a account.Account
a.RefID = uniqueid.Generate()
a.UserID = userID
a.OrgID = ctx.OrgID
a.Editor = true
a.Admin = false
a.Active = true
err = h.Store.Account.Add(ctx, a)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
}
if addUser {
event.Handler().Publish(string(event.TypeAddUser))
h.Store.Audit.Record(ctx, audit.EventTypeUserAdd)
}
if addAccount {
event.Handler().Publish(string(event.TypeAddAccount))
h.Store.Audit.Record(ctx, audit.EventTypeAccountAdd)
}
// If we did not add user or give them access (account) then we error back
if !addUser && !addAccount {
h.Runtime.Log.Info(method + " duplicate user not added")
continue
}
// Invite new user and prepare invitation email (that contains SSO link)
if addUser && addAccount {
size := len(requestedPassword)
auth := fmt.Sprintf("%s:%s:%s", ctx.AppURL, userModel.Email, requestedPassword[:size])
encrypted := secrets.EncodeBase64([]byte(auth))
url := fmt.Sprintf("%s/%s", ctx.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted)))
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, ctx.AppURL))
} else {
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.InviteExistingUser(userModel.Email, inviter.Fullname(), ctx.GetAppURL(""))
h.Runtime.Log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email))
}
}
ctx.Transaction.Commit()
response.WriteEmpty(w)
}