updated createuser api (#687)

This commit is contained in:
Yaroslav Vorobiov 2018-11-21 17:51:43 +02:00 committed by GitHub
parent f72832ee69
commit c829835dc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 327 additions and 72 deletions

View File

@ -24,6 +24,16 @@ type Companies interface {
Update(ctx context.Context, company *Company) error Update(ctx context.Context, company *Company) error
} }
// CompanyInfo holds data needed to create/update Company
type CompanyInfo struct {
Name string
Address string
Country string
City string
State string
PostalCode string
}
// Company is a database object that describes Company entity // Company is a database object that describes Company entity
type Company struct { type Company struct {
ID uuid.UUID ID uuid.UUID

View File

@ -55,11 +55,12 @@ func (gw *gateway) grapqlHandler(w http.ResponseWriter, req *http.Request) {
err = json.NewEncoder(w).Encode(result) err = json.NewEncoder(w).Encode(result)
if err != nil { if err != nil {
gw.logger.Error(err) gw.log.Error(err.Error())
return return
} }
gw.logger.Debug(result) sugar := gw.log.Sugar()
sugar.Debug(result)
} }
// getToken retrieves token from request // getToken retrieves token from request

View File

@ -28,7 +28,7 @@ type Config struct {
// Run implements Responsibility interface // Run implements Responsibility interface
func (c Config) Run(ctx context.Context, server *provider.Provider) error { func (c Config) Run(ctx context.Context, server *provider.Provider) error {
sugar := zap.NewExample().Sugar() log := zap.NewExample()
// Create satellite DB // Create satellite DB
dbURL, err := utils.ParseURL(c.DatabaseURL) dbURL, err := utils.ParseURL(c.DatabaseURL)
@ -42,9 +42,12 @@ func (c Config) Run(ctx context.Context, server *provider.Provider) error {
} }
err = db.CreateTables() err = db.CreateTables()
sugar.Error(err) if err != nil {
log.Error(err.Error())
}
service, err := satellite.NewService( service, err := satellite.NewService(
log,
&satelliteauth.Hmac{Secret: []byte("my-suppa-secret-key")}, &satelliteauth.Hmac{Secret: []byte("my-suppa-secret-key")},
db, db,
) )
@ -71,7 +74,7 @@ func (c Config) Run(ctx context.Context, server *provider.Provider) error {
go (&gateway{ go (&gateway{
schema: schema, schema: schema,
config: c.GatewayConfig, config: c.GatewayConfig,
logger: sugar, log: log,
}).run() }).run()
return server.Run(ctx) return server.Run(ctx)

View File

@ -21,7 +21,7 @@ type GatewayConfig struct {
type gateway struct { type gateway struct {
schema graphql.Schema schema graphql.Schema
config GatewayConfig config GatewayConfig
logger *zap.SugaredLogger log *zap.Logger
} }
func (gw *gateway) run() { func (gw *gateway) run() {
@ -36,7 +36,7 @@ func (gw *gateway) run() {
} }
err := http.ListenAndServe(gw.config.Address, mux) err := http.ListenAndServe(gw.config.Address, mux)
gw.logger.Errorf("unexpected exit of satellite gateway server: ", err) gw.log.Error("unexpected exit of satellite gateway server: ", zap.Error(err))
} }
func (gw *gateway) appHandler(w http.ResponseWriter, req *http.Request) { func (gw *gateway) appHandler(w http.ResponseWriter, req *http.Request) {

View File

@ -0,0 +1,95 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package satelliteql
import (
"github.com/graphql-go/graphql"
"storj.io/storj/pkg/satellite"
)
const (
companyType = "company"
companyInputType = "companyInput"
fieldUserID = "userID"
fieldName = "name"
fieldAddress = "address"
fieldCountry = "country"
fieldCity = "city"
fieldState = "state"
fieldPostalCode = "postalCode"
)
// graphqlCompany creates *graphql.Object type representation of satellite.Company
func graphqlCompany() *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{
Name: companyType,
Fields: graphql.Fields{
fieldID: &graphql.Field{
Type: graphql.String,
},
fieldUserID: &graphql.Field{
Type: graphql.String,
},
fieldName: &graphql.Field{
Type: graphql.String,
},
fieldAddress: &graphql.Field{
Type: graphql.String,
},
fieldCountry: &graphql.Field{
Type: graphql.String,
},
fieldCity: &graphql.Field{
Type: graphql.String,
},
fieldState: &graphql.Field{
Type: graphql.String,
},
fieldPostalCode: &graphql.Field{
Type: graphql.String,
},
},
})
}
// graphqlCompanyInput creates graphql.InputObject type needed to register/update satellite.Company
func graphqlCompanyInput() *graphql.InputObject {
return graphql.NewInputObject(graphql.InputObjectConfig{
Name: companyInputType,
Fields: graphql.InputObjectConfigFieldMap{
fieldName: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
fieldAddress: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
fieldCountry: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
fieldCity: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
fieldState: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
fieldPostalCode: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
},
})
}
// fromMapCompanyInfo creates satellite.CompanyInfo from input args
func fromMapCompanyInfo(args map[string]interface{}) (company satellite.CompanyInfo) {
company.Name, _ = args[fieldName].(string)
company.Address, _ = args[fieldAddress].(string)
company.Country, _ = args[fieldCountry].(string)
company.City, _ = args[fieldCity].(string)
company.State, _ = args[fieldState].(string)
company.PostalCode, _ = args[fieldPostalCode].(string)
return
}

View File

@ -13,7 +13,9 @@ const (
// Mutation is graphql request that modifies data // Mutation is graphql request that modifies data
Mutation = "mutation" Mutation = "mutation"
registerMutation = "register" createUserMutation = "createUser"
input = "input"
) )
// rootMutation creates mutation for graphql populated by AccountsClient // rootMutation creates mutation for graphql populated by AccountsClient
@ -21,43 +23,28 @@ func rootMutation(service *satellite.Service, types Types) *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{ return graphql.NewObject(graphql.ObjectConfig{
Name: Mutation, Name: Mutation,
Fields: graphql.Fields{ Fields: graphql.Fields{
registerMutation: &graphql.Field{ createUserMutation: &graphql.Field{
Type: types.UserType(), Type: graphql.String,
Args: graphql.FieldConfigArgument{ Args: graphql.FieldConfigArgument{
fieldEmail: &graphql.ArgumentConfig{ input: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String), Type: graphql.NewNonNull(types.UserInput()),
},
fieldPassword: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
fieldFirstName: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
fieldLastName: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
}, },
}, },
// creates user and company from input params and returns userID if succeed
Resolve: func(p graphql.ResolveParams) (interface{}, error) { Resolve: func(p graphql.ResolveParams) (interface{}, error) {
email, _ := p.Args[fieldEmail].(string) var userInput = fromMapUserInfo(p.Args[input].(map[string]interface{}))
password, _ := p.Args[fieldPassword].(string)
firstName, _ := p.Args[fieldFirstName].(string)
lastName, _ := p.Args[fieldLastName].(string)
user, err := service.Register( user, err := service.CreateUser(
p.Context, p.Context,
&satellite.User{ userInput.User,
Email: email, userInput.Company,
FirstName: firstName,
LastName: lastName,
PasswordHash: []byte(password),
},
) )
if err != nil { if err != nil {
return nil, err return "", err
} }
return user, nil return user.ID.String(), nil
}, },
}, },
}, },

View File

@ -24,7 +24,7 @@ func rootQuery(service *satellite.Service, types Types) *graphql.Object {
Name: Query, Name: Query,
Fields: graphql.Fields{ Fields: graphql.Fields{
userQuery: &graphql.Field{ userQuery: &graphql.Field{
Type: types.UserType(), Type: types.User(),
Args: graphql.FieldConfigArgument{ Args: graphql.FieldConfigArgument{
fieldID: &graphql.ArgumentConfig{ fieldID: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String), Type: graphql.NewNonNull(graphql.String),
@ -60,7 +60,7 @@ func rootQuery(service *satellite.Service, types Types) *graphql.Object {
email, _ := p.Args[fieldEmail].(string) email, _ := p.Args[fieldEmail].(string)
pass, _ := p.Args[fieldPassword].(string) pass, _ := p.Args[fieldPassword].(string)
token, err := service.Login(p.Context, email, pass) token, err := service.Token(p.Context, email, pass)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,6 +5,7 @@ package satelliteql
import ( import (
"github.com/graphql-go/graphql" "github.com/graphql-go/graphql"
"storj.io/storj/pkg/satellite" "storj.io/storj/pkg/satellite"
) )
@ -13,7 +14,11 @@ type Types interface {
RootQuery() *graphql.Object RootQuery() *graphql.Object
RootMutation() *graphql.Object RootMutation() *graphql.Object
UserType() *graphql.Object User() *graphql.Object
Company() *graphql.Object
UserInput() *graphql.InputObject
CompanyInput() *graphql.InputObject
} }
// TypeCreator handles graphql type creation and error checking // TypeCreator handles graphql type creation and error checking
@ -21,7 +26,11 @@ type TypeCreator struct {
query *graphql.Object query *graphql.Object
mutation *graphql.Object mutation *graphql.Object
user *graphql.Object user *graphql.Object
company *graphql.Object
userInput *graphql.InputObject
companyInput *graphql.InputObject
} }
// RootQuery returns instance of query *graphql.Object // RootQuery returns instance of query *graphql.Object
@ -36,11 +45,26 @@ func (c *TypeCreator) RootMutation() *graphql.Object {
// Create create types and check for error // Create create types and check for error
func (c *TypeCreator) Create(service *satellite.Service) error { func (c *TypeCreator) Create(service *satellite.Service) error {
c.user = graphqlUser() c.company = graphqlCompany()
if err := c.company.Error(); err != nil {
return err
}
c.companyInput = graphqlCompanyInput()
if err := c.companyInput.Error(); err != nil {
return err
}
c.user = graphqlUser(service, c)
if err := c.user.Error(); err != nil { if err := c.user.Error(); err != nil {
return err return err
} }
c.userInput = graphqlUserInput(c)
if err := c.userInput.Error(); err != nil {
return err
}
c.query = rootQuery(service, c) c.query = rootQuery(service, c)
if err := c.query.Error(); err != nil { if err := c.query.Error(); err != nil {
return err return err
@ -54,7 +78,22 @@ func (c *TypeCreator) Create(service *satellite.Service) error {
return nil return nil
} }
// UserType returns instance of user *graphql.Object // User returns instance of satellite.User *graphql.Object
func (c *TypeCreator) UserType() *graphql.Object { func (c *TypeCreator) User() *graphql.Object {
return c.user return c.user
} }
// Company returns instance of satellite.Company *graphql.Object
func (c *TypeCreator) Company() *graphql.Object {
return c.company
}
// UserInput returns instance of UserInput *graphql.Object
func (c *TypeCreator) UserInput() *graphql.InputObject {
return c.userInput
}
// CompanyInput returns instance of CompanyInfo *graphql.Object
func (c *TypeCreator) CompanyInput() *graphql.InputObject {
return c.companyInput
}

View File

@ -5,10 +5,13 @@ package satelliteql
import ( import (
"github.com/graphql-go/graphql" "github.com/graphql-go/graphql"
"storj.io/storj/pkg/satellite"
) )
const ( const (
userType = "user" userType = "user"
userInputType = "userInput"
fieldID = "id" fieldID = "id"
fieldEmail = "email" fieldEmail = "email"
@ -18,8 +21,8 @@ const (
fieldCreatedAt = "createdAt" fieldCreatedAt = "createdAt"
) )
// graphqlUser creates instance of user *graphql.Object // graphqlUser creates *graphql.Object type representation of satellite.User
func graphqlUser() *graphql.Object { func graphqlUser(service *satellite.Service, types Types) *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{ return graphql.NewObject(graphql.ObjectConfig{
Name: userType, Name: userType,
Fields: graphql.Fields{ Fields: graphql.Fields{
@ -38,6 +41,59 @@ func graphqlUser() *graphql.Object {
fieldCreatedAt: &graphql.Field{ fieldCreatedAt: &graphql.Field{
Type: graphql.DateTime, Type: graphql.DateTime,
}, },
companyType: &graphql.Field{
Type: types.Company(),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
user, _ := p.Source.(satellite.User)
return service.GetCompany(p.Context, user.ID)
},
},
}, },
}) })
} }
// graphqlUserInput creates graphql.InputObject type needed to register/update satellite.User
func graphqlUserInput(types Types) *graphql.InputObject {
return graphql.NewInputObject(graphql.InputObjectConfig{
Name: userInputType,
Fields: graphql.InputObjectConfigFieldMap{
fieldEmail: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
fieldFirstName: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
fieldLastName: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
fieldPassword: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
companyType: &graphql.InputObjectFieldConfig{
Type: types.CompanyInput(),
},
},
})
}
// UserInput encapsulates satellite.UserInfo and satellite.CompanyInfo which is used in user related queries
type UserInput struct {
User satellite.UserInfo
Company satellite.CompanyInfo
}
// fromMapUserInfo creates UserInput from input args
func fromMapUserInfo(args map[string]interface{}) (input UserInput) {
input.User.Email, _ = args[fieldEmail].(string)
input.User.FirstName, _ = args[fieldFirstName].(string)
input.User.LastName, _ = args[fieldLastName].(string)
input.User.Password, _ = args[fieldPassword].(string)
companyArgs, ok := args[companyType].(map[string]interface{})
if !ok {
return
}
input.Company = fromMapCompanyInfo(companyArgs)
return
}

View File

@ -9,6 +9,8 @@ import (
"crypto/subtle" "crypto/subtle"
"time" "time"
"go.uber.org/zap"
"github.com/skyrings/skyring-common/tools/uuid" "github.com/skyrings/skyring-common/tools/uuid"
"github.com/zeebo/errs" "github.com/zeebo/errs"
@ -21,10 +23,11 @@ type Service struct {
Signer Signer
store DB store DB
log *zap.Logger
} }
// NewService returns new instance of Service // NewService returns new instance of Service
func NewService(signer Signer, store DB) (*Service, error) { func NewService(log *zap.Logger, signer Signer, store DB) (*Service, error) {
if signer == nil { if signer == nil {
return nil, errs.New("signer can't be nil") return nil, errs.New("signer can't be nil")
} }
@ -33,24 +36,65 @@ func NewService(signer Signer, store DB) (*Service, error) {
return nil, errs.New("store can't be nil") return nil, errs.New("store can't be nil")
} }
return &Service{Signer: signer, store: store}, nil if log == nil {
return nil, errs.New("log can't be nil")
}
return &Service{Signer: signer, store: store, log: log}, nil
} }
// Register gets password hash value and creates new user // CreateUser gets password hash value and creates new user
func (s *Service) Register(ctx context.Context, user *User) (*User, error) { func (s *Service) CreateUser(ctx context.Context, userInfo UserInfo, companyInfo CompanyInfo) (*User, error) {
passwordHash := sha256.Sum256(user.PasswordHash) passwordHash := sha256.Sum256([]byte(userInfo.Password))
user.PasswordHash = passwordHash[:]
user, err := s.store.Users().Insert(ctx, &User{
Email: userInfo.Email,
FirstName: userInfo.FirstName,
LastName: userInfo.LastName,
PasswordHash: passwordHash[:],
})
newUser, err := s.store.Users().Insert(ctx, user)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newUser, nil _, err = s.store.Companies().Insert(ctx, &Company{
UserID: user.ID,
Name: companyInfo.Name,
Address: companyInfo.Address,
Country: companyInfo.Country,
City: companyInfo.City,
State: companyInfo.State,
PostalCode: companyInfo.PostalCode,
})
if err != nil {
s.log.Error(err.Error())
}
return user, nil
} }
// Login authenticates user by credentials and returns auth token // CreateCompany creates Company for authorized User
func (s *Service) Login(ctx context.Context, email, password string) (string, error) { func (s *Service) CreateCompany(ctx context.Context, info CompanyInfo) (*Company, error) {
user, err := s.Authorize(ctx)
if err != nil {
return nil, err
}
return s.store.Companies().Insert(ctx, &Company{
UserID: user.ID,
Name: info.Name,
Address: info.Address,
Country: info.Country,
City: info.City,
State: info.State,
PostalCode: info.PostalCode,
})
}
// Token authenticates user by credentials and returns auth token
func (s *Service) Token(ctx context.Context, email, password string) (string, error) {
passwordHash := sha256.Sum256([]byte(password)) passwordHash := sha256.Sum256([]byte(password))
user, err := s.store.Users().GetByCredentials(ctx, passwordHash[:], email) user, err := s.store.Users().GetByCredentials(ctx, passwordHash[:], email)
@ -74,27 +118,22 @@ func (s *Service) Login(ctx context.Context, email, password string) (string, er
// GetUser returns user by id // GetUser returns user by id
func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (*User, error) { func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (*User, error) {
token, ok := auth.GetAPIKey(ctx) _, err := s.Authorize(ctx)
if !ok {
return nil, errs.New("no api key was provided")
}
claims, err := s.authenticate(string(token))
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = s.authorize(ctx, claims) return s.store.Users().Get(ctx, id)
}
// GetCompany returns company by userID
func (s *Service) GetCompany(ctx context.Context, userID uuid.UUID) (*Company, error) {
_, err := s.Authorize(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
user, err := s.store.Users().Get(ctx, id) return s.store.Companies().GetByUserID(ctx, userID)
if err != nil {
return nil, err
}
return user, nil
} }
func (s *Service) createToken(claims *satelliteauth.Claims) (string, error) { func (s *Service) createToken(claims *satelliteauth.Claims) (string, error) {
@ -112,6 +151,22 @@ func (s *Service) createToken(claims *satelliteauth.Claims) (string, error) {
return token.String(), nil return token.String(), nil
} }
// Authorize validates token from context and returns authenticated and authorized User
func (s *Service) Authorize(ctx context.Context) (*User, error) {
token, ok := auth.GetAPIKey(ctx)
if !ok {
return nil, errs.New("no api key was provided")
}
claims, err := s.authenticate(string(token))
if err != nil {
return nil, err
}
return s.authorize(ctx, claims)
}
// authenticate validates toke signature and returns authenticated *satelliteauth.Claims
func (s *Service) authenticate(tokenS string) (*satelliteauth.Claims, error) { func (s *Service) authenticate(tokenS string) (*satelliteauth.Claims, error) {
token, err := satelliteauth.FromBase64URLString(tokenS) token, err := satelliteauth.FromBase64URLString(tokenS)
if err != nil { if err != nil {
@ -137,15 +192,16 @@ func (s *Service) authenticate(tokenS string) (*satelliteauth.Claims, error) {
return claims, nil return claims, nil
} }
func (s *Service) authorize(ctx context.Context, claims *satelliteauth.Claims) error { // authorize checks claims and returns authorized User
func (s *Service) authorize(ctx context.Context, claims *satelliteauth.Claims) (*User, error) {
if !claims.Expiration.IsZero() && claims.Expiration.Before(time.Now()) { if !claims.Expiration.IsZero() && claims.Expiration.Before(time.Now()) {
return errs.New("token is outdated") return nil, errs.New("token is outdated")
} }
_, err := s.store.Users().Get(ctx, claims.ID) user, err := s.store.Users().Get(ctx, claims.ID)
if err != nil { if err != nil {
return errs.New("authorization failed. no user with id: %s", claims.ID.String()) return nil, errs.New("authorization failed. no user with id: %s", claims.ID.String())
} }
return nil return user, nil
} }

View File

@ -24,6 +24,14 @@ type Users interface {
Update(ctx context.Context, user *User) error Update(ctx context.Context, user *User) error
} }
// UserInfo holds data needed to create/update User
type UserInfo struct {
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email string `json:"email"`
Password string `json:"password"`
}
// User is a database object that describes User entity // User is a database object that describes User entity
type User struct { type User struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`