| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
- package grpcserver
- import (
- "context"
- "crypto/subtle"
- "fmt"
- "strings"
- "git3.techno-world.net/lrosales/broad-announce/internal/pipeline"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/metadata"
- "google.golang.org/grpc/status"
- )
- // authenticate validates the API key from gRPC metadata and returns the
- // corresponding SourceConfig. It is the gRPC equivalent of the HTTP
- // X-BA-Signature HMAC check.
- //
- // Metadata key: "authorization" → "Bearer <company_id>:<source_id>:<secret>"
- //
- // Returns codes.Unauthenticated on any failure.
- func authenticate(ctx context.Context, sources map[string]pipeline.SourceConfig) (*pipeline.SourceConfig, error) {
- md, ok := metadata.FromIncomingContext(ctx)
- if !ok {
- return nil, status.Error(codes.Unauthenticated, "missing metadata")
- }
- // Support both canonical "authorization" and " Authorization".
- authVals := md.Get("authorization")
- if len(authVals) == 0 {
- authVals = md.Get("Authorization")
- }
- if len(authVals) == 0 || authVals[0] == "" {
- return nil, status.Error(codes.Unauthenticated, "missing authorization header")
- }
- raw := strings.TrimPrefix(authVals[0], "Bearer ")
- if raw == authVals[0] {
- return nil, status.Error(codes.Unauthenticated, "authorization must use Bearer scheme")
- }
- key := parseAPIKey(raw)
- if key == nil {
- return nil, status.Error(codes.Unauthenticated, "malformed API key")
- }
- src, ok := sources[key.SourceKey()]
- if !ok {
- return nil, status.Error(codes.Unauthenticated,
- fmt.Sprintf("no such source %s/%s", key.CompanyID, key.SourceID))
- }
- // Verify the secret. The sources map stores the full raw API key as the
- // secret for env-based auth. Constant-time compare to avoid timing leaks.
- want := key.CompanyID + ":" + key.SourceID + ":" + string(src.HMACSecret)
- if subtle.ConstantTimeCompare([]byte(raw), []byte(want)) != 1 {
- return nil, status.Error(codes.Unauthenticated, "invalid API key")
- }
- return &src, nil
- }
- // apiKey represents a parsed API key in "<company_id>:<source_id>:<secret>" form.
- type apiKey struct {
- CompanyID string
- SourceID string
- Secret string
- }
- func parseAPIKey(raw string) *apiKey {
- parts := strings.SplitN(raw, ":", 3)
- if len(parts) != 3 {
- return nil
- }
- return &apiKey{CompanyID: parts[0], SourceID: parts[1], Secret: parts[2]}
- }
- func (k *apiKey) SourceKey() string { return k.CompanyID + ":" + k.SourceID }
|