diff --git a/internal/domain/profile.go b/internal/domain/profile.go index c002854..e008d14 100644 --- a/internal/domain/profile.go +++ b/internal/domain/profile.go @@ -6,10 +6,10 @@ import ( // Profile describes the data about the user. type Profile struct { - Photo []*URL `json:"photo"` - URL []*URL `json:"url"` - Email []*Email `json:"email"` - Name []string `json:"name"` + Photo []*URL `json:"photo,omitempty"` + URL []*URL `json:"url,omitempty"` + Email []*Email `json:"email,omitempty"` + Name []string `json:"name,omitempty"` } func NewProfile() *Profile { diff --git a/internal/domain/token.go b/internal/domain/token.go index fc7b69d..9535a7c 100644 --- a/internal/domain/token.go +++ b/internal/domain/token.go @@ -19,8 +19,8 @@ type ( Expiry time.Time ClientID *ClientID Me *Me + Profile *Profile Scope Scopes - Extra map[string]interface{} AccessToken string RefreshToken string } @@ -30,9 +30,9 @@ type ( Expiration time.Duration Issuer *ClientID Subject *Me + Profile *Profile Scope Scopes Secret []byte - Claims map[string]interface{} Algorithm string NonceLength int } @@ -48,7 +48,7 @@ var DefaultNewTokenOptions = NewTokenOptions{ Secret: nil, Algorithm: "HS256", NonceLength: 32, - Claims: nil, + Profile: nil, } // NewToken create a new token by provided options. @@ -83,9 +83,20 @@ func NewToken(opts NewTokenOptions) (*Token, error) { } } - for key, val := range opts.Claims { - if err = tkn.Set(key, val); err != nil { - return nil, fmt.Errorf("failed to set JWT token claim: %w", err) + if opts.Profile != nil { + for key, val := range map[string]interface{}{ + "name": opts.Profile.GetName(), + "url": opts.Profile.GetURL(), + "photo": opts.Profile.GetPhoto(), + "email": opts.Profile.GetEmail(), + } { + if val == nil { + continue + } + + if err = tkn.Set(key, val); err != nil { + return nil, fmt.Errorf("failed to set JWT token claim: %w", err) + } } } @@ -111,8 +122,8 @@ func NewToken(opts NewTokenOptions) (*Token, error) { ClientID: opts.Issuer, CreatedAt: now, Expiry: now.Add(opts.Expiration), - Extra: opts.Claims, Me: opts.Subject, + Profile: opts.Profile, RefreshToken: "", // TODO(toby3d) Scope: opts.Scope, }, nil @@ -165,7 +176,7 @@ func TestToken(tb testing.TB) *Token { ClientID: cid, Me: me, Scope: scope, - Extra: nil, + Profile: TestProfile(tb), AccessToken: string(accessToken), RefreshToken: "", // TODO(toby3d) } diff --git a/internal/token/usecase/token_ucase.go b/internal/token/usecase/token_ucase.go index 14d07d9..d7d1519 100644 --- a/internal/token/usecase/token_ucase.go +++ b/internal/token/usecase/token_ucase.go @@ -20,7 +20,10 @@ type tokenUseCase struct { } func NewTokenUseCase(tokens token.Repository, sessions session.Repository, config *domain.Config) token.UseCase { + jwt.RegisterCustomField("email", new(domain.Email)) + jwt.RegisterCustomField("photo", new(domain.URL)) jwt.RegisterCustomField("scope", make(domain.Scopes, 0)) + jwt.RegisterCustomField("url", new(domain.URL)) return &tokenUseCase{ config: config, @@ -56,28 +59,27 @@ func (useCase *tokenUseCase) Exchange(ctx context.Context, opts token.ExchangeOp return nil, nil, token.ErrEmptyScope } + if !session.Scope.Has(domain.ScopeProfile) { + session.Profile = nil + } else if !session.Scope.Has(domain.ScopeEmail) { + session.Profile.Email = nil + } + tkn, err := domain.NewToken(domain.NewTokenOptions{ - Algorithm: useCase.config.JWT.Algorithm, Expiration: useCase.config.JWT.Expiry, Issuer: session.ClientID, - NonceLength: useCase.config.JWT.NonceLength, + Subject: session.Me, Scope: session.Scope, Secret: []byte(useCase.config.JWT.Secret), - Subject: session.Me, + Profile: session.Profile, + Algorithm: useCase.config.JWT.Algorithm, + NonceLength: useCase.config.JWT.NonceLength, }) if err != nil { return nil, nil, fmt.Errorf("cannot generate a new access token: %w", err) } - if !session.Scope.Has(domain.ScopeProfile) { - return tkn, nil, nil - } - - p := new(domain.Profile) - - // TODO(toby3d): if session.Scope.Has(domain.ScopeEmail) {} - - return tkn, p, nil + return tkn, session.Profile, nil } func (useCase *tokenUseCase) Verify(ctx context.Context, accessToken string) (*domain.Token, error) { @@ -100,8 +102,16 @@ func (useCase *tokenUseCase) Verify(ctx context.Context, accessToken string) (*d return nil, fmt.Errorf("cannot validate JWT token: %w", err) } - result := new(domain.Token) - result.AccessToken = accessToken + result := &domain.Token{ + CreatedAt: tkn.IssuedAt(), + Expiry: tkn.Expiration(), + ClientID: nil, + Me: nil, + Profile: nil, + Scope: nil, + AccessToken: accessToken, + RefreshToken: "", // TODO(toby3d) + } result.ClientID, _ = domain.ParseClientID(tkn.Issuer()) result.Me, _ = domain.ParseMe(tkn.Subject()) @@ -109,6 +119,40 @@ func (useCase *tokenUseCase) Verify(ctx context.Context, accessToken string) (*d result.Scope, _ = scope.(domain.Scopes) } + if !result.Scope.Has(domain.ScopeProfile) { + return result, nil + } + + result.Profile = domain.NewProfile() + + if name, ok := tkn.Get("name"); ok { + if n, ok := name.(string); ok { + result.Profile.Name = append(result.Profile.Name, n) + } + } + + if url, ok := tkn.Get("url"); ok { + if u, ok := url.(*domain.URL); ok { + result.Profile.URL = append(result.Profile.URL, u) + } + } + + if photo, ok := tkn.Get("photo"); ok { + if p, ok := photo.(*domain.URL); ok { + result.Profile.Photo = append(result.Profile.Photo, p) + } + } + + if !result.Scope.Has(domain.ScopeEmail) { + return result, nil + } + + if email, ok := tkn.Get("email"); ok { + if e, ok := email.(*domain.Email); ok { + result.Profile.Email = append(result.Profile.Email, e) + } + } + return result, nil } diff --git a/internal/user/delivery/http/user_http.go b/internal/user/delivery/http/user_http.go index 08374c8..c8eeca2 100644 --- a/internal/user/delivery/http/user_http.go +++ b/internal/user/delivery/http/user_http.go @@ -88,18 +88,24 @@ func (h *RequestHandler) handleUserInformation(ctx *http.RequestCtx) { } resp := new(UserInformationResponse) - if tkn.Extra == nil { + if tkn.Profile == nil { _ = encoder.Encode(resp) return } - resp.Name, _ = tkn.Extra["name"].(string) - resp.URL, _ = tkn.Extra["url"].(string) - resp.Photo, _ = tkn.Extra["photo"].(string) + resp.Name = tkn.Profile.GetName() - if tkn.Scope.Has(domain.ScopeEmail) { - resp.Email, _ = tkn.Extra["email"].(string) + if url := tkn.Profile.GetURL(); url != nil { + resp.URL = url.String() + } + + if photo := tkn.Profile.GetPhoto(); photo != nil { + resp.Photo = photo.String() + } + + if email := tkn.Profile.GetEmail(); email != nil && tkn.Scope.Has(domain.ScopeEmail) { + resp.Email = email.String() } _ = encoder.Encode(resp)