From ab3013609aa344704c3df1568205c8a80fb5c40d Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 13 Aug 2026 16:51:18 -0600 Subject: [PATCH] chore(auth/jwt): enforce Signer/Verifier interfaces internally --- auth/jwt/interfaces.go | 54 ++++++++++++++++++++++++++++++++++++++++++ auth/jwt/sign.go | 2 ++ auth/jwt/verify.go | 2 ++ 3 files changed, 58 insertions(+) create mode 100644 auth/jwt/interfaces.go diff --git a/auth/jwt/interfaces.go b/auth/jwt/interfaces.go new file mode 100644 index 0000000..03cc6fd --- /dev/null +++ b/auth/jwt/interfaces.go @@ -0,0 +1,54 @@ +// Copyright 2026 AJ ONeal (https://therootcompany.com) +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +package jwt + +// jwtsigner is the interface for signing JWTs. +// +// Implementations include [*Signer] and [*HMACSigner]. Use this interface when +// you want to accept any JWT signer (e.g., for testing with a mock, or for +// future algorithm variants). +type jwtsigner[T jwtverifier] interface { + // SignJWT signs a JWT in-place. Sets the alg header and computes + // the HMAC signature over the protected.payload input. + SignJWT(jws SignableJWT) error + + // SignRaw signs an arbitrary protected header and payload, returning + // the result as a [*jwt.RawJWT] suitable for json.Marshal (flattened + // JWS) or jwt.Encode (compact serialization). + // + // Unlike SignJWT, SignRaw does not set or validate the KID field — + // the caller controls it entirely. The alg field is always set from + // the signer's algorithm. If hdr already has a non-empty Alg that + // conflicts with the signer's algorithm, SignRaw returns an error. + // + // payload is the raw bytes to encode as the JWS payload. A nil + // payload produces an empty payload segment (used by ACME POST-as-GET). + SignRaw(hdr Header, payload []byte) (*RawJWT, error) + + // SignToString creates and signs a JWT from claims and returns the + // compact token string (header.payload.signature). + SignToString(claims Claims) (string, error) + + // HMACVerifier returns a new [*HMACVerifier] for verifying tokens signed + // by this signer. + Verifier() T +} + +// Verifier is the interface for verifying JWT signatures with HMAC. +// +// Implementations include [*HMACVerifier]. Use this interface when you want +// to accept any HMAC-based verifier (e.g., for testing with a mock). +type jwtverifier interface { + // Verify checks the signature of an already-decoded [*jwt.JWT]. + // + // Returns nil on success, a descriptive error on failure. Claim + // values (iss, aud, exp, etc.) are NOT checked — call + // jwt.Validator.Validate on the unmarshalled claims after verifying. + Verify(jws VerifiableJWT) error +} diff --git a/auth/jwt/sign.go b/auth/jwt/sign.go index 3da0fdb..22674b0 100644 --- a/auth/jwt/sign.go +++ b/auth/jwt/sign.go @@ -39,6 +39,8 @@ type Signer struct { signerIdx atomic.Uint64 } +var _ jwtsigner[*Verifier] = (*Signer)(nil) + // NewSigner creates a Signer from the provided signing keys. // // NewSigner normalises each key: diff --git a/auth/jwt/verify.go b/auth/jwt/verify.go index 99133dd..dc6c5e4 100644 --- a/auth/jwt/verify.go +++ b/auth/jwt/verify.go @@ -36,6 +36,8 @@ type Verifier struct { pubKeys []PublicKey } +var _ jwtverifier = (*Verifier)(nil) + // NewVerifier creates a Verifier with an explicit set of public keys. // // Multiple keys may share the same KID (e.g. during key rotation).