Nuxt Nation 大会即将到来。加入我们,时间为 11 月 12 日至 13 日。

nuxt-authorization

管理应用程序和服务器内部的权限。

Nuxt 授权

npm versionnpm downloadsLicenseNuxt

轻松处理 Nuxt 和 Nitro 中的授权。

此模块不实现 ACL 或 RBAC。它提供了一些低级原语,您可以使用它们来实现自己的授权逻辑。

!注意 未来,此模块可能作为 Nitro 模块和 Nuxt 模块提供,但 Nitro 模块尚未准备好。

要了解有关此模块及其解决问题的更多信息,请查看我关于Nuxt 中的授权的博文。

功能

  • ⛰  可在客户端(Nuxt)和服务器(Nitro)上运行
  • 🌟  编写一次权限并在任何地方使用
  • 👨‍👩‍👧‍👦  与身份验证层无关
  • 🫸  使用组件有条件地显示 UI 的一部分
  • 💧  可以访问原语以进行完全自定义

快速设置

使用一条命令将模块安装到您的 Nuxt 应用程序中

npx nuxi module add nuxt-authorization

就是这样!您现在可以在您的 Nuxt 应用程序中使用该模块 ✨

文档

!注意您可以查看游乐场以查看模块的运行情况。

设置

在使用模块并定义第一个权限之前,您需要提供 2 个解析器。这些函数在内部用于检索用户,但您必须实现它们。这使得模块与身份验证层无关。

对于 Nuxt 应用程序,在plugins/authorization-resolver.ts中创建一个新的插件

export default defineNuxtPlugin({
  name: 'authorization-resolver',
  parallel: true,
  setup() {
    return {
      provide: {
        authorization: {
          resolveClientUser: () => {
            // Your logic to retrieve the user from the client
          },
        },
      },
    }
  },
})

每次在客户端检查授权时都会调用此函数。它应该返回用户对象,如果用户未经身份验证,则返回null。它可以是异步的。

对于 Nitro 服务器,在server/plugins/authorization-resolver.ts中创建一个新的插件

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', async (event) => {
    event.context.$authorization = {
      resolveServerUser: () => {
        // Your logic to retrieve the user from the server
      },
    }
  })
})

!注意阅读有关event.context的更多信息

此解析器在request钩子中设置,并接收事件。您可以使用它从会话或请求中检索用户。它应该返回用户对象,如果用户未经身份验证,则返回null。它可以是异步的。

通常,您使用插件在应用程序启动时获取用户,然后将其存储。解析器函数应该只返回存储的用户,而不是再次获取它(否则,您可能会遇到严重的性能问题)。

使用nuxt-auth-utils的示例

模块nuxt-auth-utils为 Nuxt 提供了一个身份验证层。如果您使用此模块,则可以使用以下解析器

Nuxt 插件

export default defineNuxtPlugin({
  name: 'authorization-resolver',
  parallel: true,
  setup() {
    return {
      provide: {
        authorization: {
          resolveClientUser: () => useUserSession().user.value,
        },
      },
    }
  },
})

Nitro 插件

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', async (event) => {
    event.context.$authorization = {
      resolveServerUser: async () => {
        const session = await getUserSession(event)
        return session.user ?? null
      },
    }
  })
})

简单!

定义权限

!注意使用即将推出的 Nuxt4,您可以将您的权限存储在app/utils/abilities.ts文件中。然后,您可以使用~/utils/abilities将其导入到您的服务器文件夹中。查看使用Orion 项目的示例。

!注意使用 Nuxt4,将引入一个新的shared目录以轻松地在客户端和服务器之间共享代码。查看此问题

现在解析器已设置,您可以定义您的第一个权限。权限是一个至少接受用户的函数,并返回一个布尔值以指示用户是否可以执行操作。它还可以接受其他参数。

我建议创建一个新的文件utils/abilities.ts来创建您的权限

export const listPosts = defineAbility(() => true) // Only authenticated users can list posts

export const editPost = defineAbility((user: User, post: Post) => {
  return user.id === post.authorId
})

如果您有很多权限,您可能更愿意创建一个目录utils/abilities/并为每个权限创建一个文件。将权限放在utils目录中允许在客户端中使用自动导入,同时在服务器~/utils/abilities中进行简单的导入。

默认情况下,访客不允许执行任何操作,并且不会调用权限。此行为可以按权限更改

export const listPosts = defineAbility({ allowGuest: true }, (user: User | null) => true)

现在,未经身份验证的用户可以列出帖子。

使用权限

要使用权限,您可以访问 3 个权限检查函数:allowsdeniesauthorize。两者都可以在客户端和服务器上使用。*实现不同,但 API 相同,并且对开发人员完全透明。*

如果用户可以执行操作,则allows函数返回一个布尔值

if (await allows(listPosts)) {
  // User can list posts
}

如果用户不能执行操作,则denies函数返回一个布尔值

if (await denies(editPost, post)) {
  // User cannot edit the post
}

如果用户不能执行操作,则authorize函数会抛出一个错误

await authorize(editPost, post)

// User can edit the post

您可以根据权限的返回值自定义错误消息和状态代码。这对于返回 404 而不是 403 以防止用户意识到资源的存在很有用。

export const editPost = defineAbility((user: User, post: Post) => {
  if(user.id === post.authorId) {
    return true // or allow()
  }

  return deny('This post does not exist', 404)
})

allowdeny类似于返回truefalse,但deny允许为错误返回自定义消息和状态代码。

大多数情况下,您的 API 端点将使用authorize。如果不需要参数,则这可以是端点的第一行,或者在数据库查询之后检查用户是否可以访问资源。您无需捕获错误,因为它是一个H3Error,将由 Nitro 服务器捕获。

allowsdenies函数在客户端中用于执行条件渲染或逻辑很有用。您还可以使用它们对您的授权逻辑进行细粒度的控制。

使用组件

该模块提供了 2 个组件,可帮助您有条件地显示 UI 的一部分。假设您有一个按钮来编辑帖子,未经授权的用户不应该看到该按钮。

<template>
  <Can
    :ability="editPost"
    :args="[post]" // Optional if the ability does not take any arguments
  >
    <button>Edit</button>
  </Can>
</template>

只有当用户可以编辑帖子时,Can组件才会渲染该按钮。如果用户不能编辑帖子,则不会渲染该按钮。

作为对应项,您可以使用Cannot组件仅在用户不能编辑帖子时渲染该按钮。

<template>
  <Cannot
    :ability="editPost"
    :args="[post]" // Optional if the ability does not take any arguments
  >
    <p>You're not allowed to edit the post.</p>
  </Cannot>
</template>

Bouncer组件提供了一种更灵活和集中化的方式来处理单个组件内的 can 和 cannot 场景。与其使用单独的CanCannot组件,不如利用 Bouncer 组件及其命名插槽在一个统一的块中处理这两种状态。

<Bouncer
  :ability="editPost"
  :args="[post]" // Optional if the ability does not take any arguments
>
  <template #can>
    <button>Edit</button>
  </template>

  <template #cannot>
    <p>You're not allowed to edit the post.</p>
  </template>
</Bouncer>

贡献

本地开发
# Install dependencies
npm install

# Generate type stubs
npm run dev:prepare

# Develop with the playground
npm run dev

# Build the playground
npm run dev:build

# Run ESLint
npm run lint

# Run Vitest
npm run test
npm run test:watch

# Release new version
npm run release

鸣谢

此模块(代码和设计)在很大程度上受到Adonis Bouncer的启发。它是一个写得很好的软件包,我认为每次都重新发明轮子是不必要的。

许可证

MIT 许可证