♻️ Refactored ticket package
This commit is contained in:
parent
b80fd68b3d
commit
143bc65d3b
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
http "github.com/valyala/fasthttp"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
)
|
||||
|
@ -67,17 +66,22 @@ func initConfig() {
|
|||
log.Fatalln("fail to read config:", err)
|
||||
}
|
||||
|
||||
u, logo, redirect := http.AcquireURI(), http.AcquireURI(), http.AcquireURI()
|
||||
if err = u.Parse(nil, []byte(rootURL)); err != nil {
|
||||
log.Fatalln("cannot parse client URL:", err)
|
||||
url, err := domain.NewURL(rootURL)
|
||||
if err != nil {
|
||||
log.Fatalln("cannot parse root URL as client URL:", err)
|
||||
}
|
||||
|
||||
u.CopyTo(logo)
|
||||
u.CopyTo(redirect)
|
||||
redirect.SetPath("/callback")
|
||||
logo.SetPath(config.Server.StaticURLPrefix + "/icon.svg")
|
||||
logo, err := domain.NewURL(rootURL + config.Server.StaticURLPrefix + "/icon.svg")
|
||||
if err != nil {
|
||||
log.Fatalln("cannot parse root URL as client URL:", err)
|
||||
}
|
||||
|
||||
client.URL = []*http.URI{u}
|
||||
client.Logo = []*http.URI{logo}
|
||||
client.RedirectURI = []*http.URI{redirect}
|
||||
redirectURI, err := domain.NewURL(rootURL + "/callback")
|
||||
if err != nil {
|
||||
log.Fatalln("cannot parse root URL as client URL:", err)
|
||||
}
|
||||
|
||||
client.URL = []*domain.URL{url}
|
||||
client.Logo = []*domain.URL{logo}
|
||||
client.RedirectURI = []*domain.URL{redirectURI}
|
||||
}
|
||||
|
|
|
@ -21,14 +21,11 @@ import (
|
|||
"golang.org/x/text/message"
|
||||
|
||||
clienthttpdelivery "source.toby3d.me/website/oauth/internal/client/delivery/http"
|
||||
clientrepo "source.toby3d.me/website/oauth/internal/client/repository/http"
|
||||
clientucase "source.toby3d.me/website/oauth/internal/client/usecase"
|
||||
healthhttpdelivery "source.toby3d.me/website/oauth/internal/health/delivery/http"
|
||||
metadatahttpdelivery "source.toby3d.me/website/oauth/internal/metadata/delivery/http"
|
||||
tickethttpdelivery "source.toby3d.me/website/oauth/internal/ticket/delivery/http"
|
||||
ticketrepo "source.toby3d.me/website/oauth/internal/ticket/repository/http"
|
||||
ticketucase "source.toby3d.me/website/oauth/internal/ticket/usecase"
|
||||
userrepo "source.toby3d.me/website/oauth/internal/user/repository/http"
|
||||
userucase "source.toby3d.me/website/oauth/internal/user/usecase"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -84,9 +81,15 @@ func startServer(cmd *cobra.Command, args []string) {
|
|||
healthhttpdelivery.NewRequestHandler().Register(r)
|
||||
metadatahttpdelivery.NewRequestHandler(config).Register(r)
|
||||
clienthttpdelivery.NewRequestHandler(config, client, matcher).Register(r)
|
||||
/*
|
||||
serverhttpdelivery.NewRequestHandler(serverhttpdelivery.Config{
|
||||
Config: config,
|
||||
Clients: clientucase.NewClientUseCase(clientrepo.NewHTTPClientRepository(httpClient)),
|
||||
Matcher: matcher,
|
||||
}).Register(r)
|
||||
*/
|
||||
tickethttpdelivery.NewRequestHandler(
|
||||
ticketucase.NewTicketUseCase(httpClient),
|
||||
userucase.NewUserUseCase(userrepo.NewHTTPUserRepository(httpClient)),
|
||||
ticketucase.NewTicketUseCase(ticketrepo.NewHTTPTicketRepository(httpClient), httpClient),
|
||||
).Register(r)
|
||||
|
||||
if enablePprof {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
|
@ -59,3 +60,16 @@ func (u *URL) UnmarshalJSON(v []byte) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *URL) URL() *url.URL {
|
||||
if u.URI == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := url.ParseRequestURI(u.URI.String())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"source.toby3d.me/website/oauth/internal/common"
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/ticket"
|
||||
"source.toby3d.me/website/oauth/internal/user"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -24,19 +23,18 @@ type (
|
|||
Resource *domain.URL `form:"resource"`
|
||||
|
||||
// The access token should be used when acting on behalf of this URL.
|
||||
// WARN(toby3d): deadcode for now
|
||||
Subject *domain.Me `form:"subject"`
|
||||
}
|
||||
|
||||
RequestHandler struct {
|
||||
users user.UseCase
|
||||
tickets ticket.UseCase
|
||||
useCase ticket.UseCase
|
||||
}
|
||||
)
|
||||
|
||||
func NewRequestHandler(tickets ticket.UseCase, users user.UseCase) *RequestHandler {
|
||||
func NewRequestHandler(useCase ticket.UseCase) *RequestHandler {
|
||||
return &RequestHandler{
|
||||
tickets: tickets,
|
||||
users: users,
|
||||
useCase: useCase,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,20 +56,11 @@ func (h *RequestHandler) update(ctx *http.RequestCtx) {
|
|||
return
|
||||
}
|
||||
|
||||
// TODO(toby3d): fetch token endpoint on Resource URL instead
|
||||
u, err := h.users.Fetch(ctx, req.Subject)
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(http.StatusBadRequest)
|
||||
encoder.Encode(domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: err.Error(),
|
||||
Frame: xerrors.Caller(1),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.tickets.Redeem(ctx, u.TokenEndpoint, req.Ticket)
|
||||
token, err := h.useCase.Redeem(ctx, &domain.Ticket{
|
||||
Ticket: req.Ticket,
|
||||
Resource: req.Resource,
|
||||
Subject: req.Subject,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(http.StatusBadRequest)
|
||||
encoder.Encode(domain.Error{
|
||||
|
|
|
@ -15,9 +15,8 @@ import (
|
|||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/testing/httptest"
|
||||
delivery "source.toby3d.me/website/oauth/internal/ticket/delivery/http"
|
||||
ticketrepo "source.toby3d.me/website/oauth/internal/ticket/repository/memory"
|
||||
ucase "source.toby3d.me/website/oauth/internal/ticket/usecase"
|
||||
userrepo "source.toby3d.me/website/oauth/internal/user/repository/memory"
|
||||
userucase "source.toby3d.me/website/oauth/internal/user/usecase"
|
||||
)
|
||||
|
||||
// TODO(toby3d): looks ugly, refactor this?
|
||||
|
@ -30,7 +29,10 @@ func TestUpdate(t *testing.T) {
|
|||
token := domain.TestToken(t)
|
||||
|
||||
store := new(sync.Map)
|
||||
store.Store(path.Join(userrepo.DefaultPathPrefix, ticket.Subject.String()), domain.TestUser(t))
|
||||
store.Store(
|
||||
path.Join(ticketrepo.DefaultPathPrefix, ticket.Resource.String()),
|
||||
domain.TestURL(t, "https://example.com/token"),
|
||||
)
|
||||
|
||||
userClient, _, userCleanup := httptest.New(t, func(ctx *http.RequestCtx) {
|
||||
ctx.SuccessString(common.MIMEApplicationJSONCharsetUTF8, fmt.Sprintf(`{
|
||||
|
@ -47,9 +49,8 @@ func TestUpdate(t *testing.T) {
|
|||
client, _, cleanup := httptest.New(t, r.Handler)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
delivery.NewRequestHandler(
|
||||
ucase.NewTicketUseCase(userClient), userucase.NewUserUseCase(userrepo.NewMemoryUserRepository(store)),
|
||||
).Register(r)
|
||||
delivery.NewRequestHandler(ucase.NewTicketUseCase(ticketrepo.NewMemoryTicketRepository(store), userClient)).
|
||||
Register(r)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "https://example.com/ticket", []byte(
|
||||
`ticket=`+ticket.Ticket+
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package ticket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
// Get returns token endpoint founded by resource URL.
|
||||
Get(ctx context.Context, resource *domain.URL) (*domain.URL, error)
|
||||
}
|
||||
|
||||
var ErrNotExist = errors.New("token_endpoint not found on resource URL")
|
|
@ -0,0 +1,174 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/tomnomnom/linkheader"
|
||||
http "github.com/valyala/fasthttp"
|
||||
"willnorris.com/go/microformats"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/ticket"
|
||||
)
|
||||
|
||||
type (
|
||||
//nolint: tagliatelle
|
||||
Response struct {
|
||||
// The server's issuer identifier. The issuer identifier is a
|
||||
// URL that uses the "https" scheme and has no query or fragment
|
||||
// components. The identifier MUST be a prefix of the
|
||||
// indieauth-metadata URL. e.g. for an indieauth-metadata
|
||||
// endpoint
|
||||
// https://example.com/.well-known/oauth-authorization-server,
|
||||
// the issuer URL could be https://example.com/, or for a
|
||||
// metadata URL of
|
||||
// https://example.com/wp-json/indieauth/1.0/metadata, the
|
||||
// issuer URL could be https://example.com/wp-json/indieauth/1.0
|
||||
Issuer *domain.URL `json:"issuer"`
|
||||
|
||||
// The Authorization Endpoint.
|
||||
AuthorizationEndpoint *domain.URL `json:"authorization_endpoint"`
|
||||
|
||||
// The Token Endpoint.
|
||||
TokenEndpoint *domain.URL `json:"token_endpoint"`
|
||||
|
||||
// JSON array containing scope values supported by the
|
||||
// IndieAuth server. Servers MAY choose not to advertise some
|
||||
// supported scope values even when this parameter is used.
|
||||
ScopesSupported domain.Scopes `json:"scopes_supported,omitempty"`
|
||||
|
||||
// JSON array containing the response_type values supported.
|
||||
// This differs from RFC8414 in that this parameter is OPTIONAL
|
||||
// and that, if omitted, the default is code.
|
||||
ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"`
|
||||
|
||||
// JSON array containing grant type values supported. If
|
||||
// omitted, the default value differs from RFC8414 and is
|
||||
// authorization_code.
|
||||
GrantTypesSupported []domain.GrantType `json:"grant_types_supported,omitempty"`
|
||||
|
||||
// URL of a page containing human-readable information that
|
||||
// developers might need to know when using the server. This
|
||||
// might be a link to the IndieAuth spec or something more
|
||||
// personal to your implementation.
|
||||
ServiceDocumentation *domain.URL `json:"service_documentation,omitempty"`
|
||||
|
||||
// JSON array containing the methods supported for PKCE. This
|
||||
// parameter differs from RFC8414 in that it is not optional as
|
||||
// PKCE is REQUIRED.
|
||||
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
||||
|
||||
// Boolean parameter indicating whether the authorization server
|
||||
// provides the iss parameter. If omitted, the default value is
|
||||
// false. As the iss parameter is REQUIRED, this is provided for
|
||||
// compatibility with OAuth 2.0 servers implementing the
|
||||
// parameter.
|
||||
//
|
||||
//nolint: lll
|
||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||
}
|
||||
|
||||
httpTicketRepository struct {
|
||||
client *http.Client
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
relIndieAuthMetadata string = "indieauth-metadata"
|
||||
relTokenEndpoint string = "token_endpoint"
|
||||
)
|
||||
|
||||
func NewHTTPTicketRepository(client *http.Client) ticket.Repository {
|
||||
return &httpTicketRepository{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *httpTicketRepository) Get(_ context.Context, resource *domain.URL) (*domain.URL, error) {
|
||||
req := http.AcquireRequest()
|
||||
defer http.ReleaseRequest(req)
|
||||
req.Header.SetMethod(http.MethodGet)
|
||||
req.SetRequestURIBytes(resource.FullURI())
|
||||
|
||||
resp := http.AcquireResponse()
|
||||
defer http.ReleaseResponse(resp)
|
||||
|
||||
if err := repo.client.Do(req, resp); err != nil {
|
||||
return nil, fmt.Errorf("cannot fetch resource URL: %w", err)
|
||||
}
|
||||
|
||||
if metadataEndpoint := extractEndpoint(resp, relIndieAuthMetadata); metadataEndpoint != nil {
|
||||
if result, err := extractFromMetadata(repo.client, metadataEndpoint); err == nil && result != nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
if result := extractEndpoint(resp, relTokenEndpoint); result != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return nil, ticket.ErrNotExist
|
||||
}
|
||||
|
||||
func extractEndpoint(resp *http.Response, name string) *domain.URL {
|
||||
u, err := extractEndpointFromHeader(resp, name)
|
||||
if err == nil && u != nil {
|
||||
return u
|
||||
}
|
||||
|
||||
if u, err = extractEndpointFromBody(resp, name); err == nil && u != nil {
|
||||
return u
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractEndpointFromHeader(resp *http.Response, name string) (*domain.URL, error) {
|
||||
for _, link := range linkheader.Parse(string(resp.Header.Peek(http.HeaderLink))) {
|
||||
if !strings.EqualFold(link.Rel, name) {
|
||||
continue
|
||||
}
|
||||
|
||||
u := http.AcquireURI()
|
||||
if err := u.Parse(resp.Header.Peek(http.HeaderHost), []byte(link.URL)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.URL{URI: u}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func extractEndpointFromBody(resp *http.Response, name string) (*domain.URL, error) {
|
||||
host, err := url.Parse(string(resp.Header.Peek(http.HeaderHost)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse host header: %w", err)
|
||||
}
|
||||
|
||||
endpoints, ok := microformats.Parse(bytes.NewReader(resp.Body()), host).Rels[name]
|
||||
if !ok || len(endpoints) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return domain.NewURL(endpoints[len(endpoints)-1])
|
||||
}
|
||||
|
||||
func extractFromMetadata(client *http.Client, endpoint *domain.URL) (*domain.URL, error) {
|
||||
_, body, err := client.Get(nil, endpoint.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := new(Response)
|
||||
if err = json.Unmarshal(body, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.TokenEndpoint, nil
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package http_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/fasthttp/router"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
http "github.com/valyala/fasthttp"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/common"
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/testing/httptest"
|
||||
repository "source.toby3d.me/website/oauth/internal/ticket/repository/http"
|
||||
)
|
||||
|
||||
type TestCase struct {
|
||||
name string
|
||||
bodyLinks map[string]string
|
||||
metadata string
|
||||
}
|
||||
|
||||
const testBody string = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Secret</title>
|
||||
%s
|
||||
</head>
|
||||
<body>
|
||||
<h1>Nothing to see here.</h1>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
resource := domain.TestURL(t, "https://alice.example.com/private")
|
||||
endpoint := domain.TestURL(t, "https://example.org/token")
|
||||
|
||||
for _, testCase := range []TestCase{{
|
||||
name: "link",
|
||||
bodyLinks: map[string]string{
|
||||
"token_endpoint": endpoint.String(),
|
||||
},
|
||||
metadata: `{"token_endpoint": ""}`,
|
||||
}, {
|
||||
name: "metadata",
|
||||
bodyLinks: map[string]string{
|
||||
"indieauth-metadata": "https://example.com/.well-known/oauth-authorization-server",
|
||||
},
|
||||
metadata: `{"token_endpoint": "` + endpoint.String() + `"}`,
|
||||
}, {
|
||||
name: "fallback",
|
||||
bodyLinks: map[string]string{
|
||||
"token_endpoint": endpoint.String(),
|
||||
"indieauth-metadata": "https://example.com/.well-known/oauth-authorization-server",
|
||||
},
|
||||
metadata: `{}`,
|
||||
}, {
|
||||
name: "priority",
|
||||
bodyLinks: map[string]string{
|
||||
"token_endpoint": "dont-touch-me",
|
||||
"indieauth-metadata": "https://example.com/.well-known/oauth-authorization-server",
|
||||
},
|
||||
metadata: `{"token_endpoint": "` + endpoint.String() + `"}`,
|
||||
}} {
|
||||
testCase := testCase
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := router.New()
|
||||
r.GET("/.well-known/oauth-authorization-server", func(ctx *http.RequestCtx) {
|
||||
ctx.SuccessString(common.MIMEApplicationJSONCharsetUTF8, testCase.metadata)
|
||||
})
|
||||
r.GET("/private", func(ctx *http.RequestCtx) {
|
||||
bodyLinks := make([]string, 0)
|
||||
for k, v := range testCase.bodyLinks {
|
||||
bodyLinks = append(bodyLinks, `<link rel="`+k+`" href="`+v+`">`)
|
||||
}
|
||||
|
||||
ctx.SuccessString(
|
||||
common.MIMETextHTMLCharsetUTF8,
|
||||
fmt.Sprintf(testBody, strings.Join(bodyLinks, "\n")),
|
||||
)
|
||||
})
|
||||
|
||||
client, _, cleanup := httptest.New(t, r.Handler)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
result, err := repository.NewHTTPTicketRepository(client).
|
||||
Get(context.Background(), resource)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, endpoint.String(), result.String())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/ticket"
|
||||
)
|
||||
|
||||
type memoryTicketRepository struct {
|
||||
store *sync.Map
|
||||
}
|
||||
|
||||
const DefaultPathPrefix string = "tickets"
|
||||
|
||||
func NewMemoryTicketRepository(store *sync.Map) ticket.Repository {
|
||||
return &memoryTicketRepository{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *memoryTicketRepository) Get(_ context.Context, resource *domain.URL) (*domain.URL, error) {
|
||||
src, ok := repo.store.Load(path.Join(DefaultPathPrefix, resource.String()))
|
||||
if !ok {
|
||||
return nil, ticket.ErrNotExist
|
||||
}
|
||||
|
||||
result, ok := src.(*domain.URL)
|
||||
if !ok {
|
||||
return nil, ticket.ErrNotExist
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package memory_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
repository "source.toby3d.me/website/oauth/internal/ticket/repository/memory"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ticket := domain.TestTicket(t)
|
||||
user := domain.TestUser(t)
|
||||
|
||||
store := new(sync.Map)
|
||||
store.Store(path.Join(repository.DefaultPathPrefix, ticket.Resource.String()), user.TokenEndpoint)
|
||||
|
||||
result, err := repository.NewMemoryTicketRepository(store).Get(context.Background(), ticket.Resource)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.TokenEndpoint.String(), result.String())
|
||||
}
|
|
@ -8,5 +8,5 @@ import (
|
|||
|
||||
type UseCase interface {
|
||||
// Redeem transform received ticket into access token.
|
||||
Redeem(ctx context.Context, endpoint *domain.URL, ticket string) (*domain.Token, error)
|
||||
Redeem(ctx context.Context, ticket *domain.Ticket) (*domain.Token, error)
|
||||
}
|
||||
|
|
|
@ -23,16 +23,23 @@ type (
|
|||
|
||||
ticketUseCase struct {
|
||||
client *http.Client
|
||||
repo ticket.Repository
|
||||
}
|
||||
)
|
||||
|
||||
func NewTicketUseCase(client *http.Client) ticket.UseCase {
|
||||
func NewTicketUseCase(repo ticket.Repository, client *http.Client) ticket.UseCase {
|
||||
return &ticketUseCase{
|
||||
client: client,
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (useCase *ticketUseCase) Redeem(ctx context.Context, endpoint *domain.URL, ticket string) (*domain.Token, error) {
|
||||
func (useCase *ticketUseCase) Redeem(ctx context.Context, ticket *domain.Ticket) (*domain.Token, error) {
|
||||
endpoint, err := useCase.repo.Get(ctx, ticket.Resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot discovery token endpoint: %w", err)
|
||||
}
|
||||
|
||||
req := http.AcquireRequest()
|
||||
defer http.ReleaseRequest(req)
|
||||
req.Header.SetMethod(http.MethodPost)
|
||||
|
@ -40,7 +47,7 @@ func (useCase *ticketUseCase) Redeem(ctx context.Context, endpoint *domain.URL,
|
|||
req.Header.SetContentType(common.MIMEApplicationForm)
|
||||
req.Header.Set(http.HeaderAccept, common.MIMEApplicationJSON)
|
||||
req.PostArgs().Set("grant_type", domain.GrantTypeTicket.String())
|
||||
req.PostArgs().Set("ticket", ticket)
|
||||
req.PostArgs().Set("ticket", ticket.Ticket)
|
||||
|
||||
resp := http.AcquireResponse()
|
||||
defer http.ReleaseResponse(resp)
|
||||
|
|
|
@ -14,8 +14,8 @@ import (
|
|||
"source.toby3d.me/website/oauth/internal/common"
|
||||
"source.toby3d.me/website/oauth/internal/domain"
|
||||
"source.toby3d.me/website/oauth/internal/testing/httptest"
|
||||
repo "source.toby3d.me/website/oauth/internal/ticket/repository/memory"
|
||||
ucase "source.toby3d.me/website/oauth/internal/ticket/usecase"
|
||||
userrepo "source.toby3d.me/website/oauth/internal/user/repository/memory"
|
||||
)
|
||||
|
||||
func TestRedeem(t *testing.T) {
|
||||
|
@ -25,20 +25,23 @@ func TestRedeem(t *testing.T) {
|
|||
ticket := domain.TestTicket(t)
|
||||
|
||||
store := new(sync.Map)
|
||||
store.Store(path.Join(userrepo.DefaultPathPrefix, ticket.Subject.String()), domain.TestUser(t))
|
||||
store.Store(
|
||||
path.Join(repo.DefaultPathPrefix, ticket.Resource.String()),
|
||||
domain.TestURL(t, "https://example.com/token"),
|
||||
)
|
||||
|
||||
client, _, cleanup := httptest.New(t, func(ctx *http.RequestCtx) {
|
||||
ctx.SuccessString(common.MIMEApplicationJSONCharsetUTF8, fmt.Sprintf(`{
|
||||
"access_token": "%s",
|
||||
"token_type": "Bearer",
|
||||
"access_token": "%s",
|
||||
"scope": "%s",
|
||||
"me": "%s"
|
||||
}`, token.AccessToken, token.Scope.String(), token.Me.String()))
|
||||
})
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
result, err := ucase.NewTicketUseCase(client).
|
||||
Redeem(context.Background(), domain.TestURL(t, "https://bob.example.com/token"), ticket.Ticket)
|
||||
result, err := ucase.NewTicketUseCase(repo.NewMemoryTicketRepository(store), client).
|
||||
Redeem(context.Background(), ticket)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, token.AccessToken, result.AccessToken)
|
||||
assert.Equal(t, token.Me.String(), result.Me.String())
|
||||
|
|
Loading…
Reference in New Issue