2019-01-24 16:26:36 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
2018-11-15 12:00:08 +00:00
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
2019-01-15 13:03:24 +00:00
|
|
|
package console
|
2018-11-14 10:50:15 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/subtle"
|
2019-06-06 17:07:14 +01:00
|
|
|
"fmt"
|
2018-11-14 10:50:15 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/skyrings/skyring-common/tools/uuid"
|
|
|
|
"github.com/zeebo/errs"
|
2018-11-30 13:40:13 +00:00
|
|
|
"go.uber.org/zap"
|
2019-01-08 14:05:14 +00:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2019-06-04 12:55:38 +01:00
|
|
|
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
2018-12-20 20:10:27 +00:00
|
|
|
|
2018-11-14 10:50:15 +00:00
|
|
|
"storj.io/storj/pkg/auth"
|
2019-05-24 17:51:27 +01:00
|
|
|
"storj.io/storj/pkg/macaroon"
|
2019-01-15 13:03:24 +00:00
|
|
|
"storj.io/storj/satellite/console/consoleauth"
|
2019-06-03 14:46:57 +01:00
|
|
|
"storj.io/storj/satellite/payments"
|
2018-11-14 10:50:15 +00:00
|
|
|
)
|
|
|
|
|
2018-12-20 20:10:27 +00:00
|
|
|
var (
|
|
|
|
mon = monkit.Package()
|
|
|
|
)
|
|
|
|
|
2018-12-26 14:00:53 +00:00
|
|
|
const (
|
|
|
|
// maxLimit specifies the limit for all paged queries
|
2019-01-30 15:04:40 +00:00
|
|
|
maxLimit = 50
|
|
|
|
tokenExpirationTime = 24 * time.Hour
|
2019-02-05 17:31:53 +00:00
|
|
|
|
|
|
|
// DefaultPasswordCost is the hashing complexity
|
|
|
|
DefaultPasswordCost = bcrypt.DefaultCost
|
|
|
|
// TestPasswordCost is the hashing complexity to use for testing
|
|
|
|
TestPasswordCost = bcrypt.MinCost
|
2018-12-26 14:00:53 +00:00
|
|
|
)
|
2018-12-17 14:28:58 +00:00
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
// Error messages
|
|
|
|
const (
|
2019-04-10 20:16:10 +01:00
|
|
|
internalErrMsg = "It looks like we had a problem on our end. Please try again"
|
|
|
|
unauthorizedErrMsg = "You are not authorized to perform this action"
|
|
|
|
vanguardRegTokenErrMsg = "We are unable to create your account. This is an invite-only alpha, please join our waitlist to receive an invitation"
|
|
|
|
emailUsedErrMsg = "This email is already in use, try another"
|
|
|
|
activationTokenIsExpiredErrMsg = "Your account activation link has expired, please sign up again"
|
|
|
|
passwordRecoveryTokenIsExpiredErrMsg = "Your password recovery link has expired, please request another one"
|
|
|
|
credentialsErrMsg = "Your email or password was incorrect, please try again"
|
|
|
|
oldPassIncorrectErrMsg = "Old password is incorrect, please try again"
|
|
|
|
passwordIncorrectErrMsg = "Your password needs at least %d characters long"
|
2019-05-24 17:51:27 +01:00
|
|
|
teamMemberDoesNotExistErrMsg = `There is no account on this Satellite for the user(s) you have entered.
|
2019-04-10 01:15:12 +01:00
|
|
|
Please add team members with active accounts`
|
|
|
|
|
|
|
|
// TODO: remove after vanguard release
|
|
|
|
usedRegTokenVanguardErrMsg = "This registration token has already been used"
|
|
|
|
projLimitVanguardErrMsg = "Sorry, during the Vanguard release you have a limited number of projects"
|
|
|
|
)
|
|
|
|
|
2018-11-14 10:50:15 +00:00
|
|
|
// Service is handling accounts related logic
|
|
|
|
type Service struct {
|
|
|
|
Signer
|
|
|
|
|
2018-11-21 15:51:43 +00:00
|
|
|
log *zap.Logger
|
2019-06-03 14:46:57 +01:00
|
|
|
pm payments.Service
|
|
|
|
store DB
|
2019-02-05 17:31:53 +00:00
|
|
|
|
|
|
|
passwordCost int
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewService returns new instance of Service
|
2019-06-03 14:46:57 +01:00
|
|
|
func NewService(log *zap.Logger, signer Signer, store DB, pm payments.Service, passwordCost int) (*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")
|
|
|
|
}
|
2019-02-05 17:31:53 +00:00
|
|
|
if passwordCost == 0 {
|
|
|
|
passwordCost = bcrypt.DefaultCost
|
|
|
|
}
|
|
|
|
|
2019-03-02 15:22:20 +00:00
|
|
|
return &Service{
|
2019-06-03 14:46:57 +01:00
|
|
|
log: log,
|
2019-03-02 15:22:20 +00:00
|
|
|
Signer: signer,
|
|
|
|
store: store,
|
2019-06-03 14:46:57 +01:00
|
|
|
pm: pm,
|
2019-03-02 15:22:20 +00:00
|
|
|
passwordCost: passwordCost,
|
|
|
|
}, nil
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 15:04:40 +00:00
|
|
|
// CreateUser gets password hash value and creates new inactive User
|
2019-03-19 17:55:43 +00:00
|
|
|
func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret RegistrationSecret) (u *User, err error) {
|
2018-12-20 20:10:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-10 15:57:06 +00:00
|
|
|
if err := user.IsValid(); err != nil {
|
2018-11-29 16:23:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-19 17:55:43 +00:00
|
|
|
// TODO: remove after vanguard release
|
|
|
|
registrationToken, err := s.store.RegistrationTokens().GetBySecret(ctx, tokenSecret)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(vanguardRegTokenErrMsg)
|
2019-03-19 17:55:43 +00:00
|
|
|
}
|
|
|
|
if registrationToken.OwnerID != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(usedRegTokenVanguardErrMsg)
|
2019-03-19 17:55:43 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 15:04:40 +00:00
|
|
|
// TODO: store original email input in the db,
|
2019-01-08 13:54:12 +00:00
|
|
|
// add normalization
|
|
|
|
email := normalizeEmail(user.Email)
|
|
|
|
|
2019-01-30 15:04:40 +00:00
|
|
|
u, err = s.store.Users().GetByEmail(ctx, email)
|
2019-02-11 10:33:56 +00:00
|
|
|
if err == nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(emailUsedErrMsg)
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 17:31:53 +00:00
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), s.passwordCost)
|
2018-11-21 15:51:43 +00:00
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(internalErrMsg)
|
2018-11-21 15:51:43 +00:00
|
|
|
}
|
2019-01-08 13:54:12 +00:00
|
|
|
|
2019-06-03 14:46:57 +01:00
|
|
|
// store data
|
|
|
|
tx, err := s.store.BeginTx(ctx)
|
2019-04-10 01:15:12 +01:00
|
|
|
if err != nil {
|
2019-06-03 14:46:57 +01:00
|
|
|
return nil, err
|
2019-04-10 01:15:12 +01:00
|
|
|
}
|
2019-01-30 15:04:40 +00:00
|
|
|
|
2019-06-03 14:46:57 +01:00
|
|
|
err = withTx(tx, func(tx DBTx) error {
|
|
|
|
u, err = tx.Users().Insert(ctx,
|
|
|
|
&User{
|
|
|
|
Email: user.Email,
|
|
|
|
FullName: user.FullName,
|
|
|
|
ShortName: user.ShortName,
|
|
|
|
PasswordHash: hash,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.RegistrationTokens().UpdateOwner(ctx, registrationToken.Secret, u.ID)
|
|
|
|
if err != nil {
|
|
|
|
return errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
2019-06-06 17:07:14 +01:00
|
|
|
cus, err := s.pm.CreateCustomer(ctx, payments.CreateCustomerParams{
|
|
|
|
Email: email,
|
|
|
|
Name: user.FullName,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = tx.UserPayments().Create(ctx, UserPayment{
|
|
|
|
UserID: u.ID,
|
|
|
|
CustomerID: cus.ID,
|
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
2019-06-03 14:46:57 +01:00
|
|
|
})
|
|
|
|
|
2019-03-19 17:55:43 +00:00
|
|
|
if err != nil {
|
2019-06-03 14:46:57 +01:00
|
|
|
return nil, err
|
2019-03-19 17:55:43 +00:00
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
return u, nil
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GenerateActivationToken - is a method for generating activation token
|
2019-03-26 15:56:16 +00:00
|
|
|
func (s *Service) GenerateActivationToken(ctx context.Context, id uuid.UUID, email string) (token string, err error) {
|
2019-01-30 15:04:40 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-03-02 15:22:20 +00:00
|
|
|
//TODO: activation token should differ from auth token
|
2019-01-30 15:04:40 +00:00
|
|
|
claims := &consoleauth.Claims{
|
|
|
|
ID: id,
|
|
|
|
Email: email,
|
2019-03-02 15:22:20 +00:00
|
|
|
Expiration: time.Now().Add(time.Hour * 24),
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
return s.createToken(ctx, claims)
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
2019-04-10 20:16:10 +01:00
|
|
|
// GeneratePasswordRecoveryToken - is a method for generating password recovery token
|
2019-05-13 16:53:52 +01:00
|
|
|
func (s *Service) GeneratePasswordRecoveryToken(ctx context.Context, id uuid.UUID) (token string, err error) {
|
2019-04-10 20:16:10 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-05-13 16:53:52 +01:00
|
|
|
resetPasswordToken, err := s.store.ResetPasswordTokens().GetByOwnerID(ctx, id)
|
|
|
|
if err == nil {
|
|
|
|
err := s.store.ResetPasswordTokens().Delete(ctx, resetPasswordToken.Secret)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-04-10 20:16:10 +01:00
|
|
|
}
|
|
|
|
|
2019-05-13 16:53:52 +01:00
|
|
|
resetPasswordToken, err = s.store.ResetPasswordTokens().Create(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return resetPasswordToken.Secret.String(), nil
|
2019-04-10 20:16:10 +01:00
|
|
|
}
|
|
|
|
|
2019-01-30 15:04:40 +00:00
|
|
|
// ActivateAccount - is a method for activating user account after registration
|
2019-03-08 14:01:11 +00:00
|
|
|
func (s *Service) ActivateAccount(ctx context.Context, activationToken string) (err error) {
|
2019-01-30 15:04:40 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
token, err := consoleauth.FromBase64URLString(activationToken)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
claims, err := s.authenticate(ctx, token)
|
2019-01-30 15:04:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-05 16:08:14 +01:00
|
|
|
_, err = s.store.Users().GetByEmail(ctx, normalizeEmail(claims.Email))
|
|
|
|
if err == nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(emailUsedErrMsg)
|
2019-04-05 16:08:14 +01:00
|
|
|
}
|
|
|
|
|
2019-01-30 15:04:40 +00:00
|
|
|
user, err := s.store.Users().Get(ctx, claims.ID)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
2019-02-11 10:33:56 +00:00
|
|
|
if user.Status != Inactive {
|
2019-03-08 14:01:11 +00:00
|
|
|
return errs.New("account is already active")
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if now.After(user.CreatedAt.Add(tokenExpirationTime)) {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(activationTokenIsExpiredErrMsg)
|
2019-01-30 15:04:40 +00:00
|
|
|
}
|
|
|
|
|
2019-02-11 10:33:56 +00:00
|
|
|
user.Status = Active
|
2019-01-30 15:04:40 +00:00
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
err = s.store.Users().Update(ctx, user)
|
|
|
|
if err != nil {
|
|
|
|
return errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
2019-04-10 20:16:10 +01:00
|
|
|
// ResetPassword - is a method for reseting user password
|
|
|
|
func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, password string) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-05-13 16:53:52 +01:00
|
|
|
secret, err := ResetPasswordSecretFromBase64(resetPasswordToken)
|
2019-04-10 20:16:10 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2019-05-13 16:53:52 +01:00
|
|
|
token, err := s.store.ResetPasswordTokens().GetBySecret(ctx, secret)
|
2019-04-10 20:16:10 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-05-13 16:53:52 +01:00
|
|
|
user, err := s.store.Users().Get(ctx, *token.OwnerID)
|
2019-04-10 20:16:10 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := validatePassword(password); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-05-13 16:53:52 +01:00
|
|
|
if time.Since(token.CreatedAt) > tokenExpirationTime {
|
2019-04-10 20:16:10 +01:00
|
|
|
return errs.New(passwordRecoveryTokenIsExpiredErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), s.passwordCost)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
user.PasswordHash = hash
|
2019-05-13 16:53:52 +01:00
|
|
|
|
|
|
|
err = s.store.Users().Update(ctx, user)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.store.ResetPasswordTokens().Delete(ctx, token.Secret)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RevokeResetPasswordToken - is a method to revoke reset password token
|
|
|
|
func (s *Service) RevokeResetPasswordToken(ctx context.Context, resetPasswordToken string) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
secret, err := ResetPasswordSecretFromBase64(resetPasswordToken)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.store.ResetPasswordTokens().Delete(ctx, secret)
|
2019-04-10 20:16:10 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 10:31:15 +00:00
|
|
|
// Token authenticates User by credentials and returns auth token
|
2018-12-20 20:10:27 +00:00
|
|
|
func (s *Service) Token(ctx context.Context, email, password string) (token string, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-01-08 13:54:12 +00:00
|
|
|
|
|
|
|
email = normalizeEmail(email)
|
|
|
|
|
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 {
|
2019-04-10 01:15:12 +01:00
|
|
|
return "", errs.New(credentialsErrMsg)
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
2018-12-10 15:57:06 +00:00
|
|
|
err = bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password))
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return "", ErrUnauthorized.New(credentialsErrMsg)
|
2018-12-10 13:47:48 +00:00
|
|
|
}
|
|
|
|
|
2019-01-15 13:03:24 +00:00
|
|
|
claims := consoleauth.Claims{
|
2018-11-14 10:50:15 +00:00
|
|
|
ID: user.ID,
|
2019-01-30 15:04:40 +00:00
|
|
|
Expiration: time.Now().Add(tokenExpirationTime),
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
token, err = s.createToken(ctx, &claims)
|
2018-11-14 10:50:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
2018-11-28 10:31:15 +00:00
|
|
|
// GetUser returns User by id
|
2018-12-20 20:10:27 +00:00
|
|
|
func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (u *User, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-14 10:50:15 +00:00
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
user, err := s.store.Users().Get(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return user, nil
|
2018-11-21 15:51:43 +00:00
|
|
|
}
|
2018-11-14 10:50:15 +00:00
|
|
|
|
2019-04-10 20:16:10 +01:00
|
|
|
// GetUserByEmail returns User by email
|
|
|
|
func (s *Service) GetUserByEmail(ctx context.Context, email string) (u *User, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
return s.store.Users().GetByEmail(ctx, email)
|
|
|
|
}
|
|
|
|
|
2018-12-24 12:52:52 +00:00
|
|
|
// UpdateAccount updates User
|
|
|
|
func (s *Service) UpdateAccount(ctx context.Context, info UserInfo) (err error) {
|
2018-12-20 20:10:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-24 12:52:52 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-11-28 10:31:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:23:44 +00:00
|
|
|
if err = info.IsValid(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
err = s.store.Users().Update(ctx, &User{
|
2018-12-24 12:52:52 +00:00
|
|
|
ID: auth.User.ID,
|
2019-03-27 12:33:32 +00:00
|
|
|
FullName: info.FullName,
|
|
|
|
ShortName: info.ShortName,
|
2019-04-25 16:06:19 +01:00
|
|
|
Email: auth.User.Email,
|
2018-12-10 15:57:06 +00:00
|
|
|
PasswordHash: nil,
|
2019-04-23 15:46:54 +01:00
|
|
|
Status: auth.User.Status,
|
2018-11-28 10:31:15 +00:00
|
|
|
})
|
2019-04-10 01:15:12 +01:00
|
|
|
if err != nil {
|
|
|
|
return errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-11-28 10:31:15 +00:00
|
|
|
}
|
|
|
|
|
2018-12-24 12:52:52 +00:00
|
|
|
// ChangePassword updates password for a given user
|
|
|
|
func (s *Service) ChangePassword(ctx context.Context, pass, newPass string) (err error) {
|
2018-12-20 20:10:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-24 12:52:52 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-12-10 15:57:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-24 12:52:52 +00:00
|
|
|
err = bcrypt.CompareHashAndPassword(auth.User.PasswordHash, []byte(pass))
|
2018-12-10 15:57:06 +00:00
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(oldPassIncorrectErrMsg)
|
2018-12-10 15:57:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := validatePassword(newPass); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-02-05 17:31:53 +00:00
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(newPass), s.passwordCost)
|
2018-12-10 15:57:06 +00:00
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2018-12-10 15:57:06 +00:00
|
|
|
}
|
|
|
|
|
2018-12-24 12:52:52 +00:00
|
|
|
auth.User.PasswordHash = hash
|
2019-04-10 01:15:12 +01:00
|
|
|
err = s.store.Users().Update(ctx, &auth.User)
|
|
|
|
if err != nil {
|
|
|
|
return errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-12-10 15:57:06 +00:00
|
|
|
}
|
|
|
|
|
2018-12-24 12:52:52 +00:00
|
|
|
// DeleteAccount deletes User
|
|
|
|
func (s *Service) DeleteAccount(ctx context.Context, password string) (err error) {
|
2018-12-20 20:10:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-14 16:14:17 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-11-27 14:20:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-14 16:14:17 +00:00
|
|
|
err = bcrypt.CompareHashAndPassword(auth.User.PasswordHash, []byte(password))
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return ErrUnauthorized.New(oldPassIncorrectErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = s.store.Users().Delete(ctx, auth.User.ID)
|
|
|
|
if err != nil {
|
|
|
|
return errs.New(internalErrMsg)
|
2018-12-14 16:14:17 +00:00
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil
|
2018-11-27 14:20:58 +00:00
|
|
|
}
|
|
|
|
|
2018-11-26 10:47:23 +00:00
|
|
|
// GetProject is a method for querying project by id
|
2018-12-20 20:10:27 +00:00
|
|
|
func (s *Service) GetProject(ctx context.Context, projectID uuid.UUID) (p *Project, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
_, err = GetAuth(ctx)
|
2018-11-26 10:47:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
p, err = s.store.Projects().Get(ctx, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2018-11-26 10:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetUsersProjects is a method for querying all projects
|
2018-12-20 20:10:27 +00:00
|
|
|
func (s *Service) GetUsersProjects(ctx context.Context) (ps []Project, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-06 15:19:47 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-11-26 10:47:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
ps, err = s.store.Projects().GetByUserID(ctx, auth.User.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
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-12-20 20:10:27 +00:00
|
|
|
func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p *Project, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
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
|
|
|
|
}
|
|
|
|
|
2019-03-19 17:55:43 +00:00
|
|
|
// TODO: remove after vanguard release
|
|
|
|
err = s.checkProjectLimit(ctx, auth.User.ID)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-03 14:46:57 +01:00
|
|
|
tx, err := s.store.BeginTx(ctx)
|
2018-12-06 15:19:47 +00:00
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(internalErrMsg)
|
2018-12-06 15:19:47 +00:00
|
|
|
}
|
|
|
|
|
2019-06-03 14:46:57 +01:00
|
|
|
err = withTx(tx, func(tx DBTx) (err error) {
|
|
|
|
p, err = tx.Projects().Insert(ctx,
|
|
|
|
&Project{
|
|
|
|
Description: projectInfo.Description,
|
|
|
|
Name: projectInfo.Name,
|
|
|
|
},
|
|
|
|
)
|
2018-12-26 14:00:53 +00:00
|
|
|
if err != nil {
|
2019-06-03 14:46:57 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2019-06-03 14:46:57 +01:00
|
|
|
_, err = tx.ProjectMembers().Insert(ctx, auth.User.ID, p.ID)
|
2019-04-10 01:15:12 +01:00
|
|
|
if err != nil {
|
2019-06-03 14:46:57 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2019-04-10 01:15:12 +01:00
|
|
|
}
|
2018-12-26 14:00:53 +00:00
|
|
|
|
2019-06-06 17:07:14 +01:00
|
|
|
return err
|
2019-06-03 14:46:57 +01:00
|
|
|
})
|
2018-12-10 12:29:01 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2019-06-03 14:46:57 +01:00
|
|
|
return nil, err
|
2018-12-10 12:29:01 +00:00
|
|
|
}
|
2018-12-06 15:19:47 +00:00
|
|
|
|
2019-06-03 14:46:57 +01:00
|
|
|
return p, nil
|
2018-11-26 10:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteProject is a method for deleting project by id
|
2018-12-20 20:10:27 +00:00
|
|
|
func (s *Service) DeleteProject(ctx context.Context, projectID uuid.UUID) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-03-29 12:13:37 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-12-18 17:43:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-03-29 12:13:37 +00:00
|
|
|
if _, err = s.isProjectMember(ctx, auth.User.ID, projectID); err != nil {
|
|
|
|
return ErrUnauthorized.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
err = s.store.Projects().Delete(ctx, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-11-26 10:47:23 +00:00
|
|
|
}
|
|
|
|
|
2018-11-28 16:20:23 +00:00
|
|
|
// UpdateProject is a method for updating project description by id
|
2018-12-20 20:10:27 +00:00
|
|
|
func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, description string) (p *Project, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-03-29 12:13:37 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-11-26 10:47:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-29 12:13:37 +00:00
|
|
|
isMember, err := s.isProjectMember(ctx, auth.User.ID, projectID)
|
2018-11-26 10:47:23 +00:00
|
|
|
if err != nil {
|
2019-03-29 12:13:37 +00:00
|
|
|
return nil, ErrUnauthorized.Wrap(err)
|
2018-11-26 10:47:23 +00:00
|
|
|
}
|
|
|
|
|
2019-03-29 12:13:37 +00:00
|
|
|
project := isMember.project
|
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 {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(internalErrMsg)
|
2018-11-26 10:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return project, nil
|
|
|
|
}
|
|
|
|
|
2018-12-21 15:41:53 +00:00
|
|
|
// AddProjectMembers adds users by email to given project
|
2019-03-06 15:42:19 +00:00
|
|
|
func (s *Service) AddProjectMembers(ctx context.Context, projectID uuid.UUID, emails []string) (users []*User, err error) {
|
2018-12-20 20:10:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-27 15:30:15 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-12-06 14:40:32 +00:00
|
|
|
if err != nil {
|
2019-03-06 15:42:19 +00:00
|
|
|
return nil, err
|
2018-12-06 14:40:32 +00:00
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
if _, err = s.isProjectMember(ctx, auth.User.ID, projectID); err != nil {
|
2019-03-06 15:42:19 +00:00
|
|
|
return nil, ErrUnauthorized.Wrap(err)
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
|
|
|
|
2018-12-21 15:41:53 +00:00
|
|
|
var userErr errs.Group
|
|
|
|
|
|
|
|
// collect user querying errors
|
|
|
|
for _, email := range emails {
|
|
|
|
user, err := s.store.Users().GetByEmail(ctx, email)
|
|
|
|
if err != nil {
|
|
|
|
userErr.Add(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-03-06 15:42:19 +00:00
|
|
|
users = append(users, user)
|
2018-12-21 15:41:53 +00:00
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
if err = userErr.Err(); err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(teamMemberDoesNotExistErrMsg)
|
2018-12-21 15:41:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// add project members in transaction scope
|
|
|
|
tx, err := s.store.BeginTx(ctx)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(internalErrMsg)
|
2018-12-21 15:41:53 +00:00
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
err = errs.Combine(err, tx.Rollback())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
}()
|
|
|
|
|
2019-03-06 15:42:19 +00:00
|
|
|
for _, user := range users {
|
|
|
|
_, err = tx.ProjectMembers().Insert(ctx, user.ID, projectID)
|
2018-12-21 15:41:53 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(internalErrMsg)
|
2018-12-21 15:41:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-06 15:42:19 +00:00
|
|
|
return users, nil
|
2018-12-06 14:40:32 +00:00
|
|
|
}
|
|
|
|
|
2018-12-21 15:41:53 +00:00
|
|
|
// DeleteProjectMembers removes users by email from given project
|
|
|
|
func (s *Service) DeleteProjectMembers(ctx context.Context, projectID uuid.UUID, emails []string) (err error) {
|
2018-12-20 20:10:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-27 15:30:15 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-12-06 14:40:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
if _, err = s.isProjectMember(ctx, auth.User.ID, projectID); err != nil {
|
|
|
|
return ErrUnauthorized.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2018-12-21 15:41:53 +00:00
|
|
|
var userIDs []uuid.UUID
|
|
|
|
var userErr errs.Group
|
|
|
|
|
|
|
|
// collect user querying errors
|
|
|
|
for _, email := range emails {
|
|
|
|
user, err := s.store.Users().GetByEmail(ctx, email)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
userErr.Add(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
userIDs = append(userIDs, user.ID)
|
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
if err = userErr.Err(); err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(teamMemberDoesNotExistErrMsg)
|
2018-12-21 15:41:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// delete project members in transaction scope
|
|
|
|
tx, err := s.store.BeginTx(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
err = errs.Combine(err, tx.Rollback())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
}()
|
|
|
|
|
2018-12-21 15:41:53 +00:00
|
|
|
for _, uID := range userIDs {
|
2018-12-27 15:30:15 +00:00
|
|
|
err = tx.ProjectMembers().Delete(ctx, uID, projectID)
|
2018-12-21 15:41:53 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2018-12-21 15:41:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
return nil
|
2018-12-06 14:40:32 +00:00
|
|
|
}
|
|
|
|
|
2018-12-10 11:38:42 +00:00
|
|
|
// GetProjectMembers returns ProjectMembers for given Project
|
2018-12-28 12:07:35 +00:00
|
|
|
func (s *Service) GetProjectMembers(ctx context.Context, projectID uuid.UUID, pagination Pagination) (pm []ProjectMember, err error) {
|
2018-12-20 20:10:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-04-05 16:08:14 +01:00
|
|
|
auth, err := GetAuth(ctx)
|
2018-12-10 11:38:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-05 16:08:14 +01:00
|
|
|
_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrUnauthorized.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2018-12-28 12:07:35 +00:00
|
|
|
if pagination.Limit > maxLimit {
|
|
|
|
pagination.Limit = maxLimit
|
2018-12-19 13:03:12 +00:00
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
pm, err = s.store.ProjectMembers().GetByProjectID(ctx, projectID, pagination)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2018-12-10 11:38:42 +00:00
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
// CreateAPIKey creates new api key
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) CreateAPIKey(ctx context.Context, projectID uuid.UUID, name string) (_ *APIKeyInfo, _ *macaroon.APIKey, err error) {
|
2018-12-27 15:30:15 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-26 14:00:53 +00:00
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
auth, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2018-12-27 15:30:15 +00:00
|
|
|
_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
|
2018-12-26 14:00:53 +00:00
|
|
|
if err != nil {
|
2018-12-27 15:30:15 +00:00
|
|
|
return nil, nil, ErrUnauthorized.Wrap(err)
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2019-05-24 17:51:27 +01:00
|
|
|
secret, err := macaroon.NewSecret()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := macaroon.NewAPIKey(secret)
|
2018-12-26 14:00:53 +00:00
|
|
|
if err != nil {
|
2018-12-27 15:30:15 +00:00
|
|
|
return nil, nil, err
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2019-05-24 17:51:27 +01:00
|
|
|
info, err := s.store.APIKeys().Create(ctx, key.Head(), APIKeyInfo{
|
2018-12-26 14:00:53 +00:00
|
|
|
Name: name,
|
|
|
|
ProjectID: projectID,
|
2019-05-24 17:51:27 +01:00
|
|
|
Secret: secret,
|
2018-12-26 14:00:53 +00:00
|
|
|
})
|
2019-04-10 01:15:12 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return info, key, nil
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetAPIKeyInfo retrieves api key by id
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) GetAPIKeyInfo(ctx context.Context, id uuid.UUID) (_ *APIKeyInfo, err error) {
|
2018-12-27 15:30:15 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
auth, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := s.store.APIKeys().Get(ctx, id)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(internalErrMsg)
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.isProjectMember(ctx, auth.User.ID, key.ProjectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrUnauthorized.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return key, nil
|
|
|
|
}
|
|
|
|
|
2019-02-13 11:34:40 +00:00
|
|
|
// DeleteAPIKeys deletes api key by id
|
|
|
|
func (s *Service) DeleteAPIKeys(ctx context.Context, ids []uuid.UUID) (err error) {
|
2018-12-27 15:30:15 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
auth, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-02-13 11:34:40 +00:00
|
|
|
var keysErr errs.Group
|
|
|
|
|
|
|
|
for _, keyID := range ids {
|
|
|
|
key, err := s.store.APIKeys().Get(ctx, keyID)
|
|
|
|
if err != nil {
|
|
|
|
keysErr.Add(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.isProjectMember(ctx, auth.User.ID, key.ProjectID)
|
|
|
|
if err != nil {
|
|
|
|
keysErr.Add(ErrUnauthorized.Wrap(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = keysErr.Err(); err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
|
|
|
|
2019-02-13 11:34:40 +00:00
|
|
|
tx, err := s.store.BeginTx(ctx)
|
2018-12-27 15:30:15 +00:00
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
|
|
|
|
2019-02-13 11:34:40 +00:00
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
err = errs.Combine(err, tx.Rollback())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
}()
|
|
|
|
|
|
|
|
for _, keyToDeleteID := range ids {
|
|
|
|
err = tx.APIKeys().Delete(ctx, keyToDeleteID)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2019-02-13 11:34:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetAPIKeysInfoByProjectID retrieves all api keys for a given project
|
|
|
|
func (s *Service) GetAPIKeysInfoByProjectID(ctx context.Context, projectID uuid.UUID) (info []APIKeyInfo, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
auth, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrUnauthorized.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
info, err = s.store.APIKeys().GetByProjectID(ctx, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return info, nil
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2019-04-04 15:56:20 +01:00
|
|
|
// GetProjectUsage retrieves project usage for a given period
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) GetProjectUsage(ctx context.Context, projectID uuid.UUID, since, before time.Time) (_ *ProjectUsage, err error) {
|
2019-04-04 15:56:20 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
auth, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
projectUsage, err := s.store.UsageRollups().GetProjectTotal(ctx, projectID, since, before)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New(internalErrMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return projectUsage, nil
|
2019-04-04 15:56:20 +01:00
|
|
|
}
|
|
|
|
|
2019-05-16 11:43:46 +01:00
|
|
|
// GetBucketTotals retrieves paged bucket total usages since project creation
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) GetBucketTotals(ctx context.Context, projectID uuid.UUID, cursor BucketUsageCursor, before time.Time) (_ *BucketUsagePage, err error) {
|
2019-05-16 11:43:46 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
auth, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
isMember, err := s.isProjectMember(ctx, auth.User.ID, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.store.UsageRollups().GetBucketTotals(ctx, projectID, cursor, isMember.project.CreatedAt, before)
|
|
|
|
}
|
|
|
|
|
2019-04-10 00:14:19 +01:00
|
|
|
// GetBucketUsageRollups retrieves summed usage rollups for every bucket of particular project for a given period
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) GetBucketUsageRollups(ctx context.Context, projectID uuid.UUID, since, before time.Time) (_ []BucketUsageRollup, err error) {
|
2019-04-10 00:14:19 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
auth, err := GetAuth(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.store.UsageRollups().GetBucketUsageRollups(ctx, projectID, since, before)
|
|
|
|
}
|
|
|
|
|
2019-06-06 17:07:14 +01:00
|
|
|
// CreateMonthlyProjectInvoices creates invoices for all created projects on monthly basis.
|
|
|
|
// Edge Dates are derived from the date parameter taking UTC year and month, then adding first
|
|
|
|
// and last date of the month accordingly
|
|
|
|
func (s *Service) CreateMonthlyProjectInvoices(ctx context.Context, date time.Time) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
utc := date.UTC()
|
|
|
|
startDate := time.Date(utc.Year(), utc.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
endDate := time.Date(utc.Year(), utc.Month()+1, 1, 0, 0, 0, -1, time.UTC)
|
|
|
|
|
|
|
|
// disallow invoice generation for future periods
|
|
|
|
if endDate.After(time.Now()) {
|
|
|
|
return errs.New("can not create invoices for future periods")
|
|
|
|
}
|
|
|
|
|
|
|
|
projects, err := s.store.Projects().GetCreatedBefore(ctx, endDate)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var invoiceError errs.Group
|
|
|
|
for _, proj := range projects {
|
|
|
|
// check if there is entry in the db for selected project and date
|
|
|
|
// range, if so skip project as invoice has already been created
|
|
|
|
// this way we can run this function for the second time to generate
|
|
|
|
// invoices only for project that failed before
|
|
|
|
_, err := s.store.ProjectInvoiceStamps().GetByProjectIDStartDate(ctx, proj.ID, startDate)
|
|
|
|
if err == nil {
|
|
|
|
s.log.Info(fmt.Sprintf("skipping project %s during invoice generation, invoice stamp already exists", proj.ID))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
paymentInfo, err := s.store.ProjectPayments().GetByProjectID(ctx, proj.ID)
|
|
|
|
if err != nil {
|
|
|
|
invoiceError.Add(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
payerInfo, err := s.store.UserPayments().Get(ctx, paymentInfo.PayerID)
|
|
|
|
if err != nil {
|
|
|
|
invoiceError.Add(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
totals, err := s.store.UsageRollups().GetProjectTotal(ctx, proj.ID, startDate, endDate)
|
|
|
|
if err != nil {
|
|
|
|
invoiceError.Add(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
inv, err := s.pm.CreateProjectInvoice(ctx,
|
|
|
|
payments.CreateProjectInvoiceParams{
|
|
|
|
ProjectName: proj.Name,
|
|
|
|
CustomerID: payerInfo.CustomerID,
|
|
|
|
PaymentMethodID: paymentInfo.PaymentMethodID,
|
|
|
|
Storage: totals.Storage,
|
|
|
|
Egress: totals.Egress,
|
|
|
|
ObjectCount: totals.ObjectCount,
|
|
|
|
StartDate: startDate,
|
|
|
|
EndDate: endDate,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
invoiceError.Add(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.store.ProjectInvoiceStamps().Create(ctx,
|
|
|
|
ProjectInvoiceStamp{
|
|
|
|
ProjectID: proj.ID,
|
|
|
|
InvoiceID: inv.ID,
|
|
|
|
StartDate: startDate,
|
|
|
|
EndDate: endDate,
|
|
|
|
CreatedAt: inv.CreatedAt,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
invoiceError.Add(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return invoiceError.Err()
|
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
// Authorize validates token from context and returns authorized Authorization
|
2018-12-20 20:10:27 +00:00
|
|
|
func (s *Service) Authorize(ctx context.Context) (a Authorization, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-27 14:20:58 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-01-15 13:03:24 +00:00
|
|
|
token, err := consoleauth.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
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
claims, err := s.authenticate(ctx, token)
|
2018-11-27 14:20:58 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-03-19 17:55:43 +00:00
|
|
|
// checkProjectLimit is used to check if user is able to create a new project
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) checkProjectLimit(ctx context.Context, userID uuid.UUID) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-03-19 17:55:43 +00:00
|
|
|
registrationToken, err := s.store.RegistrationTokens().GetByOwnerID(ctx, userID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
projects, err := s.GetUsersProjects(ctx)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(internalErrMsg)
|
2019-03-19 17:55:43 +00:00
|
|
|
}
|
|
|
|
if len(projects) >= registrationToken.ProjectLimit {
|
2019-04-10 01:15:12 +01:00
|
|
|
return errs.New(projLimitVanguardErrMsg)
|
2019-03-19 17:55:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateRegToken creates new registration token. Needed for testing
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) CreateRegToken(ctx context.Context, projLimit int) (_ *RegistrationToken, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-03-19 17:55:43 +00:00
|
|
|
return s.store.RegistrationTokens().Create(ctx, projLimit)
|
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
// createToken creates string representation
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) createToken(ctx context.Context, claims *consoleauth.Claims) (_ string, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2018-11-22 10:38:58 +00:00
|
|
|
json, err := claims.JSON()
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return "", errs.New(internalErrMsg)
|
2018-11-22 10:38:58 +00:00
|
|
|
}
|
|
|
|
|
2019-01-15 13:03:24 +00:00
|
|
|
token := consoleauth.Token{Payload: json}
|
2018-11-22 10:38:58 +00:00
|
|
|
err = signToken(&token, s.Signer)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return "", errs.New(internalErrMsg)
|
2018-11-22 10:38:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return token.String(), nil
|
|
|
|
}
|
|
|
|
|
2018-11-27 14:20:58 +00:00
|
|
|
// authenticate validates token signature and returns authenticated *satelliteauth.Authorization
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) authenticate(ctx context.Context, token consoleauth.Token) (_ *consoleauth.Claims, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-14 10:50:15 +00:00
|
|
|
signature := token.Signature
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
err = signToken(&token, s.Signer)
|
2018-11-14 10:50:15 +00:00
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(internalErrMsg)
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if subtle.ConstantTimeCompare(signature, token.Signature) != 1 {
|
|
|
|
return nil, errs.New("incorrect signature")
|
|
|
|
}
|
|
|
|
|
2019-01-15 13:03:24 +00:00
|
|
|
claims, err := consoleauth.FromJSON(token.Payload)
|
2018-11-14 10:50:15 +00:00
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return nil, errs.New(internalErrMsg)
|
2018-11-14 10:50:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return claims, nil
|
|
|
|
}
|
|
|
|
|
2018-11-21 15:51:43 +00:00
|
|
|
// authorize checks claims and returns authorized User
|
2019-06-04 12:55:38 +01:00
|
|
|
func (s *Service) authorize(ctx context.Context, claims *consoleauth.Claims) (_ *User, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
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
|
|
|
}
|
2018-12-27 15:30:15 +00:00
|
|
|
|
|
|
|
// isProjectMember is return type of isProjectMember service method
|
|
|
|
type isProjectMember struct {
|
|
|
|
project *Project
|
|
|
|
membership *ProjectMember
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrNoMembership is error type of not belonging to a specific project
|
|
|
|
var ErrNoMembership = errs.Class("no membership error")
|
|
|
|
|
|
|
|
// isProjectMember checks if the user is a member of given project
|
|
|
|
func (s *Service) isProjectMember(ctx context.Context, userID uuid.UUID, projectID uuid.UUID) (result isProjectMember, err error) {
|
2019-06-04 12:55:38 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-27 15:30:15 +00:00
|
|
|
project, err := s.store.Projects().Get(ctx, projectID)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return result, errs.New(internalErrMsg)
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
memberships, err := s.store.ProjectMembers().GetByMemberID(ctx, userID)
|
|
|
|
if err != nil {
|
2019-04-10 01:15:12 +01:00
|
|
|
return result, errs.New(internalErrMsg)
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, membership := range memberships {
|
|
|
|
if membership.ProjectID == projectID {
|
2019-05-29 14:30:16 +01:00
|
|
|
result.membership = &membership // nolint: scopelint
|
2018-12-27 15:30:15 +00:00
|
|
|
result.project = project
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:15:12 +01:00
|
|
|
return isProjectMember{}, ErrNoMembership.New(unauthorizedErrMsg)
|
2018-12-27 15:30:15 +00:00
|
|
|
}
|
2019-06-03 14:46:57 +01:00
|
|
|
|
|
|
|
// withTx is a helper function for executing db operations
|
|
|
|
// in transaction scope
|
|
|
|
func withTx(tx DBTx, cb func(tx DBTx) error) (err error) {
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
err = errs.Combine(err, tx.Rollback())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
}()
|
|
|
|
|
|
|
|
return cb(tx)
|
|
|
|
}
|