:replaced: Used url.URL instead domain.URL in domains

This commit is contained in:
Maxim Lebedev 2023-01-02 07:15:11 +06:00
parent 2af2a432b0
commit 0bb155ebbd
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
27 changed files with 179 additions and 212 deletions

View File

@ -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(),
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
})
}

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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,

View File

@ -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
}

View File

@ -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,

View File

@ -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",
}

View File

@ -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
}

View File

@ -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"},
}
}

View File

@ -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

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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,
`<link rel="token_endpoint" href="https://auth.example.org/token">`)
})

View File

@ -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)
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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, `<link rel="token_endpoint" href="`+
ticket.Subject.String()+`token">`)
})

View File

@ -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,
})

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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()+`",