✨ Added Generate use case in token package
This commit is contained in:
parent
d5606340a0
commit
f99cd0955d
|
@ -7,9 +7,24 @@ import (
|
||||||
"source.toby3d.me/website/oauth/internal/domain"
|
"source.toby3d.me/website/oauth/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UseCase interface {
|
type (
|
||||||
Verify(ctx context.Context, accessToken string) (*domain.Token, error)
|
GenerateOptions struct {
|
||||||
Revoke(ctx context.Context, accessToken string) error
|
ClientID string
|
||||||
}
|
Me string
|
||||||
|
Scopes []string
|
||||||
|
NonceLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
UseCase interface {
|
||||||
|
// Generate generates a new Token based on the session data.
|
||||||
|
Generate(ctx context.Context, opts GenerateOptions) (*domain.Token, error)
|
||||||
|
|
||||||
|
// Verify checks the AccessToken and returns the associated information.
|
||||||
|
Verify(ctx context.Context, accessToken string) (*domain.Token, error)
|
||||||
|
|
||||||
|
// Revoke revokes the AccessToken and blocks its further use.
|
||||||
|
Revoke(ctx context.Context, accessToken string) error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var ErrRevoke = errors.New("this token has been revoked")
|
var ErrRevoke = errors.New("this token has been revoked")
|
||||||
|
|
|
@ -3,6 +3,7 @@ package usecase
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lestrrat-go/jwx/jwa"
|
"github.com/lestrrat-go/jwx/jwa"
|
||||||
"github.com/lestrrat-go/jwx/jwt"
|
"github.com/lestrrat-go/jwx/jwt"
|
||||||
|
@ -11,19 +12,60 @@ import (
|
||||||
|
|
||||||
"source.toby3d.me/website/oauth/internal/config"
|
"source.toby3d.me/website/oauth/internal/config"
|
||||||
"source.toby3d.me/website/oauth/internal/domain"
|
"source.toby3d.me/website/oauth/internal/domain"
|
||||||
|
"source.toby3d.me/website/oauth/internal/random"
|
||||||
"source.toby3d.me/website/oauth/internal/token"
|
"source.toby3d.me/website/oauth/internal/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tokenUseCase struct {
|
type (
|
||||||
tokens token.Repository
|
Config struct {
|
||||||
configer config.UseCase
|
Configer config.UseCase
|
||||||
|
Tokens token.Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenUseCase struct {
|
||||||
|
configer config.UseCase
|
||||||
|
tokens token.Repository
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTokenUseCase(config Config) token.UseCase {
|
||||||
|
return &tokenUseCase{
|
||||||
|
configer: config.Configer,
|
||||||
|
tokens: config.Tokens,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTokenUseCase(tokens token.Repository, configer config.UseCase) token.UseCase {
|
// Generate generates a new Token based on the session data.
|
||||||
return &tokenUseCase{
|
func (useCase *tokenUseCase) Generate(ctx context.Context, opts token.GenerateOptions) (*domain.Token, error) {
|
||||||
tokens: tokens,
|
nonce, err := random.String(opts.NonceLength)
|
||||||
configer: configer,
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot generate code")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t := jwt.New()
|
||||||
|
now := time.Now().UTC().Round(time.Second)
|
||||||
|
|
||||||
|
t.Set(jwt.IssuerKey, opts.ClientID)
|
||||||
|
t.Set(jwt.SubjectKey, opts.Me)
|
||||||
|
t.Set(jwt.ExpirationKey, now.Add(useCase.configer.GetIndieAuthAccessTokenExpirationTime()))
|
||||||
|
t.Set(jwt.NotBeforeKey, now)
|
||||||
|
t.Set(jwt.IssuedAtKey, now)
|
||||||
|
t.Set("scope", strings.Join(opts.Scopes, " "))
|
||||||
|
t.Set("nonce", nonce)
|
||||||
|
|
||||||
|
token, err := jwt.Sign(t,
|
||||||
|
jwa.SignatureAlgorithm(useCase.configer.GetIndieAuthJWTSigningAlgorithm()),
|
||||||
|
[]byte(useCase.configer.GetIndieAuthJWTSecret()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot sign a new access token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.Token{
|
||||||
|
Scopes: opts.Scopes,
|
||||||
|
AccessToken: string(token),
|
||||||
|
ClientID: opts.ClientID,
|
||||||
|
Me: opts.Me,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (useCase *tokenUseCase) Verify(ctx context.Context, accessToken string) (*domain.Token, error) {
|
func (useCase *tokenUseCase) Verify(ctx context.Context, accessToken string) (*domain.Token, error) {
|
||||||
|
|
|
@ -2,10 +2,11 @@ package usecase_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/lestrrat-go/jwx/jwt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -17,12 +18,38 @@ import (
|
||||||
ucase "source.toby3d.me/website/oauth/internal/token/usecase"
|
ucase "source.toby3d.me/website/oauth/internal/token/usecase"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVerify(t *testing.T) {
|
func TestGenerate(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
v := viper.New()
|
configer := configucase.NewConfigUseCase(configrepo.NewViperConfigRepository(domain.TestConfig(t)))
|
||||||
v.Set("indieauth.jwtSigningAlgorithm", "HS256")
|
options := token.GenerateOptions{
|
||||||
v.Set("indieauth.jwtSecret", "hackme")
|
ClientID: "https://app.example.com/",
|
||||||
|
Me: "https://user.example.net/",
|
||||||
|
Scopes: []string{"create", "update", "delete"},
|
||||||
|
NonceLength: 42,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := ucase.NewTokenUseCase(ucase.Config{
|
||||||
|
Configer: configer,
|
||||||
|
Tokens: nil,
|
||||||
|
}).Generate(context.TODO(), options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, options.ClientID, result.ClientID)
|
||||||
|
assert.Equal(t, options.Me, result.Me)
|
||||||
|
assert.Equal(t, options.Scopes, result.Scopes)
|
||||||
|
|
||||||
|
token, err := jwt.ParseString(result.AccessToken)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, options.Me, token.Subject())
|
||||||
|
assert.Equal(t, options.ClientID, token.Issuer())
|
||||||
|
|
||||||
|
scope, ok := token.Get("scope")
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, strings.Join(options.Scopes, " "), scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerify(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
repo := repository.NewMemoryTokenRepository(new(sync.Map))
|
repo := repository.NewMemoryTokenRepository(new(sync.Map))
|
||||||
useCase := ucase.NewTokenUseCase(repo, configucase.NewConfigUseCase(configrepo.NewViperConfigRepository(v)))
|
useCase := ucase.NewTokenUseCase(repo, configucase.NewConfigUseCase(configrepo.NewViperConfigRepository(v)))
|
||||||
|
|
Loading…
Reference in New Issue