resolver_impl.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package graphql
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/Nerzal/gocloak/v11"
  6. "github.com/golang-jwt/jwt/v4"
  7. "github.com/gshopify/service-wrapper/auth"
  8. "github.com/gshopify/service-wrapper/config"
  9. "github.com/gshopify/service-wrapper/model"
  10. "github.com/gshopify/service-wrapper/scalar"
  11. "gshopper.com/gshopify/customer/graphql/generated"
  12. "strings"
  13. "time"
  14. )
  15. type Resolver struct {
  16. conf *auth.Config
  17. client gocloak.GoCloak
  18. }
  19. func NewResolver() (*Resolver, error) {
  20. r := &Resolver{
  21. conf: auth.New(),
  22. }
  23. if err := config.Instance().Load(context.Background(), r.conf); err != nil {
  24. return nil, err
  25. }
  26. r.client = gocloak.NewClient(r.conf.Endpoint)
  27. return r, nil
  28. }
  29. func (r *Resolver) decodeAccessToken(ctx context.Context, t string) (*jwt.Token, string, error) {
  30. t = strings.TrimSpace(t)
  31. if t == "" {
  32. return nil, "", fmt.Errorf("could not decode accessToken: Token is empty")
  33. }
  34. token, claim, err := r.client.DecodeAccessToken(ctx, t, r.conf.Realm)
  35. if err != nil {
  36. return nil, "", err
  37. }
  38. if !token.Valid {
  39. return nil, "", fmt.Errorf("could not decode accessToken: Token is NOT valid")
  40. }
  41. var sessionId string
  42. if claimed, ok := (*claim)["sid"]; ok {
  43. if s, ok := claimed.(string); ok {
  44. sessionId = s
  45. }
  46. }
  47. if sessionId == "" {
  48. return nil, "", fmt.Errorf("could not claim session id")
  49. }
  50. return token, sessionId, nil
  51. }
  52. func (r *mutationResolver) CustomerAccessTokenCreate(
  53. ctx context.Context,
  54. input generated.CustomerAccessTokenCreateInput) (*generated.CustomerAccessTokenCreatePayload, error) {
  55. var (
  56. token *gocloak.JWT
  57. err error
  58. session = auth.SessionManager()
  59. response = &generated.CustomerAccessTokenCreatePayload{}
  60. )
  61. if token, err = r.client.Login(ctx, r.conf.ClientId, r.conf.ClientSecret, r.conf.Realm, input.Email, input.Password); err != nil {
  62. response.CustomerUserErrors = append(response.CustomerUserErrors,
  63. CustomerError(generated.CustomerErrorCodeUnidentifiedCustomer, err))
  64. return response, nil
  65. }
  66. if err = session.PutToken(ctx, token.SessionState, token.RefreshToken, time.Duration(token.RefreshExpiresIn)*time.Second); err != nil {
  67. response.CustomerUserErrors = append(response.CustomerUserErrors,
  68. CustomerError(generated.CustomerErrorCodeTokenInvalid, err))
  69. return response, nil
  70. }
  71. response.CustomerAccessToken = &generated.CustomerAccessToken{
  72. AccessToken: token.AccessToken,
  73. ExpiresAt: scalar.NewDateTimeIn(token.RefreshExpiresIn).String(),
  74. }
  75. return response, nil
  76. }
  77. func (r *mutationResolver) CustomerAccessTokenRenew(ctx context.Context, t string) (*generated.CustomerAccessTokenRenewPayload, error) {
  78. var (
  79. session = auth.SessionManager()
  80. response = &generated.CustomerAccessTokenRenewPayload{}
  81. )
  82. _, sid, err := r.decodeAccessToken(ctx, t)
  83. if err != nil {
  84. response.UserErrors = append(response.UserErrors, ErrTokenNotExists)
  85. return response, nil
  86. }
  87. refresh, err := session.Token(ctx, sid)
  88. if err != nil {
  89. response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
  90. return response, nil
  91. }
  92. token, err := r.client.RefreshToken(ctx, refresh, r.conf.ClientId, r.conf.ClientSecret, r.conf.Realm)
  93. if err != nil {
  94. response.UserErrors = append(response.UserErrors, ErrTokenExpired)
  95. return response, nil
  96. }
  97. if err = session.PutToken(ctx, token.SessionState, token.RefreshToken, time.Duration(token.RefreshExpiresIn)*time.Second); err != nil {
  98. response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
  99. return response, nil
  100. }
  101. response.CustomerAccessToken = &generated.CustomerAccessToken{
  102. AccessToken: token.AccessToken,
  103. ExpiresAt: scalar.NewDateTimeIn(token.RefreshExpiresIn).String(),
  104. }
  105. return response, nil
  106. }
  107. func (r *mutationResolver) CustomerAccessTokenDelete(ctx context.Context, t string) (*generated.CustomerAccessTokenDeletePayload, error) {
  108. var (
  109. response = &generated.CustomerAccessTokenDeletePayload{}
  110. session = auth.SessionManager()
  111. )
  112. _, sid, err := r.decodeAccessToken(ctx, t)
  113. if err != nil {
  114. response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
  115. return response, nil
  116. }
  117. refresh, err := session.Token(ctx, sid)
  118. if err != nil {
  119. response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
  120. return response, nil
  121. }
  122. if err = r.client.Logout(ctx, r.conf.ClientId, r.conf.ClientSecret, r.conf.Realm, refresh); err != nil {
  123. response.UserErrors = append(response.UserErrors, ErrToken(err.Error()))
  124. return response, nil
  125. }
  126. response.DeletedAccessToken = &t
  127. response.DeletedCustomerAccessTokenID = &sid
  128. return response, nil
  129. }
  130. func (r *queryResolver) Customer(ctx context.Context, t string) (*generated.Customer, error) {
  131. if _, _, err := r.decodeAccessToken(ctx, t); err != nil {
  132. return nil, err
  133. }
  134. userinfo, err := r.client.GetUserInfo(ctx, t, r.conf.Realm)
  135. if err != nil {
  136. return nil, err
  137. }
  138. return &generated.Customer{
  139. AcceptsMarketing: false, //TODO:
  140. Addresses: nil, //TODO:
  141. DefaultAddress: nil, //TODO:
  142. DisplayName: *userinfo.PreferredUsername,
  143. Email: userinfo.Email,
  144. FirstName: userinfo.Name,
  145. ID: *userinfo.Sub,
  146. LastName: userinfo.FamilyName,
  147. NumberOfOrders: model.UInt(0), //TODO:
  148. Phone: userinfo.PhoneNumber,
  149. Tags: nil, //TODO:
  150. }, nil
  151. }
  152. // Mutation returns generated.MutationResolver implementation.
  153. func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
  154. // Query returns generated.QueryResolver implementation.
  155. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
  156. type mutationResolver struct{ *Resolver }
  157. type queryResolver struct{ *Resolver }