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
}
// 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
type Company struct {
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)
if err != nil {
gw.logger.Error(err)
gw.log.Error(err.Error())
return
}
gw.logger.Debug(result)
sugar := gw.log.Sugar()
sugar.Debug(result)
}
// getToken retrieves token from request

View File

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

View File

@ -21,7 +21,7 @@ type GatewayConfig struct {
type gateway struct {
schema graphql.Schema
config GatewayConfig
logger *zap.SugaredLogger
log *zap.Logger
}
func (gw *gateway) run() {
@ -36,7 +36,7 @@ func (gw *gateway) run() {
}
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) {

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 = "mutation"
registerMutation = "register"
createUserMutation = "createUser"
input = "input"
)
// 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{
Name: Mutation,
Fields: graphql.Fields{
registerMutation: &graphql.Field{
Type: types.UserType(),
createUserMutation: &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument{
fieldEmail: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
fieldPassword: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
fieldFirstName: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
fieldLastName: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
input: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(types.UserInput()),
},
},
// creates user and company from input params and returns userID if succeed
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
email, _ := p.Args[fieldEmail].(string)
password, _ := p.Args[fieldPassword].(string)
firstName, _ := p.Args[fieldFirstName].(string)
lastName, _ := p.Args[fieldLastName].(string)
var userInput = fromMapUserInfo(p.Args[input].(map[string]interface{}))
user, err := service.Register(
user, err := service.CreateUser(
p.Context,
&satellite.User{
Email: email,
FirstName: firstName,
LastName: lastName,
PasswordHash: []byte(password),
},
userInput.User,
userInput.Company,
)
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,
Fields: graphql.Fields{
userQuery: &graphql.Field{
Type: types.UserType(),
Type: types.User(),
Args: graphql.FieldConfigArgument{
fieldID: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
@ -60,7 +60,7 @@ func rootQuery(service *satellite.Service, types Types) *graphql.Object {
email, _ := p.Args[fieldEmail].(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 {
return nil, err
}

View File

@ -5,6 +5,7 @@ package satelliteql
import (
"github.com/graphql-go/graphql"
"storj.io/storj/pkg/satellite"
)
@ -13,7 +14,11 @@ type Types interface {
RootQuery() *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
@ -21,7 +26,11 @@ type TypeCreator struct {
query *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
@ -36,11 +45,26 @@ func (c *TypeCreator) RootMutation() *graphql.Object {
// Create create types and check for 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 {
return err
}
c.userInput = graphqlUserInput(c)
if err := c.userInput.Error(); err != nil {
return err
}
c.query = rootQuery(service, c)
if err := c.query.Error(); err != nil {
return err
@ -54,7 +78,22 @@ func (c *TypeCreator) Create(service *satellite.Service) error {
return nil
}
// UserType returns instance of user *graphql.Object
func (c *TypeCreator) UserType() *graphql.Object {
// User returns instance of satellite.User *graphql.Object
func (c *TypeCreator) User() *graphql.Object {
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 (
"github.com/graphql-go/graphql"
"storj.io/storj/pkg/satellite"
)
const (
userType = "user"
userType = "user"
userInputType = "userInput"
fieldID = "id"
fieldEmail = "email"
@ -18,8 +21,8 @@ const (
fieldCreatedAt = "createdAt"
)
// graphqlUser creates instance of user *graphql.Object
func graphqlUser() *graphql.Object {
// graphqlUser creates *graphql.Object type representation of satellite.User
func graphqlUser(service *satellite.Service, types Types) *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{
Name: userType,
Fields: graphql.Fields{
@ -38,6 +41,59 @@ func graphqlUser() *graphql.Object {
fieldCreatedAt: &graphql.Field{
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"
"time"
"go.uber.org/zap"
"github.com/skyrings/skyring-common/tools/uuid"
"github.com/zeebo/errs"
@ -21,10 +23,11 @@ type Service struct {
Signer
store DB
log *zap.Logger
}
// 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 {
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 &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
func (s *Service) Register(ctx context.Context, user *User) (*User, error) {
passwordHash := sha256.Sum256(user.PasswordHash)
user.PasswordHash = passwordHash[:]
// CreateUser gets password hash value and creates new user
func (s *Service) CreateUser(ctx context.Context, userInfo UserInfo, companyInfo CompanyInfo) (*User, error) {
passwordHash := sha256.Sum256([]byte(userInfo.Password))
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 {
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
func (s *Service) Login(ctx context.Context, email, password string) (string, error) {
// CreateCompany creates Company for authorized User
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))
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
func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (*User, error) {
token, ok := auth.GetAPIKey(ctx)
if !ok {
return nil, errs.New("no api key was provided")
}
claims, err := s.authenticate(string(token))
_, err := s.Authorize(ctx)
if err != nil {
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 {
return nil, err
}
user, err := s.store.Users().Get(ctx, id)
if err != nil {
return nil, err
}
return user, nil
return s.store.Companies().GetByUserID(ctx, userID)
}
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
}
// 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) {
token, err := satelliteauth.FromBase64URLString(tokenS)
if err != nil {
@ -137,15 +192,16 @@ func (s *Service) authenticate(tokenS string) (*satelliteauth.Claims, error) {
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()) {
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 {
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
}
// 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
type User struct {
ID uuid.UUID `json:"id"`