satellite/console/consoleweb: add support for partner parameter for

signup page

With this change partner id can be associated with user during creating
account by adding `?partner=partner_name` parameter to signup page url
e.g. https://tardigrade.io/signup?partner=mongodb

https://storjlabs.atlassian.net/browse/USR-999

Change-Id: I12a5ebec92a6f5135909447172ef24da57fb1c68
This commit is contained in:
Michal Niewrzal 2020-07-28 16:23:17 +02:00
parent edfd3d7661
commit 4561d9bdb0
9 changed files with 128 additions and 10 deletions

View File

@ -600,6 +600,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
peer.Console.Service,
peer.Mail.Service,
peer.Referrals.Service,
peer.Marketing.PartnersService,
peer.Console.Listener,
config.Payments.StripeCoinPayments.StripePublicKey,
)

View File

@ -17,6 +17,7 @@ import (
"storj.io/storj/satellite/console/consoleweb/consoleql"
"storj.io/storj/satellite/console/consoleweb/consolewebauth"
"storj.io/storj/satellite/mailservice"
"storj.io/storj/satellite/rewards"
)
// ErrAuthAPI - console auth api error type.
@ -32,10 +33,11 @@ type Auth struct {
service *console.Service
mailService *mailservice.Service
cookieAuth *consolewebauth.CookieAuth
partners *rewards.PartnersService
}
// NewAuth is a constructor for api auth controller.
func NewAuth(log *zap.Logger, service *console.Service, mailService *mailservice.Service, cookieAuth *consolewebauth.CookieAuth, externalAddress string, letUsKnowURL string, termsAndConditionsURL string, contactInfoURL string) *Auth {
func NewAuth(log *zap.Logger, service *console.Service, mailService *mailservice.Service, cookieAuth *consolewebauth.CookieAuth, partners *rewards.PartnersService, externalAddress string, letUsKnowURL string, termsAndConditionsURL string, contactInfoURL string) *Auth {
return &Auth{
log: log,
ExternalAddress: externalAddress,
@ -45,6 +47,7 @@ func NewAuth(log *zap.Logger, service *console.Service, mailService *mailservice
service: service,
mailService: mailService,
cookieAuth: cookieAuth,
partners: partners,
}
}
@ -102,6 +105,7 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
FullName string `json:"fullName"`
ShortName string `json:"shortName"`
Email string `json:"email"`
Partner string `json:"partner"`
PartnerID string `json:"partnerId"`
Password string `json:"password"`
SecretInput string `json:"secret"`
@ -120,6 +124,15 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
return
}
if registerData.Partner != "" {
info, err := a.partners.ByName(ctx, registerData.Partner)
if err != nil {
a.log.Warn("Invalid partner name", zap.String("Partner name", registerData.Partner), zap.String("User email", registerData.Email), zap.Error(err))
} else {
registerData.PartnerID = info.ID
}
}
user, err := a.service.CreateUser(ctx,
console.CreateUser{
FullName: registerData.FullName,

View File

@ -0,0 +1,92 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package consoleapi_test
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"storj.io/common/testcontext"
"storj.io/common/uuid"
"storj.io/storj/private/testplanet"
"storj.io/storj/satellite"
)
func TestAuth_Register(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
Reconfigure: testplanet.Reconfigure{
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
config.Console.OpenRegistrationEnabled = true
config.Console.RateLimit.Burst = 10
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
for i, test := range []struct {
Partner string
ValidPartner bool
}{
{Partner: "minio", ValidPartner: true},
{Partner: "Minio", ValidPartner: true},
{Partner: "Raiden Network", ValidPartner: true},
{Partner: "Raiden nEtwork", ValidPartner: true},
{Partner: "invalid-name", ValidPartner: false},
} {
registerData := struct {
FullName string `json:"fullName"`
ShortName string `json:"shortName"`
Email string `json:"email"`
Partner string `json:"partner"`
PartnerID string `json:"partnerId"`
Password string `json:"password"`
SecretInput string `json:"secret"`
ReferrerUserID string `json:"referrerUserId"`
}{
FullName: "testuser" + strconv.Itoa(i),
ShortName: "test",
Email: "user@test" + strconv.Itoa(i),
Partner: test.Partner,
Password: "abc123",
}
jsonBody, err := json.Marshal(registerData)
require.NoError(t, err)
result, err := http.Post("http://"+planet.Satellites[0].API.Console.Listener.Addr().String()+"/api/v0/auth/register", "application/json", bytes.NewBuffer(jsonBody))
require.NoError(t, err)
require.Equal(t, http.StatusOK, result.StatusCode)
defer func() {
err = result.Body.Close()
require.NoError(t, err)
}()
body, err := ioutil.ReadAll(result.Body)
require.NoError(t, err)
var userID uuid.UUID
err = json.Unmarshal(body, &userID)
require.NoError(t, err)
user, err := planet.Satellites[0].API.Console.Service.GetUser(ctx, userID)
require.NoError(t, err)
if test.ValidPartner {
info, err := planet.Satellites[0].API.Marketing.PartnersService.ByName(ctx, test.Partner)
require.NoError(t, err)
require.Equal(t, info.UUID, user.PartnerID)
} else {
require.Equal(t, uuid.UUID{}, user.PartnerID)
}
}
})
}

View File

@ -39,6 +39,7 @@ import (
"storj.io/storj/satellite/console/consoleweb/consolewebauth"
"storj.io/storj/satellite/mailservice"
"storj.io/storj/satellite/referrals"
"storj.io/storj/satellite/rewards"
)
const (
@ -93,6 +94,7 @@ type Server struct {
service *console.Service
mailService *mailservice.Service
referralsService *referrals.Service
partners *rewards.PartnersService
listener net.Listener
server http.Server
@ -114,7 +116,7 @@ type Server struct {
}
// NewServer creates new instance of console server.
func NewServer(logger *zap.Logger, config Config, service *console.Service, mailService *mailservice.Service, referralsService *referrals.Service, listener net.Listener, stripePublicKey string) *Server {
func NewServer(logger *zap.Logger, config Config, service *console.Service, mailService *mailservice.Service, referralsService *referrals.Service, partners *rewards.PartnersService, listener net.Listener, stripePublicKey string) *Server {
server := Server{
log: logger,
config: config,
@ -122,6 +124,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
service: service,
mailService: mailService,
referralsService: referralsService,
partners: partners,
stripePublicKey: stripePublicKey,
rateLimiter: web.NewIPRateLimiter(config.RateLimit),
}
@ -164,7 +167,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
referralsRouter.Handle("/tokens", server.withAuth(http.HandlerFunc(referralsController.GetTokens))).Methods(http.MethodGet)
referralsRouter.HandleFunc("/register", referralsController.Register).Methods(http.MethodPost)
authController := consoleapi.NewAuth(logger, service, mailService, server.cookieAuth, server.config.ExternalAddress, config.LetUsKnowURL, config.TermsAndConditionsURL, config.ContactInfoURL)
authController := consoleapi.NewAuth(logger, service, mailService, server.cookieAuth, partners, server.config.ExternalAddress, config.LetUsKnowURL, config.TermsAndConditionsURL, config.ContactInfoURL)
authRouter := router.PathPrefix("/api/v0/auth").Subrouter()
authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.GetAccount))).Methods(http.MethodGet)
authRouter.Handle("/account", server.withAuth(http.HandlerFunc(authController.UpdateAccount))).Methods(http.MethodPatch)

View File

@ -114,7 +114,7 @@ func (service *PartnersService) ByUserAgent(ctx context.Context, userAgentString
return PartnerInfo{}, ErrPartners.Wrap(err)
}
return service.db.ByName(ctx, info.Product.Name)
return service.db.ByUserAgent(ctx, info.Product.Name)
}
// All returns all partners.

View File

@ -6,6 +6,7 @@ package rewards
import (
"context"
"sort"
"strings"
"github.com/zeebo/errs"
)
@ -37,10 +38,11 @@ func NewPartnersStaticDB(list *PartnerList) (*PartnersStaticDB, error) {
var errg errs.Group
for _, p := range list.Partners {
if _, exists := db.byName[p.Name]; exists {
name := strings.ToLower(p.Name)
if _, exists := db.byName[name]; exists {
errg.Add(ErrPartners.New("name %q already exists", p.Name))
} else {
db.byName[p.Name] = p
db.byName[name] = p
}
if _, exists := db.byID[p.ID]; exists {
@ -65,9 +67,9 @@ func (db *PartnersStaticDB) All(ctx context.Context) ([]PartnerInfo, error) {
return append([]PartnerInfo{}, db.list.Partners...), nil
}
// ByName returns partner definitions for a given name.
// ByName returns partner definitions for a given name. Name is case insensitive.
func (db *PartnersStaticDB) ByName(ctx context.Context, name string) (PartnerInfo, error) {
partner, ok := db.byName[name]
partner, ok := db.byName[strings.ToLower(name)]
if !ok {
return PartnerInfo{}, ErrPartnerNotExist.New("%q", name)
}

View File

@ -196,7 +196,7 @@ export class AuthHttpApi {
* @returns id of created user
* @throws Error
*/
public async register(user: {fullName: string; shortName: string; email: string; partnerId: string; password: string}, secret: string, referrerUserId: string): Promise<string> {
public async register(user: {fullName: string; shortName: string; email: string; partner: string, partnerId: string; password: string}, secret: string, referrerUserId: string): Promise<string> {
const path = `${this.ROOT_PATH}/register`;
const body = {
secret: secret,
@ -205,6 +205,7 @@ export class AuthHttpApi {
fullName: user.fullName,
shortName: user.shortName,
email: user.email,
partner: user.partner ? user.partner : '',
partnerId: user.partnerId ? user.partnerId : '',
};

View File

@ -29,14 +29,16 @@ export class User {
public fullName: string;
public shortName: string;
public email: string;
public partner: string;
public partnerId: string;
public password: string;
public constructor(id: string = '', fullName: string = '', shortName: string = '', email: string = '', partnerId: string = '', password: string = '') {
public constructor(id: string = '', fullName: string = '', shortName: string = '', email: string = '', partner: string = '', partnerId: string = '', password: string = '') {
this.id = id;
this.fullName = fullName;
this.shortName = shortName;
this.email = email;
this.partner = partner;
this.partnerId = partnerId;
this.password = password;
}

View File

@ -106,6 +106,10 @@ export default class RegisterArea extends Vue {
this.referralToken = this.$route.query.referralToken.toString();
}
if (this.$route.query.partner) {
this.user.partner = this.$route.query.partner.toString();
}
const { ids = '' } = this.$route.params;
let decoded = '';
try {