亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定

使用StackAuth團隊功能和Next.js構建多租戶應用:打造待辦事項列表神器

构建一个功能完善的多租户应用程序可能非常有挑战性。除了要有灵活的注册和登录系统之外,你还需要实现几个其他关键的功能模块。

  • 租户的创建与管理
  • 邀请用户流程
  • 管理角色和权限
  • 在整个应用程序中实现数据隔离和访问控制

这确实听起来像很多工作。如果你是经验丰富的SaaS开发者,你可能已经多次做过这样的工作了。

StackAuth 是一个开源的身份验证和用户管理平台,旨在无缝集成到 Next.js 项目中。它结合了前端和后端 API 以及预构建的 UI 组件,大大简化了功能集成过程。同样,它的新的“团队”功能为创建多租户应用提供了一个很好的起点。本文将探讨如何利用它来构建一个非平凡的应用程序,同时尽量保持代码简洁清晰。

目标与技术栈

我们将要构建的应用程序是一个待办事项列表。它的核心功能很简单:创建列表并在列表中管理待办事项。不过,重点将放在多租户支持和访问控制上。

  • 团队管理

用户可以创建团队,邀请别人加入,并管理成员,分配角色。

当前情境

用户可以选一个团队作为当前的背景。

  • 数据隔离

只能访问当前团队的数据。

  • 基于角色的访问控制

  • 管理员可以访问他们团队内的所有数据。
  • 普通用户可以完全访问他们自己创建的待办事项列表。
  • 普通用户可以查看并管理其他成员的待办事项列表,只要这些待办事项列表不是设置为私密的。

我们用来构建App的必备工具是

  • Next.js: 全栈框架 (Next.js)
  • StackAuth: 用户认证及团队管理
  • Prisma: 我们用来与数据库通信的 ORM (Prisma)
  • ZenStack: 位于 Prisma 之上的授权层,处理数据隔离和访问控制

您可以在帖子最后找到完成项目的链接。

团队管理的添加

你已经创建了一个 Next.js 项目吧,并且按照 StackAuth 的设置指南完成了所有步骤,。确认基本的注册和登录流程是否正常运行。另外,在 StackAuth 的管理控制台里,启用“客户端团队创建”和“自动团队创建”的选项,在“团队设置”中的选项。

团队设置

现在,我们可以把“SelectedTeamSwitcher”组件加到布局中了。

    // src/app/layout.tsx

    import { SelectedTeamSwitcher } from "@stackframe/stack";
    ...

    // 导出默认的 RootLayout 函数,它接收一个包含 children 属性的对象作为参数
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            // 使用 StackProvider 组件包裹整个布局,并传递应用程序实例作为属性
            <StackProvider app={stackServerApp}>
              // StackTheme 组件用于设置主题
              <StackTheme>
                // header 区域包含 SelectedTeamSwitcher 组件,用于切换团队
                <header>
                  <SelectedTeamSwitcher />
                </header>
                // main 区域包含传入的 children 组件
                <main>{children}</main>
              </StackTheme>
            </StackProvider>
          </body>
        </html>
      );
    }

全屏模式 退出全屏

有了这句话,你将获得一整套用于管理团队并选择一个团队的完整UI组件!

团队管理(团队管理)

尽管 StackAuth 让添加“团队”功能变得轻松实现,但如何利用用户和团队信息来控制数据访问则取决于你。接下来我们将看看如何将其与 Prisma/ZenStack 结合以实现授权。

搭建数据库环境

我们的用户和团队数据存储在 StackAuth 方面。我们需要将待办事项列表和项目存储在我们自己的数据库中。在此部分,我们将配置 Prisma 和 ZenStack 并建立数据库架构。

我们先来安装所需的软件包。

// 安装 Prisma 和 Zenstack 开发依赖
npm install --save-dev prisma zenstack
// 安装 Prisma 客户端和 Zenstack 运行时
npm install @prisma/client @zenstackhq/runtime

点击全屏模式 / 点击退出全屏

然后我们可以创建数据库架构。请注意,我们将创建一个 schema.zmodel 文件(替代原来的 "schema.prisma" 文件)。ZModel 语言 不仅允许你定义数据模式,还允许你定义访问控制策略。在本节中,我们将仅关注数据建模方面。

    // schema.zmodel

    数据源定义 db {
      提供程序 = "postgresql"
      url      = env("DATABASE_URL")
    }

    生成器定义 js {
      提供程序 = "prisma-client-js"
    }

    // 待办事项列表
    模型定义 List {
      id        String        @id @default(cuid())
      createdAt DateTime      @default(now())
      title     String
      private   Boolean       @default(false)
      orgId     String?
      ownerId   String
      todos     Todo[]
    }

    // 待办事项
    模型定义 Todo {
      id          String    @id @default(cuid())
      title       String
      completedAt DateTime?
      list        List      @relation(fields: [listId], references: [id], onDelete: Cascade)
      listId      String
    }

切换到全屏 退出全屏

你可以生成一个普通的 Prisma 模式文件,并将该模式推送到数据库里。

    # `zenstack generate` 命令会创建 "prisma/schema.prisma" 文件并运行 "prisma generate"
    zenstack generate
    npx prisma db push    # 执行数据库迁移命令

全屏 退出全屏

最后,在项目中,创建一个 "src/server/db.ts" 文件以导出 Prisma 客户端实例。

// src/server/db.ts
// 引入 PrismaClient 类
import { PrismaClient } from "@prisma/client";
// 导出一个新的 PrismaClient 实例
export const prisma = new PrismaClient();

全屏模式,退出全屏

实施访问控制

如前所述,ZenStack 允许你在单一模式中建模数据和访问控制。让我们看看如何完全通过它来满足我们的授权需求。规则通过 @@allow@@deny 属性来定义。默认情况下,访问被拒绝,除非明确地通过 @@allow 规则授予访问权限。

尽管授权和认证是两个不同的概念,授权通常依赖于认证来运行。例如,要确定当前用户是否能访问某个列表,需要根据用户的ID、当前团队和在团队中的角色来判断。为了表示这些信息,让我们先定义一个类型来表示它们。

// schema.zmodel

// auth 的定义
type Auth {
  // 当前用户 ID
  userId         String  @id

  // 当前团队 ID
  currentTeamId   String?

  // 当前团队角色
  currentTeamRole String?

  @@auth
}

点击进入全屏 点击退出全屏

然后你可以使用访问策略规则中的特殊 auth() 函数来访问当前用户的信息。我们以 List 模型为例来说明这些规则是如何设定的。

    // schema.zmodel

    model 列表模型 {
      ...

      // 拒绝未认证访问
      @@deny('all', auth() == null)

      // 租户隔离:如果用户当前组织与目标组织不匹配,则拒绝访问
      @@deny('all', auth().currentOrgId != orgId)

      // 所有者或管理员具有全部访问权限
      @@allow('all', auth().userId == ownerId || auth().currentOrgRole == 'org:admin')

      // 如果非私有,组织成员可以读取
      @@allow('read', !private)

      // 创建时,必须将所有者设置为当前用户
      @@allow('create', ownerId == auth().userId)
    }

切换到全屏模式并随时退出全屏

最后一个谜题是,正如你可能已经猜到的,auth()的值是从哪里来的?在运行时,ZenStack 提供了一个 enhance() API,用于生成一个增强版的 PrismaClient(一个轻量级的包装),该包装会自动实施访问策略。当你调用 enhance() 时,需要传递一个用户上下文(通常是从认证服务提供商获取的),这个上下文提供了 auth() 的值来源。

我们将在下一节详细看看它是怎么工作的。

最后,用户界面

在开始构建UI之前,让我们先制作一个辅助工具,用于为当前用户、团队和角色获取增强版的PrismaClient实例,

    // src/server/db.ts

    import { enhance } from "@zenstackhq/runtime";
    import { stackServerApp } from "~/stack";

    export async function getUserDb() {
      const stackAuthUser = await stackServerApp.getUser();
      const currentTeam = stackAuthUser?.selectedTeam;

      // 默认情况下,StackAuth 的团队成员具有 "admin" 或 "member" 角色的身份
      const perm =
        currentTeam && (await stackAuthUser.getPermission(currentTeam, "admin"));

      const user = stackAuthUser
        ? {
            userId: stackAuthUser.id,
            currentTeamId: stackAuthUser.selectedTeam?.id,
            currentTeamRole: perm ? "admin" : "member",
          }
        : undefined; // 根据上下文,此处可以省略注释
      return enhance(prisma, { user });
    }

点击这里切换到全屏模式)或退出全屏模式)

让我们使用React Server Components(RSC)和Server Actions来构建用户界面。我们还将始终使用getUserDb()(获取用户数据库的帮助函数)来访问受控访问的数据库。

这里有一个RSC,它会为当前用户渲染待办事项(样式部分已省略)。

    // src/components/TodoList.tsx

    // 显示当前用户待办事项列表的组件

    export default async function TodoLists() {
      const db = await getUserDb();

      // 增强的 PrismaClient 自动过滤出无法访问的列表
      const lists = await db.list.findMany({
        orderBy: { updatedAt: "desc" },
      });

      return (
        <div>
          <div>
            {/* 客户端组件,用于创建新列表 */}
            <CreateList />

            <ul>
              {lists?.map((list) => (<Link href={`/lists/${list.id}`} key={list.id}><li>{list.title}</li></Link>))}
            </ul>
          </div>
        </div>
      );
    }

点击进入全屏播放,退出全屏

客户端组件通过调用服务器上的动作创建新列表。

// src/components/CreateList.tsx

"use client";

import { createList } from "~/app/actions";

export default function CreateList() {
  function onCreate() {
    const title = prompt("请输入你的列表标题");
    if (title) {
      createList(title);
    }
  }

  return (
    <button onClick={onCreate}>
      新建列表
    </button>
  );
}

全屏查看;退出全屏

    // src/app/actions.ts

    'use server';

    import { revalidatePath } from "next/cache";
    import { getUserDb } from "~/server/db";

    export async function 创建清单(title: string) {
      const db = await getUserDb();
      await db.list.create({ data: { title } });
      revalidatePath("/");
    }

全屏模式;退出全屏模式

List UI

管理Todo项的组件为了简洁起见省略了展示,但是其思路是相似的。你可以在这里找到完整的代码实现here

结论部分

认证和授权是大多数应用程序的两大基石。对于多租户应用来说,构建起来可能特别有挑战性。本文展示了如何通过结合StackAuth的“团队”功能和ZenStack的权限控制能力,大大简化了这一过程。最终结果是一个既安全又灵活,且几乎不需要样板代码的应用程序。

StackAuth 还支持为团队设定自定义权限。虽然这里没有详细介绍,但经过一些调整,你可以利用它来设定访问策略。这样一来,你就可以在 StackAuth 的仪表板上管理这些权限,并让 ZenStack 在运行时强制实施这些权限。

點擊查看更多內容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優質文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學習,寫下你的評論
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學

大額優惠券免費領

立即參與 放棄機會
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號

舉報

0/150
提交
取消