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"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/documize/community/core/api/mail"
|
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-07-26 20:03:23 +01:00
|
|
|
"github.com/documize/community/model/account"
|
|
|
|
"github.com/documize/community/model/audit"
|
|
|
|
"github.com/documize/community/model/space"
|
|
|
|
"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())
|
|
|
|
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())
|
|
|
|
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)
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
ctx.Transaction.Commit()
|
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)
|
|
|
|
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-07-26 10:50:26 +01:00
|
|
|
go mail.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
|
|
|
|
|
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-07-26 20:03:23 +01:00
|
|
|
go mail.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
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2017-07-26 20:03:23 +01:00
|
|
|
u, err = h.Store.User.GetUsersForOrganization(ctx)
|
2017-07-26 10:50:26 +01:00
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
response.WriteServerError(w, method, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(u) == 0 {
|
2017-07-26 20:03:23 +01:00
|
|
|
u = []user.User{}
|
2017-07-26 10:50:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := range u {
|
2017-07-26 20:03:23 +01:00
|
|
|
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &u[i])
|
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
|
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
folderID := request.Param(r, "folderID")
|
2017-07-26 10:50:26 +01:00
|
|
|
if len(folderID) == 0 {
|
|
|
|
response.WriteMissingDataError(w, method, "folderID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// check to see space type as it determines user selection criteria
|
2017-07-26 20:03:23 +01:00
|
|
|
folder, err := h.Store.Space.Get(ctx, folderID)
|
2017-07-26 10:50:26 +01:00
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
h.Runtime.Log.Error("cannot get space", err)
|
|
|
|
response.WriteJSON(w, u)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch folder.Type {
|
2017-07-26 20:03:23 +01:00
|
|
|
case space.ScopePublic:
|
|
|
|
u, err = h.Store.User.GetActiveUsersForOrganization(ctx)
|
2017-07-26 10:50:26 +01:00
|
|
|
break
|
2017-07-26 20:03:23 +01:00
|
|
|
case space.ScopePrivate:
|
2017-07-26 10:50:26 +01:00
|
|
|
// just me
|
2017-07-26 20:03:23 +01:00
|
|
|
var me user.User
|
|
|
|
me, err = h.Store.User.Get(ctx, ctx.UserID)
|
2017-07-26 10:50:26 +01:00
|
|
|
u = append(u, me)
|
|
|
|
break
|
2017-07-26 20:03:23 +01:00
|
|
|
case space.ScopeRestricted:
|
|
|
|
u, err = h.Store.User.GetSpaceUsers(ctx, folderID)
|
2017-07-26 10:50:26 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(u) == 0 {
|
2017-07-26 20:03:23 +01:00
|
|
|
u = []user.User{}
|
2017-07-26 10:50:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
h.Runtime.Log.Error("cannot get users for space", err)
|
|
|
|
response.WriteJSON(w, u)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
err = h.Store.Space.ChangeOwner(ctx, userID, 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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
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
|
|
|
ctx.Transaction.Commit()
|
2017-07-26 10:50:26 +01:00
|
|
|
|
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-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())
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
a.Editor = u.Editor
|
|
|
|
a.Admin = u.Admin
|
|
|
|
a.Active = u.Active
|
|
|
|
|
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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
h.Store.Audit.Record(ctx, audit.EventTypeUserUpdate)
|
2017-07-26 10:50:26 +01:00
|
|
|
|
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
|
|
|
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())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
newPassword := string(body)
|
|
|
|
|
|
|
|
// can only update your own account unless you are an admin
|
2017-07-26 20:03:23 +01:00
|
|
|
if userID != ctx.UserID || !ctx.Administrator {
|
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 {
|
|
|
|
response.WriteServerError(w, method, err)
|
|
|
|
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 {
|
|
|
|
response.WriteServerError(w, method, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
ctx.Transaction.Rollback()
|
|
|
|
|
2017-07-26 10:50:26 +01:00
|
|
|
response.WriteEmpty(w)
|
|
|
|
}
|
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
// UserSpacePermissions returns folder permission for authenticated user.
|
|
|
|
func (h *Handler) UserSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
|
|
|
method := "user.UserSpacePermissions"
|
|
|
|
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")
|
|
|
|
if userID != ctx.UserID {
|
2017-07-26 10:50:26 +01:00
|
|
|
response.WriteForbiddenError(w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
roles, err := h.Store.Space.GetUserRoles(ctx)
|
2017-07-26 10:50:26 +01:00
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
err = nil
|
|
|
|
roles = []space.Role{}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
response.WriteJSON(w, roles)
|
|
|
|
}
|
|
|
|
|
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-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")
|
|
|
|
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")
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
response.WriteEmpty(w)
|
2017-07-26 20:03:23 +01:00
|
|
|
h.Runtime.Log.Info(fmt.Sprintf("User %s not found for password reset process", u.Email))
|
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-07-26 10:50:26 +01:00
|
|
|
go mail.PasswordReset(u.Email, appURL)
|
|
|
|
|
|
|
|
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) {
|
2017-07-26 10:50:26 +01:00
|
|
|
method := "user.ForgotUserPassword"
|
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
|
|
|
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")
|
|
|
|
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)
|
|
|
|
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 {
|
|
|
|
response.WriteServerError(w, method, err)
|
|
|
|
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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
h.Store.Audit.Record(ctx, audit.EventTypeUserPasswordReset)
|
2017-07-26 10:50:26 +01:00
|
|
|
|
2017-07-26 20:03:23 +01:00
|
|
|
ctx.Transaction.Commit()
|
2017-07-26 10:50:26 +01:00
|
|
|
|
|
|
|
response.WriteEmpty(w)
|
|
|
|
}
|