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