2018-11-15 12:00:08 +00:00
|
|
|
// Copyright (C) 2018 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
2018-11-14 10:50:15 +00:00
|
|
|
package satellite
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/subtle"
|
|
|
|
"time"
|
|
|
|
|
2018-12-10 15:57:06 +00:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2018-12-10 12:29:01 +00:00
|
|
|
|
2018-11-14 10:50:15 +00:00
|
|
|
"github.com/skyrings/skyring-common/tools/uuid"
|
|
|
|
"github.com/zeebo/errs"
|
2018-12-06 14:40:32 +00:00
|
|
|
|
2018-11-30 13:40:13 +00:00
|
|
|
"go.uber.org/zap"
|
2018-11-14 10:50:15 +00:00
|
|
|
|
|
|
|
"storj.io/storj/pkg/auth"
|
|
|
|
"storj.io/storj/pkg/satellite/satelliteauth"
|
2018-12-10 15:57:06 +00:00
|
|
|
"storj.io/storj/pkg/utils"
|
2018-11-14 10:50:15 +00:00
|
|
|
)
|
|
|
|
|
2018-12-19 13:03:12 +00:00
|
|
|
// maxLimit specifies the limit for all paged queries
|
|
|
|
const maxLimit = 50
|
2018-12-17 14:28:58 +00:00
|
|
|
|
2018-11-14 10:50:15 +00:00
|
|
|
// Service is handling accounts related logic
|
|
|
|
type Service struct {
|
|
|
|
Signer
|
|
|
|
|
|
|
|
store DB
|
2018-11-21 15:51:43 +00:00
|
|
|
log *zap.Logger
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewService returns new instance of Service
|
2018-11-21 15:51:43 +00:00
|
|
|
func NewService(log *zap.Logger, signer Signer, store DB) (*Service, error) {
|
2018-11-14 10:50:15 +00:00
|
|
|
if signer == nil {
|
|
|
|
return nil, errs.New("signer can't be nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
if store == nil {
|
|
|
|
return nil, errs.New("store can't be nil")
|
|
|
|
}
|
|
|
|
|
2018-11-21 15:51:43 +00:00
|
|
|
if log == nil {
|
|
|
|
return nil, errs.New("log can't be nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Service{Signer: signer, store: store, log: log}, nil
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
2018-11-28 10:31:15 +00:00
|
|
|
// CreateUser gets password hash value and creates new User
|
2018-12-10 15:57:06 +00:00
|
|
|
func (s *Service) CreateUser(ctx context.Context, user CreateUser) (*User, error) {
|
|
|
|
if err := user.IsValid(); err != nil {
|
2018-11-29 16:23:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-12-10 15:57:06 +00:00
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
|
2018-11-21 15:51:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-12-10 15:57:06 +00:00
|
|
|
//passwordHash := sha256.Sum256()
|
|
|
|
return s.store.Users().Insert(ctx, &User{
|
|
|
|
Email: user.Email,
|
|
|
|
FirstName: user.FirstName,
|
|
|
|
LastName: user.LastName,
|
|
|
|
PasswordHash: hash,
|
2018-11-21 15:51:43 +00:00
|
|
|
})
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
2018-11-28 10:31:15 +00:00
|
|
|
// Token authenticates User by credentials and returns auth token
|
2018-11-21 15:51:43 +00:00
|
|
|
func (s *Service) Token(ctx context.Context, email, password string) (string, error) {
|
2018-12-10 13:47:48 +00:00
|
|
|
user, err := s.store.Users().GetByEmail(ctx, email)
|
2018-11-14 10:50:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2018-12-10 15:57:06 +00:00
|
|
|
err = bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password))
|
|
|
|
if err != nil {
|
2018-12-18 17:43:02 +00:00
|
|
|
return "", ErrUnauthorized.New("password is incorrect: %s", err.Error())
|
2018-12-10 13:47:48 +00:00
|
|
|
}
|
|
|
|
|
2018-12-10 15:57:06 +00:00
|
|
|
// TODO: move expiration time to constants
|
2018-11-14 10:50:15 +00:00
|
|
|
claims := satelliteauth.Claims{
|
|
|
|
ID: user.ID,
|
|
|
|
Expiration: time.Now().Add(time.Minute * 15),
|
|
|
|
}
|
|
|
|
|
|
|
|
token, err := s.createToken(&claims)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
2018-11-28 10:31:15 +00:00
|
|
|
// GetUser returns User by id
|
2018-11-14 10:50:15 +00:00
|
|
|
func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (*User, error) {
|
2018-11-27 14:20:58 +00:00
|
|
|
_, err := GetAuth(ctx)
|
2018-11-14 10:50:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-11-21 15:51:43 +00:00
|
|
|
return s.store.Users().Get(ctx, id)
|
|
|
|
}
|
2018-11-14 10:50:15 +00:00
|
|
|
|
2018-11-28 10:31:15 +00:00
|
|
|
// UpdateUser updates User with given id
|
|
|
|
func (s *Service) UpdateUser(ctx context.Context, id uuid.UUID, info UserInfo) error {
|
|
|
|
_, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:23:44 +00:00
|
|
|
if err = info.IsValid(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-28 10:31:15 +00:00
|
|
|
return s.store.Users().Update(ctx, &User{
|
|
|
|
ID: id,
|
|
|
|
FirstName: info.FirstName,
|
|
|
|
LastName: info.LastName,
|
|
|
|
Email: info.Email,
|
2018-12-10 15:57:06 +00:00
|
|
|
PasswordHash: nil,
|
2018-11-28 10:31:15 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-12-10 15:57:06 +00:00
|
|
|
// ChangeUserPassword updates password for a given user
|
|
|
|
func (s *Service) ChangeUserPassword(ctx context.Context, id uuid.UUID, pass, newPass string) error {
|
|
|
|
_, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
user, err := s.store.Users().Get(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(pass))
|
|
|
|
if err != nil {
|
2018-12-18 17:43:02 +00:00
|
|
|
return ErrUnauthorized.New("origin password is incorrect: %s", err.Error())
|
2018-12-10 15:57:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := validatePassword(newPass); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
user.PasswordHash = hash
|
|
|
|
return s.store.Users().Update(ctx, user)
|
|
|
|
}
|
|
|
|
|
2018-11-28 12:30:38 +00:00
|
|
|
// DeleteUser deletes User by id
|
2018-12-14 16:14:17 +00:00
|
|
|
func (s *Service) DeleteUser(ctx context.Context, id uuid.UUID, password string) error {
|
|
|
|
auth, err := GetAuth(ctx)
|
2018-11-27 14:20:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-18 17:43:02 +00:00
|
|
|
if !uuid.Equal(auth.User.ID, id) {
|
2018-12-14 16:14:17 +00:00
|
|
|
return ErrUnauthorized.New("user has no rights")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = bcrypt.CompareHashAndPassword(auth.User.PasswordHash, []byte(password))
|
|
|
|
if err != nil {
|
|
|
|
return ErrUnauthorized.New("origin password is incorrect")
|
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
return s.store.Users().Delete(ctx, id)
|
|
|
|
}
|
|
|
|
|
2018-11-26 10:47:23 +00:00
|
|
|
// GetProject is a method for querying project by id
|
|
|
|
func (s *Service) GetProject(ctx context.Context, projectID uuid.UUID) (*Project, error) {
|
2018-11-27 14:20:58 +00:00
|
|
|
_, err := GetAuth(ctx)
|
2018-11-26 10:47:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.store.Projects().Get(ctx, projectID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUsersProjects is a method for querying all projects
|
2018-12-06 15:19:47 +00:00
|
|
|
func (s *Service) GetUsersProjects(ctx context.Context) ([]Project, error) {
|
|
|
|
auth, err := GetAuth(ctx)
|
2018-11-26 10:47:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-12-06 15:19:47 +00:00
|
|
|
return s.store.Projects().GetByUserID(ctx, auth.User.ID)
|
2018-11-26 10:47:23 +00:00
|
|
|
}
|
|
|
|
|
2018-11-27 13:14:10 +00:00
|
|
|
// CreateProject is a method for creating new project
|
2018-11-26 10:47:23 +00:00
|
|
|
func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (*Project, error) {
|
2018-11-27 14:20:58 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-11-26 10:47:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !projectInfo.IsTermsAccepted {
|
2018-12-18 17:43:02 +00:00
|
|
|
return nil, errs.New("terms of use should be accepted!")
|
2018-11-26 10:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
project := &Project{
|
|
|
|
Description: projectInfo.Description,
|
|
|
|
Name: projectInfo.Name,
|
|
|
|
TermsAccepted: 1, //TODO: get lat version of Term of Use
|
|
|
|
}
|
|
|
|
|
2018-12-10 12:29:01 +00:00
|
|
|
transaction, err := s.store.BeginTx(ctx)
|
2018-12-06 15:19:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-12-10 12:29:01 +00:00
|
|
|
prj, err := transaction.Projects().Insert(ctx, project)
|
|
|
|
if err != nil {
|
|
|
|
return nil, utils.CombineErrors(err, transaction.Rollback())
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = transaction.ProjectMembers().Insert(ctx, auth.User.ID, prj.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, utils.CombineErrors(err, transaction.Rollback())
|
|
|
|
}
|
2018-12-06 15:19:47 +00:00
|
|
|
|
2018-12-10 12:29:01 +00:00
|
|
|
return prj, transaction.Commit()
|
2018-11-26 10:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteProject is a method for deleting project by id
|
|
|
|
func (s *Service) DeleteProject(ctx context.Context, projectID uuid.UUID) error {
|
2018-12-20 16:18:08 +00:00
|
|
|
_, err := GetAuth(ctx)
|
2018-12-18 17:43:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-20 16:18:08 +00:00
|
|
|
// TODO: before deletion we should check if user is a project member
|
2018-11-26 10:47:23 +00:00
|
|
|
return s.store.Projects().Delete(ctx, projectID)
|
|
|
|
}
|
|
|
|
|
2018-11-28 16:20:23 +00:00
|
|
|
// UpdateProject is a method for updating project description by id
|
|
|
|
func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, description string) (*Project, error) {
|
2018-11-27 14:20:58 +00:00
|
|
|
_, err := GetAuth(ctx)
|
2018-11-26 10:47:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
project, err := s.store.Projects().Get(ctx, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New("Project doesn't exist!")
|
|
|
|
}
|
|
|
|
|
2018-11-28 16:20:23 +00:00
|
|
|
project.Description = description
|
2018-11-26 10:47:23 +00:00
|
|
|
|
|
|
|
err = s.store.Projects().Update(ctx, project)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return project, nil
|
|
|
|
}
|
|
|
|
|
2018-12-06 14:40:32 +00:00
|
|
|
// AddProjectMember adds User as member of given Project
|
|
|
|
func (s *Service) AddProjectMember(ctx context.Context, projectID, userID uuid.UUID) error {
|
|
|
|
_, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.store.ProjectMembers().Insert(ctx, userID, projectID)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteProjectMember removes user membership for given project
|
|
|
|
func (s *Service) DeleteProjectMember(ctx context.Context, projectID, userID uuid.UUID) error {
|
|
|
|
_, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-10 12:29:01 +00:00
|
|
|
return s.store.ProjectMembers().Delete(ctx, userID, projectID)
|
2018-12-06 14:40:32 +00:00
|
|
|
}
|
|
|
|
|
2018-12-10 11:38:42 +00:00
|
|
|
// GetProjectMembers returns ProjectMembers for given Project
|
2018-12-19 13:03:12 +00:00
|
|
|
func (s *Service) GetProjectMembers(ctx context.Context, projectID uuid.UUID, limit int, offset int64) ([]ProjectMember, error) {
|
2018-12-10 11:38:42 +00:00
|
|
|
_, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-12-19 13:03:12 +00:00
|
|
|
if limit < 0 || offset < 0 {
|
|
|
|
return nil, errs.New("invalid pagination argument")
|
|
|
|
}
|
|
|
|
|
|
|
|
if limit > maxLimit {
|
|
|
|
limit = maxLimit
|
|
|
|
}
|
2018-12-17 14:28:58 +00:00
|
|
|
|
2018-12-19 13:03:12 +00:00
|
|
|
return s.store.ProjectMembers().GetByProjectID(ctx, projectID, limit, offset)
|
2018-12-10 11:38:42 +00:00
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
// Authorize validates token from context and returns authorized Authorization
|
|
|
|
func (s *Service) Authorize(ctx context.Context) (Authorization, error) {
|
|
|
|
tokenS, ok := auth.GetAPIKey(ctx)
|
2018-11-21 15:51:43 +00:00
|
|
|
if !ok {
|
2018-12-18 17:43:02 +00:00
|
|
|
return Authorization{}, ErrUnauthorized.New("no api key was provided")
|
2018-11-21 15:51:43 +00:00
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
token, err := satelliteauth.FromBase64URLString(string(tokenS))
|
2018-11-21 15:51:43 +00:00
|
|
|
if err != nil {
|
2018-12-18 17:43:02 +00:00
|
|
|
return Authorization{}, ErrUnauthorized.Wrap(err)
|
2018-11-21 15:51:43 +00:00
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
claims, err := s.authenticate(token)
|
|
|
|
if err != nil {
|
2018-12-18 17:43:02 +00:00
|
|
|
return Authorization{}, ErrUnauthorized.Wrap(err)
|
2018-11-27 14:20:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
user, err := s.authorize(ctx, claims)
|
|
|
|
if err != nil {
|
2018-12-18 17:43:02 +00:00
|
|
|
return Authorization{}, ErrUnauthorized.Wrap(err)
|
2018-11-27 14:20:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return Authorization{
|
|
|
|
User: *user,
|
|
|
|
Claims: *claims,
|
|
|
|
}, nil
|
2018-11-21 15:51:43 +00:00
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
// createToken creates string representation
|
2018-11-22 10:38:58 +00:00
|
|
|
func (s *Service) createToken(claims *satelliteauth.Claims) (string, error) {
|
|
|
|
json, err := claims.JSON()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
token := satelliteauth.Token{Payload: json}
|
|
|
|
err = signToken(&token, s.Signer)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return token.String(), nil
|
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
// authenticate validates token signature and returns authenticated *satelliteauth.Authorization
|
|
|
|
func (s *Service) authenticate(token satelliteauth.Token) (*satelliteauth.Claims, error) {
|
2018-11-14 10:50:15 +00:00
|
|
|
signature := token.Signature
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
err := signToken(&token, s.Signer)
|
2018-11-14 10:50:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if subtle.ConstantTimeCompare(signature, token.Signature) != 1 {
|
|
|
|
return nil, errs.New("incorrect signature")
|
|
|
|
}
|
|
|
|
|
|
|
|
claims, err := satelliteauth.FromJSON(token.Payload)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return claims, nil
|
|
|
|
}
|
|
|
|
|
2018-11-21 15:51:43 +00:00
|
|
|
// authorize checks claims and returns authorized User
|
|
|
|
func (s *Service) authorize(ctx context.Context, claims *satelliteauth.Claims) (*User, error) {
|
2018-11-14 10:50:15 +00:00
|
|
|
if !claims.Expiration.IsZero() && claims.Expiration.Before(time.Now()) {
|
2018-11-21 15:51:43 +00:00
|
|
|
return nil, errs.New("token is outdated")
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
2018-11-21 15:51:43 +00:00
|
|
|
user, err := s.store.Users().Get(ctx, claims.ID)
|
2018-11-14 10:50:15 +00:00
|
|
|
if err != nil {
|
2018-11-21 15:51:43 +00:00
|
|
|
return nil, errs.New("authorization failed. no user with id: %s", claims.ID.String())
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
2018-11-21 15:51:43 +00:00
|
|
|
return user, nil
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|