Аутентификация и контроль доступа с ретрансляцией

Официальная версия Facebook гласит, что Relay «преднамеренно не зависит от механизмов аутентификации». Во всех примерах в репозитории Relay аутентификация и контроль доступа являются отдельной задачей. На практике я не нашел простого способа реализовать это разделение.

Все примеры, представленные в репозитории Relay, имеют корневые схемы с полем viewer, которое предполагает наличие одного пользователя. И этот пользователь имеет доступ ко всему.

Однако на самом деле у приложения много пользователей, и каждый пользователь имеет разные степени доступа к каждому узлу.

Предположим, у меня есть эта схема в JavaScript:

export const Schema = new GraphQLSchema({
    query: new GraphQLObjectType({
        name: 'Query',
        fields: () => ({
            node: nodeField,
            user: {
                type: new GraphQLObjectType({
                    name: 'User',
                    args: {
                        // The `id` of the user being queried for
                        id: { type: new GraphQLNonNull(GraphQLID) },
                        // Identity the user who is querying
                        session: { type: new GraphQLInputObjectType({ ... }) },
                    },
                    resolve: (_, { id, session }) => {
                        // Given `session, get user with `id`
                        return data.getUser({ id, session });
                    }
                    fields: () => ({
                        name: {
                            type: GraphQLString,
                            resolve: user => {
                                // Does `session` have access to this user's
                                // name?
                                user.name
                            }
                        }
                    })
                })
            }
        })
    })
});

Некоторые пользователи являются полностью конфиденциальными с точки зрения запрашивающего пользователя. Другие пользователи могут открывать запрашивающему пользователю только определенные поля. Таким образом, чтобы получить пользователя, клиент должен не только предоставить идентификатор пользователя, который он запрашивает, но также должен идентифицировать себя, чтобы можно было осуществлять контроль доступа.

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

Кроме того, мне нужно контролировать доступ для каждого корневого запроса, например nodeField. Мне нужно убедиться, что каждый узел, реализующий nodeInterface.

Все это кажется повторяющейся работой. Существуют ли какие-либо известные шаблоны для упрощения этого? Я неправильно думаю об этом?


person Dmitry Minkovsky    schedule 05.11.2015    source источник
comment
Я думаю, было бы очень здорово, если бы в Relay было какое-то промежуточное ПО, которое сидело бы над механизмом выполнения и переписывало бы запросы AST на основе информации о сеансе.   -  person Dmitry Minkovsky    schedule 05.11.2015
comment
Вы когда-нибудь получали хороший пример/ответ? Я ищу информацию об аутентификации токена (без сеанса) с реле, но трудно что-либо найти   -  person irl_irl    schedule 08.02.2016
comment
@GreenRails здесь нет, но я понял, как это сделать. Это очень мило! По сути, ключом для меня было выяснить, что вы можете помещать вещи в корневое значение GraphQL, которое доступно на всех уровнях разрешения. Если вы используете экспресс-промежуточное ПО, это делается следующим образом: gist.github.com/dminkovsky/. То же самое можно сделать для любой реализации. Затем, согласно приведенному ниже ответу, вы также можете использовать «ориентированный на зрителя» подход к загрузке данных, чтобы помочь в ACL. github.com/facebook/dataloader — хороший вспомогательный инструмент.   -  person Dmitry Minkovsky    schedule 08.02.2016
comment
@GreenRails только что добавил ответ   -  person Dmitry Minkovsky    schedule 08.02.2016


Ответы (3)


Разные приложения имеют очень разные требования к форме управления доступом, поэтому встраивание чего-либо в базовую структуру Relay или эталонную реализацию GraphQL, вероятно, не имеет смысла.

Подход, который я видел довольно успешным, заключается в том, чтобы запечь контроль конфиденциальности/доступа в структуру модели данных/загрузчика данных. Каждый раз, когда вы загружаете объект, вы не просто загружаете его по идентификатору, но также предоставляете контекст средства просмотра. Если зритель не может видеть объект, он не сможет загрузить как будто его не существует, чтобы предотвратить даже утечку информации о существовании объекта. Объект также сохраняет контекст средства просмотра, и некоторые поля могут иметь ограниченный доступ, которые проверяются перед возвратом из объекта. Запекание этого в механизме загрузки данных более низкого уровня помогает гарантировать, что ошибки в коде продукта/GraphQL более высокого уровня не приводят к утечке личных данных.

В конкретном примере мне может быть не разрешено видеть какого-то Пользователя, потому что он меня заблокировал. Вам может быть разрешено видеться с ним в целом, но не с его электронной почтой, так как вы с ним не друзья.

В коде примерно так:

var viewer = new Viewer(getLoggedInUser());
User.load(id, viewer).then(
  (user) => console.log("User name:", user.name),
  (error) => console.log("User does not exist or you don't have access.")
)

Попытка реализовать видимость на уровне GraphQL может привести к утечке информации. Подумайте о многих способах доступа к пользователю в реализации GraphQL для Facebook:

node($userID) { name }
node($postID) { author { name } }
node($postID) { likers { name } }
node($otherUserID) { friends { name } }

Все эти запросы могут загрузить имя пользователя, и если пользователь заблокировал вас, ни один из них не должен возвращать пользователя или его имя. Имея контроль доступа ко всем этим полям и не забывая про галочку где-либо, вы можете где-то пропустить галочку.

person kassens    schedule 07.11.2015
comment
Должен быть репозиторий github, демонстрирующий это. - person Mehdi; 07.11.2015
comment
Это не конкретный пример. Вы как бы ответили на контроль доступа (что просто добавляет некоторую логику), но не коснулись аутентификации - person irl_irl; 08.02.2016

Я обнаружил, что обработка аутентификации упрощается, если вы используете GraphQL rootValue, который передается механизму выполнения при выполнении запроса по схеме. Это значение доступно на всех уровнях выполнения и полезно для хранения токена доступа или чего-то еще, что идентифицирует текущего пользователя.

Если вы используете ПО промежуточного слоя express-graphql, вы можете загрузить сеанс в ПО промежуточного слоя перед Промежуточное ПО GraphQL, а затем настройте промежуточное ПО GraphQL, чтобы поместить этот сеанс в корневое значение:

function getSession(req, res, next) {
  loadSession(req).then(session => {
    req.session = session;
    next();
  }).catch(
    res.sendStatus(400);
  );
}

app.use('/graphql', getSession, graphqlHTTP(({ session }) => ({
  schema: schema,
  rootValue: { session }
})));

Затем этот сеанс доступен на любой глубине схемы:

new GraphQLObjectType({
  name: 'MyType',
  fields: {
    myField: {
      type: GraphQLString,
      resolve(parentValue, _, { rootValue: { session } }) {
        // use `session` here
      }
    }
  }
});

Вы можете сочетать это с загрузкой данных, ориентированной на зрителя, для достижения контроля доступа. Ознакомьтесь с https://github.com/facebook/dataloader, который помогает создать такой объект загрузки данных и обеспечивает пакетную обработку и кэширование.

function createLoaders(authToken) {
  return {
    users: new DataLoader(ids => genUsers(authToken, ids)),
    cdnUrls: new DataLoader(rawUrls => genCdnUrls(authToken, rawUrls)),
    stories: new DataLoader(keys => genStories(authToken, keys)),
  };
}
person Dmitry Minkovsky    schedule 08.02.2016
comment
Как вы находите этот подход по сравнению с отдыхом? - person irl_irl; 08.02.2016
comment
@GreenRails Я не очень опытен. Может быть, кто-то другой ответит на это? - person Dmitry Minkovsky; 09.02.2016

Если у кого-то есть проблемы с этой темой: я сделал пример repo для Relay/GraphQL/экспресс-аутентификации на основе на ответ димадимы. Он сохраняет данные сеанса (идентификатор пользователя и роль) в файле cookie с использованием экспресс-промежуточного программного обеспечения и мутации GraphQL.

person jkettmann    schedule 29.03.2016