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/schemas/ConnectedAccountSchema.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/util/schemas/ConnectedAccountSchema.ts (limited to 'src/util/schemas/ConnectedAccountSchema.ts') diff --git a/src/util/schemas/ConnectedAccountSchema.ts b/src/util/schemas/ConnectedAccountSchema.ts new file mode 100644 index 00000000..e00e4fa1 --- /dev/null +++ b/src/util/schemas/ConnectedAccountSchema.ts @@ -0,0 +1,16 @@ +export interface ConnectedAccountSchema { + external_id: string; + user_id: string; + access_token?: string; + friend_sync?: boolean; + name: string; + revoked?: boolean; + show_activity?: boolean; + type: string; + verified?: boolean; + visibility?: number; + integrations?: string[]; + metadata_?: any; + metadata_visibility?: number; + two_way_link?: boolean; +} -- cgit 1.5.1 From 0db1fa5f0b2b9b357c1f96178c0e5df7858a99ab Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Fri, 23 Dec 2022 18:34:36 -0500 Subject: Refreshable connections, refactoring, access-token endpoint - Aded /users/@me/connections/:connection_name/:connection_id/access-token - Replaced `access_token` property on ConnectedAccount with `token_data` object for refreshing tokens - Made a common interface for connection things like ComonOAuthTokenResponse - Added `RefreshableConnection` class - Added token refresh to Spotify connection (disabled) --- .../#connection_id/access-token.ts | 84 ++++++++++++++++++++++ src/api/routes/users/@me/connections/index.ts | 2 +- src/connections/BattleNet/index.ts | 31 ++++---- src/connections/Discord/index.ts | 19 ++--- src/connections/EpicGames/index.ts | 22 +++--- src/connections/Facebook/index.ts | 31 ++++---- src/connections/GitHub/index.ts | 19 ++--- src/connections/Reddit/index.ts | 20 ++---- src/connections/Spotify/index.ts | 79 +++++++++++++++----- src/util/connections/Connection.ts | 25 +++++-- src/util/connections/ConnectionStore.ts | 4 +- src/util/connections/RefreshableConnection.ts | 30 ++++++++ src/util/connections/index.ts | 1 + src/util/dtos/ConnectedAccountDTO.ts | 4 +- src/util/entities/ConnectedAccount.ts | 7 +- src/util/interfaces/ConnectedAccount.ts | 16 +++++ src/util/interfaces/index.ts | 5 +- src/util/schemas/ConnectedAccountSchema.ts | 4 +- 18 files changed, 292 insertions(+), 111 deletions(-) create mode 100644 src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts create mode 100644 src/util/connections/RefreshableConnection.ts create mode 100644 src/util/interfaces/ConnectedAccount.ts (limited to 'src/util/schemas/ConnectedAccountSchema.ts') diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts new file mode 100644 index 00000000..8d51a770 --- /dev/null +++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts @@ -0,0 +1,84 @@ +import { route } from "@fosscord/api"; +import { + ApiError, + ConnectedAccount, + ConnectionStore, + DiscordApiErrors, + FieldErrors, +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import RefreshableConnection from "../../../../../../../util/connections/RefreshableConnection"; +const router = Router(); + +// TODO: this route is only used for spotify, twitch, and youtube. (battlenet seems to be able to PUT, maybe others also) + +// spotify is disabled here because it cant be used +const ALLOWED_CONNECTIONS = ["twitch", "youtube"]; + +router.get("/", route({}), async (req: Request, res: Response) => { + // TODO: get the current access token or refresh it if it's expired + const { connection_name, connection_id } = req.params; + + const connection = ConnectionStore.connections.get(connection_id); + + if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection) + throw FieldErrors({ + provider_id: { + code: "BASE_TYPE_CHOICES", + message: req.t("common:field.BASE_TYPE_CHOICES", { + types: ALLOWED_CONNECTIONS.join(", "), + }), + }, + }); + + if (!connection.settings.enabled) + throw FieldErrors({ + provider_id: { + message: "This connection has been disabled server-side.", + }, + }); + + const connectedAccount = await ConnectedAccount.findOne({ + where: { + type: connection_name, + id: connection_id, + user_id: req.user_id, + }, + select: [ + "external_id", + "type", + "name", + "verified", + "visibility", + "show_activity", + "revoked", + "token_data", + "friend_sync", + "integrations", + ], + }); + if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION; + if (connectedAccount.revoked) + throw new ApiError("Connection revoked", 0, 400); + if (!connectedAccount.token_data) + throw new ApiError("No token data", 0, 400); + + let access_token = connectedAccount.token_data.access_token; + const { expires_at, expires_in } = connectedAccount.token_data; + + if (expires_at && expires_at < Date.now()) { + if (!(connection instanceof RefreshableConnection)) + throw new ApiError("Access token expired", 0, 400); + const tokenData = await connection.refresh(connectedAccount); + access_token = tokenData.access_token; + } else if (expires_in && expires_in < Date.now()) { + if (!(connection instanceof RefreshableConnection)) + throw new ApiError("Access token expired", 0, 400); + const tokenData = await connection.refresh(connectedAccount); + access_token = tokenData.access_token; + } + + res.json({ access_token }); +}); + +export default router; diff --git a/src/api/routes/users/@me/connections/index.ts b/src/api/routes/users/@me/connections/index.ts index a5041be1..8e762f19 100644 --- a/src/api/routes/users/@me/connections/index.ts +++ b/src/api/routes/users/@me/connections/index.ts @@ -35,7 +35,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { "visibility", "show_activity", "revoked", - "access_token", + "token_data", "friend_sync", "integrations", ], diff --git a/src/connections/BattleNet/index.ts b/src/connections/BattleNet/index.ts index f4b317cc..ecba0fa9 100644 --- a/src/connections/BattleNet/index.ts +++ b/src/connections/BattleNet/index.ts @@ -1,6 +1,7 @@ import { Config, ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, ConnectionCallbackSchema, ConnectionLoader, DiscordApiErrors, @@ -9,14 +10,6 @@ import fetch from "node-fetch"; import Connection from "../../util/connections/Connection"; 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; @@ -65,7 +58,10 @@ export default class BattleNetConnection extends Connection { return this.tokenUrl; } - async exchangeCode(state: string, code: string): Promise { + async exchangeCode( + state: string, + code: string, + ): Promise { this.validateState(state); const url = this.getTokenUrl(); @@ -86,10 +82,15 @@ export default class BattleNetConnection extends Connection { }), }) .then((res) => res.json()) - .then((res: OAuthTokenResponse & BattleNetErrorResponse) => { - if (res.error) throw new Error(res.error_description); - return res.access_token; - }) + .then( + ( + res: ConnectedAccountCommonOAuthTokenResponse & + BattleNetErrorResponse, + ) => { + if (res.error) throw new Error(res.error_description); + return res; + }, + ) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, @@ -117,8 +118,8 @@ export default class BattleNetConnection extends Connection { 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 tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); const exists = await this.hasConnection(userId, userInfo.id.toString()); diff --git a/src/connections/Discord/index.ts b/src/connections/Discord/index.ts index 05074c26..61efcfc5 100644 --- a/src/connections/Discord/index.ts +++ b/src/connections/Discord/index.ts @@ -1,6 +1,7 @@ import { Config, ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, ConnectionCallbackSchema, ConnectionLoader, DiscordApiErrors, @@ -9,14 +10,6 @@ import fetch from "node-fetch"; 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; @@ -65,7 +58,10 @@ export default class DiscordConnection extends Connection { return this.tokenUrl; } - async exchangeCode(state: string, code: string): Promise { + async exchangeCode( + state: string, + code: string, + ): Promise { this.validateState(state); const url = this.getTokenUrl(); @@ -86,7 +82,6 @@ export default class DiscordConnection extends Connection { }), }) .then((res) => res.json()) - .then((res: OAuthTokenResponse) => res.access_token) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, @@ -109,8 +104,8 @@ export default class DiscordConnection extends Connection { 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 tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); const exists = await this.hasConnection(userId, userInfo.id); diff --git a/src/connections/EpicGames/index.ts b/src/connections/EpicGames/index.ts index cffafc60..f1f3f24c 100644 --- a/src/connections/EpicGames/index.ts +++ b/src/connections/EpicGames/index.ts @@ -1,6 +1,7 @@ import { Config, ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, ConnectionCallbackSchema, ConnectionLoader, DiscordApiErrors, @@ -9,21 +10,14 @@ import fetch from "node-fetch"; import Connection from "../../util/connections/Connection"; import { EpicGamesSettings } from "./EpicGamesSettings"; -interface OAuthTokenResponse { - access_token: string; - token_type: string; - scope: string; - refresh_token?: string; - expires_in?: number; -} - export interface UserResponse { accountId: string; displayName: string; preferredLanguage: string; } -export interface EpicTokenResponse extends OAuthTokenResponse { +export interface EpicTokenResponse + extends ConnectedAccountCommonOAuthTokenResponse { expires_at: string; refresh_expires_in: number; refresh_expires_at: string; @@ -70,7 +64,10 @@ export default class EpicGamesConnection extends Connection { return this.tokenUrl; } - async exchangeCode(state: string, code: string): Promise { + async exchangeCode( + state: string, + code: string, + ): Promise { this.validateState(state); const url = this.getTokenUrl(); @@ -90,7 +87,6 @@ export default class EpicGamesConnection extends Connection { }), }) .then((res) => res.json()) - .then((res: EpicTokenResponse) => res.access_token) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, @@ -117,8 +113,8 @@ export default class EpicGamesConnection extends Connection { 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 tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); const exists = await this.hasConnection(userId, userInfo[0].accountId); diff --git a/src/connections/Facebook/index.ts b/src/connections/Facebook/index.ts index 80950a8e..2d490c63 100644 --- a/src/connections/Facebook/index.ts +++ b/src/connections/Facebook/index.ts @@ -1,6 +1,7 @@ import { Config, ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, ConnectionCallbackSchema, ConnectionLoader, DiscordApiErrors, @@ -9,14 +10,6 @@ import fetch from "node-fetch"; import Connection from "../../util/connections/Connection"; import { FacebookSettings } from "./FacebookSettings"; -interface OAuthTokenResponse { - access_token: string; - token_type: string; - scope: string; - refresh_token?: string; - expires_in?: number; -} - export interface FacebookErrorResponse { error: { message: string; @@ -81,7 +74,10 @@ export default class FacebookConnection extends Connection { return url.toString(); } - async exchangeCode(state: string, code: string): Promise { + async exchangeCode( + state: string, + code: string, + ): Promise { this.validateState(state); const url = this.getTokenUrl(code); @@ -93,10 +89,15 @@ export default class FacebookConnection extends Connection { }, }) .then((res) => res.json()) - .then((res: OAuthTokenResponse & FacebookErrorResponse) => { - if (res.error) throw new Error(res.error.message); - return res.access_token; - }) + .then( + ( + res: ConnectedAccountCommonOAuthTokenResponse & + FacebookErrorResponse, + ) => { + if (res.error) throw new Error(res.error.message); + return res; + }, + ) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, @@ -124,8 +125,8 @@ export default class FacebookConnection extends Connection { 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 tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); const exists = await this.hasConnection(userId, userInfo.id); diff --git a/src/connections/GitHub/index.ts b/src/connections/GitHub/index.ts index d79fd51e..ab3f8e65 100644 --- a/src/connections/GitHub/index.ts +++ b/src/connections/GitHub/index.ts @@ -1,6 +1,7 @@ import { Config, ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, ConnectionCallbackSchema, ConnectionLoader, DiscordApiErrors, @@ -9,14 +10,6 @@ import fetch from "node-fetch"; import Connection from "../../util/connections/Connection"; 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; @@ -63,7 +56,10 @@ export default class GitHubConnection extends Connection { return url.toString(); } - async exchangeCode(state: string, code: string): Promise { + async exchangeCode( + state: string, + code: string, + ): Promise { this.validateState(state); const url = this.getTokenUrl(code); @@ -75,7 +71,6 @@ export default class GitHubConnection extends Connection { }, }) .then((res) => res.json()) - .then((res: OAuthTokenResponse) => res.access_token) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, @@ -98,8 +93,8 @@ export default class GitHubConnection extends Connection { 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 tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); const exists = await this.hasConnection(userId, userInfo.id.toString()); diff --git a/src/connections/Reddit/index.ts b/src/connections/Reddit/index.ts index c529cfa3..182cd5a5 100644 --- a/src/connections/Reddit/index.ts +++ b/src/connections/Reddit/index.ts @@ -1,6 +1,7 @@ import { Config, ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, ConnectionCallbackSchema, ConnectionLoader, DiscordApiErrors, @@ -9,14 +10,6 @@ import fetch from "node-fetch"; import Connection from "../../util/connections/Connection"; import { RedditSettings } from "./RedditSettings"; -interface OAuthTokenResponse { - access_token: string; - token_type: string; - scope: string; - refresh_token?: string; - expires_in?: number; -} - export interface UserResponse { verified: boolean; coins: number; @@ -72,7 +65,10 @@ export default class RedditConnection extends Connection { return this.tokenUrl; } - async exchangeCode(state: string, code: string): Promise { + async exchangeCode( + state: string, + code: string, + ): Promise { this.validateState(state); const url = this.getTokenUrl(); @@ -95,7 +91,6 @@ export default class RedditConnection extends Connection { }), }) .then((res) => res.json()) - .then((res: OAuthTokenResponse) => res.access_token) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, @@ -118,8 +113,8 @@ export default class RedditConnection extends Connection { 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 tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); const exists = await this.hasConnection(userId, userInfo.id.toString()); @@ -128,7 +123,6 @@ export default class RedditConnection extends Connection { // TODO: connection metadata return await this.createConnection({ - access_token: token, user_id: userId, external_id: userInfo.id.toString(), friend_sync: params.friend_sync, diff --git a/src/connections/Spotify/index.ts b/src/connections/Spotify/index.ts index eb662141..b40d6189 100644 --- a/src/connections/Spotify/index.ts +++ b/src/connections/Spotify/index.ts @@ -1,22 +1,15 @@ import { Config, ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, ConnectionCallbackSchema, ConnectionLoader, DiscordApiErrors, } from "@fosscord/util"; import fetch from "node-fetch"; -import Connection from "../../util/connections/Connection"; +import RefreshableConnection from "../../util/connections/RefreshableConnection"; import { SpotifySettings } from "./SpotifySettings"; -interface OAuthTokenResponse { - access_token: string; - token_type: string; - scope: string; - refresh_token?: string; - expires_in?: number; -} - export interface UserResponse { display_name: string; id: string; @@ -34,7 +27,7 @@ export interface ErrorResponse { }; } -export default class SpotifyConnection extends Connection { +export default class SpotifyConnection extends RefreshableConnection { public readonly id = "spotify"; public readonly authorizeUrl = "https://accounts.spotify.com/authorize"; public readonly tokenUrl = "https://accounts.spotify.com/api/token"; @@ -48,6 +41,11 @@ export default class SpotifyConnection extends Connection { settings: SpotifySettings = new SpotifySettings(); init(): void { + /** + * The way Discord shows the currently playing song is by using Spotifys partner API. This is obviously not possible for us. + * So to prevent spamming the spotify api we disable the ability to refresh. + */ + this.refreshEnabled = false; this.settings = ConnectionLoader.getConnectionConfig( this.id, this.settings, @@ -76,7 +74,10 @@ export default class SpotifyConnection extends Connection { return this.tokenUrl; } - async exchangeCode(state: string, code: string): Promise { + async exchangeCode( + state: string, + code: string, + ): Promise { this.validateState(state); const url = this.getTokenUrl(); @@ -99,10 +100,15 @@ export default class SpotifyConnection extends Connection { }), }) .then((res) => res.json()) - .then((res: OAuthTokenResponse & TokenErrorResponse) => { - if (res.error) throw new Error(res.error_description); - return res.access_token; - }) + .then( + ( + res: ConnectedAccountCommonOAuthTokenResponse & + TokenErrorResponse, + ) => { + if (res.error) throw new Error(res.error_description); + return res; + }, + ) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, @@ -111,6 +117,44 @@ export default class SpotifyConnection extends Connection { }); } + async refreshToken(connectedAccount: ConnectedAccount) { + if (!connectedAccount.token_data?.refresh_token) + throw new Error("No refresh token available."); + const refresh_token = connectedAccount.token_data.refresh_token; + const url = this.getTokenUrl(); + + return fetch(url.toString(), { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${Buffer.from( + `${this.settings.clientId!}:${this.settings.clientSecret!}`, + ).toString("base64")}`, + }, + body: new URLSearchParams({ + grant_type: "refresh_token", + refresh_token, + }), + }) + .then((res) => res.json()) + .then( + ( + res: ConnectedAccountCommonOAuthTokenResponse & + TokenErrorResponse, + ) => { + if (res.error) throw new Error(res.error_description); + return res; + }, + ) + .catch((e) => { + console.error( + `Error refreshing 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(), { @@ -130,14 +174,15 @@ export default class SpotifyConnection extends Connection { 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 tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); const exists = await this.hasConnection(userId, userInfo.id); if (exists) return null; return await this.createConnection({ + token_data: tokenData, user_id: userId, external_id: userInfo.id, friend_sync: params.friend_sync, diff --git a/src/util/connections/Connection.ts b/src/util/connections/Connection.ts index 164cfac7..8b60b0d2 100644 --- a/src/util/connections/Connection.ts +++ b/src/util/connections/Connection.ts @@ -1,9 +1,11 @@ import crypto from "crypto"; import { ConnectedAccount } from "../entities"; -import { OrmUtils } from "../imports"; import { ConnectedAccountSchema, ConnectionCallbackSchema } from "../schemas"; import { DiscordApiErrors } from "../util"; +/** + * A connection that can be used to connect to an external service. + */ export default abstract class Connection { id: string; settings: { enabled: boolean }; @@ -21,7 +23,9 @@ export default abstract class Connection { * Processes the callback * @param args Callback arguments */ - abstract handleCallback(params: ConnectionCallbackSchema): Promise; + abstract handleCallback( + params: ConnectionCallbackSchema, + ): Promise; /** * Gets a user id from state @@ -54,12 +58,25 @@ export default abstract class Connection { this.states.delete(state); } - async createConnection(data: ConnectedAccountSchema): Promise { - const ca = OrmUtils.mergeDeep(new ConnectedAccount(), data) as ConnectedAccount; + /** + * Creates a Connected Account in the database. + * @param data connected account data + * @returns the new connected account + */ + async createConnection( + data: ConnectedAccountSchema, + ): Promise { + const ca = ConnectedAccount.create({ ...data }); await ca.save(); return ca; } + /** + * Checks if a user has an exist connected account for the given extenal id. + * @param userId the user id + * @param externalId the connection id to find + * @returns + */ async hasConnection(userId: string, externalId: string): Promise { const existing = await ConnectedAccount.findOne({ where: { diff --git a/src/util/connections/ConnectionStore.ts b/src/util/connections/ConnectionStore.ts index 406e8232..759b6de7 100644 --- a/src/util/connections/ConnectionStore.ts +++ b/src/util/connections/ConnectionStore.ts @@ -1,5 +1,7 @@ import Connection from "./Connection"; +import RefreshableConnection from "./RefreshableConnection"; export class ConnectionStore { - public static connections: Map = new Map(); + public static connections: Map = + new Map(); } diff --git a/src/util/connections/RefreshableConnection.ts b/src/util/connections/RefreshableConnection.ts new file mode 100644 index 00000000..0008cbc0 --- /dev/null +++ b/src/util/connections/RefreshableConnection.ts @@ -0,0 +1,30 @@ +import { ConnectedAccount } from "../entities"; +import { ConnectedAccountCommonOAuthTokenResponse } from "../interfaces"; +import Connection from "./Connection"; + +/** + * A connection that can refresh its token. + */ +export default abstract class RefreshableConnection extends Connection { + refreshEnabled = true; + /** + * Refreshes the token for a connected account. + * @param connectedAccount The connected account to refresh + */ + abstract refreshToken( + connectedAccount: ConnectedAccount, + ): Promise; + + /** + * Refreshes the token for a connected account and saves it to the database. + * @param connectedAccount The connected account to refresh + */ + async refresh( + connectedAccount: ConnectedAccount, + ): Promise { + const tokenData = await this.refreshToken(connectedAccount); + connectedAccount.token_data = tokenData; + await connectedAccount.save(); + return tokenData; + } +} diff --git a/src/util/connections/index.ts b/src/util/connections/index.ts index e15d0c8c..8d20bf27 100644 --- a/src/util/connections/index.ts +++ b/src/util/connections/index.ts @@ -2,3 +2,4 @@ export * from "./Connection"; export * from "./ConnectionConfig"; export * from "./ConnectionLoader"; export * from "./ConnectionStore"; +export * from "./RefreshableConnection"; diff --git a/src/util/dtos/ConnectedAccountDTO.ts b/src/util/dtos/ConnectedAccountDTO.ts index debc5535..ca15ff41 100644 --- a/src/util/dtos/ConnectedAccountDTO.ts +++ b/src/util/dtos/ConnectedAccountDTO.ts @@ -23,8 +23,8 @@ export class ConnectedAccountDTO { this.id = connectedAccount.external_id; this.user_id = connectedAccount.user_id; this.access_token = - connectedAccount.access_token && with_token - ? connectedAccount.access_token + connectedAccount.token_data && with_token + ? connectedAccount.token_data.access_token : undefined; this.friend_sync = connectedAccount.friend_sync; this.name = connectedAccount.name; diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts index 25d5a0c7..beb53e41 100644 --- a/src/util/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts @@ -17,6 +17,7 @@ */ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { ConnectedAccountTokenData } from "../interfaces"; import { BaseClass } from "./BaseClass"; import { User } from "./User"; @@ -40,9 +41,6 @@ export class ConnectedAccount extends BaseClass { }) user: User; - @Column({ select: false, nullable: true }) - access_token?: string; - @Column({ select: false }) friend_sync?: boolean = false; @@ -75,4 +73,7 @@ export class ConnectedAccount extends BaseClass { @Column() two_way_link?: boolean = false; + + @Column({ select: false, nullable: true, type: "simple-json" }) + token_data?: ConnectedAccountTokenData; } diff --git a/src/util/interfaces/ConnectedAccount.ts b/src/util/interfaces/ConnectedAccount.ts new file mode 100644 index 00000000..c96e5f79 --- /dev/null +++ b/src/util/interfaces/ConnectedAccount.ts @@ -0,0 +1,16 @@ +export interface ConnectedAccountCommonOAuthTokenResponse { + access_token: string; + token_type: string; + scope: string; + refresh_token?: string; + expires_in?: number; +} + +export interface ConnectedAccountTokenData { + access_token: string; + token_type?: string; + scope?: string; + refresh_token?: string; + expires_in?: number; + expires_at?: number; +} diff --git a/src/util/interfaces/index.ts b/src/util/interfaces/index.ts index fa259ce1..e194d174 100644 --- a/src/util/interfaces/index.ts +++ b/src/util/interfaces/index.ts @@ -17,7 +17,8 @@ */ export * from "./Activity"; -export * from "./Presence"; -export * from "./Interaction"; +export * from "./ConnectedAccount"; export * from "./Event"; +export * from "./Interaction"; +export * from "./Presence"; export * from "./Status"; diff --git a/src/util/schemas/ConnectedAccountSchema.ts b/src/util/schemas/ConnectedAccountSchema.ts index e00e4fa1..e5f838d0 100644 --- a/src/util/schemas/ConnectedAccountSchema.ts +++ b/src/util/schemas/ConnectedAccountSchema.ts @@ -1,7 +1,9 @@ +import { ConnectedAccountTokenData } from "../interfaces"; + export interface ConnectedAccountSchema { external_id: string; user_id: string; - access_token?: string; + token_data?: ConnectedAccountTokenData; friend_sync?: boolean; name: string; revoked?: boolean; -- cgit 1.5.1 From 6d6944cfee4af656c6386c7a44efc6b99bdfd6ed Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Sat, 24 Dec 2022 16:24:58 -0500 Subject: Add Twitch, error handling, revokation changes, etc --- assets/schemas.json | 1865 +++++++++++++++++++- .../#connection_id/access-token.ts | 16 +- .../#connection_name/#connection_id/index.ts | 13 +- src/connections/BattleNet/index.ts | 25 +- src/connections/Discord/index.ts | 26 +- src/connections/EpicGames/index.ts | 26 +- src/connections/Facebook/index.ts | 25 +- src/connections/GitHub/index.ts | 28 +- src/connections/Reddit/index.ts | 28 +- src/connections/Spotify/index.ts | 51 +- src/connections/Twitch/TwitchSettings.ts | 5 + src/connections/Twitch/index.ts | 196 ++ src/util/connections/RefreshableConnection.ts | 2 +- src/util/dtos/ConnectedAccountDTO.ts | 2 +- src/util/entities/ConnectedAccount.ts | 10 +- src/util/interfaces/ConnectedAccount.ts | 1 + src/util/schemas/ConnectedAccountSchema.ts | 2 +- src/util/schemas/ConnectionUpdateSchema.ts | 1 + src/util/util/Constants.ts | 5 + 19 files changed, 2278 insertions(+), 49 deletions(-) create mode 100644 src/connections/Twitch/TwitchSettings.ts create mode 100644 src/connections/Twitch/index.ts (limited to 'src/util/schemas/ConnectedAccountSchema.ts') diff --git a/assets/schemas.json b/assets/schemas.json index fe4fcea3..5f897c86 100644 --- a/assets/schemas.json +++ b/assets/schemas.json @@ -36,6 +36,33 @@ ], "$schema": "http://json-schema.org/draft-07/schema#" }, + "ConnectedAccountCommonOAuthTokenResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "scope", + "token_type" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, "SMTPConnection.CustomAuthenticationResponse": { "type": "object", "properties": { @@ -419,6 +446,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -1021,6 +1079,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -1623,6 +1712,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -2220,6 +2340,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -2799,8 +2950,8 @@ "user_id": { "type": "string" }, - "access_token": { - "type": "string" + "token_data": { + "$ref": "#/definitions/ConnectedAccountTokenData" }, "friend_sync": { "type": "boolean" @@ -2812,7 +2963,7 @@ "type": "boolean" }, "show_activity": { - "type": "boolean" + "type": "integer" }, "type": { "type": "string" @@ -2853,6 +3004,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -3455,6 +3637,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -4030,6 +4243,9 @@ "properties": { "visibility": { "type": "boolean" + }, + "show_activity": { + "type": "boolean" } }, "additionalProperties": false, @@ -4042,6 +4258,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -4638,6 +4885,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -5243,6 +5521,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -5836,6 +6145,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -6429,6 +6769,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -7041,6 +7412,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -7637,6 +8039,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -8293,6 +8726,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -8908,6 +9372,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -9663,6 +10158,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -10274,6 +10800,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -10889,6 +11446,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -11495,6 +12083,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -12107,6 +12726,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -12709,6 +13359,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -13299,6 +13980,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -14000,6 +14712,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -14698,6 +15441,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -15291,6 +16065,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -15892,6 +16697,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -16486,6 +17322,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -17080,6 +17947,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -17703,6 +18601,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -18297,6 +19226,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -18890,6 +19850,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -19498,6 +20489,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -20095,6 +21117,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -20766,6 +21819,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -21359,6 +22443,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -21952,6 +23067,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -22542,6 +23688,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -23138,6 +24315,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -23744,6 +24952,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -24334,6 +25573,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -24973,6 +26243,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -25598,6 +26899,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -26213,6 +27545,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -26917,6 +28280,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -27506,6 +28900,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -28134,6 +29559,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -28747,6 +30203,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -29415,6 +30902,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -30005,6 +31523,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -30603,6 +32152,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -31191,6 +32771,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -31785,6 +33396,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -32379,6 +34021,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -32973,6 +34646,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -33554,6 +35258,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -34147,6 +35882,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -34737,6 +36503,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -35327,6 +37124,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { @@ -35923,6 +37751,37 @@ ], "type": "number" }, + "ConnectedAccountTokenData": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "token_type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "expires_at": { + "type": "integer" + }, + "fetched_at": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "access_token", + "fetched_at" + ] + }, "ChannelModifySchema": { "type": "object", "properties": { diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts index 760f8135..1ad1c7a7 100644 --- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts +++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts @@ -19,7 +19,7 @@ const ALLOWED_CONNECTIONS = ["twitch", "youtube"]; router.get("/", route({}), async (req: Request, res: Response) => { const { connection_name, connection_id } = req.params; - const connection = ConnectionStore.connections.get(connection_id); + const connection = ConnectionStore.connections.get(connection_name); if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection) throw FieldErrors({ @@ -41,7 +41,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { const connectedAccount = await ConnectedAccount.findOne({ where: { type: connection_name, - id: connection_id, + external_id: connection_id, user_id: req.user_id, }, select: [ @@ -64,14 +64,12 @@ router.get("/", route({}), async (req: Request, res: Response) => { throw new ApiError("No token data", 0, 400); let access_token = connectedAccount.token_data.access_token; - const { expires_at, expires_in } = connectedAccount.token_data; + const { expires_at, expires_in, fetched_at } = connectedAccount.token_data; - if (expires_at && expires_at < Date.now()) { - if (!(connection instanceof RefreshableConnection)) - throw new ApiError("Access token expired", 0, 400); - const tokenData = await connection.refresh(connectedAccount); - access_token = tokenData.access_token; - } else if (expires_in && expires_in < Date.now()) { + if ( + (expires_at && expires_at < Date.now()) || + (expires_in && fetched_at + expires_in * 1000 < Date.now()) + ) { if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400); const tokenData = await connection.refresh(connectedAccount); diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts index 9d5f517d..07440eac 100644 --- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts +++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts @@ -1,5 +1,10 @@ import { route } from "@fosscord/api"; -import { ConnectedAccount, DiscordApiErrors, emitEvent, ConnectionUpdateSchema } from "@fosscord/util"; +import { + ConnectedAccount, + ConnectionUpdateSchema, + DiscordApiErrors, + emitEvent +} from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); @@ -35,6 +40,8 @@ router.patch( //@ts-ignore For some reason the client sends this as a boolean, even tho docs say its a number? if (typeof body.visibility === "boolean") body.visibility = body.visibility ? 1 : 0; + //@ts-ignore For some reason the client sends this as a boolean, even tho docs say its a number? + if (typeof body.show_activity === "boolean") body.show_activity = body.show_activity ? 1 : 0; connection.assign(req.body); @@ -58,7 +65,7 @@ router.delete("/", route({}), async (req: Request, res: Response) => { user_id: req.user_id, external_id: connection_id, type: connection_name, - } + }, }); await Promise.all([ @@ -67,7 +74,7 @@ router.delete("/", route({}), async (req: Request, res: Response) => { event: "USER_CONNECTIONS_UPDATE", data: account, user_id: req.user_id, - }) + }), ]); return res.sendStatus(200); diff --git a/src/connections/BattleNet/index.ts b/src/connections/BattleNet/index.ts index ecba0fa9..8e8eeeed 100644 --- a/src/connections/BattleNet/index.ts +++ b/src/connections/BattleNet/index.ts @@ -1,4 +1,5 @@ import { + ApiError, Config, ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, @@ -81,7 +82,13 @@ export default class BattleNetConnection extends Connection { }/connections/${this.id}/callback`, }), }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to exchange code", 0, 400); + } + + return res.json(); + }) .then( ( res: ConnectedAccountCommonOAuthTokenResponse & @@ -95,7 +102,7 @@ export default class BattleNetConnection extends Connection { console.error( `Error exchanging token for ${this.id} connection: ${e}`, ); - throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -107,10 +114,22 @@ export default class BattleNetConnection extends Connection { Authorization: `Bearer ${token}`, }, }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to fetch user", 0, 400); + } + + return res.json(); + }) .then((res: BattleNetConnectionUser & BattleNetErrorResponse) => { if (res.error) throw new Error(res.error_description); return res; + }) + .catch((e) => { + console.error( + `Error fetching user for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; }); } diff --git a/src/connections/Discord/index.ts b/src/connections/Discord/index.ts index 61efcfc5..23f5d978 100644 --- a/src/connections/Discord/index.ts +++ b/src/connections/Discord/index.ts @@ -1,4 +1,5 @@ import { + ApiError, Config, ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, @@ -81,12 +82,18 @@ export default class DiscordConnection extends Connection { }/connections/${this.id}/callback`, }), }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to exchange token", 0, 400); + } + + return res.json(); + }) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, ); - throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -97,7 +104,20 @@ export default class DiscordConnection extends Connection { headers: { Authorization: `Bearer ${token}`, }, - }).then((res) => res.json()); + }) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to fetch user", 0, 400); + } + + return res.json(); + }) + .catch((e) => { + console.error( + `Error fetching user for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; + }); } async handleCallback( diff --git a/src/connections/EpicGames/index.ts b/src/connections/EpicGames/index.ts index f1f3f24c..c720dc5d 100644 --- a/src/connections/EpicGames/index.ts +++ b/src/connections/EpicGames/index.ts @@ -1,4 +1,5 @@ import { + ApiError, Config, ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, @@ -86,12 +87,18 @@ export default class EpicGamesConnection extends Connection { code, }), }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to exchange code", 0, 400); + } + + return res.json(); + }) .catch((e) => { console.error( `Error exchanging token for ${this.id} connection: ${e}`, ); - throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -106,7 +113,20 @@ export default class EpicGamesConnection extends Connection { headers: { Authorization: `Bearer ${token}`, }, - }).then((res) => res.json()); + }) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to fetch user", 0, 400); + } + + return res.json(); + }) + .catch((e) => { + console.error( + `Error fetching user for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; + }); } async handleCallback( diff --git a/src/connections/Facebook/index.ts b/src/connections/Facebook/index.ts index 2d490c63..67f8da79 100644 --- a/src/connections/Facebook/index.ts +++ b/src/connections/Facebook/index.ts @@ -1,4 +1,5 @@ import { + ApiError, Config, ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, @@ -88,7 +89,13 @@ export default class FacebookConnection extends Connection { Accept: "application/json", }, }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to exchange code", 0, 400); + } + + return res.json(); + }) .then( ( res: ConnectedAccountCommonOAuthTokenResponse & @@ -102,7 +109,7 @@ export default class FacebookConnection extends Connection { console.error( `Error exchanging token for ${this.id} connection: ${e}`, ); - throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -114,10 +121,22 @@ export default class FacebookConnection extends Connection { Authorization: `Bearer ${token}`, }, }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to fetch user", 0, 400); + } + + return res.json(); + }) .then((res: UserResponse & FacebookErrorResponse) => { if (res.error) throw new Error(res.error.message); return res; + }) + .catch((e) => { + console.error( + `Error fetching user for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; }); } diff --git a/src/connections/GitHub/index.ts b/src/connections/GitHub/index.ts index ab3f8e65..aa686b03 100644 --- a/src/connections/GitHub/index.ts +++ b/src/connections/GitHub/index.ts @@ -1,4 +1,5 @@ import { + ApiError, Config, ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, @@ -70,12 +71,18 @@ export default class GitHubConnection extends Connection { Accept: "application/json", }, }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to exchange code", 0, 400); + } + + return res.json(); + }) .catch((e) => { console.error( - `Error exchanging token for ${this.id} connection: ${e}`, + `Error exchanging code for ${this.id} connection: ${e}`, ); - throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -86,7 +93,20 @@ export default class GitHubConnection extends Connection { headers: { Authorization: `Bearer ${token}`, }, - }).then((res) => res.json()); + }) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to fetch user", 0, 400); + } + + return res.json(); + }) + .catch((e) => { + console.error( + `Error fetching user for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; + }); } async handleCallback( diff --git a/src/connections/Reddit/index.ts b/src/connections/Reddit/index.ts index 182cd5a5..06fbcbe5 100644 --- a/src/connections/Reddit/index.ts +++ b/src/connections/Reddit/index.ts @@ -1,4 +1,5 @@ import { + ApiError, Config, ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, @@ -90,12 +91,18 @@ export default class RedditConnection extends Connection { }/connections/${this.id}/callback`, }), }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to code", 0, 400); + } + + return res.json(); + }) .catch((e) => { console.error( - `Error exchanging token for ${this.id} connection: ${e}`, + `Error exchanging code for ${this.id} connection: ${e}`, ); - throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -106,7 +113,20 @@ export default class RedditConnection extends Connection { headers: { Authorization: `Bearer ${token}`, }, - }).then((res) => res.json()); + }) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to fetch user", 0, 400); + } + + return res.json(); + }) + .catch((e) => { + console.error( + `Error fetching user for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; + }); } async handleCallback( diff --git a/src/connections/Spotify/index.ts b/src/connections/Spotify/index.ts index b40d6189..44a4bc28 100644 --- a/src/connections/Spotify/index.ts +++ b/src/connections/Spotify/index.ts @@ -1,4 +1,5 @@ import { + ApiError, Config, ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, @@ -99,21 +100,28 @@ export default class SpotifyConnection extends RefreshableConnection { }/connections/${this.id}/callback`, }), }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to refresh token", 0, 400); + } + + return res.json(); + }) .then( ( res: ConnectedAccountCommonOAuthTokenResponse & TokenErrorResponse, ) => { - if (res.error) throw new Error(res.error_description); + if (res.error) + throw new ApiError(res.error_description, 0, 400); return res; }, ) .catch((e) => { console.error( - `Error exchanging token for ${this.id} connection: ${e}`, + `Error exchanging code for ${this.id} connection: ${e}`, ); - throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -137,13 +145,26 @@ export default class SpotifyConnection extends RefreshableConnection { refresh_token, }), }) - .then((res) => res.json()) + .then(async (res) => { + if ([400, 401].includes(res.status)) { + // assume the token was revoked + await connectedAccount.revoke(); + return DiscordApiErrors.CONNECTION_REVOKED; + } + // otherwise throw a general error + if (!res.ok) { + throw new ApiError("Failed to refresh token", 0, 400); + } + + return await res.json(); + }) .then( ( res: ConnectedAccountCommonOAuthTokenResponse & TokenErrorResponse, ) => { - if (res.error) throw new Error(res.error_description); + if (res.error) + throw new ApiError(res.error_description, 0, 400); return res; }, ) @@ -151,7 +172,7 @@ export default class SpotifyConnection extends RefreshableConnection { console.error( `Error refreshing token for ${this.id} connection: ${e}`, ); - throw DiscordApiErrors.INVALID_OAUTH_TOKEN; + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -163,10 +184,22 @@ export default class SpotifyConnection extends RefreshableConnection { Authorization: `Bearer ${token}`, }, }) - .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to fetch user", 0, 400); + } + + return res.json(); + }) .then((res: UserResponse & ErrorResponse) => { if (res.error) throw new Error(res.error.message); return res; + }) + .catch((e) => { + console.error( + `Error fetching user for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; }); } @@ -182,7 +215,7 @@ export default class SpotifyConnection extends RefreshableConnection { if (exists) return null; return await this.createConnection({ - token_data: tokenData, + token_data: { ...tokenData, fetched_at: Date.now() }, user_id: userId, external_id: userInfo.id, friend_sync: params.friend_sync, diff --git a/src/connections/Twitch/TwitchSettings.ts b/src/connections/Twitch/TwitchSettings.ts new file mode 100644 index 00000000..eb732c82 --- /dev/null +++ b/src/connections/Twitch/TwitchSettings.ts @@ -0,0 +1,5 @@ +export class TwitchSettings { + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; +} diff --git a/src/connections/Twitch/index.ts b/src/connections/Twitch/index.ts new file mode 100644 index 00000000..ce04f098 --- /dev/null +++ b/src/connections/Twitch/index.ts @@ -0,0 +1,196 @@ +import { + ApiError, + Config, + ConnectedAccount, + ConnectedAccountCommonOAuthTokenResponse, + ConnectionCallbackSchema, + ConnectionLoader, + DiscordApiErrors, +} from "@fosscord/util"; +import fetch from "node-fetch"; +import RefreshableConnection from "../../util/connections/RefreshableConnection"; +import { TwitchSettings } from "./TwitchSettings"; + +interface TwitchConnectionUserResponse { + data: { + id: string; + login: string; + display_name: string; + type: string; + broadcaster_type: string; + description: string; + profile_image_url: string; + offline_image_url: string; + view_count: number; + created_at: string; + }[]; +} + +export default class TwitchConnection extends RefreshableConnection { + public readonly id = "twitch"; + public readonly authorizeUrl = "https://id.twitch.tv/oauth2/authorize"; + public readonly tokenUrl = "https://id.twitch.tv/oauth2/token"; + public readonly userInfoUrl = "https://api.twitch.tv/helix/users"; + public readonly scopes = [ + "channel_subscriptions", + "channel_check_subscription", + "channel:read:subscriptions", + ]; + settings: TwitchSettings = new TwitchSettings(); + + init(): void { + this.settings = ConnectionLoader.getConnectionConfig( + this.id, + this.settings, + ) as TwitchSettings; + } + + 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("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + return url.toString(); + } + + getTokenUrl(): string { + return this.tokenUrl; + } + + async exchangeCode( + state: string, + code: string, + ): Promise { + this.validateState(state); + + const url = this.getTokenUrl(); + + return fetch(url.toString(), { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + }, + 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) => { + if (!res.ok) { + throw new ApiError("Failed to exchange code", 0, 400); + } + + return res.json(); + }) + .catch((e) => { + console.error( + `Error exchanging code for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } + + async refreshToken( + connectedAccount: ConnectedAccount, + ): Promise { + if (!connectedAccount.token_data?.refresh_token) + throw new Error("No refresh token available."); + const refresh_token = connectedAccount.token_data.refresh_token; + + const url = this.getTokenUrl(); + + return fetch(url.toString(), { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + grant_type: "refresh_token", + client_id: this.settings.clientId!, + client_secret: this.settings.clientSecret!, + refresh_token: refresh_token, + }), + }) + .then(async (res) => { + if ([400, 401].includes(res.status)) { + // assume the token was revoked + await connectedAccount.revoke(); + return DiscordApiErrors.CONNECTION_REVOKED; + } + // otherwise throw a general error + if (!res.ok) { + throw new ApiError("Failed to refresh token", 0, 400); + } + + return await res.json(); + }) + .catch((e) => { + console.error( + `Error refreshing token for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } + + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); + return fetch(url.toString(), { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + "Client-Id": this.settings.clientId!, + }, + }) + .then((res) => { + if (!res.ok) { + throw new ApiError("Failed to fetch user", 0, 400); + } + + return res.json(); + }) + .catch((e) => { + console.error( + `Error fetching user for ${this.id} connection: ${e}`, + ); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } + + async handleCallback( + params: ConnectionCallbackSchema, + ): Promise { + const userId = this.getUserId(params.state); + const tokenData = await this.exchangeCode(params.state, params.code!); + const userInfo = await this.getUser(tokenData.access_token); + + const exists = await this.hasConnection(userId, userInfo.data[0].id); + + if (exists) return null; + + return await this.createConnection({ + token_data: { ...tokenData, fetched_at: Date.now() }, + user_id: userId, + external_id: userInfo.data[0].id, + friend_sync: params.friend_sync, + name: userInfo.data[0].display_name, + type: this.id, + }); + } +} diff --git a/src/util/connections/RefreshableConnection.ts b/src/util/connections/RefreshableConnection.ts index 0008cbc0..87f5f6dd 100644 --- a/src/util/connections/RefreshableConnection.ts +++ b/src/util/connections/RefreshableConnection.ts @@ -23,7 +23,7 @@ export default abstract class RefreshableConnection extends Connection { connectedAccount: ConnectedAccount, ): Promise { const tokenData = await this.refreshToken(connectedAccount); - connectedAccount.token_data = tokenData; + connectedAccount.token_data = { ...tokenData, fetched_at: Date.now() }; await connectedAccount.save(); return tokenData; } diff --git a/src/util/dtos/ConnectedAccountDTO.ts b/src/util/dtos/ConnectedAccountDTO.ts index ca15ff41..a3618fd1 100644 --- a/src/util/dtos/ConnectedAccountDTO.ts +++ b/src/util/dtos/ConnectedAccountDTO.ts @@ -7,7 +7,7 @@ export class ConnectedAccountDTO { friend_sync?: boolean; name: string; revoked?: boolean; - show_activity?: boolean; + show_activity?: number; type: string; verified?: boolean; visibility?: number; diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts index beb53e41..d8a9de20 100644 --- a/src/util/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts @@ -51,7 +51,7 @@ export class ConnectedAccount extends BaseClass { revoked?: boolean = false; @Column({ select: false }) - show_activity?: boolean = true; + show_activity?: number = 0; @Column() type: string; @@ -75,5 +75,11 @@ export class ConnectedAccount extends BaseClass { two_way_link?: boolean = false; @Column({ select: false, nullable: true, type: "simple-json" }) - token_data?: ConnectedAccountTokenData; + token_data?: ConnectedAccountTokenData | null; + + async revoke() { + this.revoked = true; + this.token_data = null; + await this.save(); + } } diff --git a/src/util/interfaces/ConnectedAccount.ts b/src/util/interfaces/ConnectedAccount.ts index c96e5f79..ede02f6d 100644 --- a/src/util/interfaces/ConnectedAccount.ts +++ b/src/util/interfaces/ConnectedAccount.ts @@ -13,4 +13,5 @@ export interface ConnectedAccountTokenData { refresh_token?: string; expires_in?: number; expires_at?: number; + fetched_at: number; } diff --git a/src/util/schemas/ConnectedAccountSchema.ts b/src/util/schemas/ConnectedAccountSchema.ts index e5f838d0..fa834bd6 100644 --- a/src/util/schemas/ConnectedAccountSchema.ts +++ b/src/util/schemas/ConnectedAccountSchema.ts @@ -7,7 +7,7 @@ export interface ConnectedAccountSchema { friend_sync?: boolean; name: string; revoked?: boolean; - show_activity?: boolean; + show_activity?: number; type: string; verified?: boolean; visibility?: number; diff --git a/src/util/schemas/ConnectionUpdateSchema.ts b/src/util/schemas/ConnectionUpdateSchema.ts index ac234e7e..eb6c0916 100644 --- a/src/util/schemas/ConnectionUpdateSchema.ts +++ b/src/util/schemas/ConnectionUpdateSchema.ts @@ -1,3 +1,4 @@ export interface ConnectionUpdateSchema { visibility?: boolean; + show_activity?: boolean; } diff --git a/src/util/util/Constants.ts b/src/util/util/Constants.ts index 3bdfcfa9..47f650f4 100644 --- a/src/util/util/Constants.ts +++ b/src/util/util/Constants.ts @@ -787,6 +787,11 @@ export const DiscordApiErrors = { 40006, ), USER_BANNED: new ApiError("The user is banned from this guild", 40007), + CONNECTION_REVOKED: new ApiError( + "The connection has been revoked", + 40012, + 400, + ), TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError( "Target user is not connected to voice", 40032, -- cgit 1.5.1 From 844f1de6aa67cfa9bcb0fdf3a5f8b45026d96e5d Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Sun, 2 Apr 2023 11:32:40 +1000 Subject: License information --- .../#connection_name/#connection_id/refresh.ts | 18 ++++++++++++++++++ .../routes/connections/#connection_name/authorize.ts | 18 ++++++++++++++++++ .../routes/connections/#connection_name/callback.ts | 18 ++++++++++++++++++ .../#connection_name/#connection_id/access-token.ts | 18 ++++++++++++++++++ .../#connection_name/#connection_id/index.ts | 18 ++++++++++++++++++ src/connections/BattleNet/BattleNetSettings.ts | 18 ++++++++++++++++++ src/connections/BattleNet/index.ts | 18 ++++++++++++++++++ src/connections/Discord/DiscordSettings.ts | 18 ++++++++++++++++++ src/connections/Discord/index.ts | 18 ++++++++++++++++++ src/connections/EpicGames/EpicGamesSettings.ts | 18 ++++++++++++++++++ src/connections/EpicGames/index.ts | 18 ++++++++++++++++++ src/connections/Facebook/FacebookSettings.ts | 18 ++++++++++++++++++ src/connections/Facebook/index.ts | 18 ++++++++++++++++++ src/connections/GitHub/GitHubSettings.ts | 18 ++++++++++++++++++ src/connections/GitHub/index.ts | 18 ++++++++++++++++++ src/connections/Reddit/RedditSettings.ts | 18 ++++++++++++++++++ src/connections/Reddit/index.ts | 18 ++++++++++++++++++ src/connections/Spotify/SpotifySettings.ts | 18 ++++++++++++++++++ src/connections/Spotify/index.ts | 18 ++++++++++++++++++ src/connections/Twitch/TwitchSettings.ts | 18 ++++++++++++++++++ src/connections/Twitch/index.ts | 18 ++++++++++++++++++ src/connections/Twitter/TwitterSettings.ts | 18 ++++++++++++++++++ src/connections/Twitter/index.ts | 18 ++++++++++++++++++ src/connections/Xbox/XboxSettings.ts | 18 ++++++++++++++++++ src/connections/Xbox/index.ts | 18 ++++++++++++++++++ src/connections/Youtube/YoutubeSettings.ts | 18 ++++++++++++++++++ src/connections/Youtube/index.ts | 18 ++++++++++++++++++ src/util/connections/Connection.ts | 18 ++++++++++++++++++ src/util/connections/ConnectionConfig.ts | 18 ++++++++++++++++++ src/util/connections/ConnectionLoader.ts | 18 ++++++++++++++++++ src/util/connections/ConnectionStore.ts | 18 ++++++++++++++++++ src/util/connections/RefreshableConnection.ts | 18 ++++++++++++++++++ src/util/connections/index.ts | 18 ++++++++++++++++++ src/util/dtos/ConnectedAccountDTO.ts | 18 ++++++++++++++++++ src/util/entities/ConnectionConfigEntity.ts | 18 ++++++++++++++++++ src/util/interfaces/ConnectedAccount.ts | 18 ++++++++++++++++++ src/util/schemas/ConnectedAccountSchema.ts | 18 ++++++++++++++++++ src/util/schemas/ConnectionCallbackSchema.ts | 18 ++++++++++++++++++ src/util/schemas/ConnectionUpdateSchema.ts | 18 ++++++++++++++++++ 39 files changed, 702 insertions(+) (limited to 'src/util/schemas/ConnectedAccountSchema.ts') diff --git a/src/api/routes/connections/#connection_name/#connection_id/refresh.ts b/src/api/routes/connections/#connection_name/#connection_id/refresh.ts index 4fa64978..0d432c2b 100644 --- a/src/api/routes/connections/#connection_name/#connection_id/refresh.ts +++ b/src/api/routes/connections/#connection_name/#connection_id/refresh.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { route } from "@spacebar/api"; import { Request, Response, Router } from "express"; const router = Router(); diff --git a/src/api/routes/connections/#connection_name/authorize.ts b/src/api/routes/connections/#connection_name/authorize.ts index 63738e7c..b43f46d7 100644 --- a/src/api/routes/connections/#connection_name/authorize.ts +++ b/src/api/routes/connections/#connection_name/authorize.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { route } from "@spacebar/api"; import { Request, Response, Router } from "express"; import { ConnectionStore, FieldErrors } from "../../../../util"; diff --git a/src/api/routes/connections/#connection_name/callback.ts b/src/api/routes/connections/#connection_name/callback.ts index 74021170..bc9ba455 100644 --- a/src/api/routes/connections/#connection_name/callback.ts +++ b/src/api/routes/connections/#connection_name/callback.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { route } from "@spacebar/api"; import { ConnectionCallbackSchema, diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts index a290d88a..9031f3c8 100644 --- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts +++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { route } from "@spacebar/api"; import { ApiError, diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts index afb0df06..3a4e5e0a 100644 --- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts +++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { route } from "@spacebar/api"; import { ConnectedAccount, diff --git a/src/connections/BattleNet/BattleNetSettings.ts b/src/connections/BattleNet/BattleNetSettings.ts index 75e5c3ae..8fa0748b 100644 --- a/src/connections/BattleNet/BattleNetSettings.ts +++ b/src/connections/BattleNet/BattleNetSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class BattleNetSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/BattleNet/index.ts b/src/connections/BattleNet/index.ts index 7ea919f1..7edc2e92 100644 --- a/src/connections/BattleNet/index.ts +++ b/src/connections/BattleNet/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/Discord/DiscordSettings.ts b/src/connections/Discord/DiscordSettings.ts index 3751b041..12633076 100644 --- a/src/connections/Discord/DiscordSettings.ts +++ b/src/connections/Discord/DiscordSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class DiscordSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/Discord/index.ts b/src/connections/Discord/index.ts index 24e90860..76de33be 100644 --- a/src/connections/Discord/index.ts +++ b/src/connections/Discord/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/EpicGames/EpicGamesSettings.ts b/src/connections/EpicGames/EpicGamesSettings.ts index 4820a88a..a66b6f4f 100644 --- a/src/connections/EpicGames/EpicGamesSettings.ts +++ b/src/connections/EpicGames/EpicGamesSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class EpicGamesSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/EpicGames/index.ts b/src/connections/EpicGames/index.ts index 5e758540..bd7c7eef 100644 --- a/src/connections/EpicGames/index.ts +++ b/src/connections/EpicGames/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/Facebook/FacebookSettings.ts b/src/connections/Facebook/FacebookSettings.ts index cc3e3402..17811a12 100644 --- a/src/connections/Facebook/FacebookSettings.ts +++ b/src/connections/Facebook/FacebookSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class FacebookSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/Facebook/index.ts b/src/connections/Facebook/index.ts index 8e0f8d27..6ce722dd 100644 --- a/src/connections/Facebook/index.ts +++ b/src/connections/Facebook/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/GitHub/GitHubSettings.ts b/src/connections/GitHub/GitHubSettings.ts index 1b4070d2..ef5fe405 100644 --- a/src/connections/GitHub/GitHubSettings.ts +++ b/src/connections/GitHub/GitHubSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class GitHubSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/GitHub/index.ts b/src/connections/GitHub/index.ts index 638a34af..a675873f 100644 --- a/src/connections/GitHub/index.ts +++ b/src/connections/GitHub/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/Reddit/RedditSettings.ts b/src/connections/Reddit/RedditSettings.ts index 13208fb5..3d1bf8bc 100644 --- a/src/connections/Reddit/RedditSettings.ts +++ b/src/connections/Reddit/RedditSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class RedditSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/Reddit/index.ts b/src/connections/Reddit/index.ts index 2f3418c0..191c6452 100644 --- a/src/connections/Reddit/index.ts +++ b/src/connections/Reddit/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/Spotify/SpotifySettings.ts b/src/connections/Spotify/SpotifySettings.ts index e73c0304..0f5ae95e 100644 --- a/src/connections/Spotify/SpotifySettings.ts +++ b/src/connections/Spotify/SpotifySettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class SpotifySettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/Spotify/index.ts b/src/connections/Spotify/index.ts index c9517b1a..61b17366 100644 --- a/src/connections/Spotify/index.ts +++ b/src/connections/Spotify/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/Twitch/TwitchSettings.ts b/src/connections/Twitch/TwitchSettings.ts index eb732c82..31927e3e 100644 --- a/src/connections/Twitch/TwitchSettings.ts +++ b/src/connections/Twitch/TwitchSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class TwitchSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/Twitch/index.ts b/src/connections/Twitch/index.ts index d53215f1..6d679aa4 100644 --- a/src/connections/Twitch/index.ts +++ b/src/connections/Twitch/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/Twitter/TwitterSettings.ts b/src/connections/Twitter/TwitterSettings.ts index e4aa58eb..df979d5f 100644 --- a/src/connections/Twitter/TwitterSettings.ts +++ b/src/connections/Twitter/TwitterSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class TwitterSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/Twitter/index.ts b/src/connections/Twitter/index.ts index f8eac894..aa48ca12 100644 --- a/src/connections/Twitter/index.ts +++ b/src/connections/Twitter/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/Xbox/XboxSettings.ts b/src/connections/Xbox/XboxSettings.ts index c1a41056..5dc764d3 100644 --- a/src/connections/Xbox/XboxSettings.ts +++ b/src/connections/Xbox/XboxSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class XboxSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/Xbox/index.ts b/src/connections/Xbox/index.ts index 011f87a8..c592fd0b 100644 --- a/src/connections/Xbox/index.ts +++ b/src/connections/Xbox/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/connections/Youtube/YoutubeSettings.ts b/src/connections/Youtube/YoutubeSettings.ts index 5d11fa40..f2daaada 100644 --- a/src/connections/Youtube/YoutubeSettings.ts +++ b/src/connections/Youtube/YoutubeSettings.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export class YoutubeSettings { enabled: boolean = false; clientId: string | null = null; diff --git a/src/connections/Youtube/index.ts b/src/connections/Youtube/index.ts index 4d90e452..f3a43fcc 100644 --- a/src/connections/Youtube/index.ts +++ b/src/connections/Youtube/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount, ConnectedAccountCommonOAuthTokenResponse, diff --git a/src/util/connections/Connection.ts b/src/util/connections/Connection.ts index 26279299..becee589 100644 --- a/src/util/connections/Connection.ts +++ b/src/util/connections/Connection.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import crypto from "crypto"; import { ConnectedAccount } from "../entities"; import { ConnectedAccountSchema, ConnectionCallbackSchema } from "../schemas"; diff --git a/src/util/connections/ConnectionConfig.ts b/src/util/connections/ConnectionConfig.ts index 7d1f9857..5a2239a0 100644 --- a/src/util/connections/ConnectionConfig.ts +++ b/src/util/connections/ConnectionConfig.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + /* eslint-disable @typescript-eslint/no-explicit-any */ import { ConnectionConfigEntity } from "../entities/ConnectionConfigEntity"; diff --git a/src/util/connections/ConnectionLoader.ts b/src/util/connections/ConnectionLoader.ts index b32f77cd..28f1a202 100644 --- a/src/util/connections/ConnectionLoader.ts +++ b/src/util/connections/ConnectionLoader.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import fs from "fs"; import path from "path"; import Connection from "./Connection"; diff --git a/src/util/connections/ConnectionStore.ts b/src/util/connections/ConnectionStore.ts index 759b6de7..39abfea6 100644 --- a/src/util/connections/ConnectionStore.ts +++ b/src/util/connections/ConnectionStore.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import Connection from "./Connection"; import RefreshableConnection from "./RefreshableConnection"; diff --git a/src/util/connections/RefreshableConnection.ts b/src/util/connections/RefreshableConnection.ts index 87f5f6dd..fd93adfa 100644 --- a/src/util/connections/RefreshableConnection.ts +++ b/src/util/connections/RefreshableConnection.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount } from "../entities"; import { ConnectedAccountCommonOAuthTokenResponse } from "../interfaces"; import Connection from "./Connection"; diff --git a/src/util/connections/index.ts b/src/util/connections/index.ts index 8d20bf27..8ad267b1 100644 --- a/src/util/connections/index.ts +++ b/src/util/connections/index.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export * from "./Connection"; export * from "./ConnectionConfig"; export * from "./ConnectionLoader"; diff --git a/src/util/dtos/ConnectedAccountDTO.ts b/src/util/dtos/ConnectedAccountDTO.ts index a3618fd1..0a3604d5 100644 --- a/src/util/dtos/ConnectedAccountDTO.ts +++ b/src/util/dtos/ConnectedAccountDTO.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccount } from "../entities"; export class ConnectedAccountDTO { diff --git a/src/util/entities/ConnectionConfigEntity.ts b/src/util/entities/ConnectionConfigEntity.ts index 9c212b15..e4b7cea8 100644 --- a/src/util/entities/ConnectionConfigEntity.ts +++ b/src/util/entities/ConnectionConfigEntity.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { Column, Entity } from "typeorm"; import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass"; diff --git a/src/util/interfaces/ConnectedAccount.ts b/src/util/interfaces/ConnectedAccount.ts index ede02f6d..7278c0cb 100644 --- a/src/util/interfaces/ConnectedAccount.ts +++ b/src/util/interfaces/ConnectedAccount.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export interface ConnectedAccountCommonOAuthTokenResponse { access_token: string; token_type: string; diff --git a/src/util/schemas/ConnectedAccountSchema.ts b/src/util/schemas/ConnectedAccountSchema.ts index fa834bd6..fe808a35 100644 --- a/src/util/schemas/ConnectedAccountSchema.ts +++ b/src/util/schemas/ConnectedAccountSchema.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + import { ConnectedAccountTokenData } from "../interfaces"; export interface ConnectedAccountSchema { diff --git a/src/util/schemas/ConnectionCallbackSchema.ts b/src/util/schemas/ConnectionCallbackSchema.ts index 09ae8a46..eb86c087 100644 --- a/src/util/schemas/ConnectionCallbackSchema.ts +++ b/src/util/schemas/ConnectionCallbackSchema.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export interface ConnectionCallbackSchema { code?: string; state: string; diff --git a/src/util/schemas/ConnectionUpdateSchema.ts b/src/util/schemas/ConnectionUpdateSchema.ts index e1e6523a..f9f17b0d 100644 --- a/src/util/schemas/ConnectionUpdateSchema.ts +++ b/src/util/schemas/ConnectionUpdateSchema.ts @@ -1,3 +1,21 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + export interface ConnectionUpdateSchema { visibility?: boolean; show_activity?: boolean; -- cgit 1.5.1