summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-12-24 18:55:14 +1100
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-12-24 18:55:14 +1100
commita02f929d343b7d2b5ca0c61dce019929fec9b7a0 (patch)
tree6f5799ec84f3120969ca17d806dffac214bff73b /src
parentFix creating bot accounts (diff)
downloadserver-a02f929d343b7d2b5ca0c61dce019929fec9b7a0.tar.xz
OAuth2 authorize bot flow
Diffstat (limited to 'src')
-rw-r--r--src/api/routes/oauth2/authorize.ts146
-rw-r--r--src/util/schemas/ApplicationAuthorizeSchema.ts7
-rw-r--r--src/util/schemas/index.ts3
3 files changed, 155 insertions, 1 deletions
diff --git a/src/api/routes/oauth2/authorize.ts b/src/api/routes/oauth2/authorize.ts
new file mode 100644
index 00000000..e4c2e986
--- /dev/null
+++ b/src/api/routes/oauth2/authorize.ts
@@ -0,0 +1,146 @@
+import { Router, Request, Response } from "express";
+import { route } from "@fosscord/api";
+import { ApiError, Application, ApplicationAuthorizeSchema, getPermission, DiscordApiErrors, Member, Permissions, User, getRights, Rights, MemberPrivateProjection } from "@fosscord/util";
+const router = Router();
+
+// TODO: scopes, other oauth types
+
+router.get("/", route({}), async (req: Request, res: Response) => {
+	const {
+		client_id,
+		scope,
+		response_type,
+		redirect_url,
+	} = req.query;
+
+	const app = await Application.findOne({
+		where: {
+			id: client_id as string,
+		},
+		relations: ["bot"],
+	});
+
+	// TODO: use DiscordApiErrors
+	// findOneOrFail throws code 404
+	if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
+	if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
+
+	const bot = app.bot;
+	delete app.bot;
+
+	const user = await User.findOneOrFail({
+		where: {
+			id: req.user_id,
+			bot: false,
+		},
+		select: ["id", "username", "avatar", "discriminator", "public_flags"]
+	});
+
+	const guilds = await Member.find({
+		where: {
+			user: {
+				id: req.user_id,
+			},
+		},
+		relations: ["guild", "roles"],
+		//@ts-ignore
+		select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"]
+	});
+
+	const guildsWithPermissions = guilds.map(x => {
+		const perms = x.guild.owner_id === user.id
+			? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
+			: Permissions.finalPermission({
+				user: {
+					id: user.id,
+					roles: x.roles?.map(x => x.id) || [],
+				},
+				guild: {
+					roles: x?.roles || [],
+				}
+			});
+
+		return {
+			id: x.guild.id,
+			name: x.guild.name,
+			icon: x.guild.icon,
+			mfa_level: x.guild.mfa_level,
+			permissions: perms.bitfield.toString(),
+		};
+	});
+
+	return res.json({
+		guilds: guildsWithPermissions,
+		user: {
+			id: user.id,
+			username: user.username,
+			avatar: user.avatar,
+			avatar_decoration: null,	// TODO
+			discriminator: user.discriminator,
+			public_flags: user.public_flags,
+		},
+		application: {
+			id: app.id,
+			name: app.name,
+			icon: app.icon,
+			description: app.description,
+			summary: app.summary,
+			type: app.type,
+			hook: app.hook,
+			guild_id: null,	// TODO support guilds
+			bot_public: app.bot_public,
+			bot_require_code_grant: app.bot_require_code_grant,
+			verify_key: app.verify_key,
+			flags: app.flags,
+		},
+		bot: {
+			id: bot.id,
+			username: bot.username,
+			avatar: bot.avatar,
+			avatar_decoration: null,	// TODO
+			discriminator: bot.discriminator,
+			public_flags: bot.public_flags,
+			bot: true,
+			approximated_guild_count: 0,	// TODO
+		},
+		authorized: false,
+	});
+});
+
+router.post("/", route({ body: "ApplicationAuthorizeSchema" }), async (req: Request, res: Response) => {
+	const body = req.body as ApplicationAuthorizeSchema;
+	const {
+		client_id,
+		scope,
+		response_type,
+		redirect_url
+	} = req.query;
+
+	// TODO: captcha verification
+	// TODO: MFA verification
+
+	const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
+	// getPermission cache won't exist if we're owner
+	if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member!.user.bot) throw DiscordApiErrors.UNAUTHORIZED;
+	perms.hasThrow("MANAGE_GUILD");
+
+	const app = await Application.findOne({
+		where: {
+			id: client_id as string,
+		},
+		relations: ["bot"],
+	});
+
+	// TODO: use DiscordApiErrors
+	// findOneOrFail throws code 404
+	if (!app) throw new ApiError("Unknown Application", 10002, 404);
+	if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
+
+	await Member.addToGuild(app.id, body.guild_id);
+
+	return res.json({
+		location: "/oauth2/authorized",	// redirect URL
+	});
+});
+
+export default router;
diff --git a/src/util/schemas/ApplicationAuthorizeSchema.ts b/src/util/schemas/ApplicationAuthorizeSchema.ts
new file mode 100644
index 00000000..1a0aae0f
--- /dev/null
+++ b/src/util/schemas/ApplicationAuthorizeSchema.ts
@@ -0,0 +1,7 @@
+export interface ApplicationAuthorizeSchema {
+	authorize: boolean;
+	guild_id: string;
+	permissions: string;
+	captcha_key?: string;
+	code?: string;	// 2fa code
+}
\ No newline at end of file
diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts
index a03cebe2..be4be0c7 100644
--- a/src/util/schemas/index.ts
+++ b/src/util/schemas/index.ts
@@ -58,4 +58,5 @@ export * from "./ChannelReorderSchema";
 export * from "./UserSettingsSchema";
 export * from "./BotModifySchema";
 export * from "./ApplicationModifySchema";
-export * from "./ApplicationCreateSchema";
\ No newline at end of file
+export * from "./ApplicationCreateSchema";
+export * from "./ApplicationAuthorizeSchema";
\ No newline at end of file