Как бы вы реорганизовали эту функцию, используя Ramda.js? - PullRequest
0 голосов
/ 16 декабря 2018

Я пытаюсь выучить Ramda.js, но постоянно сложно подумать о том, как простые вещи будут выглядеть в Ramda.Если вы являетесь экспертом по Рамде или функциональному программированию, как бы вы изменили этот рефакторинг?

const validateFirebaseIdToken = async (req, res, next) => {
  console.log("Check if request is authorized with Firebase ID token");

  if (
    (!req.headers.authorization ||
      !req.headers.authorization.startsWith("Bearer ")) &&
    !(req.cookies && req.cookies.__session)
  ) {
    console.error(
      "No Firebase ID token was passed as a Bearer token in the Authorization header.",
      "Make sure you authorize your request by providing the following HTTP header:",
      "Authorization: Bearer <Firebase ID Token>",
      'or by passing a "__session" cookie.'
    );
    res.status(403).send("Unauthorized");
    return;
  }

  let idToken;
  if (
    req.headers.authorization &&
    req.headers.authorization.startsWith("Bearer ")
  ) {
    console.log('Found "Authorization" header');
    // Read the ID Token from the Authorization header.
    idToken = req.headers.authorization.split("Bearer ")[1];
  } else if (req.cookies) {
    console.log('Found "__session" cookie');
    // Read the ID Token from cookie.
    idToken = req.cookies.__session;
  } else {
    // No cookie
    res.status(403).send("Unauthorized");
    return;
  }

  try {
    const decodedIdToken = await admin.auth().verifyIdToken(idToken);
    //console.log("ID Token correctly decoded", decodedIdToken);
    req.user = decodedIdToken;
    next();
    return;
  } catch (error) {
    console.error("Error while verifying Firebase ID token:", error);
    res.status(403).send("Unauthorized");
    return;
  }
};

1 Ответ

0 голосов
/ 16 декабря 2018

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

Проверка запроса

Два набора условных выражений могут быть сведены вместе.Проверка правильности запроса и извлечение токена может быть выражена следующим образом: (Мы рассмотрим несанкционированный случай позже)

const token = hasToken(req) ? getToken(req) : false;

hasToken проверит, еслизапрос req имеет действительный заголовок авторизации или файл cookie __session:

const hasBearer = pipe(pathOr('', ['headers', 'authorization']), startsWith('Bearer'));
const hasSession = hasPath(['cookies', '__session']);
const hasToken = either(hasBearer, hasSession);

Получение токена

Мы можем получить токен иззаголовок или из файла cookie:

const getBearer = pipe(path(['headers', 'authorization']), replace('Bearer ', ''));
const getSession = path(['cookies', '__session']);
const getToken = either(getBearer, getSession);

Создание 403 «несанкционированного» ответа

Мы могли бы также создать функцию для этого.Это может быть так просто:

const unauthorized = res => res.status(403).send('Unauthorized');

Но мы также можем разбить это на более мелкие многократно используемые фрагменты:

const status = invoker(1, 'status');
const send = invoker(1, 'send');
const unauthorized = pipe(status(403), send('Unauthorized'));

Так что если вы хотите генерировать ответы другого типа, вымог бы сделать:

const notFound = pipe(status(404), send('Not Found'));
const serverError = pipe(status(500), send('Server Error'));

Проверка правильности токена

Мы можем использовать функциональный подход для попытки / перехвата:

const verifyToken = async (admin, token) =>
  tryCatch(() => admin.auth().verifyIdToken(token), F)();

Возвращение контроля admin

Оригинальная функция validateFirebaseIdToken использует внешнюю переменную admin, что не идеально для ИМХО.Было бы лучше, чтобы он был передан в качестве параметра.Для этого мы можем использовать curry:

const validateFirebaseIdTokenFunction = curry(async (admin, req, res, next) => {
  // …
});

Собираем все вместе

const {
  curry,
  either,
  F,
  hasPath,
  invoker,
  path,
  pathOr,
  pipe,
  replace,
  set,
  startsWith,
  tryCatch } = require('ramda');

const hasBearer = pipe(pathOr('', ['headers', 'authorization']), startsWith('Bearer'));
const getBearer = pipe(path(['headers', 'authorization']), replace('Bearer ', ''));
const hasSession = hasPath(['cookies', '__session']);
const getSession = path(['cookies', '__session']);
const hasToken = either(hasBearer, hasSession);
const getToken = either(getBearer, getSession);
const status = invoker(1, 'status');
const send = invoker(1, 'send');
const unauthorized = pipe(status(403), send('Unauthorized'));

const verifyToken = async (admin, token) =>
  tryCatch(() => admin.auth().verifyIdToken(token), F)();

const validateFirebaseIdTokenFunction = curry(async (admin, req, res, next) => {
  const token = hasToken(req) ? getToken(req) : false;
  const tokenVerified = token ? await verifyToken(admin, token) : false;
  return tokenVerified ? set('user', tokenVerified, req) && next() : unauthorized(res);
});

// This function now “waits” for the remaining arguments: `req`, `res` and `next`
const validateFirebaseIdToken = validateFirebaseIdTokenFunction(admin);

Список используемых функций Ramda

...