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/util | |
parent | Merge pull request #1005 from Xanderplayz18/patch-1 (diff) | |
download | server-21bfda32e452c05b8906bf318df7415d6cd5acd0.tar.xz |
add connections
Diffstat (limited to 'src/util')
-rw-r--r-- | src/util/connections/Connection.ts | 72 | ||||
-rw-r--r-- | src/util/connections/ConnectionConfig.ts | 79 | ||||
-rw-r--r-- | src/util/connections/ConnectionLoader.ts | 65 | ||||
-rw-r--r-- | src/util/connections/ConnectionStore.ts | 5 | ||||
-rw-r--r-- | src/util/connections/index.ts | 4 | ||||
-rw-r--r-- | src/util/dtos/ConnectedAccountDTO.ts | 41 | ||||
-rw-r--r-- | src/util/dtos/index.ts | 1 | ||||
-rw-r--r-- | src/util/entities/ConnectedAccount.ts | 23 | ||||
-rw-r--r-- | src/util/entities/ConnectionConfigEntity.ts | 11 | ||||
-rw-r--r-- | src/util/entities/index.ts | 1 | ||||
-rw-r--r-- | src/util/interfaces/Event.ts | 7 | ||||
-rw-r--r-- | src/util/schemas/ConnectionCallbackSchema.ts | 7 | ||||
-rw-r--r-- | src/util/schemas/index.ts | 1 |
13 files changed, 313 insertions, 4 deletions
diff --git a/src/util/connections/Connection.ts b/src/util/connections/Connection.ts new file mode 100644 index 00000000..02104d39 --- /dev/null +++ b/src/util/connections/Connection.ts @@ -0,0 +1,72 @@ +import crypto from "crypto"; +import { ConnectedAccount } from "../entities"; +import { OrmUtils } from "../imports"; +import { ConnectionCallbackSchema } from "../schemas"; +import { DiscordApiErrors } from "../util"; + +export default abstract class Connection { + id: string; + settings: { enabled: boolean }; + states: Map<string, string> = new Map(); + + abstract init(): void; + + /** + * Generates an authorization url for the connection. + * @param args + */ + abstract getAuthorizationUrl(userId: string): string; + + /** + * Processes the callback + * @param args Callback arguments + */ + abstract handleCallback(params: ConnectionCallbackSchema): Promise<boolean>; + + /** + * Gets a user id from state + * @param state the state to get the user id from + * @returns the user id associated with the state + */ + getUserId(state: string): string { + if (!this.states.has(state)) throw DiscordApiErrors.INVALID_OAUTH_STATE; + return this.states.get(state) as string; + } + + /** + * Generates a state + * @param user_id The user id to generate a state for. + * @returns a new state + */ + createState(userId: string): string { + const state = crypto.randomBytes(16).toString("hex"); + this.states.set(state, userId); + + return state; + } + + /** + * Takes a state and checks if it is valid, and deletes it. + * @param state The state to check. + */ + validateState(state: string): void { + if (!this.states.has(state)) throw DiscordApiErrors.INVALID_OAUTH_STATE; + this.states.delete(state); + } + + async createConnection(data: any): Promise<void> { + const ca = OrmUtils.mergeDeep(new ConnectedAccount(), data); + await ca.save(); + } + + async hasConnection(userId: string, externalId: string): Promise<boolean> { + const existing = await ConnectedAccount.findOne({ + where: { + user_id: userId, + external_id: externalId, + }, + }); + + return !!existing; + } +} diff --git a/src/util/connections/ConnectionConfig.ts b/src/util/connections/ConnectionConfig.ts new file mode 100644 index 00000000..9b120c93 --- /dev/null +++ b/src/util/connections/ConnectionConfig.ts @@ -0,0 +1,79 @@ +import { ConnectionConfigEntity } from "../entities/ConnectionConfigEntity"; + +let config: any; +let pairs: ConnectionConfigEntity[]; + +export const ConnectionConfig = { + init: async function init() { + if (config) return config; + console.log("[ConnectionConfig] Loading configuration..."); + pairs = await ConnectionConfigEntity.find(); + config = pairsToConfig(pairs); + + return this.set(config); + }, + get: function get() { + if (!config) { + return {}; + } + return config; + }, + set: function set(val: Partial<any>) { + if (!config || !val) return; + config = val.merge(config); + console.debug("config", config); // TODO: if no more issues with sql, remove this or find the reason why it's happening + + return applyConfig(config); + }, +}; + +function applyConfig(val: any) { + async function apply(obj: any, key = ""): Promise<any> { + if (typeof obj === "object" && obj !== null && !(obj instanceof Date)) + return Promise.all( + Object.keys(obj).map((k) => + apply(obj[k], key ? `${key}_${k}` : k), + ), + ); + + let pair = pairs.find((x) => x.key === key); + if (!pair) pair = new ConnectionConfigEntity(); + + pair.key = key; + + if (pair.value !== obj) { + pair.value = obj; + if (!pair.key || pair.key == null) { + console.log(`[ConnectionConfig] WARN: Empty key`); + console.log(pair); + } else return pair.save(); + } + } + + return apply(val); +} + +function pairsToConfig(pairs: ConnectionConfigEntity[]) { + let value: any = {}; + + pairs.forEach((p) => { + const keys = p.key.split("_"); + let obj = value; + let prev = ""; + let prevObj = obj; + let i = 0; + + for (const key of keys) { + if (!isNaN(Number(key)) && !prevObj[prev]?.length) + prevObj[prev] = obj = []; + if (i++ === keys.length - 1) obj[key] = p.value; + else if (!obj[key]) obj[key] = {}; + + prev = key; + prevObj = obj; + obj = obj[key]; + } + }); + + return value; +} diff --git a/src/util/connections/ConnectionLoader.ts b/src/util/connections/ConnectionLoader.ts new file mode 100644 index 00000000..d06a6446 --- /dev/null +++ b/src/util/connections/ConnectionLoader.ts @@ -0,0 +1,65 @@ +import fs from "fs"; +import path from "path"; +import { OrmUtils } from "../imports"; +import Connection from "./Connection"; +import { ConnectionConfig } from "./ConnectionConfig"; +import { ConnectionStore } from "./ConnectionStore"; + +const root = "dist/connections"; +let connectionsLoaded = false; + +export class ConnectionLoader { + public static async loadConnections() { + if (connectionsLoaded) return; + ConnectionConfig.init(); + const dirs = fs.readdirSync(root).filter((x) => { + try { + fs.readdirSync(path.join(root, x)); + return true; + } catch (e) { + return false; + } + }); + + dirs.forEach(async (x) => { + let modPath = path.resolve(path.join(root, x)); + console.log(`Loading connection: ${modPath}`); + const mod = new (require(modPath).default)() as Connection; + ConnectionStore.connections.set(mod.id, mod); + + mod.init(); + console.log(`[Connections] Loaded connection '${mod.id}'`); + }); + } + + public static getConnectionConfig(id: string, defaults?: any): any { + let cfg = ConnectionConfig.get()[id]; + if (defaults) { + if (cfg) cfg = OrmUtils.mergeDeep(defaults, cfg); + else cfg = defaults; + this.setConnectionConfig(id, cfg); + } + + if (!cfg) + console.log( + `[ConnectionConfig/WARN] Getting connection settings for '${id}' returned null! (Did you forget to add settings?)`, + ); + return cfg; + } + + public static async setConnectionConfig( + id: string, + config: Partial<any>, + ): Promise<void> { + if (!config) + console.log( + `[ConnectionConfig/WARN] ${id} tried to set config=null!`, + ); + await ConnectionConfig.set({ + [id]: OrmUtils.mergeDeep( + ConnectionLoader.getConnectionConfig(id) || {}, + config, + ), + }); + } +} diff --git a/src/util/connections/ConnectionStore.ts b/src/util/connections/ConnectionStore.ts new file mode 100644 index 00000000..406e8232 --- /dev/null +++ b/src/util/connections/ConnectionStore.ts @@ -0,0 +1,5 @@ +import Connection from "./Connection"; + +export class ConnectionStore { + public static connections: Map<string, Connection> = new Map(); +} diff --git a/src/util/connections/index.ts b/src/util/connections/index.ts new file mode 100644 index 00000000..e15d0c8c --- /dev/null +++ b/src/util/connections/index.ts @@ -0,0 +1,4 @@ +export * from "./Connection"; +export * from "./ConnectionConfig"; +export * from "./ConnectionLoader"; +export * from "./ConnectionStore"; diff --git a/src/util/dtos/ConnectedAccountDTO.ts b/src/util/dtos/ConnectedAccountDTO.ts new file mode 100644 index 00000000..a0287086 --- /dev/null +++ b/src/util/dtos/ConnectedAccountDTO.ts @@ -0,0 +1,41 @@ +import { ConnectedAccount } from "../entities"; + +export class ConnectedAccountDTO { + id: string; + user_id: string; + access_token?: string; + friend_sync: boolean; + name: string; + revoked: boolean; + show_activity: boolean; + type: string; + verified: boolean; + visibility: boolean; + integrations: string[]; + metadata_: any; + metadata_visibility: boolean; + two_way_link: boolean; + + constructor( + connectedAccount: ConnectedAccount, + with_token: boolean = false, + ) { + this.id = connectedAccount.external_id; + this.user_id = connectedAccount.user_id; + this.access_token = + connectedAccount.access_token && with_token + ? connectedAccount.access_token + : undefined; + this.friend_sync = connectedAccount.friend_sync; + this.name = connectedAccount.name; + this.revoked = connectedAccount.revoked; + this.show_activity = connectedAccount.show_activity; + this.type = connectedAccount.type; + this.verified = connectedAccount.verified; + this.visibility = connectedAccount.visibility; + this.integrations = connectedAccount.integrations; + this.metadata_ = connectedAccount.metadata_; + this.metadata_visibility = connectedAccount.metadata_visibility; + this.two_way_link = connectedAccount.two_way_link; + } +} diff --git a/src/util/dtos/index.ts b/src/util/dtos/index.ts index cf3863fa..b91889ff 100644 --- a/src/util/dtos/index.ts +++ b/src/util/dtos/index.ts @@ -16,6 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +export * from "./ConnectedAccountDTO"; export * from "./DmChannelDTO"; export * from "./ReadyGuildDTO"; export * from "./UserDTO"; diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts index 9f0ce35e..70923d2c 100644 --- a/src/util/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts @@ -27,6 +27,9 @@ export type PublicConnectedAccount = Pick< @Entity("connected_accounts") export class ConnectedAccount extends BaseClass { + @Column() + external_id: string; + @Column({ nullable: true }) @RelationId((account: ConnectedAccount) => account.user) user_id: string; @@ -41,16 +44,16 @@ export class ConnectedAccount extends BaseClass { access_token: string; @Column({ select: false }) - friend_sync: boolean; + friend_sync: boolean = false; @Column() name: string; @Column({ select: false }) - revoked: boolean; + revoked: boolean = false; @Column({ select: false }) - show_activity: boolean; + show_activity: boolean = true; @Column() type: string; @@ -59,5 +62,17 @@ export class ConnectedAccount extends BaseClass { verified: boolean; @Column({ select: false }) - visibility: number; + visibility: boolean = true; + + @Column({ type: "simple-array" }) + integrations: string[]; + + @Column({ type: "simple-json", name: "metadata" }) + metadata_: any; + + @Column() + metadata_visibility: boolean = true; + + @Column() + two_way_link: boolean = false; } diff --git a/src/util/entities/ConnectionConfigEntity.ts b/src/util/entities/ConnectionConfigEntity.ts new file mode 100644 index 00000000..9c212b15 --- /dev/null +++ b/src/util/entities/ConnectionConfigEntity.ts @@ -0,0 +1,11 @@ +import { Column, Entity } from "typeorm"; +import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass"; + +@Entity("connection_config") +export class ConnectionConfigEntity extends BaseClassWithoutId { + @PrimaryIdColumn() + key: string; + + @Column({ type: "simple-json", nullable: true }) + value: number | boolean | null | string | Date | undefined; +} diff --git a/src/util/entities/index.ts b/src/util/entities/index.ts index 6dfbd822..ad34f67b 100644 --- a/src/util/entities/index.ts +++ b/src/util/entities/index.ts @@ -27,6 +27,7 @@ export * from "./Channel"; export * from "./ClientRelease"; export * from "./Config"; export * from "./ConnectedAccount"; +export * from "./ConnectionConfigEntity"; export * from "./EmbedCache"; export * from "./Emoji"; export * from "./Encryption"; diff --git a/src/util/interfaces/Event.ts b/src/util/interfaces/Event.ts index c3bfbf9b..6609c9ee 100644 --- a/src/util/interfaces/Event.ts +++ b/src/util/interfaces/Event.ts @@ -420,6 +420,10 @@ export interface UserDeleteEvent extends Event { }; } +export interface UserConnectionsUpdateEvent extends Event { + event: "USER_CONNECTIONS_UPDATE"; +} + export interface VoiceStateUpdateEvent extends Event { event: "VOICE_STATE_UPDATE"; data: VoiceState & { @@ -561,6 +565,7 @@ export type EventData = | TypingStartEvent | UserUpdateEvent | UserDeleteEvent + | UserConnectionsUpdateEvent | VoiceStateUpdateEvent | VoiceServerUpdateEvent | WebhooksUpdateEvent @@ -612,6 +617,7 @@ export enum EVENTEnum { TypingStart = "TYPING_START", UserUpdate = "USER_UPDATE", UserDelete = "USER_DELETE", + UserConnectionsUpdate = "USER_CONNECTIONS_UPDATE", WebhooksUpdate = "WEBHOOKS_UPDATE", InteractionCreate = "INTERACTION_CREATE", VoiceStateUpdate = "VOICE_STATE_UPDATE", @@ -663,6 +669,7 @@ export type EVENT = | "TYPING_START" | "USER_UPDATE" | "USER_DELETE" + | "USER_CONNECTIONS_UPDATE" | "USER_NOTE_UPDATE" | "WEBHOOKS_UPDATE" | "INTERACTION_CREATE" diff --git a/src/util/schemas/ConnectionCallbackSchema.ts b/src/util/schemas/ConnectionCallbackSchema.ts new file mode 100644 index 00000000..09ae8a46 --- /dev/null +++ b/src/util/schemas/ConnectionCallbackSchema.ts @@ -0,0 +1,7 @@ +export interface ConnectionCallbackSchema { + code?: string; + state: string; + insecure: boolean; + friend_sync: boolean; + openid_params?: any; // TODO: types +} diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts index 44909a3a..2831d42a 100644 --- a/src/util/schemas/index.ts +++ b/src/util/schemas/index.ts @@ -30,6 +30,7 @@ export * from "./ChannelModifySchema"; export * from "./ChannelPermissionOverwriteSchema"; export * from "./ChannelReorderSchema"; export * from "./CodesVerificationSchema"; +export * from "./ConnectionCallbackSchema"; export * from "./DmChannelCreateSchema"; export * from "./EmojiCreateSchema"; export * from "./EmojiModifySchema"; |