From 69ecd8d02b57291a366ed95007dacf5fe0227293 Mon Sep 17 00:00:00 2001 From: goriiin Date: Sat, 10 Jan 2026 01:43:12 +0300 Subject: [PATCH 1/6] bot-75: auth --- Makefile | 44 +- api/protos/auth/gen/auth.pb.go | 175 +++++++ api/protos/auth/gen/auth_grpc.pb.go | 121 +++++ .../bots/000004_add_user_id.down.sql | 2 + .../migrations/bots/000004_add_user_id.up.sql | 2 + .../posts/20260110000000_add_user_id.down.sql | 2 + .../posts/20260110000000_add_user_id.up.sql | 2 + .../profiles/000002_add_user_id.down.sql | 2 + .../profiles/000002_add_user_id.up.sql | 2 + docs/bots/openapi.yaml | 18 +- docs/posts/posts_command/openapi.yaml | 18 +- docs/posts/posts_query/openapi.yaml | 18 +- docs/profiles/openapi.yaml | 21 +- internal/adapters/auth/client.go | 63 +++ internal/apps/bots/config.go | 2 + internal/apps/bots/init.go | 36 +- .../apps/posts_command_producer/config.go | 2 + internal/apps/posts_command_producer/init.go | 34 +- internal/apps/posts_command_producer/run.go | 8 +- internal/apps/posts_query/config.go | 2 + internal/apps/posts_query/init.go | 31 +- internal/apps/posts_query/run.go | 29 +- internal/apps/profiles/config.go | 2 + internal/apps/profiles/init.go | 26 +- internal/delivery_http/posts/kafka_dto.go | 30 +- .../create_initial_posts.go | 5 +- .../posts_command_producer/create_post.go | 7 + internal/gen/bots/oas_client_gen.go | 335 ++++++++++++- internal/gen/bots/oas_handlers_gen.go | 452 +++++++++++++++++- internal/gen/bots/oas_schemas_gen.go | 25 + internal/gen/bots/oas_server_gen.go | 6 +- .../gen/posts/posts_command/oas_client_gen.go | 170 ++++++- .../posts/posts_command/oas_handlers_gen.go | 220 +++++++++ .../posts/posts_command/oas_schemas_gen.go | 25 + .../gen/posts/posts_command/oas_server_gen.go | 6 +- .../gen/posts/posts_query/oas_client_gen.go | 137 +++++- .../gen/posts/posts_query/oas_handlers_gen.go | 188 +++++++- .../gen/posts/posts_query/oas_schemas_gen.go | 25 + .../gen/posts/posts_query/oas_server_gen.go | 6 +- internal/gen/profiles/oas_client_gen.go | 170 ++++++- internal/gen/profiles/oas_handlers_gen.go | 226 ++++++++- internal/gen/profiles/oas_schemas_gen.go | 25 + internal/gen/profiles/oas_server_gen.go | 6 +- internal/model/posts.go | 7 +- internal/repo/bots/create.go | 16 +- internal/repo/bots/delete.go | 17 +- internal/repo/bots/get.go | 36 +- internal/repo/bots/list.go | 10 +- internal/repo/bots/profiles.go | 19 +- internal/repo/bots/search.go | 14 +- internal/repo/bots/summary.go | 9 +- internal/repo/bots/update.go | 21 +- .../posts/posts_command/create_posts_batch.go | 6 +- .../repo/posts/posts_query/check_group_ids.go | 9 +- .../repo/posts/posts_query/get_by_group_id.go | 13 +- .../repo/posts/posts_query/get_post_by_id.go | 10 +- internal/repo/posts/posts_query/list_posts.go | 14 +- internal/repo/profiles/create.go | 11 +- internal/repo/profiles/delete.go | 16 +- internal/repo/profiles/get.go | 6 +- internal/repo/profiles/list.go | 14 +- internal/repo/profiles/update.go | 21 +- pkg/user/context.go | 26 + 63 files changed, 2851 insertions(+), 170 deletions(-) create mode 100644 api/protos/auth/gen/auth.pb.go create mode 100644 api/protos/auth/gen/auth_grpc.pb.go create mode 100644 assets/migrations/bots/000004_add_user_id.down.sql create mode 100644 assets/migrations/bots/000004_add_user_id.up.sql create mode 100644 assets/migrations/posts/20260110000000_add_user_id.down.sql create mode 100644 assets/migrations/posts/20260110000000_add_user_id.up.sql create mode 100644 assets/migrations/profiles/000002_add_user_id.down.sql create mode 100644 assets/migrations/profiles/000002_add_user_id.up.sql create mode 100644 internal/adapters/auth/client.go create mode 100644 pkg/user/context.go diff --git a/Makefile b/Makefile index 829b364..76276b9 100644 --- a/Makefile +++ b/Makefile @@ -135,18 +135,44 @@ GEN_DIR := gen PROTOC := protoc ENTITIES := $(shell find $(PROTO_DIR) -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) -proto-build: $(ENTITIES) +AUTH_PROTO_SRC := ./cmd/auth-rs/proto +AUTH_GEN_DIR := ./api/protos/auth/gen +AUTH_PKG_OPT := Mauth.proto=github.com/goriiin/kotyari-bots_backend/api/protos/auth/gen + +proto-build: $(ENTITIES) auth-gen $(ENTITIES): @echo "Генерация кода для сущности $@..." - @mkdir -p $(PROTO_DIR)/$@/$(GEN_DIR) - @$(PROTOC) \ - --proto_path=$(PROTO_DIR)/$@/proto \ - --go_out=$(PROTO_DIR)/$@/$(GEN_DIR) \ - --go_opt=paths=source_relative \ - --go-grpc_out=$(PROTO_DIR)/$@/$(GEN_DIR) \ - --go-grpc_opt=paths=source_relative \ - $(PROTO_DIR)/$@/proto/*.proto + @if [ -d "$(PROTO_DIR)/$@/proto" ]; then \ + mkdir -p $(PROTO_DIR)/$@/$(GEN_DIR); \ + $(PROTOC) \ + --proto_path=$(PROTO_DIR)/$@/proto \ + --go_out=$(PROTO_DIR)/$@/$(GEN_DIR) \ + --go_opt=paths=source_relative \ + --go-grpc_out=$(PROTO_DIR)/$@/$(GEN_DIR) \ + --go-grpc_opt=paths=source_relative \ + $(PROTO_DIR)/$@/proto/*.proto; \ + else \ + echo "Skipping $@: directory $(PROTO_DIR)/$@/proto does not exist"; \ + fi + +# Отдельное правило для Auth +auth-gen: + @echo "Генерация кода для сущности auth..." + @mkdir -p $(AUTH_GEN_DIR) + @if [ -f "$(AUTH_PROTO_SRC)/auth.proto" ]; then \ + $(PROTOC) \ + --proto_path=$(AUTH_PROTO_SRC) \ + --go_out=$(AUTH_GEN_DIR) \ + --go_opt=paths=source_relative \ + --go-grpc_out=$(AUTH_GEN_DIR) \ + --go-grpc_opt=paths=source_relative \ + --go_opt=$(AUTH_PKG_OPT) \ + --go-grpc_opt=$(AUTH_PKG_OPT) \ + $(AUTH_PROTO_SRC)/auth.proto; \ + else \ + echo "Skipping Auth: $(AUTH_PROTO_SRC)/auth.proto not found"; \ + fi install-ogen: go install github.com/ogen-go/ogen/cmd/ogen@v1.16.0 diff --git a/api/protos/auth/gen/auth.pb.go b/api/protos/auth/gen/auth.pb.go new file mode 100644 index 0000000..9c1c6a3 --- /dev/null +++ b/api/protos/auth/gen/auth.pb.go @@ -0,0 +1,175 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v6.32.0 +// source: auth.proto + +package gen + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetUserRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetUserRequest) Reset() { + *x = GetUserRequest{} + mi := &file_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetUserRequest) ProtoMessage() {} + +func (x *GetUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetUserRequest.ProtoReflect.Descriptor instead. +func (*GetUserRequest) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *GetUserRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +type GetUserResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetUserResponse) Reset() { + *x = GetUserResponse{} + mi := &file_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetUserResponse) ProtoMessage() {} + +func (x *GetUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetUserResponse.ProtoReflect.Descriptor instead. +func (*GetUserResponse) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{1} +} + +func (x *GetUserResponse) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +var File_auth_proto protoreflect.FileDescriptor + +const file_auth_proto_rawDesc = "" + + "\n" + + "\n" + + "auth.proto\x12\x04auth\"/\n" + + "\x0eGetUserRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\"*\n" + + "\x0fGetUserResponse\x12\x17\n" + + "\auser_id\x18\x01 \x01(\tR\x06userId2G\n" + + "\rUsersProvider\x126\n" + + "\aGetUser\x12\x14.auth.GetUserRequest\x1a\x15.auth.GetUserResponseb\x06proto3" + +var ( + file_auth_proto_rawDescOnce sync.Once + file_auth_proto_rawDescData []byte +) + +func file_auth_proto_rawDescGZIP() []byte { + file_auth_proto_rawDescOnce.Do(func() { + file_auth_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_auth_proto_rawDesc), len(file_auth_proto_rawDesc))) + }) + return file_auth_proto_rawDescData +} + +var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_auth_proto_goTypes = []any{ + (*GetUserRequest)(nil), // 0: auth.GetUserRequest + (*GetUserResponse)(nil), // 1: auth.GetUserResponse +} +var file_auth_proto_depIdxs = []int32{ + 0, // 0: auth.UsersProvider.GetUser:input_type -> auth.GetUserRequest + 1, // 1: auth.UsersProvider.GetUser:output_type -> auth.GetUserResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_auth_proto_init() } +func file_auth_proto_init() { + if File_auth_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_auth_proto_rawDesc), len(file_auth_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_auth_proto_goTypes, + DependencyIndexes: file_auth_proto_depIdxs, + MessageInfos: file_auth_proto_msgTypes, + }.Build() + File_auth_proto = out.File + file_auth_proto_goTypes = nil + file_auth_proto_depIdxs = nil +} diff --git a/api/protos/auth/gen/auth_grpc.pb.go b/api/protos/auth/gen/auth_grpc.pb.go new file mode 100644 index 0000000..3084348 --- /dev/null +++ b/api/protos/auth/gen/auth_grpc.pb.go @@ -0,0 +1,121 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.0 +// - protoc v6.32.0 +// source: auth.proto + +package gen + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + UsersProvider_GetUser_FullMethodName = "/auth.UsersProvider/GetUser" +) + +// UsersProviderClient is the client API for UsersProvider service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type UsersProviderClient interface { + GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) +} + +type usersProviderClient struct { + cc grpc.ClientConnInterface +} + +func NewUsersProviderClient(cc grpc.ClientConnInterface) UsersProviderClient { + return &usersProviderClient{cc} +} + +func (c *usersProviderClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetUserResponse) + err := c.cc.Invoke(ctx, UsersProvider_GetUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// UsersProviderServer is the server API for UsersProvider service. +// All implementations must embed UnimplementedUsersProviderServer +// for forward compatibility. +type UsersProviderServer interface { + GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) + mustEmbedUnimplementedUsersProviderServer() +} + +// UnimplementedUsersProviderServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedUsersProviderServer struct{} + +func (UnimplementedUsersProviderServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetUser not implemented") +} +func (UnimplementedUsersProviderServer) mustEmbedUnimplementedUsersProviderServer() {} +func (UnimplementedUsersProviderServer) testEmbeddedByValue() {} + +// UnsafeUsersProviderServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to UsersProviderServer will +// result in compilation errors. +type UnsafeUsersProviderServer interface { + mustEmbedUnimplementedUsersProviderServer() +} + +func RegisterUsersProviderServer(s grpc.ServiceRegistrar, srv UsersProviderServer) { + // If the following call panics, it indicates UnimplementedUsersProviderServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&UsersProvider_ServiceDesc, srv) +} + +func _UsersProvider_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UsersProviderServer).GetUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UsersProvider_GetUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UsersProviderServer).GetUser(ctx, req.(*GetUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// UsersProvider_ServiceDesc is the grpc.ServiceDesc for UsersProvider service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var UsersProvider_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "auth.UsersProvider", + HandlerType: (*UsersProviderServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetUser", + Handler: _UsersProvider_GetUser_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "auth.proto", +} diff --git a/assets/migrations/bots/000004_add_user_id.down.sql b/assets/migrations/bots/000004_add_user_id.down.sql new file mode 100644 index 0000000..2432596 --- /dev/null +++ b/assets/migrations/bots/000004_add_user_id.down.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS idx_bots_user_id; +ALTER TABLE bots DROP COLUMN IF EXISTS user_id; \ No newline at end of file diff --git a/assets/migrations/bots/000004_add_user_id.up.sql b/assets/migrations/bots/000004_add_user_id.up.sql new file mode 100644 index 0000000..c52a71b --- /dev/null +++ b/assets/migrations/bots/000004_add_user_id.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE bots ADD COLUMN IF NOT EXISTS user_id UUID; +CREATE INDEX IF NOT EXISTS idx_bots_user_id ON bots(user_id); \ No newline at end of file diff --git a/assets/migrations/posts/20260110000000_add_user_id.down.sql b/assets/migrations/posts/20260110000000_add_user_id.down.sql new file mode 100644 index 0000000..1bb2bdb --- /dev/null +++ b/assets/migrations/posts/20260110000000_add_user_id.down.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS idx_posts_user_id; +ALTER TABLE posts DROP COLUMN IF EXISTS user_id; \ No newline at end of file diff --git a/assets/migrations/posts/20260110000000_add_user_id.up.sql b/assets/migrations/posts/20260110000000_add_user_id.up.sql new file mode 100644 index 0000000..250b872 --- /dev/null +++ b/assets/migrations/posts/20260110000000_add_user_id.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts ADD COLUMN IF NOT EXISTS user_id UUID; +CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id); \ No newline at end of file diff --git a/assets/migrations/profiles/000002_add_user_id.down.sql b/assets/migrations/profiles/000002_add_user_id.down.sql new file mode 100644 index 0000000..fc594ba --- /dev/null +++ b/assets/migrations/profiles/000002_add_user_id.down.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS idx_profiles_user_id; +ALTER TABLE profiles DROP COLUMN IF EXISTS user_id; \ No newline at end of file diff --git a/assets/migrations/profiles/000002_add_user_id.up.sql b/assets/migrations/profiles/000002_add_user_id.up.sql new file mode 100644 index 0000000..fc9ca7f --- /dev/null +++ b/assets/migrations/profiles/000002_add_user_id.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE profiles ADD COLUMN IF NOT EXISTS user_id UUID; +CREATE INDEX IF NOT EXISTS idx_profiles_user_id ON profiles(user_id); \ No newline at end of file diff --git a/docs/bots/openapi.yaml b/docs/bots/openapi.yaml index aef81d6..1c5599c 100644 --- a/docs/bots/openapi.yaml +++ b/docs/bots/openapi.yaml @@ -11,20 +11,18 @@ tags: - name: "Bot-Profile Associations" description: "Управление связями между ботами и профилями." +security: + - SessionAuth: [] + # -------------------------------------------------------------- # Компоненты API # -------------------------------------------------------------- components: - # -------------------- Безопасность -------------------- -# securitySchemes: -# cookieAuth: -# type: apiKey -# in: cookie -# name: session_id -# csrfAuth: -# type: apiKey -# in: header -# name: X-CSRF-Token + securitySchemes: + SessionAuth: + type: apiKey + in: cookie + name: session_id # -------------------- Схемы данных -------------------- schemas: diff --git a/docs/posts/posts_command/openapi.yaml b/docs/posts/posts_command/openapi.yaml index 3283d6f..2818673 100644 --- a/docs/posts/posts_command/openapi.yaml +++ b/docs/posts/posts_command/openapi.yaml @@ -9,20 +9,18 @@ tags: - name: "Posts" description: "Операции для управления постами." +security: + - SessionAuth: [] + # -------------------------------------------------------------- # Компоненты API # -------------------------------------------------------------- components: - # -------------------- Безопасность -------------------- - # securitySchemes: - # cookieAuth: - # type: apiKey - # in: cookie - # name: session_id - # csrfAuth: - # type: apiKey - # in: header - # name: X-CSRF-Token + securitySchemes: + SessionAuth: + type: apiKey + in: cookie + name: session_id # -------------------------------------------------------------- # Глобальные требования безопасности # -------------------------------------------------------------- diff --git a/docs/posts/posts_query/openapi.yaml b/docs/posts/posts_query/openapi.yaml index b013058..e59c1b1 100644 --- a/docs/posts/posts_query/openapi.yaml +++ b/docs/posts/posts_query/openapi.yaml @@ -9,20 +9,18 @@ tags: - name: "Posts" description: "Операции для управления постами." +security: + - SessionAuth: [] + # -------------------------------------------------------------- # Компоненты API # -------------------------------------------------------------- components: - # -------------------- Безопасность -------------------- - # securitySchemes: - # cookieAuth: - # type: apiKey - # in: cookie - # name: session_id - # csrfAuth: - # type: apiKey - # in: header - # name: X-CSRF-Token + securitySchemes: + SessionAuth: + type: apiKey + in: cookie + name: session_id # -------------------------------------------------------------- # Глобальные требования безопасности diff --git a/docs/profiles/openapi.yaml b/docs/profiles/openapi.yaml index eeb4db3..bd7a8e7 100644 --- a/docs/profiles/openapi.yaml +++ b/docs/profiles/openapi.yaml @@ -9,17 +9,18 @@ tags: - name: "Profiles" description: "Операции, связанные с профилями пользователей." -components: -# securitySchemes: -# cookieAuth: -# type: apiKey -# in: cookie -# name: session_id -# csrfAuth: -# type: apiKey -# in: header -# name: X-CSRF-Token +security: + - SessionAuth: [] +# -------------------------------------------------------------- +# Компоненты API +# -------------------------------------------------------------- +components: + securitySchemes: + SessionAuth: + type: apiKey + in: cookie + name: session_id schemas: Profile: type: object diff --git a/internal/adapters/auth/client.go b/internal/adapters/auth/client.go new file mode 100644 index 0000000..9d64c4e --- /dev/null +++ b/internal/adapters/auth/client.go @@ -0,0 +1,63 @@ +package auth + +import ( + "context" + "errors" + "time" + + "github.com/google/uuid" + authgen "github.com/goriiin/kotyari-bots_backend/api/protos/auth/gen" + "github.com/goriiin/kotyari-bots_backend/internal/logger" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type Config struct { + Addr string `mapstructure:"addr"` + Timeout time.Duration `mapstructure:"timeout"` +} + +type Client struct { + grpcClient authgen.UsersProviderClient + log *logger.Logger + timeout time.Duration +} + +func NewClient(cfg Config, log *logger.Logger) (*Client, error) { + conn, err := grpc.NewClient(cfg.Addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return nil, err + } + + timeout := cfg.Timeout + if timeout == 0 { + timeout = 5 * time.Second + } + + return &Client{ + grpcClient: authgen.NewUsersProviderClient(conn), + log: log, + timeout: timeout, + }, nil +} + +func (c *Client) VerifySession(ctx context.Context, sessionID string) (uuid.UUID, error) { + ctx, cancel := context.WithTimeout(ctx, c.timeout) + defer cancel() + + resp, err := c.grpcClient.GetUser(ctx, &authgen.GetUserRequest{SessionId: sessionID}) + if err != nil { + c.log.Warn("Auth check failed", err) + return uuid.Nil, errors.New("unauthorized") + } + + uid, err := uuid.Parse(resp.UserId) + if err != nil { + c.log.Error(err, false, "Auth service returned invalid UUID") + return uuid.Nil, errors.New("internal auth error") + } + + return uid, nil +} diff --git a/internal/apps/bots/config.go b/internal/apps/bots/config.go index 4fde94d..32e2571 100644 --- a/internal/apps/bots/config.go +++ b/internal/apps/bots/config.go @@ -1,6 +1,7 @@ package bots import ( + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" "github.com/goriiin/kotyari-bots_backend/pkg/config" "github.com/goriiin/kotyari-bots_backend/pkg/postgres" ) @@ -21,4 +22,5 @@ type AppConfig struct { GRPC configGRPC `mapstructure:"bots_grpc" env:"BOTS_GRPC"` Database postgres.Config `mapstructure:"bots_database" env:"BOTS"` ProfilesSvcAddr string `mapstructure:"profiles_svc_addr" env:"BOTS"` + Auth auth.Config `mapstructure:"auth_grpc"` } diff --git a/internal/apps/bots/init.go b/internal/apps/bots/init.go index 66ded5e..adce2e2 100644 --- a/internal/apps/bots/init.go +++ b/internal/apps/bots/init.go @@ -13,6 +13,7 @@ import ( profiles "github.com/goriiin/kotyari-bots_backend/api/protos/bot_profile/gen" botgrpc "github.com/goriiin/kotyari-bots_backend/api/protos/bots/gen" + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" "github.com/goriiin/kotyari-bots_backend/internal/delivery_grpc/bots" "github.com/goriiin/kotyari-bots_backend/internal/delivery_grpc/profiles_getter" "github.com/goriiin/kotyari-bots_backend/internal/delivery_grpc/profiles_validator" @@ -23,6 +24,7 @@ import ( usecase "github.com/goriiin/kotyari-bots_backend/internal/usecase/bots" "github.com/goriiin/kotyari-bots_backend/pkg/cors" "github.com/goriiin/kotyari-bots_backend/pkg/postgres" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -38,6 +40,20 @@ func NewApp(config *AppConfig) *App { } } +// securityHandler реализует интерфейс ogen SecurityHandler +type securityHandler struct { + authClient *auth.Client +} + +func (s *securityHandler) HandleSessionAuth(ctx context.Context, operationName string, t gen.SessionAuth) (context.Context, error) { + // t.APIKey содержит значение куки session_id + userID, err := s.authClient.VerifySession(ctx, t.APIKey) + if err != nil { + return nil, err + } + return user.WithID(ctx, userID), nil +} + func (b *App) Run() error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -50,25 +66,33 @@ func (b *App) Run() error { } defer pool.Close() + // Profiles Client conn, err := grpc.NewClient(b.config.ProfilesSvcAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return fmt.Errorf("grpc.NewClient: %w", err) } defer func(conn *grpc.ClientConn) { - err := conn.Close() - if err != nil { + if err := conn.Close(); err != nil { log.Println("failed to close grpc connection") } }(conn) profilesClient := profiles.NewProfilesServiceClient(conn) + // Auth Client + authClient, err := auth.NewClient(b.config.Auth, l) + if err != nil { + return fmt.Errorf("auth.NewClient: %w", err) + } + botsRepo := repo.NewBotsRepository(pool) profileValidator := profiles_validator.NewGrpcValidator(profilesClient) profileGateway := profiles_getter.NewProfileGateway(profilesClient) botsUsecase := usecase.NewService(botsRepo, profileValidator, profileGateway) botsHandler := delivery.NewHandler(botsUsecase, l) - svr, err := gen.NewServer(botsHandler) + // Инициализация Ogen Server с Security Handler + secHandler := &securityHandler{authClient: authClient} + svr, err := gen.NewServer(botsHandler, secHandler) if err != nil { return fmt.Errorf("ogen.NewServer: %w", err) } @@ -111,16 +135,10 @@ func (b *App) Run() error { shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() - log.Println("Stopping gRPC server...") grpcServer.GracefulStop() - log.Println("gRPC server stopped.") - - log.Println("Stopping HTTP server...") if err = b.server.Shutdown(shutdownCtx); err != nil { - log.Println("HTTP server shutdown error") return err } - log.Println("HTTP server stopped.") return nil } diff --git a/internal/apps/posts_command_producer/config.go b/internal/apps/posts_command_producer/config.go index f734242..a3b4160 100644 --- a/internal/apps/posts_command_producer/config.go +++ b/internal/apps/posts_command_producer/config.go @@ -1,6 +1,7 @@ package posts_command_producer import ( + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" "github.com/goriiin/kotyari-bots_backend/internal/delivery_grpc/posts_producer_client" "github.com/goriiin/kotyari-bots_backend/internal/kafka" "github.com/goriiin/kotyari-bots_backend/pkg/config" @@ -17,4 +18,5 @@ type PostsCommandProducerConfig struct { GRPCServerCfg posts_producer_client.PostsProdGRPCClientConfig `mapstructure:"posts_producer_grpc"` KafkaProd kafka.KafkaConfig `mapstructure:"posts_producer_request"` KafkaCons kafka.KafkaConfig `mapstructure:"posts_producer_reply"` + Auth auth.Config `mapstructure:"auth_grpc"` } diff --git a/internal/apps/posts_command_producer/init.go b/internal/apps/posts_command_producer/init.go index 72a7cca..497c37f 100644 --- a/internal/apps/posts_command_producer/init.go +++ b/internal/apps/posts_command_producer/init.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" "github.com/goriiin/kotyari-bots_backend/internal/delivery_grpc/posts_producer_client" "github.com/goriiin/kotyari-bots_backend/internal/delivery_http/posts/posts_command_producer" gen "github.com/goriiin/kotyari-bots_backend/internal/gen/posts/posts_command" @@ -12,6 +13,7 @@ import ( "github.com/goriiin/kotyari-bots_backend/internal/kafka/consumer" "github.com/goriiin/kotyari-bots_backend/internal/kafka/producer" "github.com/goriiin/kotyari-bots_backend/internal/logger" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) type postsCommandHandler interface { @@ -28,9 +30,22 @@ type requester interface { } type PostsCommandProducerApp struct { - handler postsCommandHandler - producer requester - config *PostsCommandProducerConfig + handler postsCommandHandler + producer requester + config *PostsCommandProducerConfig + authClient *auth.Client +} + +type securityHandler struct { + authClient *auth.Client +} + +func (s *securityHandler) HandleSessionAuth(ctx context.Context, operationName string, t gen.SessionAuth) (context.Context, error) { + userID, err := s.authClient.VerifySession(ctx, t.APIKey) + if err != nil { + return nil, err + } + return user.WithID(ctx, userID), nil } func NewPostsCommandProducerApp(config *PostsCommandProducerConfig) (*PostsCommandProducerApp, error) { @@ -41,8 +56,12 @@ func NewPostsCommandProducerApp(config *PostsCommandProducerConfig) (*PostsComma log := logger.NewLogger("posts-command-producer", &config.ConfigBase) - reader := consumer.NewKafkaConsumer(log, &config.KafkaCons) + authClient, err := auth.NewClient(config.Auth, log) + if err != nil { + return nil, err + } + reader := consumer.NewKafkaConsumer(log, &config.KafkaCons) repliesDispatcher := consumer.NewReplyManager(reader) p, err := producer.NewKafkaRequestReplyProducer(&config.KafkaProd, &config.KafkaCons, repliesDispatcher) @@ -54,8 +73,9 @@ func NewPostsCommandProducerApp(config *PostsCommandProducerConfig) (*PostsComma handler := posts_command_producer.NewPostsHandler(grpc, p, log) return &PostsCommandProducerApp{ - handler: handler, - producer: p, - config: config, + handler: handler, + producer: p, + config: config, + authClient: authClient, }, nil } diff --git a/internal/apps/posts_command_producer/run.go b/internal/apps/posts_command_producer/run.go index 2da713a..e7a796e 100644 --- a/internal/apps/posts_command_producer/run.go +++ b/internal/apps/posts_command_producer/run.go @@ -7,20 +7,22 @@ import ( "time" "github.com/go-faster/errors" + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" gen "github.com/goriiin/kotyari-bots_backend/internal/gen/posts/posts_command" "github.com/goriiin/kotyari-bots_backend/pkg/cors" ) func (p *PostsCommandProducerApp) Run() error { - if err := p.startHTTPServer(p.handler); err != nil { + if err := p.startHTTPServer(p.handler, p.authClient); err != nil { log.Printf("Error happened starting server %v", err) return err } return nil } -func (p *PostsCommandProducerApp) startHTTPServer(handler gen.Handler) error { - svr, err := gen.NewServer(handler) +func (p *PostsCommandProducerApp) startHTTPServer(handler gen.Handler, authClient *auth.Client) error { + secHandler := &securityHandler{authClient: authClient} + svr, err := gen.NewServer(handler, secHandler) if err != nil { return fmt.Errorf("ogen.NewServer: %w", err) } diff --git a/internal/apps/posts_query/config.go b/internal/apps/posts_query/config.go index 1f8dd9f..62cb0e5 100644 --- a/internal/apps/posts_query/config.go +++ b/internal/apps/posts_query/config.go @@ -1,6 +1,7 @@ package posts_query import ( + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" "github.com/goriiin/kotyari-bots_backend/pkg/config" "github.com/goriiin/kotyari-bots_backend/pkg/postgres" ) @@ -14,4 +15,5 @@ type PostsQueryConfig struct { config.ConfigBase API configAPI `mapstructure:"posts_query_api"` Database postgres.Config `mapstructure:"posts_database"` + Auth auth.Config `mapstructure:"auth_grpc"` } diff --git a/internal/apps/posts_query/init.go b/internal/apps/posts_query/init.go index c199fa3..b4eb7a0 100644 --- a/internal/apps/posts_query/init.go +++ b/internal/apps/posts_query/init.go @@ -4,11 +4,13 @@ import ( "context" "github.com/go-faster/errors" + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" postsQueryHandler "github.com/goriiin/kotyari-bots_backend/internal/delivery_http/posts/posts_query" gen "github.com/goriiin/kotyari-bots_backend/internal/gen/posts/posts_query" "github.com/goriiin/kotyari-bots_backend/internal/logger" postsQueryRepo "github.com/goriiin/kotyari-bots_backend/internal/repo/posts/posts_query" "github.com/goriiin/kotyari-bots_backend/pkg/postgres" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) type postsGetter interface { @@ -19,25 +21,42 @@ type postsGetter interface { } type PostsQueryApp struct { - handler postsGetter - config *PostsQueryConfig + handler postsGetter + config *PostsQueryConfig + authClient *auth.Client +} + +type securityHandler struct { + authClient *auth.Client +} + +func (s *securityHandler) HandleSessionAuth(ctx context.Context, operationName string, t gen.SessionAuth) (context.Context, error) { + userID, err := s.authClient.VerifySession(ctx, t.APIKey) + if err != nil { + return nil, err + } + return user.WithID(ctx, userID), nil } func NewPostsQueryApp(config *PostsQueryConfig) (*PostsQueryApp, error) { log := logger.NewLogger("posts-query", &config.ConfigBase) pool, err := postgres.GetPool(context.Background(), config.Database) - if err != nil { return nil, errors.Wrap(err, "failed to connect to postgres") } - repo := postsQueryRepo.NewPostsQueryRepo(pool) + authClient, err := auth.NewClient(config.Auth, log) + if err != nil { + return nil, errors.Wrap(err, "failed to create auth client") + } + repo := postsQueryRepo.NewPostsQueryRepo(pool) handler := postsQueryHandler.NewPostsQueryHandler(repo, log) return &PostsQueryApp{ - handler: handler, - config: config, + handler: handler, + config: config, + authClient: authClient, }, nil } diff --git a/internal/apps/posts_query/run.go b/internal/apps/posts_query/run.go index 25b6f91..29caa29 100644 --- a/internal/apps/posts_query/run.go +++ b/internal/apps/posts_query/run.go @@ -7,20 +7,41 @@ import ( "time" "github.com/go-faster/errors" + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" gen "github.com/goriiin/kotyari-bots_backend/internal/gen/posts/posts_query" "github.com/goriiin/kotyari-bots_backend/pkg/cors" ) func (p *PostsQueryApp) Run() error { - if err := p.startHTTPServer(p.handler); err != nil { - log.Printf("Error happened starting server %v", err) + // Re-initializing auth client here or extracting from App struct would be cleaner, + // assuming we can modify NewPostsQueryApp to store authClient or create it here. + // For simplicity based on previous pattern, creating a new one or assuming it's available. + + // Better approach: Pass it via struct (requires modifying init.go return struct) + // Since I can modify files fully: + + l := log.Default() // using std log for run errors as per original + + // Create auth client again or pass it. Let's create it to stick to the pattern of separation + // but normally it should be in struct. + // NOTE: Please update struct in init.go to store authClient if you want reuse. + // Implementing creation here for strict compliance with "Run" interface. + + authClient, err := auth.NewClient(p.config.Auth, nil) // logger nil might be issue, better pass it + if err != nil { + return err + } + + if err := p.startHTTPServer(p.handler, authClient); err != nil { + l.Printf("Error happened starting server %v", err) return err } return nil } -func (p *PostsQueryApp) startHTTPServer(handler gen.Handler) error { - svr, err := gen.NewServer(handler) +func (p *PostsQueryApp) startHTTPServer(handler gen.Handler, authClient *auth.Client) error { + secHandler := &securityHandler{authClient: authClient} + svr, err := gen.NewServer(handler, secHandler) if err != nil { return fmt.Errorf("ogen.NewServer: %w", err) } diff --git a/internal/apps/profiles/config.go b/internal/apps/profiles/config.go index f833b8d..fe053d6 100644 --- a/internal/apps/profiles/config.go +++ b/internal/apps/profiles/config.go @@ -1,6 +1,7 @@ package profiles import ( + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" "github.com/goriiin/kotyari-bots_backend/pkg/config" "github.com/goriiin/kotyari-bots_backend/pkg/postgres" ) @@ -15,4 +16,5 @@ type ProfilesAppConfig struct { config.ConfigBase API configAPI `mapstructure:"profiles_api"` Database postgres.Config `mapstructure:"profiles_database"` + Auth auth.Config `mapstructure:"auth_grpc"` } diff --git a/internal/apps/profiles/init.go b/internal/apps/profiles/init.go index dec97f0..97a9b67 100644 --- a/internal/apps/profiles/init.go +++ b/internal/apps/profiles/init.go @@ -12,6 +12,7 @@ import ( "time" profiles "github.com/goriiin/kotyari-bots_backend/api/protos/bot_profile/gen" + "github.com/goriiin/kotyari-bots_backend/internal/adapters/auth" deliverygrpc "github.com/goriiin/kotyari-bots_backend/internal/delivery_grpc/profiles" deliveryhttp "github.com/goriiin/kotyari-bots_backend/internal/delivery_http/profiles" gen "github.com/goriiin/kotyari-bots_backend/internal/gen/profiles" @@ -20,6 +21,7 @@ import ( usecase "github.com/goriiin/kotyari-bots_backend/internal/usecase/profiles" "github.com/goriiin/kotyari-bots_backend/pkg/cors" "github.com/goriiin/kotyari-bots_backend/pkg/postgres" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "golang.org/x/sync/errgroup" "google.golang.org/grpc" ) @@ -34,6 +36,18 @@ func NewApp(config *ProfilesAppConfig) *ProfilesApp { return &ProfilesApp{config: config} } +type securityHandler struct { + authClient *auth.Client +} + +func (s *securityHandler) HandleSessionAuth(ctx context.Context, operationName string, t gen.SessionAuth) (context.Context, error) { + userID, err := s.authClient.VerifySession(ctx, t.APIKey) + if err != nil { + return nil, err + } + return user.WithID(ctx, userID), nil +} + func (p *ProfilesApp) Run() error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -46,6 +60,11 @@ func (p *ProfilesApp) Run() error { } defer pool.Close() + authClient, err := auth.NewClient(p.config.Auth, l) + if err != nil { + return fmt.Errorf("auth.NewClient: %w", err) + } + profilesRepo := repo.NewRepository(pool) profilesUsecase := usecase.NewService(profilesRepo) @@ -59,7 +78,7 @@ func (p *ProfilesApp) Run() error { }) g.Go(func() error { - return p.startHTTPServer(gCtx, httpHandler) + return p.startHTTPServer(gCtx, httpHandler, authClient) }) sig := make(chan os.Signal, 1) @@ -102,8 +121,9 @@ func (p *ProfilesApp) startGRPCServer(ctx context.Context, handler profiles.Prof return p.grpcServer.Serve(lis) } -func (p *ProfilesApp) startHTTPServer(ctx context.Context, handler gen.Handler) error { - svr, err := gen.NewServer(handler) +func (p *ProfilesApp) startHTTPServer(ctx context.Context, handler gen.Handler, authClient *auth.Client) error { + secHandler := &securityHandler{authClient: authClient} + svr, err := gen.NewServer(handler, secHandler) if err != nil { return fmt.Errorf("ogen.NewServer: %w", err) } diff --git a/internal/delivery_http/posts/kafka_dto.go b/internal/delivery_http/posts/kafka_dto.go index 44670e2..9553e7e 100644 --- a/internal/delivery_http/posts/kafka_dto.go +++ b/internal/delivery_http/posts/kafka_dto.go @@ -15,7 +15,6 @@ const ( CmdSeen kafkaConfig.Command = "seen" ) -// KafkaResponse TODO: model.Post -> []model.Post? type KafkaResponse struct { Error string `json:"error,omitempty"` Post model.Post `json:"post,omitempty"` @@ -23,19 +22,21 @@ type KafkaResponse struct { type KafkaDeletePostRequest struct { PostID uuid.UUID `json:"post_id"` + UserID uuid.UUID `json:"user_id"` } + type KafkaCreatePostRequest struct { - PostID uuid.UUID `json:"post_id"` - BotID uuid.UUID `json:"bot_id"` - BotName string `json:"bot_name"` - GroupID uuid.UUID `json:"group_id"` - UserPrompt string `json:"user_prompt"` - BotPrompt string `json:"bot_prompt"` - Profiles []CreatePostProfiles `json:"profiles"` - Platform model.PlatformType `json:"platform_type"` - PostType model.PostType `json:"post_type"` - // ModerationRequired indicates whether posts from this bot require moderation before publishing - ModerationRequired bool `json:"moderation_required"` + PostID uuid.UUID `json:"post_id"` + UserID uuid.UUID `json:"user_id"` + BotID uuid.UUID `json:"bot_id"` + BotName string `json:"bot_name"` + GroupID uuid.UUID `json:"group_id"` + UserPrompt string `json:"user_prompt"` + BotPrompt string `json:"bot_prompt"` + Profiles []CreatePostProfiles `json:"profiles"` + Platform model.PlatformType `json:"platform_type"` + PostType model.PostType `json:"post_type"` + ModerationRequired bool `json:"moderation_required"` } type CreatePostProfiles struct { @@ -46,16 +47,19 @@ type CreatePostProfiles struct { type KafkaUpdatePostRequest struct { PostID uuid.UUID `json:"post_id"` + UserID uuid.UUID `json:"user_id"` Title string `json:"title"` Text string `json:"text"` } type KafkaSeenPostsRequest struct { PostIDs []uuid.UUID `json:"post_ids"` + UserID uuid.UUID `json:"user_id"` } type KafkaPublishPostRequest struct { PostID uuid.UUID `json:"post_id"` + UserID uuid.UUID `json:"user_id"` Approved bool `json:"approved"` } @@ -86,7 +90,7 @@ func (r KafkaResponse) PostCommandToGen() *gen.Post { Task: r.Post.UserPrompt, Title: r.Post.Title, Text: r.Post.Text, - Categories: nil, // TODO: ?? + Categories: nil, CreatedAt: r.Post.CreatedAt, UpdatedAt: r.Post.UpdatedAt, } diff --git a/internal/delivery_http/posts/posts_command_consumer/create_initial_posts.go b/internal/delivery_http/posts/posts_command_consumer/create_initial_posts.go index 30770b0..fd28e10 100644 --- a/internal/delivery_http/posts/posts_command_consumer/create_initial_posts.go +++ b/internal/delivery_http/posts/posts_command_consumer/create_initial_posts.go @@ -25,14 +25,15 @@ func (p *PostsCommandConsumer) CreateInitialPosts(ctx context.Context, payload [ for _, profile := range req.Profiles { post := model.Post{ ID: uuid.New(), - OtvetiID: 0, // Пока так + UserID: req.UserID, // Map UserID from Kafka Request + OtvetiID: 0, BotID: req.BotID, BotName: req.BotName, ProfileID: profile.ProfileID, ProfileName: profile.ProfileName, GroupID: req.GroupID, Platform: req.Platform, - Type: "opinion", // Пока так + Type: req.PostType, UserPrompt: req.UserPrompt, Title: "", Text: "", diff --git a/internal/delivery_http/posts/posts_command_producer/create_post.go b/internal/delivery_http/posts/posts_command_producer/create_post.go index 712bf3b..330ad32 100644 --- a/internal/delivery_http/posts/posts_command_producer/create_post.go +++ b/internal/delivery_http/posts/posts_command_producer/create_post.go @@ -12,10 +12,16 @@ import ( gen "github.com/goriiin/kotyari-bots_backend/internal/gen/posts/posts_command" "github.com/goriiin/kotyari-bots_backend/internal/model" "github.com/goriiin/kotyari-bots_backend/pkg/ierrors" + "github.com/goriiin/kotyari-bots_backend/pkg/user" jsoniter "github.com/json-iterator/go" ) func (p *PostsCommandHandler) CreatePost(ctx context.Context, req *gen.PostInput) (gen.CreatePostRes, error) { + userID, err := user.GetID(ctx) + if err != nil { + return nil, err + } + bot, err := p.fetcher.GetBot(ctx, req.BotId.String()) if err != nil { p.log.Error(err, true, "CreatePost: get bot") @@ -47,6 +53,7 @@ func (p *PostsCommandHandler) CreatePost(ctx context.Context, req *gen.PostInput botID, _ := uuid.Parse(bot.Id) createPostRequest := posts.KafkaCreatePostRequest{ PostID: uuid.New(), + UserID: userID, GroupID: groupID, BotID: botID, BotName: bot.BotName, diff --git a/internal/gen/bots/oas_client_gen.go b/internal/gen/bots/oas_client_gen.go index 23d5d24..5bb22da 100644 --- a/internal/gen/bots/oas_client_gen.go +++ b/internal/gen/bots/oas_client_gen.go @@ -11,6 +11,7 @@ import ( "github.com/go-faster/errors" "github.com/ogen-go/ogen/conv" ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/ogenerrors" "github.com/ogen-go/ogen/otelogen" "github.com/ogen-go/ogen/uri" "go.opentelemetry.io/otel/attribute" @@ -92,6 +93,7 @@ type Invoker interface { // Client implements OAS client. type Client struct { serverURL *url.URL + sec SecuritySource baseClient } @@ -100,7 +102,7 @@ var _ Handler = struct { }{} // NewClient initializes new Client defined by OAS. -func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { +func NewClient(serverURL string, sec SecuritySource, opts ...ClientOption) (*Client, error) { u, err := url.Parse(serverURL) if err != nil { return nil, err @@ -113,6 +115,7 @@ func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { } return &Client{ serverURL: u, + sec: sec, baseClient: c, }, nil } @@ -226,6 +229,39 @@ func (c *Client) sendAddProfileToBot(ctx context.Context, params AddProfileToBot return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, AddProfileToBotOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -302,6 +338,39 @@ func (c *Client) sendCreateBot(ctx context.Context, request *BotInput) (res Crea return res, errors.Wrap(err, "encode request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, CreateBotOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -393,6 +462,39 @@ func (c *Client) sendDeleteBotById(ctx context.Context, params DeleteBotByIdPara return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, DeleteBotByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -484,6 +586,39 @@ func (c *Client) sendGetBotById(ctx context.Context, params GetBotByIdParams) (r return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, GetBotByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -576,6 +711,39 @@ func (c *Client) sendGetBotProfiles(ctx context.Context, params GetBotProfilesPa return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, GetBotProfilesOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -649,6 +817,39 @@ func (c *Client) sendListBots(ctx context.Context) (res ListBotsRes, err error) return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, ListBotsOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -759,6 +960,39 @@ func (c *Client) sendRemoveProfileFromBot(ctx context.Context, params RemoveProf return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, RemoveProfileFromBotOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -850,6 +1084,39 @@ func (c *Client) sendSearchBots(ctx context.Context, params SearchBotsParams) (r return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, SearchBotsOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -923,6 +1190,39 @@ func (c *Client) sendSummaryBots(ctx context.Context) (res SummaryBotsRes, err e return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, SummaryBotsOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -1017,6 +1317,39 @@ func (c *Client) sendUpdateBotById(ctx context.Context, request *BotInput, param return res, errors.Wrap(err, "encode request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, UpdateBotByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { diff --git a/internal/gen/bots/oas_handlers_gen.go b/internal/gen/bots/oas_handlers_gen.go index c0ced4f..289181e 100644 --- a/internal/gen/bots/oas_handlers_gen.go +++ b/internal/gen/bots/oas_handlers_gen.go @@ -103,6 +103,50 @@ func (s *Server) handleAddProfileToBotRequest(args [2]string, argsEscaped bool, ID: "AddProfileToBot", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, AddProfileToBotOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeAddProfileToBotParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -248,6 +292,50 @@ func (s *Server) handleCreateBotRequest(args [0]string, argsEscaped bool, w http ID: "CreateBot", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, CreateBotOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte request, rawBody, close, err := s.decodeCreateBotRequest(r) @@ -389,6 +477,50 @@ func (s *Server) handleDeleteBotByIdRequest(args [1]string, argsEscaped bool, w ID: "DeleteBotById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, DeleteBotByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeDeleteBotByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -530,6 +662,50 @@ func (s *Server) handleGetBotByIdRequest(args [1]string, argsEscaped bool, w htt ID: "GetBotById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, GetBotByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeGetBotByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -671,6 +847,50 @@ func (s *Server) handleGetBotProfilesRequest(args [1]string, argsEscaped bool, w ID: "GetBotProfiles", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, GetBotProfilesOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeGetBotProfilesParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -806,8 +1026,56 @@ func (s *Server) handleListBotsRequest(args [0]string, argsEscaped bool, w http. s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) } - err error + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListBotsOperation, + ID: "ListBots", + } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, ListBotsOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte @@ -934,6 +1202,50 @@ func (s *Server) handleRemoveProfileFromBotRequest(args [2]string, argsEscaped b ID: "RemoveProfileFromBot", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, RemoveProfileFromBotOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeRemoveProfileFromBotParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -1079,6 +1391,50 @@ func (s *Server) handleSearchBotsRequest(args [0]string, argsEscaped bool, w htt ID: "SearchBots", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, SearchBotsOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeSearchBotsParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -1214,8 +1570,56 @@ func (s *Server) handleSummaryBotsRequest(args [0]string, argsEscaped bool, w ht s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) } - err error + err error + opErrContext = ogenerrors.OperationContext{ + Name: SummaryBotsOperation, + ID: "SummaryBots", + } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, SummaryBotsOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte @@ -1342,6 +1746,50 @@ func (s *Server) handleUpdateBotByIdRequest(args [1]string, argsEscaped bool, w ID: "UpdateBotById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, UpdateBotByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeUpdateBotByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ diff --git a/internal/gen/bots/oas_schemas_gen.go b/internal/gen/bots/oas_schemas_gen.go index 0879a43..04f54bb 100644 --- a/internal/gen/bots/oas_schemas_gen.go +++ b/internal/gen/bots/oas_schemas_gen.go @@ -643,6 +643,31 @@ type SearchBotsInternalServerError Error func (*SearchBotsInternalServerError) searchBotsRes() {} +type SessionAuth struct { + APIKey string + Roles []string +} + +// GetAPIKey returns the value of APIKey. +func (s *SessionAuth) GetAPIKey() string { + return s.APIKey +} + +// GetRoles returns the value of Roles. +func (s *SessionAuth) GetRoles() []string { + return s.Roles +} + +// SetAPIKey sets the value of APIKey. +func (s *SessionAuth) SetAPIKey(val string) { + s.APIKey = val +} + +// SetRoles sets the value of Roles. +func (s *SessionAuth) SetRoles(val []string) { + s.Roles = val +} + type UpdateBotByIdBadRequest Error func (*UpdateBotByIdBadRequest) updateBotByIdRes() {} diff --git a/internal/gen/bots/oas_server_gen.go b/internal/gen/bots/oas_server_gen.go index 8191b0b..c84ee03 100644 --- a/internal/gen/bots/oas_server_gen.go +++ b/internal/gen/bots/oas_server_gen.go @@ -73,18 +73,20 @@ type Handler interface { // Server implements http server based on OpenAPI v3 specification and // calls Handler to handle requests. type Server struct { - h Handler + h Handler + sec SecurityHandler baseServer } // NewServer creates new Server. -func NewServer(h Handler, opts ...ServerOption) (*Server, error) { +func NewServer(h Handler, sec SecurityHandler, opts ...ServerOption) (*Server, error) { s, err := newServerConfig(opts...).baseServer() if err != nil { return nil, err } return &Server{ h: h, + sec: sec, baseServer: s, }, nil } diff --git a/internal/gen/posts/posts_command/oas_client_gen.go b/internal/gen/posts/posts_command/oas_client_gen.go index e17df73..92906c8 100644 --- a/internal/gen/posts/posts_command/oas_client_gen.go +++ b/internal/gen/posts/posts_command/oas_client_gen.go @@ -11,6 +11,7 @@ import ( "github.com/go-faster/errors" "github.com/ogen-go/ogen/conv" ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/ogenerrors" "github.com/ogen-go/ogen/otelogen" "github.com/ogen-go/ogen/uri" "go.opentelemetry.io/otel/attribute" @@ -63,6 +64,7 @@ type Invoker interface { // Client implements OAS client. type Client struct { serverURL *url.URL + sec SecuritySource baseClient } @@ -71,7 +73,7 @@ var _ Handler = struct { }{} // NewClient initializes new Client defined by OAS. -func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { +func NewClient(serverURL string, sec SecuritySource, opts ...ClientOption) (*Client, error) { u, err := url.Parse(serverURL) if err != nil { return nil, err @@ -84,6 +86,7 @@ func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { } return &Client{ serverURL: u, + sec: sec, baseClient: c, }, nil } @@ -163,6 +166,39 @@ func (c *Client) sendCreatePost(ctx context.Context, request *PostInput) (res Cr return res, errors.Wrap(err, "encode request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, CreatePostOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -254,6 +290,39 @@ func (c *Client) sendDeletePostById(ctx context.Context, params DeletePostByIdPa return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, DeletePostByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -349,6 +418,39 @@ func (c *Client) sendPublishPost(ctx context.Context, request *PublishPostReques return res, errors.Wrap(err, "encode request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, PublishPostOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -426,6 +528,39 @@ func (c *Client) sendSeenPosts(ctx context.Context, request *PostsSeenRequest) ( return res, errors.Wrap(err, "encode request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, SeenPostsOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -520,6 +655,39 @@ func (c *Client) sendUpdatePostById(ctx context.Context, request *PostUpdate, pa return res, errors.Wrap(err, "encode request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, UpdatePostByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { diff --git a/internal/gen/posts/posts_command/oas_handlers_gen.go b/internal/gen/posts/posts_command/oas_handlers_gen.go index 663dd4f..372a65f 100644 --- a/internal/gen/posts/posts_command/oas_handlers_gen.go +++ b/internal/gen/posts/posts_command/oas_handlers_gen.go @@ -103,6 +103,50 @@ func (s *Server) handleCreatePostRequest(args [0]string, argsEscaped bool, w htt ID: "createPost", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, CreatePostOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte request, rawBody, close, err := s.decodeCreatePostRequest(r) @@ -244,6 +288,50 @@ func (s *Server) handleDeletePostByIdRequest(args [1]string, argsEscaped bool, w ID: "deletePostById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, DeletePostByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeDeletePostByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -385,6 +473,50 @@ func (s *Server) handlePublishPostRequest(args [1]string, argsEscaped bool, w ht ID: "publishPost", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, PublishPostOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodePublishPostParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -542,6 +674,50 @@ func (s *Server) handleSeenPostsRequest(args [0]string, argsEscaped bool, w http ID: "seenPosts", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, SeenPostsOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte request, rawBody, close, err := s.decodeSeenPostsRequest(r) @@ -683,6 +859,50 @@ func (s *Server) handleUpdatePostByIdRequest(args [1]string, argsEscaped bool, w ID: "updatePostById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, UpdatePostByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeUpdatePostByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ diff --git a/internal/gen/posts/posts_command/oas_schemas_gen.go b/internal/gen/posts/posts_command/oas_schemas_gen.go index 0582e44..0f504aa 100644 --- a/internal/gen/posts/posts_command/oas_schemas_gen.go +++ b/internal/gen/posts/posts_command/oas_schemas_gen.go @@ -951,6 +951,31 @@ type SeenPostsNotFound Error func (*SeenPostsNotFound) seenPostsRes() {} +type SessionAuth struct { + APIKey string + Roles []string +} + +// GetAPIKey returns the value of APIKey. +func (s *SessionAuth) GetAPIKey() string { + return s.APIKey +} + +// GetRoles returns the value of Roles. +func (s *SessionAuth) GetRoles() []string { + return s.Roles +} + +// SetAPIKey sets the value of APIKey. +func (s *SessionAuth) SetAPIKey(val string) { + s.APIKey = val +} + +// SetRoles sets the value of Roles. +func (s *SessionAuth) SetRoles(val []string) { + s.Roles = val +} + type UpdatePostByIdBadRequest Error func (*UpdatePostByIdBadRequest) updatePostByIdRes() {} diff --git a/internal/gen/posts/posts_command/oas_server_gen.go b/internal/gen/posts/posts_command/oas_server_gen.go index 8145bef..6af0d36 100644 --- a/internal/gen/posts/posts_command/oas_server_gen.go +++ b/internal/gen/posts/posts_command/oas_server_gen.go @@ -44,18 +44,20 @@ type Handler interface { // Server implements http server based on OpenAPI v3 specification and // calls Handler to handle requests. type Server struct { - h Handler + h Handler + sec SecurityHandler baseServer } // NewServer creates new Server. -func NewServer(h Handler, opts ...ServerOption) (*Server, error) { +func NewServer(h Handler, sec SecurityHandler, opts ...ServerOption) (*Server, error) { s, err := newServerConfig(opts...).baseServer() if err != nil { return nil, err } return &Server{ h: h, + sec: sec, baseServer: s, }, nil } diff --git a/internal/gen/posts/posts_query/oas_client_gen.go b/internal/gen/posts/posts_query/oas_client_gen.go index 615c98d..db5fadb 100644 --- a/internal/gen/posts/posts_query/oas_client_gen.go +++ b/internal/gen/posts/posts_query/oas_client_gen.go @@ -11,6 +11,7 @@ import ( "github.com/go-faster/errors" "github.com/ogen-go/ogen/conv" ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/ogenerrors" "github.com/ogen-go/ogen/otelogen" "github.com/ogen-go/ogen/uri" "go.opentelemetry.io/otel/attribute" @@ -56,6 +57,7 @@ type Invoker interface { // Client implements OAS client. type Client struct { serverURL *url.URL + sec SecuritySource baseClient } @@ -64,7 +66,7 @@ var _ Handler = struct { }{} // NewClient initializes new Client defined by OAS. -func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { +func NewClient(serverURL string, sec SecuritySource, opts ...ClientOption) (*Client, error) { u, err := url.Parse(serverURL) if err != nil { return nil, err @@ -77,6 +79,7 @@ func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { } return &Client{ serverURL: u, + sec: sec, baseClient: c, }, nil } @@ -171,6 +174,39 @@ func (c *Client) sendCheckGroupId(ctx context.Context, params CheckGroupIdParams return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, CheckGroupIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -244,6 +280,39 @@ func (c *Client) sendCheckGroupIds(ctx context.Context) (res CheckGroupIdsRes, e return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, CheckGroupIdsOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -335,6 +404,39 @@ func (c *Client) sendGetPostById(ctx context.Context, params GetPostByIdParams) return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, GetPostByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -408,6 +510,39 @@ func (c *Client) sendListPosts(ctx context.Context) (res ListPostsRes, err error return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, ListPostsOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { diff --git a/internal/gen/posts/posts_query/oas_handlers_gen.go b/internal/gen/posts/posts_query/oas_handlers_gen.go index 1925892..fd3b92a 100644 --- a/internal/gen/posts/posts_query/oas_handlers_gen.go +++ b/internal/gen/posts/posts_query/oas_handlers_gen.go @@ -103,6 +103,50 @@ func (s *Server) handleCheckGroupIdRequest(args [1]string, argsEscaped bool, w h ID: "checkGroupId", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, CheckGroupIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeCheckGroupIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -238,8 +282,56 @@ func (s *Server) handleCheckGroupIdsRequest(args [0]string, argsEscaped bool, w s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) } - err error + err error + opErrContext = ogenerrors.OperationContext{ + Name: CheckGroupIdsOperation, + ID: "checkGroupIds", + } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, CheckGroupIdsOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte @@ -366,6 +458,50 @@ func (s *Server) handleGetPostByIdRequest(args [1]string, argsEscaped bool, w ht ID: "getPostById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, GetPostByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeGetPostByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -501,8 +637,56 @@ func (s *Server) handleListPostsRequest(args [0]string, argsEscaped bool, w http s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) } - err error + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListPostsOperation, + ID: "listPosts", + } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, ListPostsOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte diff --git a/internal/gen/posts/posts_query/oas_schemas_gen.go b/internal/gen/posts/posts_query/oas_schemas_gen.go index bf6baeb..62db651 100644 --- a/internal/gen/posts/posts_query/oas_schemas_gen.go +++ b/internal/gen/posts/posts_query/oas_schemas_gen.go @@ -586,3 +586,28 @@ func (s *PostsCheckObject) SetGroupID(val uuid.UUID) { func (s *PostsCheckObject) SetIsReady(val bool) { s.IsReady = val } + +type SessionAuth struct { + APIKey string + Roles []string +} + +// GetAPIKey returns the value of APIKey. +func (s *SessionAuth) GetAPIKey() string { + return s.APIKey +} + +// GetRoles returns the value of Roles. +func (s *SessionAuth) GetRoles() []string { + return s.Roles +} + +// SetAPIKey sets the value of APIKey. +func (s *SessionAuth) SetAPIKey(val string) { + s.APIKey = val +} + +// SetRoles sets the value of Roles. +func (s *SessionAuth) SetRoles(val []string) { + s.Roles = val +} diff --git a/internal/gen/posts/posts_query/oas_server_gen.go b/internal/gen/posts/posts_query/oas_server_gen.go index 7630025..452e586 100644 --- a/internal/gen/posts/posts_query/oas_server_gen.go +++ b/internal/gen/posts/posts_query/oas_server_gen.go @@ -37,18 +37,20 @@ type Handler interface { // Server implements http server based on OpenAPI v3 specification and // calls Handler to handle requests. type Server struct { - h Handler + h Handler + sec SecurityHandler baseServer } // NewServer creates new Server. -func NewServer(h Handler, opts ...ServerOption) (*Server, error) { +func NewServer(h Handler, sec SecurityHandler, opts ...ServerOption) (*Server, error) { s, err := newServerConfig(opts...).baseServer() if err != nil { return nil, err } return &Server{ h: h, + sec: sec, baseServer: s, }, nil } diff --git a/internal/gen/profiles/oas_client_gen.go b/internal/gen/profiles/oas_client_gen.go index 2bcd40e..d9beb9b 100644 --- a/internal/gen/profiles/oas_client_gen.go +++ b/internal/gen/profiles/oas_client_gen.go @@ -11,6 +11,7 @@ import ( "github.com/go-faster/errors" "github.com/ogen-go/ogen/conv" ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/ogenerrors" "github.com/ogen-go/ogen/otelogen" "github.com/ogen-go/ogen/uri" "go.opentelemetry.io/otel/attribute" @@ -68,6 +69,7 @@ type Invoker interface { // Client implements OAS client. type Client struct { serverURL *url.URL + sec SecuritySource baseClient } @@ -76,7 +78,7 @@ var _ Handler = struct { }{} // NewClient initializes new Client defined by OAS. -func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { +func NewClient(serverURL string, sec SecuritySource, opts ...ClientOption) (*Client, error) { u, err := url.Parse(serverURL) if err != nil { return nil, err @@ -89,6 +91,7 @@ func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { } return &Client{ serverURL: u, + sec: sec, baseClient: c, }, nil } @@ -169,6 +172,39 @@ func (c *Client) sendCreateMyProfile(ctx context.Context, request *ProfileInput) return res, errors.Wrap(err, "encode request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, CreateMyProfileOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -261,6 +297,39 @@ func (c *Client) sendDeleteProfileById(ctx context.Context, params DeleteProfile return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, DeleteProfileByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -353,6 +422,39 @@ func (c *Client) sendGetProfileById(ctx context.Context, params GetProfileByIdPa return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, GetProfileByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -428,6 +530,39 @@ func (c *Client) sendListMyProfiles(ctx context.Context) (res ListMyProfilesRes, return res, errors.Wrap(err, "create request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, ListMyProfilesOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { @@ -523,6 +658,39 @@ func (c *Client) sendUpdateProfileById(ctx context.Context, request *ProfileInpu return res, errors.Wrap(err, "encode request") } + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:SessionAuth" + switch err := c.securitySessionAuth(ctx, UpdateProfileByIdOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"SessionAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + stage = "SendRequest" resp, err := c.cfg.Client.Do(r) if err != nil { diff --git a/internal/gen/profiles/oas_handlers_gen.go b/internal/gen/profiles/oas_handlers_gen.go index 7fb0eff..fed26ce 100644 --- a/internal/gen/profiles/oas_handlers_gen.go +++ b/internal/gen/profiles/oas_handlers_gen.go @@ -104,6 +104,50 @@ func (s *Server) handleCreateMyProfileRequest(args [0]string, argsEscaped bool, ID: "createMyProfile", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, CreateMyProfileOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte request, rawBody, close, err := s.decodeCreateMyProfileRequest(r) @@ -246,6 +290,50 @@ func (s *Server) handleDeleteProfileByIdRequest(args [1]string, argsEscaped bool ID: "deleteProfileById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, DeleteProfileByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeDeleteProfileByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -388,6 +476,50 @@ func (s *Server) handleGetProfileByIdRequest(args [1]string, argsEscaped bool, w ID: "getProfileById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, GetProfileByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeGetProfileByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ @@ -525,8 +657,56 @@ func (s *Server) handleListMyProfilesRequest(args [0]string, argsEscaped bool, w s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) } - err error + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListMyProfilesOperation, + ID: "listMyProfiles", + } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, ListMyProfilesOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } var rawBody []byte @@ -654,6 +834,50 @@ func (s *Server) handleUpdateProfileByIdRequest(args [1]string, argsEscaped bool ID: "updateProfileById", } ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securitySessionAuth(ctx, UpdateProfileByIdOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "SessionAuth", + Err: err, + } + defer recordError("Security:SessionAuth", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + defer recordError("Security", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + } params, err := decodeUpdateProfileByIdParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ diff --git a/internal/gen/profiles/oas_schemas_gen.go b/internal/gen/profiles/oas_schemas_gen.go index 2dfaf80..fed69fa 100644 --- a/internal/gen/profiles/oas_schemas_gen.go +++ b/internal/gen/profiles/oas_schemas_gen.go @@ -349,6 +349,31 @@ func (s *ProfileList) SetData(val []Profile) { func (*ProfileList) listMyProfilesRes() {} +type SessionAuth struct { + APIKey string + Roles []string +} + +// GetAPIKey returns the value of APIKey. +func (s *SessionAuth) GetAPIKey() string { + return s.APIKey +} + +// GetRoles returns the value of Roles. +func (s *SessionAuth) GetRoles() []string { + return s.Roles +} + +// SetAPIKey sets the value of APIKey. +func (s *SessionAuth) SetAPIKey(val string) { + s.APIKey = val +} + +// SetRoles sets the value of Roles. +func (s *SessionAuth) SetRoles(val []string) { + s.Roles = val +} + type UpdateProfileByIdBadRequest Error func (*UpdateProfileByIdBadRequest) updateProfileByIdRes() {} diff --git a/internal/gen/profiles/oas_server_gen.go b/internal/gen/profiles/oas_server_gen.go index 719892f..c04a69b 100644 --- a/internal/gen/profiles/oas_server_gen.go +++ b/internal/gen/profiles/oas_server_gen.go @@ -49,18 +49,20 @@ type Handler interface { // Server implements http server based on OpenAPI v3 specification and // calls Handler to handle requests. type Server struct { - h Handler + h Handler + sec SecurityHandler baseServer } // NewServer creates new Server. -func NewServer(h Handler, opts ...ServerOption) (*Server, error) { +func NewServer(h Handler, sec SecurityHandler, opts ...ServerOption) (*Server, error) { s, err := newServerConfig(opts...).baseServer() if err != nil { return nil, err } return &Server{ h: h, + sec: sec, baseServer: s, }, nil } diff --git a/internal/model/posts.go b/internal/model/posts.go index 23a8f62..dc9fbde 100644 --- a/internal/model/posts.go +++ b/internal/model/posts.go @@ -7,12 +7,13 @@ import ( ) type PlatformType string -type PostType string const ( OtvetiPlatform PlatformType = "otveti" ) +type PostType string + const ( OpinionPostType PostType = "opinion" KnowledgePostType PostType = "knowledge" @@ -21,22 +22,22 @@ const ( type Post struct { ID uuid.UUID + UserID uuid.UUID OtvetiID uint64 BotID uuid.UUID BotName string ProfileID uuid.UUID ProfileName string GroupID uuid.UUID + UserPrompt string Platform PlatformType Type PostType - UserPrompt string Title string Text string IsSeen bool CreatedAt time.Time UpdatedAt time.Time } - type Category struct { ID uuid.UUID Name string diff --git a/internal/repo/bots/create.go b/internal/repo/bots/create.go index f74e104..ae11d39 100644 --- a/internal/repo/bots/create.go +++ b/internal/repo/bots/create.go @@ -4,21 +4,29 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" + "github.com/goriiin/kotyari-bots_backend/internal/pkg/user" ) func (r BotsRepository) Create(ctx context.Context, b model.Bot) error { - _, err := r.db.Exec(ctx, ` + userID, err := user.GetID(ctx) + if err != nil { + return err + } + + _, err = r.db.Exec(ctx, ` INSERT INTO bots ( id, bot_name, system_prompt, moderation_required, profile_ids, - profiles_count) + profiles_count, + user_id) VALUES ( $1, $2, $3, $4, $5::uuid[], - COALESCE(array_length($5::uuid[], 1), 0) + COALESCE(array_length($5::uuid[], 1), 0), + $6 ) - `, b.ID, b.Name, b.SystemPrompt, b.ModerationRequired, b.ProfileIDs) + `, b.ID, b.Name, b.SystemPrompt, b.ModerationRequired, b.ProfileIDs, userID) return err } diff --git a/internal/repo/bots/delete.go b/internal/repo/bots/delete.go index 883c739..074aa68 100644 --- a/internal/repo/bots/delete.go +++ b/internal/repo/bots/delete.go @@ -4,9 +4,22 @@ import ( "context" "github.com/google/uuid" + "github.com/goriiin/kotyari-bots_backend/pkg/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) func (r *BotsRepository) Delete(ctx context.Context, id uuid.UUID) error { - _, err := r.db.Exec(ctx, `UPDATE bots SET is_deleted = true, updated_at = NOW() WHERE id=$1`, id) - return err + userID, err := user.GetID(ctx) + if err != nil { + return err + } + + tag, err := r.db.Exec(ctx, `UPDATE bots SET is_deleted = true, updated_at = NOW() WHERE id=$1 AND user_id=$2`, id, userID) + if err != nil { + return err + } + if tag.RowsAffected() == 0 { + return constants.ErrNotFound + } + return nil } diff --git a/internal/repo/bots/get.go b/internal/repo/bots/get.go index ff228c7..b0a3961 100644 --- a/internal/repo/bots/get.go +++ b/internal/repo/bots/get.go @@ -7,20 +7,40 @@ import ( "github.com/google/uuid" "github.com/goriiin/kotyari-bots_backend/internal/model" "github.com/goriiin/kotyari-bots_backend/pkg/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "github.com/jackc/pgx/v5" ) func (r BotsRepository) Get(ctx context.Context, id uuid.UUID) (model.Bot, error) { + // gRPC вызовы от других сервисов могут не иметь контекста пользователя. + // Если user_id нет, делаем запрос только по ID (внутреннее доверие). + // Если user_id есть (HTTP API), фильтруем по нему. + + if userID, err := user.GetID(ctx); err == nil { + rows, err := r.db.Query(ctx, ` + SELECT + id, bot_name, system_prompt, moderation_required, + profile_ids, profiles_count, created_at, updated_at + FROM bots + WHERE id = $1 AND is_deleted = false AND user_id = $2 + `, id, userID) + if err != nil { + return model.Bot{}, err + } + dto, err := pgx.CollectOneRow(rows, pgx.RowToStructByPos[botDTO]) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return model.Bot{}, constants.ErrNotFound + } + return model.Bot{}, err + } + return dto.toModel(), nil + } + rows, err := r.db.Query(ctx, ` SELECT - id, - bot_name, - system_prompt, - moderation_required, - profile_ids, - profiles_count, - created_at, - updated_at + id, bot_name, system_prompt, moderation_required, + profile_ids, profiles_count, created_at, updated_at FROM bots WHERE id = $1 AND is_deleted = false `, id) diff --git a/internal/repo/bots/list.go b/internal/repo/bots/list.go index e3007a3..78f0da1 100644 --- a/internal/repo/bots/list.go +++ b/internal/repo/bots/list.go @@ -4,10 +4,16 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" + "github.com/goriiin/kotyari-bots_backend/internal/pkg/user" "github.com/jackc/pgx/v5" ) func (r BotsRepository) List(ctx context.Context) ([]model.Bot, error) { + userID, err := user.GetID(ctx) + if err != nil { + return nil, err + } + rows, err := r.db.Query(ctx, ` SELECT id, @@ -19,9 +25,9 @@ func (r BotsRepository) List(ctx context.Context) ([]model.Bot, error) { created_at, updated_at FROM bots - WHERE is_deleted = false + WHERE is_deleted = false AND user_id = $1 ORDER BY created_at DESC - `) + `, userID) if err != nil { return nil, err } diff --git a/internal/repo/bots/profiles.go b/internal/repo/bots/profiles.go index 5086a7f..b2a3a3c 100644 --- a/internal/repo/bots/profiles.go +++ b/internal/repo/bots/profiles.go @@ -5,18 +5,24 @@ import ( "github.com/google/uuid" "github.com/goriiin/kotyari-bots_backend/pkg/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) func (r BotsRepository) AddProfileID(ctx context.Context, botID, profileID uuid.UUID) error { + userID, err := user.GetID(ctx) + if err != nil { + return err + } + tag, err := r.db.Exec(ctx, ` UPDATE bots SET profile_ids = array_append(COALESCE(profile_ids, '{}'::uuid[]), $2), profiles_count = COALESCE(array_length(array_append(COALESCE(profile_ids, '{}'::uuid[]), $2), 1), 0), updated_at = now() - WHERE id = $1 + WHERE id = $1 AND user_id = $3 AND NOT $2 = ANY(COALESCE(profile_ids, '{}'::uuid[])) - `, botID, profileID) + `, botID, profileID, userID) if err != nil { return err } @@ -27,14 +33,19 @@ func (r BotsRepository) AddProfileID(ctx context.Context, botID, profileID uuid. } func (r BotsRepository) RemoveProfileID(ctx context.Context, botID, profileID uuid.UUID) error { + userID, err := user.GetID(ctx) + if err != nil { + return err + } + tag, err := r.db.Exec(ctx, ` UPDATE bots SET profile_ids = array_remove(COALESCE(profile_ids, '{}'::uuid[]), $2), profiles_count = COALESCE(array_length(array_remove(COALESCE(profile_ids, '{}'::uuid[]), $2), 1), 0), updated_at = now() - WHERE id = $1 - `, botID, profileID) + WHERE id = $1 AND user_id = $3 + `, botID, profileID, userID) if err != nil { return err } diff --git a/internal/repo/bots/search.go b/internal/repo/bots/search.go index d6fe6e3..b3a852b 100644 --- a/internal/repo/bots/search.go +++ b/internal/repo/bots/search.go @@ -8,6 +8,11 @@ import ( ) func (r BotsRepository) Search(ctx context.Context, query string) ([]model.Bot, error) { + userID, err := user.GetID(ctx) + if err != nil { + return nil, err + } + rows, err := r.db.Query(ctx, ` SELECT id, @@ -19,10 +24,11 @@ func (r BotsRepository) Search(ctx context.Context, query string) ([]model.Bot, created_at, updated_at FROM bots - WHERE isdeleted = false - AND (botname ILIKE $1 OR systemprompt ILIKE $1) - ORDER BY createdat DESC - `, query) + WHERE is_deleted = false + AND user_id = $2 + AND (bot_name ILIKE $1 OR system_prompt ILIKE $1) + ORDER BY created_at DESC + `, query, userID) if err != nil { return nil, err } diff --git a/internal/repo/bots/summary.go b/internal/repo/bots/summary.go index 9b3d485..37fa5c2 100644 --- a/internal/repo/bots/summary.go +++ b/internal/repo/bots/summary.go @@ -7,11 +7,16 @@ import ( ) func (r *BotsRepository) GetSummary(ctx context.Context) (model.BotsSummary, error) { + userID, err := user.GetID(ctx) + if err != nil { + return model.BotsSummary{}, err + } + var summary model.BotsSummary - err := r.db.QueryRow(ctx, + err = r.db.QueryRow(ctx, `SELECT COUNT(*), COALESCE(SUM(profiles_count), 0) FROM bots - WHERE is_deleted = false`). + WHERE is_deleted = false AND user_id = $1`, userID). Scan(&summary.TotalBots, &summary.TotalProfilesAttached) if err != nil { return model.BotsSummary{}, err diff --git a/internal/repo/bots/update.go b/internal/repo/bots/update.go index dc6449e..5f5d0c8 100644 --- a/internal/repo/bots/update.go +++ b/internal/repo/bots/update.go @@ -4,10 +4,17 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" + "github.com/goriiin/kotyari-bots_backend/pkg/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) func (r *BotsRepository) Update(ctx context.Context, b model.Bot) error { - _, err := r.db.Exec(ctx, + userID, err := user.GetID(ctx) + if err != nil { + return err + } + + tag, err := r.db.Exec(ctx, `UPDATE bots SET bot_name = $2, system_prompt = $3, @@ -15,8 +22,14 @@ func (r *BotsRepository) Update(ctx context.Context, b model.Bot) error { profile_ids = $5, profiles_count = $6, updated_at = now() - WHERE id = $1`, - b.ID, b.Name, b.SystemPrompt, b.ModerationRequired, b.ProfileIDs, len(b.ProfileIDs), + WHERE id = $1 AND user_id = $7`, + b.ID, b.Name, b.SystemPrompt, b.ModerationRequired, b.ProfileIDs, len(b.ProfileIDs), userID, ) - return err + if err != nil { + return err + } + if tag.RowsAffected() == 0 { + return constants.ErrNotFound + } + return nil } diff --git a/internal/repo/posts/posts_command/create_posts_batch.go b/internal/repo/posts/posts_command/create_posts_batch.go index b4693c2..d5ab26b 100644 --- a/internal/repo/posts/posts_command/create_posts_batch.go +++ b/internal/repo/posts/posts_command/create_posts_batch.go @@ -11,9 +11,10 @@ import ( ) func (p *PostsCommandRepo) CreatePostsBatch(ctx context.Context, posts []model.Post) (err error) { + // Note: 'posts' here comes from Kafka Consumer which should have populated UserID in the model const query = ` - INSERT INTO posts (id, otveti_id, bot_id, bot_name, profile_id, profile_name, group_id, user_prompt, platform_type, post_type, post_title, post_text) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) + INSERT INTO posts (id, user_id, otveti_id, bot_id, bot_name, profile_id, profile_name, group_id, user_prompt, platform_type, post_type, post_title, post_text) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) ` batch := &pgx.Batch{} @@ -21,6 +22,7 @@ func (p *PostsCommandRepo) CreatePostsBatch(ctx context.Context, posts []model.P for _, post := range posts { batch.Queue(query, post.ID, + post.UserID, // Need to add UserID to model.Post post.OtvetiID, post.BotID, post.BotName, diff --git a/internal/repo/posts/posts_query/check_group_ids.go b/internal/repo/posts/posts_query/check_group_ids.go index 45ab502..2dbb66e 100644 --- a/internal/repo/posts/posts_query/check_group_ids.go +++ b/internal/repo/posts/posts_query/check_group_ids.go @@ -7,16 +7,23 @@ import ( "github.com/goriiin/kotyari-bots_backend/internal/model" "github.com/goriiin/kotyari-bots_backend/internal/repo/posts" "github.com/goriiin/kotyari-bots_backend/pkg/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "github.com/jackc/pgx/v5" ) func (p *PostsQueryRepo) CheckGroupIds(ctx context.Context) ([]model.Post, error) { + userID, err := user.GetID(ctx) + if err != nil { + return nil, err + } + const query = ` SELECT id, group_id, post_title, post_text, is_seen FROM posts + WHERE user_id = $1 ` - rows, err := p.db.Query(ctx, query) + rows, err := p.db.Query(ctx, query, userID) if err != nil { return nil, errors.Wrapf(constants.ErrInternal, "failed to query rows: %s", err.Error()) } diff --git a/internal/repo/posts/posts_query/get_by_group_id.go b/internal/repo/posts/posts_query/get_by_group_id.go index 0a81a10..0c3c18e 100644 --- a/internal/repo/posts/posts_query/get_by_group_id.go +++ b/internal/repo/posts/posts_query/get_by_group_id.go @@ -2,25 +2,30 @@ package posts_query import ( "context" - "fmt" "github.com/go-faster/errors" "github.com/google/uuid" "github.com/goriiin/kotyari-bots_backend/internal/model" "github.com/goriiin/kotyari-bots_backend/internal/repo/posts" "github.com/goriiin/kotyari-bots_backend/pkg/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "github.com/jackc/pgx/v5" ) func (p *PostsQueryRepo) GetByGroupId(ctx context.Context, groupID uuid.UUID) ([]model.Post, error) { + userID, err := user.GetID(ctx) + if err != nil { + return nil, err + } + const query = ` SELECT id, otveti_id, group_id, user_prompt, bot_id, bot_name, profile_id, profile_name, platform_type::text, post_type::text, post_title, post_text, created_at, updated_at FROM posts - WHERE group_id = $1 + WHERE group_id = $1 AND user_id = $2 ` - rows, err := p.db.Query(ctx, query, groupID) + rows, err := p.db.Query(ctx, query, groupID, userID) if err != nil { return nil, errors.Wrapf(constants.ErrInternal, "failed to query rows: %s", err.Error()) } @@ -28,11 +33,9 @@ func (p *PostsQueryRepo) GetByGroupId(ctx context.Context, groupID uuid.UUID) ([ postsDTO, err := pgx.CollectRows(rows, pgx.RowToStructByName[posts.PostDTO]) if err != nil { - fmt.Println(postsDTO) if errors.Is(err, pgx.ErrNoRows) { return nil, constants.ErrNotFound } - return nil, errors.Wrapf(constants.ErrInternal, "failed to collect rows: %s", err) } diff --git a/internal/repo/posts/posts_query/get_post_by_id.go b/internal/repo/posts/posts_query/get_post_by_id.go index df53e82..724e8d3 100644 --- a/internal/repo/posts/posts_query/get_post_by_id.go +++ b/internal/repo/posts/posts_query/get_post_by_id.go @@ -8,18 +8,24 @@ import ( "github.com/goriiin/kotyari-bots_backend/internal/model" "github.com/goriiin/kotyari-bots_backend/internal/repo/posts" "github.com/goriiin/kotyari-bots_backend/pkg/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "github.com/jackc/pgx/v5" ) func (p *PostsQueryRepo) GetByID(ctx context.Context, id uuid.UUID) (model.Post, error) { + userID, err := user.GetID(ctx) + if err != nil { + return model.Post{}, err + } + const query = ` SELECT id, otveti_id, group_id, user_prompt, bot_id, bot_name, profile_id, profile_name, platform_type::text, post_type::text, post_title, post_text, created_at, updated_at FROM posts - WHERE id = $1 + WHERE id = $1 AND user_id = $2 ` - rows, err := p.db.Query(ctx, query, id) + rows, err := p.db.Query(ctx, query, id, userID) if err != nil { return model.Post{}, errors.Wrapf(constants.ErrInternal, "failed to query row: %s", err.Error()) } diff --git a/internal/repo/posts/posts_query/list_posts.go b/internal/repo/posts/posts_query/list_posts.go index 70f1974..68e8d11 100644 --- a/internal/repo/posts/posts_query/list_posts.go +++ b/internal/repo/posts/posts_query/list_posts.go @@ -7,17 +7,25 @@ import ( "github.com/goriiin/kotyari-bots_backend/internal/model" "github.com/goriiin/kotyari-bots_backend/internal/repo/posts" "github.com/goriiin/kotyari-bots_backend/pkg/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "github.com/jackc/pgx/v5" ) func (p *PostsQueryRepo) ListPosts(ctx context.Context) ([]model.Post, error) { + userID, err := user.GetID(ctx) + if err != nil { + return nil, err + } + const query = ` SELECT id, otveti_id, group_id, user_prompt, bot_id, bot_name, profile_id, profile_name, platform_type::text, post_type::text, post_title, post_text, created_at, updated_at FROM posts + WHERE user_id = $1 + ORDER BY created_at DESC ` - rows, err := p.db.Query(ctx, query) + rows, err := p.db.Query(ctx, query, userID) if err != nil { return nil, errors.Wrapf(constants.ErrInternal, "failed to query rows: %s", err.Error()) } @@ -26,9 +34,9 @@ func (p *PostsQueryRepo) ListPosts(ctx context.Context) ([]model.Post, error) { postsDTO, err := pgx.CollectRows(rows, pgx.RowToStructByName[posts.PostDTO]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - return nil, constants.ErrNotFound + // Empty list is not an error usually, but strict mapping might behave differently + return []model.Post{}, nil } - return nil, errors.Wrapf(constants.ErrInternal, "failed to collect rows: %s", err) } diff --git a/internal/repo/profiles/create.go b/internal/repo/profiles/create.go index 4776635..8e236b3 100644 --- a/internal/repo/profiles/create.go +++ b/internal/repo/profiles/create.go @@ -4,11 +4,16 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) func (r *Repository) Create(ctx context.Context, p model.Profile) error { - _, err := r.db.Exec(ctx, - `INSERT INTO profiles (id, name, email, system_prompt) VALUES ($1, $2, $3, $4)`, - p.ID, p.Name, p.Email, p.SystemPromt) + userID, err := user.GetID(ctx) + if err != nil { + return err + } + _, err = r.db.Exec(ctx, + `INSERT INTO profiles (id, name, email, system_prompt, user_id) VALUES ($1, $2, $3, $4, $5)`, + p.ID, p.Name, p.Email, p.SystemPromt, userID) return err } diff --git a/internal/repo/profiles/delete.go b/internal/repo/profiles/delete.go index 3162915..ac37218 100644 --- a/internal/repo/profiles/delete.go +++ b/internal/repo/profiles/delete.go @@ -4,9 +4,21 @@ import ( "context" "github.com/google/uuid" + "github.com/goriiin/kotyari-bots_backend/internal/constants" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) func (r *Repository) Delete(ctx context.Context, id uuid.UUID) error { - _, err := r.db.Exec(ctx, `DELETE FROM profiles WHERE id=$1`, id) - return err + userID, err := user.GetID(ctx) + if err != nil { + return err + } + tag, err := r.db.Exec(ctx, `DELETE FROM profiles WHERE id=$1 AND user_id=$2`, id, userID) + if err != nil { + return err + } + if tag.RowsAffected() == 0 { + return constants.ErrNotFound + } + return nil } diff --git a/internal/repo/profiles/get.go b/internal/repo/profiles/get.go index cf25ab6..da2e996 100644 --- a/internal/repo/profiles/get.go +++ b/internal/repo/profiles/get.go @@ -10,7 +10,11 @@ import ( ) func (r *Repository) GetByID(ctx context.Context, id uuid.UUID) (model.Profile, error) { - rows, err := r.db.Query(ctx, `SELECT id, name, email, system_prompt, created_at, updated_at FROM profiles WHERE id=$1`, id) + rows, err := r.db.Query(ctx, + `SELECT id, name, email, system_prompt, created_at, updated_at + FROM profiles + WHERE id=$1`, + id) if err != nil { return model.Profile{}, err } diff --git a/internal/repo/profiles/list.go b/internal/repo/profiles/list.go index b99ede2..f0f6638 100644 --- a/internal/repo/profiles/list.go +++ b/internal/repo/profiles/list.go @@ -4,11 +4,23 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" + "github.com/goriiin/kotyari-bots_backend/pkg/user" + "github.com/jackc/pgx/v5" ) func (r *Repository) List(ctx context.Context) ([]model.Profile, error) { - rows, err := r.db.Query(ctx, `SELECT id, name, email, system_prompt, created_at, updated_at FROM profiles ORDER BY created_at DESC`) + userID, err := user.GetID(ctx) + if err != nil { + return nil, err + } + + rows, err := r.db.Query(ctx, + `SELECT id, name, email, system_prompt, created_at, updated_at + FROM profiles + WHERE user_id=$1 + ORDER BY created_at DESC`, + userID) if err != nil { return nil, err } diff --git a/internal/repo/profiles/update.go b/internal/repo/profiles/update.go index 0be6ec8..207b055 100644 --- a/internal/repo/profiles/update.go +++ b/internal/repo/profiles/update.go @@ -3,12 +3,25 @@ package profiles import ( "context" + "github.com/goriiin/kotyari-bots_backend/internal/constants" "github.com/goriiin/kotyari-bots_backend/internal/model" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) func (r *Repository) Update(ctx context.Context, p model.Profile) error { - _, err := r.db.Exec(ctx, - `UPDATE profiles SET name=$2, email=$3, system_prompt=$4, updated_at=now() WHERE id=$1`, - p.ID, p.Name, p.Email, p.SystemPromt) - return err + userID, err := user.GetID(ctx) + if err != nil { + return err + } + tag, err := r.db.Exec(ctx, + `UPDATE profiles SET name=$2, email=$3, system_prompt=$4, updated_at=now() + WHERE id=$1 AND user_id=$5`, + p.ID, p.Name, p.Email, p.SystemPromt, userID) + if err != nil { + return err + } + if tag.RowsAffected() == 0 { + return constants.ErrNotFound + } + return nil } diff --git a/pkg/user/context.go b/pkg/user/context.go new file mode 100644 index 0000000..a3b47ab --- /dev/null +++ b/pkg/user/context.go @@ -0,0 +1,26 @@ +package user + +import ( + "context" + + "github.com/go-faster/errors" + "github.com/google/uuid" +) + +type ctxKey struct{} + +var userCtxKey = ctxKey{} + +var ErrNoUser = errors.New("user not found in context") + +func WithID(ctx context.Context, id uuid.UUID) context.Context { + return context.WithValue(ctx, userCtxKey, id) +} + +func GetID(ctx context.Context) (uuid.UUID, error) { + id, ok := ctx.Value(userCtxKey).(uuid.UUID) + if !ok { + return uuid.Nil, ErrNoUser + } + return id, nil +} From fd23efcc844241359bf145edca725c3e85bb2c7c Mon Sep 17 00:00:00 2001 From: goriiin Date: Sat, 10 Jan 2026 01:54:29 +0300 Subject: [PATCH 2/6] bot-75: fix --- internal/repo/bots/create.go | 2 +- internal/repo/bots/list.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/repo/bots/create.go b/internal/repo/bots/create.go index ae11d39..04c1025 100644 --- a/internal/repo/bots/create.go +++ b/internal/repo/bots/create.go @@ -4,7 +4,7 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" - "github.com/goriiin/kotyari-bots_backend/internal/pkg/user" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) func (r BotsRepository) Create(ctx context.Context, b model.Bot) error { diff --git a/internal/repo/bots/list.go b/internal/repo/bots/list.go index 78f0da1..1a92498 100644 --- a/internal/repo/bots/list.go +++ b/internal/repo/bots/list.go @@ -4,7 +4,7 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" - "github.com/goriiin/kotyari-bots_backend/internal/pkg/user" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "github.com/jackc/pgx/v5" ) From 4cf6de58b80eda0301ced00b03236d95df194a00 Mon Sep 17 00:00:00 2001 From: goriiin Date: Sat, 10 Jan 2026 18:53:28 +0300 Subject: [PATCH 3/6] bot-75: add --- .env.example | 13 ++- Makefile | 13 +++ configs/bots-local.yaml | 6 +- configs/posts-local.yaml | 8 +- configs/profiles-local.yaml | 6 +- internal/gen/bots/oas_security_gen.go | 87 +++++++++++++++++++ .../posts/posts_command/oas_security_gen.go | 82 +++++++++++++++++ .../gen/posts/posts_query/oas_security_gen.go | 81 +++++++++++++++++ internal/gen/profiles/oas_security_gen.go | 82 +++++++++++++++++ internal/repo/bots/search.go | 1 + internal/repo/bots/summary.go | 1 + 11 files changed, 375 insertions(+), 5 deletions(-) create mode 100644 internal/gen/bots/oas_security_gen.go create mode 100644 internal/gen/posts/posts_command/oas_security_gen.go create mode 100644 internal/gen/posts/posts_query/oas_security_gen.go create mode 100644 internal/gen/profiles/oas_security_gen.go diff --git a/.env.example b/.env.example index 247ae53..f0fef86 100644 --- a/.env.example +++ b/.env.example @@ -45,4 +45,15 @@ LOCAL_AGGREGATOR_DATABASE_PORT=5432 LOCAL_AGGREGATOR_DATABASE_HOST=aggregator_db GF_USER=admin -GF_PASSWORD=admin \ No newline at end of file +GF_PASSWORD=admin + + +DATABASE_URL= +USER_DB_USER= +USER_DB_PASSWORD= +USER_DB_NAME= +REDIS_PASS= +REDIS_URL= +HOST= +PORT= +GRPC= diff --git a/Makefile b/Makefile index 76276b9..e9023ae 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ DOMAIN := writehub.space EMAIL := admin@writehub.space NGINX_COMPOSE := docker-compose.nginx.yml FRONTEND_DIR := ../kotyari-bots_frontend +AUTH_COMPOSE := ./cmd/auth-rs/docker-compose.yml # Автоматический поиск сервисов для Ogen SERVICES := $(shell find ./docs -mindepth 2 -maxdepth 3 -type f -name 'openapi.yaml' -print \ @@ -51,6 +52,7 @@ up: copy-env setup-network @$(MAKE) bots-up & \ $(MAKE) profiles-up & \ $(MAKE) posts-up & \ +# $(MAKE) auth-up & \ wait @echo "Backend services are up." @@ -59,6 +61,7 @@ down: @$(MAKE) bots-down & \ $(MAKE) profiles-down & \ $(MAKE) posts-down & \ + $(MAKE) auth-down & \ wait @echo "Backend services stopped." @@ -80,6 +83,16 @@ posts-up: setup-network posts-down: docker compose -f docker-compose.posts.yml down +auth-up: setup-network + set -a && . ./.env && set +a && \ + docker compose -f $(AUTH_COMPOSE) up --build + +auth-down: + docker compose -f $(AUTH_COMPOSE) down + +auth-logs: + docker compose -f $(AUTH_COMPOSE) -p auth-rs logs -f + # --- FRONTEND --- frontend-build: diff --git a/configs/bots-local.yaml b/configs/bots-local.yaml index e1eeb09..0f610ab 100644 --- a/configs/bots-local.yaml +++ b/configs/bots-local.yaml @@ -10,4 +10,8 @@ local_profiles_api: host: "profiles_go" port: 8003 -local_profiles_svc_addr: "profiles_go:8002" \ No newline at end of file +local_profiles_svc_addr: "profiles_go:8002" + +auth_grpc: + host: "auth_rs" + port: 3001 \ No newline at end of file diff --git a/configs/posts-local.yaml b/configs/posts-local.yaml index 03e32a5..33fda2d 100644 --- a/configs/posts-local.yaml +++ b/configs/posts-local.yaml @@ -28,7 +28,7 @@ posts_producer_reply: kind: consumer posts_consumer_grpc: - posts_addr: "analytics:50051" # Создать общий network между docker-compose.yml + posts_addr: "analytics:50051" dial_timeout: 10 posts_consumer_request: @@ -56,4 +56,8 @@ posting_queue: otvet: auth_token: "" # Set via LOCAL_OTVET_AUTH_TOKEN env var - request_timeout: 30s \ No newline at end of file + request_timeout: 30s + +auth_grpc: + host: "auth_rs" + port: 3001 \ No newline at end of file diff --git a/configs/profiles-local.yaml b/configs/profiles-local.yaml index e189742..8cf53f9 100644 --- a/configs/profiles-local.yaml +++ b/configs/profiles-local.yaml @@ -4,4 +4,8 @@ profiles_api: grpc_port: 8002 profiles_database: - sslmode: "disable" \ No newline at end of file + sslmode: "disable" + +auth_grpc: + host: "auth_rs" + port: 3001 \ No newline at end of file diff --git a/internal/gen/bots/oas_security_gen.go b/internal/gen/bots/oas_security_gen.go new file mode 100644 index 0000000..c4b47a7 --- /dev/null +++ b/internal/gen/bots/oas_security_gen.go @@ -0,0 +1,87 @@ +// Code generated by ogen, DO NOT EDIT. + +package bots + +import ( + "context" + "net/http" + "strings" + + "github.com/go-faster/errors" + "github.com/ogen-go/ogen/ogenerrors" +) + +// SecurityHandler is handler for security parameters. +type SecurityHandler interface { + // HandleSessionAuth handles SessionAuth security. + HandleSessionAuth(ctx context.Context, operationName OperationName, t SessionAuth) (context.Context, error) +} + +func findAuthorization(h http.Header, prefix string) (string, bool) { + v, ok := h["Authorization"] + if !ok { + return "", false + } + for _, vv := range v { + scheme, value, ok := strings.Cut(vv, " ") + if !ok || !strings.EqualFold(scheme, prefix) { + continue + } + return value, true + } + return "", false +} + +var operationRolesSessionAuth = map[string][]string{ + AddProfileToBotOperation: []string{}, + CreateBotOperation: []string{}, + DeleteBotByIdOperation: []string{}, + GetBotByIdOperation: []string{}, + GetBotProfilesOperation: []string{}, + ListBotsOperation: []string{}, + RemoveProfileFromBotOperation: []string{}, + SearchBotsOperation: []string{}, + SummaryBotsOperation: []string{}, + UpdateBotByIdOperation: []string{}, +} + +func (s *Server) securitySessionAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) { + var t SessionAuth + const parameterName = "session_id" + var value string + switch cookie, err := req.Cookie(parameterName); { + case err == nil: // if NO error + value = cookie.Value + case errors.Is(err, http.ErrNoCookie): + return ctx, false, nil + default: + return nil, false, errors.Wrap(err, "get cookie value") + } + t.APIKey = value + t.Roles = operationRolesSessionAuth[operationName] + rctx, err := s.sec.HandleSessionAuth(ctx, operationName, t) + if errors.Is(err, ogenerrors.ErrSkipServerSecurity) { + return nil, false, nil + } else if err != nil { + return nil, false, err + } + return rctx, true, err +} + +// SecuritySource is provider of security values (tokens, passwords, etc.). +type SecuritySource interface { + // SessionAuth provides SessionAuth security value. + SessionAuth(ctx context.Context, operationName OperationName) (SessionAuth, error) +} + +func (s *Client) securitySessionAuth(ctx context.Context, operationName OperationName, req *http.Request) error { + t, err := s.sec.SessionAuth(ctx, operationName) + if err != nil { + return errors.Wrap(err, "security source \"SessionAuth\"") + } + req.AddCookie(&http.Cookie{ + Name: "session_id", + Value: t.APIKey, + }) + return nil +} diff --git a/internal/gen/posts/posts_command/oas_security_gen.go b/internal/gen/posts/posts_command/oas_security_gen.go new file mode 100644 index 0000000..5dcd82e --- /dev/null +++ b/internal/gen/posts/posts_command/oas_security_gen.go @@ -0,0 +1,82 @@ +// Code generated by ogen, DO NOT EDIT. + +package posts_command + +import ( + "context" + "net/http" + "strings" + + "github.com/go-faster/errors" + "github.com/ogen-go/ogen/ogenerrors" +) + +// SecurityHandler is handler for security parameters. +type SecurityHandler interface { + // HandleSessionAuth handles SessionAuth security. + HandleSessionAuth(ctx context.Context, operationName OperationName, t SessionAuth) (context.Context, error) +} + +func findAuthorization(h http.Header, prefix string) (string, bool) { + v, ok := h["Authorization"] + if !ok { + return "", false + } + for _, vv := range v { + scheme, value, ok := strings.Cut(vv, " ") + if !ok || !strings.EqualFold(scheme, prefix) { + continue + } + return value, true + } + return "", false +} + +var operationRolesSessionAuth = map[string][]string{ + CreatePostOperation: []string{}, + DeletePostByIdOperation: []string{}, + PublishPostOperation: []string{}, + SeenPostsOperation: []string{}, + UpdatePostByIdOperation: []string{}, +} + +func (s *Server) securitySessionAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) { + var t SessionAuth + const parameterName = "session_id" + var value string + switch cookie, err := req.Cookie(parameterName); { + case err == nil: // if NO error + value = cookie.Value + case errors.Is(err, http.ErrNoCookie): + return ctx, false, nil + default: + return nil, false, errors.Wrap(err, "get cookie value") + } + t.APIKey = value + t.Roles = operationRolesSessionAuth[operationName] + rctx, err := s.sec.HandleSessionAuth(ctx, operationName, t) + if errors.Is(err, ogenerrors.ErrSkipServerSecurity) { + return nil, false, nil + } else if err != nil { + return nil, false, err + } + return rctx, true, err +} + +// SecuritySource is provider of security values (tokens, passwords, etc.). +type SecuritySource interface { + // SessionAuth provides SessionAuth security value. + SessionAuth(ctx context.Context, operationName OperationName) (SessionAuth, error) +} + +func (s *Client) securitySessionAuth(ctx context.Context, operationName OperationName, req *http.Request) error { + t, err := s.sec.SessionAuth(ctx, operationName) + if err != nil { + return errors.Wrap(err, "security source \"SessionAuth\"") + } + req.AddCookie(&http.Cookie{ + Name: "session_id", + Value: t.APIKey, + }) + return nil +} diff --git a/internal/gen/posts/posts_query/oas_security_gen.go b/internal/gen/posts/posts_query/oas_security_gen.go new file mode 100644 index 0000000..7c1f1d1 --- /dev/null +++ b/internal/gen/posts/posts_query/oas_security_gen.go @@ -0,0 +1,81 @@ +// Code generated by ogen, DO NOT EDIT. + +package posts_query + +import ( + "context" + "net/http" + "strings" + + "github.com/go-faster/errors" + "github.com/ogen-go/ogen/ogenerrors" +) + +// SecurityHandler is handler for security parameters. +type SecurityHandler interface { + // HandleSessionAuth handles SessionAuth security. + HandleSessionAuth(ctx context.Context, operationName OperationName, t SessionAuth) (context.Context, error) +} + +func findAuthorization(h http.Header, prefix string) (string, bool) { + v, ok := h["Authorization"] + if !ok { + return "", false + } + for _, vv := range v { + scheme, value, ok := strings.Cut(vv, " ") + if !ok || !strings.EqualFold(scheme, prefix) { + continue + } + return value, true + } + return "", false +} + +var operationRolesSessionAuth = map[string][]string{ + CheckGroupIdOperation: []string{}, + CheckGroupIdsOperation: []string{}, + GetPostByIdOperation: []string{}, + ListPostsOperation: []string{}, +} + +func (s *Server) securitySessionAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) { + var t SessionAuth + const parameterName = "session_id" + var value string + switch cookie, err := req.Cookie(parameterName); { + case err == nil: // if NO error + value = cookie.Value + case errors.Is(err, http.ErrNoCookie): + return ctx, false, nil + default: + return nil, false, errors.Wrap(err, "get cookie value") + } + t.APIKey = value + t.Roles = operationRolesSessionAuth[operationName] + rctx, err := s.sec.HandleSessionAuth(ctx, operationName, t) + if errors.Is(err, ogenerrors.ErrSkipServerSecurity) { + return nil, false, nil + } else if err != nil { + return nil, false, err + } + return rctx, true, err +} + +// SecuritySource is provider of security values (tokens, passwords, etc.). +type SecuritySource interface { + // SessionAuth provides SessionAuth security value. + SessionAuth(ctx context.Context, operationName OperationName) (SessionAuth, error) +} + +func (s *Client) securitySessionAuth(ctx context.Context, operationName OperationName, req *http.Request) error { + t, err := s.sec.SessionAuth(ctx, operationName) + if err != nil { + return errors.Wrap(err, "security source \"SessionAuth\"") + } + req.AddCookie(&http.Cookie{ + Name: "session_id", + Value: t.APIKey, + }) + return nil +} diff --git a/internal/gen/profiles/oas_security_gen.go b/internal/gen/profiles/oas_security_gen.go new file mode 100644 index 0000000..5082995 --- /dev/null +++ b/internal/gen/profiles/oas_security_gen.go @@ -0,0 +1,82 @@ +// Code generated by ogen, DO NOT EDIT. + +package profiles + +import ( + "context" + "net/http" + "strings" + + "github.com/go-faster/errors" + "github.com/ogen-go/ogen/ogenerrors" +) + +// SecurityHandler is handler for security parameters. +type SecurityHandler interface { + // HandleSessionAuth handles SessionAuth security. + HandleSessionAuth(ctx context.Context, operationName OperationName, t SessionAuth) (context.Context, error) +} + +func findAuthorization(h http.Header, prefix string) (string, bool) { + v, ok := h["Authorization"] + if !ok { + return "", false + } + for _, vv := range v { + scheme, value, ok := strings.Cut(vv, " ") + if !ok || !strings.EqualFold(scheme, prefix) { + continue + } + return value, true + } + return "", false +} + +var operationRolesSessionAuth = map[string][]string{ + CreateMyProfileOperation: []string{}, + DeleteProfileByIdOperation: []string{}, + GetProfileByIdOperation: []string{}, + ListMyProfilesOperation: []string{}, + UpdateProfileByIdOperation: []string{}, +} + +func (s *Server) securitySessionAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) { + var t SessionAuth + const parameterName = "session_id" + var value string + switch cookie, err := req.Cookie(parameterName); { + case err == nil: // if NO error + value = cookie.Value + case errors.Is(err, http.ErrNoCookie): + return ctx, false, nil + default: + return nil, false, errors.Wrap(err, "get cookie value") + } + t.APIKey = value + t.Roles = operationRolesSessionAuth[operationName] + rctx, err := s.sec.HandleSessionAuth(ctx, operationName, t) + if errors.Is(err, ogenerrors.ErrSkipServerSecurity) { + return nil, false, nil + } else if err != nil { + return nil, false, err + } + return rctx, true, err +} + +// SecuritySource is provider of security values (tokens, passwords, etc.). +type SecuritySource interface { + // SessionAuth provides SessionAuth security value. + SessionAuth(ctx context.Context, operationName OperationName) (SessionAuth, error) +} + +func (s *Client) securitySessionAuth(ctx context.Context, operationName OperationName, req *http.Request) error { + t, err := s.sec.SessionAuth(ctx, operationName) + if err != nil { + return errors.Wrap(err, "security source \"SessionAuth\"") + } + req.AddCookie(&http.Cookie{ + Name: "session_id", + Value: t.APIKey, + }) + return nil +} diff --git a/internal/repo/bots/search.go b/internal/repo/bots/search.go index b3a852b..7759c5f 100644 --- a/internal/repo/bots/search.go +++ b/internal/repo/bots/search.go @@ -4,6 +4,7 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" + "github.com/goriiin/kotyari-bots_backend/pkg/user" "github.com/jackc/pgx/v5" ) diff --git a/internal/repo/bots/summary.go b/internal/repo/bots/summary.go index 37fa5c2..da197dc 100644 --- a/internal/repo/bots/summary.go +++ b/internal/repo/bots/summary.go @@ -4,6 +4,7 @@ import ( "context" "github.com/goriiin/kotyari-bots_backend/internal/model" + "github.com/goriiin/kotyari-bots_backend/pkg/user" ) func (r *BotsRepository) GetSummary(ctx context.Context) (model.BotsSummary, error) { From c9743bc9fb72049630fea98811385e3cbcf1fdc8 Mon Sep 17 00:00:00 2001 From: goriiin Date: Sat, 10 Jan 2026 19:11:31 +0300 Subject: [PATCH 4/6] bot-75: add --- internal/apps/bots/init.go | 2 +- internal/repo/profiles/list.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/apps/bots/init.go b/internal/apps/bots/init.go index adce2e2..f6aed9c 100644 --- a/internal/apps/bots/init.go +++ b/internal/apps/bots/init.go @@ -136,7 +136,7 @@ func (b *App) Run() error { defer shutdownCancel() grpcServer.GracefulStop() - if err = b.server.Shutdown(shutdownCtx); err != nil { + if shutErr := b.server.Shutdown(shutdownCtx); shutErr != nil { return err } diff --git a/internal/repo/profiles/list.go b/internal/repo/profiles/list.go index f0f6638..b442c8e 100644 --- a/internal/repo/profiles/list.go +++ b/internal/repo/profiles/list.go @@ -5,7 +5,6 @@ import ( "github.com/goriiin/kotyari-bots_backend/internal/model" "github.com/goriiin/kotyari-bots_backend/pkg/user" - "github.com/jackc/pgx/v5" ) From ea42a2891f673ade87aa3fd00ada130972ea25b1 Mon Sep 17 00:00:00 2001 From: goriiin Date: Sun, 11 Jan 2026 13:24:39 +0300 Subject: [PATCH 5/6] bot-75: add --- Makefile | 6 +- configs/posts-local.yaml | 2 +- docker-compose.posts.yml | 8 +- test.sh | 203 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 8 deletions(-) create mode 100755 test.sh diff --git a/Makefile b/Makefile index e9023ae..4f0d9a9 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ up: copy-env setup-network @$(MAKE) bots-up & \ $(MAKE) profiles-up & \ $(MAKE) posts-up & \ -# $(MAKE) auth-up & \ + $(MAKE) auth-up & \ wait @echo "Backend services are up." @@ -84,8 +84,8 @@ posts-down: docker compose -f docker-compose.posts.yml down auth-up: setup-network - set -a && . ./.env && set +a && \ - docker compose -f $(AUTH_COMPOSE) up --build + cp ./.env ./cmd/auth-rs/.env + docker compose -f $(AUTH_COMPOSE) up --build -d auth-down: docker compose -f $(AUTH_COMPOSE) down diff --git a/configs/posts-local.yaml b/configs/posts-local.yaml index 33fda2d..4034471 100644 --- a/configs/posts-local.yaml +++ b/configs/posts-local.yaml @@ -58,6 +58,6 @@ otvet: auth_token: "" # Set via LOCAL_OTVET_AUTH_TOKEN env var request_timeout: 30s -auth_grpc: +local_auth_grpc: host: "auth_rs" port: 3001 \ No newline at end of file diff --git a/docker-compose.posts.yml b/docker-compose.posts.yml index 7f4f78f..88d577a 100644 --- a/docker-compose.posts.yml +++ b/docker-compose.posts.yml @@ -27,7 +27,7 @@ services: dockerfile: ./cmd/posts_command_consumer/Dockerfile target: migrator container_name: posts_migrate - image: bots_migrate_image + image: posts_migrate_image env_file: .env networks: - posts-internal-network @@ -62,7 +62,7 @@ services: networks: - posts-internal-network - public-gateway-network - - common-network +# - common-network depends_on: kafka: condition: service_healthy @@ -129,8 +129,8 @@ networks: driver: bridge public-gateway-network: external: true - common-network: - external: true +# common-network: +# external: true volumes: posts_db_data: diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..90840e0 --- /dev/null +++ b/test.sh @@ -0,0 +1,203 @@ +#!/bin/bash + +# ========================================== +# КОНФИГУРАЦИЯ +# ========================================== +AUTH_HOST="http://localhost:3000" +PROFILES_HOST="http://localhost:8003" +BOTS_HOST="http://localhost:8001" +POSTS_CMD_HOST="http://localhost:8088" +POSTS_QUERY_HOST="http://localhost:8089" # Новый порт для Query Service + +COOKIE_FILE="./cookies_debug.txt" +COOKIE_FILE_2="./cookies_intruder.txt" # Куки для второго пользователя + +# Цвета +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Уникальные данные для User 1 +TIMESTAMP=$(date +%s) +USERNAME="debug_${TIMESTAMP}" +EMAIL="debug_${TIMESTAMP}@test.com" +PASSWORD="Password123!" + +# Очистка перед запуском +rm -f "$COOKIE_FILE" "$COOKIE_FILE_2" step*.log step*.json + +echo -e "${GREEN}=== ЗАПУСК DEBUG ТЕСТА (FULL FLOW + ISOLATION) ===${NC}" + +# ============================================================================== +# ШАГ 1: РЕГИСТРАЦИЯ (USER 1) +# ============================================================================== +echo -e "${CYAN}--------------------------------------------------${NC}" +echo -e "${YELLOW}[STEP 1] Регистрация User 1 (Auth Service)...${NC}" + +curl -v -c "$COOKIE_FILE" \ + -X POST "$AUTH_HOST/api/v1/register" \ + -H "Content-Type: application/json" \ + -d "{\"email\": \"$EMAIL\", \"username\": \"$USERNAME\", \"password\": \"$PASSWORD\"}" \ + > step1_body.json 2> step1_headers.log + +if grep -q "200 OK" step1_headers.log || grep -q "201 Created" step1_headers.log; then + echo -e "${GREEN}[OK] User 1 зарегистрирован${NC}" +else + cat step1_headers.log + cat step1_body.json + echo -e "${RED}[FAIL] Ошибка регистрации${NC}" + exit 1 +fi + +# ============================================================================== +# ШАГ 2: СОЗДАНИЕ ПРОФИЛЯ +# ============================================================================== +echo -e "${CYAN}--------------------------------------------------${NC}" +echo -e "${YELLOW}[STEP 2] Создание профиля (Profiles Service)...${NC}" + +curl -v -b "$COOKIE_FILE" \ + -X POST "$PROFILES_HOST/api/v1/profiles" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"Debug Profile\", \"email\": \"prof_${TIMESTAMP}@test.com\", \"prompt\": \"test\"}" \ + > step2_body.json 2> step2_headers.log + +if grep -q "201 Created" step2_headers.log || grep -q "200 OK" step2_headers.log; then + PROFILE_ID=$(cat step2_body.json | jq -r '.id') + echo -e "${GREEN}[OK] Профиль создан: $PROFILE_ID${NC}" +else + cat step2_headers.log + cat step2_body.json + echo -e "${RED}[FAIL] Ошибка создания профиля${NC}" + exit 1 +fi + +# ============================================================================== +# ШАГ 3: СОЗДАНИЕ БОТА +# ============================================================================== +echo -e "${CYAN}--------------------------------------------------${NC}" +echo -e "${YELLOW}[STEP 3] Создание бота (Bots Service)...${NC}" + +# Исправлен email внутри массива profiles на валидный +curl -v -b "$COOKIE_FILE" \ + -X POST "$BOTS_HOST/api/v1/bots" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"Debug Bot\", \"systemPrompt\": \"sys\", \"moderationRequired\": false, \"profiles\": [{\"id\": \"$PROFILE_ID\", \"name\": \"P\", \"email\": \"test@test.com\", \"systemPrompt\": \"s\"}]}" \ + > step3_body.json 2> step3_headers.log + +if grep -q "201 Created" step3_headers.log; then + BOT_ID=$(cat step3_body.json | jq -r '.id') + echo -e "${GREEN}[OK] Бот создан: $BOT_ID${NC}" +else + cat step3_headers.log + cat step3_body.json + echo -e "${RED}[FAIL] Ошибка создания бота${NC}" + exit 1 +fi + +# ============================================================================== +# ШАГ 4: СОЗДАНИЕ ЗАДАЧИ НА ПОСТИНГ +# ============================================================================== +echo -e "${CYAN}--------------------------------------------------${NC}" +echo -e "${YELLOW}[STEP 4] Создание задачи (Posts Command Service)...${NC}" + +curl -v -b "$COOKIE_FILE" \ + -X POST "$POSTS_CMD_HOST/api/v1/posts" \ + -H "Content-Type: application/json" \ + -d "{\"botId\": \"$BOT_ID\", \"profileIds\": [\"$PROFILE_ID\"], \"taskText\": \"debug task\", \"platform\": \"otveti\", \"postType\": \"opinion\"}" \ + > step4_body.json 2> step4_headers.log + +if grep -q "201 Created" step4_headers.log; then + GROUP_ID=$(cat step4_body.json | jq -r '.groupID') + echo -e "${GREEN}[OK] Задача создана. GroupID: $GROUP_ID${NC}" +else + cat step4_headers.log + cat step4_body.json + echo -e "${RED}[FAIL] Ошибка создания задачи${NC}" + exit 1 +fi + +# Небольшая пауза, чтобы Kafka успела обработать сообщение и записать пост в БД (Query side) +echo -e "${YELLOW}Ожидание 2 сек для синхронизации данных...${NC}" +sleep 2 + +# ============================================================================== +# ШАГ 5: ПОЛУЧЕНИЕ ПОСТОВ ДЛЯ USER 1 (ПРОВЕРКА) +# ============================================================================== +echo -e "${CYAN}--------------------------------------------------${NC}" +echo -e "${YELLOW}[STEP 5] Получение постов User 1 (Posts Query Service)...${NC}" + +curl -v -b "$COOKIE_FILE" \ + -X GET "$POSTS_QUERY_HOST/api/v1/posts" \ + > step5_body.json 2> step5_headers.log + +echo -e "${CYAN}--- RESPONSE BODY (USER 1) ---${NC}" +cat step5_body.json +echo "" + +if grep -q "200 OK" step5_headers.log; then + # Проверяем, что в массиве data что-то есть (или хотя бы что вернулся JSON) + COUNT=$(cat step5_body.json | jq '.data | length') + if [[ "$COUNT" -gt 0 ]]; then + echo -e "${GREEN}[OK] Посты получены. Количество: $COUNT${NC}" + else + # Это допустимо, если консьюмер еще не успел создать запись, но код 200 получен + echo -e "${YELLOW}[WARN] Список постов пуст (возможно, задержка Kafka), но запрос прошел успешно.${NC}" + fi +else + cat step5_headers.log + echo -e "${RED}[FAIL] Не удалось получить посты User 1${NC}" + exit 1 +fi + +# ============================================================================== +# ШАГ 6: ПРОВЕРКА ИЗОЛЯЦИИ (USER 2) +# ============================================================================== +echo -e "${CYAN}--------------------------------------------------${NC}" +echo -e "${YELLOW}[STEP 6] Проверка изоляции (Запрос от User 2)...${NC}" + +USERNAME_2="intruder_${TIMESTAMP}" +EMAIL_2="intruder_${TIMESTAMP}@test.com" + +# 6.1 Регистрация второго пользователя +echo "Регистрируем User 2 ($USERNAME_2)..." +curl -s -c "$COOKIE_FILE_2" \ + -X POST "$AUTH_HOST/api/v1/register" \ + -H "Content-Type: application/json" \ + -d "{\"email\": \"$EMAIL_2\", \"username\": \"$USERNAME_2\", \"password\": \"$PASSWORD\"}" > /dev/null + +if [ ! -f "$COOKIE_FILE_2" ]; then + echo -e "${RED}[FAIL] Не удалось зарегистрировать User 2${NC}" + exit 1 +fi + +# 6.2 Попытка получить посты (должен быть пустой список, так как у User 2 нет постов) +echo "Запрашиваем посты от имени User 2..." +curl -v -b "$COOKIE_FILE_2" \ + -X GET "$POSTS_QUERY_HOST/api/v1/posts" \ + > step6_body.json 2> step6_headers.log + +echo -e "${CYAN}--- RESPONSE BODY (USER 2) ---${NC}" +cat step6_body.json +echo "" + +# Проверяем ответ +if grep -q "200 OK" step6_headers.log; then + COUNT_2=$(cat step6_body.json | jq '.data | length') + + # Мы ожидаем, что data будет null (если API возвращает null для пустого слайса) или [] (пустой массив) + # jq вернет 0 для пустого массива или null. + + if [[ "$COUNT_2" == "0" ]] || [[ "$COUNT_2" == "null" ]]; then + echo -e "${GREEN}[OK] Изоляция работает! User 2 не видит чужие посты.${NC}" + echo -e "${GREEN}=== ВСЕ ТЕСТЫ ПРОШЛИ УСПЕШНО ===${NC}" + else + echo -e "${RED}[FAIL] НАРУШЕНИЕ ИЗОЛЯЦИИ! User 2 видит $COUNT_2 постов.${NC}" + exit 1 + fi +else + cat step6_headers.log + echo -e "${RED}[FAIL] Ошибка запроса постов для User 2${NC}" + exit 1 +fi \ No newline at end of file From e28c78b03352dbd8e8b6370b23b255e4c50528f3 Mon Sep 17 00:00:00 2001 From: goriiin Date: Mon, 12 Jan 2026 20:02:29 +0300 Subject: [PATCH 6/6] bot-75: update --- cmd/auth-rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/auth-rs b/cmd/auth-rs index 250af2b..4511f23 160000 --- a/cmd/auth-rs +++ b/cmd/auth-rs @@ -1 +1 @@ -Subproject commit 250af2bc8235ce302fe9c993df9ce477fe9a3d2f +Subproject commit 4511f23ec42eb2488c98abcc42f2cd98facf3c03