134 lines
4.9 KiB
JavaScript
134 lines
4.9 KiB
JavaScript
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, YggdrasilError } = 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 YggdrasilError(404, "NotLinkedError", `No ${provider} account linked to any player.`)
|
|
}
|
|
if (error instanceof DefaultError) throw error
|
|
throw new DefaultError(500, `${provider} authentication failed: + ${error.message}`)
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
unlinkAccount,
|
|
handleLoginCallback,
|
|
generateLoginDiscordURL,
|
|
handleAssociationCallback,
|
|
generateAssociationDiscordURL
|
|
} |