feat: add end 2 end tests
This commit is contained in:
parent
f9a088269c
commit
0f504a98e9
46
app/webfinger/client.go
Normal file
46
app/webfinger/client.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package webfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/base64"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
"github.com/oklog/ulid/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultExpire = 30 * time.Minute
|
||||||
|
defaultIssuer = "sour.is/webfinger"
|
||||||
|
defaultAudience = "sour.is/webfinger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSignedRequest(jrd *JRD, key ed25519.PrivateKey) (string, error) {
|
||||||
|
type claims struct {
|
||||||
|
PubKey string `json:"pub"`
|
||||||
|
*JRD
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
pub := []byte(key.Public().(ed25519.PublicKey))
|
||||||
|
|
||||||
|
j := claims{
|
||||||
|
PubKey: enc(pub),
|
||||||
|
JRD: jrd.CloneValues(),
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ID: ulid.Make().String(),
|
||||||
|
Subject: jrd.Subject,
|
||||||
|
Audience: jwt.ClaimStrings{defaultAudience},
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(defaultExpire)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: defaultIssuer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
j.JRD.Subject = "" // move subject into registered claims.
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, &j)
|
||||||
|
return token.SignedString(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enc(b []byte) string {
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
}
|
|
@ -32,6 +32,19 @@ type JRD struct {
|
||||||
event.AggregateRoot `yaml:"-"`
|
event.AggregateRoot `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *JRD) CloneValues() *JRD {
|
||||||
|
m := make(map[string]*string, len(a.Properties))
|
||||||
|
for k,v := range a.Properties {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
return &JRD{
|
||||||
|
Subject: a.Subject,
|
||||||
|
Aliases: append([]string{}, a.Aliases...),
|
||||||
|
Properties: m,
|
||||||
|
Links: append([]*Link{}, a.Links...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var _ event.Aggregate = (*JRD)(nil)
|
var _ event.Aggregate = (*JRD)(nil)
|
||||||
|
|
||||||
// Link is a link to a related resource.
|
// Link is a link to a related resource.
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
type service struct {
|
type service struct {
|
||||||
es *ev.EventStore
|
es *ev.EventStore
|
||||||
self set.Set[string]
|
self set.Set[string]
|
||||||
|
cache func(string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option interface {
|
type Option interface {
|
||||||
|
@ -34,6 +35,12 @@ func (o WithHostnames) ApplyWebfinger(s *service) {
|
||||||
s.self = set.New(o...)
|
s.self = set.New(o...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WithCache func(string) bool
|
||||||
|
|
||||||
|
func (o WithCache) ApplyWebfinger(s *service) {
|
||||||
|
s.cache = o
|
||||||
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, es *ev.EventStore, opts ...Option) (*service, error) {
|
func New(ctx context.Context, es *ev.EventStore, opts ...Option) (*service, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
@ -91,10 +98,9 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
r.Body.Close()
|
r.Body.Close()
|
||||||
|
|
||||||
type claims struct {
|
type claims struct {
|
||||||
Subject string `json:"sub"`
|
|
||||||
PubKey string `json:"pub"`
|
PubKey string `json:"pub"`
|
||||||
*JRD
|
*JRD
|
||||||
jwt.StandardClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := jwt.ParseWithClaims(
|
token, err := jwt.ParseWithClaims(
|
||||||
|
@ -106,8 +112,7 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return nil, fmt.Errorf("wrong type of claim")
|
return nil, fmt.Errorf("wrong type of claim")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JRD.Subject = c.Subject
|
c.JRD.Subject = c.RegisteredClaims.Subject
|
||||||
c.StandardClaims.Subject = c.Subject
|
|
||||||
|
|
||||||
c.SetProperty(NSpubkey, &c.PubKey)
|
c.SetProperty(NSpubkey, &c.PubKey)
|
||||||
|
|
||||||
|
@ -134,7 +139,17 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
a, err := ev.Upsert(ctx, s.es, StreamID(c.Subject), func(ctx context.Context, a *JRD) error {
|
if c.ID != "" && s.cache != nil {
|
||||||
|
if ok := s.cache(c.ID); ok {
|
||||||
|
w.WriteHeader(http.StatusAlreadyReported)
|
||||||
|
fmt.Fprint(w, http.StatusText(http.StatusAlreadyReported))
|
||||||
|
span.AddEvent("already seen ID")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := ev.Upsert(ctx, s.es, StreamID(c.JRD.Subject), func(ctx context.Context, a *JRD) error {
|
||||||
var auth *JRD
|
var auth *JRD
|
||||||
|
|
||||||
// does the target have a pubkey for self auth?
|
// does the target have a pubkey for self auth?
|
||||||
|
@ -211,9 +226,16 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
resource := r.URL.Query().Get("resource")
|
resource := r.URL.Query().Get("resource")
|
||||||
rels := r.URL.Query()["rel"]
|
rels := r.URL.Query()["rel"]
|
||||||
|
|
||||||
if u := Parse(resource); u != nil && !s.self.Has(u.URL.Hostname()) {
|
if resource == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _ := splitHostPort(r.Host)
|
||||||
|
|
||||||
|
if u := Parse(resource); u != nil && !s.self.Has(host) {
|
||||||
redirect := &url.URL{}
|
redirect := &url.URL{}
|
||||||
redirect.Scheme = "https"
|
redirect.Scheme = u.URL.Scheme
|
||||||
redirect.Host = u.URL.Host
|
redirect.Host = u.URL.Host
|
||||||
redirect.RawQuery = r.URL.RawQuery
|
redirect.RawQuery = r.URL.RawQuery
|
||||||
redirect.Path = "/.well-known/webfinger"
|
redirect.Path = "/.well-known/webfinger"
|
||||||
|
@ -279,3 +301,31 @@ func dec(s string) ([]byte, error) {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
return base64.RawURLEncoding.DecodeString(s)
|
return base64.RawURLEncoding.DecodeString(s)
|
||||||
}
|
}
|
||||||
|
func splitHostPort(hostPort string) (host, port string) {
|
||||||
|
host = hostPort
|
||||||
|
|
||||||
|
colon := strings.LastIndexByte(host, ':')
|
||||||
|
if colon != -1 && validOptionalPort(host[colon:]) {
|
||||||
|
host, port = host[:colon], host[colon+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||||
|
host = host[1 : len(host)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func validOptionalPort(port string) bool {
|
||||||
|
if port == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if port[0] != ':' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, b := range port[1:] {
|
||||||
|
if b < '0' || b > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -4,7 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
"github.com/sour-is/ev"
|
"github.com/sour-is/ev"
|
||||||
"github.com/sour-is/ev/app/webfinger"
|
"github.com/sour-is/ev/app/webfinger"
|
||||||
"github.com/sour-is/ev/internal/lg"
|
"github.com/sour-is/ev/internal/lg"
|
||||||
|
@ -13,6 +15,11 @@ import (
|
||||||
"github.com/sour-is/ev/pkg/slice"
|
"github.com/sour-is/ev/pkg/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultExpire = 3 * time.Minute
|
||||||
|
cleanupInterval = 10 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error {
|
var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
@ -23,12 +30,17 @@ var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error
|
||||||
return fmt.Errorf("*es.EventStore not found in services")
|
return fmt.Errorf("*es.EventStore not found in services")
|
||||||
}
|
}
|
||||||
|
|
||||||
wf, err := webfinger.New(
|
cache := cache.New(defaultExpire, cleanupInterval)
|
||||||
ctx,
|
var withCache webfinger.WithCache = (func(s string) bool {
|
||||||
eventstore,
|
if _, ok := cache.Get(s); ok {
|
||||||
webfinger.WithHostnames(
|
return true
|
||||||
strings.Fields(env.Default("WEBFINGER_DOMAINS", "sour.is")),
|
}
|
||||||
))
|
cache.SetDefault(s, true)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
var withHostnames webfinger.WithHostnames = strings.Fields(env.Default("WEBFINGER_DOMAINS", "sour.is"))
|
||||||
|
|
||||||
|
wf, err := webfinger.New(ctx, eventstore, withCache, withHostnames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.RecordError(err)
|
span.RecordError(err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -21,17 +21,21 @@ func main() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
defer cancel() // restore interrupt function
|
defer cancel() // restore interrupt function
|
||||||
}()
|
}()
|
||||||
|
if err := Run(ctx); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func Run(ctx context.Context) error {
|
||||||
svc := &service.Harness{}
|
svc := &service.Harness{}
|
||||||
|
|
||||||
ctx, stop := lg.Init(ctx, appName)
|
ctx, stop := lg.Init(ctx, appName)
|
||||||
svc.OnStop(stop)
|
svc.OnStop(stop)
|
||||||
svc.Add(lg.NewHTTP(ctx))
|
svc.Add(lg.NewHTTP(ctx))
|
||||||
|
|
||||||
svc.Setup(ctx, apps.Apps()...)
|
svc.Setup(ctx, apps.Apps()...)
|
||||||
|
|
||||||
// Run application
|
// Run application
|
||||||
if err := svc.Run(ctx, appName, version); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := svc.Run(ctx, appName, version); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,8 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docopt/docopt-go"
|
"github.com/docopt/docopt-go"
|
||||||
"github.com/golang-jwt/jwt"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/sour-is/ev/app/webfinger"
|
"github.com/sour-is/ev/app/webfinger"
|
||||||
|
@ -127,23 +125,14 @@ func run(opts opts) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bkey := []byte(key.Public().(ed25519.PublicKey))
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
|
jrd := &webfinger.JRD{Subject: opts.Subject}
|
||||||
"sub": opts.Subject,
|
token, err := webfinger.NewSignedRequest(jrd, key)
|
||||||
"subject": opts.Subject,
|
|
||||||
"pub": enc(bkey),
|
|
||||||
"exp": time.Now().Add(90 * time.Minute).Unix(),
|
|
||||||
"iat": time.Now().Unix(),
|
|
||||||
"aud": "webfinger",
|
|
||||||
"iss": "sour.is-webfingerCLI",
|
|
||||||
})
|
|
||||||
aToken, err := token.SignedString(key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
body := strings.NewReader(aToken)
|
body := strings.NewReader(token)
|
||||||
req, err := http.NewRequest(http.MethodDelete, url.String(), body)
|
req, err := http.NewRequest(http.MethodDelete, url.String(), body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -173,7 +162,6 @@ func run(opts opts) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bkey := []byte(key.Public().(ed25519.PublicKey))
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, opts.File)
|
fmt.Fprintln(os.Stderr, opts.File)
|
||||||
fp, err := os.Open(opts.File)
|
fp, err := os.Open(opts.File)
|
||||||
|
@ -182,40 +170,20 @@ func run(opts opts) error {
|
||||||
}
|
}
|
||||||
y := yaml.NewDecoder(fp)
|
y := yaml.NewDecoder(fp)
|
||||||
|
|
||||||
type claims struct {
|
|
||||||
Subject string `json:"sub"`
|
|
||||||
PubKey string `json:"pub"`
|
|
||||||
*webfinger.JRD
|
|
||||||
jwt.StandardClaims
|
|
||||||
}
|
|
||||||
|
|
||||||
for err == nil {
|
for err == nil {
|
||||||
j := claims{
|
jrd := &webfinger.JRD{}
|
||||||
PubKey: enc(bkey),
|
|
||||||
JRD: &webfinger.JRD{},
|
|
||||||
StandardClaims: jwt.StandardClaims{
|
|
||||||
Audience: "sour.is-webfinger",
|
|
||||||
ExpiresAt: time.Now().Add(30 * time.Minute).Unix(),
|
|
||||||
IssuedAt: time.Now().Unix(),
|
|
||||||
Issuer: "sour.is-webfingerCLI",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = y.Decode(j.JRD)
|
err = y.Decode(jrd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
j.Subject = j.JRD.Subject
|
token, err := webfinger.NewSignedRequest(jrd, key)
|
||||||
j.StandardClaims.Subject = j.JRD.Subject
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, &j)
|
|
||||||
aToken, err := token.SignedString(key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
body := strings.NewReader(aToken)
|
body := strings.NewReader(token)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPut, url.String(), body)
|
req, err := http.NewRequest(http.MethodPut, url.String(), body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev"
|
|
||||||
"github.com/sour-is/ev/app/webfinger"
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/service"
|
|
||||||
"github.com/sour-is/ev/pkg/slice"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
span.AddEvent("Enable WebFinger")
|
|
||||||
eventstore, ok := slice.Find[*ev.EventStore](svc.Services...)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("*es.EventStore not found in services")
|
|
||||||
}
|
|
||||||
|
|
||||||
wf, err := webfinger.New(ctx, eventstore)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
svc.Add(wf)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
1
cmd/webfinger/app.webfinger.go
Symbolic link
1
cmd/webfinger/app.webfinger.go
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../ev/app.webfinger.go
|
|
@ -1,37 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
var apps service.Apps
|
|
||||||
var appName, version = service.AppName()
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
defer cancel() // restore interrupt function
|
|
||||||
}()
|
|
||||||
|
|
||||||
svc := &service.Harness{}
|
|
||||||
|
|
||||||
ctx, stop := lg.Init(ctx, appName)
|
|
||||||
svc.OnStop(stop)
|
|
||||||
svc.Add(lg.NewHTTP(ctx))
|
|
||||||
|
|
||||||
svc.Setup(ctx, apps.Apps()...)
|
|
||||||
|
|
||||||
// Run application
|
|
||||||
if err := svc.Run(ctx, appName, version); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
1
cmd/webfinger/main.go
Symbolic link
1
cmd/webfinger/main.go
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../ev/main.go
|
|
@ -1,53 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev"
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/env"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
diskstore "github.com/sour-is/ev/pkg/es/driver/disk-store"
|
|
||||||
memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver/projecter"
|
|
||||||
resolvelinks "github.com/sour-is/ev/pkg/es/driver/resolve-links"
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver/streamer"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/service"
|
|
||||||
"go.uber.org/multierr"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = apps.Register(10, func(ctx context.Context, svc *service.Harness) error {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
// setup eventstore
|
|
||||||
err := multierr.Combine(
|
|
||||||
ev.Init(ctx),
|
|
||||||
event.Init(ctx),
|
|
||||||
diskstore.Init(ctx),
|
|
||||||
memstore.Init(ctx),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
eventstore, err := ev.Open(
|
|
||||||
ctx,
|
|
||||||
env.Default("EV_DATA", "mem:"),
|
|
||||||
resolvelinks.New(),
|
|
||||||
streamer.New(ctx),
|
|
||||||
projecter.New(
|
|
||||||
ctx,
|
|
||||||
projecter.DefaultProjection,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
svc.Add(eventstore, &es.EventStore{EventStore: eventstore})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
1
cmd/webfinger/svc.es.go
Symbolic link
1
cmd/webfinger/svc.es.go
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../ev/svc.es.go
|
|
@ -1,46 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rs/cors"
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/env"
|
|
||||||
"github.com/sour-is/ev/pkg/mux"
|
|
||||||
"github.com/sour-is/ev/pkg/service"
|
|
||||||
"github.com/sour-is/ev/pkg/slice"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = apps.Register(20, func(ctx context.Context, svc *service.Harness) error {
|
|
||||||
s := &http.Server{}
|
|
||||||
svc.Add(s)
|
|
||||||
|
|
||||||
mux := mux.New()
|
|
||||||
s.Handler = cors.AllowAll().Handler(mux)
|
|
||||||
|
|
||||||
// s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// log.Println(r.URL.Path)
|
|
||||||
// mux.ServeHTTP(w, r)
|
|
||||||
// })
|
|
||||||
|
|
||||||
s.Addr = env.Default("EV_HTTP", ":8080")
|
|
||||||
if strings.HasPrefix(s.Addr, ":") {
|
|
||||||
s.Addr = "[::]" + s.Addr
|
|
||||||
}
|
|
||||||
svc.OnStart(func(ctx context.Context) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
log.Print("Listen on ", s.Addr)
|
|
||||||
span.AddEvent("begin listen and serve on " + s.Addr)
|
|
||||||
|
|
||||||
mux.Add(slice.FilterType[interface{ RegisterHTTP(*http.ServeMux) }](svc.Services...)...)
|
|
||||||
return s.ListenAndServe()
|
|
||||||
})
|
|
||||||
svc.OnStop(s.Shutdown)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
1
cmd/webfinger/svc.http.go
Symbolic link
1
cmd/webfinger/svc.http.go
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../ev/svc.http.go
|
108
cmd/webfinger/webfinger_e2e_test.go
Normal file
108
cmd/webfinger/webfinger_e2e_test.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"github.com/sour-is/ev/app/webfinger"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
data, err := os.MkdirTemp("", "data*")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error creating data dir: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(data)
|
||||||
|
|
||||||
|
os.Setenv("EV_HTTP", "[::1]:61234")
|
||||||
|
os.Setenv("WEBFINGER_DOMAINS", "::1")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
wg, ctx := errgroup.WithContext(ctx)
|
||||||
|
wg.Go(func() error {
|
||||||
|
// Run application
|
||||||
|
if err := Run(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
wg.Go(func() error {
|
||||||
|
m.Run()
|
||||||
|
cancel()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wg.Wait(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetHTTP(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
res, err := http.DefaultClient.Get("http://[::1]:61234/.well-known/webfinger")
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(res.StatusCode, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateResource(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
_, priv, err := ed25519.GenerateKey(nil)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
jrd := &webfinger.JRD{
|
||||||
|
Subject: "me@sour.is",
|
||||||
|
Properties: map[string]*string{
|
||||||
|
"foo": ptr("bar"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// create
|
||||||
|
token, err := webfinger.NewSignedRequest(jrd, priv)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPut, "http://[::1]:61234/.well-known/webfinger", strings.NewReader(token))
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
is.Equal(res.StatusCode, http.StatusCreated)
|
||||||
|
|
||||||
|
// repeat
|
||||||
|
req, err = http.NewRequest(http.MethodPut, "http://[::1]:61234/.well-known/webfinger", strings.NewReader(token))
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
res, err = http.DefaultClient.Do(req)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
is.Equal(res.StatusCode, http.StatusAlreadyReported)
|
||||||
|
|
||||||
|
// fetch
|
||||||
|
req, err = http.NewRequest(http.MethodGet, "http://[::1]:61234/.well-known/webfinger?resource=me@sour.is", nil)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
res, err = http.DefaultClient.Do(req)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
is.Equal(res.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
resJRD := &webfinger.JRD{}
|
||||||
|
err = json.NewDecoder(res.Body).Decode(resJRD)
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(jrd.Subject, resJRD.Subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptr[T any](t T) *T { return &t }
|
3
go.mod
3
go.mod
|
@ -24,6 +24,8 @@ require (
|
||||||
|
|
||||||
require github.com/tj/go-semver v1.0.0
|
require github.com/tj/go-semver v1.0.0
|
||||||
|
|
||||||
|
require github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.mills.io/prologic/msgbus v0.1.19 // indirect
|
git.mills.io/prologic/msgbus v0.1.19 // indirect
|
||||||
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 // indirect
|
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 // indirect
|
||||||
|
@ -101,7 +103,6 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||||
github.com/keys-pub/keys v0.1.22
|
github.com/keys-pub/keys v0.1.22
|
||||||
github.com/matryer/is v1.4.0
|
github.com/matryer/is v1.4.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -155,8 +155,6 @@ github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
@ -328,6 +326,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
||||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||||
github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0=
|
github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0=
|
||||||
|
|
Loading…
Reference in New Issue
Block a user