next-auth with EmailProvider – is it possible to assign a new user to a role based on their email domain?

huangapple go评论104阅读模式
英文:

next-auth with EmailProvider - is it possible to assign a new user to a role based on their email domain?

问题

我正在使用NextAuth和EmailProvider来处理登录到Next.js应用程序的功能,并使用Prisma将新用户保存在PostgreSQL数据库中。模式包括一个名为isAdmin的字段,默认值为false

我想要将该字段设置为true,但只适用于从特定电子邮件域登录的新用户,但我不确定是否有可能实现这一点。目前,我需要手动进入数据库,一旦用户注册后,就设置标志,这显然非常不理想。

我非常感激任何指导,甚至确认我正在尝试的是否可能!

补充说明: 现在我认为我可能需要向NextAuth函数添加事件或回调,但文档侧重于重定向用户,而不是用户创建,因此我仍然不确定我正在尝试的是否可能实现(https://next-auth.js.org/configuration/events)。

以下是我的 pages>api>auth>[...nextauth].js 代码:

  1. import NextAuth from "next-auth"
  2. import EmailProvider from "next-auth/providers/email"
  3. import { PrismaAdapter } from "@next-auth/prisma-adapter"
  4. import prisma from "lib/prisma"
  5. let data
  6. if (process.env.NODE_ENV === "development") {
  7. data = {
  8. server: process.env.EMAIL_SERVER,
  9. from: process.env.EMAIL_FROM,
  10. }
  11. } else {
  12. data = {
  13. server: {
  14. host: process.env.EMAIL_SERVER_HOST,
  15. port: process.env.EMAIL_SERVER_PORT,
  16. auth: {
  17. user: process.env.EMAIL_SERVER_USER,
  18. pass: process.env.EMAIL_SERVER_PASSWORD,
  19. },
  20. },
  21. from: process.env.EMAIL_FROM,
  22. }
  23. }
  24. export default NextAuth({
  25. providers: [EmailProvider(data)],
  26. database: process.env.DATABASE_URL,
  27. secret: process.env.SECRET,
  28. session: {
  29. jwt: true,
  30. maxAge: 30 * 24 * 60 * 60, // 30 days
  31. },
  32. debug: true,
  33. adapter: PrismaAdapter(prisma),
  34. callbacks: {
  35. session: async ({ session, user }) => {
  36. session.user.id = user.id
  37. session.user.isAdmin = user.isAdmin
  38. return Promise.resolve(session)
  39. },
  40. },
  41. })

以及我的 Prisma 模式:

  1. generator client {
  2. provider = "prisma-client-js"
  3. }
  4. datasource db {
  5. provider = "postgresql"
  6. url = env("DATABASE_URL")
  7. }
  8. model User {
  9. id String @id @default(cuid())
  10. name String?
  11. email String? @unique
  12. emailVerified DateTime?
  13. createdAt DateTime @default(now())
  14. updatedAt DateTime @updatedAt
  15. isAdmin Boolean @default(false)
  16. accounts Account[]
  17. sessions Session[]
  18. }
  19. // 其余模型略...
英文:

I'm using NextAuth and EmailProvider to handle log-in to a Next.js app, and new users are saved in a postgres database using Prisma. The schema includes a isAdmin field that defaults to false

I'd like to set that field to true for new users who sign in from a specific email domain but can't work out if that's possible - at the moment I need to go into the dB and set the flag manually once the user has signed up, which is obviously very much not ideal.

I'd very much appreciate any pointers, or even confirmation that what I'm trying to do is impossible!

ETA: I now think I may need to add either an Event or a Callback to the NextAuth function, but the documentation focuses on redirecting users rather than on user creation, so I'm still not sure if what I'm trying to do is even possible (https://next-auth.js.org/configuration/events).

Below is my pages>api>auth>[...nextauth].js code.

  1. import NextAuth from "next-auth"
  2. import EmailProvider from "next-auth/providers/email"
  3. import { PrismaAdapter } from "@next-auth/prisma-adapter"
  4. import prisma from "lib/prisma"
  5. let data
  6. if (process.env.NODE_ENV === "development") {
  7. data = {
  8. server: process.env.EMAIL_SERVER,
  9. from: process.env.EMAIL_FROM,
  10. }
  11. } else {
  12. data = {
  13. server: {
  14. host: process.env.EMAIL_SERVER_HOST,
  15. port: process.env.EMAIL_SERVER_PORT,
  16. auth: {
  17. user: process.env.EMAIL_SERVER_USER,
  18. pass: process.env.EMAIL_SERVER_PASSWORD,
  19. },
  20. },
  21. from: process.env.EMAIL_FROM,
  22. }
  23. }
  24. export default NextAuth({
  25. providers: [EmailProvider(data)],
  26. database: process.env.DATABASE_URL,
  27. secret: process.env.SECRET,
  28. session: {
  29. jwt: true,
  30. maxAge: 30 * 24 * 60 * 60, // 30 days
  31. },
  32. debug: true,
  33. adapter: PrismaAdapter(prisma),
  34. callbacks: {
  35. session: async ({ session, user }) => {
  36. session.user.id = user.id
  37. session.user.isAdmin = user.isAdmin
  38. return Promise.resolve(session)
  39. },
  40. },
  41. })

and my Prisma schema

  1. provider = "prisma-client-js"
  2. }
  3. datasource db {
  4. provider = "postgresql"
  5. url = env("DATABASE_URL")
  6. }
  7. model User {
  8. id String @id @default(cuid())
  9. name String?
  10. email String? @unique
  11. emailVerified DateTime?
  12. createdAt DateTime @default(now())
  13. updatedAt DateTime @updatedAt
  14. isAdmin Boolean @default(false)
  15. accounts Account[]
  16. sessions Session[]
  17. }
  18. model VerificationToken {
  19. identifier String
  20. token String @unique
  21. expires DateTime
  22. @@unique([identifier, token])
  23. }
  24. model Account {
  25. id String @id @default(cuid())
  26. userId String
  27. type String
  28. provider String
  29. providerAccountId String
  30. refresh_token String? @db.Text
  31. access_token String? @db.Text
  32. expires_at Int?
  33. token_type String?
  34. scope String?
  35. id_token String? @db.Text
  36. session_state String?
  37. oauth_token_secret String?
  38. oauth_token String?
  39. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  40. @@unique([provider, providerAccountId])
  41. }
  42. model Session {
  43. id String @id @default(cuid())
  44. sessionToken String @unique
  45. userId String
  46. expires DateTime
  47. user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  48. }

答案1

得分: 3

我已解决了我的问题,答案是"不过...但"。如果有可能劫持next-auth用户创建过程,我还没有找到方法 - 但现在我怀疑这可能涉及分叉next-auth代码,而且总体而言都不是一个好主意。所以"不,你不能在创建时分配角色给用户"。

但是,正如我在编辑我的问题时提到的,next-auth有事件的概念,允许您在响应next-auth生命周期事件时执行自己的代码,例如signIn事件每次成功登录时触发。

所以我使用了signIn事件,它接收用户对象和一个isNewUser布尔值作为参数。我检查用户是否是新创建的,如果是,他们是否拥有指定的电子邮件域?如果是,然后我在用户创建后更新数据库。

由于所有这些都是在登录期间完成的,对用户来说看起来像一个单一的步骤过程。

具体步骤:

(1) 在.env文件中添加所需的电子邮件域:

  1. ADMIN_EMAIL_DOMAIN="whitelist.domain.com"

(2) 在[...nextauth].js中的NextAuth函数中添加一个事件:

  1. //...与上述代码相同
  2. import { updateUserToAdmin } from "lib/data.js";
  3. //...与上述代码相同
  4. export default NextAuth({
  5. //...与上述代码相同
  6. events: {
  7. signIn: async ({ user, isNewUser }) => {
  8. if (isNewUser) {
  9. const userEmail = user.email;
  10. const isAdminEmail =
  11. userEmail.split("@")[1] === process.env.ADMIN_EMAIL_DOMAIN;
  12. isAdminEmail
  13. ? await updateUserToAdmin(user.id, prisma, isAdminEmail)
  14. : console.log("非管理员域");
  15. }
  16. },
  17. },
  18. });

(3) 在lib>data.js中添加updateUserToAdmin查询:

  1. export const updateUserToAdmin = async (userId, prisma, isAdminEmail) => {
  2. return await prisma.user.update({
  3. where: {
  4. id: userId,
  5. },
  6. data: {
  7. isAdmin: isAdminEmail,
  8. },
  9. });
  10. }

希望其他人会发现这对他们有帮助。如果您想提出任何改进意见,请随时评论。

英文:

I have solved my own problem and the answer is "no ...but". If it is possible to hijack the next-auth user creation process, I haven't found out how - but I now suspect that it would involve forking the next-auth code and would all round be a Really Bad Idea anyway. So "no, you can't assign a role to a user at the point of creation"

However, as I mentioned in my edit to my question, next-auth has the concept of Events, which allow you to execute your own code in response to next-auth life-cycle occurrences e.g. the signIn event is triggered every time a successful log in occurs.

So I used the signIn event, which receives the user object and an isNewUser boolean as params. I check if the user is newly-created and if so, do they have the specified email domain? If yes, then I update the database after the user has been created.

Since this is all done during sign in, it looks like a single-step process to the user.

In detail:

(1) add the required email domain to the .env file:

  1. ADMIN_EMAIL_DOMAIN="whitelist.domain.com"

(2) add an event to the NextAuth function in [...nextauth].js:

  1. //...code as above
  2. import { updateUserToAdmin } from "lib/data.js"
  3. //...code as above
  4. export default NextAuth({
  5. //...code as above
  6. events: {
  7. signIn: async ({ user, isNewUser }) => {
  8. if (isNewUser) {
  9. const userEmail = user.email
  10. const isAdminEmail =
  11. userEmail.split("@")[1] === process.env.ADMIN_EMAIL_DOMAIN
  12. isAdminEmail
  13. ? await updateUserToAdmin(user.id, prisma, isAdminEmail)
  14. : console.log(`non-Admin domain`)
  15. }
  16. },
  17. },
  18. })

(3) add the updateUserToAdmin query to lib>data.js:

  1. return await prisma.user.update({
  2. where: {
  3. id: userId,
  4. },
  5. data: {
  6. isAdmin: isAdminEmail,
  7. },
  8. })
  9. }

I hope someone else finds this helpful. Do feel free to comment if you want to suggest any improvements.

huangapple
  • 本文由 发表于 2023年2月8日 20:07:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/75385575.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定