storj/pkg/satellite/service.go

380 lines
9.1 KiB
Go
Raw Normal View History

// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package satellite
import (
"context"
"crypto/subtle"
"time"
"golang.org/x/crypto/bcrypt"
"github.com/skyrings/skyring-common/tools/uuid"
"github.com/zeebo/errs"
2018-11-30 13:40:13 +00:00
"go.uber.org/zap"
"storj.io/storj/pkg/auth"
"storj.io/storj/pkg/satellite/satelliteauth"
"storj.io/storj/pkg/utils"
)
// maxLimit specifies the limit for all paged queries
const maxLimit = 50
// Service is handling accounts related logic
type Service struct {
Signer
store DB
2018-11-21 15:51:43 +00:00
log *zap.Logger
}
// 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) {
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-28 10:31:15 +00:00
// CreateUser gets password hash value and creates new User
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
}
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
2018-11-21 15:51:43 +00:00
if err != nil {
return nil, err
}
//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-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)
if err != nil {
return "", err
}
err = bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password))
if err != nil {
return "", ErrUnauthorized.New("password is incorrect: %s", err.Error())
2018-12-10 13:47:48 +00:00
}
// TODO: move expiration time to constants
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
func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (*User, error) {
_, err := GetAuth(ctx)
if err != nil {
return nil, err
}
2018-11-21 15:51:43 +00:00
return s.store.Users().Get(ctx, id)
}
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,
PasswordHash: nil,
2018-11-28 10:31:15 +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 {
return ErrUnauthorized.New("origin password is incorrect: %s", err.Error())
}
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
func (s *Service) DeleteUser(ctx context.Context, id uuid.UUID, password string) error {
auth, err := GetAuth(ctx)
if err != nil {
return err
}
if !uuid.Equal(auth.User.ID, id) {
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")
}
return s.store.Users().Delete(ctx, id)
}
// GetProject is a method for querying project by id
func (s *Service) GetProject(ctx context.Context, projectID uuid.UUID) (*Project, error) {
_, err := GetAuth(ctx)
if err != nil {
return nil, err
}
return s.store.Projects().Get(ctx, projectID)
}
// GetUsersProjects is a method for querying all projects
func (s *Service) GetUsersProjects(ctx context.Context) ([]Project, error) {
auth, err := GetAuth(ctx)
if err != nil {
return nil, err
}
return s.store.Projects().GetByUserID(ctx, auth.User.ID)
}
// CreateProject is a method for creating new project
func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (*Project, error) {
auth, err := GetAuth(ctx)
if err != nil {
return nil, err
}
if !projectInfo.IsTermsAccepted {
return nil, errs.New("terms of use should be accepted!")
}
project := &Project{
Description: projectInfo.Description,
Name: projectInfo.Name,
TermsAccepted: 1, //TODO: get lat version of Term of Use
}
transaction, err := s.store.BeginTx(ctx)
if err != nil {
return nil, err
}
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())
}
return prj, transaction.Commit()
}
// DeleteProject is a method for deleting project by id
func (s *Service) DeleteProject(ctx context.Context, projectID uuid.UUID) error {
_, err := GetAuth(ctx)
if err != nil {
return err
}
// TODO: before deletion we should check if user is a project member
return s.store.Projects().Delete(ctx, projectID)
}
// UpdateProject is a method for updating project description by id
func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, description string) (*Project, error) {
_, err := GetAuth(ctx)
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!")
}
project.Description = description
err = s.store.Projects().Update(ctx, project)
if err != nil {
return nil, err
}
return project, nil
}
// 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
}
return s.store.ProjectMembers().Delete(ctx, userID, projectID)
}
2018-12-10 11:38:42 +00:00
// GetProjectMembers returns ProjectMembers for given Project
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
}
if limit < 0 || offset < 0 {
return nil, errs.New("invalid pagination argument")
}
if limit > maxLimit {
limit = maxLimit
}
return s.store.ProjectMembers().GetByProjectID(ctx, projectID, limit, offset)
2018-12-10 11:38:42 +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 {
return Authorization{}, ErrUnauthorized.New("no api key was provided")
2018-11-21 15:51:43 +00:00
}
token, err := satelliteauth.FromBase64URLString(string(tokenS))
2018-11-21 15:51:43 +00:00
if err != nil {
return Authorization{}, ErrUnauthorized.Wrap(err)
2018-11-21 15:51:43 +00:00
}
claims, err := s.authenticate(token)
if err != nil {
return Authorization{}, ErrUnauthorized.Wrap(err)
}
user, err := s.authorize(ctx, claims)
if err != nil {
return Authorization{}, ErrUnauthorized.Wrap(err)
}
return Authorization{
User: *user,
Claims: *claims,
}, nil
2018-11-21 15:51:43 +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
}
// authenticate validates token signature and returns authenticated *satelliteauth.Authorization
func (s *Service) authenticate(token satelliteauth.Token) (*satelliteauth.Claims, error) {
signature := token.Signature
err := signToken(&token, s.Signer)
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) {
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-21 15:51:43 +00:00
user, err := s.store.Users().Get(ctx, claims.ID)
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-21 15:51:43 +00:00
return user, nil
}