add expiration http auth, remove jwt

This commit is contained in:
2025-04-02 19:22:40 -01:06
parent 53557bdcdc
commit b95dce2cef
12 changed files with 176 additions and 4518 deletions

80
cmd/nauth/main.go Normal file
View File

@@ -0,0 +1,80 @@
package main
import (
"encoding/base64"
"fmt"
"os"
"time"
"realy.lol/bech32encoding"
"realy.lol/httpauth"
"realy.lol/p256k"
"realy.lol/signer"
)
const secEnv = "NOSTR_SECRET_KEY"
func fail(format string, a ...any) {
_, _ = fmt.Fprintf(os.Stderr, format+"\n", a...)
os.Exit(1)
}
func main() {
// lol.SetLogLevel("trace")
if len(os.Args) > 1 && os.Args[1] == "help" {
fmt.Printf(`nauth help:
for generating extended expiration NIP-98 tokens:
nauth <url prefix> <duration in 0h0m0s format>
* NIP-98 secret will be expected in the environment variable "%s" - if absent, will not be added to the header. Endpoint is assumed to not require it if absent. An error will be returned if it was needed.
output will be rendered to stdout
`, secEnv)
os.Exit(0)
}
if len(os.Args) < 3 {
fail(`error: nauth requires minimum 2 args: <url> <duration in 0h0m0s format>
signing nsec (in bech32 format) is expected to be found in %s environment variable.
use "help" to get usage information
`, secEnv)
}
ex, err := time.ParseDuration(os.Args[2])
if err != nil {
fail(err.Error())
}
var sign signer.I
if sign, err = GetNIP98Signer(); err != nil {
fail(err.Error())
}
exp := time.Now().Add(ex).Unix()
ev := httpauth.MakeNIP98Event(os.Args[1], "", "", exp)
if err = ev.Sign(sign); err != nil {
fail(err.Error())
}
log.T.F("nip-98 http auth event:\n%s\n", ev.SerializeIndented())
b64 := base64.URLEncoding.EncodeToString(ev.Serialize())
fmt.Println("Nostr " + b64)
}
func GetNIP98Signer() (sign signer.I, err error) {
nsex := os.Getenv(secEnv)
var sk []byte
if len(nsex) == 0 {
err = errorf.E("no bech32 secret key found in environment variable %s", secEnv)
return
} else if sk, err = bech32encoding.NsecToBytes([]byte(nsex)); chk.E(err) {
err = errorf.E("failed to decode nsec: '%s'", err.Error())
return
}
sign = &p256k.Signer{}
if err = sign.InitSec(sk); chk.E(err) {
err = errorf.E("failed to init signer: '%s'", err.Error())
return
}
return
}

12
cmd/nauth/util.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import (
"bytes"
"realy.lol/lol"
)
var (
log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf
equals = bytes.Equal
)

View File

@@ -101,7 +101,7 @@ func Get(ur *url.URL, sign signer.I) (err error) {
r.Header.Add("User-Agent", userAgent)
r.Header.Add("Accept", "application/nostr+json")
if sign != nil {
if err = httpauth.AddNIP98Header(r, ur, "GET", "", sign); chk.E(err) {
if err = httpauth.AddNIP98Header(r, ur, "GET", "", sign, 0); chk.E(err) {
fail(err.Error())
}
}
@@ -159,7 +159,7 @@ func Post(f string, ur *url.URL, sign signer.I) (err error) {
r.Header.Add("User-Agent", userAgent)
r.Header.Add("Accept", "application/nostr+json")
if sign != nil {
if err = httpauth.AddNIP98Header(r, ur, "POST", h, sign); chk.E(err) {
if err = httpauth.AddNIP98Header(r, ur, "POST", h, sign, 0); chk.E(err) {
fail(err.Error())
}
}

View File

@@ -49,6 +49,14 @@ If one of the checks was to fail the server SHOULD respond with a 401 Unauthoriz
Servers MAY perform additional implementation-specific validation checks.
== Expiration Variant
For cases where an authorization is needed for a more extended duration, and primarily for working with standard HTTP REST tooling and low-spec browsers, a variant of the foregoing specification with the following changes:
1. The method tag is not present.
2. The URL is the prefix instead of the exact URL, and the verification ensures the request matches the same prefix.
3. There is an `expiration` tag, same as the link:https://github.com/nostr-protocol/nips/blob/8f676dc0a55e75564b54d96bcadf787b61654219/40.md[NIP-40] with the expiration as a unix timestamp encoded as the string in the second, value field, and to be valid must be in the future when the server receives it.
== Request Flow
Using the `Authorization` HTTP header, the `kind 27235` event MUST be `base64` encoded and use the Authorization scheme `Nostr`

View File

@@ -1,221 +0,0 @@
package httpauth
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
"realy.lol/event"
"realy.lol/kind"
"realy.lol/tag"
"realy.lol/tags"
"realy.lol/timestamp"
)
const (
MaxSkew = 15
JWTPrefix = "Bearer"
PEMSecretLabel = "EC PRIVATE KEY"
PEMPublicLabel = "EC PUBLIC KEY"
DefaultAlg = "ES256"
)
func MakeJWTEvent(jpk string) (ev *event.T) {
ev = &event.T{
CreatedAt: timestamp.Now(),
Kind: kind.JWTBinding,
Tags: tags.New(tag.New("J", jpk)),
}
return
}
type JWT struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Algorithm string `json:"alg"`
IssuedAt int64 `json:"iat"`
ExpirationTime int64 `json:"exp,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Audience string `json:"aud,omitempty"`
}
func GenerateJWTKeys() (x509sec, x509pub, pemSec, pemPub []byte, sk *ecdsa.PrivateKey, pk ecdsa.PublicKey, err error) {
if sk, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); chk.E(err) {
return
}
pk = sk.PublicKey
var pkb []byte
if pkb, err = x509.MarshalPKIXPublicKey(sk.Public()); chk.E(err) {
return
}
x509pub = make([]byte, len(pkb)*8/6+3)
base64.URLEncoding.Encode(x509pub, pkb)
var skb []byte
if skb, err = x509.MarshalECPrivateKey(sk); chk.E(err) {
return
}
x509sec = make([]byte, len(skb)*8/6+3)
base64.URLEncoding.Encode(x509sec, skb)
bufS := new(bytes.Buffer)
if err = pem.Encode(bufS, &pem.Block{PEMSecretLabel,
nil, skb}); chk.E(err) {
return
}
pemSec = bufS.Bytes()
bufP := new(bytes.Buffer)
if err = pem.Encode(bufP, &pem.Block{PEMPublicLabel,
nil, pkb}); chk.E(err) {
return
}
pemPub = bufP.Bytes()
return
}
func GenerateJWTClaims(ur, issuer string,
exp ...string) (tok []byte, err error) {
// generate claim
claim := &JWT{
Issuer: issuer,
Subject: ur,
Algorithm: DefaultAlg,
IssuedAt: time.Now().Unix(),
}
if len(exp) > 0 {
// parse duration
var dur time.Duration
if dur, err = time.ParseDuration(exp[0]); chk.E(err) {
return
}
claim.ExpirationTime = claim.IssuedAt + int64(dur/time.Second)
}
if tok, err = json.Marshal(claim); chk.E(err) {
return
}
return
}
func SignJWTtoken(tok []byte, sec *ecdsa.PrivateKey) (bearer string, err error) {
var claims jwt.MapClaims
if err = json.Unmarshal(tok, &claims); chk.E(err) {
return
}
alg := jwt.GetSigningMethod(claims["alg"].(string))
token := jwt.NewWithClaims(alg, claims)
if bearer, err = token.SignedString(sec); chk.E(err) {
return
}
return
}
// GenerateAndSignJWTtoken is a helper to do all the steps above in one, based
// on having a base64 encoded x509 secret key provided
func GenerateAndSignJWTtoken(issuer, ur, exp, sec string) (bearer string, err error) {
var t []byte
if t, err = GenerateJWTClaims(issuer, ur, exp); chk.E(err) {
return
}
var jskb []byte
if jskb, err = base64.URLEncoding.DecodeString(sec); chk.E(err) {
return
}
var sk *ecdsa.PrivateKey
if sk, err = x509.ParseECPrivateKey(jskb); chk.E(err) {
return
}
return SignJWTtoken(t, sk)
}
// VerifyJWTFunc ostensibly should be a function that queries the event store
// for a kind 13004 containing a J tag with a x509 encoded pubkey in base64-URL
// that matches the signature on the JWT token.
type VerifyJWTFunc func(npub string) (jwtPub string, pk []byte, err error)
// VerifyJWTtoken checks that the claims and signature on a JWT token are valid,
// and returns the public key to check the signer matches with a nostr npub
// issuer.
//
// If there is an expiry, it only checks that the token's URL is the same as the
// prefix of the URL being verified for.
func VerifyJWTtoken(entry, URL string, vfn VerifyJWTFunc) (pk []byte, valid bool, err error) {
var token *jwt.Token
if token, err = jwt.Parse(entry, func(token *jwt.Token) (ifc interface{}, err error) {
var iss string
if iss, err = token.Claims.GetIssuer(); chk.E(err) {
return
}
var jwtPub string
if jwtPub, pk, err = vfn(iss); chk.E(err) {
return
}
var pkb []byte
if pkb, err = base64.URLEncoding.DecodeString(jwtPub); chk.E(err) {
return
}
var jpk any
if jpk, err = x509.ParsePKIXPublicKey(pkb); chk.E(err) {
return
}
now := time.Now().Unix()
var exp *jwt.NumericDate
if exp, err = token.Claims.GetExpirationTime(); chk.E(err) {
}
if exp != nil {
cmp := now - exp.Unix()
if cmp > MaxSkew {
err = errors.Wrapf(jwt.ErrTokenInvalidClaims,
"token is expired, %ds since expiry %d, time now %d, max allowed %d", cmp, exp.Unix(), now, MaxSkew)
return
}
} else {
var iat *jwt.NumericDate
if iat, err = token.Claims.GetIssuedAt(); chk.E(err) {
return
}
cmp := time.Now().Unix() - iat.Unix()
if cmp > 15 || cmp < -15 {
err = errors.Wrapf(jwt.ErrTokenInvalidClaims,
"issued at is more than %d seconds skewed", cmp)
return
}
}
var sub string
if sub, err = token.Claims.GetSubject(); chk.E(err) {
err = errors.Wrap(jwt.ErrTokenInvalidClaims, err.Error())
return
}
// when expiry is present the URL only needs to match on a prefix (already checked)
if exp != nil {
if !strings.HasPrefix(URL, sub) {
log.I.S(URL, sub)
err = errors.Wrap(jwt.ErrTokenInvalidClaims,
fmt.Sprintf("subject doesn't match expected URL prefix for an expiring token %s != %s", sub, URL))
return
}
} else if sub != URL {
err = errors.Wrap(jwt.ErrTokenInvalidClaims, "subject doesn't match expected URL")
return
}
ifc = jpk
return
}, jwt.WithoutClaimsValidation()); chk.E(err) {
return
}
valid = token.Valid
return
}

View File

@@ -1,42 +0,0 @@
package httpauth
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
func (j *JWT) GetExpirationTime() (exp *jwt.NumericDate, err error) {
exp = jwt.NewNumericDate(time.Unix(j.ExpirationTime, 0))
return
}
func (j *JWT) GetIssuedAt() (iat *jwt.NumericDate, err error) {
iat = jwt.NewNumericDate(time.Unix(j.IssuedAt, 0))
return
}
func (j *JWT) GetNotBefore() (nbf *jwt.NumericDate, err error) {
nbf = jwt.NewNumericDate(time.Unix(j.NotBefore, 0))
return
}
func (j *JWT) GetIssuer() (iss string, err error) {
iss = j.Issuer
return
}
func (j *JWT) GetSubject() (sub string, err error) {
sub = j.Subject
return
}
func (j *JWT) GetAudience() (aud jwt.ClaimStrings, err error) {
aud = jwt.ClaimStrings{j.Audience}
return
}
func (j *JWT) Validate() (err error) {
log.I.S("validate")
return
}

View File

@@ -1,96 +0,0 @@
package httpauth
import (
"bytes"
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"fmt"
"testing"
"realy.lol/p256k"
)
const jwtSecret = "MHcCAQEEIDlyFWD0KouB4n7aTPqlpNkoRTnuy7gMyY-YJusMsl0boAoGCCqGSM49AwEHoUQDQgAEDkH_rMzfr1LIHqnoFXyIYuz7dIYkg4qonbQhjeR0N_6CXpX2MqVHRLz9sx2EyXZZKPsFFbE_KJPczKu6qcIsRA=="
const URL = "https://example.com"
func TestSignJWTtoken_VerifyJWTtoken(t *testing.T) {
sign := &p256k.Signer{}
var err error
if err = sign.Generate(); chk.E(err) {
t.Fatal(err)
}
pub := fmt.Sprintf("%0x", sign.Pub())
var jskb []byte
if jskb, err = base64.URLEncoding.DecodeString(jwtSecret); chk.E(err) {
t.Fatal(err)
}
var sec *ecdsa.PrivateKey
if sec, err = x509.ParseECPrivateKey(jskb); chk.E(err) {
t.Fatal(err)
}
spk := &sec.PublicKey
var spkb []byte
if spkb, err = x509.MarshalPKIXPublicKey(spk); chk.E(err) {
t.Fatal(err)
}
spub := base64.URLEncoding.EncodeToString(spkb)
var tok []byte
if tok, err = GenerateJWTClaims("https://example.com", pub, "1h"); chk.E(err) {
t.Fatal(err)
}
var entry string
if entry, err = SignJWTtoken(tok, sec); chk.E(err) {
t.Fatal(err)
}
vfn := func(npub string) (jwtPub string, pk []byte, err error) {
// pubkey in token claims must match what we just put in it
if npub != pub {
err = fmt.Errorf("invalid jwt token npub, got %s expected %s", npub, pub)
return
}
pk = sign.Pub()
// we pretend that we found the 13004 event with the key if the above passed.
jwtPub = spub
return
}
var valid bool
var pk []byte
if pk, valid, err = VerifyJWTtoken(entry, URL, vfn); chk.E(err) {
t.Fatal(err)
}
if !bytes.Equal(pk, sign.Pub()) {
t.Fatalf("invalid npub, got %0x, expected %0x", pk, sign.Pub())
}
if !valid {
log.I.S(valid, err)
}
}
func TestMakeJWTEvent(t *testing.T) {
var err error
sign := &p256k.Signer{}
if err = sign.Generate(); chk.E(err) {
t.Fatal(err)
}
var jskb []byte
var sec *ecdsa.PrivateKey
if jskb, err = base64.URLEncoding.DecodeString(jwtSecret); chk.E(err) {
t.Fatal(err)
}
if sec, err = x509.ParseECPrivateKey(jskb); chk.E(err) {
t.Fatal(err)
}
spk := &sec.PublicKey
var spkb []byte
if spkb, err = x509.MarshalPKIXPublicKey(spk); chk.E(err) {
t.Fatal(err)
}
spub := base64.URLEncoding.EncodeToString(spkb)
ev := MakeJWTEvent(spub)
if err = ev.Sign(sign); chk.E(err) {
return
}
log.I.F("%s", ev.SerializeIndented())
}

View File

@@ -19,30 +19,30 @@ const (
NIP98Prefix = "Nostr"
)
func MakeNIP98Event(u, method, hash string) (ev *event.T) {
if hash != "" {
ev = &event.T{
CreatedAt: timestamp.Now(),
Kind: kind.HTTPAuth,
Tags: tags.New(
tag.New("u", u),
tag.New("method", strings.ToUpper(method)),
tag.New("payload", hash),
),
}
func MakeNIP98Event(u, method, hash string, expiry int64) (ev *event.T) {
var t []*tag.T
t = append(t, tag.New("u", u))
if expiry > 0 {
t = append(t,
tag.New("expiration", timestamp.FromUnix(expiry).String()))
} else {
ev = &event.T{
CreatedAt: timestamp.Now(),
Kind: kind.HTTPAuth,
Tags: tags.New(tag.New("u", u),
tag.New("method", strings.ToUpper(method))),
}
t = append(t,
tag.New("method", strings.ToUpper(method)))
}
if hash != "" {
t = append(t, tag.New("payload", hash))
}
ev = &event.T{
CreatedAt: timestamp.Now(),
Kind: kind.HTTPAuth,
Tags: tags.New(t...),
}
return
}
func AddNIP98Header(r *http.Request, ur *url.URL, method, hash string, sign signer.I) (err error) {
ev := MakeNIP98Event(ur.String(), method, hash)
func AddNIP98Header(r *http.Request, ur *url.URL, method, hash string,
sign signer.I, expiry int64) (err error) {
ev := MakeNIP98Event(ur.String(), method, hash, expiry)
if err = ev.Sign(sign); chk.E(err) {
return
}

View File

@@ -1,71 +0,0 @@
= http authentication
:toc:
The nostr protocol includes an authentication mechanism based on nostr events and HTTP headers that you can read our local version here: link:98.adoc[nip-98]; however, there are numerous tools you might want to use to interact with HTTP listeners such as the simplified HTTP protocol described in link:../readme.adoc#simplified-nostr[the readme] which would permit the deployment of a relay with auth-controlled access, but be able to use tooling and clients that can't be easily augmented to use link:98.adoc[nip-98], and use PEM encoded secret keys that can be used with standards compliant JWT implementations to generate them.
The main use cases are for testing and administrative purposes, but it may also enable devices such as old/low-spec ebook readers' browsers to interact with nostr based document libraries such as Alexandria, for devices that can neither do websockets nor easily handle NIP-98 headers for paid/private document repositories.
HTTP versions of the protocol are not just about enabling devices that may not be able to do websockets, or be easily programmable even still to do NIP-98 authentication, they are also simpler and enable lowering the overhead caused by the use of sockets for simple request/response protocols that have no need to maintain liveness or store socket state information.
== JWT Authentication Registration Event
[[authevent]]
The simplest way to enable this for a nostr relay is to create a new event kind which allows a user to associate a JWT public key token with their nostr public key, and then the relay's access control mechanism can search for it and authorize using JWT in place of NIP-98 or NIP-42 websocket auth (to be implemented later).
To make this mesh with the standard NIP-01 specification for indexing, the simplest way is to place the JWT public key in a `J` tag and then when a request is received with the JWT token, after it is decoded, the event tagged with this token can be found with a simple query and then the associated public key of the user that published the event with this token can be located.
So, the event structure is as follows:
[source,json]
----
{
"id": "<event id>",
"kind": 13004,
"pubkey": "<user pubkey>",
"created_at": 1234567890,
"content": "",
"tags": [
["J","<JWT public key in x509 binary in base64 URL encoding>"]
],
"sig": "<signature matching user pubkey>"
}
----
By publishing this event, with the JWT secret key, the user proves control of the JWT token and that it is equivalent as their own nsec for signing JWT signed HTTP requests in place of using the NIP-98 authentication.
NOTE: it should be possible to later also use this to assign other authentication mechanisms trust, should other mechanisms be usable for other environments that would otherwise not be able to work with nostr relays. Other mechanisms would use a different tag key than `J` which stands for JWT.
== JWT Authentication Protocol
In every respect, other than the different key/signature algorithm, and authentication event token encoding, this protocol is identical to NIP-98
This means that the JWT must include the HTTP method, the full URL of the request, and a base 10 encoded timestamp that is within 15 seconds of the server/relay current unix time:
The encoding should then be:
[source,json]
----
{
"iss":"<hex pubkey of user>",
"typ":"message",
"sub":"http://example.com/path/to/endpoint",
"alg":"ES256"
"iat":1234567890,
}
----
The JWT token must then have a signature on this string, and that signature match the JWT token registered in the kind 30050.
An additional field `exp` can be present and will allow the client to generate one certificate and use it until the time specified. The authorization can be revoked by deleting or updating the link:#authevent[registration event]
== The Actual Authorization
Where the NIP-98 uses:
Authorization: Nostr <base64 encoded auth event>
to comport with the standard for JWT:
Authorization: Bearer <jwt.token.string>
The relay must be able to distinguish between the `Nostr` and `Bearer` sentinels in the header value for `Authorization` and then it can validate the JWT token, and then search for the JWT registration, and then use the associated nostr public key to check for permission to do whatever the POST (or QUERY) body specifies.

View File

@@ -8,6 +8,7 @@ import (
"time"
"realy.lol/event"
"realy.lol/ints"
"realy.lol/kind"
"realy.lol/tag"
)
@@ -21,7 +22,7 @@ var ErrMissingKey = fmt.Errorf(
//
// A VerifyJWTFunc should be provided in order to search the event store for a
// kind 13004 with a JWT signer pubkey that is granted authority for the request.
func CheckAuth(r *http.Request, vfn VerifyJWTFunc, tolerance ...time.Duration) (valid bool,
func CheckAuth(r *http.Request, tolerance ...time.Duration) (valid bool,
pubkey []byte, err error) {
val := r.Header.Get(HeaderKey)
if val == "" {
@@ -69,27 +70,45 @@ func CheckAuth(r *http.Request, vfn VerifyJWTFunc, tolerance ...time.Duration) (
ev.Kind.K, ev.Kind.Name(), kind.HTTPAuth.K, kind.HTTPAuth.Name())
return
}
// The created_at timestamp MUST be within a reasonable time window (suggestion ~60~ 15 seconds)
ts := ev.CreatedAt.I64()
tn := time.Now().Unix()
if ts < tn-tolerate || ts > tn+tolerate {
err = errorf.E("timestamp %d is more than %d seconds divergent from now %d",
ts, tolerate, tn)
// if there is an expiration timestamp it supersedes the created_at for validity.
exp := ev.Tags.GetAll(tag.New("expiration"))
if exp.Len() > 1 {
err = errorf.E("more than one \"expiration\" tag found: '%s'", exp.MarshalTo(nil))
return
}
// we are going to say anything not specified in nip-98 is invalid also, such as extra tags
if ev.Tags.Len() < 2 {
err = errorf.E("other than exactly 2 tags found in event\n%s",
ev.Tags.MarshalTo(nil))
return
var expiring bool
if exp.Len() == 1 {
ex := ints.New(0)
exp1 := exp.F()[0]
if rem, err = ex.Unmarshal(exp1.Value()); chk.E(err) {
return
}
tn := time.Now().Unix()
if tn > ex.Int64()+tolerate {
err = errorf.E("HTTP auth event is expired %d time now %d",
tn, ex.Int64()+tolerate)
return
}
expiring = true
} else {
// The created_at timestamp MUST be within a reasonable time window (suggestion 60
// seconds)
ts := ev.CreatedAt.I64()
tn := time.Now().Unix()
if ts < tn-tolerate || ts > tn+tolerate {
err = errorf.E("timestamp %d is more than %d seconds divergent from now %d",
ts, tolerate, tn)
return
}
}
ut := ev.Tags.GetAll(tag.New("u"))
if ut.Len() != 1 {
if ut.Len() > 1 {
err = errorf.E("more than one \"u\" tag found: '%s'", ut.MarshalTo(nil))
return
}
uts := ut.Value()
// The u tag MUST be exactly the same as the absolute request URL (including query parameters).
// The u tag MUST be exactly the same as the absolute request URL (including query
// parameters).
proto := r.URL.Scheme
// if this came through a proxy we need to get the protocol to match the event
if p := r.Header.Get("X-Forwarded-Proto"); p != "" {
@@ -102,22 +121,31 @@ func CheckAuth(r *http.Request, vfn VerifyJWTFunc, tolerance ...time.Duration) (
evUrl := string(uts[0].Value())
// log.I.S(r)
log.T.F("full URL: %s event u tag value: %s", fullUrl, evUrl)
if fullUrl != evUrl {
if expiring {
// if it is expiring, the URL only needs to be the same prefix to allow its use with
// multiple endpoints.
if !strings.HasPrefix(fullUrl, evUrl) {
err = errorf.E("request URL %s does not start with the u tag URL %s",
fullUrl, evUrl)
}
} else if fullUrl != evUrl {
err = errorf.E("request has URL %s but signed nip-98 event has url %s",
fullUrl, string(uts[0].Value()))
return
}
// The method tag MUST be the same HTTP method used for the requested resource.
mt := ev.Tags.GetAll(tag.New("method"))
if mt.Len() != 1 {
err = errorf.E("more than one \"method\" tag found: '%s'", mt.MarshalTo(nil))
return
}
mts := mt.Value()
if strings.ToLower(string(mts[0].Value())) != strings.ToLower(r.Method) {
err = errorf.E("request has method %s but event has method %s",
string(mts[0].Value()), r.Method)
return
if !expiring {
// The method tag MUST be the same HTTP method used for the requested resource.
mt := ev.Tags.GetAll(tag.New("method"))
if mt.Len() != 1 {
err = errorf.E("more than one \"method\" tag found: '%s'", mt.MarshalTo(nil))
return
}
mts := mt.Value()
if strings.ToLower(string(mts[0].Value())) != strings.ToLower(r.Method) {
err = errorf.E("request has method %s but event has method %s",
string(mts[0].Value()), r.Method)
return
}
}
if valid, err = ev.Verify(); chk.E(err) {
return
@@ -126,34 +154,6 @@ func CheckAuth(r *http.Request, vfn VerifyJWTFunc, tolerance ...time.Duration) (
return
}
pubkey = ev.Pubkey
case strings.HasPrefix(val, JWTPrefix):
if vfn == nil {
err = errorf.E("JWT bearer header found but no JWT verifier function provided")
return
}
split := strings.Split(val, " ")
if len(split) == 1 {
err = errorf.E("missing JWT auth token from '%s' http header key: '%s'",
HeaderKey, val)
}
if len(split) > 2 {
err = errorf.E("extraneous content after second field space separated: %s", val)
return
}
// The u tag MUST be exactly the same as the absolute request URL (including query parameters).
proto := r.URL.Scheme
// if this came through a proxy we need to get the protocol to match the event
if p := r.Header.Get("X-Forwarded-Proto"); p != "" {
proto = p
}
if proto == "" {
proto = "http"
}
fullUrl := proto + "://" + r.Host + r.URL.RequestURI()
if pubkey, valid, err = VerifyJWTtoken(split[1], fullUrl, vfn); chk.E(err) {
return
}
default:
err = errorf.E("invalid '%s' value: '%s'", HeaderKey, val)
return

4012
query.json

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
v1.12.2
v1.13.0