package auth import ( "errors" "time" "github.com/golang-jwt/jwt/v5" ) // AccessTokenTTL ist bewusst kurz gehalten (Session-Ablauf statt langlebiger // Tokens), passend zur "so vertrauenswuerdig wie noetig"-Produkt-DNA. const AccessTokenTTL = 30 * time.Minute var ErrInvalidToken = errors.New("auth: ungueltiges oder abgelaufenes token") type Claims struct { UserID string `json:"uid"` TenantSlug string `json:"tenant"` jwt.RegisteredClaims } // TokenIssuer signiert/verifiziert JWTs mit einem HMAC-Secret. Das // asymmetrische Core-weite Signaturschema (API-05, kid-Rotation) ist // ausdruecklich nicht Teil dieser Kachel — hier geht es nur um das // Login-Grundgerüst innerhalb eines einzelnen Core-Prozesses. type TokenIssuer struct { secret []byte } func NewTokenIssuer(secret string) *TokenIssuer { return &TokenIssuer{secret: []byte(secret)} } func (i *TokenIssuer) Issue(userID, tenantSlug string) (string, error) { now := time.Now() claims := Claims{ UserID: userID, TenantSlug: tenantSlug, RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(AccessTokenTTL)), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(i.secret) } // Verify prueft Signatur UND Ablauf (jwt.ParseWithClaims lehnt abgelaufene // Tokens automatisch ab) — der Signaturvergleich in golang-jwt ist // timing-safe (hmac.Equal). func (i *TokenIssuer) Verify(tokenString string) (*Claims, error) { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, ErrInvalidToken } return i.secret, nil }) if err != nil || !token.Valid { return nil, ErrInvalidToken } return claims, nil }