From 6a52e65e27d514273a4210dadafbee9692f23354 Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Thu, 22 Dec 2022 10:47:32 -0500 Subject: adding connection now works --- src/util/index.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/util/index.ts') diff --git a/src/util/index.ts b/src/util/index.ts index a3495a0c..cb180ee8 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -25,3 +25,4 @@ export * from "./dtos/index"; export * from "./schemas"; export * from "./imports"; export * from "./config"; +export * from "./connections" \ No newline at end of file -- cgit 1.5.1 From d76198d2009f9ddf96aa219d1d0652bc6cb393bb Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:56:33 +1100 Subject: WIP Discord connection --- src/connections/Discord/DiscordSettings.ts | 5 ++ src/connections/Discord/index.ts | 118 +++++++++++++++++++++++++++++ src/util/index.ts | 2 +- 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/connections/Discord/DiscordSettings.ts create mode 100644 src/connections/Discord/index.ts (limited to 'src/util/index.ts') diff --git a/src/connections/Discord/DiscordSettings.ts b/src/connections/Discord/DiscordSettings.ts new file mode 100644 index 00000000..a8976f12 --- /dev/null +++ b/src/connections/Discord/DiscordSettings.ts @@ -0,0 +1,5 @@ +export class DiscordSettings { + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; +} \ No newline at end of file diff --git a/src/connections/Discord/index.ts b/src/connections/Discord/index.ts new file mode 100644 index 00000000..5a89860e --- /dev/null +++ b/src/connections/Discord/index.ts @@ -0,0 +1,118 @@ +import fetch from "node-fetch"; +import { + Config, + ConnectedAccount, + ConnectionCallbackSchema, + DiscordApiErrors, + ConnectionLoader, +} from "@fosscord/util"; +import Connection from "../../util/connections/Connection"; +import { DiscordSettings } from "./DiscordSettings"; + +interface OAuthTokenResponse { + access_token: string; + token_type: string; + scope: string; + refresh_token?: string; + expires_in?: number; +} + +interface UserResponse { + id: string; + username: string; + avatar_url: string | null; +} + +export default class DiscordConnection extends Connection { + public readonly id = "discord"; + public readonly authorizeUrl = "https://discord.com/oauth2/authorize"; + public readonly tokenUrl = "https://discord.com/oauth2/token"; + public readonly userInfoUrl = "https://discord.com/api/users/@me"; + public readonly scopes = ["identify"]; + settings: DiscordSettings = new DiscordSettings(); + + init(): void { + this.settings = ConnectionLoader.getConnectionConfig( + this.id, + this.settings, + ) as DiscordSettings; + } + + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); + + url.searchParams.append("state", state); + url.searchParams.append("client_id", this.settings.clientId!); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("response_type", "code"); + // controls whether, on repeated authorizations, the consent screen is shown + url.searchParams.append("consent", "none"); + + // 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`, + ); + + 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("grant_type", "authorization_code"); + url.searchParams.append("code", code); + return url.toString(); + } + + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); + const url = this.getTokenUrl(code); + + return fetch(url, { + 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 { + 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 { + 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); + + if (exists) return null; + + return await this.createConnection({ + user_id: userId, + external_id: userInfo.id, + friend_sync: params.friend_sync, + name: userInfo.username, + type: this.id, + }) + } +} \ No newline at end of file diff --git a/src/util/index.ts b/src/util/index.ts index cb180ee8..9c0f7881 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -25,4 +25,4 @@ export * from "./dtos/index"; export * from "./schemas"; export * from "./imports"; export * from "./config"; -export * from "./connections" \ No newline at end of file +export * from "./connections" -- cgit 1.5.1 From 5a7765c7dc6e0ee5c873625ef76b0ee9c4e12300 Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Sat, 18 Mar 2023 19:50:38 -0400 Subject: prettier --- src/api/Server.ts | 2 +- src/api/middlewares/Authentication.ts | 2 +- src/connections/Discord/DiscordSettings.ts | 2 +- src/gateway/opcodes/Identify.ts | 101 +++++++++++++++-------------- src/util/index.ts | 2 +- 5 files changed, 58 insertions(+), 51 deletions(-) (limited to 'src/util/index.ts') diff --git a/src/api/Server.ts b/src/api/Server.ts index dc3b66ef..30f02e57 100644 --- a/src/api/Server.ts +++ b/src/api/Server.ts @@ -26,7 +26,7 @@ import { Sentry, WebAuthn, ConnectionConfig, - ConnectionLoader + ConnectionLoader, } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { Server, ServerOptions } from "lambert-server"; diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts index 55527984..7f008a39 100644 --- a/src/api/middlewares/Authentication.ts +++ b/src/api/middlewares/Authentication.ts @@ -53,7 +53,7 @@ export const NO_AUTHORIZATION_ROUTES = [ // Asset delivery /\/guilds\/\d+\/widget\.(json|png)/, // Connections - /\/connections\/\w+\/callback/ + /\/connections\/\w+\/callback/, ]; export const API_PREFIX = /^\/api(\/v\d+)?/; diff --git a/src/connections/Discord/DiscordSettings.ts b/src/connections/Discord/DiscordSettings.ts index a8976f12..3751b041 100644 --- a/src/connections/Discord/DiscordSettings.ts +++ b/src/connections/Discord/DiscordSettings.ts @@ -2,4 +2,4 @@ export class DiscordSettings { enabled: boolean = false; clientId: string | null = null; clientSecret: string | null = null; -} \ No newline at end of file +} diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index d1d15790..c2a12918 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -79,53 +79,60 @@ export async function onIdentify(this: WebSocket, data: Payload) { this.user_id = decoded.id; const session_id = this.session_id; - const [user, read_states, members, recipients, session, application, connected_accounts] = - await Promise.all([ - User.findOneOrFail({ - where: { id: this.user_id }, - relations: ["relationships", "relationships.to", "settings"], - select: [...PrivateUserProjection, "relationships"], - }), - ReadState.find({ where: { user_id: this.user_id } }), - Member.find({ - where: { id: this.user_id }, - select: MemberPrivateProjection, - relations: [ - "guild", - "guild.channels", - "guild.emojis", - "guild.roles", - "guild.stickers", - "user", - "roles", - ], - }), - Recipient.find({ - where: { user_id: this.user_id, closed: false }, - relations: [ - "channel", - "channel.recipients", - "channel.recipients.user", - ], - // TODO: public user selection - }), - // save the session and delete it when the websocket is closed - Session.create({ - user_id: this.user_id, - session_id: session_id, - // TODO: check if status is only one of: online, dnd, offline, idle - status: identify.presence?.status || "offline", //does the session always start as online? - client_info: { - //TODO read from identity - client: "desktop", - os: identify.properties?.os, - version: 0, - }, - activities: [], - }).save(), - Application.findOne({ where: { id: this.user_id } }), - ConnectedAccount.find({ where: { user_id: this.user_id } }) - ]); + const [ + user, + read_states, + members, + recipients, + session, + application, + connected_accounts, + ] = await Promise.all([ + User.findOneOrFail({ + where: { id: this.user_id }, + relations: ["relationships", "relationships.to", "settings"], + select: [...PrivateUserProjection, "relationships"], + }), + ReadState.find({ where: { user_id: this.user_id } }), + Member.find({ + where: { id: this.user_id }, + select: MemberPrivateProjection, + relations: [ + "guild", + "guild.channels", + "guild.emojis", + "guild.roles", + "guild.stickers", + "user", + "roles", + ], + }), + Recipient.find({ + where: { user_id: this.user_id, closed: false }, + relations: [ + "channel", + "channel.recipients", + "channel.recipients.user", + ], + // TODO: public user selection + }), + // save the session and delete it when the websocket is closed + Session.create({ + user_id: this.user_id, + session_id: session_id, + // TODO: check if status is only one of: online, dnd, offline, idle + status: identify.presence?.status || "offline", //does the session always start as online? + client_info: { + //TODO read from identity + client: "desktop", + os: identify.properties?.os, + version: 0, + }, + activities: [], + }).save(), + Application.findOne({ where: { id: this.user_id } }), + ConnectedAccount.find({ where: { user_id: this.user_id } }), + ]); if (!user) return this.close(CLOSECODES.Authentication_failed); if (!user.settings) { diff --git a/src/util/index.ts b/src/util/index.ts index 9c0f7881..ef2671a5 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -25,4 +25,4 @@ export * from "./dtos/index"; export * from "./schemas"; export * from "./imports"; export * from "./config"; -export * from "./connections" +export * from "./connections"; -- cgit 1.5.1