-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.config.ts
More file actions
155 lines (139 loc) · 4.06 KB
/
auth.config.ts
File metadata and controls
155 lines (139 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import prisma from "./lib/db"
import { getDepartment } from "./lib/graph"
import { Prisma, Role } from "@prisma/client"
import { DefaultSession, NextAuthConfig } from "next-auth"
import MicrosoftEntraIDProfile from "next-auth/providers/microsoft-entra-id"
const ALLOWED_ADMINS = process.env.CPP_ALLOWED_ADMINS?.split(",") ?? []
const ALLOWED_DEPARTMENTS = process.env.CPP_ALLOWED_DEPARTMENTS?.split(",") ?? ["Department of Computing", "Computing"]
declare module "@auth/core/adapters" {
interface AdapterUser {
role?: Role
}
}
declare module "@auth/core/jwt" {
interface JWT extends DefaultJWT {
role: Role
id: string
}
}
declare module "next-auth" {
interface User {
role: Role
}
interface Session {
user: {
role: Role
id: string
} & DefaultSession["user"]
}
interface JWT {
role: Role
id: string
}
}
export default {
providers: [
MicrosoftEntraIDProfile({
clientId: process.env.MS_ENTRA_CLIENT_ID,
clientSecret: process.env.MS_ENTRA_CLIENT_SECRET,
tenantId: process.env.MS_ENTRA_TENANT_ID,
authorization: {
params: {
scope: "offline_access openid profile email User.Read",
},
},
async profile(profile) {
// By default the role is STUDENT unless in admins
const role: Role = ALLOWED_ADMINS.includes(profile.email) ? "ADMIN" : "STUDENT"
// Update role if user already exists
const currentUser = await prisma.user.findUnique({
select: {
role: true,
},
where: {
email: profile.email,
},
})
if (currentUser && currentUser.role !== role) {
await prisma.user.update({
data: {
role: role,
},
where: {
email: profile.email,
},
})
}
const user: Prisma.UserCreateInput = {
id: profile.sub,
role,
name: profile.name,
email: profile.email,
studentProfile:
role === "STUDENT"
? {
create: {
studentShortcode: profile.preferred_username.split("@")[0],
},
}
: undefined,
}
return user
},
}),
],
callbacks: {
authorized: async ({ auth }) => {
// Logged in users are authenticated, otherwise redirect to login page
return !!auth
},
signIn: async ({ account, user, email }) => {
// Admins always have access
// Company users will always exist in the database
// If the role field is not present, it means that user doesn't exist
if (user.role === "ADMIN" || user.role === "COMPANY") {
return true
// if email.verificationRequest is true, it means the user is signing in for the first time
// using magic sign in
// If they are allowed to do that the COMPANY check will have already passed
} else if (!account?.access_token || email?.verificationRequest) {
console.error("Authentication failed for user", user, account, email)
return false
}
const department = await getDepartment(account?.access_token)
const isCorrectDepartment = department && ALLOWED_DEPARTMENTS.includes(department)
if (!isCorrectDepartment) {
console.error(`User ${user.name} is from department ${department}, access denied`)
}
return isCorrectDepartment
},
jwt({ token, user }) {
if (user) {
if (user.role) {
token.role = user.role
}
if (user.id) {
token.id = user.id
}
}
// Clear image
if (token.picture) {
delete token.picture
}
if (token.image) {
delete token.image
}
return token
},
session({ session, token }) {
session.user.role = token.role
session.user.id = token.id
return session
},
},
pages: {
signIn: "/auth/login",
verifyRequest: "/auth/login/partner/success",
error: "/auth/error",
},
} satisfies NextAuthConfig