List possible services and filter unavailable
All checks were successful
ci/woodpecker/push/build Pipeline was successful

Signed-off-by: Nikolai Rodionov <allanger@badhouseplants.net>
This commit is contained in:
2026-05-14 12:26:03 +02:00
parent 327410f76e
commit e96ec08a86
6 changed files with 197 additions and 10 deletions

View File

@@ -4,6 +4,8 @@ import (
"context"
test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
)
func NewTestServer() *TestServer {
@@ -18,6 +20,15 @@ func (t *TestServer) Pong(ctx context.Context, in *test.PongRequest) (*test.Pong
return &test.PongResponse{}, nil
}
func (t *TestServer) PongStream(in *emptypb.Empty, stream grpc.ServerStreamingServer[test.PongStreamResponse]) error {
for i := 0; i < 10; i++ {
if err := stream.Send(&test.PongStreamResponse{Dummy: "test"}); err != nil {
return err
}
}
return nil
}
func NewPublicTestServer() *PublicTestServer {
return &PublicTestServer{}
}
@@ -29,3 +40,12 @@ type PublicTestServer struct {
func (t *PublicTestServer) Ping(ctx context.Context, in *test.PingRequest) (*test.PingResponse, error) {
return &test.PingResponse{}, nil
}
func (t *PublicTestServer) PingStream(in *emptypb.Empty, stream grpc.ServerStreamingServer[test.PingStreamResponse]) error {
for i := 0; i < 10; i++ {
if err := stream.Send(&test.PingStreamResponse{Dummy: "test"}); err != nil {
return err
}
}
return nil
}

View File

@@ -1,9 +1,100 @@
package v1
import (
"context"
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
// var _ tokens.TokensServiceServer = (*TokensServer)(nil)
type TokensServer struct {
tokens.UnimplementedTokensServiceServer
tokenCtrl *controllers.TokenController
authorizationCtrl *controllers.AuthController
}
func NewTokensServer(
tokenCtrl *controllers.TokenController,
authorizationCtrl *controllers.AuthController,
) *TokensServer {
return &TokensServer{
tokenCtrl: tokenCtrl,
authorizationCtrl: authorizationCtrl,
}
}
// CreateToken implements [v1.TokensServiceServer].
func (srv *TokensServer) CreateToken(ctx context.Context, in *tokens.CreateTokenRequest) (*tokens.CreateTokenResponse, error) {
claims, err := srv.authorizationCtrl.ClaimsFromContext(ctx)
if err != nil {
return nil, status.Error(codes.Aborted, "Context is invalid")
}
if claims.UserID == "" {
return nil, status.Error(codes.Aborted, "Context is invalid")
}
tokenData := &controllers.TokenData{
Name: in.TokenMetadata.GetName(),
UserID: claims.UserID,
Scopes: &controllers.Scopes{},
}
token, err := srv.tokenCtrl.Create(ctx, tokenData)
if err != nil {
return nil, err
}
return &tokens.CreateTokenResponse{
TokenUuid: &tokens.TokenUUID{},
TokenMetadata: &tokens.TokenMetadata{},
TokenPermissions: &tokens.TokenPermissions{},
TokenValue: &tokens.TokenValue{Token: token},
}, status.Error(codes.Unimplemented, "Method is not implemented")
}
// ForceTokenExpiration implements [v1.TokensServiceServer].
func (t *TokensServer) ForceTokenExpiration(context.Context, *tokens.ForceTokenExpirationRequest) (*emptypb.Empty, error) {
return nil, status.Error(codes.Unimplemented, "Method is not implemented")
}
// GetToken implements [v1.TokensServiceServer].
func (t *TokensServer) GetToken(context.Context, *tokens.GetTokenRequest) (*tokens.GetTokenResponse, error) {
return nil, status.Error(codes.Unimplemented, "Method is not implemented")
}
// ListTokens implements [v1.TokensServiceServer].
func (t *TokensServer) ListTokens(*emptypb.Empty, grpc.ServerStreamingServer[tokens.ListTokensResponse]) error {
return status.Error(codes.Unimplemented, "Method is not implemented")
}
// RegenerateToken implements [v1.TokensServiceServer].
func (t *TokensServer) RegenerateToken(context.Context, *tokens.RegenerateTokenRequest) (*tokens.RegenerateTokenResponse, error) {
return nil, status.Error(codes.Unimplemented, "Method is not implemented")
}
// UpdateToken implements [v1.TokensServiceServer].
func (t *TokensServer) UpdateToken(context.Context, *tokens.UpdateTokenRequest) (*tokens.UpdateTokenResponse, error) {
return nil, status.Error(codes.Unimplemented, "Method is not implemented")
}
// ListPermissions implements [v1.TokensServiceServer].
func (srv *TokensServer) ListPermissions(in *emptypb.Empty, stream grpc.ServerStreamingServer[tokens.ListPermissionsResponse]) error {
data := srv.tokenCtrl.ListPermissions(stream.Context())
for key, data := range data {
result := &tokens.ListPermissionsResponse{
Permissions: &tokens.Permissions{
AvailabiePermissions: map[string]*tokens.MethodList{
key: {Methods: data},
},
},
}
if err := stream.Send(result); err != nil {
return status.Error(codes.Aborted, "Couldn't send data to the client")
}
}
return nil
}

View File

@@ -13,6 +13,7 @@ import (
"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"
@@ -82,13 +83,24 @@ func (cmd *Server) Run(ctx context.Context) error {
selector.MatchFunc(selectorRequireAuth),
),
),
grpc.StreamInterceptor(grpc_zap.StreamServerInterceptor(logger.SetupLogger("info"))),
grpc.ChainStreamInterceptor(
grpc_zap.StreamServerInterceptor(logger.SetupLogger("info")),
selector.StreamServerInterceptor(
auth.StreamServerInterceptor(authInterceptor.AuthInterceptorFN),
selector.MatchFunc(selectorRequireAuth),
),
),
)
if cmd.Reflection {
reflection.Register(grpcServer)
}
tokenCtrl := &controllers.TokenController{
DB: db,
HashCost: cmd.HashCost,
}
accountCtrl := &controllers.AccountController{
HashCost: cmd.HashCost,
DB: db,
@@ -99,10 +111,17 @@ func (cmd *Server) Run(ctx context.Context) error {
Redis: rdb,
}
accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authInterceptor))
// Services that should be accessible for tokens should go here
accounts.RegisterAccountsServiceServer(grpcServer, v1.NewAccountServer(accountCtrl, authInterceptor))
test.RegisterTestServiceServer(grpcServer, v1.NewTestServer())
test.RegisterPublicTestServiceServer(grpcServer, v1.NewPublicTestServer())
tokens.RegisterTokensServiceServer(grpcServer, v1.NewTokensServer(tokenCtrl, authInterceptor))
accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authInterceptor))
info := grpcServer.GetServiceInfo()
tokenCtrl.SetGRPCInfo(info)
tokenCtrl.SetRules()
if err := grpcServer.Serve(lis); err != nil {
return err
}
@@ -116,8 +135,15 @@ func selectorRequireAuth(ctx context.Context, callMeta interceptors.CallMeta) bo
if len(serviceParts) == 0 {
return false
}
serviceName := serviceParts[len(serviceParts)-1]
fmt.Println(serviceName)
return !strings.HasPrefix(serviceName, "Public")
if strings.HasPrefix(serviceName, "Public") {
return false
}
if strings.Contains(serviceName, "ServerReflection") {
return false
}
return true
}

2
go.mod
View File

@@ -42,7 +42,7 @@ require (
)
require (
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260511094640-0f4959475dc9
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260514095622-3ce39b865e5a
github.com/golang/protobuf v1.5.4
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.40.0 // indirect

4
go.sum
View File

@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260511094640-0f4959475dc9 h1:gVB4z3qZXriL8xfJnY8hEqiBTGHGeeWO5E3GY/7aNRM=
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260511094640-0f4959475dc9/go.mod h1:AgOh1lkPHyRgBf3/s1btKcAqke/33LbKYarTD13qeAg=
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260514095622-3ce39b865e5a h1:F21MJw0xsiZf3cj4D+n8JPqkX38XlY+xFju2gQkC9eA=
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260514095622-3ce39b865e5a/go.mod h1:AgOh1lkPHyRgBf3/s1btKcAqke/33LbKYarTD13qeAg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

View File

@@ -4,19 +4,26 @@ import (
"context"
"database/sql"
"errors"
"regexp"
"time"
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/hash"
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger"
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/token"
"github.com/google/uuid"
"google.golang.org/grpc"
)
type TokenController struct {
DB *sql.DB
HashCost int16
DB *sql.DB
HashCost int16
ServiceInfo map[string]grpc.ServiceInfo
rules []rule
}
// Services that are not available for tokens
var DisabledServicesRegex = []string{".*Accounts.*", ".*Tokens.*"}
// Errors
var (
ErrServerError = errors.New("internal server error")
@@ -35,6 +42,20 @@ type TokenData struct {
type Scopes struct{}
// Set the grpc info, must happen after all the service are initialized
func (ctrl *TokenController) SetGRPCInfo(info map[string]grpc.ServiceInfo) {
ctrl.ServiceInfo = info
}
func (ctrl *TokenController) SetRules() {
rules := []rule{
{re: regexp.MustCompile(`.*Tokens.*`)},
{re: regexp.MustCompile(`.*Accounts.*`)},
{re: regexp.MustCompile(`.*Reflection.*`)},
}
ctrl.rules = rules
}
// Create a new token, store its hash in the database and return the token value
func (ctrl *TokenController) Create(ctx context.Context, data *TokenData) (string, error) {
id := uuid.NewString()
@@ -86,3 +107,32 @@ func (ctrl *TokenController) Get(ctx context.Context, uuid string) error {
func (ctrl *TokenController) List(ctx context.Context) error {
return nil
}
// Lis all available permissions
func (ctrl *TokenController) ListPermissions(ctx context.Context) (result map[string][]string) {
result = map[string][]string{}
for key, val := range ctrl.ServiceInfo {
if shouldSkip(key, ctrl.rules) {
continue
}
var services []string
for _, svc := range val.Methods {
services = append(services, svc.Name)
}
result[key] = services
}
return
}
type rule struct {
re *regexp.Regexp
}
func shouldSkip(s string, rules []rule) bool {
for _, r := range rules {
if r.re.MatchString(s) {
return true
}
}
return false
}