const { DiscordOAuth2 } = require("@mgalacyber/discord-oauth2") const oauth2Repository = require("../repositories/oauth2Repository") const userService = require("./userService") const authService = require("./authService") const { StateTypes, Scopes, PromptTypes, ResponseCodeTypes } = require("@mgalacyber/discord-oauth2") const { DefaultError } = require("../errors/errors") const oauth2_association = new DiscordOAuth2({ clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET, redirectUri: process.env.DISCORD_ASSOCIATION_REDIRECT_URL }) const oauth2_login = new DiscordOAuth2({ clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET, redirectUri: process.env.DISCORD_LOGIN_REDIRECT_URL }) async function generateAssociationDiscordURL(playerUuid) { const redirectObject = await oauth2_association.GenerateOAuth2Url({ state: StateTypes.UserAuth, scope: [ Scopes.Identify ], prompt: PromptTypes.Consent, responseCode: ResponseCodeTypes.Code, }) await oauth2Repository.createLinkAttempt(redirectObject.state, playerUuid) return redirectObject } async function handleAssociationCallback(provider, code, state) { const playerUuid = await oauth2Repository.popLinkAttempt(state) if (!playerUuid) { throw new DefaultError(400, "Invalid or expired session state.", "InvalidStateError") } let isProviderAlreadyLinked = false try { await userService.getPlayerProperty(playerUuid, `${provider}Id`) isProviderAlreadyLinked = true } catch (error) { if (error.code !== 404) throw error } if (isProviderAlreadyLinked) { throw new DefaultError(409, `Account from ${provider} already linked to that player`, "AlreadyLinkedException") } try { const tokenResponse = await oauth2_association.GetAccessToken(code) const userProfile = await oauth2_association.UserDataSchema.GetUserProfile(tokenResponse.accessToken) if (!userProfile || !userProfile.id) { throw new DefaultError(500, `Failed to retrieve ${provider} profile.`) } await userService.addPlayerProperty(playerUuid, `${provider}Id`, userProfile.id) return { code: 200, message: "Account linked successfully", provider: { id: userProfile.id, username: userProfile.username } } } catch (error) { if (error instanceof DefaultError) throw error throw new DefaultError(500, `${provider} authentication failed: + ${error.message}`) } } async function unlinkAccount(provider, playerUuid) { try { const property = await userService.getPlayerProperty(playerUuid, `${provider}Id`).catch(() => null) if (!property) { throw new DefaultError(404, `No ${provider} account linked to this player.`, "NotLinkedError") } const success = await oauth2Repository.unlinkProviderAccount(provider, playerUuid) if (!success) { throw new DefaultError(500, "Failed to unlink the account. Please try again.") } return { code: 200, message: `${provider} account successfully unlinked.` } } catch (error) { if (error instanceof DefaultError) throw error; throw new DefaultError(500, "An error occurred during unlinking: " + error.message) } } async function generateLoginDiscordURL() { const redirectObject = await oauth2_login.GenerateOAuth2Url({ state: StateTypes.UserAuth, scope: [ Scopes.Identify ], prompt: PromptTypes.Consent, responseCode: ResponseCodeTypes.Code, }) return redirectObject } async function handleLoginCallback(provider, code, requestUser) { try { const tokenResponse = await oauth2_login.GetAccessToken(code) const userProfile = await oauth2_login.UserDataSchema.GetUserProfile(tokenResponse.accessToken) if (!userProfile || !userProfile.id) { throw new DefaultError(500, `Failed to retrieve ${provider} profile.`) } const propertyObject = await userService.getPlayerPropertyByValue(`${provider}Id`, userProfile.id) return await authService.authenticateWithoutPassword({ identifier: propertyObject.property.uuid, requireUser: requestUser || true }) } catch (error) { if (error.code == 404) { throw new DefaultError(404, `No ${provider} account linked to any player.`, "NotLinkedError") } if (error instanceof DefaultError) throw error throw new DefaultError(500, `${provider} authentication failed: + ${error.message}`) } } module.exports = { unlinkAccount, handleLoginCallback, generateLoginDiscordURL, handleAssociationCallback, generateAssociationDiscordURL }