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

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

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 代码:

import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import prisma from "lib/prisma"

let data

if (process.env.NODE_ENV === "development") {
    data = {
        server: process.env.EMAIL_SERVER,
        from: process.env.EMAIL_FROM,
    }
} else {
    data = {
        server: {
            host: process.env.EMAIL_SERVER_HOST,
            port: process.env.EMAIL_SERVER_PORT,
            auth: {
                user: process.env.EMAIL_SERVER_USER,
                pass: process.env.EMAIL_SERVER_PASSWORD,
            },
        },
        from: process.env.EMAIL_FROM,
    }
}

export default NextAuth({
    providers: [EmailProvider(data)],

    database: process.env.DATABASE_URL,
    secret: process.env.SECRET,

    session: {
        jwt: true,
        maxAge: 30 * 24 * 60 * 60, // 30 days
    },

    debug: true,
    adapter: PrismaAdapter(prisma),

    callbacks: {
        session: async ({ session, user }) => {
            session.user.id = user.id
            session.user.isAdmin = user.isAdmin
            return Promise.resolve(session)
        },
    },
})

以及我的 Prisma 模式:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  isAdmin       Boolean   @default(false)
  accounts      Account[]
  sessions      Session[]
}

// 其余模型略...
英文:

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.

import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import prisma from "lib/prisma"

let data

if (process.env.NODE_ENV === "development") {
    data = {
        server: process.env.EMAIL_SERVER,
        from: process.env.EMAIL_FROM,
    }
} else {
    data = {
        server: {
            host: process.env.EMAIL_SERVER_HOST,
            port: process.env.EMAIL_SERVER_PORT,
            auth: {
                user: process.env.EMAIL_SERVER_USER,
                pass: process.env.EMAIL_SERVER_PASSWORD,
            },
        },
        from: process.env.EMAIL_FROM,
    }
}

export default NextAuth({
    providers: [EmailProvider(data)],

    database: process.env.DATABASE_URL,
    secret: process.env.SECRET,

    session: {
        jwt: true,
        maxAge: 30 * 24 * 60 * 60, // 30 days
    },

    debug: true,
    adapter: PrismaAdapter(prisma),

    callbacks: {
        session: async ({ session, user }) => {
            session.user.id = user.id
            session.user.isAdmin = user.isAdmin
            return Promise.resolve(session)
        },
    },
})

and my Prisma schema

  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  isAdmin       Boolean   @default(false)
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?
  oauth_token_secret String?
  oauth_token        String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

答案1

得分: 3

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

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

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

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

具体步骤:

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

ADMIN_EMAIL_DOMAIN="whitelist.domain.com"

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

//...与上述代码相同

import { updateUserToAdmin } from "lib/data.js";

//...与上述代码相同

export default NextAuth({
   //...与上述代码相同

    events: {
        signIn: async ({ user, isNewUser }) => {
            if (isNewUser) {
                const userEmail = user.email;
                const isAdminEmail =
                    userEmail.split("@")[1] === process.env.ADMIN_EMAIL_DOMAIN;

                isAdminEmail
                    ? await updateUserToAdmin(user.id, prisma, isAdminEmail)
                    : console.log("非管理员域");
            }
        },
    },
});

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

export const updateUserToAdmin = async (userId, prisma, isAdminEmail) => {
    return await prisma.user.update({
        where: {
            id: userId,
        },
        data: {
            isAdmin: isAdminEmail,
        },
    });
}

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

英文:

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:

ADMIN_EMAIL_DOMAIN="whitelist.domain.com"

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

//...code as above

import { updateUserToAdmin } from "lib/data.js"

//...code as above

export default NextAuth({
   //...code as above

    events: {
        signIn: async ({ user, isNewUser }) => {
            if (isNewUser) {
                const userEmail = user.email
                const isAdminEmail =
                    userEmail.split("@")[1] === process.env.ADMIN_EMAIL_DOMAIN

                isAdminEmail
                    ? await updateUserToAdmin(user.id, prisma, isAdminEmail)
                    : console.log(`non-Admin domain`)
            }
        },
    },
})

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

    return await prisma.user.update({
        where: {
            id: userId,
        },
        data: {
            isAdmin: isAdminEmail,
        },
    })
}

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:

确定