1// Copyright (C) 2019 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package console
5
6import (
7	"context"
8	"crypto/subtle"
9	"database/sql"
10	"errors"
11	"fmt"
12	"net/mail"
13	"sort"
14	"time"
15
16	"github.com/spacemonkeygo/monkit/v3"
17	"github.com/spf13/pflag"
18	"github.com/stripe/stripe-go/v72"
19	"github.com/zeebo/errs"
20	"go.uber.org/zap"
21	"golang.org/x/crypto/bcrypt"
22
23	"storj.io/common/macaroon"
24	"storj.io/common/memory"
25	"storj.io/common/storj"
26	"storj.io/common/uuid"
27	"storj.io/private/cfgstruct"
28	"storj.io/storj/satellite/accounting"
29	"storj.io/storj/satellite/analytics"
30	"storj.io/storj/satellite/console/consoleauth"
31	"storj.io/storj/satellite/payments"
32	"storj.io/storj/satellite/rewards"
33)
34
35var mon = monkit.Package()
36
37const (
38	// maxLimit specifies the limit for all paged queries.
39	maxLimit = 50
40
41	// TokenExpirationTime specifies the expiration time for
42	// auth tokens, account recovery tokens, and activation tokens.
43	TokenExpirationTime = 24 * time.Hour
44
45	// TestPasswordCost is the hashing complexity to use for testing.
46	TestPasswordCost = bcrypt.MinCost
47)
48
49// Error messages.
50const (
51	unauthorizedErrMsg                   = "You are not authorized to perform this action"
52	emailUsedErrMsg                      = "This email is already in use, try another"
53	passwordRecoveryTokenIsExpiredErrMsg = "Your password recovery link has expired, please request another one"
54	credentialsErrMsg                    = "Your email or password was incorrect, please try again"
55	passwordIncorrectErrMsg              = "Your password needs at least %d characters long"
56	projectOwnerDeletionForbiddenErrMsg  = "%s is a project owner and can not be deleted"
57	apiKeyWithNameExistsErrMsg           = "An API Key with this name already exists in this project, please use a different name"
58	apiKeyWithNameDoesntExistErrMsg      = "An API Key with this name doesn't exist in this project."
59	teamMemberDoesNotExistErrMsg         = `There is no account on this Satellite for the user(s) you have entered.
60									     Please add team members with active accounts`
61
62	usedRegTokenErrMsg = "This registration token has already been used"
63	projLimitErrMsg    = "Sorry, project creation is limited for your account. Please contact support!"
64)
65
66var (
67	// Error describes internal console error.
68	Error = errs.Class("console service")
69
70	// ErrNoMembership is error type of not belonging to a specific project.
71	ErrNoMembership = errs.Class("no membership")
72
73	// ErrTokenExpiration is error type of token reached expiration time.
74	ErrTokenExpiration = errs.Class("token expiration")
75
76	// ErrProjLimit is error type of project limit.
77	ErrProjLimit = errs.Class("project limit")
78
79	// ErrUsage is error type of project usage.
80	ErrUsage = errs.Class("project usage")
81
82	// ErrEmailUsed is error type that occurs on repeating auth attempts with email.
83	ErrEmailUsed = errs.Class("email used")
84
85	// ErrNoAPIKey is error type that occurs when there is no api key found.
86	ErrNoAPIKey = errs.Class("no api key found")
87
88	// ErrRegToken describes registration token errors.
89	ErrRegToken = errs.Class("registration token")
90
91	// ErrRecaptcha describes reCAPTCHA validation errors.
92	ErrRecaptcha = errs.Class("recaptcha validation")
93
94	// ErrRecoveryToken describes account recovery token errors.
95	ErrRecoveryToken = errs.Class("recovery token")
96)
97
98// Service is handling accounts related logic.
99//
100// architecture: Service
101type Service struct {
102	Signer
103
104	log, auditLogger  *zap.Logger
105	store             DB
106	projectAccounting accounting.ProjectAccounting
107	projectUsage      *accounting.Service
108	buckets           Buckets
109	partners          *rewards.PartnersService
110	accounts          payments.Accounts
111	recaptchaHandler  RecaptchaHandler
112	analytics         *analytics.Service
113
114	config Config
115}
116
117func init() {
118	var c Config
119	cfgstruct.Bind(pflag.NewFlagSet("", pflag.PanicOnError), &c, cfgstruct.UseTestDefaults())
120	if c.PasswordCost != TestPasswordCost {
121		panic("invalid test constant defined in struct tag")
122	}
123	cfgstruct.Bind(pflag.NewFlagSet("", pflag.PanicOnError), &c, cfgstruct.UseReleaseDefaults())
124	if c.PasswordCost != 0 {
125		panic("invalid release constant defined in struct tag. should be 0 (=automatic)")
126	}
127}
128
129// Config keeps track of core console service configuration parameters.
130type Config struct {
131	PasswordCost            int  `help:"password hashing cost (0=automatic)" testDefault:"4" default:"0"`
132	OpenRegistrationEnabled bool `help:"enable open registration" default:"false" testDefault:"true"`
133	DefaultProjectLimit     int  `help:"default project limits for users" default:"1" testDefault:"5"`
134	UsageLimits             UsageLimitsConfig
135	Recaptcha               RecaptchaConfig
136}
137
138// RecaptchaConfig contains configurations for the reCAPTCHA system.
139type RecaptchaConfig struct {
140	Enabled   bool   `help:"whether or not reCAPTCHA is enabled for user registration" default:"false"`
141	SiteKey   string `help:"reCAPTCHA site key"`
142	SecretKey string `help:"reCAPTCHA secret key"`
143}
144
145// PaymentsService separates all payment related functionality.
146type PaymentsService struct {
147	service *Service
148}
149
150// NewService returns new instance of Service.
151func NewService(log *zap.Logger, signer Signer, store DB, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets Buckets, partners *rewards.PartnersService, accounts payments.Accounts, analytics *analytics.Service, config Config) (*Service, error) {
152	if signer == nil {
153		return nil, errs.New("signer can't be nil")
154	}
155	if store == nil {
156		return nil, errs.New("store can't be nil")
157	}
158	if log == nil {
159		return nil, errs.New("log can't be nil")
160	}
161	if config.PasswordCost == 0 {
162		config.PasswordCost = bcrypt.DefaultCost
163	}
164
165	return &Service{
166		log:               log,
167		auditLogger:       log.Named("auditlog"),
168		Signer:            signer,
169		store:             store,
170		projectAccounting: projectAccounting,
171		projectUsage:      projectUsage,
172		buckets:           buckets,
173		partners:          partners,
174		accounts:          accounts,
175		recaptchaHandler:  NewDefaultRecaptcha(config.Recaptcha.SecretKey),
176		analytics:         analytics,
177		config:            config,
178	}, nil
179}
180
181func getRequestingIP(ctx context.Context) (source, forwardedFor string) {
182	if req := GetRequest(ctx); req != nil {
183		return req.RemoteAddr, req.Header.Get("X-Forwarded-For")
184	}
185	return "", ""
186}
187
188func (s *Service) auditLog(ctx context.Context, operation string, userID *uuid.UUID, email string, extra ...zap.Field) {
189	sourceIP, forwardedForIP := getRequestingIP(ctx)
190	fields := append(
191		make([]zap.Field, 0, len(extra)+5),
192		zap.String("operation", operation),
193		zap.String("source-ip", sourceIP),
194		zap.String("forwarded-for-ip", forwardedForIP),
195	)
196	if userID != nil {
197		fields = append(fields, zap.String("userID", userID.String()))
198	}
199	if email != "" {
200		fields = append(fields, zap.String("email", email))
201	}
202	fields = append(fields, fields...)
203	s.auditLogger.Info("console activity", fields...)
204}
205
206func (s *Service) getAuthAndAuditLog(ctx context.Context, operation string, extra ...zap.Field) (Authorization, error) {
207	auth, err := GetAuth(ctx)
208	if err != nil {
209		sourceIP, forwardedForIP := getRequestingIP(ctx)
210		s.auditLogger.Info("console activity unauthorized",
211			append(append(
212				make([]zap.Field, 0, len(extra)+4),
213				zap.String("operation", operation),
214				zap.Error(err),
215				zap.String("source-ip", sourceIP),
216				zap.String("forwarded-for-ip", forwardedForIP),
217			), extra...)...)
218		return Authorization{}, err
219	}
220	s.auditLog(ctx, operation, &auth.User.ID, auth.User.Email, extra...)
221	return auth, nil
222}
223
224// Payments separates all payment related functionality.
225func (s *Service) Payments() PaymentsService {
226	return PaymentsService{service: s}
227}
228
229// SetupAccount creates payment account for authorized user.
230func (paymentService PaymentsService) SetupAccount(ctx context.Context) (err error) {
231	defer mon.Task()(&ctx)(&err)
232
233	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "setup payment account")
234	if err != nil {
235		return Error.Wrap(err)
236	}
237
238	return paymentService.service.accounts.Setup(ctx, auth.User.ID, auth.User.Email)
239}
240
241// AccountBalance return account balance.
242func (paymentService PaymentsService) AccountBalance(ctx context.Context) (balance payments.Balance, err error) {
243	defer mon.Task()(&ctx)(&err)
244
245	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "get account balance")
246	if err != nil {
247		return payments.Balance{}, Error.Wrap(err)
248	}
249
250	return paymentService.service.accounts.Balance(ctx, auth.User.ID)
251}
252
253// AddCreditCard is used to save new credit card and attach it to payment account.
254func (paymentService PaymentsService) AddCreditCard(ctx context.Context, creditCardToken string) (err error) {
255	defer mon.Task()(&ctx, creditCardToken)(&err)
256
257	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "add credit card")
258	if err != nil {
259		return Error.Wrap(err)
260	}
261
262	err = paymentService.service.accounts.CreditCards().Add(ctx, auth.User.ID, creditCardToken)
263	if err != nil {
264		return Error.Wrap(err)
265	}
266
267	if !auth.User.PaidTier {
268		// put this user into the paid tier and convert projects to upgraded limits.
269		err = paymentService.service.store.Users().UpdatePaidTier(ctx, auth.User.ID, true,
270			paymentService.service.config.UsageLimits.Bandwidth.Paid,
271			paymentService.service.config.UsageLimits.Storage.Paid)
272		if err != nil {
273			return Error.Wrap(err)
274		}
275
276		projects, err := paymentService.service.store.Projects().GetOwn(ctx, auth.User.ID)
277		if err != nil {
278			return Error.Wrap(err)
279		}
280		for _, project := range projects {
281			if project.StorageLimit == nil || *project.StorageLimit < paymentService.service.config.UsageLimits.Storage.Paid {
282				project.StorageLimit = new(memory.Size)
283				*project.StorageLimit = paymentService.service.config.UsageLimits.Storage.Paid
284			}
285			if project.BandwidthLimit == nil || *project.BandwidthLimit < paymentService.service.config.UsageLimits.Bandwidth.Paid {
286				project.BandwidthLimit = new(memory.Size)
287				*project.BandwidthLimit = paymentService.service.config.UsageLimits.Bandwidth.Paid
288			}
289			err = paymentService.service.store.Projects().Update(ctx, &project)
290			if err != nil {
291				return Error.Wrap(err)
292			}
293		}
294	}
295
296	return nil
297}
298
299// MakeCreditCardDefault makes a credit card default payment method.
300func (paymentService PaymentsService) MakeCreditCardDefault(ctx context.Context, cardID string) (err error) {
301	defer mon.Task()(&ctx, cardID)(&err)
302
303	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "make credit card default")
304	if err != nil {
305		return Error.Wrap(err)
306	}
307
308	return paymentService.service.accounts.CreditCards().MakeDefault(ctx, auth.User.ID, cardID)
309}
310
311// ProjectsCharges returns how much money current user will be charged for each project which he owns.
312func (paymentService PaymentsService) ProjectsCharges(ctx context.Context, since, before time.Time) (_ []payments.ProjectCharge, err error) {
313	defer mon.Task()(&ctx)(&err)
314
315	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "project charges")
316	if err != nil {
317		return nil, Error.Wrap(err)
318	}
319
320	return paymentService.service.accounts.ProjectCharges(ctx, auth.User.ID, since, before)
321}
322
323// ListCreditCards returns a list of credit cards for a given payment account.
324func (paymentService PaymentsService) ListCreditCards(ctx context.Context) (_ []payments.CreditCard, err error) {
325	defer mon.Task()(&ctx)(&err)
326
327	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "list credit cards")
328	if err != nil {
329		return nil, Error.Wrap(err)
330	}
331
332	return paymentService.service.accounts.CreditCards().List(ctx, auth.User.ID)
333}
334
335// RemoveCreditCard is used to detach a credit card from payment account.
336func (paymentService PaymentsService) RemoveCreditCard(ctx context.Context, cardID string) (err error) {
337	defer mon.Task()(&ctx, cardID)(&err)
338
339	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "remove credit card")
340	if err != nil {
341		return Error.Wrap(err)
342	}
343
344	return paymentService.service.accounts.CreditCards().Remove(ctx, auth.User.ID, cardID)
345}
346
347// BillingHistory returns a list of billing history items for payment account.
348func (paymentService PaymentsService) BillingHistory(ctx context.Context) (billingHistory []*BillingHistoryItem, err error) {
349	defer mon.Task()(&ctx)(&err)
350
351	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "get billing history")
352	if err != nil {
353		return nil, Error.Wrap(err)
354	}
355
356	invoices, couponUsages, err := paymentService.service.accounts.Invoices().ListWithDiscounts(ctx, auth.User.ID)
357	if err != nil {
358		return nil, Error.Wrap(err)
359	}
360
361	for _, invoice := range invoices {
362		billingHistory = append(billingHistory, &BillingHistoryItem{
363			ID:          invoice.ID,
364			Description: invoice.Description,
365			Amount:      invoice.Amount,
366			Status:      invoice.Status,
367			Link:        invoice.Link,
368			End:         invoice.End,
369			Start:       invoice.Start,
370			Type:        Invoice,
371		})
372	}
373
374	txsInfos, err := paymentService.service.accounts.StorjTokens().ListTransactionInfos(ctx, auth.User.ID)
375	if err != nil {
376		return nil, Error.Wrap(err)
377	}
378
379	for _, info := range txsInfos {
380		billingHistory = append(billingHistory, &BillingHistoryItem{
381			ID:          info.ID.String(),
382			Description: "STORJ Token Deposit",
383			Amount:      info.AmountCents,
384			Received:    info.ReceivedCents,
385			Status:      info.Status.String(),
386			Link:        info.Link,
387			Start:       info.CreatedAt,
388			End:         info.ExpiresAt,
389			Type:        Transaction,
390		})
391	}
392
393	charges, err := paymentService.service.accounts.Charges(ctx, auth.User.ID)
394	if err != nil {
395		return nil, Error.Wrap(err)
396	}
397
398	for _, charge := range charges {
399		desc := fmt.Sprintf("Payment(%s %s)", charge.CardInfo.Brand, charge.CardInfo.LastFour)
400
401		billingHistory = append(billingHistory, &BillingHistoryItem{
402			ID:          charge.ID,
403			Description: desc,
404			Amount:      charge.Amount,
405			Start:       charge.CreatedAt,
406			Type:        Charge,
407		})
408	}
409
410	for _, usage := range couponUsages {
411		desc := "Coupon"
412		if usage.Coupon.Name != "" {
413			desc = usage.Coupon.Name
414		}
415		if usage.Coupon.PromoCode != "" {
416			desc += " (" + usage.Coupon.PromoCode + ")"
417		}
418
419		billingHistory = append(billingHistory, &BillingHistoryItem{
420			Description: desc,
421			Amount:      usage.Amount,
422			Start:       usage.PeriodStart,
423			End:         usage.PeriodEnd,
424			Type:        Coupon,
425		})
426	}
427
428	bonuses, err := paymentService.service.accounts.StorjTokens().ListDepositBonuses(ctx, auth.User.ID)
429	if err != nil {
430		return nil, Error.Wrap(err)
431	}
432
433	for _, bonus := range bonuses {
434		billingHistory = append(billingHistory,
435			&BillingHistoryItem{
436				Description: fmt.Sprintf("%d%% Bonus for STORJ Token Deposit", bonus.Percentage),
437				Amount:      bonus.AmountCents,
438				Status:      "Added to balance",
439				Start:       bonus.CreatedAt,
440				Type:        DepositBonus,
441			},
442		)
443	}
444
445	sort.SliceStable(billingHistory,
446		func(i, j int) bool {
447			return billingHistory[i].Start.After(billingHistory[j].Start)
448		},
449	)
450
451	return billingHistory, nil
452}
453
454// TokenDeposit creates new deposit transaction for adding STORJ tokens to account balance.
455func (paymentService PaymentsService) TokenDeposit(ctx context.Context, amount int64) (_ *payments.Transaction, err error) {
456	defer mon.Task()(&ctx)(&err)
457
458	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "token deposit")
459	if err != nil {
460		return nil, Error.Wrap(err)
461	}
462
463	tx, err := paymentService.service.accounts.StorjTokens().Deposit(ctx, auth.User.ID, amount)
464
465	return tx, Error.Wrap(err)
466}
467
468// checkOutstandingInvoice returns if the payment account has any unpaid/outstanding invoices or/and invoice items.
469func (paymentService PaymentsService) checkOutstandingInvoice(ctx context.Context) (err error) {
470	defer mon.Task()(&ctx)(&err)
471
472	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "get outstanding invoices")
473	if err != nil {
474		return err
475	}
476
477	invoices, err := paymentService.service.accounts.Invoices().List(ctx, auth.User.ID)
478	if err != nil {
479		return err
480	}
481	if len(invoices) > 0 {
482		for _, invoice := range invoices {
483			if invoice.Status != string(stripe.InvoiceStatusPaid) {
484				return ErrUsage.New("user has unpaid/pending invoices")
485			}
486		}
487	}
488
489	hasItems, err := paymentService.service.accounts.Invoices().CheckPendingItems(ctx, auth.User.ID)
490	if err != nil {
491		return err
492	}
493	if hasItems {
494		return ErrUsage.New("user has pending invoice items")
495	}
496	return nil
497}
498
499// checkProjectInvoicingStatus returns if for the given project there are outstanding project records and/or usage
500// which have not been applied/invoiced yet (meaning sent over to stripe).
501func (paymentService PaymentsService) checkProjectInvoicingStatus(ctx context.Context, projectID uuid.UUID) (unpaidUsage bool, err error) {
502	defer mon.Task()(&ctx)(&err)
503
504	_, err = paymentService.service.getAuthAndAuditLog(ctx, "project charges")
505	if err != nil {
506		return false, Error.Wrap(err)
507	}
508
509	return paymentService.service.accounts.CheckProjectInvoicingStatus(ctx, projectID)
510}
511
512// ApplyCouponCode applies a coupon code to a Stripe customer
513// and returns the coupon corresponding to the code.
514func (paymentService PaymentsService) ApplyCouponCode(ctx context.Context, couponCode string) (coupon *payments.Coupon, err error) {
515	defer mon.Task()(&ctx)(&err)
516
517	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "apply coupon code")
518	if err != nil {
519		return nil, Error.Wrap(err)
520	}
521
522	coupon, err = paymentService.service.accounts.Coupons().ApplyCouponCode(ctx, auth.User.ID, couponCode)
523	if err != nil {
524		return nil, Error.Wrap(err)
525	}
526
527	return coupon, nil
528}
529
530// GetCoupon returns the coupon applied to the user's account.
531func (paymentService PaymentsService) GetCoupon(ctx context.Context) (coupon *payments.Coupon, err error) {
532	defer mon.Task()(&ctx)(&err)
533
534	auth, err := paymentService.service.getAuthAndAuditLog(ctx, "get coupon")
535	if err != nil {
536		return nil, Error.Wrap(err)
537	}
538
539	coupon, err = paymentService.service.accounts.Coupons().GetByUserID(ctx, auth.User.ID)
540	if err != nil {
541		return nil, Error.Wrap(err)
542	}
543
544	return coupon, nil
545}
546
547// checkRegistrationSecret returns a RegistrationToken if applicable (nil if not), and an error
548// if and only if the registration shouldn't proceed.
549func (s *Service) checkRegistrationSecret(ctx context.Context, tokenSecret RegistrationSecret) (*RegistrationToken, error) {
550	if s.config.OpenRegistrationEnabled && tokenSecret.IsZero() {
551		// in this case we're going to let the registration happen without a token
552		return nil, nil
553	}
554
555	// in all other cases, require a registration token
556	registrationToken, err := s.store.RegistrationTokens().GetBySecret(ctx, tokenSecret)
557	if err != nil {
558		return nil, ErrUnauthorized.Wrap(err)
559	}
560	// if a registration token is already associated with an user ID, that means the token is already used
561	// we should terminate the account creation process and return an error
562	if registrationToken.OwnerID != nil {
563		return nil, ErrValidation.New(usedRegTokenErrMsg)
564	}
565
566	return registrationToken, nil
567}
568
569// CreateUser gets password hash value and creates new inactive User.
570func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret RegistrationSecret) (u *User, err error) {
571	defer mon.Task()(&ctx)(&err)
572
573	if s.config.Recaptcha.Enabled {
574		valid, err := s.recaptchaHandler.Verify(ctx, user.RecaptchaResponse, user.IP)
575		if err != nil {
576			s.log.Error("reCAPTCHA authorization failed", zap.Error(err))
577			return nil, ErrRecaptcha.Wrap(err)
578		}
579		if !valid {
580			return nil, ErrRecaptcha.New("reCAPTCHA validation unsuccessful")
581		}
582	}
583
584	if err := user.IsValid(); err != nil {
585		return nil, Error.Wrap(err)
586	}
587
588	registrationToken, err := s.checkRegistrationSecret(ctx, tokenSecret)
589	if err != nil {
590		return nil, ErrRegToken.Wrap(err)
591	}
592
593	u, err = s.store.Users().GetByEmail(ctx, user.Email)
594	if err == nil {
595		return nil, ErrEmailUsed.New(emailUsedErrMsg)
596	}
597	if !errors.Is(err, sql.ErrNoRows) {
598		return nil, Error.Wrap(err)
599	}
600
601	hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), s.config.PasswordCost)
602	if err != nil {
603		return nil, Error.Wrap(err)
604	}
605
606	// store data
607	err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error {
608		userID, err := uuid.New()
609		if err != nil {
610			return Error.Wrap(err)
611		}
612
613		newUser := &User{
614			ID:               userID,
615			Email:            user.Email,
616			FullName:         user.FullName,
617			ShortName:        user.ShortName,
618			PasswordHash:     hash,
619			Status:           Inactive,
620			IsProfessional:   user.IsProfessional,
621			Position:         user.Position,
622			CompanyName:      user.CompanyName,
623			EmployeeCount:    user.EmployeeCount,
624			HaveSalesContact: user.HaveSalesContact,
625		}
626
627		if user.UserAgent != nil {
628			newUser.UserAgent = user.UserAgent
629		}
630
631		if registrationToken != nil {
632			newUser.ProjectLimit = registrationToken.ProjectLimit
633		} else {
634			newUser.ProjectLimit = s.config.DefaultProjectLimit
635		}
636
637		// TODO: move the project limits into the registration token.
638		newUser.ProjectStorageLimit = s.config.UsageLimits.Storage.Free.Int64()
639		newUser.ProjectBandwidthLimit = s.config.UsageLimits.Bandwidth.Free.Int64()
640
641		u, err = tx.Users().Insert(ctx,
642			newUser,
643		)
644		if err != nil {
645			return Error.Wrap(err)
646		}
647
648		if registrationToken != nil {
649			err = tx.RegistrationTokens().UpdateOwner(ctx, registrationToken.Secret, u.ID)
650			if err != nil {
651				return Error.Wrap(err)
652			}
653		}
654
655		return nil
656	})
657
658	if err != nil {
659		return nil, Error.Wrap(err)
660	}
661
662	s.auditLog(ctx, "create user", nil, user.Email)
663
664	return u, nil
665}
666
667// TestSwapRecaptchaHandler replaces the existing handler for reCAPTCHAs with
668// the one specified for use in testing.
669func (s *Service) TestSwapRecaptchaHandler(h RecaptchaHandler) {
670	s.recaptchaHandler = h
671}
672
673// GenerateActivationToken - is a method for generating activation token.
674func (s *Service) GenerateActivationToken(ctx context.Context, id uuid.UUID, email string) (token string, err error) {
675	defer mon.Task()(&ctx)(&err)
676
677	// TODO: activation token should differ from auth token
678	claims := &consoleauth.Claims{
679		ID:         id,
680		Email:      email,
681		Expiration: time.Now().Add(time.Hour * 24),
682	}
683
684	return s.createToken(ctx, claims)
685}
686
687// GeneratePasswordRecoveryToken - is a method for generating password recovery token.
688func (s *Service) GeneratePasswordRecoveryToken(ctx context.Context, id uuid.UUID) (token string, err error) {
689	defer mon.Task()(&ctx)(&err)
690
691	resetPasswordToken, err := s.store.ResetPasswordTokens().GetByOwnerID(ctx, id)
692	if err == nil {
693		err := s.store.ResetPasswordTokens().Delete(ctx, resetPasswordToken.Secret)
694		if err != nil {
695			return "", Error.Wrap(err)
696		}
697	}
698
699	resetPasswordToken, err = s.store.ResetPasswordTokens().Create(ctx, id)
700	if err != nil {
701		return "", Error.Wrap(err)
702	}
703
704	s.auditLog(ctx, "generate password recovery token", &id, "")
705
706	return resetPasswordToken.Secret.String(), nil
707}
708
709// ActivateAccount - is a method for activating user account after registration.
710func (s *Service) ActivateAccount(ctx context.Context, activationToken string) (token string, err error) {
711	defer mon.Task()(&ctx)(&err)
712
713	parsedActivationToken, err := consoleauth.FromBase64URLString(activationToken)
714	if err != nil {
715		return "", Error.Wrap(err)
716	}
717
718	claims, err := s.authenticate(ctx, parsedActivationToken)
719	if err != nil {
720		return "", err
721	}
722
723	_, err = s.store.Users().GetByEmail(ctx, claims.Email)
724	if err == nil {
725		return "", ErrEmailUsed.New(emailUsedErrMsg)
726	}
727
728	user, err := s.store.Users().Get(ctx, claims.ID)
729	if err != nil {
730		return "", Error.Wrap(err)
731	}
732
733	now := time.Now()
734
735	if now.After(user.CreatedAt.Add(TokenExpirationTime)) {
736		return "", ErrTokenExpiration.Wrap(err)
737	}
738
739	user.Status = Active
740	err = s.store.Users().Update(ctx, user)
741	if err != nil {
742		return "", Error.Wrap(err)
743	}
744	s.auditLog(ctx, "activate account", &user.ID, user.Email)
745
746	s.analytics.TrackAccountVerified(user.ID, user.Email)
747
748	// now that the account is activated, create a token to be stored in a cookie to log the user in.
749	claims = &consoleauth.Claims{
750		ID:         user.ID,
751		Expiration: time.Now().Add(TokenExpirationTime),
752	}
753
754	token, err = s.createToken(ctx, claims)
755	if err != nil {
756		return "", err
757	}
758	s.auditLog(ctx, "login", &user.ID, user.Email)
759
760	s.analytics.TrackSignedIn(user.ID, user.Email)
761
762	return token, nil
763}
764
765// ResetPassword - is a method for resetting user password.
766func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, password string, t time.Time) (err error) {
767	defer mon.Task()(&ctx)(&err)
768
769	secret, err := ResetPasswordSecretFromBase64(resetPasswordToken)
770	if err != nil {
771		return ErrRecoveryToken.Wrap(err)
772	}
773	token, err := s.store.ResetPasswordTokens().GetBySecret(ctx, secret)
774	if err != nil {
775		return ErrRecoveryToken.Wrap(err)
776	}
777
778	user, err := s.store.Users().Get(ctx, *token.OwnerID)
779	if err != nil {
780		return Error.Wrap(err)
781	}
782
783	if err := ValidatePassword(password); err != nil {
784		return Error.Wrap(err)
785	}
786
787	if t.Sub(token.CreatedAt) > TokenExpirationTime {
788		return ErrRecoveryToken.Wrap(ErrTokenExpiration.New(passwordRecoveryTokenIsExpiredErrMsg))
789	}
790
791	hash, err := bcrypt.GenerateFromPassword([]byte(password), s.config.PasswordCost)
792	if err != nil {
793		return Error.Wrap(err)
794	}
795
796	user.PasswordHash = hash
797
798	err = s.store.Users().Update(ctx, user)
799	if err != nil {
800		return Error.Wrap(err)
801	}
802	s.auditLog(ctx, "password reset", &user.ID, user.Email)
803
804	if err = s.store.ResetPasswordTokens().Delete(ctx, token.Secret); err != nil {
805		return Error.Wrap(err)
806	}
807
808	return nil
809}
810
811// RevokeResetPasswordToken - is a method to revoke reset password token.
812func (s *Service) RevokeResetPasswordToken(ctx context.Context, resetPasswordToken string) (err error) {
813	defer mon.Task()(&ctx)(&err)
814
815	secret, err := ResetPasswordSecretFromBase64(resetPasswordToken)
816	if err != nil {
817		return Error.Wrap(err)
818	}
819
820	return s.store.ResetPasswordTokens().Delete(ctx, secret)
821}
822
823// Token authenticates User by credentials and returns auth token.
824func (s *Service) Token(ctx context.Context, request AuthUser) (token string, err error) {
825	defer mon.Task()(&ctx)(&err)
826
827	user, err := s.store.Users().GetByEmail(ctx, request.Email)
828	if err != nil {
829		return "", ErrUnauthorized.New(credentialsErrMsg)
830	}
831
832	err = bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(request.Password))
833	if err != nil {
834		return "", ErrUnauthorized.New(credentialsErrMsg)
835	}
836
837	if user.MFAEnabled {
838		if request.MFARecoveryCode != "" && request.MFAPasscode != "" {
839			return "", ErrMFAConflict.New(mfaConflictErrMsg)
840		}
841
842		if request.MFARecoveryCode != "" {
843			found := false
844			codeIndex := -1
845			for i, code := range user.MFARecoveryCodes {
846				if code == request.MFARecoveryCode {
847					found = true
848					codeIndex = i
849					break
850				}
851			}
852			if !found {
853				return "", ErrUnauthorized.New(mfaRecoveryInvalidErrMsg)
854			}
855
856			user.MFARecoveryCodes = append(user.MFARecoveryCodes[:codeIndex], user.MFARecoveryCodes[codeIndex+1:]...)
857
858			err = s.store.Users().Update(ctx, user)
859			if err != nil {
860				return "", err
861			}
862		} else if request.MFAPasscode != "" {
863			valid, err := ValidateMFAPasscode(request.MFAPasscode, user.MFASecretKey, time.Now())
864			if err != nil {
865				return "", ErrUnauthorized.Wrap(err)
866			}
867			if !valid {
868				return "", ErrUnauthorized.New(mfaPasscodeInvalidErrMsg)
869			}
870		} else {
871			return "", ErrMFALogin.Wrap(ErrMFAMissing.New(mfaRequiredErrMsg))
872		}
873	}
874
875	claims := consoleauth.Claims{
876		ID:         user.ID,
877		Expiration: time.Now().Add(TokenExpirationTime),
878	}
879
880	token, err = s.createToken(ctx, &claims)
881	if err != nil {
882		return "", err
883	}
884	s.auditLog(ctx, "login", &user.ID, user.Email)
885
886	s.analytics.TrackSignedIn(user.ID, user.Email)
887
888	return token, nil
889}
890
891// GetUser returns User by id.
892func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (u *User, err error) {
893	defer mon.Task()(&ctx)(&err)
894
895	user, err := s.store.Users().Get(ctx, id)
896	if err != nil {
897		return nil, Error.Wrap(err)
898	}
899
900	return user, nil
901}
902
903// GetUserID returns the User ID from the session.
904func (s *Service) GetUserID(ctx context.Context) (id uuid.UUID, err error) {
905	defer mon.Task()(&ctx)(&err)
906
907	auth, err := s.getAuthAndAuditLog(ctx, "get user ID")
908	if err != nil {
909		return uuid.UUID{}, Error.Wrap(err)
910	}
911	return auth.User.ID, nil
912}
913
914// GetUserByEmail returns User by email.
915func (s *Service) GetUserByEmail(ctx context.Context, email string) (u *User, err error) {
916	defer mon.Task()(&ctx)(&err)
917
918	result, err := s.store.Users().GetByEmail(ctx, email)
919	if err != nil {
920		return nil, Error.Wrap(err)
921	}
922
923	return result, nil
924}
925
926// UpdateAccount updates User.
927func (s *Service) UpdateAccount(ctx context.Context, fullName string, shortName string) (err error) {
928	defer mon.Task()(&ctx)(&err)
929	auth, err := s.getAuthAndAuditLog(ctx, "update account")
930	if err != nil {
931		return Error.Wrap(err)
932	}
933
934	// validate fullName
935	err = ValidateFullName(fullName)
936	if err != nil {
937		return ErrValidation.Wrap(err)
938	}
939
940	err = s.store.Users().Update(ctx, &User{
941		ID:           auth.User.ID,
942		FullName:     fullName,
943		ShortName:    shortName,
944		Email:        auth.User.Email,
945		PasswordHash: nil,
946		Status:       auth.User.Status,
947	})
948	if err != nil {
949		return Error.Wrap(err)
950	}
951
952	return nil
953}
954
955// ChangeEmail updates email for a given user.
956func (s *Service) ChangeEmail(ctx context.Context, newEmail string) (err error) {
957	defer mon.Task()(&ctx)(&err)
958	auth, err := s.getAuthAndAuditLog(ctx, "change email")
959	if err != nil {
960		return Error.Wrap(err)
961	}
962
963	if _, err := mail.ParseAddress(newEmail); err != nil {
964		return ErrValidation.Wrap(err)
965	}
966
967	_, err = s.store.Users().GetByEmail(ctx, newEmail)
968	if err == nil {
969		return ErrEmailUsed.New(emailUsedErrMsg)
970	}
971
972	auth.User.Email = newEmail
973	err = s.store.Users().Update(ctx, &auth.User)
974	if err != nil {
975		return Error.Wrap(err)
976	}
977
978	return nil
979}
980
981// ChangePassword updates password for a given user.
982func (s *Service) ChangePassword(ctx context.Context, pass, newPass string) (err error) {
983	defer mon.Task()(&ctx)(&err)
984	auth, err := s.getAuthAndAuditLog(ctx, "change password")
985	if err != nil {
986		return Error.Wrap(err)
987	}
988
989	err = bcrypt.CompareHashAndPassword(auth.User.PasswordHash, []byte(pass))
990	if err != nil {
991		return ErrUnauthorized.New(credentialsErrMsg)
992	}
993
994	if err := ValidatePassword(newPass); err != nil {
995		return ErrValidation.Wrap(err)
996	}
997
998	hash, err := bcrypt.GenerateFromPassword([]byte(newPass), s.config.PasswordCost)
999	if err != nil {
1000		return Error.Wrap(err)
1001	}
1002
1003	auth.User.PasswordHash = hash
1004	err = s.store.Users().Update(ctx, &auth.User)
1005	if err != nil {
1006		return Error.Wrap(err)
1007	}
1008
1009	return nil
1010}
1011
1012// DeleteAccount deletes User.
1013func (s *Service) DeleteAccount(ctx context.Context, password string) (err error) {
1014	defer mon.Task()(&ctx)(&err)
1015	auth, err := s.getAuthAndAuditLog(ctx, "delete account")
1016	if err != nil {
1017		return Error.Wrap(err)
1018	}
1019
1020	err = bcrypt.CompareHashAndPassword(auth.User.PasswordHash, []byte(password))
1021	if err != nil {
1022		return ErrUnauthorized.New(credentialsErrMsg)
1023	}
1024
1025	err = s.Payments().checkOutstandingInvoice(ctx)
1026	if err != nil {
1027		return Error.Wrap(err)
1028	}
1029
1030	err = s.store.Users().Delete(ctx, auth.User.ID)
1031	if err != nil {
1032		return Error.Wrap(err)
1033	}
1034
1035	return nil
1036}
1037
1038// GetProject is a method for querying project by id.
1039func (s *Service) GetProject(ctx context.Context, projectID uuid.UUID) (p *Project, err error) {
1040	defer mon.Task()(&ctx)(&err)
1041	auth, err := s.getAuthAndAuditLog(ctx, "get project", zap.String("projectID", projectID.String()))
1042	if err != nil {
1043		return nil, Error.Wrap(err)
1044	}
1045
1046	if _, err = s.isProjectMember(ctx, auth.User.ID, projectID); err != nil {
1047		return nil, Error.Wrap(err)
1048	}
1049
1050	p, err = s.store.Projects().Get(ctx, projectID)
1051	if err != nil {
1052		return nil, Error.Wrap(err)
1053	}
1054
1055	return
1056}
1057
1058// GetUsersProjects is a method for querying all projects.
1059func (s *Service) GetUsersProjects(ctx context.Context) (ps []Project, err error) {
1060	defer mon.Task()(&ctx)(&err)
1061	auth, err := s.getAuthAndAuditLog(ctx, "get users projects")
1062	if err != nil {
1063		return nil, Error.Wrap(err)
1064	}
1065
1066	ps, err = s.store.Projects().GetByUserID(ctx, auth.User.ID)
1067	if err != nil {
1068		return nil, Error.Wrap(err)
1069	}
1070
1071	return
1072}
1073
1074// GetUsersOwnedProjectsPage is a method for querying paged projects.
1075func (s *Service) GetUsersOwnedProjectsPage(ctx context.Context, cursor ProjectsCursor) (_ ProjectsPage, err error) {
1076	defer mon.Task()(&ctx)(&err)
1077	auth, err := s.getAuthAndAuditLog(ctx, "get user's owned projects page")
1078	if err != nil {
1079		return ProjectsPage{}, Error.Wrap(err)
1080	}
1081
1082	projects, err := s.store.Projects().ListByOwnerID(ctx, auth.User.ID, cursor)
1083	if err != nil {
1084		return ProjectsPage{}, Error.Wrap(err)
1085	}
1086
1087	return projects, nil
1088}
1089
1090// CreateProject is a method for creating new project.
1091func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p *Project, err error) {
1092	defer mon.Task()(&ctx)(&err)
1093	auth, err := s.getAuthAndAuditLog(ctx, "create project")
1094	if err != nil {
1095		return nil, Error.Wrap(err)
1096	}
1097
1098	currentProjectCount, err := s.checkProjectLimit(ctx, auth.User.ID)
1099	if err != nil {
1100		return nil, ErrProjLimit.Wrap(err)
1101	}
1102
1103	newProjectLimits, err := s.getUserProjectLimits(ctx, auth.User.ID)
1104	if err != nil {
1105		return nil, ErrProjLimit.Wrap(err)
1106	}
1107
1108	var projectID uuid.UUID
1109	err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error {
1110		p, err = tx.Projects().Insert(ctx,
1111			&Project{
1112				Description:    projectInfo.Description,
1113				Name:           projectInfo.Name,
1114				OwnerID:        auth.User.ID,
1115				PartnerID:      auth.User.PartnerID,
1116				UserAgent:      auth.User.UserAgent,
1117				StorageLimit:   &newProjectLimits.StorageLimit,
1118				BandwidthLimit: &newProjectLimits.BandwidthLimit,
1119			},
1120		)
1121		if err != nil {
1122			return Error.Wrap(err)
1123		}
1124
1125		_, err = tx.ProjectMembers().Insert(ctx, auth.User.ID, p.ID)
1126		if err != nil {
1127			return Error.Wrap(err)
1128		}
1129
1130		projectID = p.ID
1131
1132		return nil
1133	})
1134
1135	if err != nil {
1136		return nil, Error.Wrap(err)
1137	}
1138
1139	s.analytics.TrackProjectCreated(auth.User.ID, auth.User.Email, projectID, currentProjectCount+1)
1140
1141	return p, nil
1142}
1143
1144// DeleteProject is a method for deleting project by id.
1145func (s *Service) DeleteProject(ctx context.Context, projectID uuid.UUID) (err error) {
1146	defer mon.Task()(&ctx)(&err)
1147	auth, err := s.getAuthAndAuditLog(ctx, "delete project", zap.String("projectID", projectID.String()))
1148	if err != nil {
1149		return Error.Wrap(err)
1150	}
1151
1152	_, err = s.isProjectOwner(ctx, auth.User.ID, projectID)
1153	if err != nil {
1154		return Error.Wrap(err)
1155	}
1156
1157	err = s.checkProjectCanBeDeleted(ctx, projectID)
1158	if err != nil {
1159		return Error.Wrap(err)
1160	}
1161
1162	err = s.store.Projects().Delete(ctx, projectID)
1163	if err != nil {
1164		return Error.Wrap(err)
1165	}
1166
1167	return nil
1168}
1169
1170// UpdateProject is a method for updating project name and description by id.
1171func (s *Service) UpdateProject(ctx context.Context, projectID uuid.UUID, projectInfo ProjectInfo) (p *Project, err error) {
1172	defer mon.Task()(&ctx)(&err)
1173
1174	auth, err := s.getAuthAndAuditLog(ctx, "update project name and description", zap.String("projectID", projectID.String()))
1175	if err != nil {
1176		return nil, Error.Wrap(err)
1177	}
1178
1179	err = ValidateNameAndDescription(projectInfo.Name, projectInfo.Description)
1180	if err != nil {
1181		return nil, Error.Wrap(err)
1182	}
1183
1184	isMember, err := s.isProjectMember(ctx, auth.User.ID, projectID)
1185	if err != nil {
1186		return nil, Error.Wrap(err)
1187	}
1188	project := isMember.project
1189	project.Name = projectInfo.Name
1190	project.Description = projectInfo.Description
1191
1192	if auth.User.PaidTier {
1193		if project.BandwidthLimit != nil && *project.BandwidthLimit == 0 {
1194			return nil, Error.New("current bandwidth limit for project is set to 0 (updating disabled)")
1195		}
1196		if project.StorageLimit != nil && *project.StorageLimit == 0 {
1197			return nil, Error.New("current storage limit for project is set to 0 (updating disabled)")
1198		}
1199		if projectInfo.StorageLimit <= 0 || projectInfo.BandwidthLimit <= 0 {
1200			return nil, Error.New("project limits must be greater than 0")
1201		}
1202
1203		if projectInfo.StorageLimit > s.config.UsageLimits.Storage.Paid {
1204			return nil, Error.New("specified storage limit exceeds allowed maximum for current tier")
1205		}
1206
1207		if projectInfo.BandwidthLimit > s.config.UsageLimits.Bandwidth.Paid {
1208			return nil, Error.New("specified bandwidth limit exceeds allowed maximum for current tier")
1209		}
1210
1211		storageUsed, err := s.projectUsage.GetProjectStorageTotals(ctx, projectID)
1212		if err != nil {
1213			return nil, Error.Wrap(err)
1214		}
1215		if projectInfo.StorageLimit.Int64() < storageUsed {
1216			return nil, Error.New("cannot set storage limit below current usage")
1217		}
1218
1219		bandwidthUsed, err := s.projectUsage.GetProjectBandwidthTotals(ctx, projectID)
1220		if err != nil {
1221			return nil, Error.Wrap(err)
1222		}
1223		if projectInfo.BandwidthLimit.Int64() < bandwidthUsed {
1224			return nil, Error.New("cannot set bandwidth limit below current usage")
1225		}
1226
1227		project.StorageLimit = new(memory.Size)
1228		*project.StorageLimit = projectInfo.StorageLimit
1229		project.BandwidthLimit = new(memory.Size)
1230		*project.BandwidthLimit = projectInfo.BandwidthLimit
1231	}
1232
1233	err = s.store.Projects().Update(ctx, project)
1234	if err != nil {
1235		return nil, Error.Wrap(err)
1236	}
1237
1238	return project, nil
1239}
1240
1241// AddProjectMembers adds users by email to given project.
1242func (s *Service) AddProjectMembers(ctx context.Context, projectID uuid.UUID, emails []string) (users []*User, err error) {
1243	defer mon.Task()(&ctx)(&err)
1244	auth, err := s.getAuthAndAuditLog(ctx, "add project members", zap.String("projectID", projectID.String()), zap.Strings("emails", emails))
1245	if err != nil {
1246		return nil, Error.Wrap(err)
1247	}
1248
1249	if _, err = s.isProjectMember(ctx, auth.User.ID, projectID); err != nil {
1250		return nil, Error.Wrap(err)
1251	}
1252
1253	var userErr errs.Group
1254
1255	// collect user querying errors
1256	for _, email := range emails {
1257		user, err := s.store.Users().GetByEmail(ctx, email)
1258		if err != nil {
1259			userErr.Add(err)
1260			continue
1261		}
1262
1263		users = append(users, user)
1264	}
1265
1266	if err = userErr.Err(); err != nil {
1267		return nil, ErrValidation.New(teamMemberDoesNotExistErrMsg)
1268	}
1269
1270	// add project members in transaction scope
1271	err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error {
1272		for _, user := range users {
1273			if _, err := tx.ProjectMembers().Insert(ctx, user.ID, projectID); err != nil {
1274				return err
1275			}
1276		}
1277		return nil
1278	})
1279	if err != nil {
1280		return nil, Error.Wrap(err)
1281	}
1282
1283	return users, nil
1284}
1285
1286// DeleteProjectMembers removes users by email from given project.
1287func (s *Service) DeleteProjectMembers(ctx context.Context, projectID uuid.UUID, emails []string) (err error) {
1288	defer mon.Task()(&ctx)(&err)
1289	auth, err := s.getAuthAndAuditLog(ctx, "delete project members", zap.String("projectID", projectID.String()), zap.Strings("emails", emails))
1290	if err != nil {
1291		return Error.Wrap(err)
1292	}
1293
1294	if _, err = s.isProjectMember(ctx, auth.User.ID, projectID); err != nil {
1295		return Error.Wrap(err)
1296	}
1297
1298	var userIDs []uuid.UUID
1299	var userErr errs.Group
1300
1301	// collect user querying errors
1302	for _, email := range emails {
1303		user, err := s.store.Users().GetByEmail(ctx, email)
1304
1305		if err != nil {
1306			userErr.Add(err)
1307			continue
1308		}
1309
1310		isOwner, err := s.isProjectOwner(ctx, user.ID, projectID)
1311		if isOwner {
1312			return ErrValidation.New(projectOwnerDeletionForbiddenErrMsg, user.Email)
1313		}
1314		if err != nil && !ErrUnauthorized.Has(err) {
1315			return Error.Wrap(err)
1316		}
1317
1318		userIDs = append(userIDs, user.ID)
1319	}
1320
1321	if err = userErr.Err(); err != nil {
1322		return ErrValidation.New(teamMemberDoesNotExistErrMsg)
1323	}
1324
1325	// delete project members in transaction scope
1326	err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error {
1327		for _, uID := range userIDs {
1328			err = tx.ProjectMembers().Delete(ctx, uID, projectID)
1329			if err != nil {
1330				return err
1331			}
1332		}
1333		return nil
1334	})
1335	return Error.Wrap(err)
1336}
1337
1338// GetProjectMembers returns ProjectMembers for given Project.
1339func (s *Service) GetProjectMembers(ctx context.Context, projectID uuid.UUID, cursor ProjectMembersCursor) (pmp *ProjectMembersPage, err error) {
1340	defer mon.Task()(&ctx)(&err)
1341
1342	auth, err := s.getAuthAndAuditLog(ctx, "get project members", zap.String("projectID", projectID.String()))
1343	if err != nil {
1344		return nil, Error.Wrap(err)
1345	}
1346
1347	_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
1348	if err != nil {
1349		return nil, Error.Wrap(err)
1350	}
1351
1352	if cursor.Limit > maxLimit {
1353		cursor.Limit = maxLimit
1354	}
1355
1356	pmp, err = s.store.ProjectMembers().GetPagedByProjectID(ctx, projectID, cursor)
1357	if err != nil {
1358		return nil, Error.Wrap(err)
1359	}
1360
1361	return
1362}
1363
1364// CreateAPIKey creates new api key.
1365func (s *Service) CreateAPIKey(ctx context.Context, projectID uuid.UUID, name string) (_ *APIKeyInfo, _ *macaroon.APIKey, err error) {
1366	defer mon.Task()(&ctx)(&err)
1367
1368	auth, err := s.getAuthAndAuditLog(ctx, "create api key", zap.String("projectID", projectID.String()))
1369	if err != nil {
1370		return nil, nil, Error.Wrap(err)
1371	}
1372
1373	_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
1374	if err != nil {
1375		return nil, nil, Error.Wrap(err)
1376	}
1377
1378	_, err = s.store.APIKeys().GetByNameAndProjectID(ctx, name, projectID)
1379	if err == nil {
1380		return nil, nil, ErrValidation.New(apiKeyWithNameExistsErrMsg)
1381	}
1382
1383	secret, err := macaroon.NewSecret()
1384	if err != nil {
1385		return nil, nil, Error.Wrap(err)
1386	}
1387
1388	key, err := macaroon.NewAPIKey(secret)
1389	if err != nil {
1390		return nil, nil, Error.Wrap(err)
1391	}
1392
1393	apikey := APIKeyInfo{
1394		Name:      name,
1395		ProjectID: projectID,
1396		Secret:    secret,
1397		PartnerID: auth.User.PartnerID,
1398		UserAgent: auth.User.UserAgent,
1399	}
1400
1401	info, err := s.store.APIKeys().Create(ctx, key.Head(), apikey)
1402	if err != nil {
1403		return nil, nil, Error.Wrap(err)
1404	}
1405
1406	s.analytics.TrackAccessGrantCreated(auth.User.ID, auth.User.Email)
1407
1408	return info, key, nil
1409}
1410
1411// GetAPIKeyInfo retrieves api key by id.
1412func (s *Service) GetAPIKeyInfo(ctx context.Context, id uuid.UUID) (_ *APIKeyInfo, err error) {
1413	defer mon.Task()(&ctx)(&err)
1414
1415	auth, err := s.getAuthAndAuditLog(ctx, "get api key info", zap.String("apiKeyID", id.String()))
1416	if err != nil {
1417		return nil, err
1418	}
1419
1420	key, err := s.store.APIKeys().Get(ctx, id)
1421	if err != nil {
1422		return nil, Error.Wrap(err)
1423	}
1424
1425	_, err = s.isProjectMember(ctx, auth.User.ID, key.ProjectID)
1426	if err != nil {
1427		return nil, Error.Wrap(err)
1428	}
1429
1430	return key, nil
1431}
1432
1433// DeleteAPIKeys deletes api key by id.
1434func (s *Service) DeleteAPIKeys(ctx context.Context, ids []uuid.UUID) (err error) {
1435	defer mon.Task()(&ctx)(&err)
1436
1437	idStrings := make([]string, 0, len(ids))
1438	for _, id := range ids {
1439		idStrings = append(idStrings, id.String())
1440	}
1441
1442	auth, err := s.getAuthAndAuditLog(ctx, "delete api keys", zap.Strings("apiKeyIDs", idStrings))
1443	if err != nil {
1444		return Error.Wrap(err)
1445	}
1446
1447	var keysErr errs.Group
1448
1449	for _, keyID := range ids {
1450		key, err := s.store.APIKeys().Get(ctx, keyID)
1451		if err != nil {
1452			keysErr.Add(err)
1453			continue
1454		}
1455
1456		_, err = s.isProjectMember(ctx, auth.User.ID, key.ProjectID)
1457		if err != nil {
1458			keysErr.Add(ErrUnauthorized.Wrap(err))
1459			continue
1460		}
1461	}
1462
1463	if err = keysErr.Err(); err != nil {
1464		return Error.Wrap(err)
1465	}
1466
1467	err = s.store.WithTx(ctx, func(ctx context.Context, tx DBTx) error {
1468		for _, keyToDeleteID := range ids {
1469			err = tx.APIKeys().Delete(ctx, keyToDeleteID)
1470			if err != nil {
1471				return err
1472			}
1473		}
1474
1475		return nil
1476	})
1477	return Error.Wrap(err)
1478}
1479
1480// DeleteAPIKeyByNameAndProjectID deletes api key by name and project ID.
1481func (s *Service) DeleteAPIKeyByNameAndProjectID(ctx context.Context, name string, projectID uuid.UUID) (err error) {
1482	defer mon.Task()(&ctx)(&err)
1483
1484	auth, err := s.getAuthAndAuditLog(ctx, "delete api key by name and project ID", zap.String("apiKeyName", name), zap.String("projectID", projectID.String()))
1485	if err != nil {
1486		return Error.Wrap(err)
1487	}
1488
1489	_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
1490	if err != nil {
1491		return Error.Wrap(err)
1492	}
1493
1494	key, err := s.store.APIKeys().GetByNameAndProjectID(ctx, name, projectID)
1495	if err != nil {
1496		return ErrNoAPIKey.New(apiKeyWithNameDoesntExistErrMsg)
1497	}
1498
1499	err = s.store.APIKeys().Delete(ctx, key.ID)
1500	if err != nil {
1501		return Error.Wrap(err)
1502	}
1503
1504	return nil
1505}
1506
1507// GetAPIKeys returns paged api key list for given Project.
1508func (s *Service) GetAPIKeys(ctx context.Context, projectID uuid.UUID, cursor APIKeyCursor) (page *APIKeyPage, err error) {
1509	defer mon.Task()(&ctx)(&err)
1510
1511	auth, err := s.getAuthAndAuditLog(ctx, "get api keys", zap.String("projectID", projectID.String()))
1512	if err != nil {
1513		return nil, Error.Wrap(err)
1514	}
1515
1516	_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
1517	if err != nil {
1518		return nil, Error.Wrap(err)
1519	}
1520
1521	if cursor.Limit > maxLimit {
1522		cursor.Limit = maxLimit
1523	}
1524
1525	page, err = s.store.APIKeys().GetPagedByProjectID(ctx, projectID, cursor)
1526	if err != nil {
1527		return nil, Error.Wrap(err)
1528	}
1529
1530	return
1531}
1532
1533// GetProjectUsage retrieves project usage for a given period.
1534func (s *Service) GetProjectUsage(ctx context.Context, projectID uuid.UUID, since, before time.Time) (_ *accounting.ProjectUsage, err error) {
1535	defer mon.Task()(&ctx)(&err)
1536
1537	auth, err := s.getAuthAndAuditLog(ctx, "get project usage", zap.String("projectID", projectID.String()))
1538	if err != nil {
1539		return nil, Error.Wrap(err)
1540	}
1541
1542	_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
1543	if err != nil {
1544		return nil, Error.Wrap(err)
1545	}
1546
1547	projectUsage, err := s.projectAccounting.GetProjectTotal(ctx, projectID, since, before)
1548	if err != nil {
1549		return nil, Error.Wrap(err)
1550	}
1551
1552	return projectUsage, nil
1553}
1554
1555// GetBucketTotals retrieves paged bucket total usages since project creation.
1556func (s *Service) GetBucketTotals(ctx context.Context, projectID uuid.UUID, cursor accounting.BucketUsageCursor, before time.Time) (_ *accounting.BucketUsagePage, err error) {
1557	defer mon.Task()(&ctx)(&err)
1558
1559	auth, err := s.getAuthAndAuditLog(ctx, "get bucket totals", zap.String("projectID", projectID.String()))
1560	if err != nil {
1561		return nil, Error.Wrap(err)
1562	}
1563
1564	isMember, err := s.isProjectMember(ctx, auth.User.ID, projectID)
1565	if err != nil {
1566		return nil, Error.Wrap(err)
1567	}
1568
1569	usage, err := s.projectAccounting.GetBucketTotals(ctx, projectID, cursor, isMember.project.CreatedAt, before)
1570	if err != nil {
1571		return nil, Error.Wrap(err)
1572	}
1573
1574	return usage, nil
1575}
1576
1577// GetAllBucketNames retrieves all bucket names of a specific project.
1578func (s *Service) GetAllBucketNames(ctx context.Context, projectID uuid.UUID) (_ []string, err error) {
1579	defer mon.Task()(&ctx)(&err)
1580
1581	auth, err := s.getAuthAndAuditLog(ctx, "get all bucket names", zap.String("projectID", projectID.String()))
1582	if err != nil {
1583		return nil, Error.Wrap(err)
1584	}
1585
1586	_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
1587	if err != nil {
1588		return nil, Error.Wrap(err)
1589	}
1590
1591	listOptions := storj.BucketListOptions{
1592		Direction: storj.Forward,
1593	}
1594
1595	allowedBuckets := macaroon.AllowedBuckets{
1596		All: true,
1597	}
1598
1599	bucketsList, err := s.buckets.ListBuckets(ctx, projectID, listOptions, allowedBuckets)
1600	if err != nil {
1601		return nil, Error.Wrap(err)
1602	}
1603
1604	var list []string
1605	for _, bucket := range bucketsList.Items {
1606		list = append(list, bucket.Name)
1607	}
1608
1609	return list, nil
1610}
1611
1612// GetBucketUsageRollups retrieves summed usage rollups for every bucket of particular project for a given period.
1613func (s *Service) GetBucketUsageRollups(ctx context.Context, projectID uuid.UUID, since, before time.Time) (_ []accounting.BucketUsageRollup, err error) {
1614	defer mon.Task()(&ctx)(&err)
1615
1616	auth, err := s.getAuthAndAuditLog(ctx, "get bucket usage rollups", zap.String("projectID", projectID.String()))
1617	if err != nil {
1618		return nil, Error.Wrap(err)
1619	}
1620
1621	_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
1622	if err != nil {
1623		return nil, Error.Wrap(err)
1624	}
1625
1626	result, err := s.projectAccounting.GetBucketUsageRollups(ctx, projectID, since, before)
1627	if err != nil {
1628		return nil, Error.Wrap(err)
1629	}
1630
1631	return result, nil
1632}
1633
1634// GetProjectUsageLimits returns project limits and current usage.
1635//
1636// Among others,it can return one of the following errors returned by
1637// storj.io/storj/satellite/accounting.Service, wrapped Error.
1638func (s *Service) GetProjectUsageLimits(ctx context.Context, projectID uuid.UUID) (_ *ProjectUsageLimits, err error) {
1639	defer mon.Task()(&ctx)(&err)
1640
1641	auth, err := s.getAuthAndAuditLog(ctx, "get project usage limits", zap.String("projectID", projectID.String()))
1642	if err != nil {
1643		return nil, Error.Wrap(err)
1644	}
1645
1646	_, err = s.isProjectMember(ctx, auth.User.ID, projectID)
1647	if err != nil {
1648		return nil, Error.Wrap(err)
1649	}
1650
1651	prUsageLimits, err := s.getProjectUsageLimits(ctx, projectID)
1652	if err != nil {
1653		return nil, Error.Wrap(err)
1654	}
1655
1656	prObjectsSegments, err := s.projectAccounting.GetProjectObjectsSegments(ctx, projectID)
1657	if err != nil {
1658		return nil, Error.Wrap(err)
1659	}
1660
1661	return &ProjectUsageLimits{
1662		StorageLimit:   prUsageLimits.StorageLimit,
1663		BandwidthLimit: prUsageLimits.BandwidthLimit,
1664		StorageUsed:    prUsageLimits.StorageUsed,
1665		BandwidthUsed:  prUsageLimits.BandwidthUsed,
1666		ObjectCount:    prObjectsSegments.ObjectCount,
1667		SegmentCount:   prObjectsSegments.SegmentCount,
1668	}, nil
1669}
1670
1671// GetTotalUsageLimits returns total limits and current usage for all the projects.
1672func (s *Service) GetTotalUsageLimits(ctx context.Context) (_ *ProjectUsageLimits, err error) {
1673	defer mon.Task()(&ctx)(&err)
1674
1675	auth, err := s.getAuthAndAuditLog(ctx, "get total usage and limits for all the projects")
1676	if err != nil {
1677		return nil, Error.Wrap(err)
1678	}
1679
1680	projects, err := s.store.Projects().GetOwn(ctx, auth.User.ID)
1681	if err != nil {
1682		return nil, Error.Wrap(err)
1683	}
1684
1685	var totalStorageLimit int64
1686	var totalBandwidthLimit int64
1687	var totalStorageUsed int64
1688	var totalBandwidthUsed int64
1689
1690	for _, pr := range projects {
1691		prUsageLimits, err := s.getProjectUsageLimits(ctx, pr.ID)
1692		if err != nil {
1693			return nil, Error.Wrap(err)
1694		}
1695
1696		totalStorageLimit += prUsageLimits.StorageLimit
1697		totalBandwidthLimit += prUsageLimits.BandwidthLimit
1698		totalStorageUsed += prUsageLimits.StorageUsed
1699		totalBandwidthUsed += prUsageLimits.BandwidthUsed
1700	}
1701
1702	return &ProjectUsageLimits{
1703		StorageLimit:   totalStorageLimit,
1704		BandwidthLimit: totalBandwidthLimit,
1705		StorageUsed:    totalStorageUsed,
1706		BandwidthUsed:  totalBandwidthUsed,
1707	}, nil
1708}
1709
1710func (s *Service) getProjectUsageLimits(ctx context.Context, projectID uuid.UUID) (_ *ProjectUsageLimits, err error) {
1711	defer mon.Task()(&ctx)(&err)
1712
1713	storageLimit, err := s.projectUsage.GetProjectStorageLimit(ctx, projectID)
1714	if err != nil {
1715		return nil, err
1716	}
1717	bandwidthLimit, err := s.projectUsage.GetProjectBandwidthLimit(ctx, projectID)
1718	if err != nil {
1719		return nil, err
1720	}
1721
1722	storageUsed, err := s.projectUsage.GetProjectStorageTotals(ctx, projectID)
1723	if err != nil {
1724		return nil, err
1725	}
1726	bandwidthUsed, err := s.projectUsage.GetProjectBandwidthTotals(ctx, projectID)
1727	if err != nil {
1728		return nil, err
1729	}
1730
1731	return &ProjectUsageLimits{
1732		StorageLimit:   storageLimit.Int64(),
1733		BandwidthLimit: bandwidthLimit.Int64(),
1734		StorageUsed:    storageUsed,
1735		BandwidthUsed:  bandwidthUsed,
1736	}, nil
1737}
1738
1739// Authorize validates token from context and returns authorized Authorization.
1740func (s *Service) Authorize(ctx context.Context) (a Authorization, err error) {
1741	defer mon.Task()(&ctx)(&err)
1742	tokenS, ok := consoleauth.GetAPIKey(ctx)
1743	if !ok {
1744		return Authorization{}, ErrUnauthorized.New("no api key was provided")
1745	}
1746
1747	token, err := consoleauth.FromBase64URLString(string(tokenS))
1748	if err != nil {
1749		return Authorization{}, ErrUnauthorized.Wrap(err)
1750	}
1751
1752	claims, err := s.authenticate(ctx, token)
1753	if err != nil {
1754		return Authorization{}, ErrUnauthorized.Wrap(err)
1755	}
1756
1757	user, err := s.authorize(ctx, claims)
1758	if err != nil {
1759		return Authorization{}, ErrUnauthorized.Wrap(err)
1760	}
1761
1762	return Authorization{
1763		User:   *user,
1764		Claims: *claims,
1765	}, nil
1766}
1767
1768// checkProjectCanBeDeleted ensures that all data, api-keys and buckets are deleted and usage has been accounted.
1769// no error means the project status is clean.
1770func (s *Service) checkProjectCanBeDeleted(ctx context.Context, project uuid.UUID) (err error) {
1771	defer mon.Task()(&ctx)(&err)
1772
1773	buckets, err := s.buckets.CountBuckets(ctx, project)
1774	if err != nil {
1775		return err
1776	}
1777	if buckets > 0 {
1778		return ErrUsage.New("some buckets still exist")
1779	}
1780
1781	keys, err := s.store.APIKeys().GetPagedByProjectID(ctx, project, APIKeyCursor{Limit: 1, Page: 1})
1782	if err != nil {
1783		return err
1784	}
1785	if keys.TotalCount > 0 {
1786		return ErrUsage.New("some api-keys still exist")
1787	}
1788
1789	outstanding, err := s.Payments().checkProjectInvoicingStatus(ctx, project)
1790	if outstanding {
1791		return ErrUsage.New("there is outstanding usage that is not charged yet")
1792	}
1793	return ErrUsage.Wrap(err)
1794}
1795
1796// checkProjectLimit is used to check if user is able to create a new project.
1797func (s *Service) checkProjectLimit(ctx context.Context, userID uuid.UUID) (currentProjects int, err error) {
1798	defer mon.Task()(&ctx)(&err)
1799
1800	limit, err := s.store.Users().GetProjectLimit(ctx, userID)
1801	if err != nil {
1802		return 0, Error.Wrap(err)
1803	}
1804
1805	projects, err := s.GetUsersProjects(ctx)
1806	if err != nil {
1807		return 0, Error.Wrap(err)
1808	}
1809
1810	if len(projects) >= limit {
1811		return 0, ErrProjLimit.New(projLimitErrMsg)
1812	}
1813
1814	return len(projects), nil
1815}
1816
1817// getUserProjectLimits is a method to get the users storage and bandwidth limits for new projects.
1818func (s *Service) getUserProjectLimits(ctx context.Context, userID uuid.UUID) (_ *UserProjectLimits, err error) {
1819	defer mon.Task()(&ctx)(&err)
1820
1821	result, err := s.store.Users().GetUserProjectLimits(ctx, userID)
1822	if err != nil {
1823		return nil, Error.Wrap(err)
1824	}
1825
1826	return &UserProjectLimits{
1827		StorageLimit:   result.ProjectStorageLimit,
1828		BandwidthLimit: result.ProjectBandwidthLimit,
1829	}, nil
1830}
1831
1832// CreateRegToken creates new registration token. Needed for testing.
1833func (s *Service) CreateRegToken(ctx context.Context, projLimit int) (_ *RegistrationToken, err error) {
1834	defer mon.Task()(&ctx)(&err)
1835	result, err := s.store.RegistrationTokens().Create(ctx, projLimit)
1836	if err != nil {
1837		return nil, Error.Wrap(err)
1838	}
1839
1840	return result, nil
1841}
1842
1843// createToken creates string representation.
1844func (s *Service) createToken(ctx context.Context, claims *consoleauth.Claims) (_ string, err error) {
1845	defer mon.Task()(&ctx)(&err)
1846
1847	json, err := claims.JSON()
1848	if err != nil {
1849		return "", Error.Wrap(err)
1850	}
1851
1852	token := consoleauth.Token{Payload: json}
1853	err = signToken(&token, s.Signer)
1854	if err != nil {
1855		return "", Error.Wrap(err)
1856	}
1857
1858	return token.String(), nil
1859}
1860
1861// authenticate validates token signature and returns authenticated *satelliteauth.Authorization.
1862func (s *Service) authenticate(ctx context.Context, token consoleauth.Token) (_ *consoleauth.Claims, err error) {
1863	defer mon.Task()(&ctx)(&err)
1864	signature := token.Signature
1865
1866	err = signToken(&token, s.Signer)
1867	if err != nil {
1868		return nil, Error.Wrap(err)
1869	}
1870
1871	if subtle.ConstantTimeCompare(signature, token.Signature) != 1 {
1872		return nil, Error.New("incorrect signature")
1873	}
1874
1875	claims, err := consoleauth.FromJSON(token.Payload)
1876	if err != nil {
1877		return nil, Error.Wrap(err)
1878	}
1879
1880	return claims, nil
1881}
1882
1883// authorize checks claims and returns authorized User.
1884func (s *Service) authorize(ctx context.Context, claims *consoleauth.Claims) (_ *User, err error) {
1885	defer mon.Task()(&ctx)(&err)
1886	if !claims.Expiration.IsZero() && claims.Expiration.Before(time.Now()) {
1887		return nil, ErrTokenExpiration.New("")
1888	}
1889
1890	user, err := s.store.Users().Get(ctx, claims.ID)
1891	if err != nil {
1892		return nil, ErrValidation.New("authorization failed. no user with id: %s", claims.ID.String())
1893	}
1894
1895	if user.Status != Active {
1896		return nil, ErrValidation.New("authorization failed. no active user with id: %s", claims.ID.String())
1897	}
1898	return user, nil
1899}
1900
1901// isProjectMember is return type of isProjectMember service method.
1902type isProjectMember struct {
1903	project    *Project
1904	membership *ProjectMember
1905}
1906
1907// isProjectOwner checks if the user is an owner of a project.
1908func (s *Service) isProjectOwner(ctx context.Context, userID uuid.UUID, projectID uuid.UUID) (isOwner bool, err error) {
1909	defer mon.Task()(&ctx)(&err)
1910	project, err := s.store.Projects().Get(ctx, projectID)
1911	if err != nil {
1912		return false, err
1913	}
1914
1915	if project.OwnerID != userID {
1916		return false, ErrUnauthorized.New(unauthorizedErrMsg)
1917	}
1918
1919	return true, nil
1920}
1921
1922// isProjectMember checks if the user is a member of given project.
1923func (s *Service) isProjectMember(ctx context.Context, userID uuid.UUID, projectID uuid.UUID) (_ isProjectMember, err error) {
1924	defer mon.Task()(&ctx)(&err)
1925	project, err := s.store.Projects().Get(ctx, projectID)
1926	if err != nil {
1927		return isProjectMember{}, Error.Wrap(err)
1928	}
1929
1930	memberships, err := s.store.ProjectMembers().GetByMemberID(ctx, userID)
1931	if err != nil {
1932		return isProjectMember{}, Error.Wrap(err)
1933	}
1934
1935	membership, ok := findMembershipByProjectID(memberships, projectID)
1936	if ok {
1937		return isProjectMember{
1938			project:    project,
1939			membership: &membership,
1940		}, nil
1941	}
1942
1943	return isProjectMember{}, ErrNoMembership.New(unauthorizedErrMsg)
1944}
1945
1946func findMembershipByProjectID(memberships []ProjectMember, projectID uuid.UUID) (ProjectMember, bool) {
1947	for _, membership := range memberships {
1948		if membership.ProjectID == projectID {
1949			return membership, true
1950		}
1951	}
1952	return ProjectMember{}, false
1953}
1954