diff --git a/src/matrix/kstate.js b/src/matrix/kstate.js index 03d09e0..11155f5 100644 --- a/src/matrix/kstate.js +++ b/src/matrix/kstate.js @@ -87,12 +87,6 @@ function diffKState(actual, target) { diff[key] = temp } - } else if (key === "chat.schildi.hide_ui/read_receipts") { - // Special handling: don't add this key if it's new. Do overwrite if already present. - if (key in actual) { - diff[key] = target[key] - } - } else if (key in actual) { // diff if (!isDeepStrictEqual(actual[key], target[key])) { diff --git a/src/matrix/kstate.test.js b/src/matrix/kstate.test.js index 1b67ad5..0538450 100644 --- a/src/matrix/kstate.test.js +++ b/src/matrix/kstate.test.js @@ -234,31 +234,3 @@ test("diffKState: kstate keys must contain a slash separator", t => { , /does not contain a slash separator/) t.pass() }) - -test("diffKState: don't add hide_ui when not present", t => { - test("diffKState: detects new properties", t => { - t.deepEqual( - diffKState({ - }, { - "chat.schildi.hide_ui/read_receipts/": {} - }), - { - } - ) - }) -}) - -test("diffKState: overwriten hide_ui when present", t => { - test("diffKState: detects new properties", t => { - t.deepEqual( - diffKState({ - "chat.schildi.hide_ui/read_receipts/": {hidden: true} - }, { - "chat.schildi.hide_ui/read_receipts/": {} - }), - { - "chat.schildi.hide_ui/read_receipts/": {} - } - ) - }) -}) diff --git a/src/web/auth.js b/src/web/auth.js deleted file mode 100644 index a4f9384..0000000 --- a/src/web/auth.js +++ /dev/null @@ -1,33 +0,0 @@ -// @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 56b16fb..a966f06 100644 --- a/src/web/pug-sync.js +++ b/src/web/pug-sync.js @@ -5,13 +5,11 @@ const fs = require("fs") const {join} = require("path") const getRelativePath = require("get-relative-path") const h3 = require("h3") -const {defineEventHandler, defaultContentType, setResponseStatus, getQuery} = h3 +const {defineEventHandler, defaultContentType, setResponseStatus, useSession, getQuery} = h3 const {compileFile} = require("@cloudrac3r/pug") const pretty = process.argv.join(" ").includes("test") -const {sync} = require("../passthrough") -/** @type {import("./auth")} */ -const auth = sync.require("./auth") +const {reg} = require("../matrix/read-registration") // Pug @@ -37,8 +35,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 auth.useSession(event) - const managed = await auth.getManagedGuilds(event) + const session = await useSession(event, {password: reg.as_token}) + const managed = new Set((session.data.managedGuilds || []).concat(session.data.matrixGuilds || [])) 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 bdc7148..9c02198 100644 --- a/src/web/routes/guild-settings.js +++ b/src/web/routes/guild-settings.js @@ -2,12 +2,10 @@ const assert = require("assert/strict") const {z} = require("zod") -const {defineEventHandler, createError, readValidatedBody, getRequestHeader, setResponseHeader, sendRedirect, H3Event} = require("h3") +const {defineEventHandler, useSession, createError, readValidatedBody, getRequestHeader, setResponseHeader, sendRedirect, H3Event} = require("h3") const {as, db, sync, select} = require("../../passthrough") - -/** @type {import("../auth")} */ -const auth = sync.require("../auth") +const {reg} = require("../../matrix/read-registration") /** * @param {H3Event} event @@ -33,8 +31,8 @@ const schema = { as.router.post("/api/autocreate", defineEventHandler(async event => { const parsedBody = await readValidatedBody(event, schema.autocreate.parse) - 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 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"}) db.prepare("UPDATE guild_active SET autocreate = ? WHERE guild_id = ?").run(+!!parsedBody.autocreate, parsedBody.guild_id) @@ -53,8 +51,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 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 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 createSpace = getCreateSpace(event) const i = levels.indexOf(parsedBody.level) diff --git a/src/web/routes/guild.js b/src/web/routes/guild.js index 64e79ed..081965f 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, createError, getValidatedQuery, readValidatedBody, setResponseHeader} = require("h3") +const {H3Event, defineEventHandler, sendRedirect, useSession, createError, getValidatedQuery, readValidatedBody, setResponseHeader} = require("h3") const {randomUUID} = require("crypto") const {LRUCache} = require("lru-cache") const Ty = require("../../types") @@ -13,8 +13,6 @@ 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 = { @@ -110,14 +108,13 @@ function getChannelRoomsLinks(guildID, rooms) { as.router.get("/guild", defineEventHandler(async event => { const {guild_id} = await getValidatedQuery(event, schema.guild.parse) - const session = await auth.useSession(event) - const managed = await auth.getManagedGuilds(event) + const session = await useSession(event, {password: reg.as_token}) 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 || !managed.has(guild_id) || !row) { + if (!guild_id || !guild || !(session.data.managedGuilds || []).concat(session.data.matrixGuilds || []).includes(guild_id) || !row) { return pugSync.render(event, "guild_access_denied.pug", {guild_id, row}) } @@ -162,13 +159,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 managed = await auth.getManagedGuilds(event) + const session = await useSession(event, {password: reg.as_token}) const api = getAPI(event) // Check guild ID or nonce if (parsedBody.guild_id) { var guild_id = parsedBody.guild_id - 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"}) + 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"}) } 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 10b0817..e5fa76a 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -1,13 +1,11 @@ // @ts-check const {z} = require("zod") -const {defineEventHandler, createError, readValidatedBody, setResponseHeader, H3Event} = require("h3") +const {defineEventHandler, useSession, 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") /** @@ -55,13 +53,12 @@ const schema = { as.router.post("/api/link-space", defineEventHandler(async event => { const parsedBody = await readValidatedBody(event, schema.linkSpace.parse) - const session = await auth.useSession(event) - const managed = await auth.getManagedGuilds(event) + const session = await useSession(event, {password: reg.as_token}) const api = getAPI(event) // Check guild ID const guildID = parsedBody.guild_id - if (!managed.has(guildID)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"}) + 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"}) // 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"}) @@ -107,14 +104,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 managed = await auth.getManagedGuilds(event) + const session = await useSession(event, {password: reg.as_token}) const api = getAPI(event) const createRoom = getCreateRoom(event) const createSpace = getCreateSpace(event) // Check guild ID or nonce const guildID = parsedBody.guild_id - if (!managed.has(guildID)) throw createError({status: 403, message: "Forbidden", data: "Can't edit a guild you don't have Manage Server permissions in"}) + 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"}) // Check guild is bridged const guild = discord.guilds.get(guildID) @@ -178,11 +175,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 managed = await auth.getManagedGuilds(event) + const session = await useSession(event, {password: reg.as_token}) const createRoom = getCreateRoom(event) // Check guild ID or nonce - 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"}) + 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"}) // 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 70a60bb..f3d3ed5 100644 --- a/src/web/routes/log-in-with-matrix.js +++ b/src/web/routes/log-in-with-matrix.js @@ -2,18 +2,16 @@ const {z} = require("zod") const {randomUUID} = require("crypto") -const {defineEventHandler, getValidatedQuery, sendRedirect, readValidatedBody, createError, getRequestHeader, H3Event} = require("h3") +const {defineEventHandler, getValidatedQuery, sendRedirect, readValidatedBody, useSession, createError, getRequestHeader, H3Event} = require("h3") const {LRUCache} = require("lru-cache") -const {as} = require("../../passthrough") +const {as, db} = 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({ @@ -56,12 +54,14 @@ 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 auth.useSession(event) + const session = await useSession(event, {password: reg.as_token}) const mxid = validToken.get(token) assert(mxid) validToken.delete(token) - await session.update({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(mxid) + + await session.update({mxid, matrixGuilds}) 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 a6e31f0..38d2cc9 100644 --- a/src/web/routes/oauth.js +++ b/src/web/routes/oauth.js @@ -2,16 +2,14 @@ const {z} = require("zod") const {randomUUID} = require("crypto") -const {defineEventHandler, getValidatedQuery, sendRedirect, createError} = require("h3") +const {defineEventHandler, getValidatedQuery, sendRedirect, useSession, 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, sync} = require("../../passthrough") +const {as, db} = 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` @@ -35,7 +33,7 @@ const schema = { } as.router.get("/oauth", defineEventHandler(async event => { - const session = await auth.useSession(event) + const session = await useSession(event, {password: reg.as_token}) let scope = "guilds" const parsedFirstQuery = await getValidatedQuery(event, schema.first.safeParse) diff --git a/src/web/server.js b/src/web/server.js index fc4543d..6c2d087 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, handleCacheHeaders} = h3 +const {defineEventHandler, defaultContentType, getRequestHeader, setResponseHeader, setResponseStatus, useSession, getQuery, handleCacheHeaders} = h3 const icons = require("@stackoverflow/stacks-icons") const DiscordTypes = require("discord-api-types/v10") const dUtils = require("../discord/utils")