Payload CMS - Аутентификация в Nuxt с использованием собственного плагина

Payload CMS - Аутентификация в Nuxt с использованием собственного плагина

Содержание
  1. Обзор
  2. Видео
  3. Установка и Настройка
    1. Создайте новое приложение Nuxt
  4. Добавить документацию модуля Nuxt/UI
    1. Установите модуль
    2. Измените конфигурацию Nuxt
  5. Код
    1. Плагин
  6. Промежуточное программное обеспечение
  7. Страница входа
  8. The Index/Home страница

Обзор

Это сопутствующая публикация в блоге для поддержки видео по способу интеграции функций входа, выхода и создания учетной записи Payload CMS в приложении Nuxt JS.

Payload CMS – Лучший способ создания современного бэкенда и административного интерфейса. Никакой черной магии, только TypeScript и полностью с открытым исходным кодом, Payload - это и фреймворк приложений, и безголовый CMS.

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

Видео

Установка и Настройка

Создайте новое приложение Nuxt

npx nuxi@latest init <project-name>
cd /<project-name>
npm install

Добавить документацию модуля Nuxt/UI

Установите модуль

npm install @nuxt/ui

Измените конфигурацию Nuxt

export default defineNuxtConfig({
	modules: ['@nuxt/ui'],
});

Код

Я включил основные файлы, которые должны быть изменены, чтобы запустить приложение после удаления файла App.vue.

Плагин

Основное событие здесь — это пользовательский плагин Nuxt . Я создал пользовательский плагин в Nuxt и настроил его так, чтобы он запускался первым, начиная его имя с 01.

Плагин проверяет наличие пользователя, используя конечную точку Payload CMS customers/me для проверки наличия пользователя; если пользователь существует, он будет возвращен, в противном случае возвращается null.

Затем плагин сохраняет информацию о пользователе и информацию об аутентификации в переменных состояния с использованием функции Nuxt useState.

Также к плагину добавлены две дополнительные функции:

  • updateUser устанавливает информацию о текущем пользователе после успешного входа в систему

  • clearUser очищает информацию о пользователе и информацию об аутентификации после выхода пользователя из системы

    Как я упомянул в видео, рефакторинг плагина для включения функций signIn и signOut сделает эти две функции ненужными.

Плагин

// nuxt-app/plugins/01.payload-auth.ts
import { Customer } from "~/payload-types";

export interface CurrentUserAuthInfo {
  token: string;
  exp: number;
}

export interface CurrentPayloadUserInfo extends CurrentUserAuthInfo {
  user: Customer;
}

export default defineNuxtPlugin(async () => {
  const currentUser = useState<Customer | null>("currentUser", () => null);
  const userAuthInfo = useState<null | CurrentUserAuthInfo>("authInfo", () => {
    return {
      token: "",
      exp: 0,
    };
  });

  async function getUser() {
    if (currentUser.value) {
      return currentUser.value;
    }
    try {
      const resp = await fetch("http://localhost:3100/api/customers/me", {
        method: "GET",
        credentials: "include",
        headers: {
          ...useRequestHeaders(),
        },
      });

      if (!resp.ok) {
        const errorMsg = (await resp.json())?.errors[0].message;
        throw new Error(errorMsg);
      }
      const userInfo = (await resp.json()) as CurrentPayloadUserInfo;
      console.log(userInfo);
      userAuthInfo.value = {
        token: userInfo.token,
        exp: userInfo.exp,
      };
      currentUser.value = userInfo?.user;
      return userInfo?.user;
    } catch (error: any) {
      console.log("getUser - error", error);
      currentUser.value = null;
      return currentUser.value;
    }
  }

  await getUser();
  console.log("In Payload plugin", currentUser);

  return {
    provide: {
      payloadAuth: {
        currentUser,
        userAuthInfo,
        /**
         * called to make sure we have the current user
         * information set in the composable.
         */
        updateUser: async () => {
          await getUser();
        },
        /**
         * clear user information from the composable
         */
        clearUser: () => {
          currentUser.value = null;
          userAuthInfo.value = null;
        },
      },
    },
  };
});


Промежуточное программное обеспечение

Промежуточное программное обеспечение auth.ts выполняет единственную задачу, а именно перенаправление пользователя на страницу входа.

Это достигается путем доступа к плагину с использованием хука useNuxtApp для получения доступа к ранее обсуждаемому плагину $payloadAuth.

// nuxt-app/middleware/auth.ts

import { defineNuxtRouteMiddleware } from '#app';

export default defineNuxtRouteMiddleware(async (to, from) => {
	const { $payloadAuth } = useNuxtApp();
	const user = $payloadAuth.currentUser?.value;
	console.log('middleware user', user);
	if (!user) {
		// Redirect to login page
		return navigateTo('/login');
	}
});

Страница входа

Страница входа использует NuxtUI, чтобы сделать ее более привлекательной с точки зрения дизайна, но также имеет функциональность проверки, предоставленную NuxtUI, которую мы используем, чтобы убедиться, что нам предоставлены значения электронной почты и пароля для использования в вызове API Payload CMS.

Важно отметить, как мы получаем доступ к плагину после успешного входа в систему, чтобы обновить информацию о пользователе в плагине с информацией о текущем аутентифицированном пользователе с помощью вызова $payloadAuth.updateUser().

<template>
  <UContainer class="mt-6">
    <UCard class="m-4">
      <template #header>
        <h3>Login</h3>
      </template>

      <UForm
        ref="loginInputForm"
        :validate="validate"
        :state="loginInput"
        @submit.prevent="submit"
      >
        <UFormGroup label="Email" name="email">
          <UInput v-model="loginInput.email" />
        </UFormGroup>
        <UFormGroup label="Password" name="password">
          <UInput v-model="loginInput.password" type="password" />
        </UFormGroup>
        <UButton type="submit" class="mt-8"> Submit </UButton>
      </UForm>

      <template #footer />
    </UCard>
  </UContainer>
</template>
<script setup lang="ts">
import { FormError } from "@nuxt/ui/dist/runtime/types/form";
import { ref } from "vue";

const {$payloadAuth} = useNuxtApp();

type LoginInput = {
  email: string;
  password: string;
};
const loginInputForm = ref();
const loginInput = ref<LoginInput>({
  email: "",
  password: "",
});

/**
 * validate form information
 *
 * @param state
 */
const validate = (state: LoginInput): FormError[] => {
  const errors = [];
  if (!state.email) errors.push({ path: "email", message: "Required" });
  if (!state.password) errors.push({ path: "password", message: "Required" });

  return errors;
};

/**
 *
 */
async function submit() {
  try {
    const resp = await fetch("http://localhost:3100/api/customers/login", {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
        ...useRequestHeaders()
      },
      body: JSON.stringify({
        email: loginInput.value.email,
        password: loginInput.value.password,
      }),
    });

    if (!resp.ok) {
      const errorMsg = (await resp.json())?.errors[0].message;
      throw new Error(errorMsg);
    }
    const user = await resp.json();
    console.log(user);

    // set user globally
    await $payloadAuth.updateUser()

    // goto home
    await navigateTo("/");
  } catch (error: any) {
    alert("Sign In Error " + error.message);
  }
}
</script>


The Index/Home страница

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

У нас есть функция logOut, которая вызывает API Payload CMS, и после завершения выхода мы снова используем плагин, чтобы очистить информацию о пользователе с помощью $payloadAuth.clearUser().

<template>
  <UContainer class="mt-6">
    HELLO
    {{ $payloadAuth.currentUser }}
    <UButton @click="handleLogout">SIGN OUT</UButton>
  </UContainer>
</template>
<script setup lang="ts">
definePageMeta({
  middleware: ["auth"],
  alias: ["/", "/index"],
});
const {$payloadAuth} = useNuxtApp();

/**
 *
 */
async function handleLogout() {

  try {
    const resp = await fetch("http://localhost:3100/api/customers/logout", {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!resp.ok) {
      const errorMsg = (await resp.json())?.errors[0].message;
      throw new Error(errorMsg);
    }

    // clear user
    $payloadAuth.clearUser()

    // redirect
    navigateTo("/login")

  } catch (error: any) {
    alert("Sign Out Error " + error.message);
  }
}
</script>