From 0bb155ebbdc399510568a063df9a41fa5a6ae09a Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Mon, 2 Jan 2023 07:15:11 +0600 Subject: [PATCH] :replaced: Used url.URL instead domain.URL in domains --- internal/auth/delivery/http/auth_http.go | 22 +++---- internal/auth/usecase.go | 5 +- .../client/repository/http/http_client.go | 21 +++---- internal/domain/app.go | 10 ++-- internal/domain/client.go | 47 +++++++-------- internal/domain/client_test.go | 19 +++---- internal/domain/error.go | 8 +-- internal/domain/me.go | 57 ++++++------------- internal/domain/metadata.go | 41 ++++++------- internal/domain/profile.go | 21 +++---- internal/domain/session.go | 5 +- internal/domain/ticket.go | 5 +- internal/domain/url.go | 29 ++-------- internal/domain/user.go | 28 +++++---- internal/httputil/httputil.go | 24 ++++---- .../metadata/repository/http/http_metadata.go | 6 +- .../profile/repository/http/http_profile.go | 7 ++- internal/ticket/delivery/http/ticket_http.go | 4 +- .../ticket/delivery/http/ticket_http_test.go | 2 +- .../repository/sqlite3/sqlite3_ticket.go | 3 +- .../repository/sqlite3/sqlite3_ticket_test.go | 2 +- internal/ticket/usecase/ticket_ucase.go | 7 ++- internal/ticket/usecase/ticket_ucase_test.go | 2 +- internal/token/delivery/http/token_http.go | 2 +- internal/token/usecase.go | 3 +- internal/user/repository/http/http_user.go | 9 +-- .../user/repository/http/http_user_test.go | 2 +- 27 files changed, 179 insertions(+), 212 deletions(-) diff --git a/internal/auth/delivery/http/auth_http.go b/internal/auth/delivery/http/auth_http.go index f106352..c760960 100644 --- a/internal/auth/delivery/http/auth_http.go +++ b/internal/auth/delivery/http/auth_http.go @@ -206,7 +206,7 @@ func (h *RequestHandler) handleAuthorize(ctx *http.RequestCtx) { return } - if !client.ValidateRedirectURI(req.RedirectURI) { + if !client.ValidateRedirectURI(req.RedirectURI.URL) { ctx.SetStatusCode(http.StatusBadRequest) web.WriteTemplate(ctx, &web.ErrorPage{ BaseOf: baseOf, @@ -251,14 +251,10 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) { return } - redirectURL := http.AcquireURI() - defer http.ReleaseURI(redirectURL) - req.RedirectURI.CopyTo(redirectURL) - if strings.EqualFold(req.Authorize, "deny") { domain.NewError(domain.ErrorCodeAccessDenied, "user deny authorization request", "", req.State). - SetReirectURI(redirectURL) - ctx.Redirect(redirectURL.String(), http.StatusFound) + SetReirectURI(req.RedirectURI.URL) + ctx.Redirect(req.RedirectURI.String(), http.StatusFound) return } @@ -266,7 +262,7 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) { code, err := h.useCase.Generate(ctx, auth.GenerateOptions{ ClientID: req.ClientID, Me: req.Me, - RedirectURI: req.RedirectURI, + RedirectURI: req.RedirectURI.URL, CodeChallengeMethod: req.CodeChallengeMethod, Scope: req.Scope, CodeChallenge: req.CodeChallenge, @@ -284,10 +280,10 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) { "iss": h.config.Server.GetRootURL(), "state": req.State, } { - redirectURL.QueryArgs().Set(key, val) + req.RedirectURI.Query().Set(key, val) } - ctx.Redirect(redirectURL.String(), http.StatusFound) + ctx.Redirect(req.RedirectURI.String(), http.StatusFound) } func (h *RequestHandler) handleExchange(ctx *http.RequestCtx) { @@ -307,7 +303,7 @@ func (h *RequestHandler) handleExchange(ctx *http.RequestCtx) { me, profile, err := h.useCase.Exchange(ctx, auth.ExchangeOptions{ Code: req.Code, ClientID: req.ClientID, - RedirectURI: req.RedirectURI, + RedirectURI: req.RedirectURI.URL, CodeVerifier: req.CodeVerifier, }) if err != nil { @@ -322,8 +318,8 @@ func (h *RequestHandler) handleExchange(ctx *http.RequestCtx) { if profile != nil { userInfo = &AuthProfileResponse{ Email: profile.GetEmail(), - Photo: profile.GetPhoto(), - URL: profile.GetURL(), + Photo: &domain.URL{URL: profile.GetPhoto()}, + URL: &domain.URL{URL: profile.GetURL()}, Name: profile.GetName(), } } diff --git a/internal/auth/usecase.go b/internal/auth/usecase.go index 166e6c5..cf92b73 100644 --- a/internal/auth/usecase.go +++ b/internal/auth/usecase.go @@ -2,6 +2,7 @@ package auth import ( "context" + "net/url" "source.toby3d.me/toby3d/auth/internal/domain" ) @@ -10,7 +11,7 @@ type ( GenerateOptions struct { ClientID *domain.ClientID Me *domain.Me - RedirectURI *domain.URL + RedirectURI *url.URL CodeChallengeMethod domain.CodeChallengeMethod Scope domain.Scopes CodeChallenge string @@ -18,7 +19,7 @@ type ( ExchangeOptions struct { ClientID *domain.ClientID - RedirectURI *domain.URL + RedirectURI *url.URL Code string CodeVerifier string } diff --git a/internal/client/repository/http/http_client.go b/internal/client/repository/http/http_client.go index 7fe2391..81e6a21 100644 --- a/internal/client/repository/http/http_client.go +++ b/internal/client/repository/http/http_client.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "net/url" http "github.com/valyala/fasthttp" @@ -65,9 +66,9 @@ func (repo *httpClientRepository) Get(ctx context.Context, cid *domain.ClientID) client := &domain.Client{ ID: cid, - RedirectURI: make([]*domain.URL, 0), - Logo: make([]*domain.URL, 0), - URL: make([]*domain.URL, 0), + RedirectURI: make([]*url.URL, 0), + Logo: make([]*url.URL, 0), + URL: make([]*url.URL, 0), Name: make([]string, 0), } @@ -93,24 +94,24 @@ func extract(dst *domain.Client, src *http.Response) { for _, logo := range httputil.ExtractProperty(src, itemType, propertyLogo) { var ( - uri *domain.URL + u *url.URL err error ) switch l := logo.(type) { case string: - uri, err = domain.ParseURL(l) + u, err = url.Parse(l) case map[string]string: if value, ok := l["value"]; ok { - uri, err = domain.ParseURL(value) + u, err = url.Parse(value) } } - if err != nil || containsURL(dst.Logo, uri) { + if err != nil || containsURL(dst.Logo, u) { continue } - dst.Logo = append(dst.Logo, uri) + dst.Logo = append(dst.Logo, u) } for _, property := range httputil.ExtractProperty(src, itemType, propertyURL) { @@ -119,7 +120,7 @@ func extract(dst *domain.Client, src *http.Response) { continue } - if u, err := domain.ParseURL(prop); err == nil || !containsURL(dst.URL, u) { + if u, err := url.Parse(prop); err == nil || !containsURL(dst.URL, u) { dst.URL = append(dst.URL, u) } } @@ -138,7 +139,7 @@ func containsString(src []string, find string) bool { return false } -func containsURL(src []*domain.URL, find *domain.URL) bool { +func containsURL(src []*url.URL, find *url.URL) bool { for i := range src { if src[i].String() != find.String() { continue diff --git a/internal/domain/app.go b/internal/domain/app.go index 751119d..e1b1a49 100644 --- a/internal/domain/app.go +++ b/internal/domain/app.go @@ -1,8 +1,10 @@ package domain +import "net/url" + type App struct { - Logo []*URL - URL []*URL + Logo []*url.URL + URL []*url.URL Name []string } @@ -16,7 +18,7 @@ func (a App) GetName() string { } // GetURL safe returns first URL, if any. -func (a App) GetURL() *URL { +func (a App) GetURL() *url.URL { if len(a.URL) == 0 { return nil } @@ -25,7 +27,7 @@ func (a App) GetURL() *URL { } // GetLogo safe returns first logo, if any. -func (a App) GetLogo() *URL { +func (a App) GetLogo() *url.URL { if len(a.Logo) == 0 { return nil } diff --git a/internal/domain/client.go b/internal/domain/client.go index a983113..a2fb8d1 100644 --- a/internal/domain/client.go +++ b/internal/domain/client.go @@ -1,8 +1,8 @@ package domain import ( - "bytes" "net" + "net/url" "strings" "testing" ) @@ -10,9 +10,9 @@ import ( // Client describes the client requesting data about the user. type Client struct { ID *ClientID - Logo []*URL - RedirectURI []*URL - URL []*URL + Logo []*url.URL + RedirectURI []*url.URL + URL []*url.URL Name []string } @@ -20,9 +20,9 @@ type Client struct { func NewClient(cid *ClientID) *Client { return &Client{ ID: cid, - Logo: make([]*URL, 0), - RedirectURI: make([]*URL, 0), - URL: make([]*URL, 0), + Logo: make([]*url.URL, 0), + RedirectURI: make([]*url.URL, 0), + URL: make([]*url.URL, 0), Name: make([]string, 0), } } @@ -31,20 +31,15 @@ func NewClient(cid *ClientID) *Client { func TestClient(tb testing.TB) *Client { tb.Helper() - redirects := make([]*URL, 0) - for _, redirect := range []string{ - "https://app.example.com/redirect", - "https://app.example.net/redirect", - } { - redirects = append(redirects, TestURL(tb, redirect)) - } - return &Client{ - ID: TestClientID(tb), - Name: []string{"Example App"}, - URL: []*URL{TestURL(tb, "https://app.example.com/")}, - Logo: []*URL{TestURL(tb, "https://app.example.com/logo.png")}, - RedirectURI: redirects, + ID: TestClientID(tb), + Name: []string{"Example App"}, + URL: []*url.URL{{Scheme: "https", Host: "app.example.com", Path: "/"}}, + Logo: []*url.URL{{Scheme: "https", Host: "app.example.com", Path: "/logo.png"}}, + RedirectURI: []*url.URL{ + {Scheme: "https", Host: "app.example.com", Path: "/redirect"}, + {Scheme: "https", Host: "app.example.net", Path: "/redirect"}, + }, } } @@ -55,14 +50,14 @@ func TestClient(tb testing.TB) *Client { // match that of the client_id, then the authorization endpoint SHOULD verify // that the requested redirect_uri matches one of the redirect URLs published by // the client, and SHOULD block the request from proceeding if not. -func (c *Client) ValidateRedirectURI(redirectURI *URL) bool { +func (c *Client) ValidateRedirectURI(redirectURI *url.URL) bool { if redirectURI == nil { return false } - rHost, rPort, err := net.SplitHostPort(string(redirectURI.Host())) + rHost, rPort, err := net.SplitHostPort(redirectURI.Host) if err != nil { - rHost = string(redirectURI.Host()) + rHost = redirectURI.Hostname() } cHost, cPort, err := net.SplitHostPort(c.ID.clientID.Host) @@ -70,7 +65,7 @@ func (c *Client) ValidateRedirectURI(redirectURI *URL) bool { cHost = c.ID.clientID.Hostname() } - if bytes.EqualFold(redirectURI.Scheme(), []byte(c.ID.clientID.Scheme)) && + if strings.EqualFold(redirectURI.Scheme, c.ID.clientID.Scheme) && strings.EqualFold(rHost, cHost) && strings.EqualFold(rPort, cPort) { return true @@ -97,7 +92,7 @@ func (c Client) GetName() string { } // GetURL safe returns first URL, if any. -func (c Client) GetURL() *URL { +func (c Client) GetURL() *url.URL { if len(c.URL) == 0 { return nil } @@ -106,7 +101,7 @@ func (c Client) GetURL() *URL { } // GetLogo safe returns first logo, if any. -func (c Client) GetLogo() *URL { +func (c Client) GetLogo() *url.URL { if len(c.Logo) == 0 { return nil } diff --git a/internal/domain/client_test.go b/internal/domain/client_test.go index 0699c53..ae7cef8 100644 --- a/internal/domain/client_test.go +++ b/internal/domain/client_test.go @@ -1,7 +1,7 @@ package domain_test import ( - "fmt" + "net/url" "testing" "source.toby3d.me/toby3d/auth/internal/domain" @@ -12,20 +12,17 @@ func TestClient_ValidateRedirectURI(t *testing.T) { client := domain.TestClient(t) - for _, tc := range []struct { - name string - in *domain.URL - }{ - {name: "client_id prefix", in: domain.TestURL(t, fmt.Sprint(client.ID, "/callback"))}, - {name: "registered redirect_uri", in: client.RedirectURI[len(client.RedirectURI)-1]}, + for name, in := range map[string]*url.URL{ + "client_id prefix": client.ID.URL().JoinPath("/callback"), + "registered redirect_uri": client.RedirectURI[len(client.RedirectURI)-1], } { - tc := tc + name, in := name, in - t.Run(tc.name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() - if result := client.ValidateRedirectURI(tc.in); !result { - t.Errorf("ValidateRedirectURI(%v) = %t, want %t", tc.in, result, true) + if out := client.ValidateRedirectURI(in); !out { + t.Errorf("ValidateRedirectURI(%v) = %t, want %t", in, out, true) } }) } diff --git a/internal/domain/error.go b/internal/domain/error.go index 03302ce..b0d5748 100644 --- a/internal/domain/error.go +++ b/internal/domain/error.go @@ -2,10 +2,10 @@ package domain import ( "fmt" + "net/url" "strconv" "strings" - http "github.com/valyala/fasthttp" "golang.org/x/xerrors" "source.toby3d.me/toby3d/auth/internal/common" @@ -226,8 +226,8 @@ func (e Error) FormatError(printer xerrors.Printer) error { // SetReirectURI sets fasthttp.QueryArgs with the request state, code, // description and error URI in the provided fasthttp.URI. -func (e Error) SetReirectURI(uri *http.URI) { - if uri == nil { +func (e Error) SetReirectURI(u *url.URL) { + if u == nil { return } @@ -241,7 +241,7 @@ func (e Error) SetReirectURI(uri *http.URI) { continue } - uri.QueryArgs().Set(key, val) + u.Query().Set(key, val) } } diff --git a/internal/domain/me.go b/internal/domain/me.go index d89d870..9a6e6a3 100644 --- a/internal/domain/me.go +++ b/internal/domain/me.go @@ -7,21 +7,19 @@ import ( "strconv" "strings" "testing" - - http "github.com/valyala/fasthttp" ) // Me is a URL user identifier. type Me struct { - id *http.URI + id *url.URL } // ParseMe parse string as me URL identifier. // //nolint:funlen,cyclop func ParseMe(raw string) (*Me, error) { - id := http.AcquireURI() - if err := id.Parse(nil, []byte(raw)); err != nil { + id, err := url.Parse(raw) + if err != nil { return nil, NewError( ErrorCodeInvalidRequest, err.Error(), @@ -30,8 +28,7 @@ func ParseMe(raw string) (*Me, error) { ) } - scheme := string(id.Scheme()) - if scheme != "http" && scheme != "https" { + if id.Scheme != "http" && id.Scheme != "https" { return nil, NewError( ErrorCodeInvalidRequest, "profile URL MUST have either an https or http scheme", @@ -40,8 +37,11 @@ func ParseMe(raw string) (*Me, error) { ) } - path := string(id.PathOriginal()) - if path == "" || strings.Contains(path, "/.") || strings.Contains(path, "/..") { + if id.Path == "" { + id.Path = "/" + } + + if strings.Contains(id.Path, "/.") || strings.Contains(id.Path, "/..") { return nil, NewError( ErrorCodeInvalidRequest, "profile URL MUST contain a path component (/ is a valid path), MUST NOT contain single-dot "+ @@ -51,7 +51,7 @@ func ParseMe(raw string) (*Me, error) { ) } - if id.Hash() != nil { + if id.Fragment != "" { return nil, NewError( ErrorCodeInvalidRequest, "profile URL MUST NOT contain a fragment component", @@ -60,7 +60,7 @@ func ParseMe(raw string) (*Me, error) { ) } - if id.Username() != nil || id.Password() != nil { + if id.User != nil { return nil, NewError( ErrorCodeInvalidRequest, "profile URL MUST NOT contain a username or password component", @@ -69,8 +69,7 @@ func ParseMe(raw string) (*Me, error) { ) } - domain := string(id.Host()) - if domain == "" { + if id.Host == "" { return nil, NewError( ErrorCodeInvalidRequest, "profile host name MUST be a domain name", @@ -79,7 +78,7 @@ func ParseMe(raw string) (*Me, error) { ) } - if _, port, _ := net.SplitHostPort(domain); port != "" { + if _, port, _ := net.SplitHostPort(id.Host); port != "" { return nil, NewError( ErrorCodeInvalidRequest, "profile MUST NOT contain a port", @@ -88,7 +87,7 @@ func ParseMe(raw string) (*Me, error) { ) } - if net.ParseIP(domain) != nil { + if net.ParseIP(id.Host) != nil { return nil, NewError( ErrorCodeInvalidRequest, "profile MUST NOT be ipv4 or ipv6 addresses", @@ -146,37 +145,15 @@ func (m Me) MarshalJSON() ([]byte, error) { return []byte(strconv.Quote(m.String())), nil } -// URI returns copy of parsed me in *fasthttp.URI representation. -// This copy MUST be released via fasthttp.ReleaseURI. -func (m Me) URI() *http.URI { - if m.id == nil { - return nil - } - - u := http.AcquireURI() - m.id.CopyTo(u) - - return u -} - // URL returns copy of parsed me in *url.URL representation. func (m Me) URL() *url.URL { if m.id == nil { return nil } - return &url.URL{ - ForceQuery: false, - Fragment: string(m.id.Hash()), - Host: string(m.id.Host()), - Opaque: "", - Path: string(m.id.Path()), - RawFragment: "", - RawPath: string(m.id.PathOriginal()), - RawQuery: string(m.id.QueryString()), - Scheme: string(m.id.Scheme()), - User: nil, - } + out, _ := url.Parse(m.id.String()) + + return out } // String returns string representation of me. diff --git a/internal/domain/metadata.go b/internal/domain/metadata.go index 8f46c5f..f078453 100644 --- a/internal/domain/metadata.go +++ b/internal/domain/metadata.go @@ -1,6 +1,9 @@ package domain -import "testing" +import ( + "net/url" + "testing" +) type Metadata struct { // The server's issuer identifier. The issuer identifier is a URL that @@ -14,33 +17,33 @@ type Metadata struct { Issuer *ClientID // The Authorization Endpoint. - AuthorizationEndpoint *URL + AuthorizationEndpoint *url.URL // The Token Endpoint. - TokenEndpoint *URL + TokenEndpoint *url.URL // The Ticket Endpoint. - TicketEndpoint *URL + TicketEndpoint *url.URL // The Micropub Endpoint. - MicropubEndpoint *URL + MicropubEndpoint *url.URL // The Microsub Endpoint. - MicrosubEndpoint *URL + MicrosubEndpoint *url.URL // The Introspection Endpoint. - IntrospectionEndpoint *URL + IntrospectionEndpoint *url.URL // The Revocation Endpoint. - RevocationEndpoint *URL + RevocationEndpoint *url.URL // The User Info Endpoint. - UserinfoEndpoint *URL + UserinfoEndpoint *url.URL // 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 *URL + ServiceDocumentation *url.URL // JSON array containing scope values supported by the IndieAuth server. // Servers MAY choose not to advertise some supported scope values even @@ -79,15 +82,15 @@ func TestMetadata(tb testing.TB) *Metadata { return &Metadata{ Issuer: TestClientID(tb), - AuthorizationEndpoint: TestURL(tb, "https://indieauth.example.com/auth"), - TokenEndpoint: TestURL(tb, "https://indieauth.example.com/token"), - TicketEndpoint: TestURL(tb, "https://auth.example.org/ticket"), - MicropubEndpoint: TestURL(tb, "https://micropub.example.com/"), - MicrosubEndpoint: TestURL(tb, "https://microsub.example.com/"), - IntrospectionEndpoint: TestURL(tb, "https://indieauth.example.com/introspect"), - RevocationEndpoint: TestURL(tb, "https://indieauth.example.com/revocation"), - UserinfoEndpoint: TestURL(tb, "https://indieauth.example.com/userinfo"), - ServiceDocumentation: TestURL(tb, "https://indieauth.net/draft/"), + AuthorizationEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/auth"}, + TokenEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/token"}, + TicketEndpoint: &url.URL{Scheme: "https", Host: "auth.example.org", Path: "/ticket"}, + MicropubEndpoint: &url.URL{Scheme: "https", Host: "micropub.example.com", Path: "/"}, + MicrosubEndpoint: &url.URL{Scheme: "https", Host: "microsub.example.com", Path: "/"}, + IntrospectionEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/introspect"}, + RevocationEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/revocation"}, + UserinfoEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/userinfo"}, + ServiceDocumentation: &url.URL{Scheme: "https", Host: "indieauth.net", Path: "/draft/"}, ScopesSupported: Scopes{ ScopeBlock, ScopeChannels, diff --git a/internal/domain/profile.go b/internal/domain/profile.go index 3375357..55fdb01 100644 --- a/internal/domain/profile.go +++ b/internal/domain/profile.go @@ -1,21 +1,22 @@ package domain import ( + "net/url" "testing" ) // Profile describes the data about the user. type Profile struct { - Photo []*URL `json:"photo,omitempty"` - URL []*URL `json:"url,omitempty"` - Email []*Email `json:"email,omitempty"` - Name []string `json:"name,omitempty"` + Photo []*url.URL `json:"photo,omitempty"` + URL []*url.URL `json:"url,omitempty"` + Email []*Email `json:"email,omitempty"` + Name []string `json:"name,omitempty"` } func NewProfile() *Profile { return &Profile{ - Photo: make([]*URL, 0), - URL: make([]*URL, 0), + Photo: make([]*url.URL, 0), + URL: make([]*url.URL, 0), Email: make([]*Email, 0), Name: make([]string, 0), } @@ -28,8 +29,8 @@ func TestProfile(tb testing.TB) *Profile { return &Profile{ Email: []*Email{TestEmail(tb)}, Name: []string{"Example User"}, - Photo: []*URL{TestURL(tb, "https://user.example.net/photo.jpg")}, - URL: []*URL{TestURL(tb, "https://user.example.net/")}, + Photo: []*url.URL{{Scheme: "https", Host: "user.example.net", Path: "/photo.jpg"}}, + URL: []*url.URL{{Scheme: "https", Host: "user.example.net", Path: "/"}}, } } @@ -51,7 +52,7 @@ func (p Profile) HasURL() bool { } // GetURL safe returns first URL, if any. -func (p Profile) GetURL() *URL { +func (p Profile) GetURL() *url.URL { if len(p.URL) == 0 { return nil } @@ -64,7 +65,7 @@ func (p Profile) HasPhoto() bool { } // GetPhoto safe returns first photo, if any. -func (p Profile) GetPhoto() *URL { +func (p Profile) GetPhoto() *url.URL { if len(p.Photo) == 0 { return nil } diff --git a/internal/domain/session.go b/internal/domain/session.go index 23869d7..bda7542 100644 --- a/internal/domain/session.go +++ b/internal/domain/session.go @@ -1,6 +1,7 @@ package domain import ( + "net/url" "testing" "source.toby3d.me/toby3d/auth/internal/random" @@ -9,7 +10,7 @@ import ( //nolint:tagliatelle type Session struct { ClientID *ClientID `json:"client_id"` - RedirectURI *URL `json:"redirect_uri"` + RedirectURI *url.URL `json:"redirect_uri"` Me *Me `json:"me"` Profile *Profile `json:"profile,omitempty"` Scope Scopes `json:"scope"` @@ -36,7 +37,7 @@ func TestSession(tb testing.TB) *Session { CodeChallengeMethod: CodeChallengeMethodPLAIN, Profile: TestProfile(tb), Me: TestMe(tb, "https://user.example.net/"), - RedirectURI: TestURL(tb, "https://example.com/callback"), + RedirectURI: &url.URL{Scheme: "https", Host: "example.com", Path: "/callback"}, Scope: Scopes{ ScopeEmail, ScopeProfile, diff --git a/internal/domain/ticket.go b/internal/domain/ticket.go index 63289a2..06ccc4f 100644 --- a/internal/domain/ticket.go +++ b/internal/domain/ticket.go @@ -1,12 +1,13 @@ package domain import ( + "net/url" "testing" ) type Ticket struct { // The access token will work at this URL. - Resource *URL + Resource *url.URL // The access token should be used when acting on behalf of this URL. Subject *Me @@ -20,7 +21,7 @@ func TestTicket(tb testing.TB) *Ticket { tb.Helper() return &Ticket{ - Resource: TestURL(tb, "https://alice.example.com/private/"), + Resource: &url.URL{Scheme: "https", Host: "alice.example.com", Path: "/private/"}, Subject: TestMe(tb, "https://bob.example.com/"), Ticket: "32985723984723985792834", } diff --git a/internal/domain/url.go b/internal/domain/url.go index c922c41..6a95bf7 100644 --- a/internal/domain/url.go +++ b/internal/domain/url.go @@ -5,23 +5,21 @@ import ( "net/url" "strconv" "testing" - - http "github.com/valyala/fasthttp" ) // URL describe any valid HTTP URL. type URL struct { - *http.URI + *url.URL } // ParseURL parse string as URL. func ParseURL(src string) (*URL, error) { - u := http.AcquireURI() - if err := u.Parse(nil, []byte(src)); err != nil { + u, err := url.Parse(src) + if err != nil { return nil, fmt.Errorf("cannot parse URL: %w", err) } - return &URL{URI: u}, nil + return &URL{URL: u}, nil } // MustParseURL parse string as URL or panic. @@ -38,11 +36,10 @@ func MustParseURL(src string) *URL { func TestURL(tb testing.TB, src string) *URL { tb.Helper() - u := http.AcquireURI() - u.Update(src) + u, _ := url.Parse(src) return &URL{ - URI: u, + URL: u, } } @@ -78,17 +75,3 @@ func (u *URL) UnmarshalJSON(v []byte) error { func (u URL) MarshalJSON() ([]byte, error) { return []byte(strconv.Quote(u.String())), nil } - -// URL returns url.URL representation of URL. -func (u URL) URL() *url.URL { - if u.URI == nil { - return nil - } - - result, err := url.ParseRequestURI(u.String()) - if err != nil { - return nil - } - - return result -} diff --git a/internal/domain/user.go b/internal/domain/user.go index c2f0fd3..259969c 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -1,17 +1,18 @@ package domain import ( + "net/url" "testing" ) type User struct { Me *Me - AuthorizationEndpoint *URL - IndieAuthMetadata *URL - Micropub *URL - Microsub *URL - TicketEndpoint *URL - TokenEndpoint *URL + AuthorizationEndpoint *url.URL + IndieAuthMetadata *url.URL + Micropub *url.URL + Microsub *url.URL + TicketEndpoint *url.URL + TokenEndpoint *url.URL *Profile } @@ -22,11 +23,14 @@ func TestUser(tb testing.TB) *User { return &User{ Profile: TestProfile(tb), Me: TestMe(tb, "https://user.example.net/"), - AuthorizationEndpoint: TestURL(tb, "https://example.org/auth"), - IndieAuthMetadata: TestURL(tb, "https://example.org/.well-known/oauth-authorization-server"), - Micropub: TestURL(tb, "https://microsub.example.org/"), - Microsub: TestURL(tb, "https://micropub.example.org/"), - TicketEndpoint: TestURL(tb, "https://example.org/ticket"), - TokenEndpoint: TestURL(tb, "https://example.org/token"), + AuthorizationEndpoint: &url.URL{Scheme: "https", Host: "example.org", Path: "/auth"}, + IndieAuthMetadata: &url.URL{ + Scheme: "https", Host: "example.org", + Path: "/.well-known/oauth-authorization-server", + }, + Micropub: &url.URL{Scheme: "https", Host: "microsub.example.org", Path: "/"}, + Microsub: &url.URL{Scheme: "https", Host: "micropub.example.org", Path: "/"}, + TicketEndpoint: &url.URL{Scheme: "https", Host: "example.org", Path: "/ticket"}, + TokenEndpoint: &url.URL{Scheme: "https", Host: "example.org", Path: "/token"}, } } diff --git a/internal/httputil/httputil.go b/internal/httputil/httputil.go index 9b22a36..d51b62a 100644 --- a/internal/httputil/httputil.go +++ b/internal/httputil/httputil.go @@ -20,8 +20,8 @@ var ErrEndpointNotExist = domain.NewError( "https://indieauth.net/source/#discovery-0", ) -func ExtractEndpoints(resp *http.Response, rel string) []*domain.URL { - results := make([]*domain.URL, 0) +func ExtractEndpoints(resp *http.Response, rel string) []*url.URL { + results := make([]*url.URL, 0) urls, err := ExtractEndpointsFromHeader(resp, rel) if err == nil { @@ -36,40 +36,40 @@ func ExtractEndpoints(resp *http.Response, rel string) []*domain.URL { return results } -func ExtractEndpointsFromHeader(resp *http.Response, rel string) ([]*domain.URL, error) { - results := make([]*domain.URL, 0) +func ExtractEndpointsFromHeader(resp *http.Response, rel string) ([]*url.URL, error) { + results := make([]*url.URL, 0) for _, link := range linkheader.Parse(string(resp.Header.Peek(http.HeaderLink))) { if !strings.EqualFold(link.Rel, rel) { continue } - u := http.AcquireURI() - if err := u.Parse(resp.Header.Peek(http.HeaderHost), []byte(link.URL)); err != nil { + u, err := url.ParseRequestURI(link.URL) + if err != nil { return nil, fmt.Errorf("cannot parse header endpoint: %w", err) } - results = append(results, &domain.URL{URI: u}) + results = append(results, u) } return results, nil } -func ExtractEndpointsFromBody(resp *http.Response, rel string) ([]*domain.URL, error) { +func ExtractEndpointsFromBody(resp *http.Response, rel string) ([]*url.URL, error) { endpoints, ok := microformats.Parse(bytes.NewReader(resp.Body()), nil).Rels[rel] if !ok || len(endpoints) == 0 { return nil, ErrEndpointNotExist } - results := make([]*domain.URL, 0) + results := make([]*url.URL, 0) for i := range endpoints { - u := http.AcquireURI() - if err := u.Parse(resp.Header.Peek(http.HeaderHost), []byte(endpoints[i])); err != nil { + u, err := url.Parse(endpoints[i]) + if err != nil { return nil, fmt.Errorf("cannot parse body endpoint: %w", err) } - results = append(results, &domain.URL{URI: u}) + results = append(results, u) } return results, nil diff --git a/internal/metadata/repository/http/http_metadata.go b/internal/metadata/repository/http/http_metadata.go index b35d622..618432a 100644 --- a/internal/metadata/repository/http/http_metadata.go +++ b/internal/metadata/repository/http/http_metadata.go @@ -74,15 +74,15 @@ func (repo *httpMetadataRepository) Get(ctx context.Context, me *domain.Me) (*do //nolint:exhaustivestruct // TODO(toby3d) return &domain.Metadata{ - AuthorizationEndpoint: data.AuthorizationEndpoint, + AuthorizationEndpoint: data.AuthorizationEndpoint.URL, AuthorizationResponseIssParameterSupported: data.AuthorizationResponseIssParameterSupported, CodeChallengeMethodsSupported: data.CodeChallengeMethodsSupported, GrantTypesSupported: data.GrantTypesSupported, Issuer: data.Issuer, ResponseTypesSupported: data.ResponseTypesSupported, ScopesSupported: data.ScopesSupported, - ServiceDocumentation: data.ServiceDocumentation, - TokenEndpoint: data.TokenEndpoint, + ServiceDocumentation: data.ServiceDocumentation.URL, + TokenEndpoint: data.TokenEndpoint.URL, // TODO(toby3d): support extensions? // Micropub: data.Micropub, // Microsub: data.Microsub, diff --git a/internal/profile/repository/http/http_profile.go b/internal/profile/repository/http/http_profile.go index d6eccb2..ffd4ef1 100644 --- a/internal/profile/repository/http/http_profile.go +++ b/internal/profile/repository/http/http_profile.go @@ -3,6 +3,7 @@ package http import ( "context" "fmt" + "net/url" http "github.com/valyala/fasthttp" @@ -66,12 +67,12 @@ func (repo *httpProfileRepository) Get(ctx context.Context, me *domain.Me) (*dom } for _, rawURL := range httputil.ExtractProperty(resp, hCard, propertyURL) { - url, ok := rawURL.(string) + rawURL, ok := rawURL.(string) if !ok { continue } - if u, err := domain.ParseURL(url); err == nil { + if u, err := url.Parse(rawURL); err == nil { result.URL = append(result.URL, u) } } @@ -82,7 +83,7 @@ func (repo *httpProfileRepository) Get(ctx context.Context, me *domain.Me) (*dom continue } - if p, err := domain.ParseURL(photo); err == nil { + if p, err := url.Parse(photo); err == nil { result.Photo = append(result.Photo, p) } } diff --git a/internal/ticket/delivery/http/ticket_http.go b/internal/ticket/delivery/http/ticket_http.go index 59c6ce3..67a6d89 100644 --- a/internal/ticket/delivery/http/ticket_http.go +++ b/internal/ticket/delivery/http/ticket_http.go @@ -136,7 +136,7 @@ func (h *RequestHandler) handleSend(ctx *http.RequestCtx) { ticket := &domain.Ticket{ Ticket: "", - Resource: req.Resource, + Resource: req.Resource.URL, Subject: req.Subject, } @@ -177,7 +177,7 @@ func (h *RequestHandler) handleRedeem(ctx *http.RequestCtx) { token, err := h.tickets.Redeem(ctx, &domain.Ticket{ Ticket: req.Ticket, - Resource: req.Resource, + Resource: req.Resource.URL, Subject: req.Subject, }) if err != nil { diff --git a/internal/ticket/delivery/http/ticket_http_test.go b/internal/ticket/delivery/http/ticket_http_test.go index 8204f00..9eec61b 100644 --- a/internal/ticket/delivery/http/ticket_http_test.go +++ b/internal/ticket/delivery/http/ticket_http_test.go @@ -81,7 +81,7 @@ func NewDependencies(tb testing.TB) Dependencies { r := router.New() // NOTE(toby3d): private resource - r.GET(ticket.Resource.URL().EscapedPath(), func(ctx *http.RequestCtx) { + r.GET(ticket.Resource.Path, func(ctx *http.RequestCtx) { ctx.SuccessString(common.MIMETextHTMLCharsetUTF8, ``) }) diff --git a/internal/ticket/repository/sqlite3/sqlite3_ticket.go b/internal/ticket/repository/sqlite3/sqlite3_ticket.go index bcd64cc..1c6b615 100644 --- a/internal/ticket/repository/sqlite3/sqlite3_ticket.go +++ b/internal/ticket/repository/sqlite3/sqlite3_ticket.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "net/url" "time" "github.com/jmoiron/sqlx" @@ -117,5 +118,5 @@ func NewTicket(src *domain.Ticket) *Ticket { func (t *Ticket) Populate(dst *domain.Ticket) { dst.Ticket = t.Ticket dst.Subject, _ = domain.ParseMe(t.Subject) - dst.Resource, _ = domain.ParseURL(t.Resource) + dst.Resource, _ = url.Parse(t.Resource) } diff --git a/internal/ticket/repository/sqlite3/sqlite3_ticket_test.go b/internal/ticket/repository/sqlite3/sqlite3_ticket_test.go index 3e640a1..55307ea 100644 --- a/internal/ticket/repository/sqlite3/sqlite3_ticket_test.go +++ b/internal/ticket/repository/sqlite3/sqlite3_ticket_test.go @@ -12,7 +12,7 @@ import ( repository "source.toby3d.me/toby3d/auth/internal/ticket/repository/sqlite3" ) -//nolint: gochecknoglobals // slices cannot be contants +// nolint: gochecknoglobals // slices cannot be contants var tableColumns = []string{"created_at", "resource", "subject", "ticket"} func TestCreate(t *testing.T) { diff --git a/internal/ticket/usecase/ticket_ucase.go b/internal/ticket/usecase/ticket_ucase.go index cb74ea1..b285525 100644 --- a/internal/ticket/usecase/ticket_ucase.go +++ b/internal/ticket/usecase/ticket_ucase.go @@ -3,6 +3,7 @@ package usecase import ( "context" "fmt" + "net/url" "time" json "github.com/goccy/go-json" @@ -59,7 +60,7 @@ func (useCase *ticketUseCase) Generate(ctx context.Context, tkt *domain.Ticket) return fmt.Errorf("cannot discovery ticket subject: %w", err) } - var ticketEndpoint *domain.URL + var ticketEndpoint *url.URL // NOTE(toby3d): find metadata first if metadata, err := httputil.ExtractMetadata(resp, useCase.client); err == nil && metadata != nil { @@ -80,7 +81,7 @@ func (useCase *ticketUseCase) Generate(ctx context.Context, tkt *domain.Ticket) req.Reset() req.Header.SetMethod(http.MethodPost) - req.SetRequestURIBytes(ticketEndpoint.RequestURI()) + req.SetRequestURI(ticketEndpoint.String()) req.Header.SetContentType(common.MIMEApplicationForm) req.PostArgs().Set("ticket", tkt.Ticket) req.PostArgs().Set("subject", tkt.Subject.String()) @@ -107,7 +108,7 @@ func (useCase *ticketUseCase) Redeem(ctx context.Context, tkt *domain.Ticket) (* return nil, fmt.Errorf("cannot discovery ticket resource: %w", err) } - var tokenEndpoint *domain.URL + var tokenEndpoint *url.URL // NOTE(toby3d): find metadata first if metadata, err := httputil.ExtractMetadata(resp, useCase.client); err == nil && metadata != nil { diff --git a/internal/ticket/usecase/ticket_ucase_test.go b/internal/ticket/usecase/ticket_ucase_test.go index 61411ed..73199a8 100644 --- a/internal/ticket/usecase/ticket_ucase_test.go +++ b/internal/ticket/usecase/ticket_ucase_test.go @@ -21,7 +21,7 @@ func TestRedeem(t *testing.T) { ticket := domain.TestTicket(t) router := router.New() - router.GET(string(ticket.Resource.Path()), func(ctx *http.RequestCtx) { + router.GET(string(ticket.Resource.Path), func(ctx *http.RequestCtx) { ctx.SuccessString(common.MIMETextHTMLCharsetUTF8, ``) }) diff --git a/internal/token/delivery/http/token_http.go b/internal/token/delivery/http/token_http.go index 6139cbc..6b94433 100644 --- a/internal/token/delivery/http/token_http.go +++ b/internal/token/delivery/http/token_http.go @@ -244,7 +244,7 @@ func (h *RequestHandler) handleExchange(ctx *http.RequestCtx) { token, profile, err := h.tokens.Exchange(ctx, token.ExchangeOptions{ ClientID: req.ClientID, - RedirectURI: req.RedirectURI, + RedirectURI: req.RedirectURI.URL, Code: req.Code, CodeVerifier: req.CodeVerifier, }) diff --git a/internal/token/usecase.go b/internal/token/usecase.go index 92bddbf..818514f 100644 --- a/internal/token/usecase.go +++ b/internal/token/usecase.go @@ -2,6 +2,7 @@ package token import ( "context" + "net/url" "source.toby3d.me/toby3d/auth/internal/domain" ) @@ -9,7 +10,7 @@ import ( type ( ExchangeOptions struct { ClientID *domain.ClientID - RedirectURI *domain.URL + RedirectURI *url.URL Code string CodeVerifier string } diff --git a/internal/user/repository/http/http_user.go b/internal/user/repository/http/http_user.go index 0fac2d7..bd3252f 100644 --- a/internal/user/repository/http/http_user.go +++ b/internal/user/repository/http/http_user.go @@ -3,6 +3,7 @@ package http import ( "context" "fmt" + "net/url" http "github.com/valyala/fasthttp" @@ -117,7 +118,7 @@ func extractUser(dst *domain.User, src *http.Response) { } } -//nolint: cyclop +//nolint:cyclop func extractProfile(dst *domain.Profile, src *http.Response) { for _, name := range httputil.ExtractProperty(src, hCard, propertyName) { if n, ok := name.(string); ok { @@ -137,12 +138,12 @@ func extractProfile(dst *domain.Profile, src *http.Response) { } for _, rawURL := range httputil.ExtractProperty(src, hCard, propertyURL) { - url, ok := rawURL.(string) + rawURL, ok := rawURL.(string) if !ok { continue } - if u, err := domain.ParseURL(url); err == nil { + if u, err := url.Parse(rawURL); err == nil { dst.URL = append(dst.URL, u) } } @@ -153,7 +154,7 @@ func extractProfile(dst *domain.Profile, src *http.Response) { continue } - if p, err := domain.ParseURL(photo); err == nil { + if p, err := url.Parse(photo); err == nil { dst.Photo = append(dst.Photo, p) } } diff --git a/internal/user/repository/http/http_user_test.go b/internal/user/repository/http/http_user_test.go index 328e237..083ad30 100644 --- a/internal/user/repository/http/http_user_test.go +++ b/internal/user/repository/http/http_user_test.go @@ -84,7 +84,7 @@ func testHandler(tb testing.TB, user *domain.User) http.RequestHandler { testBody, user.Name[0], user.URL[0].String(), user.Photo[0].String(), user.Email[0], )) }) - router.GET(string(user.IndieAuthMetadata.Path()), func(ctx *http.RequestCtx) { + router.GET(user.IndieAuthMetadata.Path, func(ctx *http.RequestCtx) { ctx.SuccessString(common.MIMEApplicationJSONCharsetUTF8, `{ "issuer": "`+user.Me.String()+`", "authorization_endpoint": "`+user.AuthorizationEndpoint.String()+`",