fix: replace the authentication with more secure one
This commit is contained in:
parent
796b6a71f0
commit
15f309aa7a
15 changed files with 573 additions and 155 deletions
15
.env.example
15
.env.example
|
@ -5,6 +5,21 @@ PORT=3000
|
||||||
# If not set, the "attachments" directory in the script root will be used
|
# If not set, the "attachments" directory in the script root will be used
|
||||||
ATTACHMENTS_PATH=
|
ATTACHMENTS_PATH=
|
||||||
|
|
||||||
|
# MongoDB connection string
|
||||||
|
MONGODB_CONNSTRING=
|
||||||
|
|
||||||
|
# 256-bit secret key and 128-bit IV for password encryption
|
||||||
|
# You can generate the secret key using "openssl rand -base64 32" command
|
||||||
|
# You can generate the IV using "openssl rand -base64 16" command
|
||||||
|
ENCRYPTION_SECRETKEY=
|
||||||
|
ENCRYPTION_IV=
|
||||||
|
|
||||||
|
# JWT secret and cookie options
|
||||||
|
# You can generate the JWT secret using "openssl rand -base64 32" command
|
||||||
|
# If users access the webmail from HTTPS, set JWT_SECURECOOKIE to 1
|
||||||
|
JWT_SECRET=
|
||||||
|
JWT_SECURECOOKIE=
|
||||||
|
|
||||||
# Email receiving protocol and host parameters
|
# Email receiving protocol and host parameters
|
||||||
# The EMAIL_RECV_PROTOCOL can be "pop3" or "imap"
|
# The EMAIL_RECV_PROTOCOL can be "pop3" or "imap"
|
||||||
EMAIL_RECV_PROTOCOL=
|
EMAIL_RECV_PROTOCOL=
|
||||||
|
|
|
@ -2,17 +2,17 @@ import LoginForm from "@/layouts/LoginForm.jsx";
|
||||||
import Loading from "@/layouts/Loading.jsx";
|
import Loading from "@/layouts/Loading.jsx";
|
||||||
import MainLayout from "@/layouts/MainLayout.jsx";
|
import MainLayout from "@/layouts/MainLayout.jsx";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { load, checkAuth } from "@/slices/authSlice.js";
|
import { checkAuth } from "@/slices/authSlice.js";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const loading = useSelector((state) => state.auth.loading);
|
const loading = useSelector((state) => state.auth.loading);
|
||||||
const auth = useSelector((state) => state.auth.auth);
|
const email = useSelector((state) => state.auth.email);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
dispatch(load);
|
dispatch(checkAuth);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
|
@ -24,7 +24,7 @@ function App() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
} else if (auth === null) {
|
} else if (email === null) {
|
||||||
return <LoginForm />;
|
return <LoginForm />;
|
||||||
} else {
|
} else {
|
||||||
return <MainLayout />;
|
return <MainLayout />;
|
||||||
|
|
|
@ -20,9 +20,7 @@ import Content from "@/components/Content.jsx";
|
||||||
import { setView } from "@/slices/viewSlice.js";
|
import { setView } from "@/slices/viewSlice.js";
|
||||||
|
|
||||||
function LoginLayout() {
|
function LoginLayout() {
|
||||||
const email = useSelector((state) =>
|
const email = useSelector((state) => state.auth.email);
|
||||||
state.auth.auth ? state.auth.auth.email : "Unknown"
|
|
||||||
);
|
|
||||||
const menuShown = useSelector((state) => state.menu.shown);
|
const menuShown = useSelector((state) => state.menu.shown);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -135,7 +133,7 @@ function LoginLayout() {
|
||||||
href="#"
|
href="#"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dispatch(logout());
|
dispatch(logout);
|
||||||
}}
|
}}
|
||||||
className="inline-block text-inherit w-8 h-8 py-1 mx-0.5 align-middle rounded-sm hover:bg-primary-foreground/30 hover:text-primary-foreground transition-colors"
|
className="inline-block text-inherit w-8 h-8 py-1 mx-0.5 align-middle rounded-sm hover:bg-primary-foreground/30 hover:text-primary-foreground transition-colors"
|
||||||
>
|
>
|
||||||
|
|
|
@ -5,111 +5,49 @@ export const authSlice = createSlice({
|
||||||
initialState: {
|
initialState: {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null,
|
error: null,
|
||||||
auth: null
|
email: null
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
load: (state, action) => {
|
|
||||||
if (state.loading) state.loading = false;
|
|
||||||
if (action.payload && action.payload.error !== undefined)
|
|
||||||
state.error = action.payload.error;
|
|
||||||
if (action.payload && action.payload.auth !== undefined) {
|
|
||||||
state.auth = action.payload.auth;
|
|
||||||
if (!localStorage.getItem("credentials"))
|
|
||||||
localStorage.setItem(
|
|
||||||
"credentials",
|
|
||||||
btoa(JSON.stringify(action.payload.auth))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
login: (state, action) => {
|
login: (state, action) => {
|
||||||
if (state.loading) state.loading = false;
|
if (state.loading) state.loading = false;
|
||||||
if (action.payload && action.payload.error !== undefined)
|
if (action.payload && action.payload.error !== undefined)
|
||||||
state.error = action.payload.error;
|
state.error = action.payload.error;
|
||||||
if (action.payload && action.payload.auth !== undefined) {
|
if (action.payload && action.payload.email !== undefined) {
|
||||||
state.auth = action.payload.auth;
|
state.email = action.payload.email;
|
||||||
localStorage.setItem(
|
|
||||||
"credentials",
|
|
||||||
btoa(JSON.stringify(action.payload.auth))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
logout: (state) => {
|
logout: (state) => {
|
||||||
if (state.loading) state.loading = false;
|
if (state.loading) state.loading = false;
|
||||||
localStorage.removeItem("credentials");
|
state.email = null;
|
||||||
state.auth = null;
|
|
||||||
},
|
},
|
||||||
verificationFailed: (state) => {
|
verificationFailed: (state) => {
|
||||||
if (state.loading) state.loading = false;
|
if (state.loading) state.loading = false;
|
||||||
state.error = null;
|
state.error = null;
|
||||||
state.auth = null;
|
state.email = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { logout, verificationFailed } = authSlice.actions;
|
export const { verificationFailed } = authSlice.actions;
|
||||||
|
|
||||||
export async function load(dispatch) {
|
|
||||||
const state = {};
|
|
||||||
let credentials = {};
|
|
||||||
try {
|
|
||||||
credentials = JSON.parse(atob(localStorage.getItem("credentials")));
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
} catch (err) {
|
|
||||||
// Use empty credentials
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/check", {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization:
|
|
||||||
credentials.email && credentials.password
|
|
||||||
? "BasicMERNMail " +
|
|
||||||
btoa(
|
|
||||||
credentials.email.replace(/:/g, "") + ":" + credentials.password
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (res.status == 200) {
|
|
||||||
state.auth =
|
|
||||||
credentials.email && credentials.password
|
|
||||||
? { email: credentials.email, password: credentials.password }
|
|
||||||
: {};
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
state.error = err.message;
|
|
||||||
}
|
|
||||||
dispatch(authSlice.actions.load(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function login(email, password) {
|
export function login(email, password) {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
const state = {};
|
const state = {};
|
||||||
let credentials = {
|
|
||||||
email: email,
|
|
||||||
password: password
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/check", {
|
const res = await fetch("/api/login", {
|
||||||
method: "GET",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization:
|
"Content-Type": "application/json"
|
||||||
credentials.email && credentials.password
|
},
|
||||||
? "BasicMERNMail " +
|
body: JSON.stringify({
|
||||||
btoa(
|
email: email,
|
||||||
credentials.email.replace(/:/g, "") +
|
password: password
|
||||||
":" +
|
}),
|
||||||
credentials.password
|
credentials: "include"
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
state.auth =
|
state.email = email;
|
||||||
credentials.email && credentials.password
|
|
||||||
? { email: credentials.email, password: credentials.password }
|
|
||||||
: {};
|
|
||||||
state.error = null;
|
state.error = null;
|
||||||
} else {
|
} else {
|
||||||
state.error = data.message;
|
state.error = data.message;
|
||||||
|
@ -121,38 +59,38 @@ export function login(email, password) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkAuth(dispatch, getState) {
|
export async function checkAuth(dispatch) {
|
||||||
const state = {};
|
const state = {};
|
||||||
let credentials = getState().auth.auth;
|
|
||||||
if (credentials === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/check", {
|
const res = await fetch("/api/check", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
credentials: "include"
|
||||||
Authorization:
|
|
||||||
credentials.email && credentials.password
|
|
||||||
? "BasicMERNMail " +
|
|
||||||
btoa(
|
|
||||||
credentials.email.replace(/:/g, "") + ":" + credentials.password
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
state.auth =
|
const data = await res.json();
|
||||||
credentials.email && credentials.password
|
state.email = data.email;
|
||||||
? { email: credentials.email, password: credentials.password }
|
|
||||||
: {};
|
|
||||||
} else {
|
|
||||||
state.auth = null;
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
state.error = err.message;
|
||||||
|
}
|
||||||
|
dispatch(authSlice.actions.login(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logout(dispatch) {
|
||||||
|
try {
|
||||||
|
await fetch("/api/logout", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
credentials: "include"
|
||||||
|
});
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Don't display the message
|
// Logout failed
|
||||||
}
|
}
|
||||||
dispatch(authSlice.actions.load(state));
|
dispatch(authSlice.actions.logout());
|
||||||
}
|
}
|
||||||
|
|
||||||
export default authSlice.reducer;
|
export default authSlice.reducer;
|
||||||
|
|
|
@ -75,22 +75,14 @@ export const { initCurrentMailbox, setCurrentMailboxFromURL } =
|
||||||
|
|
||||||
export async function setMailboxes(dispatch, getState) {
|
export async function setMailboxes(dispatch, getState) {
|
||||||
const state = {};
|
const state = {};
|
||||||
let credentials = getState().auth.auth;
|
let email = getState().auth.email;
|
||||||
if (credentials === null) {
|
if (email === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/receive/mailboxes", {
|
const res = await fetch("/api/receive/mailboxes", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
credentials: "include"
|
||||||
Authorization:
|
|
||||||
credentials.email && credentials.password
|
|
||||||
? "BasicMERNMail " +
|
|
||||||
btoa(
|
|
||||||
credentials.email.replace(/:/g, "") + ":" + credentials.password
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
|
|
|
@ -29,8 +29,8 @@ export const { resetLoading } = messagesSlice.actions;
|
||||||
export function setMessages(signal) {
|
export function setMessages(signal) {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = {};
|
const state = {};
|
||||||
let credentials = getState().auth.auth;
|
let email = getState().auth.email;
|
||||||
if (credentials === null) {
|
if (email === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let currentMailbox = getState().mailboxes.currentMailbox;
|
let currentMailbox = getState().mailboxes.currentMailbox;
|
||||||
|
@ -45,17 +45,7 @@ export function setMessages(signal) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/receive/mailbox/${currentMailbox}`, {
|
const res = await fetch(`/api/receive/mailbox/${currentMailbox}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
credentials: "include",
|
||||||
Authorization:
|
|
||||||
credentials.email && credentials.password
|
|
||||||
? "BasicMERNMail " +
|
|
||||||
btoa(
|
|
||||||
credentials.email.replace(/:/g, "") +
|
|
||||||
":" +
|
|
||||||
credentials.password
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
},
|
|
||||||
signal: signal
|
signal: signal
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
323
package-lock.json
generated
323
package-lock.json
generated
|
@ -9,10 +9,14 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.3",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"imap-node": "^0.9.9",
|
"imap-node": "^0.9.9",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mailparser": "^3.7.1",
|
"mailparser": "^3.7.1",
|
||||||
|
"mongoose": "^8.7.1",
|
||||||
"node-pop3": "^0.9.0",
|
"node-pop3": "^0.9.0",
|
||||||
"nodemailer": "^6.9.15",
|
"nodemailer": "^6.9.15",
|
||||||
"serve-static": "^1.16.2"
|
"serve-static": "^1.16.2"
|
||||||
|
@ -862,6 +866,14 @@
|
||||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
|
"version": "1.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
|
||||||
|
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
|
||||||
|
"dependencies": {
|
||||||
|
"sparse-bitfield": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@napi-rs/nice": {
|
"node_modules/@napi-rs/nice": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz",
|
||||||
|
@ -1238,6 +1250,19 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/webidl-conversions": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/whatwg-url": {
|
||||||
|
"version": "11.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||||
|
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/webidl-conversions": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
|
@ -1643,6 +1668,14 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bson": {
|
||||||
|
"version": "6.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz",
|
||||||
|
"integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/buffer": {
|
"node_modules/buffer": {
|
||||||
"version": "5.7.1",
|
"version": "5.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||||
|
@ -1667,6 +1700,11 @@
|
||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.1.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-equal-constant-time": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
|
@ -2173,6 +2211,26 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie-parser": {
|
||||||
|
"version": "1.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||||
|
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.7.2",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-parser/node_modules/cookie": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cookie-signature": {
|
"node_modules/cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
@ -2542,6 +2600,14 @@
|
||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
@ -4294,6 +4360,59 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonwebtoken": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"jws": "^3.2.2",
|
||||||
|
"lodash.includes": "^4.3.0",
|
||||||
|
"lodash.isboolean": "^3.0.3",
|
||||||
|
"lodash.isinteger": "^4.0.4",
|
||||||
|
"lodash.isnumber": "^3.0.3",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.isstring": "^4.0.1",
|
||||||
|
"lodash.once": "^4.0.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"semver": "^7.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsonwebtoken/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
|
},
|
||||||
|
"node_modules/jwa": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-equal-constant-time": "1.0.1",
|
||||||
|
"ecdsa-sig-formatter": "1.0.11",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jws": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||||
|
"dependencies": {
|
||||||
|
"jwa": "^1.4.1",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/kareem": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
|
@ -4769,11 +4888,35 @@
|
||||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.includes": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isboolean": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isinteger": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isnumber": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||||
|
},
|
||||||
"node_modules/lodash.isplainobject": {
|
"node_modules/lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||||
"dev": true
|
},
|
||||||
|
"node_modules/lodash.isstring": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.kebabcase": {
|
"node_modules/lodash.kebabcase": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -4799,6 +4942,11 @@
|
||||||
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
|
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.once": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||||
|
},
|
||||||
"node_modules/lodash.snakecase": {
|
"node_modules/lodash.snakecase": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
||||||
|
@ -5090,6 +5238,11 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/memory-pager": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
|
||||||
|
},
|
||||||
"node_modules/meow": {
|
"node_modules/meow": {
|
||||||
"version": "12.1.1",
|
"version": "12.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
|
||||||
|
@ -5233,6 +5386,126 @@
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mongodb": {
|
||||||
|
"version": "6.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz",
|
||||||
|
"integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@mongodb-js/saslprep": "^1.1.5",
|
||||||
|
"bson": "^6.7.0",
|
||||||
|
"mongodb-connection-string-url": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@aws-sdk/credential-providers": "^3.188.0",
|
||||||
|
"@mongodb-js/zstd": "^1.1.0",
|
||||||
|
"gcp-metadata": "^5.2.0",
|
||||||
|
"kerberos": "^2.0.1",
|
||||||
|
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||||
|
"snappy": "^7.2.2",
|
||||||
|
"socks": "^2.7.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@aws-sdk/credential-providers": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@mongodb-js/zstd": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"gcp-metadata": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"kerberos": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"mongodb-client-encryption": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"snappy": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"socks": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/whatwg-url": "^11.0.2",
|
||||||
|
"whatwg-url": "^13.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongoose": {
|
||||||
|
"version": "8.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.1.tgz",
|
||||||
|
"integrity": "sha512-RpNMyhyzLVCVbf8xTVbrf/18G3MqQzNw5pJdvOJ60fzbCa3cOZzz9L+8XpqzBXtRlgZGWv0T7MmOtvrT8ocp1Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"bson": "^6.7.0",
|
||||||
|
"kareem": "2.6.3",
|
||||||
|
"mongodb": "6.9.0",
|
||||||
|
"mpath": "0.9.0",
|
||||||
|
"mquery": "5.0.0",
|
||||||
|
"ms": "2.1.3",
|
||||||
|
"sift": "17.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mongoose"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongoose/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
|
},
|
||||||
|
"node_modules/mpath": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mquery": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mquery/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mquery/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
@ -5824,7 +6097,6 @@
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
@ -6158,7 +6430,6 @@
|
||||||
"version": "7.6.3",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
},
|
},
|
||||||
|
@ -6311,6 +6582,11 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sift": {
|
||||||
|
"version": "17.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
|
||||||
|
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
|
||||||
|
},
|
||||||
"node_modules/signal-exit": {
|
"node_modules/signal-exit": {
|
||||||
"version": "3.0.7",
|
"version": "3.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
|
@ -6411,6 +6687,14 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sparse-bitfield": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"memory-pager": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/split2": {
|
"node_modules/split2": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
@ -6694,6 +6978,17 @@
|
||||||
"nodetouch": "bin/nodetouch.js"
|
"nodetouch": "bin/nodetouch.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tree-kill": {
|
"node_modules/tree-kill": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||||
|
@ -6872,6 +7167,26 @@
|
||||||
"defaults": "^1.0.3"
|
"defaults": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "13.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
|
||||||
|
"integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "^4.1.1",
|
||||||
|
"webidl-conversions": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||||
|
|
|
@ -20,10 +20,14 @@
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.3",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"imap-node": "^0.9.9",
|
"imap-node": "^0.9.9",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mailparser": "^3.7.1",
|
"mailparser": "^3.7.1",
|
||||||
|
"mongoose": "^8.7.1",
|
||||||
"node-pop3": "^0.9.0",
|
"node-pop3": "^0.9.0",
|
||||||
"nodemailer": "^6.9.15",
|
"nodemailer": "^6.9.15",
|
||||||
"serve-static": "^1.16.2"
|
"serve-static": "^1.16.2"
|
||||||
|
|
|
@ -13,14 +13,31 @@ if (!process.env.ATTACHMENTS_PATH) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
|
const mongoose = require("mongoose");
|
||||||
const serveStatic = require("serve-static");
|
const serveStatic = require("serve-static");
|
||||||
const authAndInitReceiveMiddleware = require("./middleware/authAndInitReceive.js");
|
const authAndInitReceiveMiddleware = require("./middleware/authAndInitReceive.js");
|
||||||
const checkRoute = require("./routes/check.js");
|
const checkRoute = require("./routes/check.js");
|
||||||
|
const loginRoute = require("./routes/login.js");
|
||||||
|
const logoutRoute = require("./routes/logout.js");
|
||||||
const receiveRoute = require("./routes/receive.js");
|
const receiveRoute = require("./routes/receive.js");
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
const cookieParser = require("cookie-parser");
|
||||||
|
|
||||||
|
mongoose
|
||||||
|
.connect(process.env.MONGODB_CONNSTRING)
|
||||||
|
.then(() => console.log(`Database connected successfully`));
|
||||||
|
|
||||||
|
// Since mongoose's Promise is deprecated, we override it with Node's Promise
|
||||||
|
mongoose.Promise = global.Promise;
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
|
app.use("/api", bodyParser.json());
|
||||||
|
app.use("/api/login", loginRoute);
|
||||||
app.use("/api", authAndInitReceiveMiddleware);
|
app.use("/api", authAndInitReceiveMiddleware);
|
||||||
|
app.use("/api/logout", logoutRoute);
|
||||||
app.use("/api/check", checkRoute);
|
app.use("/api/check", checkRoute);
|
||||||
app.use("/api/receive", receiveRoute);
|
app.use("/api/receive", receiveRoute);
|
||||||
app.use("/api", (req, res, next) => {
|
app.use("/api", (req, res, next) => {
|
||||||
|
|
|
@ -1,23 +1,41 @@
|
||||||
const initReceiveDriver = require("../utils/initReceiveDriver.js");
|
const initReceiveDriver = require("../utils/initReceiveDriver.js");
|
||||||
|
const { decryptPassword } = require("../utils/passwordCrypto.js");
|
||||||
|
const userModel = require("../models/user.js");
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
|
||||||
module.exports = function authAndInitReceiveMiddleware(req, res, next) {
|
module.exports = function authAndInitReceiveMiddleware(req, res, next) {
|
||||||
// Custom implementation of HTTP Basic authentication
|
jwt.verify(req.cookies.token, process.env.JWT_SECRET, (err, decoded) => {
|
||||||
const deny = (message) => {
|
if (err) {
|
||||||
res.set("WWW-Authenticate", "BasicMERNMail");
|
res.status(401).json({ message: err.message });
|
||||||
res.status(401).json({ message: message || "Authentication required." });
|
return;
|
||||||
};
|
}
|
||||||
|
const email = decoded.email;
|
||||||
const authTPair = (req.headers.authorization || "").split(" ");
|
userModel
|
||||||
if (authTPair[0] != "Basic" && authTPair[0] != "BasicMERNMail") {
|
.findOne({ email: email })
|
||||||
deny();
|
.then((result) => {
|
||||||
return;
|
if (!result) {
|
||||||
}
|
res.status(401).json({ message: "User not found" });
|
||||||
const b64auth = authTPair[1] || "";
|
} else {
|
||||||
const authPair = Buffer.from(b64auth, "base64").toString().split(":");
|
const password = decryptPassword(result.encryptedPassword);
|
||||||
const email = authPair[0];
|
initReceiveDriver(email, password, (err, driver) => {
|
||||||
const password = authPair[1];
|
if (err) {
|
||||||
|
res.status(401).json({ message: err.message });
|
||||||
if (email && password) {
|
} else {
|
||||||
|
req.credentials = {
|
||||||
|
email: email,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
req.receiveDriver = driver;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
res.status(500).json({ message: err.message });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/*if (email && password) {
|
||||||
initReceiveDriver(email, password, (err, driver) => {
|
initReceiveDriver(email, password, (err, driver) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
deny(err.message);
|
deny(err.message);
|
||||||
|
@ -32,5 +50,5 @@ module.exports = function authAndInitReceiveMiddleware(req, res, next) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
deny();
|
deny();
|
||||||
}
|
}*/
|
||||||
};
|
};
|
||||||
|
|
18
src/models/user.js
Normal file
18
src/models/user.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// models/User.js
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const userSchema = new mongoose.Schema({
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
encryptedPassword: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const User = mongoose.model("users", userSchema);
|
||||||
|
|
||||||
|
module.exports = User;
|
|
@ -3,7 +3,7 @@ const router = express.Router();
|
||||||
|
|
||||||
router.get("/", (req, res) => {
|
router.get("/", (req, res) => {
|
||||||
req.receiveDriver.close();
|
req.receiveDriver.close();
|
||||||
res.json({ message: "OK!" });
|
res.json({ email: req.credentials.email });
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
76
src/routes/login.js
Normal file
76
src/routes/login.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
const express = require("express");
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
const userModel = require("../models/user.js");
|
||||||
|
const initReceiveDriver = require("../utils/initReceiveDriver.js");
|
||||||
|
const { encryptPassword } = require("../utils/passwordCrypto.js");
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post("/", (req, res) => {
|
||||||
|
if (!req.body || !req.body.email || !req.body.password)
|
||||||
|
res.status(400).json({ message: "Email and password are required" });
|
||||||
|
initReceiveDriver(
|
||||||
|
String(req.body.email),
|
||||||
|
String(req.body.password),
|
||||||
|
(err, driver) => {
|
||||||
|
if (err) {
|
||||||
|
res.status(401).json({ message: err.message });
|
||||||
|
} else {
|
||||||
|
driver.close();
|
||||||
|
userModel
|
||||||
|
.findOne({ email: req.body.email })
|
||||||
|
.then((result) => {
|
||||||
|
const createCallback = () => {
|
||||||
|
jwt.sign(
|
||||||
|
{ email: req.body.email },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
(err, token) => {
|
||||||
|
if (err) {
|
||||||
|
res.status(401).json({ message: err.message });
|
||||||
|
} else {
|
||||||
|
res.cookie("token", token, {
|
||||||
|
maxAge: 315360000000,
|
||||||
|
httpOnly: true,
|
||||||
|
secure: parseInt(process.env.JWT_SECURECOOKIE) > 0,
|
||||||
|
sameSite: "strict"
|
||||||
|
});
|
||||||
|
res.json({ message: "Logged in successfully" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const encryptedPassword = encryptPassword(req.body.password);
|
||||||
|
if (!result) {
|
||||||
|
userModel
|
||||||
|
.create({
|
||||||
|
email: req.body.email,
|
||||||
|
encryptedPassword: encryptedPassword
|
||||||
|
})
|
||||||
|
.then(createCallback)
|
||||||
|
.catch((err) => {
|
||||||
|
res.status(500).json({ message: err.message });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
userModel
|
||||||
|
.replaceOne(
|
||||||
|
{ email: req.body.email },
|
||||||
|
{
|
||||||
|
email: req.body.email,
|
||||||
|
encryptedPassword: encryptedPassword
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(createCallback)
|
||||||
|
.catch((err) => {
|
||||||
|
res.status(500).json({ message: err.message });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
res.status(500).json({ message: err.message });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
11
src/routes/logout.js
Normal file
11
src/routes/logout.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const express = require("express");
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// POST method to prevent logout CSRF
|
||||||
|
router.post("/", (req, res) => {
|
||||||
|
req.receiveDriver.close();
|
||||||
|
res.clearCookie("token");
|
||||||
|
res.json({ message: "Logged out successfully" });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
26
src/utils/passwordCrypto.js
Normal file
26
src/utils/passwordCrypto.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
// Decode the base64 strings to buffers
|
||||||
|
const secretKey = Buffer.from(process.env.ENCRYPTION_SECRETKEY, "base64");
|
||||||
|
const iv = Buffer.from(process.env.ENCRYPTION_IV, "base64");
|
||||||
|
|
||||||
|
// Encrypt the password
|
||||||
|
function encryptPassword(password) {
|
||||||
|
const cipher = crypto.createCipheriv("aes-256-cbc", secretKey, iv);
|
||||||
|
let encrypted = cipher.update(password, "utf8", "hex");
|
||||||
|
encrypted += cipher.final("hex");
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the password
|
||||||
|
function decryptPassword(encryptedPassword) {
|
||||||
|
const decipher = crypto.createDecipheriv("aes-256-cbc", secretKey, iv);
|
||||||
|
let decrypted = decipher.update(encryptedPassword, "hex", "utf8");
|
||||||
|
decrypted += decipher.final("utf8");
|
||||||
|
return decrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
encryptPassword: encryptPassword,
|
||||||
|
decryptPassword: decryptPassword
|
||||||
|
};
|
Loading…
Reference in a new issue