🗃️ Added metadata repository implementations

This commit is contained in:
Maxim Lebedev 2022-02-16 20:30:36 +05:00
parent 2e791a74f6
commit a2420359fa
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
3 changed files with 143 additions and 0 deletions

View File

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

View File

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

View File

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