🗃️ Added metadata repository implementations
This commit is contained in:
parent
2e791a74f6
commit
a2420359fa
|
@ -0,0 +1,17 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"source.toby3d.me/website/indieauth/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
Get(ctx context.Context, me *domain.Me) (*domain.Metadata, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNotExist error = domain.NewError(
|
||||||
|
domain.ErrorCodeInvalidClient,
|
||||||
|
"not found 'indieauth-metadata' endpoint on provided me URL",
|
||||||
|
"https://indieauth.net/source/#discovery-0",
|
||||||
|
)
|
|
@ -0,0 +1,90 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
http "github.com/valyala/fasthttp"
|
||||||
|
|
||||||
|
"source.toby3d.me/website/indieauth/internal/domain"
|
||||||
|
"source.toby3d.me/website/indieauth/internal/metadata"
|
||||||
|
"source.toby3d.me/website/indieauth/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
//nolint: tagliatelle,lll
|
||||||
|
Metadata struct {
|
||||||
|
Issuer *domain.ClientID `json:"issuer"`
|
||||||
|
AuthorizationEndpoint *domain.URL `json:"authorization_endpoint"`
|
||||||
|
IntrospectionEndpoint *domain.URL `json:"introspection_endpoint"`
|
||||||
|
RevocationEndpoint *domain.URL `json:"revocation_endpoint,omitempty"`
|
||||||
|
ServiceDocumentation *domain.URL `json:"service_documentation,omitempty"`
|
||||||
|
TokenEndpoint *domain.URL `json:"token_endpoint"`
|
||||||
|
UserinfoEndpoint *domain.URL `json:"userinfo_endpoint,omitempty"`
|
||||||
|
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
||||||
|
GrantTypesSupported []domain.GrantType `json:"grant_types_supported,omitempty"`
|
||||||
|
ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"`
|
||||||
|
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
||||||
|
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
|
||||||
|
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
||||||
|
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
httpMetadataRepository struct {
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultMaxRedirectsCount int = 10
|
||||||
|
|
||||||
|
func NewHTTPMetadataRepository(client *http.Client) metadata.Repository {
|
||||||
|
return &httpMetadataRepository{
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *httpMetadataRepository) Get(ctx context.Context, me *domain.Me) (*domain.Metadata, error) {
|
||||||
|
req := http.AcquireRequest()
|
||||||
|
defer http.ReleaseRequest(req)
|
||||||
|
req.SetRequestURI(me.String())
|
||||||
|
req.Header.SetMethod(http.MethodGet)
|
||||||
|
|
||||||
|
resp := http.AcquireResponse()
|
||||||
|
defer http.ReleaseResponse(resp)
|
||||||
|
|
||||||
|
if err := repo.client.DoRedirects(req, resp, DefaultMaxRedirectsCount); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to make a request to the client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints := util.ExtractEndpoints(resp, "indieauth-metadata")
|
||||||
|
if len(endpoints) == 0 {
|
||||||
|
return nil, metadata.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
_, body, err := repo.client.Get(nil, endpoints[len(endpoints)-1].String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch metadata endpoint configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := new(Metadata)
|
||||||
|
if err = json.Unmarshal(body, data); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot unmarshal metadata configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.Metadata{
|
||||||
|
AuthorizationEndpoint: data.AuthorizationEndpoint,
|
||||||
|
AuthorizationResponseIssParameterSupported: data.AuthorizationResponseIssParameterSupported,
|
||||||
|
CodeChallengeMethodsSupported: data.CodeChallengeMethodsSupported,
|
||||||
|
GrantTypesSupported: data.GrantTypesSupported,
|
||||||
|
Issuer: data.Issuer,
|
||||||
|
ResponseTypesSupported: data.ResponseTypesSupported,
|
||||||
|
ScopesSupported: data.ScopesSupported,
|
||||||
|
ServiceDocumentation: data.ServiceDocumentation,
|
||||||
|
TokenEndpoint: data.TokenEndpoint,
|
||||||
|
// TODO(toby3d): support extensions?
|
||||||
|
// Micropub: data.Micropub,
|
||||||
|
// Microsub: data.Microsub,
|
||||||
|
// TicketEndpoint: data.TicketEndpoint,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"source.toby3d.me/website/indieauth/internal/domain"
|
||||||
|
"source.toby3d.me/website/indieauth/internal/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type memoryMetadataRepository struct {
|
||||||
|
store *sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultPathPrefix = "metadata"
|
||||||
|
|
||||||
|
func NewMemoryMetadataRepository(store *sync.Map) metadata.Repository {
|
||||||
|
return &memoryMetadataRepository{
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *memoryMetadataRepository) Get(ctx context.Context, me *domain.Me) (*domain.Metadata, error) {
|
||||||
|
src, ok := repo.store.Load(path.Join(DefaultPathPrefix, me.String()))
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := src.(*domain.Metadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
Loading…
Reference in New Issue