Подписка GraphQL, websocket, nodejs, экспресс-сессия

Я использую Apollo Server 2 (но эта проблема не только с Аполлоном) и Express.js vanilla (с apollo-server-express).

Все хорошо работает и с Подписки, кроме механизма Экспресс-сессия.

Эта проблема:

Я использую cookie-сессия (https://github.com/expressjs/cookie-session, но я думаю, что это то же самое для промежуточного программного обеспечения экспресс-сессия), и когда мой браузер запускает новое соединение с моим сервером, хук ApolloServer onConnect не имеет атрибута req, ни req.session и так далее ...

Что я могу сделать, так это проанализировать файлы cookie из webSocket.upgradeReq.headers.cookie в ловушке жизненного цикла onConnect, но мне это кажется очень хакерским.

Код:

const { ApolloServer } = require('apollo-server-express')

const typeDefs = require('../src/graphql/types')
const resolvers = require('../src/graphql/resolvers')
const models = require('../src/models')

const apolloServer = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req, connection }) => {
    // connection exists only on webSocket connection
    if (connection) {
      return {
        currentUser: connection.context.currentUser // <-- I NEED THIS!
      }
    }
    // if not a (webSocket) connection it is a "default" HTTP call
    return {
      models,
      currentUser: { id: req.user.id }
    }
  },
  subscriptions: {
    onConnect: (connectionParams, webSocket) => {
      // "connectionParams" is from the client but I cannot use it because cookies are HTTP-Only
      // I can retrieve cookies from here: "webSocket.upgradeReq.headers.cookie" but then I need to parse them which seems a bit hacky to me
      // return { currentUser: req.user.id } // <-- I NEED THIS (req.user.id doesn't exists)!
    }
  }
})

module.exports = apolloServer

Я не могу найти ничего на сайте Apollo Server Docs (другие темы очень хорошо документированы!).

Где я делаю не так?

как ты это решил?

Nux 01.10.2019 18:05
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
1
1 393
4

Ответы 4

См. Этот комментарий: https://github.com/expressjs/cookie-session/issues/117#issuecomment-452046225 от dougwilson, чтобы получить значения сеанса.

If you just want to get the session contents (and not write back a new session -- which would not be possible without a res anyhow), you could likely just use the cookies module directly, which is what this module uses under the hood to read the cookie and validate the signature. A possible example:

const Cookies = require('cookies')

// …

const cookies = new Cookies(req, null, { keys }) const session =
JSON.parse(Buffer.from(cookies.get('session'),'base64').toString('utf8'))

И onConnect вернет все, что вам нужно, на subscribe ()

https://www.apollographql.com/docs/apollo-server/features/subscriptions.html#Authentication-Over-WebSocket

Похоже, это предполагает, что вы можете передать необходимые данные cookie на стороне клиента для получения в контексте на сервере.

Передайте параметры для подключения к веб-сокету: https://www.apollographql.com/docs/react/advanced/subscriptions.html#authentication

Я знаю это старое, но сам столкнулся с этой проблемой, и как я ее решил, я запустил webSocket.upgradeReq.session через промежуточное ПО для сеанса.

Затем в onConnect:

И у вас есть доступ к вашей сессии

Это то, что ! использовать

  //Session middleware before resolvers
  const sessionParser = session({
    store,
    secret: config.get('keys.session_secret'),
    //...The rest config here
    }
  });
  const apolloServer = new ApolloServer({
    schema,
    context: ({req, res, connection}: TheContext): TheContext => {
      return {req, res, redisStore: store, connection}; //connection is must here
    },
    subscriptions: {
      onConnect: (_params, _ws, ctx) => {
        return new Promise((resolve, reject) => {
          const req = ctx.request as express.Request;
          const res = ({} as any) as express.Response;

          //Parsing session
          sessionParser(req, res, (_: any) => {
            const session = req.session as any;

            //Getting userId which was stored during login
            const userId = session.userId;

            if (!userId) {
              reject(new AppError('You are not logged in'));
            }

            //This will be available in subscription via connection as
            //something.connection.context.userId
            resolve({userId});
          });
        });
      }
    }
  });

И я использую с ТипGraphql вот так

  @Subscription(() => Post, {
    topics: ({context}) => `${Topic.DELETED_POST}:${parseUserId(context)}`,
    description: 'To be used in requests list and not for individual request'
  })
  deletedPost(@Root() post: Post) {
    return post;
  }

Вот как я получаю доступ к userId, который был сохранен в сеансе

export function parseUserId(obj: any): string {
  return obj.connection.context.userId ?? '';
}

Кредит на все ответы здесь

Другие вопросы по теме