diff options
author | Puyodead1 <puyodead@proton.me> | 2022-12-22 10:05:51 -0500 |
---|---|---|
committer | Puyodead1 <puyodead@proton.me> | 2023-03-18 19:09:51 -0400 |
commit | 21bfda32e452c05b8906bf318df7415d6cd5acd0 (patch) | |
tree | 997f6ff0dd5ec6969cfea270776e4d96f1d82820 /src/connections | |
parent | Merge pull request #1005 from Xanderplayz18/patch-1 (diff) | |
download | server-21bfda32e452c05b8906bf318df7415d6cd5acd0.tar.xz |
add connections
Diffstat (limited to 'src/connections')
-rw-r--r-- | src/connections/BattleNet/BattleNetSettings.ts | 5 | ||||
-rw-r--r-- | src/connections/BattleNet/index.ts | 133 | ||||
-rw-r--r-- | src/connections/GitHub/GitHubSettings.ts | 5 | ||||
-rw-r--r-- | src/connections/GitHub/index.ts | 114 |
4 files changed, 257 insertions, 0 deletions
diff --git a/src/connections/BattleNet/BattleNetSettings.ts b/src/connections/BattleNet/BattleNetSettings.ts new file mode 100644 index 00000000..75e5c3ae --- /dev/null +++ b/src/connections/BattleNet/BattleNetSettings.ts @@ -0,0 +1,5 @@ +export class BattleNetSettings { + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; +} diff --git a/src/connections/BattleNet/index.ts b/src/connections/BattleNet/index.ts new file mode 100644 index 00000000..0fd0aa18 --- /dev/null +++ b/src/connections/BattleNet/index.ts @@ -0,0 +1,133 @@ +import fetch from "node-fetch"; +import { Config, ConnectionCallbackSchema, DiscordApiErrors } from "../../util"; +import Connection from "../../util/connections/Connection"; +import { ConnectionLoader } from "../../util/connections/ConnectionLoader"; +import { BattleNetSettings } from "./BattleNetSettings"; + +interface OAuthTokenResponse { + access_token: string; + token_type: string; + scope: string; + refresh_token?: string; + expires_in?: number; +} + +interface BattleNetConnectionUser { + sub: string; + id: number; + battletag: string; +} + +interface BattleNetErrorResponse { + error: string; + error_description: string; +} + +export default class BattleNetConnection extends Connection { + public readonly id = "battlenet"; + public readonly authorizeUrl = "https://oauth.battle.net/authorize"; + public readonly tokenUrl = "https://oauth.battle.net/token"; + public readonly userInfoUrl = "https://us.battle.net/oauth/userinfo"; + public readonly scopes = []; + settings: BattleNetSettings = new BattleNetSettings(); + + init(): void { + this.settings = ConnectionLoader.getConnectionConfig( + this.id, + this.settings, + ) as BattleNetSettings; + } + + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); + + url.searchParams.append("client_id", this.settings.clientId!); + // TODO: probably shouldn't rely on cdn as this could be different from what we actually want. we should have an api endpoint setting. + url.searchParams.append( + "redirect_uri", + `${ + Config.get().cdn.endpointPrivate || "http://localhost:3001" + }/connections/${this.id}/callback`, + ); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + url.searchParams.append("response_type", "code"); + return url.toString(); + } + + getTokenUrl(): string { + return this.tokenUrl; + } + + async exchangeCode(state: string, code: string): Promise<string> { + this.validateState(state); + + const url = this.getTokenUrl(); + + return fetch(url.toString(), { + method: "POST", + headers: { + Accept: "application/json", + }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code: code, + client_id: this.settings.clientId!, + client_secret: this.settings.clientSecret!, + redirect_uri: `${ + Config.get().cdn.endpointPrivate || "http://localhost:3001" + }/connections/${this.id}/callback`, + }), + }) + .then((res) => res.json()) + .then((res: OAuthTokenResponse & BattleNetErrorResponse) => { + if (res.error) throw new Error(res.error_description); + return res.access_token; + }) + .catch((e) => { + console.error( + `Error exchanging token for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + }); + } + + async getUser(token: string): Promise<BattleNetConnectionUser> { + const url = new URL(this.userInfoUrl); + return fetch(url.toString(), { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => res.json()) + .then((res: BattleNetConnectionUser & BattleNetErrorResponse) => { + if (res.error) throw new Error(res.error_description); + return res; + }); + } + + async handleCallback(params: ConnectionCallbackSchema): Promise<boolean> { + const userId = this.getUserId(params.state); + const token = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(token); + + const exists = await this.hasConnection(userId, userInfo.id.toString()); + + if (exists) return false; + await this.createConnection({ + user_id: userId, + external_id: userInfo.id, + friend_sync: params.friend_sync, + name: userInfo.battletag, + revoked: false, + show_activity: false, + type: this.id, + verified: true, + visibility: 0, + integrations: [], + }); + return true; + } +} diff --git a/src/connections/GitHub/GitHubSettings.ts b/src/connections/GitHub/GitHubSettings.ts new file mode 100644 index 00000000..1b4070d2 --- /dev/null +++ b/src/connections/GitHub/GitHubSettings.ts @@ -0,0 +1,5 @@ +export class GitHubSettings { + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; +} diff --git a/src/connections/GitHub/index.ts b/src/connections/GitHub/index.ts new file mode 100644 index 00000000..a96ac68e --- /dev/null +++ b/src/connections/GitHub/index.ts @@ -0,0 +1,114 @@ +import fetch from "node-fetch"; +import { Config, ConnectionCallbackSchema, DiscordApiErrors } from "../../util"; +import Connection from "../../util/connections/Connection"; +import { ConnectionLoader } from "../../util/connections/ConnectionLoader"; +import { GitHubSettings } from "./GitHubSettings"; + +interface OAuthTokenResponse { + access_token: string; + token_type: string; + scope: string; + refresh_token?: string; + expires_in?: number; +} + +interface UserResponse { + login: string; + id: number; + name: string; +} + +export default class GitHubConnection extends Connection { + public readonly id = "github"; + public readonly authorizeUrl = "https://github.com/login/oauth/authorize"; + public readonly tokenUrl = "https://github.com/login/oauth/access_token"; + public readonly userInfoUrl = "https://api.github.com/user"; + public readonly scopes = ["read:user"]; + settings: GitHubSettings = new GitHubSettings(); + + init(): void { + this.settings = ConnectionLoader.getConnectionConfig( + this.id, + this.settings, + ) as GitHubSettings; + } + + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); + + url.searchParams.append("client_id", this.settings.clientId!); + // TODO: probably shouldn't rely on cdn as this could be different from what we actually want. we should have an api endpoint setting. + url.searchParams.append( + "redirect_uri", + `${ + Config.get().cdn.endpointPrivate || "http://localhost:3001" + }/connections/${this.id}/callback`, + ); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + return url.toString(); + } + + getTokenUrl(code: string): string { + const url = new URL(this.tokenUrl); + url.searchParams.append("client_id", this.settings.clientId!); + url.searchParams.append("client_secret", this.settings.clientSecret!); + url.searchParams.append("code", code); + return url.toString(); + } + + async exchangeCode(state: string, code: string): Promise<string> { + this.validateState(state); + + const url = this.getTokenUrl(code); + + return fetch(url.toString(), { + method: "POST", + headers: { + Accept: "application/json", + }, + }) + .then((res) => res.json()) + .then((res: OAuthTokenResponse) => res.access_token) + .catch((e) => { + console.error( + `Error exchanging token for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + }); + } + + async getUser(token: string): Promise<UserResponse> { + const url = new URL(this.userInfoUrl); + return fetch(url.toString(), { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + }, + }).then((res) => res.json()); + } + + async handleCallback(params: ConnectionCallbackSchema): Promise<boolean> { + const userId = this.getUserId(params.state); + const token = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(token); + + const exists = await this.hasConnection(userId, userInfo.id.toString()); + + if (exists) return false; + await this.createConnection({ + user_id: userId, + external_id: userInfo.id, + friend_sync: params.friend_sync, + name: userInfo.name, + revoked: false, + show_activity: false, + type: this.id, + verified: true, + visibility: 0, + integrations: [], + }); + return true; + } +} |