Initial commit of Descrybe v2 without local scratch artifacts.

Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
package auth
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
var ErrInvalidAPIKey = errors.New("invalid api key")
// APIKeyIdentity is the tenant binding resolved from a valid API key.
type APIKeyIdentity struct {
KeyID uuid.UUID
CompanyID uuid.UUID
UserID uuid.UUID
MembershipRole string // active membership role for the key owner (admin|member)
}
// HashAPIKey returns a SHA-256 hex digest for O(1) api_keys.key_hash lookup.
// Matches hashes written by dashboard key creation. Also used for invite tokens at rest.
func HashAPIKey(raw string) string {
sum := sha256.Sum256([]byte(raw))
return hex.EncodeToString(sum[:])
}
// AuthenticateAPIKey looks up a non-revoked key by hash and updates last_used_at.
// Keys owned by inactive users or without an active company membership are rejected.
// MembershipRole is returned so HTTP middleware can withhold company-admin powers
// when the owner is no longer an admin (keys are admin-created; scopes/expiry columns
// do not exist yet — empty/full privilege remains the default for admin-owned keys).
func (s *Service) AuthenticateAPIKey(ctx context.Context, raw string) (APIKeyIdentity, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return APIKeyIdentity{}, ErrInvalidAPIKey
}
hash := HashAPIKey(raw)
var id APIKeyIdentity
var role string
err := s.Pool.QueryRow(ctx, `
SELECT k.id, k.company_id, k.user_id, m.role
FROM api_keys k
INNER JOIN users u ON u.id = k.user_id AND u.is_active = true
INNER JOIN memberships m ON m.user_id = k.user_id
AND m.company_id = k.company_id
AND m.status = 'active'
WHERE k.key_hash = $1 AND k.revoked_at IS NULL`, hash).
Scan(&id.KeyID, &id.CompanyID, &id.UserID, &role)
if errors.Is(err, pgx.ErrNoRows) {
return APIKeyIdentity{}, ErrInvalidAPIKey
}
if err != nil {
return APIKeyIdentity{}, err
}
id.MembershipRole = NormalizeMembershipRole(role)
_, _ = s.Pool.Exec(ctx, `
UPDATE api_keys SET last_used_at = now(), updated_at = now() WHERE id = $1`, id.KeyID)
return id, nil
}