package server import ( "context" "fmt" "net/http" "net/http/httptest" "strings" "testing" "time" "git.apinb.com/bsm-sdk/core/errcode" "github.com/golang-jwt/jwt/v5" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) const testAuthorizationKey = "0123456789abcdef0123456789abcdef" func TestHTTPAuthorization(t *testing.T) { auth, err := newAuthorization(testAuthorizationKey, 3600, []string{"/passport.Login/Pwd", "/rest/fts/ping"}) if err != nil { t.Fatal(err) } next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) }) handler := auth.httpMiddleware(next) tests := []struct { name string path string token string want int code int32 }{ {name: "grpc gateway anonymous", path: "/passport.Login/Pwd", want: http.StatusNoContent}, {name: "dynamic rpc anonymous", path: "/rpc/passport/Login/Pwd", want: http.StatusNoContent}, {name: "rest anonymous", path: "/rest/fts/ping", want: http.StatusNoContent}, {name: "missing token", path: "/passport.Account/Get", want: http.StatusOK, code: int32(status.Code(errcode.ErrHeaderAuthorization))}, {name: "valid raw token", path: "/passport.Account/Get", token: signedToken(t, time.Now(), time.Now().Add(time.Hour)), want: http.StatusNoContent}, {name: "bearer rejected", path: "/passport.Account/Get", token: "Bearer " + signedToken(t, time.Now(), time.Now().Add(time.Hour)), want: http.StatusOK, code: int32(status.Code(errcode.ErrTokenAuthParseFail))}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { request := httptest.NewRequest(http.MethodPost, test.path, nil) if test.token != "" { request.Header.Set("Authorization", test.token) } response := httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != test.want { t.Fatalf("status=%d body=%s", response.Code, response.Body.String()) } if test.want == http.StatusOK && !strings.Contains(response.Body.String(), `"code":`+fmt.Sprint(test.code)) { t.Fatalf("unexpected authorization error: %s", response.Body.String()) } }) } } func TestAuthorizationRejectsTokenOlderThanConfiguredLifetime(t *testing.T) { auth, err := newAuthorization(testAuthorizationKey, 60, nil) if err != nil { t.Fatal(err) } if err := auth.validate(signedToken(t, time.Now().Add(-2*time.Minute), time.Now().Add(time.Hour))); err == nil { t.Fatal("expected token older than configured lifetime to be rejected") } } func TestGRPCAuthorization(t *testing.T) { auth, err := newAuthorization(testAuthorizationKey, 3600, []string{"/passport.Login/Pwd"}) if err != nil { t.Fatal(err) } handler := func(context.Context, any) (any, error) { return "ok", nil } if _, err := auth.unaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Login/Pwd"}, handler); err != nil { t.Fatalf("anonymous method failed: %v", err) } if _, err := auth.unaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Account/Get"}, handler); status.Code(err) != status.Code(errcode.ErrHeaderAuthorization) { t.Fatalf("expected unauthenticated, got %v", err) } ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", signedToken(t, time.Now(), time.Now().Add(time.Hour)))) if _, err := auth.unaryInterceptor(ctx, nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Account/Get"}, handler); err != nil { t.Fatalf("valid token failed: %v", err) } } func signedToken(t *testing.T, issuedAt, expiresAt time.Time) string { t.Helper() claims := jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(issuedAt), NotBefore: jwt.NewNumericDate(issuedAt), ExpiresAt: jwt.NewNumericDate(expiresAt), } value, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(testAuthorizationKey)) if err != nil { t.Fatal(err) } return value }