package grpc import ( "context" "errors" "git.sch9.ru/new_gate/ms-auth/internal/models" "git.sch9.ru/new_gate/ms-auth/internal/users" "git.sch9.ru/new_gate/ms-auth/pkg" userv1 "git.sch9.ru/new_gate/ms-auth/proto/user/v1" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" "strings" ) type UserHandlers struct { userv1.UnimplementedUserServiceServer userUC users.UseCase } func NewUserHandlers(gserver *grpc.Server, userUC users.UseCase) { handlers := &UserHandlers{ userUC: userUC, } userv1.RegisterUserServiceServer(gserver, handlers) } const ( SessionHeaderName = "x-session-id" AuthUserHeaderName = "x-auth-user-id" ) func (h *UserHandlers) Login(ctx context.Context, req *userv1.LoginRequest) (*emptypb.Empty, error) { const op = "UserHandlers.Login" var ( err error user *models.User ) username := req.GetUsername() password := req.GetPassword() user, err = h.userUC.ReadUserByUsername(ctx, username) if err != nil { return nil, pkg.ToGRPC(err) } err = user.ComparePassword(password) if err != nil { return nil, pkg.ToGRPC(pkg.Wrap(pkg.ErrNotFound, err, op, "bad username or password")) } sessionId, err := h.userUC.CreateSession(ctx, user.Id, user.Role) if err != nil { return nil, pkg.ToGRPC(err) } header := metadata.New(map[string]string{ SessionHeaderName: sessionId, }) err = grpc.SendHeader(ctx, header) if err != nil { return nil, err } return &emptypb.Empty{}, nil } func AuthSessionIdFromContext(ctx context.Context) (string, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return "", errors.New("failed to get metadata") } tokens := md.Get(SessionHeaderName) sessionId := strings.Join(tokens, "") if len(sessionId) == 0 { return "", errors.New("no session id in context") } return sessionId, nil } func (h *UserHandlers) Refresh(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { const op = "UserHandlers.Refresh" sessionId, err := AuthSessionIdFromContext(ctx) if err != nil { return nil, pkg.ToGRPC(pkg.Wrap(err, pkg.ErrUnauthenticated, op, "no session id in context")) } err = h.userUC.UpdateSession(ctx, sessionId) if err != nil { return nil, pkg.ToGRPC(err) } return &emptypb.Empty{}, nil } func (h *UserHandlers) Logout(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { const op = "UserHandlers.Logout" sessionId, err := AuthSessionIdFromContext(ctx) if err != nil { return nil, pkg.ToGRPC(pkg.Wrap(err, pkg.ErrUnauthenticated, op, "no session id in context")) } err = h.userUC.DeleteSession(ctx, sessionId) if err != nil { return nil, pkg.ToGRPC(err) } return &emptypb.Empty{}, nil } func (h *UserHandlers) CompleteLogout(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { const op = "UserHandlers.CompleteLogout" sessionId, err := AuthSessionIdFromContext(ctx) if err != nil { return nil, pkg.ToGRPC(pkg.Wrap(err, pkg.ErrUnauthenticated, op, "no session id in context")) } session, err := h.userUC.ReadSession(ctx, sessionId) if err != nil { return nil, pkg.ToGRPC(err) } err = h.userUC.DeleteAllSessions(ctx, session.UserId) if err != nil { return nil, pkg.ToGRPC(err) } return &emptypb.Empty{}, nil } func (h *UserHandlers) Verify(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { const op = "UserHandlers.Verify" sessionId, err := AuthSessionIdFromContext(ctx) if err != nil { return nil, pkg.ToGRPC(pkg.Wrap(err, pkg.ErrUnauthenticated, op, "no session id in context")) } token, err := h.userUC.Verify(ctx, sessionId) if err != nil { return nil, pkg.ToGRPC(err) } header := metadata.New(map[string]string{ AuthUserHeaderName: token, }) err = grpc.SendHeader(ctx, header) if err != nil { return nil, err } return &emptypb.Empty{}, nil } func (h *UserHandlers) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) { const op = "UserHandlers.CreateUser" sessionId, err := AuthSessionIdFromContext(ctx) if err != nil { return nil, pkg.ToGRPC(pkg.Wrap(err, pkg.ErrUnauthenticated, op, "no session id in context")) } ctx = context.WithValue(ctx, "userId", sessionId) id, err := h.userUC.CreateUser( ctx, req.GetUsername(), req.GetPassword(), models.RoleParticipant, ) if err != nil { return nil, pkg.ToGRPC(err) } return &userv1.CreateUserResponse{ Id: id, }, nil } func (h *UserHandlers) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) { user, err := h.userUC.ReadUserById( ctx, req.GetId(), ) if err != nil { return nil, pkg.ToGRPC(err) } return &userv1.GetUserResponse{ User: &userv1.User{ Id: user.Id, Username: user.Username, CreatedAt: timestamppb.New(user.CreatedAt), ModifiedAt: timestamppb.New(user.ModifiedAt), Role: userv1.Role(user.Role), }, }, nil } func (h *UserHandlers) UpdateUser(ctx context.Context, req *userv1.UpdateUserRequest) (*emptypb.Empty, error) { const op = "UserHandlers.UpdateUser" sessionId, err := AuthSessionIdFromContext(ctx) if err != nil { return nil, pkg.ToGRPC(pkg.Wrap(err, pkg.ErrUnauthenticated, op, "no session id in context")) } ctx = context.WithValue(ctx, "userId", sessionId) err = h.userUC.UpdateUser( ctx, req.GetId(), AsStringP(req.Username), AsMRoleP(req.Role), ) if err != nil { return nil, pkg.ToGRPC(err) } return &emptypb.Empty{}, nil } func (h *UserHandlers) DeleteUser(ctx context.Context, req *userv1.DeleteUserRequest) (*emptypb.Empty, error) { const op = "UserHandlers.DeleteUser" sessionId, err := AuthSessionIdFromContext(ctx) if err != nil { return nil, pkg.ToGRPC(pkg.Wrap(err, pkg.ErrUnauthenticated, op, "no session id in context")) } ctx = context.WithValue(ctx, "userId", sessionId) err = h.userUC.DeleteUser( ctx, req.GetId(), ) if err != nil { return nil, pkg.ToGRPC(err) } return &emptypb.Empty{}, nil } func AsMRoleP(v userv1.Role) *models.Role { vv := models.Role(v.Number()) return &vv } func AsRoleP(v models.Role) *models.Role { return &v } func AsStringP(str string) *string { return &str }