Reputation: 81
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)
}
Upvotes: 1
Views: 1396
Reputation: 1233
If you want to intercept only at the point of creation you can do this by wrapping or patching the adapter object. For example:
...
const patchedAdapter = PrismaAdapter(prisma);
const originalCreateUser = patchedAdapter.createUser;
if (originalCreateUser) {
patchedAdapter.createUser = async (user) => {
const isAdmin = ...
originalCreateUser({...user, isAdmin});
}
}
export default NextAuth({
...
adapter: patchedAdapter,
...
This intercepts the call from AuthJS to the DB and injects the isAdmin property. It will work a little better than what you have since the first user object returned will also have isAdmin set to true.
Hope this helps!
Upvotes: 0
Reputation: 81
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.
Upvotes: 3