From 3b034dd6e50a8c356beb8b558a16ee4cf84235d0 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 25 Feb 2025 14:36:49 +1300 Subject: [PATCH] Optional password protection for the web server --- docs/get-started.md | 2 +- scripts/setup.js | 13 +++++- src/types.d.ts | 3 +- src/web/auth.js | 2 +- src/web/pug/home.pug | 72 ++++++++++++++++++++++--------- src/web/pug/includes/template.pug | 3 ++ src/web/routes/oauth.js | 16 ++++--- src/web/routes/password.js | 21 +++++++++ src/web/server.js | 3 +- 9 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 src/web/routes/password.js diff --git a/docs/get-started.md b/docs/get-started.md index 2459a66..2c79476 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -71,7 +71,7 @@ server { client_max_body_size 5M; location / { - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; proxy_pass http://127.0.0.1:6693; } } diff --git a/scripts/setup.js b/scripts/setup.js index 4ac6f84..d949580 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -141,6 +141,7 @@ function defineEchoHandler() { console.log("OOYE has its own web server. It needs to be accessible on the public internet.") console.log("You need to enter a public URL where you will be able to host this web server.") console.log("OOYE listens on localhost:6693, so you will probably have to set up a reverse proxy.") + console.log("Examples: https://gitdab.com/cadence/out-of-your-element/src/branch/main/docs/get-started.md#appendix") console.log("Now listening on port 6693. Feel free to send some test requests.") /** @type {{bridge_origin: string}} */ const bridgeOriginResponse = await prompt({ @@ -207,6 +208,15 @@ function defineEchoHandler() { }) } + console.log("Would you like to require a password to add your bot to servers? This will discourage others from using your bridge.") + console.log("Important: To make it truly private, you MUST ALSO disable Public Bot in the Discord bot configuration page.") + /** @type {{web_password: string}} */ + const passwordResponse = await prompt({ + type: "text", + name: "web_password", + message: "Choose a simple password (optional)" + }) + console.log("What is your Discord client secret?") console.log(`You can find it in the application's OAuth2 section: https://discord.com/developers/applications/${client.id}/oauth2`) /** @type {{discord_client_secret: string}} */ @@ -244,7 +254,8 @@ function defineEchoHandler() { ...bridgeOriginResponse, server_origin: serverOrigin, ...discordTokenResponse, - ...clientSecretResponse + ...clientSecretResponse, + ...passwordResponse } } registration.reg = reg diff --git a/src/types.d.ts b/src/types.d.ts index 62adf27..8c51735 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -29,7 +29,8 @@ export type AppServiceRegistrationConfig = { include_user_id_in_mxid: boolean invite: string[] discord_origin?: string - discord_cdn_origin?: string + discord_cdn_origin?: string, + web_password: string } old_bridge?: { as_token: string diff --git a/src/web/auth.js b/src/web/auth.js index a4f9384..77e60cd 100644 --- a/src/web/auth.js +++ b/src/web/auth.js @@ -23,7 +23,7 @@ async function getManagedGuilds(event) { /** * @param {h3.H3Event} event - * @returns {ReturnType>} + * @returns {ReturnType>} */ function useSession(event) { return h3.useSession(event, {password: reg.as_token}) diff --git a/src/web/pug/home.pug b/src/web/pug/home.pug index a906423..6ba278a 100644 --- a/src/web/pug/home.pug +++ b/src/web/pug/home.pug @@ -1,24 +1,56 @@ extends includes/template.pug block body - .s-page-title.mb24 - h1.s-page-title--header Bridge a Discord server + - let locked = reg.ooye.web_password && reg.ooye.web_password !== session.data.password - .d-grid.g24.grid__2(class="sm:grid__1") - .s-card.bs-md.d-flex.fd-column - h2 Easy mode - p Add the bot to your Discord server. - p It will automatically create new Matrix rooms for you. - .fl-grow1 - a.s-btn.s-btn__filled.s-btn__icon(href=rel("/oauth?action=add")) - != icons.Icons.IconPlus - = ` Add to server` - .s-card.bs-md.d-flex.fd-column - h2 Self-service - p OOYE will link an existing Discord server and Matrix space together. - p Choose this option if you already have a community set up on Matrix. - p Or, choose this if you're migrating from a different bridge. - .fl-grow1 - a.s-btn.s-btn__outlined.s-btn__icon(href=rel("/oauth?action=add-self-service")) - != icons.Icons.IconUnorderedList - = ` Set up self-service` + if locked + aside.s-notice.s-notice__warning.p8 + .d-flex.flex__center.jc-space-between.s-banner--container.g8(class="md:fw-wrap") + .d-flex.ai-center.g8 + .flex--item!= icons.Icons.IconLock + p.m0 Private instance. You need the password to use this instance of Out Of Your Element. + form(method="post" action="/api/password") + input.s-input(placeholder="Enter password" name="password") + + .h32 + + .s-page-title.mb24 + h1.s-page-title--header Out Of Your Element + + else + .s-page-title.mb24 + h1.s-page-title--header Bridge a Discord server + + .d-grid.g24.grid__2.mb24(class="sm:grid__1") + .s-card.bs-md.d-flex.fd-column + h2 Easy mode + p Add the bot to your Discord server. + p It will automatically create new Matrix rooms for you. + .fl-grow1 + a.s-btn.s-btn__filled.s-btn__icon(href=rel("/oauth?action=add")) + != icons.Icons.IconPlus + = ` Add to server` + .s-card.bs-md.d-flex.fd-column + h2 Self-service + p OOYE will link an existing Discord server and Matrix space together. + p Choose this option if you already have a community set up on Matrix. + p Or, choose this if you're migrating from a different bridge. + .fl-grow1 + a.s-btn.s-btn__outlined.s-btn__icon(href=rel("/oauth?action=add-self-service")) + != icons.Icons.IconUnorderedList + = ` Set up self-service` + + .s-prose + h2 What is this? + p #[a(href="https://gitdab.com/cadence/out-of-your-element") Out Of Your Element] is a bridge between the Discord and Matrix chat apps. It lets people on both platforms chat with each other without needing to get everyone on the same app. + p Just chat like usual, and the bridge will forward messages back and forth between the two platforms, so everyone sees the whole conversation. + p All kinds of content are supported, including pictures, threads, emojis, and @mentions. + p It's really easy to set up, even if you only have Discord. Just add the bot to your server, and it'll make everything available on Matrix automatically. + + if locked + h2 This is a private instance + p Anybody can run their own instance of the Out Of Your Element software. The person running this instance has made it private, so you can't add it to your server just yet. If you know the people in charge of #{reg.ooye.server_name}, ask them for the password. + + h2 Run your own instance + p You can still use Out Of Your Element by running your own copy of the software, but this requires some technical skill. + p To get started, #[a(href="https://gitdab.com/cadence/out-of-your-element/src/branch/main/docs/get-started.md") check the installation instructions.] diff --git a/src/web/pug/includes/template.pug b/src/web/pug/includes/template.pug index 131d7d2..ae395c5 100644 --- a/src/web/pug/includes/template.pug +++ b/src/web/pug/includes/template.pug @@ -61,6 +61,9 @@ html(lang="en") meta(name="htmx-config" content='{"requestClass":"is-loading"}') style. + .s-prose a { + text-decoration: underline; + } .themed { --theme-base-primary-color-h: 266; --theme-base-primary-color-s: 53%; diff --git a/src/web/routes/oauth.js b/src/web/routes/oauth.js index ace7b72..f659115 100644 --- a/src/web/routes/oauth.js +++ b/src/web/routes/oauth.js @@ -37,13 +37,15 @@ as.router.get("/oauth", defineEventHandler(async event => { const session = await auth.useSession(event) let scope = "guilds" - const parsedFirstQuery = await getValidatedQuery(event, schema.first.safeParse) - if (parsedFirstQuery.data?.action === "add") { - scope = "bot+guilds" - await session.update({selfService: false}) - } else if (parsedFirstQuery.data?.action === "add-self-service") { - scope = "bot+guilds" - await session.update({selfService: true}) + if (!reg.ooye.web_password || reg.ooye.web_password === session.data.password) { + const parsedFirstQuery = await getValidatedQuery(event, schema.first.safeParse) + if (parsedFirstQuery.data?.action === "add") { + scope = "bot+guilds" + await session.update({selfService: false}) + } else if (parsedFirstQuery.data?.action === "add-self-service") { + scope = "bot+guilds" + await session.update({selfService: true}) + } } async function tryAgain() { diff --git a/src/web/routes/password.js b/src/web/routes/password.js new file mode 100644 index 0000000..e1dd299 --- /dev/null +++ b/src/web/routes/password.js @@ -0,0 +1,21 @@ +// @ts-check + +const {z} = require("zod") +const {defineEventHandler, readValidatedBody, sendRedirect} = require("h3") +const {as, sync} = require("../../passthrough") + +/** @type {import("../auth")} */ +const auth = sync.require("../auth") + +const schema = { + password: z.object({ + password: z.string() + }) +} + +as.router.post("/api/password", defineEventHandler(async event => { + const {password} = await readValidatedBody(event, schema.password.parse) + const session = await auth.useSession(event) + await session.update({password}) + return sendRedirect(event, "../") +})) diff --git a/src/web/server.js b/src/web/server.js index d4e7398..565f29a 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -30,8 +30,9 @@ sync.require("./routes/download-discord") sync.require("./routes/guild-settings") sync.require("./routes/guild") sync.require("./routes/link") -sync.require("./routes/oauth") sync.require("./routes/log-in-with-matrix") +sync.require("./routes/oauth") +sync.require("./routes/password") // Files