package authn import ( "context" "encoding/base64" "fmt" "strings" "git.sr.ht/~sircmpwn/core-go/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 " → ResolveBasic (meta personal access token); // - "Bearer " → 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) } }