Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update profile image on login #3491

Merged
merged 1 commit into from
Jun 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion website/src/components/Header/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function UserMenu() {
<Menu>
<MenuButton border="solid" borderRadius="full" borderWidth="thin" borderColor={borderColor}>
<Box display="flex" alignItems="center" gap="3" p="1">
<Avatar size="sm" src={session.user.image!} />
<Avatar size="sm" src={session.user.image} />
<Text data-cy="username" className="hidden lg:flex ltr:pr-2 rtl:pl-2" style={{ overflow: "hidden" }}>
{session.user.name || "New User"}
</Text>
Expand Down
51 changes: 0 additions & 51 deletions website/src/lib/discord_avatar_refresh.ts

This file was deleted.

75 changes: 27 additions & 48 deletions website/src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -90,15 +89,15 @@ 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<string, Set<string>> = process.env.ADMIN_USERS.split(",").reduce((result, entry) => {
const [authType, id] = entry.split(":");
const s = result.get(authType) || new Set();
s.add(id);
result.set(authType, s);
return result;
}, new Map());

const moderatorUserMap = process.env.MODERATOR_USERS.split(",").reduce((result, entry) => {
const moderatorUserMap: Map<string, Set<string>> = process.env.MODERATOR_USERS.split(",").reduce((result, entry) => {
const [authType, id] = entry.split(":");
const s = result.get(authType) || new Set();
s.add(id);
Expand Down Expand Up @@ -127,60 +126,44 @@ const authOptions: AuthOptions = {
session.user.role = token.role;
session.user.isNew = token.isNew;
session.user.name = token.name;
session.user.image = token.picture;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can receive image from user property in the callback here

Copy link
Collaborator Author

@AbdBarho AbdBarho Jun 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean the user object which is given as a parameter to the session callback?

the user object is only passed if we are using the database strategy, but we are using jwt.

Or did I misunderstand what you say?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the user object is only passed if we are using the database strategy, but we are using jwt.

Hmm I don't see where the docs mention this, the TS signature doesn't mark it as nullable as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

if I console log it is undefined, would be a lot easier if we did not have to pass everything though the token.

session.user.tosAcceptanceDate = token.tosAcceptanceDate;
session.inference = { isAuthenticated: !!token.inferenceTokens };
return session;
},
},
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,
});
}
},
Expand All @@ -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) {
Expand All @@ -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;
},
Expand Down