【问题标题】:How to use numeric user id with next-auth如何在 next-auth 中使用数字用户 ID
【发布时间】:2023-02-06 13:36:09
【问题描述】:

我最近发现并开始测试 create-t3-app 作为 NextJS 项目的新基础,因为它负责 TypeScript、trpc、prisma 和 next-auth 等大量样板设置,因此可以节省我大量时间。虽然这是相关的,但我认为这不是我问题的根源。我的问题是我使用的 MySQL 数据库具有自动递增的用户 ID,但似乎 next-auth 包的类型迫使我使用字符串(next-auth/core/types.d.ts 中的 DefaultUsernext-auth/adapters.d.ts 中的 AdapterUser两者都将 id 的类型设置为字符串,并且 cmets 都说 UUID)。考虑到我可能会扩展那里的内容以支持数字用户 ID,我将其添加到 next-auth.d.ts

import { DefaultSession, DefaultUser } from 'next-auth'

declare module 'next-auth' {
  interface User extends DefaultUser {
    id: number;
  }
  interface Session {
    user?: {
      id: number;
    } & DefaultSession['user'];
  }
}

这似乎在大多数地方都有效,除了在[...nextauth].ts 中它给了我这个错误

输入'字符串| number' 不能分配给类型 'number'。类型“字符串”不可分配给类型“数字”.ts(2322)

在这一段代码的session.user.id = user.id

export const authOptions: NextAuthOptions = {
  // Include user.id on session
  callbacks: {
    session({ session, user }) {
      if (session.user) {
        session.user.id = user.id
      }
      return session
    }
  },
  adapter: PrismaAdapter(prisma),
  providers: []
}

export default NextAuth(authOptions)

如果我从 next-auth/adapters.d.ts 中删除 AdapterUser 中的 id: string; 行,TypeScript 错误就会消失,然后回退到 id: number;,因为我将其设置为 User

export interface AdapterUser extends User {
    id: string; // <-- I removed this
    email: string;
    emailVerified: Date | null;
}

我不认为我应该修改库的类型来支持数字用户 ID,但我完全不知道如何解决这个问题,也没有在网上找到答案。还是我不应该使用数字 ID,即使我的数据库使用了数字 ID?如果有帮助,我正在使用

next 12.3.1
next-auth 4.12.3
react 18.2.0
react-dom 18.2.0
superjson 1.9.1
zod 3.18.0
eslint 8.22.0
postcss 8.4.14
prettier 2.7.1
prisma 4.4.0
tailwindcss 3.1.6
typescrypt 4.8.0

【问题讨论】:

  • 为什么不将 ID 字符串化?
  • @caTS 似乎和我当前的解决方案一样笨拙,带有额外的操作,特别是考虑到文档和 cmets 表明 id 旨在成为 UUID。
  • @DillanWilding UUID 是类似于“123e4567-e89b-12d3-a456-426614174000”的字符串。您是否尝试将 User.id 声明为 number | string 并更新与 ... = parseInt(user.id, 10) 的比较
  • @IQbrod 我知道 UUID 是什么。问题不在于 User 上的 id 不是数字(或字符串),而是 next-auth 提供的 TypeScript 类型已将用户 ID 类型传播到其他类型,例如 AdapterUser(我认为其他几个地方)当我想将其更改为数值时会引起问题。如果他们在扩展 User 时没有指定 id 的类型,它将默认/回退到 User 上指定的类型,但由于他们说 id: string; 它会导致问题。
  • @dillanwilding 你应该考虑在他们的项目上提出一个问题和/或一个拉取请求,因为如果不更新库的代码就无法解决这个问题

标签: typescript next.js next-auth


【解决方案1】:

不幸的是,似乎不可能“修补”或更改库的类型——即使尝试更改 NextAuthOptions 也不起作用,因为后续的属性定义必须具有相同的类型。同时,您必须使用运行时检查/强制或强制转换:

export const authOptions: NextAuthOptions = {
  // Include user.id on session
  callbacks: {
    session({ session, user }) {
      if (session.user) {
        if (typeof user.id !== "number") throw new Error("id should a number");
        session.user.id = user.id // OK
        // session.user.id = +user.id // more dangerous but still works
        // session.user.id = user.id as number // also dangerous
      }
      return session
    }
  },
  adapter: PrismaAdapter(prisma),
  providers: []
}

【讨论】:

  • 首先感谢关注。虽然这在技术上消除了 TypeScript 错误,但我很好奇是否有更好的解决方案——如果我没有得到一个,我会奖励你的答案,因为它确实解决了我的问题,而不是我想要的方式喜欢。我现在看到的问题是,我的印象是,如果我实现了扩展DefaultUserUser 并指定了它不会覆盖它的id: number,它就会充当一个联合体(即string | number)。知道是否有办法替换类型而不是建立联合吗?
  • @DillanWilding 就是这样 - 你不能。成员的后续声明必须是完全相同的类型;换句话说,您不能缩小类型或覆盖它。在库作者提供扩充其类型的方法之前,这是最好的解决方法。
【解决方案2】:

扩展上面接受的答案,我利用运行时类型保护让我的生活更轻松一些。

就我而言,我的 Prisma 用户也有一些额外的属性:

model User {
  id                  Int                 @id @default(autoincrement())
  uuid                String              @unique @default(cuid())
  name                String?
  email               String?             @unique
  emailVerified       DateTime?
  image               String?
  accounts            Account[]
  categories          Category[]
  sessions            Session[]
  records             Record[]
  permissionsGranted  SharePermission[]     @relation("permissions_granted")
  permissionsReceived SharePermission[]     @relation("permissions_received")
  @@unique([id, uuid])
  @@index(fields: [id, uuid], name: "user_by_id_idx")
}

所以我定义了这些类型保护:

import { User as PrismaUser } from '@prisma/client';
import { Session, User } from 'next-auth';

export interface IdentifiedSession extends Session {
    id: number;
    user: PrismaUser;
}

export const isNextAuthUser = (value: unknown): value is User => {
    const { id, name, email, image } = (value ?? {}) as User;
    const idIsValid = typeof id === 'number';
    const nameIsValid = typeof name === 'string' || !name;
    const emailIsValid = typeof email === 'string' || !email;
    const imageIsValid = typeof image === 'string' || !image;
    return idIsValid && nameIsValid && emailIsValid && imageIsValid;
};

export const isPrismaUser = (value: unknown): value is PrismaUser => {
    const { uuid, emailVerified } = (value ?? {}) as PrismaUser;
    const uuidIsValid = typeof uuid === 'string';
    const emailVerifiedIsEmail = emailVerified instanceof Date || !emailVerified;
    return isNextAuthUser(value) && uuidIsValid && emailVerifiedIsEmail;
};

export const isSession = (value: unknown): value is Session => {
    const { user, expires } = (value ?? {}) as Session;
    const userIsValid = isPrismaUser(user) || isNextAuthUser(user) || !user;
    const expiresIsValid = typeof expires === 'string';
    return expiresIsValid && userIsValid;
};

export const isIdentifiedSession = (value: unknown): value is IdentifiedSession => {
    const session = (value ?? {}) as IdentifiedSession;
    const sessionIsValid = isSession(session);
    const sessionUserIsValid = isPrismaUser(session.user);
    return sessionIsValid && sessionUserIsValid;
};

然后,在会话回调中,我像这样使用了一个类型保护:

session: async ({ session, user }) => {
  if (!isPrismaUser(user)) {
    return session;
  }
  const identifiedSession: IdentifiedSession = {
    ...session,
    user,
    id: user.id,
  };
  return identifiedSession;
}

这确实意味着回调的签名现在是Session | IdentifiedSession,我在下游消费者中处理它

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
    const session = await getSession({ req });
    if (!isIdentifiedSession(session)) {
        return {
            props: {
                records: [],
            },
        };
    }
    const records = await prisma.record.findMany({
        where: {
            reporter: {
                id: session.id,
            },
        },
        orderBy: {
            date: 'asc',
        },
        include: {
            category: {
                select: {
                    title: true,
                },
            },
        },
    });
    return {
        props: { records },
    };
};

【讨论】:

    猜你喜欢
    • 2023-02-11
    • 2022-01-21
    • 2021-08-06
    • 2021-11-12
    • 2022-01-22
    • 1970-01-01
    • 2022-12-15
    • 2022-01-04
    • 1970-01-01
    相关资源
    最近更新 更多