Files
erika/lib/auth/discord-user-core.ts

79 lines
2.4 KiB
TypeScript

import type { AdapterAccount } from "next-auth/adapters";
export type DiscordIdentity = {
discordId: string;
displayName: string;
avatarUrl: string | null;
email?: string | null;
account?: Partial<AdapterAccount>;
};
export function discordAccountUpdate(
account: Partial<AdapterAccount> | undefined,
) {
if (!account) return {};
return {
...(account.type !== undefined ? { type: account.type } : {}),
...(account.refresh_token !== undefined
? { refresh_token: account.refresh_token }
: {}),
...(account.access_token !== undefined
? { access_token: account.access_token }
: {}),
...(account.expires_at !== undefined
? { expires_at: account.expires_at }
: {}),
...(account.token_type !== undefined
? { token_type: account.token_type }
: {}),
...(account.scope !== undefined ? { scope: account.scope } : {}),
...(account.id_token !== undefined ? { id_token: account.id_token } : {}),
...(account.session_state !== undefined
? { session_state: account.session_state as string | null }
: {}),
};
}
export type DiscordAccountUpdate = ReturnType<typeof discordAccountUpdate>;
export type DiscordUserOperations<TUser> = {
findLinkedUserId(discordId: string): Promise<string | null>;
upsertUser(identity: DiscordIdentity): Promise<string>;
insertAccount(
userId: string,
discordId: string,
account: Partial<AdapterAccount> | undefined,
): Promise<void>;
updateUser(userId: string, identity: DiscordIdentity): Promise<TUser | null>;
updateAccount(
discordId: string,
update: DiscordAccountUpdate,
): Promise<void>;
};
export async function reconcileDiscordUser<TUser>(
identity: DiscordIdentity,
operations: DiscordUserOperations<TUser>,
) {
const { discordId, account } = identity;
let userId = await operations.findLinkedUserId(discordId);
if (!userId) {
userId = await operations.upsertUser(identity);
await operations.insertAccount(userId, discordId, account);
const winner = await operations.findLinkedUserId(discordId);
if (!winner) throw new Error("Failed to link Discord account");
userId = winner;
}
const user = await operations.updateUser(userId, identity);
const nextAccount = discordAccountUpdate(account);
if (Object.keys(nextAccount).length > 0) {
await operations.updateAccount(discordId, nextAccount);
}
if (!user) throw new Error("Failed to reconcile Discord user");
return user;
}