Blitz 尚在 beat 阶段! 🎉 预计会在今年的 Q3 季度发布 1.0
Back to Documentation Menu

如何扮演其他用户

扮演通常是 SaaS 应用的关键工具,因为它允许管理员从特定的角度来访问应用,而 无需知道他们的身份凭据。

这是一个你最终成果会达到的一个的示例: 成果会如何的截图

首先,增加一个 impersonatingFromUserId 类型到你的 会话 中。

// types.ts
import { DefaultCtx, SessionContext, DefaultPublicData } from "blitz"
import { User } from "db"

declare module "blitz" {
  export interface Ctx extends DefaultCtx {
    session: SessionContext
  }
  export interface PublicData extends DefaultPublicData {
    role: "admin" | "customer"
    userId: User["id"]
    orgId: User["orgId"]
    impersonatingFromUserId?: number
  }
}

创建以下两个 mutations,将使你能够启动和停止扮演。

// app/auth/mutations/impersonateUser.ts
import { resolver } from "blitz"
import db from "db"
import * as z from "zod"

export const ImpersonateUserInput = z.object({
  userId: z.number(),
})

export default resolver.pipe(
  resolver.zod(ImpersonateUserInput),
  resolver.authorize(),
  async ({ userId }, ctx) => {
    const user = await db.user.findFirst({ where: { id: userId } })
    if (!user) throw new Error("Could not find user id " + userId)

    await ctx.session.$create({
      userId: user.id,
      role: user.role,
      orgId: user.organizationId,
      impersonatingFromUserId: ctx.session.userId,
    })

    return user
  }
)
// app/auth/mutations/stopImpersonating.ts
import { resolver } from "blitz"
import db from "db"

export default resolver.pipe(resolver.authorize(), async (_, ctx) => {
  const userId = ctx.session.$publicData.impersonatingFromUserId
  if (!userId) {
    console.log("Already not impersonating anyone")
    return
  }

  const user = await db.user.findFirst({
    where: { id: userId },
  })
  if (!user) throw new Error("Could not find user id " + userId)

  await ctx.session.$create({
    userId: user.id,
    role: user.role,
    orgId: user.organizationId,
    impersonatingFromUserId: undefined,
  })

  return user
})

添加类似于此的表单以切换用户。

import { useMutation, queryClient } from "blitz"
import impersonateUser, {
  ImpersonateUserInput,
} from "app/auth/mutations/impersonateUser"
import { Form, FORM_ERROR } from "app/core/components/Form"
import LabeledTextField from "app/core/components/LabeledTextField"

export const ImpersonateUserForm = () => {
  const [impersonateUserMutation] = useMutation(impersonateUser)

  return (
    <Form
      schema={ImpersonateUserInput}
      submitText="Switch to User"
      onSubmit={async (values) => {
        try {
          await impersonateUserMutation(values)
          queryClient.clear()
        } catch (error) {
          return {
            [FORM_ERROR]:
              "Sorry, we had an unexpected error. Please try again. - " +
              error.toString(),
          }
        }
      }}
    >
      <LabeledTextField name="userId" type="number" label="User ID" />
    </Form>
  )
}

最后,在布局的顶部添加以下组件。

// app/core/components/ImpersonatingUserNotice.tsx
import { invoke, useSession, queryClient } from "blitz"
import stopImpersonating from "app/auth/mutations/stopImpersonating"

export const ImpersonatingUserNotice = () => {
  const session = useSession()
  if (!session.impersonatingFromUserId) return null

  return (
    <div className="bg-yellow-400 px-2 py-1 text-center font-semibold">
      Currently impersonating user {session.userId}{" "}
      <button
        className="appearance-none bg-transparent text-black uppercase"
        onClick={async () => {
          await invoke(stopImpersonating, {})
          queryClient.clear()
        }}
      >
        Exit
      </button>
    </div>
  )
}

你做到了!你现在可以扮演一个用户并停止扮演了。


Idea for improving this page? Edit it on GitHub.