diff --git a/website/src/components/Header/UserMenu.tsx b/website/src/components/Header/UserMenu.tsx index 1140d7389e..a87963c2b6 100644 --- a/website/src/components/Header/UserMenu.tsx +++ b/website/src/components/Header/UserMenu.tsx @@ -72,7 +72,7 @@ export function UserMenu() { - + {session.user.name || "New User"} diff --git a/website/src/lib/discord_avatar_refresh.ts b/website/src/lib/discord_avatar_refresh.ts deleted file mode 100644 index f0c3ea41e7..0000000000 --- a/website/src/lib/discord_avatar_refresh.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Account } from "@prisma/client"; - -import prisma from "./prismadb"; - -const ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24; - -class DiscordAvatarRefresher { - private readonly lastUpdated: Record = {}; - - async updateImageIfNecessary(account?: Account) { - if (!account) { - return; - } - - const { access_token, token_type, providerAccountId, provider, userId } = account; - console.assert(provider === "discord"); - - const lastUpdate = this.lastUpdated[providerAccountId]; - const now = Date.now(); - if (lastUpdate && now - lastUpdate < ONE_DAY_IN_MILLISECONDS) { - // updated recently, ignore - return; - } - - this.lastUpdated[providerAccountId] = now; - try { - const response = await fetch("https://discord.com/api/v10/users/@me", { - headers: { Authorization: `${token_type} ${access_token}` }, - }); - const user = await response.json(); - - let imgURL = null; - if (user && user.id && user.avatar) { - imgURL = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`; - } - - await prisma.user.update({ - where: { id: userId }, - data: { image: imgURL }, - }); - } catch (err) { - console.error(err); - // mark as refresh-able - delete this.lastUpdated[providerAccountId]; - } - } -} - -const globalForRefresh = global as unknown as { discordAvatarRefresh: DiscordAvatarRefresher }; - -export const discordAvatarRefresh = globalForRefresh.discordAvatarRefresh || new DiscordAvatarRefresher(); diff --git a/website/src/pages/api/auth/[...nextauth].ts b/website/src/pages/api/auth/[...nextauth].ts index 0d2c73e00f..b5bdf54128 100644 --- a/website/src/pages/api/auth/[...nextauth].ts +++ b/website/src/pages/api/auth/[...nextauth].ts @@ -10,7 +10,6 @@ import DiscordProvider from "next-auth/providers/discord"; import EmailProvider from "next-auth/providers/email"; import GoogleProvider from "next-auth/providers/google"; import { checkCaptcha } from "src/lib/captcha"; -import { discordAvatarRefresh } from "src/lib/discord_avatar_refresh"; import { createApiClientFromUser } from "src/lib/oasst_client_factory"; import prisma from "src/lib/prismadb"; import { convertToBackendUserCore } from "src/lib/users"; @@ -90,7 +89,7 @@ if (boolean(process.env.DEBUG_LOGIN) || process.env.NODE_ENV === "development") // Create a map of provider types to a set of admin user identifiers based on // the environment variables. We assume the list is separated by ',' and each // entry is separated by ':'. -const adminUserMap = process.env.ADMIN_USERS.split(",").reduce((result, entry) => { +const adminUserMap: Map> = process.env.ADMIN_USERS.split(",").reduce((result, entry) => { const [authType, id] = entry.split(":"); const s = result.get(authType) || new Set(); s.add(id); @@ -98,7 +97,7 @@ const adminUserMap = process.env.ADMIN_USERS.split(",").reduce((result, entry) = return result; }, new Map()); -const moderatorUserMap = process.env.MODERATOR_USERS.split(",").reduce((result, entry) => { +const moderatorUserMap: Map> = process.env.MODERATOR_USERS.split(",").reduce((result, entry) => { const [authType, id] = entry.split(":"); const s = result.get(authType) || new Set(); s.add(id); @@ -127,6 +126,7 @@ const authOptions: AuthOptions = { session.user.role = token.role; session.user.isNew = token.isNew; session.user.name = token.name; + session.user.image = token.picture; session.user.tosAcceptanceDate = token.tosAcceptanceDate; session.inference = { isAuthenticated: !!token.inferenceTokens }; return session; @@ -134,53 +134,36 @@ const authOptions: AuthOptions = { }, events: { /** - * Update the user's role after they have successfully signed in + * Update the user after they have successfully signed in */ - async signIn({ user, account, isNewUser }) { + async signIn({ user, account, isNewUser, profile }) { + // any values that might be updated in the user profile + const toBeUpdated: Partial<{ name: string; role: string; image: string }> = {}; + if (isNewUser && account.provider === "email" && !user.name) { // only generate a username if the user is new and they signed up with email and they don't have a name // although the name already assigned in the jwt callback, this is to ensure nothing breaks, and we should never reach here. - await prisma.user.update({ - data: { - name: generateUsername(), - }, - where: { - id: user.id, - }, - }); + toBeUpdated.name = generateUsername(); } - // Get the admin list for the user's auth type. - const adminForAccountType = adminUserMap.get(account.provider); - const moderatorForAccountType = moderatorUserMap.get(account.provider); - - // Return early if there's no admin list. - if (!adminForAccountType && !moderatorForAccountType) { - return; + // update image + if (profile && profile.image) { + toBeUpdated.image = profile.image; } + // update roles // TODO(#236): Reduce the number of times we update the role field. - - // Update the database if the user is an admin. - if (adminForAccountType.has(account.providerAccountId)) { - await prisma.user.update({ - data: { - role: "admin", - }, - where: { - id: user.id, - }, - }); + if (moderatorUserMap.get(account.provider)?.has(account.providerAccountId)) { + toBeUpdated.role = "moderator"; + } + if (adminUserMap.get(account.provider)?.has(account.providerAccountId)) { + toBeUpdated.role = "admin"; } - if (moderatorForAccountType.has(account.providerAccountId)) { + if (Object.keys(toBeUpdated).length > 0) { await prisma.user.update({ - data: { - role: "moderator", - }, - where: { - id: user.id, - }, + where: { id: user.id }, + data: toBeUpdated, }); } }, @@ -199,7 +182,7 @@ export default function auth(req: NextApiRequest, res: NextApiResponse) { async jwt({ token }) { const frontendUser = await prisma.user.findUnique({ where: { id: token.sub }, - select: { name: true, role: true, isNew: true, accounts: true, id: true }, + select: { name: true, role: true, isNew: true, accounts: true, image: true, id: true }, }); if (!frontendUser) { @@ -221,23 +204,19 @@ export default function auth(req: NextApiRequest, res: NextApiResponse) { }); } - const backendUser = convertToBackendUserCore(frontendUser); - if (backendUser.auth_method === "discord") { - const discordAccount = frontendUser.accounts.find((a) => a.provider === "discord"); - discordAvatarRefresh.updateImageIfNecessary(discordAccount); - } - token.name = frontendUser.name; token.role = frontendUser.role; token.isNew = frontendUser.isNew; + token.picture = frontendUser.image; // these are immutable once assigned if (!token.tosAcceptanceDate || !token.backendUserId) { + const backendUser = convertToBackendUserCore(frontendUser); const oasstApiClient = createApiClientFromUser(backendUser); - const frontendUser = await oasstApiClient.upsert_frontend_user(backendUser); - token.backendUserId = frontendUser.user_id; - token.tosAcceptanceDate = frontendUser.tos_acceptance_date; + const user = await oasstApiClient.upsert_frontend_user(backendUser); + token.backendUserId = user.user_id; + token.tosAcceptanceDate = user.tos_acceptance_date; } return token; },