Merge branch 'master' of github.com:spacebarchat/server
This commit is contained in:
commit
a25a62aa76
@ -16,7 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { checkToken, Config, Rights } from "@spacebar/util";
|
import { checkToken, Rights } from "@spacebar/util";
|
||||||
import * as Sentry from "@sentry/node";
|
import * as Sentry from "@sentry/node";
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
checkToken,
|
checkToken,
|
||||||
Config,
|
|
||||||
Email,
|
Email,
|
||||||
FieldErrors,
|
FieldErrors,
|
||||||
generateToken,
|
generateToken,
|
||||||
|
@ -22,7 +22,7 @@ const router = Router();
|
|||||||
|
|
||||||
router.post("/", route({}), async (req: Request, res: Response) => {
|
router.post("/", route({}), async (req: Request, res: Response) => {
|
||||||
// TODO:
|
// TODO:
|
||||||
const { connection_name, connection_id } = req.params;
|
// const { connection_name, connection_id } = req.params;
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,9 +23,9 @@ import {
|
|||||||
ConnectionStore,
|
ConnectionStore,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
FieldErrors,
|
FieldErrors,
|
||||||
|
RefreshableConnection,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import RefreshableConnection from "../../../../../../../util/connections/RefreshableConnection";
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// TODO: this route is only used for spotify, twitch, and youtube. (battlenet seems to be able to PUT, maybe others also)
|
// TODO: this route is only used for spotify, twitch, and youtube. (battlenet seems to be able to PUT, maybe others also)
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
ConnectedAccount,
|
ConnectedAccount,
|
||||||
ConnectedAccountCommonOAuthTokenResponse,
|
ConnectedAccountCommonOAuthTokenResponse,
|
||||||
|
Connection,
|
||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import Connection from "../../util/connections/Connection";
|
|
||||||
import { BattleNetSettings } from "./BattleNetSettings";
|
import { BattleNetSettings } from "./BattleNetSettings";
|
||||||
|
|
||||||
interface BattleNetConnectionUser {
|
interface BattleNetConnectionUser {
|
||||||
@ -33,10 +33,10 @@ interface BattleNetConnectionUser {
|
|||||||
battletag: string;
|
battletag: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BattleNetErrorResponse {
|
// interface BattleNetErrorResponse {
|
||||||
error: string;
|
// error: string;
|
||||||
error_description: string;
|
// error_description: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default class BattleNetConnection extends Connection {
|
export default class BattleNetConnection extends Connection {
|
||||||
public readonly id = "battlenet";
|
public readonly id = "battlenet";
|
||||||
@ -47,17 +47,21 @@ export default class BattleNetConnection extends Connection {
|
|||||||
settings: BattleNetSettings = new BattleNetSettings();
|
settings: BattleNetSettings = new BattleNetSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings =
|
||||||
this.id,
|
ConnectionLoader.getConnectionConfig<BattleNetSettings>(
|
||||||
this.settings,
|
this.id,
|
||||||
) as BattleNetSettings;
|
this.settings,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
url.searchParams.append("state", state);
|
url.searchParams.append("state", state);
|
||||||
@ -85,8 +89,8 @@ export default class BattleNetConnection extends Connection {
|
|||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code: code,
|
code: code,
|
||||||
client_id: this.settings.clientId!,
|
client_id: this.settings.clientId as string,
|
||||||
client_secret: this.settings.clientSecret!,
|
client_secret: this.settings.clientSecret as string,
|
||||||
redirect_uri: this.getRedirectUri(),
|
redirect_uri: this.getRedirectUri(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -115,8 +119,11 @@ export default class BattleNetConnection extends Connection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.id.toString());
|
const exists = await this.hasConnection(userId, userInfo.id.toString());
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
ConnectedAccount,
|
ConnectedAccount,
|
||||||
ConnectedAccountCommonOAuthTokenResponse,
|
ConnectedAccountCommonOAuthTokenResponse,
|
||||||
|
Connection,
|
||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import Connection from "../../util/connections/Connection";
|
|
||||||
import { DiscordSettings } from "./DiscordSettings";
|
import { DiscordSettings } from "./DiscordSettings";
|
||||||
|
|
||||||
interface UserResponse {
|
interface UserResponse {
|
||||||
@ -43,10 +43,13 @@ export default class DiscordConnection extends Connection {
|
|||||||
settings: DiscordSettings = new DiscordSettings();
|
settings: DiscordSettings = new DiscordSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<DiscordSettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as DiscordSettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
@ -54,7 +57,7 @@ export default class DiscordConnection extends Connection {
|
|||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("state", state);
|
url.searchParams.append("state", state);
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
// controls whether, on repeated authorizations, the consent screen is shown
|
// controls whether, on repeated authorizations, the consent screen is shown
|
||||||
@ -82,8 +85,8 @@ export default class DiscordConnection extends Connection {
|
|||||||
})
|
})
|
||||||
.body(
|
.body(
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
client_id: this.settings.clientId!,
|
client_id: this.settings.clientId as string,
|
||||||
client_secret: this.settings.clientSecret!,
|
client_secret: this.settings.clientSecret as string,
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code: code,
|
code: code,
|
||||||
redirect_uri: this.getRedirectUri(),
|
redirect_uri: this.getRedirectUri(),
|
||||||
@ -114,8 +117,11 @@ export default class DiscordConnection extends Connection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.id);
|
const exists = await this.hasConnection(userId, userInfo.id);
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
ConnectedAccount,
|
ConnectedAccount,
|
||||||
ConnectedAccountCommonOAuthTokenResponse,
|
ConnectedAccountCommonOAuthTokenResponse,
|
||||||
|
Connection,
|
||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import Connection from "../../util/connections/Connection";
|
|
||||||
import { EpicGamesSettings } from "./EpicGamesSettings";
|
import { EpicGamesSettings } from "./EpicGamesSettings";
|
||||||
|
|
||||||
export interface UserResponse {
|
export interface UserResponse {
|
||||||
@ -53,17 +53,21 @@ export default class EpicGamesConnection extends Connection {
|
|||||||
settings: EpicGamesSettings = new EpicGamesSettings();
|
settings: EpicGamesSettings = new EpicGamesSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings =
|
||||||
this.id,
|
ConnectionLoader.getConnectionConfig<EpicGamesSettings>(
|
||||||
this.settings,
|
this.id,
|
||||||
) as EpicGamesSettings;
|
this.settings,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
@ -127,8 +131,11 @@ export default class EpicGamesConnection extends Connection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo[0].accountId);
|
const exists = await this.hasConnection(userId, userInfo[0].accountId);
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
ConnectedAccount,
|
ConnectedAccount,
|
||||||
ConnectedAccountCommonOAuthTokenResponse,
|
ConnectedAccountCommonOAuthTokenResponse,
|
||||||
|
Connection,
|
||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import Connection from "../../util/connections/Connection";
|
|
||||||
import { FacebookSettings } from "./FacebookSettings";
|
import { FacebookSettings } from "./FacebookSettings";
|
||||||
|
|
||||||
export interface FacebookErrorResponse {
|
export interface FacebookErrorResponse {
|
||||||
@ -52,17 +52,20 @@ export default class FacebookConnection extends Connection {
|
|||||||
settings: FacebookSettings = new FacebookSettings();
|
settings: FacebookSettings = new FacebookSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<FacebookSettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as FacebookSettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("state", state);
|
url.searchParams.append("state", state);
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
@ -73,8 +76,11 @@ export default class FacebookConnection extends Connection {
|
|||||||
|
|
||||||
getTokenUrl(code: string): string {
|
getTokenUrl(code: string): string {
|
||||||
const url = new URL(this.tokenUrl);
|
const url = new URL(this.tokenUrl);
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("client_secret", this.settings.clientSecret!);
|
url.searchParams.append(
|
||||||
|
"client_secret",
|
||||||
|
this.settings.clientSecret as string,
|
||||||
|
);
|
||||||
url.searchParams.append("code", code);
|
url.searchParams.append("code", code);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
return url.toString();
|
return url.toString();
|
||||||
@ -118,8 +124,11 @@ export default class FacebookConnection extends Connection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.id);
|
const exists = await this.hasConnection(userId, userInfo.id);
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
ConnectedAccount,
|
ConnectedAccount,
|
||||||
ConnectedAccountCommonOAuthTokenResponse,
|
ConnectedAccountCommonOAuthTokenResponse,
|
||||||
|
Connection,
|
||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import Connection from "../../util/connections/Connection";
|
|
||||||
import { GitHubSettings } from "./GitHubSettings";
|
import { GitHubSettings } from "./GitHubSettings";
|
||||||
|
|
||||||
interface UserResponse {
|
interface UserResponse {
|
||||||
@ -42,17 +42,20 @@ export default class GitHubConnection extends Connection {
|
|||||||
settings: GitHubSettings = new GitHubSettings();
|
settings: GitHubSettings = new GitHubSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<GitHubSettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as GitHubSettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
url.searchParams.append("state", state);
|
url.searchParams.append("state", state);
|
||||||
@ -61,8 +64,11 @@ export default class GitHubConnection extends Connection {
|
|||||||
|
|
||||||
getTokenUrl(code: string): string {
|
getTokenUrl(code: string): string {
|
||||||
const url = new URL(this.tokenUrl);
|
const url = new URL(this.tokenUrl);
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("client_secret", this.settings.clientSecret!);
|
url.searchParams.append(
|
||||||
|
"client_secret",
|
||||||
|
this.settings.clientSecret as string,
|
||||||
|
);
|
||||||
url.searchParams.append("code", code);
|
url.searchParams.append("code", code);
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
@ -105,8 +111,11 @@ export default class GitHubConnection extends Connection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.id.toString());
|
const exists = await this.hasConnection(userId, userInfo.id.toString());
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
ConnectedAccount,
|
ConnectedAccount,
|
||||||
ConnectedAccountCommonOAuthTokenResponse,
|
ConnectedAccountCommonOAuthTokenResponse,
|
||||||
|
Connection,
|
||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import Connection from "../../util/connections/Connection";
|
|
||||||
import { RedditSettings } from "./RedditSettings";
|
import { RedditSettings } from "./RedditSettings";
|
||||||
|
|
||||||
export interface UserResponse {
|
export interface UserResponse {
|
||||||
@ -54,17 +54,20 @@ export default class RedditConnection extends Connection {
|
|||||||
settings: RedditSettings = new RedditSettings();
|
settings: RedditSettings = new RedditSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<RedditSettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as RedditSettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
@ -124,8 +127,11 @@ export default class RedditConnection extends Connection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.id.toString());
|
const exists = await this.hasConnection(userId, userInfo.id.toString());
|
||||||
|
@ -22,9 +22,9 @@ import {
|
|||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
|
RefreshableConnection,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import RefreshableConnection from "../../util/connections/RefreshableConnection";
|
|
||||||
import { SpotifySettings } from "./SpotifySettings";
|
import { SpotifySettings } from "./SpotifySettings";
|
||||||
|
|
||||||
export interface UserResponse {
|
export interface UserResponse {
|
||||||
@ -63,17 +63,20 @@ export default class SpotifyConnection extends RefreshableConnection {
|
|||||||
* So to prevent spamming the spotify api we disable the ability to refresh.
|
* So to prevent spamming the spotify api we disable the ability to refresh.
|
||||||
*/
|
*/
|
||||||
this.refreshEnabled = false;
|
this.refreshEnabled = false;
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<SpotifySettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as SpotifySettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
@ -98,7 +101,9 @@ export default class SpotifyConnection extends RefreshableConnection {
|
|||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
Authorization: `Basic ${Buffer.from(
|
Authorization: `Basic ${Buffer.from(
|
||||||
`${this.settings.clientId!}:${this.settings.clientSecret!}`,
|
`${this.settings.clientId as string}:${
|
||||||
|
this.settings.clientSecret as string
|
||||||
|
}`,
|
||||||
).toString("base64")}`,
|
).toString("base64")}`,
|
||||||
})
|
})
|
||||||
.body(
|
.body(
|
||||||
@ -129,7 +134,9 @@ export default class SpotifyConnection extends RefreshableConnection {
|
|||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
Authorization: `Basic ${Buffer.from(
|
Authorization: `Basic ${Buffer.from(
|
||||||
`${this.settings.clientId!}:${this.settings.clientSecret!}`,
|
`${this.settings.clientId as string}:${
|
||||||
|
this.settings.clientSecret as string
|
||||||
|
}`,
|
||||||
).toString("base64")}`,
|
).toString("base64")}`,
|
||||||
})
|
})
|
||||||
.body(
|
.body(
|
||||||
@ -169,8 +176,11 @@ export default class SpotifyConnection extends RefreshableConnection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.id);
|
const exists = await this.hasConnection(userId, userInfo.id);
|
||||||
|
@ -22,9 +22,9 @@ import {
|
|||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
|
RefreshableConnection,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import RefreshableConnection from "../../util/connections/RefreshableConnection";
|
|
||||||
import { TwitchSettings } from "./TwitchSettings";
|
import { TwitchSettings } from "./TwitchSettings";
|
||||||
|
|
||||||
interface TwitchConnectionUserResponse {
|
interface TwitchConnectionUserResponse {
|
||||||
@ -55,17 +55,20 @@ export default class TwitchConnection extends RefreshableConnection {
|
|||||||
settings: TwitchSettings = new TwitchSettings();
|
settings: TwitchSettings = new TwitchSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<TwitchSettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as TwitchSettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
@ -94,8 +97,8 @@ export default class TwitchConnection extends RefreshableConnection {
|
|||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code: code,
|
code: code,
|
||||||
client_id: this.settings.clientId!,
|
client_id: this.settings.clientId as string,
|
||||||
client_secret: this.settings.clientSecret!,
|
client_secret: this.settings.clientSecret as string,
|
||||||
redirect_uri: this.getRedirectUri(),
|
redirect_uri: this.getRedirectUri(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -124,8 +127,8 @@ export default class TwitchConnection extends RefreshableConnection {
|
|||||||
.body(
|
.body(
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
client_id: this.settings.clientId!,
|
client_id: this.settings.clientId as string,
|
||||||
client_secret: this.settings.clientSecret!,
|
client_secret: this.settings.clientSecret as string,
|
||||||
refresh_token: refresh_token,
|
refresh_token: refresh_token,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -148,7 +151,7 @@ export default class TwitchConnection extends RefreshableConnection {
|
|||||||
return wretch(url.toString())
|
return wretch(url.toString())
|
||||||
.headers({
|
.headers({
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
"Client-Id": this.settings.clientId!,
|
"Client-Id": this.settings.clientId as string,
|
||||||
})
|
})
|
||||||
.get()
|
.get()
|
||||||
.json<TwitchConnectionUserResponse>()
|
.json<TwitchConnectionUserResponse>()
|
||||||
@ -161,8 +164,11 @@ export default class TwitchConnection extends RefreshableConnection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.data[0].id);
|
const exists = await this.hasConnection(userId, userInfo.data[0].id);
|
||||||
|
@ -22,9 +22,9 @@ import {
|
|||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
|
RefreshableConnection,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import RefreshableConnection from "../../util/connections/RefreshableConnection";
|
|
||||||
import { TwitterSettings } from "./TwitterSettings";
|
import { TwitterSettings } from "./TwitterSettings";
|
||||||
|
|
||||||
interface TwitterUserResponse {
|
interface TwitterUserResponse {
|
||||||
@ -40,10 +40,10 @@ interface TwitterUserResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TwitterErrorResponse {
|
// interface TwitterErrorResponse {
|
||||||
error: string;
|
// error: string;
|
||||||
error_description: string;
|
// error_description: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default class TwitterConnection extends RefreshableConnection {
|
export default class TwitterConnection extends RefreshableConnection {
|
||||||
public readonly id = "twitter";
|
public readonly id = "twitter";
|
||||||
@ -55,17 +55,20 @@ export default class TwitterConnection extends RefreshableConnection {
|
|||||||
settings: TwitterSettings = new TwitterSettings();
|
settings: TwitterSettings = new TwitterSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<TwitterSettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as TwitterSettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
@ -92,14 +95,16 @@ export default class TwitterConnection extends RefreshableConnection {
|
|||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
Authorization: `Basic ${Buffer.from(
|
Authorization: `Basic ${Buffer.from(
|
||||||
`${this.settings.clientId!}:${this.settings.clientSecret!}`,
|
`${this.settings.clientId as string}:${
|
||||||
|
this.settings.clientSecret as string
|
||||||
|
}`,
|
||||||
).toString("base64")}`,
|
).toString("base64")}`,
|
||||||
})
|
})
|
||||||
.body(
|
.body(
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code: code,
|
code: code,
|
||||||
client_id: this.settings.clientId!,
|
client_id: this.settings.clientId as string,
|
||||||
redirect_uri: this.getRedirectUri(),
|
redirect_uri: this.getRedirectUri(),
|
||||||
code_verifier: "challenge", // TODO: properly use PKCE challenge
|
code_verifier: "challenge", // TODO: properly use PKCE challenge
|
||||||
}),
|
}),
|
||||||
@ -126,14 +131,16 @@ export default class TwitterConnection extends RefreshableConnection {
|
|||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
Authorization: `Basic ${Buffer.from(
|
Authorization: `Basic ${Buffer.from(
|
||||||
`${this.settings.clientId!}:${this.settings.clientSecret!}`,
|
`${this.settings.clientId as string}:${
|
||||||
|
this.settings.clientSecret as string
|
||||||
|
}`,
|
||||||
).toString("base64")}`,
|
).toString("base64")}`,
|
||||||
})
|
})
|
||||||
.body(
|
.body(
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
refresh_token,
|
refresh_token,
|
||||||
client_id: this.settings.clientId!,
|
client_id: this.settings.clientId as string,
|
||||||
redirect_uri: this.getRedirectUri(),
|
redirect_uri: this.getRedirectUri(),
|
||||||
code_verifier: "challenge", // TODO: properly use PKCE challenge
|
code_verifier: "challenge", // TODO: properly use PKCE challenge
|
||||||
}),
|
}),
|
||||||
@ -163,8 +170,11 @@ export default class TwitterConnection extends RefreshableConnection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.data.id);
|
const exists = await this.hasConnection(userId, userInfo.data.id);
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
ConnectedAccount,
|
ConnectedAccount,
|
||||||
ConnectedAccountCommonOAuthTokenResponse,
|
ConnectedAccountCommonOAuthTokenResponse,
|
||||||
|
Connection,
|
||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import Connection from "../../util/connections/Connection";
|
|
||||||
import { XboxSettings } from "./XboxSettings";
|
import { XboxSettings } from "./XboxSettings";
|
||||||
|
|
||||||
interface XboxUserResponse {
|
interface XboxUserResponse {
|
||||||
@ -44,10 +44,10 @@ interface XboxUserResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface XboxErrorResponse {
|
// interface XboxErrorResponse {
|
||||||
error: string;
|
// error: string;
|
||||||
error_description: string;
|
// error_description: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default class XboxConnection extends Connection {
|
export default class XboxConnection extends Connection {
|
||||||
public readonly id = "xbox";
|
public readonly id = "xbox";
|
||||||
@ -62,17 +62,20 @@ export default class XboxConnection extends Connection {
|
|||||||
settings: XboxSettings = new XboxSettings();
|
settings: XboxSettings = new XboxSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<XboxSettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as XboxSettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
@ -124,14 +127,16 @@ export default class XboxConnection extends Connection {
|
|||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
Authorization: `Basic ${Buffer.from(
|
Authorization: `Basic ${Buffer.from(
|
||||||
`${this.settings.clientId!}:${this.settings.clientSecret!}`,
|
`${this.settings.clientId as string}:${
|
||||||
|
this.settings.clientSecret as string
|
||||||
|
}`,
|
||||||
).toString("base64")}`,
|
).toString("base64")}`,
|
||||||
})
|
})
|
||||||
.body(
|
.body(
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code: code,
|
code: code,
|
||||||
client_id: this.settings.clientId!,
|
client_id: this.settings.clientId as string,
|
||||||
redirect_uri: this.getRedirectUri(),
|
redirect_uri: this.getRedirectUri(),
|
||||||
scope: this.scopes.join(" "),
|
scope: this.scopes.join(" "),
|
||||||
}),
|
}),
|
||||||
@ -174,8 +179,11 @@ export default class XboxConnection extends Connection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userToken = await this.getUserToken(tokenData.access_token);
|
const userToken = await this.getUserToken(tokenData.access_token);
|
||||||
const userInfo = await this.getUser(userToken);
|
const userInfo = await this.getUser(userToken);
|
||||||
|
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
ConnectedAccount,
|
ConnectedAccount,
|
||||||
ConnectedAccountCommonOAuthTokenResponse,
|
ConnectedAccountCommonOAuthTokenResponse,
|
||||||
|
Connection,
|
||||||
ConnectionCallbackSchema,
|
ConnectionCallbackSchema,
|
||||||
ConnectionLoader,
|
ConnectionLoader,
|
||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import wretch from "wretch";
|
import wretch from "wretch";
|
||||||
import Connection from "../../util/connections/Connection";
|
|
||||||
import { YoutubeSettings } from "./YoutubeSettings";
|
import { YoutubeSettings } from "./YoutubeSettings";
|
||||||
|
|
||||||
interface YouTubeConnectionChannelListResult {
|
interface YouTubeConnectionChannelListResult {
|
||||||
@ -62,17 +62,20 @@ export default class YoutubeConnection extends Connection {
|
|||||||
settings: YoutubeSettings = new YoutubeSettings();
|
settings: YoutubeSettings = new YoutubeSettings();
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
this.settings = ConnectionLoader.getConnectionConfig(
|
const settings = ConnectionLoader.getConnectionConfig<YoutubeSettings>(
|
||||||
this.id,
|
this.id,
|
||||||
this.settings,
|
this.settings,
|
||||||
) as YoutubeSettings;
|
);
|
||||||
|
|
||||||
|
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
|
||||||
|
throw new Error(`Invalid settings for connection ${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthorizationUrl(userId: string): string {
|
getAuthorizationUrl(userId: string): string {
|
||||||
const state = this.createState(userId);
|
const state = this.createState(userId);
|
||||||
const url = new URL(this.authorizeUrl);
|
const url = new URL(this.authorizeUrl);
|
||||||
|
|
||||||
url.searchParams.append("client_id", this.settings.clientId!);
|
url.searchParams.append("client_id", this.settings.clientId as string);
|
||||||
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
url.searchParams.append("redirect_uri", this.getRedirectUri());
|
||||||
url.searchParams.append("response_type", "code");
|
url.searchParams.append("response_type", "code");
|
||||||
url.searchParams.append("scope", this.scopes.join(" "));
|
url.searchParams.append("scope", this.scopes.join(" "));
|
||||||
@ -101,8 +104,8 @@ export default class YoutubeConnection extends Connection {
|
|||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code: code,
|
code: code,
|
||||||
client_id: this.settings.clientId!,
|
client_id: this.settings.clientId as string,
|
||||||
client_secret: this.settings.clientSecret!,
|
client_secret: this.settings.clientSecret as string,
|
||||||
redirect_uri: this.getRedirectUri(),
|
redirect_uri: this.getRedirectUri(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -131,8 +134,11 @@ export default class YoutubeConnection extends Connection {
|
|||||||
async handleCallback(
|
async handleCallback(
|
||||||
params: ConnectionCallbackSchema,
|
params: ConnectionCallbackSchema,
|
||||||
): Promise<ConnectedAccount | null> {
|
): Promise<ConnectedAccount | null> {
|
||||||
const userId = this.getUserId(params.state);
|
const { state, code } = params;
|
||||||
const tokenData = await this.exchangeCode(params.state, params.code!);
|
if (!code) throw new Error("No code provided");
|
||||||
|
|
||||||
|
const userId = this.getUserId(state);
|
||||||
|
const tokenData = await this.exchangeCode(state, code);
|
||||||
const userInfo = await this.getUser(tokenData.access_token);
|
const userInfo = await this.getUser(tokenData.access_token);
|
||||||
|
|
||||||
const exists = await this.hasConnection(userId, userInfo.items[0].id);
|
const exists = await this.hasConnection(userId, userInfo.items[0].id);
|
||||||
|
@ -24,7 +24,7 @@ import { Config, DiscordApiErrors } from "../util";
|
|||||||
/**
|
/**
|
||||||
* A connection that can be used to connect to an external service.
|
* A connection that can be used to connect to an external service.
|
||||||
*/
|
*/
|
||||||
export default abstract class Connection {
|
export abstract class Connection {
|
||||||
id: string;
|
id: string;
|
||||||
settings: { enabled: boolean };
|
settings: { enabled: boolean };
|
||||||
states: Map<string, string> = new Map();
|
states: Map<string, string> = new Map();
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Connection } from "@spacebar/util";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import Connection from "./Connection";
|
|
||||||
import { ConnectionConfig } from "./ConnectionConfig";
|
import { ConnectionConfig } from "./ConnectionConfig";
|
||||||
import { ConnectionStore } from "./ConnectionStore";
|
import { ConnectionStore } from "./ConnectionStore";
|
||||||
|
|
||||||
@ -48,8 +48,7 @@ export class ConnectionLoader {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
public static getConnectionConfig<T>(id: string, defaults?: unknown): T {
|
||||||
public static getConnectionConfig(id: string, defaults?: any): any {
|
|
||||||
let cfg = ConnectionConfig.get()[id];
|
let cfg = ConnectionConfig.get()[id];
|
||||||
if (defaults) {
|
if (defaults) {
|
||||||
if (cfg) cfg = Object.assign({}, defaults, cfg);
|
if (cfg) cfg = Object.assign({}, defaults, cfg);
|
||||||
@ -70,8 +69,7 @@ export class ConnectionLoader {
|
|||||||
|
|
||||||
public static async setConnectionConfig(
|
public static async setConnectionConfig(
|
||||||
id: string,
|
id: string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
config: Partial<unknown>,
|
||||||
config: Partial<any>,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!config)
|
if (!config)
|
||||||
console.warn(`[Connections/WARN] ${id} tried to set config=null!`);
|
console.warn(`[Connections/WARN] ${id} tried to set config=null!`);
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Connection from "./Connection";
|
import { Connection } from "./Connection";
|
||||||
import RefreshableConnection from "./RefreshableConnection";
|
import { RefreshableConnection } from "./RefreshableConnection";
|
||||||
|
|
||||||
export class ConnectionStore {
|
export class ConnectionStore {
|
||||||
public static connections: Map<string, Connection | RefreshableConnection> =
|
public static connections: Map<string, Connection | RefreshableConnection> =
|
||||||
|
@ -18,13 +18,14 @@
|
|||||||
|
|
||||||
import { ConnectedAccount } from "../entities";
|
import { ConnectedAccount } from "../entities";
|
||||||
import { ConnectedAccountCommonOAuthTokenResponse } from "../interfaces";
|
import { ConnectedAccountCommonOAuthTokenResponse } from "../interfaces";
|
||||||
import Connection from "./Connection";
|
import { Connection } from "./Connection";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A connection that can refresh its token.
|
* A connection that can refresh its token.
|
||||||
*/
|
*/
|
||||||
export default abstract class RefreshableConnection extends Connection {
|
export abstract class RefreshableConnection extends Connection {
|
||||||
refreshEnabled = true;
|
refreshEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the token for a connected account.
|
* Refreshes the token for a connected account.
|
||||||
* @param connectedAccount The connected account to refresh
|
* @param connectedAccount The connected account to refresh
|
||||||
|
@ -30,7 +30,7 @@ export class ConnectedAccountDTO {
|
|||||||
verified?: boolean;
|
verified?: boolean;
|
||||||
visibility?: number;
|
visibility?: number;
|
||||||
integrations?: string[];
|
integrations?: string[];
|
||||||
metadata_?: any;
|
metadata_?: unknown;
|
||||||
metadata_visibility?: number;
|
metadata_visibility?: number;
|
||||||
two_way_link?: boolean;
|
two_way_link?: boolean;
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ export class ConnectedAccount extends BaseClass {
|
|||||||
integrations?: string[] = [];
|
integrations?: string[] = [];
|
||||||
|
|
||||||
@Column({ type: "simple-json", name: "metadata", nullable: true })
|
@Column({ type: "simple-json", name: "metadata", nullable: true })
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
metadata_?: any;
|
metadata_?: any;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
|
@ -30,7 +30,7 @@ export interface ConnectedAccountSchema {
|
|||||||
verified?: boolean;
|
verified?: boolean;
|
||||||
visibility?: number;
|
visibility?: number;
|
||||||
integrations?: string[];
|
integrations?: string[];
|
||||||
metadata_?: any;
|
metadata_?: unknown;
|
||||||
metadata_visibility?: number;
|
metadata_visibility?: number;
|
||||||
two_way_link?: boolean;
|
two_way_link?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -21,5 +21,5 @@ export interface ConnectionCallbackSchema {
|
|||||||
state: string;
|
state: string;
|
||||||
insecure: boolean;
|
insecure: boolean;
|
||||||
friend_sync: boolean;
|
friend_sync: boolean;
|
||||||
openid_params?: any; // TODO: types
|
openid_params?: unknown; // TODO: types
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user