|  | @@ -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.
 |