package cmd import ( "context" "database/sql" "fmt" "net" "strings" "time" v1 "gitea.badhouseplants.net/softplayer/softplayer-backend/api/v1" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1" tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1" grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector" "github.com/redis/go-redis/v9" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) type Server struct { // Service related Port int64 `short:"p" env:"SOFTPLAYER_PORT" default:"4020"` Host string `env:"SOFTPLAYER_HOST" default:"0.0.0.0"` HashCost int16 `env:"SOFTPLAYER_HASH_COST" default:"1"` // SMTP Config SMTPHost string `env:"SOFTPLAYER_SMTP_HOST"` SMTPPort string `env:"SOFTPLAYER_SMTP_PORT" default:"587"` SMTPFrom string `env:"SOFTPLAYER_SMTP_FROM" default:"overlord@badhouseplants.net"` SMTPPassword string `env:"SOFTPLAYER_SMTP_PASSWORD"` // Database and redis DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"` RedisHost string `env:"SOFTPLAYER_REDIS_HOST"` // JWT parameters RefrestTokenTTL time.Duration `default:"8h"` AccessTokenTTL time.Duration `default:"15m"` JWTSecret string `default:"qwertyu9"` // Dev and logging Reflection bool `env:"SOFTPLAYER_REFLECTION" default:"false"` DevMode bool `env:"SOFTPLAYER_DEV_MODE" default:"false"` } // Run the grpc backend server func (cmd *Server) Run(ctx context.Context) error { // Make sure the download dir exists log := logger.FromContext(ctx) log.Info("Opening a database connection") db, err := sql.Open("postgres", cmd.DBConnectionString) if err != nil { log.Error(err, "Couldn't start a database driver") return err } address := fmt.Sprintf("%s:%d", cmd.Host, cmd.Port) lis, err := net.Listen("tcp", address) if err != nil { return err } rdb := redis.NewClient(&redis.Options{ Addr: cmd.RedisHost, }) authController := controllers.NewAuthController( []byte(cmd.JWTSecret), cmd.AccessTokenTTL, cmd.RefrestTokenTTL, rdb, ) grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( grpc_zap.UnaryServerInterceptor(logger.SetupLogger("info")), // jwtVerifier.JWTAuthInterceptor, selector.UnaryServerInterceptor( auth.UnaryServerInterceptor(authController.AuthInterceptorFN), selector.MatchFunc(selectorRequireAuth), ), ), grpc.ChainStreamInterceptor( grpc_zap.StreamServerInterceptor(logger.SetupLogger("info")), selector.StreamServerInterceptor( auth.StreamServerInterceptor(authController.AuthInterceptorFN), selector.MatchFunc(selectorRequireAuth), ), ), ) if cmd.Reflection { reflection.Register(grpcServer) } tokenCtrl := &controllers.TokenController{ DB: db, HashCost: cmd.HashCost, Redis: rdb, } accountCtrl := &controllers.AccountController{ HashCost: cmd.HashCost, DB: db, DevMode: cmd.DevMode, RefreshTokenTTL: cmd.RefrestTokenTTL, AccessTokenTTL: cmd.AccessTokenTTL, JWTSecret: []byte(cmd.JWTSecret), Redis: rdb, } // Services that should be accessible for tokens should go here accounts.RegisterAccountsServiceServer(grpcServer, v1.NewAccountServer(accountCtrl, authController)) test.RegisterTestServiceServer(grpcServer, v1.NewTestServer()) test.RegisterPublicTestServiceServer(grpcServer, v1.NewPublicTestServer()) tokens.RegisterTokensServiceServer(grpcServer, v1.NewTokensServer(tokenCtrl, authController)) tokens.RegisterPublicTokensServiceServer(grpcServer, v1.NewPublicTokensServer(tokenCtrl, authController)) accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authController)) info := grpcServer.GetServiceInfo() tokenCtrl.SetGRPCInfo(info) tokenCtrl.SetRules() if err := grpcServer.Serve(lis); err != nil { return err } return nil } // Parse the service name and check whether it's public or not func selectorRequireAuth(ctx context.Context, callMeta interceptors.CallMeta) bool { serviceParts := strings.Split(callMeta.Service, ".") if len(serviceParts) == 0 { return false } serviceName := serviceParts[len(serviceParts)-1] fmt.Println(serviceName) if strings.HasPrefix(serviceName, "Public") { return false } if strings.Contains(serviceName, "ServerReflection") { return false } if strings.Contains(callMeta.Method, "AuthenticateWithToken") { return false } return true }