Files
ucan/toolkit/_example/issuer/issuer.go
2025-08-05 16:57:19 +02:00

120 lines
2.8 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
"github.com/ucan-wg/go-ucan/token/delegation"
example "github.com/INFURA/go-ucan-toolkit/_example"
"github.com/INFURA/go-ucan-toolkit/issuer"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
// register as handler of the interrupt signal to trigger the teardown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
go func() {
<-quit
cancel()
}()
err := run(ctx, example.IssuerUrl, example.ServicePrivKey)
if err != nil {
log.Println(err)
os.Exit(1)
}
}
func run(ctx context.Context, issuerUrl string, servicePrivKey crypto.PrivKey) error {
issuingLogic := func(iss did.DID, aud did.DID, cmd command.Command) (*delegation.Token, error) {
log.Printf("issuing delegation to %v for %v", aud, cmd)
// We construct an arbitrary policy.
// Here, we enforce that the caller uses its own DID endpoint (an arbitrary construct for this example).
// You will notice that the server doesn't need to know about this logic to enforce it.
policies, err := policy.Construct(
policy.Equal(".http.path", literal.String(fmt.Sprintf("/%s", aud.String()))),
)
if err != nil {
return nil, err
}
return delegation.Root(iss, aud, cmd, policies,
// let's add an expiration, this will force the client to renew its token.
delegation.WithExpirationIn(10*time.Second),
)
}
rootIssuer, err := issuer.NewRootIssuer(servicePrivKey, issuingLogic)
if err != nil {
return err
}
handler := issuer.HttpWrapper(rootIssuer, func(r *http.Request) (*issuer.ResolvedRequest, error) {
// Let's make up a simple json protocol
req := struct {
Audience string `json:"aud"`
Cmd string `json:"cmd"`
Subject string `json:"sub"`
}{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return nil, err
}
aud, err := did.Parse(req.Audience)
if err != nil {
return nil, err
}
cmd, err := command.Parse(req.Cmd)
if err != nil {
return nil, err
}
sub, err := did.Parse(req.Subject)
if err != nil {
return nil, err
}
return &issuer.ResolvedRequest{
Audience: aud,
Cmd: cmd,
Subject: sub,
}, nil
})
srv := &http.Server{
Addr: issuerUrl,
Handler: handler,
}
go func() {
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err)
}
}()
log.Printf("listening on %s\n", srv.Addr)
<-ctx.Done()
if err := srv.Shutdown(ctx); err != nil && !errors.Is(err, context.Canceled) {
return err
}
return nil
}