package authn
import (
"context"
"encoding/base64"
"fmt"
"strings"
"sourcecraft.dev/bigbes/sr-ht-core/auth"
)
// ResolveGRPCAuth resolves the caller from a remotesapi gRPC "authorization"
// metadata header, dispatching on its scheme exactly as the dolt client sends
// it (see dolt's grpc_dial_provider):
//
// - "Basic <base64(user:pass)>" → ResolveBasic (meta personal access token);
// - "Bearer <jwt>" → ResolveDoltJWT (dolt keypair EdDSA JWT);
// - empty → (nil, nil): an anonymous request, which is
// valid for public clones.
//
// A malformed or unsupported header is a permanent rejection wrapping
// ErrInvalidToken. keys is only consulted for the Bearer path.
func ResolveGRPCAuth(ctx context.Context, authorizationHeader, expectedAud string, keys KeyStore) (*auth.AuthContext, error) {
if authorizationHeader == "" {
return nil, nil // anonymous
}
scheme, value, ok := strings.Cut(authorizationHeader, " ")
if !ok || value == "" {
return nil, fmt.Errorf("%w: malformed authorization header", ErrInvalidToken)
}
switch strings.ToLower(scheme) {
case "basic":
raw, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return nil, fmt.Errorf("%w: Basic credentials are not valid base64: %v", ErrInvalidToken, err)
}
username, password, ok := strings.Cut(string(raw), ":")
if !ok {
return nil, fmt.Errorf("%w: Basic credentials missing ':' separator", ErrInvalidToken)
}
return ResolveBasic(ctx, username, password)
case "bearer":
return ResolveDoltJWT(ctx, value, expectedAud, keys)
default:
return nil, fmt.Errorf("%w: unsupported authorization scheme %q", ErrInvalidToken, scheme)
}
}