Skip to content
Merged
4 changes: 2 additions & 2 deletions src/router.js
Original file line number Diff line number Diff line change
@@ -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()

Expand Down Expand Up @@ -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()
Expand Down
10 changes: 4 additions & 6 deletions src/routes/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -57,17 +55,17 @@ 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")
}
}

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")
Expand All @@ -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")
Expand Down
2 changes: 0 additions & 2 deletions src/routes/index.js
Original file line number Diff line number Diff line change
@@ -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()

Expand Down
5 changes: 3 additions & 2 deletions src/routes/oauth-application.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)
Expand All @@ -36,8 +39,6 @@ passport.use(
passport.serializeUser((user, done) => {
done(null, {
id: user.id,
displayName: user.displayName,
emails: user.emails,
})
})

Expand Down
32 changes: 19 additions & 13 deletions src/routes/oauth-mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand All @@ -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) {
Expand Down Expand Up @@ -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}`)

Expand Down
11 changes: 8 additions & 3 deletions src/services/mailboxes.js
Original file line number Diff line number Diff line change
@@ -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 })
}
25 changes: 25 additions & 0 deletions src/services/users.js
Original file line number Diff line number Diff line change
@@ -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
}
79 changes: 25 additions & 54 deletions src/utils/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
Loading