|
@@ -7,16 +7,20 @@ import (
|
|
|
"github.com/golang-jwt/jwt/v4"
|
|
|
"github.com/gshopify/service-wrapper/auth"
|
|
|
"github.com/gshopify/service-wrapper/config"
|
|
|
- "github.com/gshopify/service-wrapper/model"
|
|
|
- "github.com/gshopify/service-wrapper/scalar"
|
|
|
+ "github.com/mitchellh/mapstructure"
|
|
|
"gshopper.com/gshopify/customer/graphql/generated"
|
|
|
- "strings"
|
|
|
+ m "gshopper.com/gshopify/customer/model"
|
|
|
+ "sync"
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
type Resolver struct {
|
|
|
conf *auth.Config
|
|
|
client gocloak.GoCloak
|
|
|
+
|
|
|
+ mu sync.Mutex
|
|
|
+ _token *gocloak.JWT
|
|
|
+ _expiresAt time.Time
|
|
|
}
|
|
|
|
|
|
func NewResolver() (*Resolver, error) {
|
|
@@ -33,12 +37,11 @@ func NewResolver() (*Resolver, error) {
|
|
|
}
|
|
|
|
|
|
func (r *Resolver) decodeAccessToken(ctx context.Context, t string) (*jwt.Token, string, error) {
|
|
|
- t = strings.TrimSpace(t)
|
|
|
if t == "" {
|
|
|
return nil, "", fmt.Errorf("could not decode accessToken: Token is empty")
|
|
|
}
|
|
|
|
|
|
- token, claim, err := r.client.DecodeAccessToken(ctx, t, r.conf.Realm)
|
|
|
+ token, claim, err := r.client.DecodeAccessToken(ctx, t, r.conf.Cli.Realm)
|
|
|
if err != nil {
|
|
|
return nil, "", err
|
|
|
}
|
|
@@ -61,125 +64,83 @@ func (r *Resolver) decodeAccessToken(ctx context.Context, t string) (*jwt.Token,
|
|
|
return token, sessionId, nil
|
|
|
}
|
|
|
|
|
|
-func (r *mutationResolver) CustomerAccessTokenCreate(
|
|
|
- ctx context.Context,
|
|
|
- input generated.CustomerAccessTokenCreateInput) (*generated.CustomerAccessTokenCreatePayload, error) {
|
|
|
-
|
|
|
+func (r *Resolver) customer(ctx context.Context, t string) (*generated.Customer, error) {
|
|
|
var (
|
|
|
- token *gocloak.JWT
|
|
|
- err error
|
|
|
-
|
|
|
- session = auth.SessionManager()
|
|
|
- response = &generated.CustomerAccessTokenCreatePayload{}
|
|
|
+ customer = generated.Customer{}
|
|
|
+ udata map[string]any
|
|
|
+ phone *m.Phone
|
|
|
+ err error
|
|
|
)
|
|
|
|
|
|
- if token, err = r.client.Login(ctx, r.conf.ClientId, r.conf.ClientSecret, r.conf.Realm, input.Email, input.Password); err != nil {
|
|
|
- response.CustomerUserErrors = append(response.CustomerUserErrors,
|
|
|
- CustomerError(generated.CustomerErrorCodeUnidentifiedCustomer, err))
|
|
|
- return response, nil
|
|
|
- }
|
|
|
-
|
|
|
- if err = session.PutToken(ctx, token.SessionState, token.RefreshToken, time.Duration(token.RefreshExpiresIn)*time.Second); err != nil {
|
|
|
- response.CustomerUserErrors = append(response.CustomerUserErrors,
|
|
|
- CustomerError(generated.CustomerErrorCodeTokenInvalid, err))
|
|
|
- return response, nil
|
|
|
- }
|
|
|
-
|
|
|
- response.CustomerAccessToken = &generated.CustomerAccessToken{
|
|
|
- AccessToken: token.AccessToken,
|
|
|
- ExpiresAt: scalar.NewDateTimeIn(token.RefreshExpiresIn).String(),
|
|
|
- }
|
|
|
-
|
|
|
- return response, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (r *mutationResolver) CustomerAccessTokenRenew(ctx context.Context, t string) (*generated.CustomerAccessTokenRenewPayload, error) {
|
|
|
- var (
|
|
|
- session = auth.SessionManager()
|
|
|
- response = &generated.CustomerAccessTokenRenewPayload{}
|
|
|
- )
|
|
|
-
|
|
|
- _, sid, err := r.decodeAccessToken(ctx, t)
|
|
|
+ udata, err = r.client.GetRawUserInfo(ctx, t, r.conf.Cli.Realm)
|
|
|
if err != nil {
|
|
|
- response.UserErrors = append(response.UserErrors, ErrTokenNotExists)
|
|
|
- return response, nil
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
- refresh, err := session.Token(ctx, sid)
|
|
|
- if err != nil {
|
|
|
- response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
|
|
|
- return response, nil
|
|
|
+ if err = mapstructure.Decode(udata, &customer); err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
- token, err := r.client.RefreshToken(ctx, refresh, r.conf.ClientId, r.conf.ClientSecret, r.conf.Realm)
|
|
|
+ customer.Phone = nil
|
|
|
+ phone, err = m.ParsePhoneNumber(udata)
|
|
|
if err != nil {
|
|
|
- response.UserErrors = append(response.UserErrors, ErrTokenExpired)
|
|
|
- return response, nil
|
|
|
- }
|
|
|
-
|
|
|
- if err = session.PutToken(ctx, token.SessionState, token.RefreshToken, time.Duration(token.RefreshExpiresIn)*time.Second); err != nil {
|
|
|
- response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
|
|
|
- return response, nil
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
- response.CustomerAccessToken = &generated.CustomerAccessToken{
|
|
|
- AccessToken: token.AccessToken,
|
|
|
- ExpiresAt: scalar.NewDateTimeIn(token.RefreshExpiresIn).String(),
|
|
|
- }
|
|
|
- return response, nil
|
|
|
+ customer.Phone = gocloak.StringP(phone.String())
|
|
|
+ customer.Metafields = append(customer.Metafields,
|
|
|
+ &generated.Metafield{
|
|
|
+ Namespace: "customer",
|
|
|
+ Key: "phone_region",
|
|
|
+ Type: "single_line_text_field",
|
|
|
+ Value: phone.PhoneRegion,
|
|
|
+ },
|
|
|
+ &generated.Metafield{
|
|
|
+ Namespace: "customer",
|
|
|
+ Key: "phone_verified",
|
|
|
+ Type: "boolean",
|
|
|
+ Value: fmt.Sprintf("%v", phone.Verified),
|
|
|
+ })
|
|
|
+
|
|
|
+ return &customer, nil
|
|
|
}
|
|
|
|
|
|
-func (r *mutationResolver) CustomerAccessTokenDelete(ctx context.Context, t string) (*generated.CustomerAccessTokenDeletePayload, error) {
|
|
|
+func (r *Resolver) admin(ctx context.Context) (*gocloak.JWT, error) {
|
|
|
var (
|
|
|
- response = &generated.CustomerAccessTokenDeletePayload{}
|
|
|
- session = auth.SessionManager()
|
|
|
+ creds = r.conf.Admin
|
|
|
+ err error
|
|
|
)
|
|
|
|
|
|
- _, sid, err := r.decodeAccessToken(ctx, t)
|
|
|
- if err != nil {
|
|
|
- response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
|
|
|
- return response, nil
|
|
|
- }
|
|
|
-
|
|
|
- refresh, err := session.Token(ctx, sid)
|
|
|
- if err != nil {
|
|
|
- response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
|
|
|
- return response, nil
|
|
|
- }
|
|
|
+ if time.Now().After(r._expiresAt) {
|
|
|
+ r.mu.Lock()
|
|
|
+ defer r.mu.Unlock()
|
|
|
|
|
|
- if err = r.client.Logout(ctx, r.conf.ClientId, r.conf.ClientSecret, r.conf.Realm, refresh); err != nil {
|
|
|
- response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
|
|
|
- return response, nil
|
|
|
- }
|
|
|
+ if r._token != nil {
|
|
|
+ r._token, err = r.client.RefreshToken(ctx, r._token.RefreshToken, creds.ClientId, creds.ClientSecret, creds.Realm)
|
|
|
+ }
|
|
|
|
|
|
- response.DeletedAccessToken = &t
|
|
|
- response.DeletedCustomerAccessTokenID = &sid
|
|
|
- return response, nil
|
|
|
-}
|
|
|
+ if r._token == nil || err != nil {
|
|
|
+ r._token, err = r.client.LoginAdmin(ctx, creds.Login, creds.Password, creds.Realm)
|
|
|
+ }
|
|
|
|
|
|
-func (r *queryResolver) Customer(ctx context.Context, t string) (*generated.Customer, error) {
|
|
|
- if _, _, err := r.decodeAccessToken(ctx, t); err != nil {
|
|
|
- return nil, err
|
|
|
+ if r._token == nil || err != nil {
|
|
|
+ r._expiresAt = time.Time{}
|
|
|
+ } else {
|
|
|
+ r._expiresAt = time.Now().
|
|
|
+ Add(time.Duration(r._token.ExpiresIn) * time.Second).
|
|
|
+ Add(-time.Second * 5)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- userinfo, err := r.client.GetUserInfo(ctx, t, r.conf.Realm)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
+ return r._token, err
|
|
|
+}
|
|
|
|
|
|
- return &generated.Customer{
|
|
|
- AcceptsMarketing: false, //TODO:
|
|
|
- Addresses: nil, //TODO:
|
|
|
- DefaultAddress: nil, //TODO:
|
|
|
- DisplayName: *userinfo.PreferredUsername,
|
|
|
- Email: userinfo.Email,
|
|
|
- FirstName: userinfo.Name,
|
|
|
- ID: *userinfo.Sub,
|
|
|
- LastName: userinfo.FamilyName,
|
|
|
- NumberOfOrders: model.UInt(0), //TODO:
|
|
|
- Phone: userinfo.PhoneNumber,
|
|
|
- Tags: nil, //TODO:
|
|
|
- }, nil
|
|
|
+func (r *Resolver) saveSession(ctx context.Context, token *gocloak.JWT) error {
|
|
|
+ return auth.SessionManager().PutToken(
|
|
|
+ ctx,
|
|
|
+ token.SessionState,
|
|
|
+ token.RefreshToken,
|
|
|
+ time.Duration(token.RefreshExpiresIn)*time.Second)
|
|
|
}
|
|
|
|
|
|
// Mutation returns generated.MutationResolver implementation.
|