Remove internal notification caller tokens

This commit is contained in:
Stephan D committed 2026-09-12 23:59:30 +02:00
1 parent 1fe173397f
commit caad7a3d5b
27 files changed
+112 -165

No files matched your search

+3 -35
View File
@@ -2,21 +2,15 @@ package transport
import (
"context"
"crypto/sha256"
"crypto/subtle"
"errors"
"strings"
pb "git.sendico.io/tech/sxchnge/notifications/interface/notifications/v1"
"git.sendico.io/tech/sxchnge/notifications/model"
merrors "git.sendico.io/tech/sxchnge/shared/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type callerKey struct{}
type API interface {
Send(context.Context, string, model.Request) (*model.Notification, error)
Get(context.Context, string, string) (*model.Notification, error)
@@ -26,14 +20,14 @@ type Server struct {
service API
}
// The private endpoint uses the existing product storage namespace without caller authentication.
func New(service API) *Server { return &Server{service: service} }
func (s *Server) Send(ctx context.Context, r *pb.SendRequest) (*pb.SendResponse, error) {
input, err := request(r)
if err != nil {
return nil, rpcError(err)
}
caller, _ := ctx.Value(callerKey{}).(string)
n, err := s.service.Send(ctx, caller, input)
n, err := s.service.Send(ctx, "product", input)
if err != nil {
return nil, rpcError(err)
}
@@ -43,39 +37,13 @@ func (s *Server) Get(ctx context.Context, r *pb.GetRequest) (*pb.GetResponse, er
if r == nil {
return nil, status.Error(codes.InvalidArgument, "missing request")
}
caller, _ := ctx.Value(callerKey{}).(string)
n, err := s.service.Get(ctx, caller, r.Id)
n, err := s.service.Get(ctx, "product", r.Id)
if err != nil {
return nil, rpcError(err)
}
return &pb.GetResponse{Notification: response(n)}, nil
}
// Tokens are mapped to stable server-configured caller identities, never supplied IDs.
func Authenticate(tokens map[string]string) grpc.UnaryServerInterceptor {
hashes := map[string][32]byte{}
for caller, token := range tokens {
hashes[caller] = sha256.Sum256([]byte(token))
}
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
md, _ := metadata.FromIncomingContext(ctx)
values := md.Get("authorization")
if len(values) != 1 || !strings.HasPrefix(values[0], "Bearer ") {
return nil, status.Error(codes.Unauthenticated, "invalid service credentials")
}
hash := sha256.Sum256([]byte(strings.TrimPrefix(values[0], "Bearer ")))
identity := ""
for caller, expected := range hashes {
if subtle.ConstantTimeCompare(hash[:], expected[:]) == 1 {
identity = caller
}
}
if identity == "" {
return nil, status.Error(codes.Unauthenticated, "invalid service credentials")
}
return handler(context.WithValue(ctx, callerKey{}, identity), req)
}
}
func rpcError(err error) error {
if errors.Is(err, context.Canceled) {
return status.Error(codes.Canceled, "notification operation canceled")
@@ -15,7 +15,6 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/grpc/test/bufconn"
)
@@ -31,10 +30,9 @@ func (fakeAPI) Send(_ context.Context, caller string, r model.Request) (*model.N
func (fakeAPI) Get(context.Context, string, string) (*model.Notification, error) {
return nil, merrors.NoData("private details")
}
func TestGRPCAuthenticationAndMapping(t *testing.T) {
func TestGRPCWithoutAuthenticationAndMapping(t *testing.T) {
listener := bufconn.Listen(1 << 20)
token := strings.Repeat("a", 32)
server := grpc.NewServer(grpc.UnaryInterceptor(Authenticate(map[string]string{"product": token})))
server := grpc.NewServer()
pb.RegisterNotificationsServiceServer(server, New(fakeAPI{}))
go func() { _ = server.Serve(listener) }()
defer server.Stop()
@@ -45,10 +43,7 @@ func TestGRPCAuthenticationAndMapping(t *testing.T) {
defer conn.Close()
client := pb.NewNotificationsServiceClient(conn)
req := &pb.SendRequest{UserId: "u", Targets: []*pb.Target{{Destination: &pb.Target_Email{Email: &pb.EmailTarget{Address: "user@example.com"}}}}}
if _, err = client.Send(context.Background(), req); status.Code(err) != codes.Unauthenticated {
t.Fatal(err)
}
ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("authorization", "Bearer "+token))
ctx := context.Background()
result, err := client.Send(ctx, req)
if err != nil {
t.Fatal(err)