package user import ( "context" "errors" pb "github.com/ansg191/ibd-trader-backend/api/gen/idb/user/v1" "github.com/ansg191/ibd-trader-backend/internal/database" "github.com/ansg191/ibd-trader-backend/internal/ibd" "github.com/ansg191/ibd-trader-backend/internal/keys" "github.com/mennanov/fmutils" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) type Server struct { pb.UnimplementedUserServiceServer db database.TransactionExecutor kms keys.KeyManagementService keyName string client *ibd.Client } func New(db database.TransactionExecutor, kms keys.KeyManagementService, keyName string, client *ibd.Client) *Server { return &Server{ db: db, kms: kms, keyName: keyName, client: client, } } func (u *Server) CreateUser(ctx context.Context, request *pb.CreateUserRequest) (*pb.CreateUserResponse, error) { err := database.AddUser(ctx, u.db, request.Subject) if err != nil { return nil, status.Errorf(codes.Internal, "unable to create user: %v", err) } user, err := database.GetUser(ctx, u.db, request.Subject) if err != nil { return nil, status.Errorf(codes.Internal, "unable to get user: %v", err) } return &pb.CreateUserResponse{ User: &pb.User{ Subject: user.Subject, IbdUsername: user.IBDUsername, IbdPassword: nil, }, }, nil } func (u *Server) GetUser(ctx context.Context, request *pb.GetUserRequest) (*pb.GetUserResponse, error) { user, err := database.GetUser(ctx, u.db, request.Subject) if errors.Is(err, database.ErrUserNotFound) { return nil, status.New(codes.NotFound, "user not found").Err() } if err != nil { return nil, status.Errorf(codes.Internal, "unable to get user: %v", err) } return &pb.GetUserResponse{ User: &pb.User{ Subject: user.Subject, IbdUsername: user.IBDUsername, IbdPassword: nil, }, }, nil } func (u *Server) UpdateUser(ctx context.Context, request *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) { request.UpdateMask.Normalize() if !request.UpdateMask.IsValid(request.User) { return nil, status.Errorf(codes.InvalidArgument, "invalid update mask") } existingUserRes, err := u.GetUser(ctx, &pb.GetUserRequest{Subject: request.User.Subject}) if err != nil { return nil, err } existingUser := existingUserRes.User newUser := proto.Clone(existingUser).(*pb.User) fmutils.Overwrite(request.User, newUser, request.UpdateMask.Paths) // if IDB creds are both set and are different, update them if (newUser.IbdPassword != nil && newUser.IbdUsername != nil) && (newUser.IbdPassword != existingUser.IbdPassword || newUser.IbdUsername != existingUser.IbdUsername) { // Update IBD creds err = database.AddIBDCreds(ctx, u.db, u.kms, u.keyName, newUser.Subject, *newUser.IbdUsername, *newUser.IbdPassword) if err != nil { return nil, status.Errorf(codes.Internal, "unable to update user: %v", err) } } newUser.IbdPassword = nil return &pb.UpdateUserResponse{ User: newUser, }, nil } func (u *Server) CheckIBDUsername(ctx context.Context, req *pb.CheckIBDUsernameRequest) (*pb.CheckIBDUsernameResponse, error) { username := req.IbdUsername if username == "" { return nil, status.Errorf(codes.InvalidArgument, "username cannot be empty") } // Check if the username exists exists, err := u.client.CheckIBDUsername(ctx, username) if err != nil { return nil, status.Errorf(codes.Internal, "unable to check username: %v", err) } return &pb.CheckIBDUsernameResponse{ Exists: exists, }, nil } func (u *Server) AuthenticateUser(ctx context.Context, req *pb.AuthenticateUserRequest) (*pb.AuthenticateUserResponse, error) { // Check if user has cookies cookies, err := database.GetCookies(ctx, u.db, u.kms, req.Subject, false) if err != nil { return nil, status.Errorf(codes.Internal, "unable to get cookies: %v", err) } if len(cookies) > 0 { return &pb.AuthenticateUserResponse{ Authenticated: true, }, nil } // Authenticate user // Get IBD creds username, password, err := database.GetIBDCreds(ctx, u.db, u.kms, req.Subject) if errors.Is(err, database.ErrIBDCredsNotFound) { return nil, status.New(codes.NotFound, "User has no IDB creds").Err() } if err != nil { return nil, status.Errorf(codes.Internal, "unable to get IBD creds: %v", err) } // Authenticate user cookie, err := u.client.Authenticate(ctx, username, password) if errors.Is(err, ibd.ErrBadCredentials) { return &pb.AuthenticateUserResponse{ Authenticated: false, }, nil } if err != nil { return nil, status.Errorf(codes.Internal, "unable to authenticate user: %v", err) } return &pb.AuthenticateUserResponse{ Authenticated: cookie != nil, }, nil }