clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

main
tinyrelay / protocol / auth / grasp08.go
grasp08.go 2.2K
1package auth23import (4 "errors"5 "net/url"6 "strings"78 "github.com/FelineStateMachine/tinyrelay/protocol/nostr"9)1011// ErrGRASP08Unauthorized is the detail-free GRASP-08 authentication error12// returned for every failed repository proof.13var ErrGRASP08Unauthorized = errors.New("auth-required:")1415// VerifyGRASP08 validates a reusable kind 27235 GET proof for the repository16// root named by a GRASP-08 Smart HTTP request. The proof remains valid for17// one minute across the repository's supported Git requests.18func (v *Validator) VerifyGRASP08(header, rawURL string) (nostr.Event, error) {19 e, err := decodeToken(header, "GRASP-08")20 if err != nil {21 return nostr.Event{}, ErrGRASP08Unauthorized22 }23 if err := nostr.Validate(e); err != nil || e.Kind != 27235 {24 return nostr.Event{}, ErrGRASP08Unauthorized25 }26 now := v.now()27 if e.CreatedAt < now.Unix()-60 || e.CreatedAt > now.Unix()+60 {28 return nostr.Event{}, ErrGRASP08Unauthorized29 }30 if nostr.Tag(e, "method") != "GET" {31 return nostr.Event{}, ErrGRASP08Unauthorized32 }33 root, ok := GRASP08RepositoryRoot(rawURL)34 if !ok || sameRequestURL(nostr.Tag(e, "u"), root) != nil {35 return nostr.Event{}, ErrGRASP08Unauthorized36 }37 return e, nil38}3940// GRASP08RepositoryRoot returns the canonical repository URL for a supported41// Smart HTTP request beneath a .git path.42func GRASP08RepositoryRoot(raw string) (string, bool) {43 u, err := url.Parse(raw)44 if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" || u.User != nil {45 return "", false46 }47 // Keep the client's percent-encoding so the root compares equal to the48 // signed u tag, which is also kept as sent.49 path := u.EscapedPath()50 i := strings.LastIndex(path, ".git")51 if i < 0 || (i+4 < len(path) && path[i+4] != '/') {52 return "", false53 }54 rootPath := path[:i+4]55 suffix := path[i+4:]56 if suffix != "" && suffix != "/info/refs" && suffix != "/git-upload-pack" && suffix != "/git-receive-pack" {57 return "", false58 }59 if strings.HasSuffix(rootPath, "/") {60 return "", false61 }62 unescaped, err := url.PathUnescape(rootPath)63 if err != nil {64 return "", false65 }66 u.Path, u.RawPath, u.RawQuery, u.Fragment = unescaped, rootPath, "", ""67 return u.String(), true68}