diff --git a/src/router.js b/src/router.js index b1092f9..07878f0 100644 --- a/src/router.js +++ b/src/router.js @@ -1,7 +1,7 @@ import { Router } from "express" import { HttpError } from "#types/errors" -import { GetUserByID } from "#utils/db" +import services from "#services" const router = Router() @@ -66,7 +66,7 @@ router.use(async (req, res, next) => { if (id) { res.locals.id = id - res.locals.user = await GetUserByID(id) + res.locals.user = await services.users.GetUserByID(id) } next() diff --git a/src/routes/data.js b/src/routes/data.js index 9b4b34f..208bc77 100644 --- a/src/routes/data.js +++ b/src/routes/data.js @@ -2,9 +2,7 @@ import { Router } from "express" import { HttpError } from "#types/errors" import micromatch from "micromatch" -import logger from "#utils/logger" import config from "#utils/config" -import { FindBy, UpdateBy, DeleteFromArrayBy, AddToArray } from "#utils/db" import services from "#services" @@ -57,9 +55,9 @@ async function ValidateMailboxAsync(email, name) { throw new HttpError(400, "Invalid name") } - const existing = await FindBy({ "mailboxes.email": email }) + const mailbox = await services.mailboxes.GetMailboxByEmail(email) - if (existing) { + if (mailbox) { throw new HttpError(409, "Mailbox already claimed") } } @@ -67,7 +65,7 @@ async function ValidateMailboxAsync(email, name) { async function EnsureNotMailboxAsync(email) { ValidateEmail(email) - const mailbox = await FindBy({ "mailboxes.email": email }) + const mailbox = await services.mailboxes.GetMailboxByEmail(email) if (mailbox) { throw new HttpError(409, "Mailbox already claimed") @@ -77,7 +75,7 @@ async function EnsureNotMailboxAsync(email) { async function EnsureMailboxAsync(email) { ValidateEmail(email) - const mailbox = await FindBy({ "mailboxes.email": email }) + const mailbox = await services.mailboxes.GetMailboxByEmail(email) if (!mailbox) { throw new HttpError(404, "Mailbox does not exist") diff --git a/src/routes/index.js b/src/routes/index.js index bb99434..1de7f78 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,8 +1,6 @@ import { Router } from "express" -import { HttpError } from "#types/errors" import { RequireMailAuth } from "#router" -import logger from "#utils/logger" const router = Router() diff --git a/src/routes/oauth-application.js b/src/routes/oauth-application.js index 8b51c47..282eb9a 100644 --- a/src/routes/oauth-application.js +++ b/src/routes/oauth-application.js @@ -5,6 +5,7 @@ import logger from "#utils/logger" import passport from "passport" import { Strategy as OpenIDConnectStrategy } from "passport-openidconnect" +import services from "#services" const router = Router() @@ -26,6 +27,8 @@ passport.use( return done(new Error("OIDC profile missing id")) } + services.users.FindOrCreateUser(profile.id, profile.name) + logger.dev("Profile: ", profile) return done(null, profile) @@ -36,8 +39,6 @@ passport.use( passport.serializeUser((user, done) => { done(null, { id: user.id, - displayName: user.displayName, - emails: user.emails, }) }) diff --git a/src/routes/oauth-mail.js b/src/routes/oauth-mail.js index 0b705af..5af6571 100644 --- a/src/routes/oauth-mail.js +++ b/src/routes/oauth-mail.js @@ -6,12 +6,9 @@ import { DecodeToken, SignToken } from "#utils/token" import logger from "#utils/logger" import config from "#utils/config" -import { - GetUserByID, - WriteToCache, - GetFromCache, - DeleteFromCache, -} from "#utils/db" +import { WriteToCache, GetFromCache, DeleteFromCache } from "#utils/db" + +import services from "#services" import tldts from "tldts" @@ -58,7 +55,7 @@ async function GetUserInfo(endpoint, token) { async function IsOwnedByUser(id, email) { if (!id || !email) return false - const user = await GetUserByID(id) + const user = await services.users.GetUserByID(id) if (!user) return false @@ -136,18 +133,26 @@ router.get("/authorize", async (req, res, next) => { origState: req.query.state, }) - if ( - matchedUri && - tldts.parse(GetBaseUrl(req)).domain !== tldts.parse(matchedUri).domain - ) { + const matchedDomain = tldts.parse(matchedUri).domain + + // check if matchedUri's domain is equal to the current request domain + // if not repeat /authorize under the correct domain + if (matchedUri && tldts.parse(GetBaseUrl(req)).domain !== matchedDomain) { + // HACK: this replaces callback with authorize so that instances that have their /oauth/mail paths potentially redirected by a proxy + // still continue on that redirected path instead of leading back to the default /oauth/mail/authorize const authorizeUrl = matchedUri.replace("callback", "authorize") + // replace state with our nonce key const forwardedQuery = new URLSearchParams(req.query) - forwardedQuery.set("state", nonce) + + // use req.query.state instead of nonce since we are just repeating the /authorize flow, + // just this time on the correct domain + forwardedQuery.set("state", req.query.state) return res.redirect(`${authorizeUrl}?${forwardedQuery.toString()}`) } + // replace state with our nonce key const forwardedQuery = new URLSearchParams(req.query) forwardedQuery.set("state", nonce) @@ -162,6 +167,7 @@ router.get("/authorize", async (req, res, next) => { router.get("/callback", async (req, res, next) => { try { + // get our generated nonce key from the "state" const nonce = req.query.state if (!nonce) { @@ -227,7 +233,7 @@ router.get("/mailbox", async (req, res, next) => { return res.status(400).send("No pending mail session") } - const originalHost = await GetFromCache(`state:${mailData.state}`) + const originalHost = await GetFromCache(`state:${mailData.state}`)?.host const tokenRes = await GetFromCache(`code:${mailData.code}`) diff --git a/src/services/mailboxes.js b/src/services/mailboxes.js index 4679012..fe9851e 100644 --- a/src/services/mailboxes.js +++ b/src/services/mailboxes.js @@ -1,16 +1,21 @@ -import { AddToArray, DeleteFromArrayBy, UpdateBy } from "#utils/db" +import { AddToArray, DeleteFromArrayBy, UpdateBy, FindBy } from "#utils/db" export async function EditMailbox(id, email, { name }) { await UpdateBy( + "users", { id: id, "mailboxes.email": email }, { "mailboxes.$.name": name }, ) } export async function CreateMailbox(id, { email, name }) { - await AddToArray({ id: id }, { mailboxes: { email, name } }) + await AddToArray("users", { id: id }, { mailboxes: { email, name } }) } export async function DeleteMailbox(id, email) { - await DeleteFromArrayBy({ id: id }, { mailboxes: { email: email } }) + await DeleteFromArrayBy("users", { id: id }, { mailboxes: { email: email } }) +} + +export async function GetMailboxByEmail(email) { + return await FindBy("users", { "mailboxes.email": email }) } diff --git a/src/services/users.js b/src/services/users.js index e69de29..b9b8bbb 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -0,0 +1,25 @@ +import { FindOrCreate, FindBy, DeleteFromArrayBy } from "#utils/db" +import logger from "#utils/logger" + +export async function FindOrCreateUser(id, name) { + return await FindOrCreate( + "users", + { id: id }, + { + id: id, + name: name, + mailboxes: [], + }, + ) +} + +export async function GetUserByID(id) { + return await FindBy("users", { id: id }) +} + +export async function DeleteUserByID(id) { + const res = await DeleteFromArrayBy("users", { id: id }) + + logger.warn("Deleted a User") + return res +} diff --git a/src/utils/db.js b/src/utils/db.js index 66fd52b..9e8c6d5 100644 --- a/src/utils/db.js +++ b/src/utils/db.js @@ -38,82 +38,53 @@ export function Connect() { // Mongo -export async function InsertUser(user) { +export async function FindBy(collectionName, query) { const db = await Connect() - const collection = db.collection("users") + const collection = db.collection(collectionName) - const result = await collection.insertOne(user) - - return result -} - -export async function GetUserByID(id) { - const db = await Connect() - - const collection = db.collection("users") - - return await collection.findOne({ id: id }) + return collection.findOne(query) } -export async function DeleteUserByID(id) { +export async function FindOrCreate(collectionName, query, data = {}) { const db = await Connect() - const collection = db.collection("users") - - const result = await collection.deleteOne({ id: id }) - - logger.warn("Deleted a User") - - return result -} - -export async function FindBy(query) { - const db = await Connect() + const collection = db.collection(collectionName) - const collection = db.collection("users") - - const result = await collection.findOne(query) - - return result + return collection.findOneAndUpdate( + query, + { + $setOnInsert: data, + }, + { + upsert: true, + returnDocument: "after", + }, + ) } -export async function AddToArray(query, update) { +export async function AddToArray(collectionName, query, update) { const db = await Connect() - const collection = db.collection("users") + const collection = db.collection(collectionName) - const result = await collection.updateOne( - query, - { $addToSet: update }, - { upsert: true }, - ) - - return result + return collection.updateOne(query, { $addToSet: update }) } -export async function DeleteFromArrayBy(query, update) { +export async function DeleteFromArrayBy(collectionName, query, update) { const db = await Connect() - const collection = db.collection("users") - - const result = await collection.updateOne(query, { $pull: update }) + const collection = db.collection(collectionName) - return result + return collection.updateOne(query, { $pull: update }) } -export async function UpdateBy(query, update) { +export async function UpdateBy(collectionName, query, update) { const db = await Connect() - const collection = db.collection("users") - - const result = await collection.updateOne( - query, - { $set: update }, - { upsert: true }, - ) + const collection = db.collection(collectionName) - return result + return collection.updateOne(query, { $set: update }) } // REDIS @@ -132,7 +103,7 @@ export async function GetFromCache(key) { } } -export async function WriteToCache(key, value, ttl = 3600) { +export async function WriteToCache(key, value, ttl = 900) { if (typeof value !== "string") { value = JSON.stringify(value) }