Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
64 lines
2.1 KiB
Go
64 lines
2.1 KiB
Go
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
|
|
}
|