add proxy routes, webauthn bug fix
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
"fastify-zod": "^1.4.0",
|
||||
"lru-cache": "^11.0.2",
|
||||
"mongoose": "^8.9.0",
|
||||
"qs": "^6.14.0",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
70
pnpm-lock.yaml
generated
70
pnpm-lock.yaml
generated
@@ -53,6 +53,9 @@ importers:
|
||||
mongoose:
|
||||
specifier: ^8.9.0
|
||||
version: 8.9.0
|
||||
qs:
|
||||
specifier: ^6.14.0
|
||||
version: 6.14.0
|
||||
zod:
|
||||
specifier: ^3.24.1
|
||||
version: 3.24.1
|
||||
@@ -628,6 +631,10 @@ packages:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
call-bound@1.0.3:
|
||||
resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
camel-case@4.1.2:
|
||||
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
|
||||
|
||||
@@ -927,6 +934,10 @@ packages:
|
||||
no-case@3.0.4:
|
||||
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
||||
|
||||
object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
obliterator@2.0.4:
|
||||
resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==}
|
||||
|
||||
@@ -984,6 +995,10 @@ packages:
|
||||
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
qs@6.14.0:
|
||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
quick-format-unescaped@4.0.4:
|
||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||
|
||||
@@ -1033,6 +1048,22 @@ packages:
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel-map@1.0.1:
|
||||
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel-weakmap@1.0.2:
|
||||
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel@1.1.0:
|
||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
sift@17.1.3:
|
||||
resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==}
|
||||
|
||||
@@ -2225,6 +2256,11 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
call-bound@1.0.3:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
get-intrinsic: 1.2.7
|
||||
|
||||
camel-case@4.1.2:
|
||||
dependencies:
|
||||
pascal-case: 3.1.2
|
||||
@@ -2567,6 +2603,8 @@ snapshots:
|
||||
lower-case: 2.0.2
|
||||
tslib: 2.8.1
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
obliterator@2.0.4: {}
|
||||
|
||||
on-exit-leak-free@2.1.2: {}
|
||||
@@ -2633,6 +2671,10 @@ snapshots:
|
||||
|
||||
pvutils@1.1.3: {}
|
||||
|
||||
qs@6.14.0:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
||||
quick-format-unescaped@4.0.4: {}
|
||||
|
||||
real-require@0.2.0: {}
|
||||
@@ -2667,6 +2709,34 @@ snapshots:
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
|
||||
side-channel-map@1.0.1:
|
||||
dependencies:
|
||||
call-bound: 1.0.3
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.2.7
|
||||
object-inspect: 1.13.4
|
||||
|
||||
side-channel-weakmap@1.0.2:
|
||||
dependencies:
|
||||
call-bound: 1.0.3
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.2.7
|
||||
object-inspect: 1.13.4
|
||||
side-channel-map: 1.0.1
|
||||
|
||||
side-channel@1.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
side-channel-list: 1.0.0
|
||||
side-channel-map: 1.0.1
|
||||
side-channel-weakmap: 1.0.2
|
||||
|
||||
sift@17.1.3: {}
|
||||
|
||||
simple-oauth2@5.1.0:
|
||||
|
||||
39
src/mailProxy/mailProxy.route.ts
Normal file
39
src/mailProxy/mailProxy.route.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import { $mail, ProxyRequest } from "./mailProxy.schema";
|
||||
import { getOutlookTokens } from "./mailProxy.service";
|
||||
import axios from "axios";
|
||||
|
||||
export async function mailProxyRoutes(fastify: FastifyInstance) {
|
||||
fastify.post(
|
||||
"/",
|
||||
{
|
||||
schema: {
|
||||
body: $mail("proxyRequest"),
|
||||
},
|
||||
config: { requiredClaims: ["mail:all"] },
|
||||
preHandler: [fastify.authorize],
|
||||
},
|
||||
async (req, res) => {
|
||||
const input = req.body as ProxyRequest;
|
||||
|
||||
try {
|
||||
const tokens = await getOutlookTokens(input.email);
|
||||
if (!tokens) return res.code(404).send({ error: "resource not found" });
|
||||
|
||||
const result = await axios({
|
||||
url: input.url,
|
||||
method: input.method,
|
||||
headers: {
|
||||
Authorization: "Bearer " + tokens.access_token,
|
||||
},
|
||||
data: input.body,
|
||||
validateStatus: () => true,
|
||||
});
|
||||
|
||||
return res.code(result.status).send(result.data);
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
34
src/mailProxy/mailProxy.schema.ts
Normal file
34
src/mailProxy/mailProxy.schema.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { buildJsonSchemas } from "fastify-zod";
|
||||
import mongoose from "mongoose";
|
||||
import { z } from "zod";
|
||||
|
||||
export const mailModel = mongoose.model(
|
||||
"oauth",
|
||||
new mongoose.Schema({
|
||||
email: {
|
||||
type: String,
|
||||
unique: true,
|
||||
required: true,
|
||||
},
|
||||
access_token: String,
|
||||
expiry: Date,
|
||||
refresh_token: String,
|
||||
}),
|
||||
"oauth"
|
||||
);
|
||||
|
||||
const proxyRequest = z.object({
|
||||
email: z.string().email(),
|
||||
url: z.string(),
|
||||
method: z.enum(["GET", "POST", "PATCH", "DELETE"]),
|
||||
body: z.any(),
|
||||
});
|
||||
|
||||
export type ProxyRequest = z.infer<typeof proxyRequest>;
|
||||
|
||||
export const { schemas: mailSchemas, $ref: $mail } = buildJsonSchemas(
|
||||
{
|
||||
proxyRequest,
|
||||
},
|
||||
{ $id: "mail" }
|
||||
);
|
||||
48
src/mailProxy/mailProxy.service.ts
Normal file
48
src/mailProxy/mailProxy.service.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import qs from "qs";
|
||||
import axios from "axios";
|
||||
import { mailModel } from "./mailProxy.schema";
|
||||
|
||||
export async function getOutlookTokens(email: string) {
|
||||
let tokens = await mailModel.findOne({ email: email });
|
||||
|
||||
if (!tokens) {
|
||||
return null;
|
||||
} else {
|
||||
const date = new Date();
|
||||
const expiry = new Date(tokens.expiry);
|
||||
|
||||
if (expiry > date) {
|
||||
return tokens.access_token;
|
||||
} else {
|
||||
try {
|
||||
let res = await axios({
|
||||
url: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data: qs.stringify({
|
||||
client_id: process.env.MAIL_CLIENT_ID,
|
||||
scope: process.env.MAIL_SCOPE,
|
||||
refresh_token: tokens.refresh_token,
|
||||
redirect_uri: process.env.MAIL_REDIRECT_URI,
|
||||
grant_type: "refresh_token",
|
||||
client_secret: process.env.MAIL_CLIENT_SECRET,
|
||||
}),
|
||||
});
|
||||
|
||||
let expiresAt = new Date(Date.now() + res.data.expires_in * 1000);
|
||||
|
||||
await mailModel.findByIdAndUpdate(tokens._id, {
|
||||
expiry: expiresAt,
|
||||
refresh_token: res.data.refresh_token,
|
||||
access_token: res.data.access_token,
|
||||
});
|
||||
|
||||
return res.data.access_token;
|
||||
} catch (err) {
|
||||
throw new Error("error fetching tokens");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { notificationSchemas } from "./notification/notification.schema";
|
||||
import { noteSchemas } from "./note/note.schema";
|
||||
import { webAuthnRoutes } from "./webauthn/webauthn.route";
|
||||
import { configSchemas } from "./config/config.schema";
|
||||
import { mailSchemas } from "./mailProxy/mailProxy.schema";
|
||||
|
||||
const app = fastify({ logger: true, trustProxy: true });
|
||||
|
||||
@@ -49,6 +50,7 @@ for (const schema of [
|
||||
...notificationSchemas,
|
||||
...noteSchemas,
|
||||
...configSchemas,
|
||||
...mailSchemas,
|
||||
]) {
|
||||
app.addSchema(schema);
|
||||
}
|
||||
|
||||
@@ -23,4 +23,5 @@ export type Claim =
|
||||
| "notification:read"
|
||||
| "notification:write"
|
||||
| "config:read"
|
||||
| "config:write";
|
||||
| "config:write"
|
||||
| "mail:all";
|
||||
|
||||
@@ -28,6 +28,7 @@ export const rules: Record<
|
||||
"notification:write",
|
||||
"config:read",
|
||||
"config:write",
|
||||
"mail:all",
|
||||
],
|
||||
hiddenFields: {
|
||||
orgs: ["__v"],
|
||||
|
||||
@@ -154,7 +154,7 @@ export async function webAuthnRoutes(fastify: FastifyInstance) {
|
||||
{
|
||||
schema: {
|
||||
body: {
|
||||
type: "string",
|
||||
type: "object",
|
||||
properties: {
|
||||
email: { type: "string" },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user