44 lines
1.2 KiB
Go
44 lines
1.2 KiB
Go
|
|
// Package token validates incoming Bearer tokens and extracts the subject
|
||
|
|
// (client agent identity). Prod uses Pocket ID JWKS; dev uses a static table.
|
||
|
|
package token
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// ErrInvalid is returned when a bearer token cannot be parsed or verified.
|
||
|
|
var ErrInvalid = errors.New("token: invalid or missing bearer token")
|
||
|
|
|
||
|
|
// Validator turns a raw bearer string into the authenticated client subject
|
||
|
|
// (the Pocket ID `sub` claim, i.e. the client/agent identity).
|
||
|
|
type Validator interface {
|
||
|
|
Validate(ctx context.Context, bearer string) (subject string, err error)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Static is a dev-mode validator that accepts a fixed set of bearers → subjects.
|
||
|
|
type Static map[string]string
|
||
|
|
|
||
|
|
func (s Static) Validate(_ context.Context, bearer string) (string, error) {
|
||
|
|
if bearer == "" {
|
||
|
|
return "", ErrInvalid
|
||
|
|
}
|
||
|
|
sub, ok := s[bearer]
|
||
|
|
if !ok {
|
||
|
|
return "", ErrInvalid
|
||
|
|
}
|
||
|
|
return sub, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// FromAuthHeader extracts the bearer from an "Authorization: Bearer …" header.
|
||
|
|
func FromAuthHeader(r *http.Request) (string, error) {
|
||
|
|
h := r.Header.Get("Authorization")
|
||
|
|
const prefix = "Bearer "
|
||
|
|
if !strings.HasPrefix(h, prefix) {
|
||
|
|
return "", ErrInvalid
|
||
|
|
}
|
||
|
|
return strings.TrimPrefix(h, prefix), nil
|
||
|
|
}
|