Next.js и SWR: невозможно удержать пользователя от успешного входа в систему через API Passport в ловушке SWR

Я перешел от экспресс-сервера, обрабатывающего мои API, в Next, к их встроенным API-маршрутам!

Люблю это!

В любом случае, я использую Passport.js для аутентификации и авторизации и успешно это реализовал.

Но я заметил несколько вещей, которые хочу рассказать в первую очередь, поскольку я почти уверен, что они связаны с проблемой с ловушкой SWR:

В моем маршруте входа: /api/login:

      import nextConnect from 'next-connect'

import auth from '../../middleware/auth'
import passport from '../../lib/passport'

import connectDB from '../../lib/mongodb';

const handler = nextConnect()

handler
  .use(auth)
  .post(
    async (req, res, next) => {
      await connectDB();

      passport.authenticate('local', (err, user, info) => {

        if (err) { return errorHandler(err, res) }
        if (user === false) {
          return res.status(404).send({
            msg: `We were unable to find this user. Please confirm with the "Forgot password" link or the "Register" link below!`
          })

        }
        if (user) {
          if (user.isVerified) {
            req.user = user;
            return res.status(200).send({
              user: req.user,
              msg: `Your have successfully logged in; Welcome to Hillfinder!`
            });
          }
          return res.status(403).send({
            msg: 'Your username has not been verified! Check your email for a confirmation link.'
          });

        }
      })(req, res, next);
    })

export default handler

Вы можете видеть, что я использую настраиваемый обратный вызов в Passport.js, поэтому, когда я получаю пользователя после успешного входа в систему, я просто назначаю пользователя req.user = user

Я думал, это должно позволить ловушке SWR всегда возвращать истину, что пользователь вошел в систему?

Это мой hooks.js file т.е. функциональность КСВ:

      import useSWR from 'swr'

import axios from 'axios';

export const fetcher = async (url) => {
  try {
    const res = await axios.get(url);

    console.log("res ", res);
    return res.data;
  } catch (err) {

    console.log("err ", err);
    throw err.response.data;
  }
};


export function useUser() {
  const { data, mutate } = useSWR('/api/user', fetcher)
  // if data is not defined, the query has not completed

  console.log("data ", data);
  const loading = !data
  const user = data?.user
  return [user, { mutate, loading }]
}

В fetcher звонит /api/user:

      import nextConnect from 'next-connect'
import auth from '../../middleware/auth'

const handler = nextConnect()

handler
  .use(auth)
  .get((req, res) => {
    console.log("req.user ", req.user); // Shouldn't the req.user exist??
    res.json({ user: req.user })
  })


export default handler

Разве это не должно всегда возвращать пользователя после успешного входа в систему?

Напоследок вот мой LoginSubmit:

      import axios from 'axios';

export default function loginSubmit(
  email,
  password,
  router,
  dispatch,
  mutate
) {

  const data = {
    email,
    password,
  };
  axios
    .post(`/api/login`,
      data, // request body as string
      { // options
        withCredentials: true,
        headers: {
          'Content-Type': 'application/json'
        }
      }
    )
    .then(response => {

      const { userId, user } = response.data

      if (response.status === 200) {
        setTimeout(() => {
          router.push('/profile');
        }, 3000);
     
        dispatch({ type: 'userAccountIsVerified' })
       
        mutate(user)
      }
    })
}

Любая помощь будет оценена по достоинству!

Обновлять

В вопрос добавлено промежуточное ПО аутентификации:

      import nextConnect from 'next-connect'

import passport from '../lib/passport'
import session from '../lib/session'


const auth = nextConnect()
  .use(
    session({
      name: 'sess',
      secret: process.env.TOKEN_SECRET,
      cookie: {
        maxAge: 60 * 60 * 8, // 8 hours,
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        path: '/',
        sameSite: 'lax',
      },
    })
  )
  .use((req, res, next) => {
    req.session.users = req.session.users || []
    next()
  })

  .use(passport.initialize())
  .use(passport.session())

export default auth

Добавлен: serializeUser && deserializeUser функции;

      import passport from 'passport'
import LocalStrategy from 'passport-local'

import User from '../models/User'


passport.serializeUser((user, done) => {
  done(null, user._id);
});

passport.deserializeUser((id, done) => {
  User.findById(id, (err, user) => {
    done(err, user);
  });
});

passport.use(
  new LocalStrategy(
    { usernameField: 'email', passwordField: 'password', passReqToCallback: true },
    async (req, email, password, done) => {
      try {
        const user = await User.findOne({ email }).exec();

        if (!user) {
          return done(null, false, { message: 'Invalid username!' });
        }
        const passwordOk = await user.comparePassword(password);
        if (!passwordOk) {
          return done(null, false, {
            message: 'Invalid password!'
          });
        }
        return done(null, user);
      } catch (err) {
        return done(err);
      }
    }
  )
);

export default passport

И это session.js файл:

      import { parse, serialize } from 'cookie'
import { createLoginSession, getLoginSession } from './auth'

function parseCookies(req) {
  // For API Routes we don't need to parse the cookies.
  if (req.cookies) return req.cookies

  // For pages we do need to parse the cookies.
  const cookie = req.headers?.cookie
  return parse(cookie || '')
}

export default function session({ name, secret, cookie: cookieOpts }) {
  return async (req, res, next) => {
    const cookies = parseCookies(req)
    const token = cookies[name]
    let unsealed = {}

    if (token) {
      try {
        // the cookie needs to be unsealed using the password `secret`
        unsealed = await getLoginSession(token, secret)
      } catch (e) {
        // The cookie is invalid
      }
    }

    req.session = unsealed

    // We are proxying res.end to commit the session cookie
    const oldEnd = res.end
    res.end = async function resEndProxy(...args) {
      if (res.finished || res.writableEnded || res.headersSent) return
      if (cookieOpts.maxAge) {
        req.session.maxAge = cookieOpts.maxAge
      }

      const token = await createLoginSession(req.session, secret)

      res.setHeader('Set-Cookie', serialize(name, token, cookieOpts))
      oldEnd.apply(this, args)
    }

    next()
  }
}

0 ответов

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