Update build configuration and improve token handling
Build and Test / verify (push) Failing after 1m22s
Build and Test / verify (push) Failing after 1m22s
- Change CI container image to debian:trixie-slim and set GOPROXY. - Update Go version to 1.26 in Dockerfile and go.mod. - Refactor token validation to use SHA-256 hashes instead of plain tokens. - Add network policy to restrict access to the service. - Enhance README with new configuration details and usage examples. - Add tests for new token hash validation logic.
This commit is contained in:
@@ -10,11 +10,23 @@ jobs:
|
|||||||
verify:
|
verify:
|
||||||
runs-on: container-builder
|
runs-on: container-builder
|
||||||
container:
|
container:
|
||||||
image: golang:1.24-bookworm
|
image: debian:trixie-slim
|
||||||
|
env:
|
||||||
|
GOPROXY: https://gitea.nehmer.net/api/packages/torben/go,https://proxy.golang.org,direct
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install CI Dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends git nodejs curl unzip ca-certificates
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.26.x'
|
||||||
|
|
||||||
- name: Show Go Version
|
- name: Show Go Version
|
||||||
run: go version
|
run: go version
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1,7 +1,9 @@
|
|||||||
ARG GO_VERSION=1.24
|
ARG GO_VERSION=1.26
|
||||||
|
ARG GOPROXY_URL=https://gitea.nehmer.net/api/packages/torben/go,https://proxy.golang.org,direct
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-bookworm AS build
|
FROM golang:${GO_VERSION}-bookworm AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
ENV GOPROXY=${GOPROXY_URL}
|
||||||
|
|
||||||
COPY go.mod ./
|
COPY go.mod ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ Kleiner Go-basierter Traefik-ForwardAuth-Microservice, der Bearer-Tokens gegen e
|
|||||||
## Verhalten
|
## Verhalten
|
||||||
|
|
||||||
- Prueft `Authorization: Bearer <token>`.
|
- Prueft `Authorization: Bearer <token>`.
|
||||||
- Validiert Token gegen Tokens aus:
|
- Validiert Token gegen SHA-256-Hashes aus:
|
||||||
- `AUTH_PROXY_TOKENS_DIR` (jede Datei enthaelt genau einen Token), und/oder
|
- `AUTH_PROXY_TOKEN_HASHES_DIR` (jede Datei enthaelt genau einen Token-Hash), und/oder
|
||||||
- `AUTH_PROXY_TOKENS` (kommagetrennte Liste).
|
- `AUTH_PROXY_TOKEN_HASHES` (kommagetrennte Hash-Liste).
|
||||||
- Antwortet mit:
|
- Antwortet mit:
|
||||||
- `200` (leer) bei gueltigem Token,
|
- `200` (leer) bei gueltigem Token,
|
||||||
- `401` bei fehlendem/ungueltigem Header oder ungueltigem Token.
|
- `401` bei fehlendem/ungueltigem Header oder ungueltigem Token.
|
||||||
@@ -17,6 +17,7 @@ Kleiner Go-basierter Traefik-ForwardAuth-Microservice, der Bearer-Tokens gegen e
|
|||||||
## Sicherheitsaspekte
|
## Sicherheitsaspekte
|
||||||
|
|
||||||
- Token-Matching erfolgt auf Basis von SHA-256-Digests mit `crypto/subtle.ConstantTimeCompare`.
|
- Token-Matching erfolgt auf Basis von SHA-256-Digests mit `crypto/subtle.ConstantTimeCompare`.
|
||||||
|
- Der Service speichert nur Hashes der erlaubten Tokens, keine Klartext-Tokens in der Konfiguration.
|
||||||
- Token werden nie im Klartext geloggt; es wird nur ein kurzer Fingerprint (`sha256:...`) geloggt.
|
- Token werden nie im Klartext geloggt; es wird nur ein kurzer Fingerprint (`sha256:...`) geloggt.
|
||||||
- Start bricht fail-fast ab, wenn keine gueltigen Tokens geladen werden konnten.
|
- Start bricht fail-fast ab, wenn keine gueltigen Tokens geladen werden konnten.
|
||||||
|
|
||||||
@@ -25,17 +26,46 @@ Kleiner Go-basierter Traefik-ForwardAuth-Microservice, der Bearer-Tokens gegen e
|
|||||||
- `AUTH_PROXY_LISTEN_ADDR`
|
- `AUTH_PROXY_LISTEN_ADDR`
|
||||||
- Default: `:8080`
|
- Default: `:8080`
|
||||||
- Beispiel: `:8080`
|
- Beispiel: `:8080`
|
||||||
- `AUTH_PROXY_TOKENS_DIR`
|
- `AUTH_PROXY_TOKEN_HASHES_DIR`
|
||||||
- Optional
|
- Optional
|
||||||
- Pfad auf ein Verzeichnis, in dem jede Datei einen Token enthaelt (z. B. Kubernetes Secret Volume)
|
- Pfad auf ein Verzeichnis, in dem jede Datei einen SHA-256-Token-Hash enthaelt (z. B. Kubernetes Secret Volume)
|
||||||
- `AUTH_PROXY_TOKENS`
|
- `AUTH_PROXY_TOKEN_HASHES`
|
||||||
- Optional
|
- Optional
|
||||||
- Kommagetrennte Tokenliste, z. B. `token-a,token-b`
|
- Kommagetrennte Liste von SHA-256-Hashes (64 Hex-Zeichen)
|
||||||
|
- Hash-Format
|
||||||
|
- `abcdef...` (64 hex) oder `sha256:abcdef...` (64 hex)
|
||||||
- `AUTH_PROXY_LOG_LEVEL`
|
- `AUTH_PROXY_LOG_LEVEL`
|
||||||
- Default: `info`
|
- Default: `info`
|
||||||
- Werte wie `debug`, `info`, `warn`, `error`
|
- Werte wie `debug`, `info`, `warn`, `error`
|
||||||
|
|
||||||
Hinweis: Es muss mindestens eine Tokenquelle (`AUTH_PROXY_TOKENS_DIR` oder `AUTH_PROXY_TOKENS`) konfiguriert sein.
|
Hinweis: Es muss mindestens eine Hash-Quelle (`AUTH_PROXY_TOKEN_HASHES_DIR` oder `AUTH_PROXY_TOKEN_HASHES`) konfiguriert sein.
|
||||||
|
|
||||||
|
## Go Package Registry (Gitea)
|
||||||
|
|
||||||
|
Dieses Repo ist auf die Gitea Go Package Registry ausgerichtet.
|
||||||
|
|
||||||
|
- Registry-Muster: `https://<gitea-host>/api/packages/{owner}/go`
|
||||||
|
- In dieser Konfiguration: `https://gitea.nehmer.net/api/packages/torben/go`
|
||||||
|
|
||||||
|
Beispiel lokal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go env -w GOPROXY="https://gitea.nehmer.net/api/packages/torben/go,https://proxy.golang.org,direct"
|
||||||
|
```
|
||||||
|
|
||||||
|
Annahme: Deine Angabe `https://gitea.example.com/api/packages/{owner}/go` ist ein Muster/Template. Implementiert wurde konkret `gitea.nehmer.net` mit Owner `torben`.
|
||||||
|
|
||||||
|
## Healthcheck Security
|
||||||
|
|
||||||
|
`/healthz` muss nicht world-readable sein. Fuer k3s Probes und Prometheus reicht interne Erreichbarkeit im Cluster.
|
||||||
|
|
||||||
|
Empfehlung:
|
||||||
|
|
||||||
|
- Service als `ClusterIP` belassen (bereits in Referenzmanifesten umgesetzt)
|
||||||
|
- Keine externe Ingress-Route auf `/healthz` publizieren
|
||||||
|
- Zugriff auf Pod/Service-Netzwerkebene einschränken (z. B. NetworkPolicy), falls eure CNI/Policies das bereits vorsehen
|
||||||
|
|
||||||
|
Annahme: In eurem Setup ist der ForwardAuth-Service nur intern erreichbar und wird nicht direkt aus dem Internet exponiert.
|
||||||
|
|
||||||
## Lokal bauen und starten
|
## Lokal bauen und starten
|
||||||
|
|
||||||
@@ -44,7 +74,21 @@ go test ./... -v
|
|||||||
go vet ./...
|
go vet ./...
|
||||||
go build ./cmd/authproxy
|
go build ./cmd/authproxy
|
||||||
|
|
||||||
AUTH_PROXY_TOKENS="my-token-1,my-token-2" \
|
AUTH_PROXY_TOKEN_HASHES="<sha256-hash-1>,<sha256-hash-2>" \
|
||||||
|
AUTH_PROXY_LISTEN_ADDR=":8080" \
|
||||||
|
go run ./cmd/authproxy
|
||||||
|
```
|
||||||
|
|
||||||
|
Token-Hashes erzeugen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo -n "my-token-1" | sha256sum | awk '{print $1}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Startbeispiel mit Hashes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
AUTH_PROXY_TOKEN_HASHES="<sha256-hash-1>,<sha256-hash-2>" \
|
||||||
AUTH_PROXY_LISTEN_ADDR=":8080" \
|
AUTH_PROXY_LISTEN_ADDR=":8080" \
|
||||||
go run ./cmd/authproxy
|
go run ./cmd/authproxy
|
||||||
```
|
```
|
||||||
@@ -72,7 +116,7 @@ Das Projekt enthaelt ein Multi-Stage-Dockerfile mit statisch gelinktem Binary (`
|
|||||||
```bash
|
```bash
|
||||||
docker build -t gitea-mcp-auth-proxy:dev .
|
docker build -t gitea-mcp-auth-proxy:dev .
|
||||||
docker run --rm -p 8080:8080 \
|
docker run --rm -p 8080:8080 \
|
||||||
-e AUTH_PROXY_TOKENS="my-token-1,my-token-2" \
|
-e AUTH_PROXY_TOKEN_HASHES="<sha256-hash-1>,<sha256-hash-2>" \
|
||||||
gitea-mcp-auth-proxy:dev
|
gitea-mcp-auth-proxy:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -96,12 +140,15 @@ Referenzbeispiele fuer lokale Verifikation liegen unter:
|
|||||||
|
|
||||||
- `deploy/k3s/deployment.yaml`
|
- `deploy/k3s/deployment.yaml`
|
||||||
- `deploy/k3s/service.yaml`
|
- `deploy/k3s/service.yaml`
|
||||||
|
- `deploy/k3s/networkpolicy.yaml`
|
||||||
|
|
||||||
Diese Manifeste sind bewusst minimal und nicht als produktive FluxCD-Quelle gedacht.
|
Diese Manifeste sind bewusst minimal und nicht als produktive FluxCD-Quelle gedacht.
|
||||||
|
|
||||||
## Annahmen
|
## Annahmen
|
||||||
|
|
||||||
- Annahme: Go-Version ist `1.24` (aktuelle stabile Version zum Implementierungszeitpunkt muss ggf. angepasst werden).
|
- Annahme: Go-Version ist `1.26`.
|
||||||
- Annahme: Release-Build pusht initial nur `linux/amd64`.
|
- Annahme: Release-Build pusht initial nur `linux/amd64`.
|
||||||
- Annahme: Remote BuildKit ist im Runner-Netz unter `tcp://<default-gateway>:1234` erreichbar.
|
- Annahme: Remote BuildKit ist im Runner-Netz unter `tcp://<default-gateway>:1234` erreichbar.
|
||||||
- Annahme: Registry-Pfad ist `gitea.nehmer.net/torben/gitea-mcp-auth-proxy`.
|
- Annahme: Registry-Pfad ist `gitea.nehmer.net/torben/gitea-mcp-auth-proxy`.
|
||||||
|
- Annahme: Traefik laeuft in `kube-system` mit Label `app.kubernetes.io/name=traefik`.
|
||||||
|
- Annahme: Prometheus laeuft in `monitoring` mit Label `app.kubernetes.io/name=prometheus`.
|
||||||
@@ -4,6 +4,8 @@ metadata:
|
|||||||
name: gitea-mcp-auth-proxy
|
name: gitea-mcp-auth-proxy
|
||||||
labels:
|
labels:
|
||||||
app: gitea-mcp-auth-proxy
|
app: gitea-mcp-auth-proxy
|
||||||
|
annotations:
|
||||||
|
security.note/healthz: "Do not expose /healthz publicly; keep service internal and restrict ingress at network/ingress layer."
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
@@ -24,7 +26,7 @@ spec:
|
|||||||
env:
|
env:
|
||||||
- name: AUTH_PROXY_LISTEN_ADDR
|
- name: AUTH_PROXY_LISTEN_ADDR
|
||||||
value: ":8080"
|
value: ":8080"
|
||||||
- name: AUTH_PROXY_TOKENS_DIR
|
- name: AUTH_PROXY_TOKEN_HASHES_DIR
|
||||||
value: /var/run/secrets/auth-proxy
|
value: /var/run/secrets/auth-proxy
|
||||||
- name: AUTH_PROXY_LOG_LEVEL
|
- name: AUTH_PROXY_LOG_LEVEL
|
||||||
value: info
|
value: info
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: gitea-mcp-auth-proxy-ingress
|
||||||
|
labels:
|
||||||
|
app: gitea-mcp-auth-proxy
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: gitea-mcp-auth-proxy
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
# Assumption: Traefik Pods run in namespace kube-system and expose label app.kubernetes.io/name=traefik.
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik
|
||||||
|
# Assumption: Prometheus Pods run in namespace monitoring and expose label app.kubernetes.io/name=prometheus.
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: monitoring
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: prometheus
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8080
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
module gitea-mcp-forward-auth
|
module gitea.nehmer.net/torben/gitea-mcp-forward-auth
|
||||||
|
|
||||||
go 1.24
|
go 1.26
|
||||||
|
|||||||
+65
-38
@@ -3,6 +3,7 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,32 +11,36 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const EnvTokens = "AUTH_PROXY_TOKENS"
|
const EnvTokenHashes = "AUTH_PROXY_TOKEN_HASHES"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMissingAuthHeader = errors.New("missing authorization header")
|
ErrMissingAuthHeader = errors.New("missing authorization header")
|
||||||
ErrInvalidAuthHeader = errors.New("invalid authorization header format")
|
ErrInvalidAuthHeader = errors.New("invalid authorization header format")
|
||||||
ErrEmptyTokenSet = errors.New("no tokens configured")
|
ErrEmptyTokenSet = errors.New("no token hashes configured")
|
||||||
ErrTokenDirUnreadable = errors.New("unable to read token directory")
|
ErrTokenDirUnreadable = errors.New("unable to read token hash directory")
|
||||||
|
ErrInvalidTokenHash = errors.New("invalid token hash format")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
tokenDigests [][sha256.Size]byte
|
tokenDigests [][sha256.Size]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewValidator(tokens []string) (*Validator, error) {
|
func NewValidator(tokenHashes []string) (*Validator, error) {
|
||||||
if len(tokens) == 0 {
|
if len(tokenHashes) == 0 {
|
||||||
return nil, ErrEmptyTokenSet
|
return nil, ErrEmptyTokenSet
|
||||||
}
|
}
|
||||||
|
|
||||||
unique := make(map[[sha256.Size]byte]struct{}, len(tokens))
|
unique := make(map[[sha256.Size]byte]struct{}, len(tokenHashes))
|
||||||
digests := make([][sha256.Size]byte, 0, len(tokens))
|
digests := make([][sha256.Size]byte, 0, len(tokenHashes))
|
||||||
for _, token := range tokens {
|
for _, hashValue := range tokenHashes {
|
||||||
t := strings.TrimSpace(token)
|
hashValue = normalizeHash(hashValue)
|
||||||
if t == "" {
|
if hashValue == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
digest := sha256.Sum256([]byte(t))
|
digest, err := decodeHash(hashValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if _, exists := unique[digest]; exists {
|
if _, exists := unique[digest]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -86,21 +91,21 @@ func ParseBearerToken(authHeader string) (string, error) {
|
|||||||
return parts[1], nil
|
return parts[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadTokens(tokensDir, tokensCSV string) ([]string, error) {
|
func LoadTokenHashes(hashesDir, hashesCSV string) ([]string, error) {
|
||||||
tokens := make([]string, 0)
|
hashes := make([]string, 0)
|
||||||
|
|
||||||
dirTokens, err := readTokensFromDir(tokensDir)
|
dirHashes, err := readHashesFromDir(hashesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tokens = append(tokens, dirTokens...)
|
hashes = append(hashes, dirHashes...)
|
||||||
tokens = append(tokens, parseTokensCSV(tokensCSV)...)
|
hashes = append(hashes, parseHashesCSV(hashesCSV)...)
|
||||||
|
|
||||||
cleaned := make([]string, 0, len(tokens))
|
cleaned := make([]string, 0, len(hashes))
|
||||||
for _, token := range tokens {
|
for _, hashValue := range hashes {
|
||||||
t := strings.TrimSpace(token)
|
h := normalizeHash(hashValue)
|
||||||
if t != "" {
|
if h != "" {
|
||||||
cleaned = append(cleaned, t)
|
cleaned = append(cleaned, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,45 +116,67 @@ func LoadTokens(tokensDir, tokensCSV string) ([]string, error) {
|
|||||||
return cleaned, nil
|
return cleaned, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readTokensFromDir(tokensDir string) ([]string, error) {
|
func readHashesFromDir(hashesDir string) ([]string, error) {
|
||||||
if strings.TrimSpace(tokensDir) == "" {
|
if strings.TrimSpace(hashesDir) == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, err := os.ReadDir(tokensDir)
|
entries, err := os.ReadDir(hashesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w %q: %v", ErrTokenDirUnreadable, tokensDir, err)
|
return nil, fmt.Errorf("%w %q: %v", ErrTokenDirUnreadable, hashesDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens := make([]string, 0, len(entries))
|
hashes := make([]string, 0, len(entries))
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
path := filepath.Join(tokensDir, entry.Name())
|
path := filepath.Join(hashesDir, entry.Name())
|
||||||
content, err := os.ReadFile(path)
|
content, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read token file %q: %w", path, err)
|
return nil, fmt.Errorf("read hash file %q: %w", path, err)
|
||||||
}
|
}
|
||||||
tokens = append(tokens, strings.TrimSpace(string(content)))
|
hashes = append(hashes, strings.TrimSpace(string(content)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokens, nil
|
return hashes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTokensCSV(tokensCSV string) []string {
|
func parseHashesCSV(hashesCSV string) []string {
|
||||||
if strings.TrimSpace(tokensCSV) == "" {
|
if strings.TrimSpace(hashesCSV) == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
parts := strings.Split(tokensCSV, ",")
|
parts := strings.Split(hashesCSV, ",")
|
||||||
tokens := make([]string, 0, len(parts))
|
hashes := make([]string, 0, len(parts))
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
t := strings.TrimSpace(part)
|
h := normalizeHash(part)
|
||||||
if t != "" {
|
if h != "" {
|
||||||
tokens = append(tokens, t)
|
hashes = append(hashes, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tokens
|
return hashes
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeHash(value string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if strings.HasPrefix(strings.ToLower(value), "sha256:") {
|
||||||
|
value = strings.TrimSpace(value[len("sha256:"):])
|
||||||
|
}
|
||||||
|
return strings.ToLower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHash(value string) ([sha256.Size]byte, error) {
|
||||||
|
var digest [sha256.Size]byte
|
||||||
|
if len(value) != sha256.Size*2 {
|
||||||
|
return digest, fmt.Errorf("%w: expected %d hex chars, got %d", ErrInvalidTokenHash, sha256.Size*2, len(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := hex.DecodeString(value)
|
||||||
|
if err != nil {
|
||||||
|
return digest, fmt.Errorf("%w: %v", ErrInvalidTokenHash, err)
|
||||||
|
}
|
||||||
|
copy(digest[:], decoded)
|
||||||
|
return digest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TokenFingerprint(token string) string {
|
func TokenFingerprint(token string) string {
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hashHex(token string) string {
|
||||||
|
sum := sha256.Sum256([]byte(token))
|
||||||
|
return fmt.Sprintf("%x", sum[:])
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidator_IsValid(t *testing.T) {
|
func TestValidator_IsValid(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
validator, err := NewValidator([]string{"token-one", "token-two", "token-three"})
|
validator, err := NewValidator([]string{hashHex("token-one"), hashHex("token-two"), hashHex("token-three")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewValidator() error = %v", err)
|
t.Fatalf("NewValidator() error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -45,6 +54,14 @@ func TestNewValidator_EmptyTokenSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewValidator_InvalidHash(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if _, err := NewValidator([]string{"not-a-hash"}); err == nil {
|
||||||
|
t.Fatal("NewValidator(invalid hash) expected error, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseBearerToken(t *testing.T) {
|
func TestParseBearerToken(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user