account api: infrastructure, login, register, getUser (#611)
This commit is contained in:
parent
17519a7532
commit
c442205b3a
@ -21,10 +21,11 @@ import (
|
||||
"storj.io/storj/pkg/miniogw"
|
||||
"storj.io/storj/pkg/overlay"
|
||||
mock "storj.io/storj/pkg/overlay/mocks"
|
||||
psserver "storj.io/storj/pkg/piecestore/psserver"
|
||||
"storj.io/storj/pkg/piecestore/psserver"
|
||||
"storj.io/storj/pkg/pointerdb"
|
||||
"storj.io/storj/pkg/process"
|
||||
"storj.io/storj/pkg/provider"
|
||||
"storj.io/storj/pkg/satellite/satelliteweb"
|
||||
"storj.io/storj/pkg/statdb"
|
||||
"storj.io/storj/pkg/utils"
|
||||
)
|
||||
@ -43,6 +44,7 @@ type Satellite struct {
|
||||
Repairer repairer.Config
|
||||
Audit audit.Config
|
||||
StatDB statdb.Config
|
||||
Web satelliteweb.Config
|
||||
MockOverlay struct {
|
||||
Enabled bool `default:"true" help:"if false, use real overlay"`
|
||||
Host string `default:"" help:"if set, the mock overlay will return storage nodes with this host"`
|
||||
@ -131,6 +133,12 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||
if runCfg.Satellite.Audit.SatelliteAddr == "" {
|
||||
runCfg.Satellite.Audit.SatelliteAddr = runCfg.Satellite.Identity.Address
|
||||
}
|
||||
|
||||
if runCfg.Satellite.Web.SatelliteAddr == "" {
|
||||
runCfg.Satellite.Web.SatelliteAddr = runCfg.Satellite.Identity.Address
|
||||
}
|
||||
|
||||
// Run satellite
|
||||
errch <- runCfg.Satellite.Identity.Run(ctx,
|
||||
grpcauth.NewAPIKeyInterceptor(),
|
||||
runCfg.Satellite.PointerDB,
|
||||
@ -141,6 +149,7 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||
// TODO(coyle): re-enable the checker after we determine why it is panicing
|
||||
// runCfg.Satellite.Checker,
|
||||
runCfg.Satellite.Repairer,
|
||||
runCfg.Satellite.Web,
|
||||
)
|
||||
}()
|
||||
|
||||
|
3
go.mod
3
go.mod
@ -102,7 +102,8 @@ require (
|
||||
require (
|
||||
github.com/garyburd/redigo v1.0.1-0.20170216214944-0d253a66e6e1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||
github.com/hanwen/go-fuse v0.0.0-20181011180456-b760b55765be
|
||||
github.com/graphql-go/graphql v0.7.6
|
||||
github.com/hanwen/go-fuse v0.0.0-20181027161220-c029b69a13a7
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||
|
||||
|
6
go.sum
6
go.sum
@ -124,11 +124,13 @@ github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/rpc v1.1.0 h1:marKfvVP0Gpd/jHlVBKCQ8RAoUPdX7K1Nuh6l1BNh7A=
|
||||
github.com/gorilla/rpc v1.1.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
|
||||
github.com/graphql-go/graphql v0.7.6 h1:3Bn1IFB5OvPoANEfu03azF8aMyks0G/H6G1XeTfYbM4=
|
||||
github.com/graphql-go/graphql v0.7.6/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI=
|
||||
github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69 h1:7xsUJsB2NrdcttQPa7JLEaGzvdbk7KvfrjgHZXOQRo0=
|
||||
github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69/go.mod h1:YLEMZOtU+AZ7dhN9T/IpGhXVGly2bvkJQ+zxj3WeVQo=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hanwen/go-fuse v0.0.0-20181011180456-b760b55765be h1:RXF6Da5rbJlRUosxKxuy/3OrLLG77aXRrZYcjDs6aB4=
|
||||
github.com/hanwen/go-fuse v0.0.0-20181011180456-b760b55765be/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
|
||||
github.com/hanwen/go-fuse v0.0.0-20181027161220-c029b69a13a7 h1:+INF0+TK4ga3O+6Y0Z2ftiujA13KaCO/+kHN9V6Mj4A=
|
||||
github.com/hanwen/go-fuse v0.0.0-20181027161220-c029b69a13a7/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.0.0-20150518234257-fa3f63826f7c h1:BTAbnbegUIMB6xmQCwWE8yRzbA4XSpnZY5hvRJC188I=
|
||||
|
27
pkg/satellite/auth.go
Normal file
27
pkg/satellite/auth.go
Normal file
@ -0,0 +1,27 @@
|
||||
package satellite
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"storj.io/storj/pkg/satellite/satelliteauth"
|
||||
)
|
||||
|
||||
//TODO: change to JWT or Macaroon based auth
|
||||
|
||||
// Signer creates signature for provided data
|
||||
type Signer interface {
|
||||
Sign(data []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// signToken signs token with given signer
|
||||
func signToken(token *satelliteauth.Token, signer Signer) error {
|
||||
encoded := base64.URLEncoding.EncodeToString(token.Payload)
|
||||
|
||||
signature, err := signer.Sign([]byte(encoded))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token.Signature = signature
|
||||
return nil
|
||||
}
|
38
pkg/satellite/satelliteauth/claims.go
Normal file
38
pkg/satellite/satelliteauth/claims.go
Normal file
@ -0,0 +1,38 @@
|
||||
package satelliteauth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
)
|
||||
|
||||
//TODO: change to JWT or Macaroon based auth
|
||||
|
||||
// Claims represents data signed by server and used for authentication
|
||||
type Claims struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Expiration time.Time `json:"expires,omitempty"`
|
||||
}
|
||||
|
||||
// JSON returns json representation of Claims
|
||||
func (c *Claims) JSON() ([]byte, error) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
|
||||
err := json.NewEncoder(buffer).Encode(c)
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
// FromJSON returns Claims instance, parsed from JSON
|
||||
func FromJSON(data []byte) (*Claims, error) {
|
||||
claims := new(Claims)
|
||||
|
||||
err := json.NewDecoder(bytes.NewReader(data)).Decode(claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
25
pkg/satellite/satelliteauth/hmac.go
Normal file
25
pkg/satellite/satelliteauth/hmac.go
Normal file
@ -0,0 +1,25 @@
|
||||
package satelliteauth
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
)
|
||||
|
||||
//TODO: change to JWT or Macaroon based auth
|
||||
|
||||
// Hmac is hmac256 based Signer
|
||||
type Hmac struct {
|
||||
Secret []byte
|
||||
}
|
||||
|
||||
// Sign implements satellite signer
|
||||
func (a *Hmac) Sign(data []byte) ([]byte, error) {
|
||||
mac := hmac.New(sha256.New, a.Secret)
|
||||
|
||||
_, err := mac.Write(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mac.Sum(nil), nil
|
||||
}
|
52
pkg/satellite/satelliteauth/token.go
Normal file
52
pkg/satellite/satelliteauth/token.go
Normal file
@ -0,0 +1,52 @@
|
||||
package satelliteauth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
)
|
||||
|
||||
//TODO: change to JWT or Macaroon based auth
|
||||
|
||||
// Token represents authentication data structure
|
||||
type Token struct {
|
||||
Payload []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// String returns base64URLEncoded data joined with .
|
||||
func (t Token) String() string {
|
||||
payload := base64.URLEncoding.EncodeToString(t.Payload)
|
||||
signature := base64.URLEncoding.EncodeToString(t.Signature)
|
||||
|
||||
return strings.Join([]string{payload, signature}, ".")
|
||||
}
|
||||
|
||||
// FromBase64URLString creates Token instance from base64URLEncoded string representation
|
||||
func FromBase64URLString(token string) (Token, error) {
|
||||
i := strings.Index(token, ".")
|
||||
if i < 0 {
|
||||
return Token{}, errs.New("invalid token format")
|
||||
}
|
||||
|
||||
payload := token[:i]
|
||||
signature := token[i+1:]
|
||||
|
||||
payloadDecoder := base64.NewDecoder(base64.URLEncoding, bytes.NewReader([]byte(payload)))
|
||||
signatureDecoder := base64.NewDecoder(base64.URLEncoding, bytes.NewReader([]byte(signature)))
|
||||
|
||||
payloadBytes, err := ioutil.ReadAll(payloadDecoder)
|
||||
if err != nil {
|
||||
return Token{}, errs.New("decoding token's signature failed: %s", err)
|
||||
}
|
||||
|
||||
signatureBytes, err := ioutil.ReadAll(signatureDecoder)
|
||||
if err != nil {
|
||||
return Token{}, errs.New("decoding token's body failed: %s", err)
|
||||
}
|
||||
|
||||
return Token{Payload: payloadBytes, Signature: signatureBytes}, nil
|
||||
}
|
@ -6,11 +6,10 @@ package satellitedb
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/pkg/satellite"
|
||||
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"storj.io/storj/pkg/satellite/satellitedb/dbx"
|
||||
)
|
||||
|
||||
|
101
pkg/satellite/satelliteweb/api.go
Normal file
101
pkg/satellite/satelliteweb/api.go
Normal file
@ -0,0 +1,101 @@
|
||||
package satelliteweb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/graphql-go/graphql"
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/pkg/auth"
|
||||
"storj.io/storj/pkg/satellite/satelliteweb/satelliteql"
|
||||
"storj.io/storj/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
authorization = "Authorization"
|
||||
contentType = "Content-Type"
|
||||
|
||||
authorizationBearer = "Bearer "
|
||||
|
||||
applicationJSON = "application/json"
|
||||
applicationGraphql = "application/graphql"
|
||||
)
|
||||
|
||||
func (gw *gateway) grapqlHandler(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set(contentType, applicationJSON)
|
||||
|
||||
token := getToken(req)
|
||||
query, err := getQuery(req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
result := graphql.Do(graphql.Params{
|
||||
Schema: gw.schema,
|
||||
Context: auth.WithAPIKey(context.Background(), []byte(token)),
|
||||
RequestString: query,
|
||||
})
|
||||
|
||||
if result.HasErrors() {
|
||||
err = json.NewEncoder(w).Encode(result.Errors)
|
||||
} else {
|
||||
err = json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
gw.logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
gw.logger.Debug(result)
|
||||
}
|
||||
|
||||
// getToken retrieves token from request
|
||||
func getToken(req *http.Request) string {
|
||||
value := req.Header.Get(authorization)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(value, authorizationBearer) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return value[len(authorizationBearer):]
|
||||
}
|
||||
|
||||
// getQuery retrieves graphql query from request
|
||||
func getQuery(req *http.Request) (query string, err error) {
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
return req.URL.Query().Get(satelliteql.Query), nil
|
||||
case http.MethodPost:
|
||||
return queryPOST(req)
|
||||
default:
|
||||
return "", errs.New("wrong http request type")
|
||||
}
|
||||
}
|
||||
|
||||
// queryPOST retrieves query from POST request
|
||||
func queryPOST(req *http.Request) (query string, err error) {
|
||||
switch typ := req.Header.Get(contentType); typ {
|
||||
case applicationGraphql:
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
return string(body), utils.CombineErrors(err, req.Body.Close())
|
||||
//TODO(yar): test more precisely
|
||||
case applicationJSON:
|
||||
var query struct {
|
||||
Query string
|
||||
}
|
||||
|
||||
err := json.NewDecoder(req.Body).Decode(&query)
|
||||
return query.Query, utils.CombineErrors(err, req.Body.Close())
|
||||
default:
|
||||
return "", errs.New("can't parse request body of type %s", typ)
|
||||
}
|
||||
}
|
75
pkg/satellite/satelliteweb/config.go
Normal file
75
pkg/satellite/satelliteweb/config.go
Normal file
@ -0,0 +1,75 @@
|
||||
package satelliteweb
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"storj.io/storj/pkg/satellite"
|
||||
"storj.io/storj/pkg/satellite/satelliteauth"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/graphql-go/graphql"
|
||||
|
||||
"storj.io/storj/pkg/provider"
|
||||
"storj.io/storj/pkg/satellite/satellitedb"
|
||||
"storj.io/storj/pkg/satellite/satelliteweb/satelliteql"
|
||||
"storj.io/storj/pkg/utils"
|
||||
)
|
||||
|
||||
// Config contains info needed for satellite account related services
|
||||
type Config struct {
|
||||
GatewayConfig
|
||||
SatelliteAddr string `help:"satellite main endpoint" default:""`
|
||||
DatabaseURL string `help:"" default:"sqlite3://$CONFDIR/satellitedb.db"`
|
||||
}
|
||||
|
||||
// Run implements Responsibility interface
|
||||
func (c Config) Run(ctx context.Context, server *provider.Provider) error {
|
||||
sugar := zap.NewExample().Sugar()
|
||||
|
||||
// Create satellite DB
|
||||
dbURL, err := utils.ParseURL(c.DatabaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := satellitedb.New(dbURL.Scheme, dbURL.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.CreateTables()
|
||||
sugar.Error(err)
|
||||
|
||||
service, err := satellite.NewService(
|
||||
&satelliteauth.Hmac{Secret: []byte("my-suppa-secret-key")},
|
||||
db,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
creator := satelliteql.TypeCreator{}
|
||||
err = creator.Create(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schema, err := graphql.NewSchema(graphql.SchemaConfig{
|
||||
Query: creator.RootQuery(),
|
||||
Mutation: creator.RootMutation(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go (&gateway{
|
||||
schema: schema,
|
||||
config: c.GatewayConfig,
|
||||
logger: sugar,
|
||||
}).run()
|
||||
|
||||
return server.Run(ctx)
|
||||
}
|
41
pkg/satellite/satelliteweb/gateway.go
Normal file
41
pkg/satellite/satelliteweb/gateway.go
Normal file
@ -0,0 +1,41 @@
|
||||
package satelliteweb
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/graphql-go/graphql"
|
||||
)
|
||||
|
||||
// GatewayConfig contains configuration for gateway
|
||||
type GatewayConfig struct {
|
||||
Address string `help:"server address of the graphql api gateway and frontend app" default:"127.0.0.1:8081"`
|
||||
StaticPath string `help:"path to static resources" default:""`
|
||||
}
|
||||
|
||||
type gateway struct {
|
||||
schema graphql.Schema
|
||||
config GatewayConfig
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (gw *gateway) run() {
|
||||
mux := http.NewServeMux()
|
||||
fs := http.FileServer(http.Dir(gw.config.StaticPath))
|
||||
|
||||
mux.Handle("/api/graphql/v0", http.HandlerFunc(gw.grapqlHandler))
|
||||
|
||||
if gw.config.StaticPath != "" {
|
||||
mux.Handle("/", http.HandlerFunc(gw.appHandler))
|
||||
mux.Handle("/static/", http.StripPrefix("/static", fs))
|
||||
}
|
||||
|
||||
err := http.ListenAndServe(gw.config.Address, mux)
|
||||
gw.logger.Errorf("Unexpected exit of satellite gateway server: ", err)
|
||||
}
|
||||
|
||||
func (gw *gateway) appHandler(w http.ResponseWriter, req *http.Request) {
|
||||
http.ServeFile(w, req, filepath.Join(gw.config.StaticPath, "dist", "public", "index.html"))
|
||||
}
|
62
pkg/satellite/satelliteweb/satelliteql/mutation.go
Normal file
62
pkg/satellite/satelliteweb/satelliteql/mutation.go
Normal file
@ -0,0 +1,62 @@
|
||||
package satelliteql
|
||||
|
||||
import (
|
||||
"github.com/graphql-go/graphql"
|
||||
|
||||
"storj.io/storj/pkg/satellite"
|
||||
)
|
||||
|
||||
const (
|
||||
// Mutation is graphql request that modifies data
|
||||
Mutation = "mutation"
|
||||
|
||||
registerMutation = "register"
|
||||
)
|
||||
|
||||
// rootMutation creates mutation for graphql populated by AccountsClient
|
||||
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(),
|
||||
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),
|
||||
},
|
||||
},
|
||||
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)
|
||||
|
||||
user, err := service.Register(
|
||||
p.Context,
|
||||
&satellite.User{
|
||||
Email: email,
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
PasswordHash: []byte(password),
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
70
pkg/satellite/satelliteweb/satelliteql/query.go
Normal file
70
pkg/satellite/satelliteweb/satelliteql/query.go
Normal file
@ -0,0 +1,70 @@
|
||||
package satelliteql
|
||||
|
||||
import (
|
||||
"github.com/graphql-go/graphql"
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
|
||||
"storj.io/storj/pkg/satellite"
|
||||
)
|
||||
|
||||
const (
|
||||
// Query is immutable graphql request
|
||||
Query = "query"
|
||||
|
||||
userQuery = "user"
|
||||
tokenQuery = "token"
|
||||
)
|
||||
|
||||
// rootQuery creates query for graphql populated by AccountsClient
|
||||
func rootQuery(service *satellite.Service, types Types) *graphql.Object {
|
||||
return graphql.NewObject(graphql.ObjectConfig{
|
||||
Name: Query,
|
||||
Fields: graphql.Fields{
|
||||
userQuery: &graphql.Field{
|
||||
Type: types.UserType(),
|
||||
Args: graphql.FieldConfigArgument{
|
||||
fieldID: &graphql.ArgumentConfig{
|
||||
Type: graphql.NewNonNull(graphql.String),
|
||||
},
|
||||
},
|
||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||
id, _ := p.Args[fieldID].(string)
|
||||
|
||||
idBytes, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := service.GetUser(p.Context, *idBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
},
|
||||
},
|
||||
tokenQuery: &graphql.Field{
|
||||
Type: graphql.String,
|
||||
Args: graphql.FieldConfigArgument{
|
||||
fieldEmail: &graphql.ArgumentConfig{
|
||||
Type: graphql.NewNonNull(graphql.String),
|
||||
},
|
||||
fieldPassword: &graphql.ArgumentConfig{
|
||||
Type: graphql.NewNonNull(graphql.String),
|
||||
},
|
||||
},
|
||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||
email, _ := p.Args[fieldEmail].(string)
|
||||
pass, _ := p.Args[fieldPassword].(string)
|
||||
|
||||
token, err := service.Login(p.Context, email, pass)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
57
pkg/satellite/satelliteweb/satelliteql/typecreator.go
Normal file
57
pkg/satellite/satelliteweb/satelliteql/typecreator.go
Normal file
@ -0,0 +1,57 @@
|
||||
package satelliteql
|
||||
|
||||
import (
|
||||
"github.com/graphql-go/graphql"
|
||||
"storj.io/storj/pkg/satellite"
|
||||
)
|
||||
|
||||
// Types return graphql type objects
|
||||
type Types interface {
|
||||
RootQuery() *graphql.Object
|
||||
RootMutation() *graphql.Object
|
||||
|
||||
UserType() *graphql.Object
|
||||
}
|
||||
|
||||
// TypeCreator handles graphql type creation and error checking
|
||||
type TypeCreator struct {
|
||||
query *graphql.Object
|
||||
mutation *graphql.Object
|
||||
|
||||
user *graphql.Object
|
||||
}
|
||||
|
||||
// RootQuery returns instance of query *graphql.Object
|
||||
func (c *TypeCreator) RootQuery() *graphql.Object {
|
||||
return c.query
|
||||
}
|
||||
|
||||
// RootMutation returns instance of mutation *graphql.Object
|
||||
func (c *TypeCreator) RootMutation() *graphql.Object {
|
||||
return c.mutation
|
||||
}
|
||||
|
||||
// Create create types and check for error
|
||||
func (c *TypeCreator) Create(service *satellite.Service) error {
|
||||
c.user = graphqlUser()
|
||||
if err := c.user.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.query = rootQuery(service, c)
|
||||
if err := c.query.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mutation = rootMutation(service, c)
|
||||
if err := c.mutation.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserType returns instance of user *graphql.Object
|
||||
func (c *TypeCreator) UserType() *graphql.Object {
|
||||
return c.user
|
||||
}
|
36
pkg/satellite/satelliteweb/satelliteql/user.go
Normal file
36
pkg/satellite/satelliteweb/satelliteql/user.go
Normal file
@ -0,0 +1,36 @@
|
||||
package satelliteql
|
||||
|
||||
import (
|
||||
"github.com/graphql-go/graphql"
|
||||
)
|
||||
|
||||
const (
|
||||
userType = "user"
|
||||
|
||||
fieldID = "id"
|
||||
fieldEmail = "email"
|
||||
fieldPassword = "password"
|
||||
fieldFirstName = "firstName"
|
||||
fieldLastName = "lastName"
|
||||
)
|
||||
|
||||
// graphqlUser creates instance of user *graphql.Object
|
||||
func graphqlUser() *graphql.Object {
|
||||
return graphql.NewObject(graphql.ObjectConfig{
|
||||
Name: userType,
|
||||
Fields: graphql.Fields{
|
||||
fieldID: &graphql.Field{
|
||||
Type: graphql.String,
|
||||
},
|
||||
fieldEmail: &graphql.Field{
|
||||
Type: graphql.String,
|
||||
},
|
||||
fieldFirstName: &graphql.Field{
|
||||
Type: graphql.String,
|
||||
},
|
||||
fieldLastName: &graphql.Field{
|
||||
Type: graphql.String,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
148
pkg/satellite/service.go
Normal file
148
pkg/satellite/service.go
Normal file
@ -0,0 +1,148 @@
|
||||
package satellite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"time"
|
||||
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/pkg/auth"
|
||||
"storj.io/storj/pkg/satellite/satelliteauth"
|
||||
)
|
||||
|
||||
// Service is handling accounts related logic
|
||||
type Service struct {
|
||||
Signer
|
||||
|
||||
store DB
|
||||
}
|
||||
|
||||
// NewService returns new instance of Service
|
||||
func NewService(signer Signer, store DB) (*Service, error) {
|
||||
if signer == nil {
|
||||
return nil, errs.New("signer can't be nil")
|
||||
}
|
||||
|
||||
if store == nil {
|
||||
return nil, errs.New("store can't be nil")
|
||||
}
|
||||
|
||||
return &Service{Signer: signer, store: store}, 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[:]
|
||||
|
||||
newUser, err := s.store.Users().Insert(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newUser, nil
|
||||
}
|
||||
|
||||
// Login authenticates user by credentials and returns auth token
|
||||
func (s *Service) Login(ctx context.Context, email, password string) (string, error) {
|
||||
passwordHash := sha256.Sum256([]byte(password))
|
||||
|
||||
user, err := s.store.Users().GetByCredentials(ctx, passwordHash[:], email)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//TODO: move expiration time to constants
|
||||
claims := satelliteauth.Claims{
|
||||
ID: user.ID,
|
||||
Expiration: time.Now().Add(time.Minute * 15),
|
||||
}
|
||||
|
||||
token, err := s.createToken(&claims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// 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))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.authorize(ctx, claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := s.store.Users().Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Service) createToken(claims *satelliteauth.Claims) (string, error) {
|
||||
json, err := claims.JSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := satelliteauth.Token{Payload: json}
|
||||
err = signToken(&token, s.Signer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token.String(), nil
|
||||
}
|
||||
|
||||
func (s *Service) authenticate(tokenS string) (*satelliteauth.Claims, error) {
|
||||
token, err := satelliteauth.FromBase64URLString(tokenS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature := token.Signature
|
||||
|
||||
err = signToken(&token, s.Signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare(signature, token.Signature) != 1 {
|
||||
return nil, errs.New("incorrect signature")
|
||||
}
|
||||
|
||||
claims, err := satelliteauth.FromJSON(token.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (s *Service) authorize(ctx context.Context, claims *satelliteauth.Claims) error {
|
||||
if !claims.Expiration.IsZero() && claims.Expiration.Before(time.Now()) {
|
||||
return errs.New("token is outdated")
|
||||
}
|
||||
|
||||
_, 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
|
||||
}
|
@ -26,12 +26,12 @@ type Users interface {
|
||||
|
||||
// User is a database object that describes User entity
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
ID uuid.UUID `json:"id"`
|
||||
|
||||
FirstName string
|
||||
LastName string
|
||||
Email string
|
||||
PasswordHash []byte
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
PasswordHash []byte `json:"passwordHash"`
|
||||
|
||||
CreatedAt time.Time
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user