Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
import "./.next/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"prebuild": "node ./scripts/check-envs.js",
"dev": "next dev -p 3001",
"dev:tw": "TAILWIND_MODE=watch tailwindcss -o src/tailwind.css --watch",
"build": "node ./scripts/check-envs.js && next build",
"build": "node ./scripts/check-envs.js && next build --webpack",
"start": "next start",
"prod": "next export",
"cypress:open": "cypress open",
Expand Down Expand Up @@ -55,7 +55,7 @@
"memory-cache": "^0.2.0",
"moment": "^2.29.1",
"music-metadata": "^11.2.3",
"next": "15.0.8",
"next": "16.1.5",
"next-csrf": "^0.2.1",
"next-share": "^0.18.1",
"pdf-lib": "^1.17.1",
Expand Down Expand Up @@ -114,7 +114,8 @@
"nyc": "^17.1.0",
"form-data": "^4.0.4",
"tar": "^7.5.7",
"jspdf": "^4.1.0"
"jspdf": "^4.1.0",
"qs": "^6.14.1"
},
"engines": {
"node": ">=22.0.0"
Expand Down
5 changes: 4 additions & 1 deletion src/lib/csrf.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { nextCsrf } from 'next-csrf';

const { csrf, setup } = nextCsrf({
const { csrf: originalCsrf, setup: originalSetup } = nextCsrf({
// eslint-disable-next-line no-undef
secret: process.env.CSRF_SECRET,
ignoredMethods: ['OPTIONS'],
});

const setup = originalSetup as unknown as (handler: any) => any;
const csrf = originalCsrf as unknown as (handler: any) => any;

export { csrf, setup };
2 changes: 1 addition & 1 deletion src/pages/api/collect/sheet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';

import rateLimitMiddleware from '../temp-mail/rate-limiter';
import rateLimitMiddleware from '../../../utils/rate-limiter';
import { encode } from 'querystring';

interface SheetPayload {
Expand Down
46 changes: 24 additions & 22 deletions src/pages/api/dark-web-monitor/breaches.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,55 @@
import { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
import { HaveIbeenPwnedText } from '@/assets/types/have-i-been-pawned';
const CACHE_CLEAN_INTERVAL_MS = 2 * 60 * 60 * 1000;
interface BreachesProps {
textContent: HaveIbeenPwnedText['HeroSection']['breaches'];
}

const CACHE_TTL_MS = 2 * 60 * 60 * 1000;

const API_URL = process.env.INXT_MONITOR_API_URL;
const API_KEY = process.env.INXT_MONITOR_API_KEY;

const cache: Map<string, any> = new Map();
const cache: Map<string, { data: any; timestamp: number }> = new Map();

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
textContent: BreachesProps['textContent'],
): Promise<void> {
export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
if (req.method !== 'GET') {
return res.status(405).json({ error: textContent.error405 });
return res.status(405).json({ error: 'Method not allowed' });
}

setInterval(() => {
cache.clear();
}, CACHE_CLEAN_INTERVAL_MS);

const { email } = req.query;

if (!email || typeof email !== 'string') {
return res.status(400).json({ error: textContent.error400 });
return res.status(400).json({ error: 'Email is required' });
}

try {
if (cache.has(email)) {
return res.status(200).json(cache.get(email));
const cachedEntry = cache.get(email);
const now = Date.now();

if (cachedEntry && now - cachedEntry.timestamp < CACHE_TTL_MS) {
return res.status(200).json(cachedEntry.data);
}

if (cachedEntry) {
cache.delete(email);
}

const url = `${API_URL}/breachedaccount/${encodeURIComponent(email)}?truncateResponse=false`;
const headers = {
'hibp-api-key': API_KEY,
'user-agent': 'NextJS-App',
};

const response = await axios.get(url, { headers });

cache.set(email, response.data);
cache.set(email, { data: response.data, timestamp: Date.now() });

return res.status(200).json(response.data);
} catch (err: any) {
if (err.response?.status === 404) {
return res.status(200).json({ breaches: [] });
const cleanData = [];
cache.set(email, { data: cleanData, timestamp: Date.now() });
return res.status(200).json(cleanData);
}
return res.status(500).json({ error: err.response?.data });

console.error('HIBP Error:', err.message);
return res.status(500).json({ error: 'External API Error' });
}
}
53 changes: 27 additions & 26 deletions src/pages/api/dark-web-monitor/pastes.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,53 @@
import { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
import { HaveIbeenPwnedText } from '@/assets/types/have-i-been-pawned';
const CACHE_CLEAN_INTERVAL_MS = 2 * 60 * 60 * 1000;
interface BreachesProps {
textContent: HaveIbeenPwnedText['HeroSection']['breaches'];
}

const CACHE_TTL_MS = 2 * 60 * 60 * 1000;
const API_URL = process.env.INXT_MONITOR_API_URL;
const API_KEY = process.env.INXT_MONITOR_API_KEY;

const cache: Map<string, any> = new Map();
const cache: Map<string, { data: any; timestamp: number }> = new Map();

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
textContent: BreachesProps['textContent'],
): Promise<void> {
export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
if (req.method !== 'GET') {
return res.status(405).json({ error: textContent.error405 });
return res.status(405).json({ error: 'Method not allowed' });
}

setInterval(() => {
cache.clear();
}, CACHE_CLEAN_INTERVAL_MS);

const { email } = req.query;

if (!email || typeof email !== 'string') {
return res.status(400).json({ error: textContent.error400 });
return res.status(400).json({ error: 'Email is required' });
}

try {
if (cache.has(email)) {
return res.status(200).json(cache.get(email));
}
const cachedEntry = cache.get(email);
const now = Date.now();

if (cachedEntry && now - cachedEntry.timestamp < CACHE_TTL_MS) {
return res.status(200).json(cachedEntry.data);
}

const url = `${API_URL}/pasteaccount/${email}`;
if (cachedEntry) {
cache.delete(email);
}

try {
const url = `${API_URL}/pasteaccount/${encodeURIComponent(email)}`;
const headers = {
'hibp-api-key': API_KEY,
'user-agent': 'NextJS-App',
};

const response = await axios.get(url, { headers });
cache.set(email, response.data);
res.status(200).json(response.data);

cache.set(email, { data: response.data, timestamp: Date.now() });

return res.status(200).json(response.data);
} catch (err: any) {
if (err.response?.status === 404) {
return res.status(200).json({ pastes: [] });
const emptyData = { pastes: [] };
cache.set(email, { data: emptyData, timestamp: Date.now() });
return res.status(200).json(emptyData);
}
res.status(500).json({ error: err.response?.data });

return res.status(500).json({ error: err.response?.data || 'Internal Server Error' });
}
}
10 changes: 4 additions & 6 deletions src/pages/api/temp-mail/create-email.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios, { AxiosError } from 'axios';
import { NextApiRequest, NextApiResponse } from 'next';
import rateLimitMiddleware from './rate-limiter';
import rateLimitMiddleware from '../../../utils/rate-limiter';
import { csrf } from '@/lib/csrf';

const CONVERTER_URL =
Expand All @@ -10,12 +10,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') return res.status(405).json({ message: 'Method not allowed' });

try {
const email = await axios.get(`${CONVERTER_URL}/api/temp-mail/address`);

return res.status(200).json(email.data);
const response = await axios.get(`${CONVERTER_URL}/api/temp-mail/address`);
return res.status(200).json(response.data);
} catch (err) {
const error = err as Error | AxiosError;
console.log('ERROR:', error.message || JSON.stringify(error, null, 2));
const error = err as AxiosError;
return res.status(500).json({ message: error.message });
}
}
Expand Down
29 changes: 17 additions & 12 deletions src/pages/api/temp-mail/get-inbox.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextApiRequest, NextApiResponse } from 'next';
import rateLimitMiddleware from './rate-limiter';
import axios from 'axios';
import axios, { AxiosError } from 'axios';
import rateLimitMiddleware from '@/utils/rate-limiter';
import { csrf } from '@/lib/csrf';

const CONVERTER_URL =
Expand All @@ -10,20 +10,25 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') return res.status(405).json({ message: 'Method not allowed' });

const { email, token } = req.query;

if (!email || typeof email !== 'string' || !token || typeof token !== 'string') {
return res.status(400).json({ message: 'Invalid parameters' });
}

try {
const inbox = await axios.get(`${CONVERTER_URL}/api/temp-mail/messages/${email}/${token}`);
const safeEmail = encodeURIComponent(email);
const safeToken = encodeURIComponent(token);

return res.status(200).json(inbox.data.mails);
const response = await axios.get(`${CONVERTER_URL}/api/temp-mail/messages/${safeEmail}/${safeToken}`);
return res.status(200).json(response.data.mails);
} catch (err) {
const error = err as Error;
if (error.message.includes('404')) {
return res.status(404).json({
message: error.message,
});
const error = err as AxiosError;

if (error.response?.status === 404) {
return res.status(404).json({ message: 'Inbox not found' });
}
return res.status(500).json({
message: error.message,
});

return res.status(500).json({ message: 'Internal Server Error' });
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/api/temp-mail/get-message.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextApiRequest, NextApiResponse } from 'next';
import rateLimitMiddleware from './rate-limiter';
import rateLimitMiddleware from '../../../utils/rate-limiter';
import axios from 'axios';
import { csrf } from '@/lib/csrf';

Expand Down
41 changes: 0 additions & 41 deletions src/pages/api/temp-mail/rate-limiter.ts

This file was deleted.

38 changes: 24 additions & 14 deletions src/pages/temporary-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,9 @@ import { sm_faq, sm_breadcrumb } from '@/components/utils/schema-markup-generato
import { ActionBanner } from '@/components/temp-email/components/ActionBanner';
import { GlobalDialog, useGlobalDialog } from '@/contexts/GlobalUIManager';
import { setup } from '@/lib/csrf';
import { useRouter } from 'next/router';

const TempEmail = () => {
const TempEmail = ({ lang, metatags, textContent, footerLang, navbarLang, toolsContent, bannerLang }: any) => {
const dialogAction = useGlobalDialog();
const { locale: lang } = useRouter() as { locale: string };

const metatagsDescriptions = require(`@/assets/lang/${lang}/metatags-descriptions.json`);
const textContent = require(`@/assets/lang/${lang}/temporary-email.json`);
const footerLang = require(`@/assets/lang/${lang}/footer.json`);
const navbarLang = require(`@/assets/lang/${lang}/navbar.json`);
const toolsContent = require(`@/assets/lang/${lang}/components/tools/ToolSection.json`);
const bannerLang = require(`@/assets/lang/${lang}/banners.json`);

const metatags = metatagsDescriptions.filter((desc) => desc.id === 'temporary-email');

return (
<>
Expand Down Expand Up @@ -61,8 +50,29 @@ const TempEmail = () => {
);
};

export const getServerSideProps = setup(async () => {
return { props: {} };
export const getServerSideProps = setup(async (ctx: any) => {
const lang = ctx.locale || 'en';

const metatagsDescriptions = require(`@/assets/lang/${lang}/metatags-descriptions.json`);
const textContent = require(`@/assets/lang/${lang}/temporary-email.json`);
const footerLang = require(`@/assets/lang/${lang}/footer.json`);
const navbarLang = require(`@/assets/lang/${lang}/navbar.json`);
const toolsContent = require(`@/assets/lang/${lang}/components/tools/ToolSection.json`);
const bannerLang = require(`@/assets/lang/${lang}/banners.json`);

const metatags = metatagsDescriptions.filter((desc: any) => desc.id === 'temporary-email');

return {
props: {
lang,
metatags,
textContent,
footerLang,
navbarLang,
toolsContent,
bannerLang,
},
};
});

export default TempEmail;
Loading
Loading