package v1 import ( "context" "errors" "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" "google.golang.org/protobuf/types/known/timestamppb" ) // 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 := controllers.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") } if in.TokenPermissions == nil { return nil, status.Error(codes.InvalidArgument, "Permissions must be set") } permissions := map[string][]string{} for service, methods := range in.TokenPermissions.Permissions { permissions[service] = methods.GetMethods() } tokenData := &controllers.TokenData{ Name: in.TokenMetadata.GetName(), UserID: claims.UserID, ExpiresAt: in.TokenMetadata.ExpiresAt.AsTime(), Scopes: permissions, } token, tokenID, err := srv.tokenCtrl.Create(ctx, tokenData) if err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't create a token") } return &tokens.CreateTokenResponse{ TokenUuid: &tokens.TokenUUID{ Uuid: tokenID, }, TokenValue: &tokens.TokenValue{Token: token}, }, nil } // ForceTokenExpiration implements [v1.TokensServiceServer]. func (srv *TokensServer) ForceTokenExpiration(ctx context.Context, in *tokens.ForceTokenExpirationRequest) (*emptypb.Empty, error) { claims, err := controllers.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") } if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "User is now allowed to manipulate this token") } if err := srv.tokenCtrl.ForceExpiration(ctx, in.TokenUuid.GetUuid()); err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't create a token") } return &emptypb.Empty{}, nil } // GetToken implements [v1.TokensServiceServer]. func (srv *TokensServer) GetToken(ctx context.Context, in *tokens.GetTokenRequest) (*tokens.GetTokenResponse, error) { claims, err := controllers.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") } if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "User is now allowed to manipulate this token") } token, err := srv.tokenCtrl.Get(ctx, in.TokenUuid.Uuid, claims.UserID) if err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't list tokens") } return &tokens.GetTokenResponse{ TokenUuid: &tokens.TokenUUID{ Uuid: token.UUID, }, TokenMetadata: &tokens.TokenMetadata{ Name: token.Name, ExpiresAt: timestamppb.New(token.ExpiresAt), LastUsedAt: timestamppb.New(token.LastUsedAt), GeneratedAt: timestamppb.New(token.GeneratedAt), CreatedAt: timestamppb.New(token.CreatedAt), RevokedAt: timestamppb.New(token.RevokedAt), }, TokenPermissions: &tokens.TokenPermissions{}, }, nil } // ListTokens implements [v1.TokensServiceServer]. func (srv *TokensServer) ListTokens(in *emptypb.Empty, stream grpc.ServerStreamingServer[tokens.ListTokensResponse]) error { claims, err := controllers.ClaimsFromContext(stream.Context()) if err != nil { return status.Error(codes.Aborted, "Context is invalid") } if claims.UserID == "" { return status.Error(codes.Aborted, "Context is invalid") } tokensRes, err := srv.tokenCtrl.List(stream.Context(), claims.UserID) if err != nil { if errors.Is(err, controllers.ErrServerError) { return status.Error(codes.Internal, "Something is broken on our side") } return status.Error(codes.Aborted, "Couldn't list tokens") } for _, tokenRes := range tokensRes { if err := stream.Send(&tokens.ListTokensResponse{ TokenUuid: &tokens.TokenUUID{ Uuid: tokenRes.UUID, }, TokenMetadata: &tokens.TokenMetadata{ Name: tokenRes.Name, ExpiresAt: timestamppb.New(tokenRes.ExpiresAt), LastUsedAt: timestamppb.New(tokenRes.LastUsedAt), GeneratedAt: timestamppb.New(tokenRes.GeneratedAt), CreatedAt: timestamppb.New(tokenRes.CreatedAt), RevokedAt: timestamppb.New(tokenRes.RevokedAt), }, }); err != nil { return status.Error(codes.Aborted, "Couldn't send data") } } return nil } // RegenerateToken implements [v1.TokensServiceServer]. func (srv *TokensServer) RegenerateToken(ctx context.Context, in *tokens.RegenerateTokenRequest) (*tokens.RegenerateTokenResponse, error) { claims, err := controllers.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") } if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "User is now allowed to manipulate this token") } tokenVal, err := srv.tokenCtrl.Regenerate(ctx, in.TokenUuid.GetUuid()) if err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't list tokens") } return &tokens.RegenerateTokenResponse{ TokenValue: &tokens.TokenValue{ Token: tokenVal, }, }, nil } // UpdateToken implements [v1.TokensServiceServer]. func (srv *TokensServer) UpdateToken(ctx context.Context, in *tokens.UpdateTokenRequest) (*tokens.UpdateTokenResponse, error) { claims, err := controllers.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") } if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "User is now allowed to manipulate this token") } if in.TokenPermissions == nil { return nil, status.Error(codes.InvalidArgument, "Permissions must be set") } permissions := map[string][]string{} for service, methods := range in.TokenPermissions.Permissions { permissions[service] = methods.GetMethods() } tokenData := &controllers.TokenData{ Name: in.TokenMetadata.Name, Scopes: permissions, } if err := srv.tokenCtrl.Update(ctx, tokenData); err != nil { if errors.Is(err, controllers.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't list tokens") } return &tokens.UpdateTokenResponse{ TokenUuid: &tokens.TokenUUID{}, TokenMetadata: &tokens.TokenMetadata{}, TokenPermissions: &tokens.TokenPermissions{}, }, nil } // 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.TokenPermissions{ Permissions: 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 }