:replaced: Used url.URL instead domain.URL in domains
This commit is contained in:
parent
2af2a432b0
commit
0bb155ebbd
|
@ -206,7 +206,7 @@ func (h *RequestHandler) handleAuthorize(ctx *http.RequestCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !client.ValidateRedirectURI(req.RedirectURI) {
|
if !client.ValidateRedirectURI(req.RedirectURI.URL) {
|
||||||
ctx.SetStatusCode(http.StatusBadRequest)
|
ctx.SetStatusCode(http.StatusBadRequest)
|
||||||
web.WriteTemplate(ctx, &web.ErrorPage{
|
web.WriteTemplate(ctx, &web.ErrorPage{
|
||||||
BaseOf: baseOf,
|
BaseOf: baseOf,
|
||||||
|
@ -251,14 +251,10 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectURL := http.AcquireURI()
|
|
||||||
defer http.ReleaseURI(redirectURL)
|
|
||||||
req.RedirectURI.CopyTo(redirectURL)
|
|
||||||
|
|
||||||
if strings.EqualFold(req.Authorize, "deny") {
|
if strings.EqualFold(req.Authorize, "deny") {
|
||||||
domain.NewError(domain.ErrorCodeAccessDenied, "user deny authorization request", "", req.State).
|
domain.NewError(domain.ErrorCodeAccessDenied, "user deny authorization request", "", req.State).
|
||||||
SetReirectURI(redirectURL)
|
SetReirectURI(req.RedirectURI.URL)
|
||||||
ctx.Redirect(redirectURL.String(), http.StatusFound)
|
ctx.Redirect(req.RedirectURI.String(), http.StatusFound)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -266,7 +262,7 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) {
|
||||||
code, err := h.useCase.Generate(ctx, auth.GenerateOptions{
|
code, err := h.useCase.Generate(ctx, auth.GenerateOptions{
|
||||||
ClientID: req.ClientID,
|
ClientID: req.ClientID,
|
||||||
Me: req.Me,
|
Me: req.Me,
|
||||||
RedirectURI: req.RedirectURI,
|
RedirectURI: req.RedirectURI.URL,
|
||||||
CodeChallengeMethod: req.CodeChallengeMethod,
|
CodeChallengeMethod: req.CodeChallengeMethod,
|
||||||
Scope: req.Scope,
|
Scope: req.Scope,
|
||||||
CodeChallenge: req.CodeChallenge,
|
CodeChallenge: req.CodeChallenge,
|
||||||
|
@ -284,10 +280,10 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) {
|
||||||
"iss": h.config.Server.GetRootURL(),
|
"iss": h.config.Server.GetRootURL(),
|
||||||
"state": req.State,
|
"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) {
|
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{
|
me, profile, err := h.useCase.Exchange(ctx, auth.ExchangeOptions{
|
||||||
Code: req.Code,
|
Code: req.Code,
|
||||||
ClientID: req.ClientID,
|
ClientID: req.ClientID,
|
||||||
RedirectURI: req.RedirectURI,
|
RedirectURI: req.RedirectURI.URL,
|
||||||
CodeVerifier: req.CodeVerifier,
|
CodeVerifier: req.CodeVerifier,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -322,8 +318,8 @@ func (h *RequestHandler) handleExchange(ctx *http.RequestCtx) {
|
||||||
if profile != nil {
|
if profile != nil {
|
||||||
userInfo = &AuthProfileResponse{
|
userInfo = &AuthProfileResponse{
|
||||||
Email: profile.GetEmail(),
|
Email: profile.GetEmail(),
|
||||||
Photo: profile.GetPhoto(),
|
Photo: &domain.URL{URL: profile.GetPhoto()},
|
||||||
URL: profile.GetURL(),
|
URL: &domain.URL{URL: profile.GetURL()},
|
||||||
Name: profile.GetName(),
|
Name: profile.GetName(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
)
|
)
|
||||||
|
@ -10,7 +11,7 @@ type (
|
||||||
GenerateOptions struct {
|
GenerateOptions struct {
|
||||||
ClientID *domain.ClientID
|
ClientID *domain.ClientID
|
||||||
Me *domain.Me
|
Me *domain.Me
|
||||||
RedirectURI *domain.URL
|
RedirectURI *url.URL
|
||||||
CodeChallengeMethod domain.CodeChallengeMethod
|
CodeChallengeMethod domain.CodeChallengeMethod
|
||||||
Scope domain.Scopes
|
Scope domain.Scopes
|
||||||
CodeChallenge string
|
CodeChallenge string
|
||||||
|
@ -18,7 +19,7 @@ type (
|
||||||
|
|
||||||
ExchangeOptions struct {
|
ExchangeOptions struct {
|
||||||
ClientID *domain.ClientID
|
ClientID *domain.ClientID
|
||||||
RedirectURI *domain.URL
|
RedirectURI *url.URL
|
||||||
Code string
|
Code string
|
||||||
CodeVerifier string
|
CodeVerifier string
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
http "github.com/valyala/fasthttp"
|
http "github.com/valyala/fasthttp"
|
||||||
|
|
||||||
|
@ -65,9 +66,9 @@ func (repo *httpClientRepository) Get(ctx context.Context, cid *domain.ClientID)
|
||||||
|
|
||||||
client := &domain.Client{
|
client := &domain.Client{
|
||||||
ID: cid,
|
ID: cid,
|
||||||
RedirectURI: make([]*domain.URL, 0),
|
RedirectURI: make([]*url.URL, 0),
|
||||||
Logo: make([]*domain.URL, 0),
|
Logo: make([]*url.URL, 0),
|
||||||
URL: make([]*domain.URL, 0),
|
URL: make([]*url.URL, 0),
|
||||||
Name: make([]string, 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) {
|
for _, logo := range httputil.ExtractProperty(src, itemType, propertyLogo) {
|
||||||
var (
|
var (
|
||||||
uri *domain.URL
|
u *url.URL
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
switch l := logo.(type) {
|
switch l := logo.(type) {
|
||||||
case string:
|
case string:
|
||||||
uri, err = domain.ParseURL(l)
|
u, err = url.Parse(l)
|
||||||
case map[string]string:
|
case map[string]string:
|
||||||
if value, ok := l["value"]; ok {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.Logo = append(dst.Logo, uri)
|
dst.Logo = append(dst.Logo, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, property := range httputil.ExtractProperty(src, itemType, propertyURL) {
|
for _, property := range httputil.ExtractProperty(src, itemType, propertyURL) {
|
||||||
|
@ -119,7 +120,7 @@ func extract(dst *domain.Client, src *http.Response) {
|
||||||
continue
|
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)
|
dst.URL = append(dst.URL, u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +139,7 @@ func containsString(src []string, find string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsURL(src []*domain.URL, find *domain.URL) bool {
|
func containsURL(src []*url.URL, find *url.URL) bool {
|
||||||
for i := range src {
|
for i := range src {
|
||||||
if src[i].String() != find.String() {
|
if src[i].String() != find.String() {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Logo []*URL
|
Logo []*url.URL
|
||||||
URL []*URL
|
URL []*url.URL
|
||||||
Name []string
|
Name []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@ func (a App) GetName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetURL safe returns first URL, if any.
|
// GetURL safe returns first URL, if any.
|
||||||
func (a App) GetURL() *URL {
|
func (a App) GetURL() *url.URL {
|
||||||
if len(a.URL) == 0 {
|
if len(a.URL) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -25,7 +27,7 @@ func (a App) GetURL() *URL {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogo safe returns first logo, if any.
|
// GetLogo safe returns first logo, if any.
|
||||||
func (a App) GetLogo() *URL {
|
func (a App) GetLogo() *url.URL {
|
||||||
if len(a.Logo) == 0 {
|
if len(a.Logo) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -10,9 +10,9 @@ import (
|
||||||
// Client describes the client requesting data about the user.
|
// Client describes the client requesting data about the user.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ID *ClientID
|
ID *ClientID
|
||||||
Logo []*URL
|
Logo []*url.URL
|
||||||
RedirectURI []*URL
|
RedirectURI []*url.URL
|
||||||
URL []*URL
|
URL []*url.URL
|
||||||
Name []string
|
Name []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,9 +20,9 @@ type Client struct {
|
||||||
func NewClient(cid *ClientID) *Client {
|
func NewClient(cid *ClientID) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
ID: cid,
|
ID: cid,
|
||||||
Logo: make([]*URL, 0),
|
Logo: make([]*url.URL, 0),
|
||||||
RedirectURI: make([]*URL, 0),
|
RedirectURI: make([]*url.URL, 0),
|
||||||
URL: make([]*URL, 0),
|
URL: make([]*url.URL, 0),
|
||||||
Name: make([]string, 0),
|
Name: make([]string, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,20 +31,15 @@ func NewClient(cid *ClientID) *Client {
|
||||||
func TestClient(tb testing.TB) *Client {
|
func TestClient(tb testing.TB) *Client {
|
||||||
tb.Helper()
|
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{
|
return &Client{
|
||||||
ID: TestClientID(tb),
|
ID: TestClientID(tb),
|
||||||
Name: []string{"Example App"},
|
Name: []string{"Example App"},
|
||||||
URL: []*URL{TestURL(tb, "https://app.example.com/")},
|
URL: []*url.URL{{Scheme: "https", Host: "app.example.com", Path: "/"}},
|
||||||
Logo: []*URL{TestURL(tb, "https://app.example.com/logo.png")},
|
Logo: []*url.URL{{Scheme: "https", Host: "app.example.com", Path: "/logo.png"}},
|
||||||
RedirectURI: redirects,
|
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
|
// 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
|
// that the requested redirect_uri matches one of the redirect URLs published by
|
||||||
// the client, and SHOULD block the request from proceeding if not.
|
// 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 {
|
if redirectURI == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
rHost, rPort, err := net.SplitHostPort(string(redirectURI.Host()))
|
rHost, rPort, err := net.SplitHostPort(redirectURI.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rHost = string(redirectURI.Host())
|
rHost = redirectURI.Hostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
cHost, cPort, err := net.SplitHostPort(c.ID.clientID.Host)
|
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()
|
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(rHost, cHost) &&
|
||||||
strings.EqualFold(rPort, cPort) {
|
strings.EqualFold(rPort, cPort) {
|
||||||
return true
|
return true
|
||||||
|
@ -97,7 +92,7 @@ func (c Client) GetName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetURL safe returns first URL, if any.
|
// GetURL safe returns first URL, if any.
|
||||||
func (c Client) GetURL() *URL {
|
func (c Client) GetURL() *url.URL {
|
||||||
if len(c.URL) == 0 {
|
if len(c.URL) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -106,7 +101,7 @@ func (c Client) GetURL() *URL {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogo safe returns first logo, if any.
|
// GetLogo safe returns first logo, if any.
|
||||||
func (c Client) GetLogo() *URL {
|
func (c Client) GetLogo() *url.URL {
|
||||||
if len(c.Logo) == 0 {
|
if len(c.Logo) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package domain_test
|
package domain_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
@ -12,20 +12,17 @@ func TestClient_ValidateRedirectURI(t *testing.T) {
|
||||||
|
|
||||||
client := domain.TestClient(t)
|
client := domain.TestClient(t)
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for name, in := range map[string]*url.URL{
|
||||||
name string
|
"client_id prefix": client.ID.URL().JoinPath("/callback"),
|
||||||
in *domain.URL
|
"registered redirect_uri": client.RedirectURI[len(client.RedirectURI)-1],
|
||||||
}{
|
|
||||||
{name: "client_id prefix", in: domain.TestURL(t, fmt.Sprint(client.ID, "/callback"))},
|
|
||||||
{name: "registered redirect_uri", in: 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()
|
t.Parallel()
|
||||||
|
|
||||||
if result := client.ValidateRedirectURI(tc.in); !result {
|
if out := client.ValidateRedirectURI(in); !out {
|
||||||
t.Errorf("ValidateRedirectURI(%v) = %t, want %t", tc.in, result, true)
|
t.Errorf("ValidateRedirectURI(%v) = %t, want %t", in, out, true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
http "github.com/valyala/fasthttp"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/common"
|
"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,
|
// SetReirectURI sets fasthttp.QueryArgs with the request state, code,
|
||||||
// description and error URI in the provided fasthttp.URI.
|
// description and error URI in the provided fasthttp.URI.
|
||||||
func (e Error) SetReirectURI(uri *http.URI) {
|
func (e Error) SetReirectURI(u *url.URL) {
|
||||||
if uri == nil {
|
if u == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ func (e Error) SetReirectURI(uri *http.URI) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
uri.QueryArgs().Set(key, val)
|
u.Query().Set(key, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,21 +7,19 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
http "github.com/valyala/fasthttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Me is a URL user identifier.
|
// Me is a URL user identifier.
|
||||||
type Me struct {
|
type Me struct {
|
||||||
id *http.URI
|
id *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseMe parse string as me URL identifier.
|
// ParseMe parse string as me URL identifier.
|
||||||
//
|
//
|
||||||
//nolint:funlen,cyclop
|
//nolint:funlen,cyclop
|
||||||
func ParseMe(raw string) (*Me, error) {
|
func ParseMe(raw string) (*Me, error) {
|
||||||
id := http.AcquireURI()
|
id, err := url.Parse(raw)
|
||||||
if err := id.Parse(nil, []byte(raw)); err != nil {
|
if err != nil {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
err.Error(),
|
err.Error(),
|
||||||
|
@ -30,8 +28,7 @@ func ParseMe(raw string) (*Me, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := string(id.Scheme())
|
if id.Scheme != "http" && id.Scheme != "https" {
|
||||||
if scheme != "http" && scheme != "https" {
|
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile URL MUST have either an https or http scheme",
|
"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 id.Path == "" {
|
||||||
if path == "" || strings.Contains(path, "/.") || strings.Contains(path, "/..") {
|
id.Path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(id.Path, "/.") || strings.Contains(id.Path, "/..") {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile URL MUST contain a path component (/ is a valid path), MUST NOT contain single-dot "+
|
"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(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile URL MUST NOT contain a fragment component",
|
"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(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile URL MUST NOT contain a username or password component",
|
"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 id.Host == "" {
|
||||||
if domain == "" {
|
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile host name MUST be a domain name",
|
"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(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile MUST NOT contain a port",
|
"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(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile MUST NOT be ipv4 or ipv6 addresses",
|
"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
|
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.
|
// URL returns copy of parsed me in *url.URL representation.
|
||||||
func (m Me) URL() *url.URL {
|
func (m Me) URL() *url.URL {
|
||||||
if m.id == nil {
|
if m.id == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &url.URL{
|
out, _ := url.Parse(m.id.String())
|
||||||
ForceQuery: false,
|
|
||||||
Fragment: string(m.id.Hash()),
|
return out
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns string representation of me.
|
// String returns string representation of me.
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
// The server's issuer identifier. The issuer identifier is a URL that
|
// The server's issuer identifier. The issuer identifier is a URL that
|
||||||
|
@ -14,33 +17,33 @@ type Metadata struct {
|
||||||
Issuer *ClientID
|
Issuer *ClientID
|
||||||
|
|
||||||
// The Authorization Endpoint.
|
// The Authorization Endpoint.
|
||||||
AuthorizationEndpoint *URL
|
AuthorizationEndpoint *url.URL
|
||||||
|
|
||||||
// The Token Endpoint.
|
// The Token Endpoint.
|
||||||
TokenEndpoint *URL
|
TokenEndpoint *url.URL
|
||||||
|
|
||||||
// The Ticket Endpoint.
|
// The Ticket Endpoint.
|
||||||
TicketEndpoint *URL
|
TicketEndpoint *url.URL
|
||||||
|
|
||||||
// The Micropub Endpoint.
|
// The Micropub Endpoint.
|
||||||
MicropubEndpoint *URL
|
MicropubEndpoint *url.URL
|
||||||
|
|
||||||
// The Microsub Endpoint.
|
// The Microsub Endpoint.
|
||||||
MicrosubEndpoint *URL
|
MicrosubEndpoint *url.URL
|
||||||
|
|
||||||
// The Introspection Endpoint.
|
// The Introspection Endpoint.
|
||||||
IntrospectionEndpoint *URL
|
IntrospectionEndpoint *url.URL
|
||||||
|
|
||||||
// The Revocation Endpoint.
|
// The Revocation Endpoint.
|
||||||
RevocationEndpoint *URL
|
RevocationEndpoint *url.URL
|
||||||
|
|
||||||
// The User Info Endpoint.
|
// The User Info Endpoint.
|
||||||
UserinfoEndpoint *URL
|
UserinfoEndpoint *url.URL
|
||||||
|
|
||||||
// URL of a page containing human-readable information that developers
|
// 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
|
// might need to know when using the server. This might be a link to the
|
||||||
// IndieAuth spec or something more personal to your implementation.
|
// IndieAuth spec or something more personal to your implementation.
|
||||||
ServiceDocumentation *URL
|
ServiceDocumentation *url.URL
|
||||||
|
|
||||||
// JSON array containing scope values supported by the IndieAuth server.
|
// JSON array containing scope values supported by the IndieAuth server.
|
||||||
// Servers MAY choose not to advertise some supported scope values even
|
// Servers MAY choose not to advertise some supported scope values even
|
||||||
|
@ -79,15 +82,15 @@ func TestMetadata(tb testing.TB) *Metadata {
|
||||||
|
|
||||||
return &Metadata{
|
return &Metadata{
|
||||||
Issuer: TestClientID(tb),
|
Issuer: TestClientID(tb),
|
||||||
AuthorizationEndpoint: TestURL(tb, "https://indieauth.example.com/auth"),
|
AuthorizationEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/auth"},
|
||||||
TokenEndpoint: TestURL(tb, "https://indieauth.example.com/token"),
|
TokenEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/token"},
|
||||||
TicketEndpoint: TestURL(tb, "https://auth.example.org/ticket"),
|
TicketEndpoint: &url.URL{Scheme: "https", Host: "auth.example.org", Path: "/ticket"},
|
||||||
MicropubEndpoint: TestURL(tb, "https://micropub.example.com/"),
|
MicropubEndpoint: &url.URL{Scheme: "https", Host: "micropub.example.com", Path: "/"},
|
||||||
MicrosubEndpoint: TestURL(tb, "https://microsub.example.com/"),
|
MicrosubEndpoint: &url.URL{Scheme: "https", Host: "microsub.example.com", Path: "/"},
|
||||||
IntrospectionEndpoint: TestURL(tb, "https://indieauth.example.com/introspect"),
|
IntrospectionEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/introspect"},
|
||||||
RevocationEndpoint: TestURL(tb, "https://indieauth.example.com/revocation"),
|
RevocationEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/revocation"},
|
||||||
UserinfoEndpoint: TestURL(tb, "https://indieauth.example.com/userinfo"),
|
UserinfoEndpoint: &url.URL{Scheme: "https", Host: "indieauth.example.com", Path: "/userinfo"},
|
||||||
ServiceDocumentation: TestURL(tb, "https://indieauth.net/draft/"),
|
ServiceDocumentation: &url.URL{Scheme: "https", Host: "indieauth.net", Path: "/draft/"},
|
||||||
ScopesSupported: Scopes{
|
ScopesSupported: Scopes{
|
||||||
ScopeBlock,
|
ScopeBlock,
|
||||||
ScopeChannels,
|
ScopeChannels,
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Profile describes the data about the user.
|
// Profile describes the data about the user.
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
Photo []*URL `json:"photo,omitempty"`
|
Photo []*url.URL `json:"photo,omitempty"`
|
||||||
URL []*URL `json:"url,omitempty"`
|
URL []*url.URL `json:"url,omitempty"`
|
||||||
Email []*Email `json:"email,omitempty"`
|
Email []*Email `json:"email,omitempty"`
|
||||||
Name []string `json:"name,omitempty"`
|
Name []string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProfile() *Profile {
|
func NewProfile() *Profile {
|
||||||
return &Profile{
|
return &Profile{
|
||||||
Photo: make([]*URL, 0),
|
Photo: make([]*url.URL, 0),
|
||||||
URL: make([]*URL, 0),
|
URL: make([]*url.URL, 0),
|
||||||
Email: make([]*Email, 0),
|
Email: make([]*Email, 0),
|
||||||
Name: make([]string, 0),
|
Name: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
@ -28,8 +29,8 @@ func TestProfile(tb testing.TB) *Profile {
|
||||||
return &Profile{
|
return &Profile{
|
||||||
Email: []*Email{TestEmail(tb)},
|
Email: []*Email{TestEmail(tb)},
|
||||||
Name: []string{"Example User"},
|
Name: []string{"Example User"},
|
||||||
Photo: []*URL{TestURL(tb, "https://user.example.net/photo.jpg")},
|
Photo: []*url.URL{{Scheme: "https", Host: "user.example.net", Path: "/photo.jpg"}},
|
||||||
URL: []*URL{TestURL(tb, "https://user.example.net/")},
|
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.
|
// GetURL safe returns first URL, if any.
|
||||||
func (p Profile) GetURL() *URL {
|
func (p Profile) GetURL() *url.URL {
|
||||||
if len(p.URL) == 0 {
|
if len(p.URL) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -64,7 +65,7 @@ func (p Profile) HasPhoto() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPhoto safe returns first photo, if any.
|
// GetPhoto safe returns first photo, if any.
|
||||||
func (p Profile) GetPhoto() *URL {
|
func (p Profile) GetPhoto() *url.URL {
|
||||||
if len(p.Photo) == 0 {
|
if len(p.Photo) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/random"
|
"source.toby3d.me/toby3d/auth/internal/random"
|
||||||
|
@ -9,7 +10,7 @@ import (
|
||||||
//nolint:tagliatelle
|
//nolint:tagliatelle
|
||||||
type Session struct {
|
type Session struct {
|
||||||
ClientID *ClientID `json:"client_id"`
|
ClientID *ClientID `json:"client_id"`
|
||||||
RedirectURI *URL `json:"redirect_uri"`
|
RedirectURI *url.URL `json:"redirect_uri"`
|
||||||
Me *Me `json:"me"`
|
Me *Me `json:"me"`
|
||||||
Profile *Profile `json:"profile,omitempty"`
|
Profile *Profile `json:"profile,omitempty"`
|
||||||
Scope Scopes `json:"scope"`
|
Scope Scopes `json:"scope"`
|
||||||
|
@ -36,7 +37,7 @@ func TestSession(tb testing.TB) *Session {
|
||||||
CodeChallengeMethod: CodeChallengeMethodPLAIN,
|
CodeChallengeMethod: CodeChallengeMethodPLAIN,
|
||||||
Profile: TestProfile(tb),
|
Profile: TestProfile(tb),
|
||||||
Me: TestMe(tb, "https://user.example.net/"),
|
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{
|
Scope: Scopes{
|
||||||
ScopeEmail,
|
ScopeEmail,
|
||||||
ScopeProfile,
|
ScopeProfile,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ticket struct {
|
type Ticket struct {
|
||||||
// The access token will work at this URL.
|
// 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.
|
// The access token should be used when acting on behalf of this URL.
|
||||||
Subject *Me
|
Subject *Me
|
||||||
|
@ -20,7 +21,7 @@ func TestTicket(tb testing.TB) *Ticket {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
return &Ticket{
|
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/"),
|
Subject: TestMe(tb, "https://bob.example.com/"),
|
||||||
Ticket: "32985723984723985792834",
|
Ticket: "32985723984723985792834",
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,23 +5,21 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
http "github.com/valyala/fasthttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// URL describe any valid HTTP URL.
|
// URL describe any valid HTTP URL.
|
||||||
type URL struct {
|
type URL struct {
|
||||||
*http.URI
|
*url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseURL parse string as URL.
|
// ParseURL parse string as URL.
|
||||||
func ParseURL(src string) (*URL, error) {
|
func ParseURL(src string) (*URL, error) {
|
||||||
u := http.AcquireURI()
|
u, err := url.Parse(src)
|
||||||
if err := u.Parse(nil, []byte(src)); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse URL: %w", err)
|
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.
|
// MustParseURL parse string as URL or panic.
|
||||||
|
@ -38,11 +36,10 @@ func MustParseURL(src string) *URL {
|
||||||
func TestURL(tb testing.TB, src string) *URL {
|
func TestURL(tb testing.TB, src string) *URL {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
u := http.AcquireURI()
|
u, _ := url.Parse(src)
|
||||||
u.Update(src)
|
|
||||||
|
|
||||||
return &URL{
|
return &URL{
|
||||||
URI: u,
|
URL: u,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,17 +75,3 @@ func (u *URL) UnmarshalJSON(v []byte) error {
|
||||||
func (u URL) MarshalJSON() ([]byte, error) {
|
func (u URL) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(strconv.Quote(u.String())), nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Me *Me
|
Me *Me
|
||||||
AuthorizationEndpoint *URL
|
AuthorizationEndpoint *url.URL
|
||||||
IndieAuthMetadata *URL
|
IndieAuthMetadata *url.URL
|
||||||
Micropub *URL
|
Micropub *url.URL
|
||||||
Microsub *URL
|
Microsub *url.URL
|
||||||
TicketEndpoint *URL
|
TicketEndpoint *url.URL
|
||||||
TokenEndpoint *URL
|
TokenEndpoint *url.URL
|
||||||
*Profile
|
*Profile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +23,14 @@ func TestUser(tb testing.TB) *User {
|
||||||
return &User{
|
return &User{
|
||||||
Profile: TestProfile(tb),
|
Profile: TestProfile(tb),
|
||||||
Me: TestMe(tb, "https://user.example.net/"),
|
Me: TestMe(tb, "https://user.example.net/"),
|
||||||
AuthorizationEndpoint: TestURL(tb, "https://example.org/auth"),
|
AuthorizationEndpoint: &url.URL{Scheme: "https", Host: "example.org", Path: "/auth"},
|
||||||
IndieAuthMetadata: TestURL(tb, "https://example.org/.well-known/oauth-authorization-server"),
|
IndieAuthMetadata: &url.URL{
|
||||||
Micropub: TestURL(tb, "https://microsub.example.org/"),
|
Scheme: "https", Host: "example.org",
|
||||||
Microsub: TestURL(tb, "https://micropub.example.org/"),
|
Path: "/.well-known/oauth-authorization-server",
|
||||||
TicketEndpoint: TestURL(tb, "https://example.org/ticket"),
|
},
|
||||||
TokenEndpoint: TestURL(tb, "https://example.org/token"),
|
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"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ var ErrEndpointNotExist = domain.NewError(
|
||||||
"https://indieauth.net/source/#discovery-0",
|
"https://indieauth.net/source/#discovery-0",
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExtractEndpoints(resp *http.Response, rel string) []*domain.URL {
|
func ExtractEndpoints(resp *http.Response, rel string) []*url.URL {
|
||||||
results := make([]*domain.URL, 0)
|
results := make([]*url.URL, 0)
|
||||||
|
|
||||||
urls, err := ExtractEndpointsFromHeader(resp, rel)
|
urls, err := ExtractEndpointsFromHeader(resp, rel)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -36,40 +36,40 @@ func ExtractEndpoints(resp *http.Response, rel string) []*domain.URL {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractEndpointsFromHeader(resp *http.Response, rel string) ([]*domain.URL, error) {
|
func ExtractEndpointsFromHeader(resp *http.Response, rel string) ([]*url.URL, error) {
|
||||||
results := make([]*domain.URL, 0)
|
results := make([]*url.URL, 0)
|
||||||
|
|
||||||
for _, link := range linkheader.Parse(string(resp.Header.Peek(http.HeaderLink))) {
|
for _, link := range linkheader.Parse(string(resp.Header.Peek(http.HeaderLink))) {
|
||||||
if !strings.EqualFold(link.Rel, rel) {
|
if !strings.EqualFold(link.Rel, rel) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
u := http.AcquireURI()
|
u, err := url.ParseRequestURI(link.URL)
|
||||||
if err := u.Parse(resp.Header.Peek(http.HeaderHost), []byte(link.URL)); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse header endpoint: %w", err)
|
return nil, fmt.Errorf("cannot parse header endpoint: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, &domain.URL{URI: u})
|
results = append(results, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
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]
|
endpoints, ok := microformats.Parse(bytes.NewReader(resp.Body()), nil).Rels[rel]
|
||||||
if !ok || len(endpoints) == 0 {
|
if !ok || len(endpoints) == 0 {
|
||||||
return nil, ErrEndpointNotExist
|
return nil, ErrEndpointNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]*domain.URL, 0)
|
results := make([]*url.URL, 0)
|
||||||
|
|
||||||
for i := range endpoints {
|
for i := range endpoints {
|
||||||
u := http.AcquireURI()
|
u, err := url.Parse(endpoints[i])
|
||||||
if err := u.Parse(resp.Header.Peek(http.HeaderHost), []byte(endpoints[i])); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse body endpoint: %w", err)
|
return nil, fmt.Errorf("cannot parse body endpoint: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, &domain.URL{URI: u})
|
results = append(results, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
|
|
@ -74,15 +74,15 @@ func (repo *httpMetadataRepository) Get(ctx context.Context, me *domain.Me) (*do
|
||||||
|
|
||||||
//nolint:exhaustivestruct // TODO(toby3d)
|
//nolint:exhaustivestruct // TODO(toby3d)
|
||||||
return &domain.Metadata{
|
return &domain.Metadata{
|
||||||
AuthorizationEndpoint: data.AuthorizationEndpoint,
|
AuthorizationEndpoint: data.AuthorizationEndpoint.URL,
|
||||||
AuthorizationResponseIssParameterSupported: data.AuthorizationResponseIssParameterSupported,
|
AuthorizationResponseIssParameterSupported: data.AuthorizationResponseIssParameterSupported,
|
||||||
CodeChallengeMethodsSupported: data.CodeChallengeMethodsSupported,
|
CodeChallengeMethodsSupported: data.CodeChallengeMethodsSupported,
|
||||||
GrantTypesSupported: data.GrantTypesSupported,
|
GrantTypesSupported: data.GrantTypesSupported,
|
||||||
Issuer: data.Issuer,
|
Issuer: data.Issuer,
|
||||||
ResponseTypesSupported: data.ResponseTypesSupported,
|
ResponseTypesSupported: data.ResponseTypesSupported,
|
||||||
ScopesSupported: data.ScopesSupported,
|
ScopesSupported: data.ScopesSupported,
|
||||||
ServiceDocumentation: data.ServiceDocumentation,
|
ServiceDocumentation: data.ServiceDocumentation.URL,
|
||||||
TokenEndpoint: data.TokenEndpoint,
|
TokenEndpoint: data.TokenEndpoint.URL,
|
||||||
// TODO(toby3d): support extensions?
|
// TODO(toby3d): support extensions?
|
||||||
// Micropub: data.Micropub,
|
// Micropub: data.Micropub,
|
||||||
// Microsub: data.Microsub,
|
// Microsub: data.Microsub,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package http
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
http "github.com/valyala/fasthttp"
|
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) {
|
for _, rawURL := range httputil.ExtractProperty(resp, hCard, propertyURL) {
|
||||||
url, ok := rawURL.(string)
|
rawURL, ok := rawURL.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := domain.ParseURL(url); err == nil {
|
if u, err := url.Parse(rawURL); err == nil {
|
||||||
result.URL = append(result.URL, u)
|
result.URL = append(result.URL, u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +83,7 @@ func (repo *httpProfileRepository) Get(ctx context.Context, me *domain.Me) (*dom
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if p, err := domain.ParseURL(photo); err == nil {
|
if p, err := url.Parse(photo); err == nil {
|
||||||
result.Photo = append(result.Photo, p)
|
result.Photo = append(result.Photo, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ func (h *RequestHandler) handleSend(ctx *http.RequestCtx) {
|
||||||
|
|
||||||
ticket := &domain.Ticket{
|
ticket := &domain.Ticket{
|
||||||
Ticket: "",
|
Ticket: "",
|
||||||
Resource: req.Resource,
|
Resource: req.Resource.URL,
|
||||||
Subject: req.Subject,
|
Subject: req.Subject,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ func (h *RequestHandler) handleRedeem(ctx *http.RequestCtx) {
|
||||||
|
|
||||||
token, err := h.tickets.Redeem(ctx, &domain.Ticket{
|
token, err := h.tickets.Redeem(ctx, &domain.Ticket{
|
||||||
Ticket: req.Ticket,
|
Ticket: req.Ticket,
|
||||||
Resource: req.Resource,
|
Resource: req.Resource.URL,
|
||||||
Subject: req.Subject,
|
Subject: req.Subject,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -81,7 +81,7 @@ func NewDependencies(tb testing.TB) Dependencies {
|
||||||
|
|
||||||
r := router.New()
|
r := router.New()
|
||||||
// NOTE(toby3d): private resource
|
// 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,
|
ctx.SuccessString(common.MIMETextHTMLCharsetUTF8,
|
||||||
`<link rel="token_endpoint" href="https://auth.example.org/token">`)
|
`<link rel="token_endpoint" href="https://auth.example.org/token">`)
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
@ -117,5 +118,5 @@ func NewTicket(src *domain.Ticket) *Ticket {
|
||||||
func (t *Ticket) Populate(dst *domain.Ticket) {
|
func (t *Ticket) Populate(dst *domain.Ticket) {
|
||||||
dst.Ticket = t.Ticket
|
dst.Ticket = t.Ticket
|
||||||
dst.Subject, _ = domain.ParseMe(t.Subject)
|
dst.Subject, _ = domain.ParseMe(t.Subject)
|
||||||
dst.Resource, _ = domain.ParseURL(t.Resource)
|
dst.Resource, _ = url.Parse(t.Resource)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
repository "source.toby3d.me/toby3d/auth/internal/ticket/repository/sqlite3"
|
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"}
|
var tableColumns = []string{"created_at", "resource", "subject", "ticket"}
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package usecase
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
json "github.com/goccy/go-json"
|
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)
|
return fmt.Errorf("cannot discovery ticket subject: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ticketEndpoint *domain.URL
|
var ticketEndpoint *url.URL
|
||||||
|
|
||||||
// NOTE(toby3d): find metadata first
|
// NOTE(toby3d): find metadata first
|
||||||
if metadata, err := httputil.ExtractMetadata(resp, useCase.client); err == nil && metadata != nil {
|
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.Reset()
|
||||||
req.Header.SetMethod(http.MethodPost)
|
req.Header.SetMethod(http.MethodPost)
|
||||||
req.SetRequestURIBytes(ticketEndpoint.RequestURI())
|
req.SetRequestURI(ticketEndpoint.String())
|
||||||
req.Header.SetContentType(common.MIMEApplicationForm)
|
req.Header.SetContentType(common.MIMEApplicationForm)
|
||||||
req.PostArgs().Set("ticket", tkt.Ticket)
|
req.PostArgs().Set("ticket", tkt.Ticket)
|
||||||
req.PostArgs().Set("subject", tkt.Subject.String())
|
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)
|
return nil, fmt.Errorf("cannot discovery ticket resource: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenEndpoint *domain.URL
|
var tokenEndpoint *url.URL
|
||||||
|
|
||||||
// NOTE(toby3d): find metadata first
|
// NOTE(toby3d): find metadata first
|
||||||
if metadata, err := httputil.ExtractMetadata(resp, useCase.client); err == nil && metadata != nil {
|
if metadata, err := httputil.ExtractMetadata(resp, useCase.client); err == nil && metadata != nil {
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestRedeem(t *testing.T) {
|
||||||
ticket := domain.TestTicket(t)
|
ticket := domain.TestTicket(t)
|
||||||
|
|
||||||
router := router.New()
|
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="`+
|
ctx.SuccessString(common.MIMETextHTMLCharsetUTF8, `<link rel="token_endpoint" href="`+
|
||||||
ticket.Subject.String()+`token">`)
|
ticket.Subject.String()+`token">`)
|
||||||
})
|
})
|
||||||
|
|
|
@ -244,7 +244,7 @@ func (h *RequestHandler) handleExchange(ctx *http.RequestCtx) {
|
||||||
|
|
||||||
token, profile, err := h.tokens.Exchange(ctx, token.ExchangeOptions{
|
token, profile, err := h.tokens.Exchange(ctx, token.ExchangeOptions{
|
||||||
ClientID: req.ClientID,
|
ClientID: req.ClientID,
|
||||||
RedirectURI: req.RedirectURI,
|
RedirectURI: req.RedirectURI.URL,
|
||||||
Code: req.Code,
|
Code: req.Code,
|
||||||
CodeVerifier: req.CodeVerifier,
|
CodeVerifier: req.CodeVerifier,
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@ package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
)
|
)
|
||||||
|
@ -9,7 +10,7 @@ import (
|
||||||
type (
|
type (
|
||||||
ExchangeOptions struct {
|
ExchangeOptions struct {
|
||||||
ClientID *domain.ClientID
|
ClientID *domain.ClientID
|
||||||
RedirectURI *domain.URL
|
RedirectURI *url.URL
|
||||||
Code string
|
Code string
|
||||||
CodeVerifier string
|
CodeVerifier string
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package http
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
http "github.com/valyala/fasthttp"
|
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) {
|
func extractProfile(dst *domain.Profile, src *http.Response) {
|
||||||
for _, name := range httputil.ExtractProperty(src, hCard, propertyName) {
|
for _, name := range httputil.ExtractProperty(src, hCard, propertyName) {
|
||||||
if n, ok := name.(string); ok {
|
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) {
|
for _, rawURL := range httputil.ExtractProperty(src, hCard, propertyURL) {
|
||||||
url, ok := rawURL.(string)
|
rawURL, ok := rawURL.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := domain.ParseURL(url); err == nil {
|
if u, err := url.Parse(rawURL); err == nil {
|
||||||
dst.URL = append(dst.URL, u)
|
dst.URL = append(dst.URL, u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +154,7 @@ func extractProfile(dst *domain.Profile, src *http.Response) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if p, err := domain.ParseURL(photo); err == nil {
|
if p, err := url.Parse(photo); err == nil {
|
||||||
dst.Photo = append(dst.Photo, p)
|
dst.Photo = append(dst.Photo, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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],
|
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, `{
|
ctx.SuccessString(common.MIMEApplicationJSONCharsetUTF8, `{
|
||||||
"issuer": "`+user.Me.String()+`",
|
"issuer": "`+user.Me.String()+`",
|
||||||
"authorization_endpoint": "`+user.AuthorizationEndpoint.String()+`",
|
"authorization_endpoint": "`+user.AuthorizationEndpoint.String()+`",
|
||||||
|
|
Loading…
Reference in New Issue