: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
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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">`)
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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">`)
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()+`",
|
||||
|
|
Loading…
Reference in New Issue