package remoteapi import ( "context" "database/sql" "errors" "fmt" "log/slog" "net" "strings" remotesapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/remotesapi/v1alpha1" "github.com/vaughan0/go-ini" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "go.bigb.es/auxilia/scribe" "sourcecraft.dev/bigbes/sr-ht-core/config" "sourcecraft.dev/bigbes/sr-ht-core/database" "sourcecraft.dev/bigbes/sr-ht-dolt/authn" ) // credService implements remotesapi.CredentialsServiceServer.WhoAmI. `dolt // login` polls WhoAmI with the user's keypair Bearer JWT until the key is // associated with a SourceHut account through the web UI; once association // happens, the JWT verifies and WhoAmI returns the account identity, which the // CLI prints as confirmation. type credService struct { remotesapi.UnimplementedCredentialsServiceServer conf ini.File service string expectedAud string pool *sql.DB keys authn.KeyStore logger *slog.Logger } // WhoAmI verifies the request's Bearer keypair JWT (exactly as the remotesapi // interceptors do) and returns the owning SourceHut user's identity. It is // keypair-only: a missing or non-Bearer authorization header, or an invalid // token, is Unauthenticated — the state the CLI polls through until the web UI // associates the key. A transient backend failure is Unavailable. func (c *credService) WhoAmI(ctx context.Context, _ *remotesapi.WhoAmIRequest) (*remotesapi.WhoAmIResponse, error) { ctx = database.Context(config.Context(ctx, c.conf, c.service), c.pool) token, err := bearerToken(ctx) if err != nil { return nil, err } ac, err := authn.ResolveDoltJWT(ctx, token, c.expectedAud, c.keys) if err != nil { if errors.Is(err, authn.ErrInvalidToken) { return nil, status.Error(codes.Unauthenticated, "invalid or expired credentials") } c.logger.ErrorContext(ctx, "WhoAmI backend error", scribe.Err(err)) return nil, status.Error(codes.Unavailable, "authentication temporarily unavailable") } // The meta mirror carries no separate display name, so we surface the // username for DisplayName; EmailAddress comes from the mirrored profile. return &remotesapi.WhoAmIResponse{ Username: ac.Username, DisplayName: ac.Username, EmailAddress: ac.Email, }, nil } // bearerToken extracts the raw JWT from an incoming "authorization: Bearer " // metadata header. A missing header or non-Bearer scheme is Unauthenticated. func bearerToken(ctx context.Context) (string, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return "", status.Error(codes.Unauthenticated, "missing credentials") } vals := md.Get("authorization") if len(vals) == 0 || vals[0] == "" { return "", status.Error(codes.Unauthenticated, "missing credentials") } scheme, value, found := strings.Cut(vals[0], " ") if !found || !strings.EqualFold(scheme, "bearer") || value == "" { return "", status.Error(codes.Unauthenticated, "expected a Bearer keypair token") } return value, nil } // CredServer is the small standalone gRPC server hosting CredentialsService on // its own port (nginx path-routes it separately from the chunk-store server). type CredServer struct { grpc *grpc.Server addr string logger *slog.Logger } // NewCredServer assembles the CredentialsService server. It shares the keystore // derivation and audience normalization with the chunk-store server so both // verify keypair JWTs identically. func NewCredServer(cfg Config) (*CredServer, error) { if cfg.DB == nil { return nil, fmt.Errorf("remoteapi: NewCredServer requires a non-nil DB") } if cfg.CredsListenAddr == "" { return nil, fmt.Errorf("remoteapi: NewCredServer requires a CredsListenAddr") } logger := slog.Default().With("component", "credentials") svc := &credService{ conf: cfg.Conf, service: serviceName, expectedAud: normalizeAud(cfg.HttpHost), pool: cfg.DB, keys: newKeyStore(cfg.DB), logger: logger, } gsrv := grpc.NewServer() remotesapi.RegisterCredentialsServiceServer(gsrv, svc) return &CredServer{grpc: gsrv, addr: cfg.CredsListenAddr, logger: logger}, nil } // Serve binds the listener and serves until GracefulStop. It blocks. It returns // an error if binding fails or the gRPC server exits with one. func (s *CredServer) Serve() error { lis, err := net.Listen("tcp", s.addr) if err != nil { return fmt.Errorf("remoteapi: bind credentials %q: %w", s.addr, err) } if err := s.grpc.Serve(lis); err != nil && !errors.Is(err, grpc.ErrServerStopped) { return fmt.Errorf("remoteapi: credentials serve: %w", err) } return nil } // GracefulStop stops the credentials server. func (s *CredServer) GracefulStop() { s.grpc.GracefulStop() }