diff --git a/src/web/auth.js b/src/web/auth.js new file mode 100644 index 0000000..a4f9384 --- /dev/null +++ b/src/web/auth.js @@ -0,0 +1,33 @@ +// @ts-check + +const h3 = require("h3") +const {db} = require("../passthrough") +const {reg} = require("../matrix/read-registration") + +/** + * Combined guilds managed by Discord account + Matrix account. + * @param {h3.H3Event} event + * @returns {Promise>} guild IDs + */ +async function getManagedGuilds(event) { + const session = await useSession(event) + const managed = new Set(session.data.managedGuilds || []) + if (session.data.mxid) { + const matrixGuilds = db.prepare("SELECT guild_id FROM guild_space INNER JOIN member_cache ON space_id = room_id WHERE mxid = ? AND power_level >= 50").pluck().all(session.data.mxid) + for (const id of matrixGuilds) { + managed.add(id) + } + } + return managed +} + +/** + * @param {h3.H3Event} event + * @returns {ReturnType>} + */ +function useSession(event) { + return h3.useSession(event, {password: reg.as_token}) +} + +module.exports.getManagedGuilds = getManagedGuilds +module.exports.useSession = useSession diff --git a/src/web/pug-sync.js b/src/web/pug-sync.js index a966f06..56b16fb 100644 --- a/src/web/pug-sync.js +++ b/src/web/pug-sync.js @@ -5,11 +5,13 @@ const fs = require("fs") const {join} = require("path") const getRelativePath = require("get-relative-path") const h3 = require("h3") -const {defineEventHandler, defaultContentType, setResponseStatus, useSession, getQuery} = h3 +const {defineEventHandler, defaultContentType, setResponseStatus, getQuery} = h3 const {compileFile} = require("@cloudrac3r/pug") const pretty = process.argv.join(" ").includes("test") -const {reg} = require("../matrix/read-registration") +const {sync} = require("../passthrough") +/** @type {import("./auth")} */ +const auth = sync.require("./auth") // Pug @@ -35,8 +37,8 @@ function render(event, filename, locals) { const template = compileFile(path, {pretty}) pugCache.set(path, async (event, locals) => { defaultContentType(event, "text/html; charset=utf-8") - const session = await useSession(event, {password: reg.as_token}) - const managed = new Set((session.data.managedGuilds || []).concat(session.data.matrixGuilds || [])) + const session = await auth.useSession(event) + const managed = await auth.getManagedGuilds(event) const rel = x => getRelativePath(event.path, x) return template(Object.assign({}, getQuery(event), // Query parameters can be easily accessed on the top level but don't allow them to overwrite anything diff --git a/src/web/routes/guild-settings.js b/src/web/routes/guild-settings.js index 9c02198..bdc7148 100644 --- a/src/web/routes/guild-settings.js +++ b/src/web/routes/guild-settings.js @@ -2,10 +2,12 @@ const assert = require("assert/strict") const {z} = require("zod") -const {defineEventHandler, useSession, createError, readValidatedBody, getRequestHeader, setResponseHeader, sendRedirect, H3Event} = require("h3") +const {defineEventHandler, createError, readValidatedBody, getRequestHeader, setResponseHeader, sendRedirect, H3Event} = require("h3") const {as, db, sync, select} = require("../../passthrough") -const {reg} = require("../../matrix/read-registration") + +/** @type {import("../auth")} */ +const auth = sync.require("../auth") /** * @param {H3Event} event @@ -31,8 +33,8 @@ const schema = { as.router.post("/api/autocreate", defineEventHandler(async event => { const parsedBody = await readValidatedBody(event, schema.autocreate.parse) - const session = await useSession(event, {password: reg.as_token}) - if (!(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(parsedBody.guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't change settings for a guild you don't have Manage Server permissions in"}) + const managed = await auth.getManagedGuilds(event) + if (!managed.has(parsedBody.guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't change settings for a guild you don't have Manage Server permissions in"}) db.prepare("UPDATE guild_active SET autocreate = ? WHERE guild_id = ?").run(+!!parsedBody.autocreate, parsedBody.guild_id) @@ -51,8 +53,8 @@ as.router.post("/api/autocreate", defineEventHandler(async event => { as.router.post("/api/privacy-level", defineEventHandler(async event => { const parsedBody = await readValidatedBody(event, schema.privacyLevel.parse) - const session = await useSession(event, {password: reg.as_token}) - if (!(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(parsedBody.guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't change settings for a guild you don't have Manage Server permissions in"}) + const managed = await auth.getManagedGuilds(event) + if (!managed.has(parsedBody.guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't change settings for a guild you don't have Manage Server permissions in"}) const createSpace = getCreateSpace(event) const i = levels.indexOf(parsedBody.level) diff --git a/src/web/routes/guild.js b/src/web/routes/guild.js index 081965f..64e79ed 100644 --- a/src/web/routes/guild.js +++ b/src/web/routes/guild.js @@ -2,7 +2,7 @@ const assert = require("assert/strict") const {z} = require("zod") -const {H3Event, defineEventHandler, sendRedirect, useSession, createError, getValidatedQuery, readValidatedBody, setResponseHeader} = require("h3") +const {H3Event, defineEventHandler, sendRedirect, createError, getValidatedQuery, readValidatedBody, setResponseHeader} = require("h3") const {randomUUID} = require("crypto") const {LRUCache} = require("lru-cache") const Ty = require("../../types") @@ -13,6 +13,8 @@ const {discord, as, sync, select, from, db} = require("../../passthrough") const pugSync = sync.require("../pug-sync") /** @type {import("../../d2m/actions/create-space")} */ const createSpace = sync.require("../../d2m/actions/create-space") +/** @type {import("../auth")} */ +const auth = require("../auth") const {reg} = require("../../matrix/read-registration") const schema = { @@ -108,13 +110,14 @@ function getChannelRoomsLinks(guildID, rooms) { as.router.get("/guild", defineEventHandler(async event => { const {guild_id} = await getValidatedQuery(event, schema.guild.parse) - const session = await useSession(event, {password: reg.as_token}) + const session = await auth.useSession(event) + const managed = await auth.getManagedGuilds(event) const row = from("guild_active").join("guild_space", "guild_id", "left").select("space_id", "privacy_level", "autocreate").where({guild_id}).get() // @ts-ignore const guild = discord.guilds.get(guild_id) // Permission problems - if (!guild_id || !guild || !(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(guild_id) || !row) { + if (!guild_id || !guild || !managed.has(guild_id) || !row) { return pugSync.render(event, "guild_access_denied.pug", {guild_id, row}) } @@ -159,13 +162,13 @@ as.router.get("/invite", defineEventHandler(async event => { as.router.post("/api/invite", defineEventHandler(async event => { const parsedBody = await readValidatedBody(event, schema.invite.parse) - const session = await useSession(event, {password: reg.as_token}) + const managed = await auth.getManagedGuilds(event) const api = getAPI(event) // Check guild ID or nonce if (parsedBody.guild_id) { var guild_id = parsedBody.guild_id - if (!(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't invite users to a guild you don't have Manage Server permissions in"}) + if (!managed.has(guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't invite users to a guild you don't have Manage Server permissions in"}) } else if (parsedBody.nonce) { if (!validNonce.has(parsedBody.nonce)) throw createError({status: 403, message: "Nonce expired", data: "Nonce means number-used-once, and, well, you tried to use it twice..."}) let ok = validNonce.get(parsedBody.nonce) diff --git a/src/web/routes/link.js b/src/web/routes/link.js index e5fa76a..10b0817 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -1,11 +1,13 @@ // @ts-check const {z} = require("zod") -const {defineEventHandler, useSession, createError, readValidatedBody, setResponseHeader, H3Event} = require("h3") +const {defineEventHandler, createError, readValidatedBody, setResponseHeader, H3Event} = require("h3") const Ty = require("../../types") const DiscordTypes = require("discord-api-types/v10") const {discord, db, as, sync, select, from} = require("../../passthrough") +/** @type {import("../auth")} */ +const auth = require("../auth") const {reg} = require("../../matrix/read-registration") /** @@ -53,12 +55,13 @@ const schema = { as.router.post("/api/link-space", defineEventHandler(async event => { const parsedBody = await readValidatedBody(event, schema.linkSpace.parse) - const session = await useSession(event, {password: reg.as_token}) + const session = await auth.useSession(event) + const managed = await auth.getManagedGuilds(event) const api = getAPI(event) // Check guild ID const guildID = parsedBody.guild_id - if (!(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(guildID)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"}) + if (!managed.has(guildID)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"}) // Check space ID if (!session.data.mxid) throw createError({status: 403, message: "Forbidden", data: "Can't link with your Matrix space if you aren't logged in to Matrix"}) @@ -104,14 +107,14 @@ as.router.post("/api/link-space", defineEventHandler(async event => { as.router.post("/api/link", defineEventHandler(async event => { const parsedBody = await readValidatedBody(event, schema.link.parse) - const session = await useSession(event, {password: reg.as_token}) + const managed = await auth.getManagedGuilds(event) const api = getAPI(event) const createRoom = getCreateRoom(event) const createSpace = getCreateSpace(event) // Check guild ID or nonce const guildID = parsedBody.guild_id - if (!(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(guildID)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"}) + if (!managed.has(guildID)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"}) // Check guild is bridged const guild = discord.guilds.get(guildID) @@ -175,11 +178,11 @@ as.router.post("/api/link", defineEventHandler(async event => { as.router.post("/api/unlink", defineEventHandler(async event => { const {channel_id, guild_id} = await readValidatedBody(event, schema.unlink.parse) - const session = await useSession(event, {password: reg.as_token}) + const managed = await auth.getManagedGuilds(event) const createRoom = getCreateRoom(event) // Check guild ID or nonce - if (!(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"}) + if (!managed.has(guild_id)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"}) // Check guild exists const guild = discord.guilds.get(guild_id) diff --git a/src/web/routes/log-in-with-matrix.js b/src/web/routes/log-in-with-matrix.js index f3d3ed5..70a60bb 100644 --- a/src/web/routes/log-in-with-matrix.js +++ b/src/web/routes/log-in-with-matrix.js @@ -2,16 +2,18 @@ const {z} = require("zod") const {randomUUID} = require("crypto") -const {defineEventHandler, getValidatedQuery, sendRedirect, readValidatedBody, useSession, createError, getRequestHeader, H3Event} = require("h3") +const {defineEventHandler, getValidatedQuery, sendRedirect, readValidatedBody, createError, getRequestHeader, H3Event} = require("h3") const {LRUCache} = require("lru-cache") -const {as, db} = require("../../passthrough") +const {as} = require("../../passthrough") const {reg} = require("../../matrix/read-registration") const {sync} = require("../../passthrough") const assert = require("assert").strict /** @type {import("../pug-sync")} */ const pugSync = sync.require("../pug-sync") +/** @type {import("../auth")} */ +const auth = sync.require("../auth") const schema = { form: z.object({ @@ -54,14 +56,12 @@ as.router.get("/log-in-with-matrix", defineEventHandler(async event => { const token = parsed.data.token if (!validToken.has(token)) return sendRedirect(event, `${reg.ooye.bridge_origin}/log-in-with-matrix`, 302) - const session = await useSession(event, {password: reg.as_token}) + const session = await auth.useSession(event) const mxid = validToken.get(token) assert(mxid) validToken.delete(token) - const matrixGuilds = db.prepare("SELECT guild_id FROM guild_space INNER JOIN member_cache ON space_id = room_id WHERE mxid = ? AND power_level >= 50").pluck().all(mxid) - - await session.update({mxid, matrixGuilds}) + await session.update({mxid}) return sendRedirect(event, "./", 302) // open to homepage where they can see they're logged in })) diff --git a/src/web/routes/oauth.js b/src/web/routes/oauth.js index 38d2cc9..a6e31f0 100644 --- a/src/web/routes/oauth.js +++ b/src/web/routes/oauth.js @@ -2,14 +2,16 @@ const {z} = require("zod") const {randomUUID} = require("crypto") -const {defineEventHandler, getValidatedQuery, sendRedirect, useSession, createError} = require("h3") +const {defineEventHandler, getValidatedQuery, sendRedirect, createError} = require("h3") const {SnowTransfer} = require("snowtransfer") const DiscordTypes = require("discord-api-types/v10") const fetch = require("node-fetch") const getRelativePath = require("get-relative-path") -const {as, db} = require("../../passthrough") +const {as, db, sync} = require("../../passthrough") const {id} = require("../../../addbot") +/** @type {import("../auth")} */ +const auth = sync.require("../auth") const {reg} = require("../../matrix/read-registration") const redirect_uri = `${reg.ooye.bridge_origin}/oauth` @@ -33,7 +35,7 @@ const schema = { } as.router.get("/oauth", defineEventHandler(async event => { - const session = await useSession(event, {password: reg.as_token}) + const session = await auth.useSession(event) let scope = "guilds" const parsedFirstQuery = await getValidatedQuery(event, schema.first.safeParse) diff --git a/src/web/server.js b/src/web/server.js index 6c2d087..fc4543d 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -3,7 +3,7 @@ const fs = require("fs") const {join} = require("path") const h3 = require("h3") -const {defineEventHandler, defaultContentType, getRequestHeader, setResponseHeader, setResponseStatus, useSession, getQuery, handleCacheHeaders} = h3 +const {defineEventHandler, defaultContentType, getRequestHeader, setResponseHeader, handleCacheHeaders} = h3 const icons = require("@stackoverflow/stacks-icons") const DiscordTypes = require("discord-api-types/v10") const dUtils = require("../discord/utils")