courrier preview

This commit is contained in:
alma 2025-05-02 09:10:52 +02:00
parent 3fb8266a9e
commit a7dbd7cd85
15 changed files with 872 additions and 41 deletions

4
.env
View File

@ -80,3 +80,7 @@ NEWS_API_URL="http://172.16.0.104:8000"
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=mySecretPassword
MICROSOFT_CLIENT_ID="afaffea5-4e10-462a-aa64-e73baf642c57"
MICROSOFT_CLIENT_SECRET="eIx8Q~N3ZnXTjTsVM3ECZio4G7t.BO6AYlD1-b2h"
MICROSOFT_REDIRECT_URI="https://lab.slm-lab.net/ms"

View File

@ -0,0 +1,125 @@
import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { exchangeCodeForTokens } from '@/lib/services/microsoft-oauth';
import { prisma } from '@/lib/prisma';
import { testEmailConnection, saveUserEmailCredentials } from '@/lib/services/email-service';
import { invalidateFolderCache } from '@/lib/redis';
export async function POST(request: Request) {
try {
// Authenticate user
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Parse request body
const body = await request.json();
const { code, state } = body;
if (!code || !state) {
return NextResponse.json(
{ error: 'Missing required parameters' },
{ status: 400 }
);
}
// Validate state parameter to prevent CSRF
try {
const decodedState = JSON.parse(Buffer.from(state, 'base64').toString());
// Check if state contains valid userId and is not expired (10 minutes)
if (decodedState.userId !== session.user.id ||
Date.now() - decodedState.timestamp > 10 * 60 * 1000) {
return NextResponse.json(
{ error: 'Invalid or expired state parameter' },
{ status: 400 }
);
}
} catch (e) {
return NextResponse.json(
{ error: 'Invalid state parameter' },
{ status: 400 }
);
}
// Exchange code for tokens
const tokens = await exchangeCodeForTokens(code);
// Extract user email from token (would require token decoding in production)
// For this implementation, we'll use a temporary email
const userEmail = `${session.user.email || 'user'}@microsoft.com`;
// Create credentials object for Microsoft account
const credentials = {
email: userEmail,
// Use Microsoft's IMAP server for Outlook/Office365
host: 'outlook.office365.com',
port: 993,
secure: true,
// OAuth specific fields
useOAuth: true,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
tokenExpiry: Date.now() + (tokens.expires_in * 1000),
// Optional fields
display_name: `Microsoft ${userEmail}`,
color: '#0078D4', // Microsoft blue
// SMTP settings for Microsoft
smtp_host: 'smtp.office365.com',
smtp_port: 587,
smtp_secure: false
};
// Test connection before saving
console.log(`Testing Microsoft OAuth connection for user ${session.user.id}`);
const testResult = await testEmailConnection(credentials);
if (!testResult.imap) {
return NextResponse.json(
{ error: `Connection test failed: ${testResult.error || 'Could not connect to Microsoft IMAP server'}` },
{ status: 400 }
);
}
// Save credentials to database and cache
console.log(`Saving Microsoft account for user: ${session.user.id}`);
await saveUserEmailCredentials(session.user.id, userEmail, credentials);
// Fetch the created account from the database
const createdAccount = await prisma.mailCredentials.findFirst({
where: { userId: session.user.id, email: userEmail },
select: {
id: true,
email: true,
display_name: true,
color: true,
}
});
// Invalidate any existing folder caches
await invalidateFolderCache(session.user.id, userEmail, '*');
return NextResponse.json({
success: true,
account: createdAccount,
message: 'Microsoft account added successfully'
});
} catch (error) {
console.error('Error processing Microsoft callback:', error);
return NextResponse.json(
{
error: 'Failed to process Microsoft authentication',
details: error instanceof Error ? error.message : 'Unknown error'
},
{ status: 500 }
);
}
}

View File

@ -0,0 +1,41 @@
import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { getMicrosoftAuthUrl } from '@/lib/services/microsoft-oauth';
// Endpoint to initiate Microsoft OAuth flow
export async function GET(request: Request) {
try {
// Authenticate user
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Create a state parameter with the user's ID to prevent CSRF
const state = Buffer.from(JSON.stringify({
userId: session.user.id,
timestamp: Date.now()
})).toString('base64');
// Generate the authorization URL
const authUrl = getMicrosoftAuthUrl(state);
return NextResponse.json({
authUrl,
state
});
} catch (error) {
console.error('Error initiating Microsoft OAuth flow:', error);
return NextResponse.json(
{
error: 'Failed to initiate Microsoft OAuth flow',
details: error instanceof Error ? error.message : 'Unknown error'
},
{ status: 500 }
);
}
}

87
app/ms/page.tsx Normal file
View File

@ -0,0 +1,87 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
export default function MicrosoftCallbackPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [status, setStatus] = useState<string>('Processing authentication...');
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function handleCallback() {
try {
// Extract code and state from URL
const code = searchParams.get('code');
const state = searchParams.get('state');
const errorMsg = searchParams.get('error');
if (errorMsg) {
setError(`Authentication error: ${errorMsg}`);
return;
}
if (!code || !state) {
setError('Missing required parameters');
return;
}
setStatus('Authentication successful. Exchanging code for tokens...');
// Send code to our API to exchange for tokens
const response = await fetch('/api/courrier/microsoft/callback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ code, state }),
});
const data = await response.json();
if (!response.ok) {
setError(data.error || 'Failed to process authentication');
return;
}
setStatus('Account connected successfully!');
// Redirect back to email client after a short delay
setTimeout(() => {
router.push('/courrier');
}, 2000);
} catch (err) {
setError('An unexpected error occurred');
console.error('Callback processing error:', err);
}
}
handleCallback();
}, [router, searchParams]);
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50">
<div className="w-full max-w-md p-8 space-y-4 bg-white rounded-xl shadow-md">
<h1 className="text-2xl font-bold text-center">Microsoft Account Connection</h1>
{error ? (
<div className="p-4 bg-red-50 text-red-700 rounded-md">
<p className="font-medium">Error</p>
<p>{error}</p>
<button
onClick={() => router.push('/courrier')}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Return to Email Client
</button>
</div>
) : (
<div className="p-4 bg-blue-50 text-blue-700 rounded-md">
<p>{status}</p>
</div>
)}
</div>
</div>
);
}

View File

@ -227,6 +227,23 @@ export default function EmailSidebar({
);
};
// Add Microsoft button logic
const handleConnectMicrosoft = async () => {
try {
const response = await fetch('/api/courrier/microsoft');
const data = await response.json();
if (response.ok && data.authUrl) {
// Redirect to Microsoft's authorization page
window.location.href = data.authUrl;
} else {
console.error('Failed to initiate Microsoft authentication:', data.error);
}
} catch (error) {
console.error('Error connecting Microsoft account:', error);
}
};
return (
<div className="w-60 bg-white/95 backdrop-blur-sm border-r border-gray-100 flex flex-col md:flex" style={{display: "flex !important"}}>
{/* Courrier Title */}
@ -441,6 +458,24 @@ export default function EmailSidebar({
</div>
</div>
</form>
{/* Add Microsoft option */}
<div className="mt-2 pt-2 border-t border-gray-100">
<button
type="button"
onClick={handleConnectMicrosoft}
className="w-full flex items-center justify-center gap-2 h-7 text-xs bg-[#0078D4] hover:bg-[#106EBE] text-white rounded-md px-2 py-0"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 23 23">
<path fill="#f3f3f3" d="M0 0h23v23H0z" />
<path fill="#f35325" d="M1 1h10v10H1z" />
<path fill="#81bc06" d="M12 1h10v10H12z" />
<path fill="#05a6f0" d="M1 12h10v10H1z" />
<path fill="#ffba08" d="M12 12h10v10H12z" />
</svg>
Connect Microsoft Account
</button>
</div>
</div>
)}

View File

@ -18,6 +18,7 @@ import {
invalidateEmailContentCache
} from '@/lib/redis';
import { EmailCredentials, EmailMessage, EmailAddress, EmailAttachment } from '@/lib/types';
import { ensureFreshToken } from './token-refresh';
// Types specific to this service
export interface EmailListResult {
@ -250,6 +251,22 @@ export async function getImapConnection(
await cacheEmailCredentials(userId, accountId, credentials);
}
// If using OAuth, ensure we have a fresh token
if (credentials.useOAuth) {
try {
console.log(`Ensuring fresh token for OAuth account ${credentials.email}`);
const { accessToken, success } = await ensureFreshToken(userId, credentials.email);
if (success) {
credentials.accessToken = accessToken;
} else {
console.error(`Failed to refresh token for ${credentials.email}`);
}
} catch (err) {
console.error(`Error refreshing token for ${credentials.email}:`, err);
}
}
// Initialize connection tracking
connectionPool[connectionKey] = {
client: null as any,
@ -314,14 +331,23 @@ export async function getImapConnection(
* Helper function to create a new IMAP connection
*/
async function createImapConnection(credentials: EmailCredentials, connectionKey: string): Promise<ImapFlow> {
// Configure auth based on whether we're using OAuth or password
const auth = credentials.useOAuth
? {
user: credentials.email,
// Use XOAUTH2 authentication for Microsoft accounts
accessToken: credentials.accessToken
}
: {
user: credentials.email,
pass: credentials.password,
};
const client = new ImapFlow({
host: credentials.host,
port: credentials.port,
secure: credentials.secure ?? true,
auth: {
user: credentials.email,
pass: credentials.password,
},
auth,
logger: false,
emitLogs: false,
tls: {
@ -962,15 +988,23 @@ export async function sendEmail(
};
}
// Configure SMTP auth based on OAuth or password
const auth = credentials.useOAuth
? {
user: credentials.email,
accessToken: credentials.accessToken
}
: {
user: credentials.email,
pass: credentials.password,
};
// Create SMTP transporter with user's SMTP settings if available
const transporter = nodemailer.createTransport({
host: credentials.smtp_host || 'smtp.infomaniak.com',
port: credentials.smtp_port || 587,
secure: credentials.smtp_secure || false,
auth: {
user: credentials.email,
pass: credentials.password,
},
auth,
tls: {
rejectUnauthorized: false
}
@ -1036,20 +1070,31 @@ export async function testEmailConnection(credentials: EmailCredentials): Promis
}> {
console.log('Testing connection with:', {
...credentials,
password: '***'
password: credentials.password ? '***' : undefined,
accessToken: credentials.accessToken ? '***' : undefined,
refreshToken: credentials.refreshToken ? '***' : undefined
});
// Test IMAP connection
try {
console.log(`Testing IMAP connection to ${credentials.host}:${credentials.port} for ${credentials.email}`);
// Configure auth based on whether we're using OAuth or password
const auth = credentials.useOAuth
? {
user: credentials.email,
accessToken: credentials.accessToken
}
: {
user: credentials.email,
pass: credentials.password,
};
const client = new ImapFlow({
host: credentials.host,
port: credentials.port,
secure: credentials.secure ?? true,
auth: {
user: credentials.email,
pass: credentials.password,
},
auth,
logger: false,
tls: {
rejectUnauthorized: false
@ -1068,14 +1113,23 @@ export async function testEmailConnection(credentials: EmailCredentials): Promis
if (credentials.smtp_host && credentials.smtp_port) {
try {
console.log(`Testing SMTP connection to ${credentials.smtp_host}:${credentials.smtp_port}`);
// Configure SMTP auth based on OAuth or password
const smtpAuth = credentials.useOAuth
? {
user: credentials.email,
accessToken: credentials.accessToken
}
: {
user: credentials.email,
pass: credentials.password,
};
const transporter = nodemailer.createTransport({
host: credentials.smtp_host,
port: credentials.smtp_port,
secure: credentials.smtp_secure ?? false,
auth: {
user: credentials.email,
pass: credentials.password,
},
auth: smtpAuth,
tls: {
rejectUnauthorized: false
}

View File

@ -0,0 +1,109 @@
import axios from 'axios';
// Microsoft OAuth URLs
const MICROSOFT_AUTHORIZE_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
const MICROSOFT_TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
// Client configuration from environment variables
const clientId = process.env.MICROSOFT_CLIENT_ID;
const clientSecret = process.env.MICROSOFT_CLIENT_SECRET;
const redirectUri = process.env.MICROSOFT_REDIRECT_URI;
// Required scopes for IMAP and SMTP access
const REQUIRED_SCOPES = [
'offline_access',
'https://outlook.office.com/IMAP.AccessAsUser.All',
'https://outlook.office.com/SMTP.Send'
].join(' ');
/**
* Generates the authorization URL for Microsoft OAuth
*/
export function getMicrosoftAuthUrl(state: string): string {
const params = new URLSearchParams({
client_id: clientId!,
response_type: 'code',
redirect_uri: redirectUri!,
scope: REQUIRED_SCOPES,
state,
response_mode: 'query'
});
return `${MICROSOFT_AUTHORIZE_URL}?${params.toString()}`;
}
/**
* Exchange authorization code for tokens
*/
export async function exchangeCodeForTokens(code: string): Promise<{
access_token: string;
refresh_token: string;
expires_in: number;
}> {
const params = new URLSearchParams({
client_id: clientId!,
client_secret: clientSecret!,
code,
redirect_uri: redirectUri!,
grant_type: 'authorization_code'
});
try {
const response = await axios.post(MICROSOFT_TOKEN_URL, params.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
return {
access_token: response.data.access_token,
refresh_token: response.data.refresh_token,
expires_in: response.data.expires_in
};
} catch (error) {
console.error('Error exchanging code for tokens:', error);
throw new Error('Failed to exchange authorization code for tokens');
}
}
/**
* Refresh an access token using a refresh token
*/
export async function refreshAccessToken(refreshToken: string): Promise<{
access_token: string;
refresh_token?: string;
expires_in: number;
}> {
const params = new URLSearchParams({
client_id: clientId!,
client_secret: clientSecret!,
refresh_token: refreshToken,
grant_type: 'refresh_token',
scope: REQUIRED_SCOPES
});
try {
const response = await axios.post(MICROSOFT_TOKEN_URL, params.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
return {
access_token: response.data.access_token,
refresh_token: response.data.refresh_token,
expires_in: response.data.expires_in
};
} catch (error) {
console.error('Error refreshing token:', error);
throw new Error('Failed to refresh access token');
}
}
/**
* Create special XOAUTH2 string for IMAP authentication
*/
export function createXOAuth2Token(email: string, accessToken: string): string {
const auth = `user=${email}\x01auth=Bearer ${accessToken}\x01\x01`;
return Buffer.from(auth).toString('base64');
}

View File

@ -0,0 +1,79 @@
import { refreshAccessToken } from './microsoft-oauth';
import { prisma } from '@/lib/prisma';
import { getRedisClient, KEYS } from '@/lib/redis';
/**
* Check if a token is expired or about to expire (within 5 minutes)
*/
export function isTokenExpired(expiryTimestamp: number): boolean {
const fiveMinutesInMs = 5 * 60 * 1000;
return Date.now() + fiveMinutesInMs >= expiryTimestamp;
}
/**
* Refresh an access token if it's expired or about to expire
*/
export async function ensureFreshToken(
userId: string,
email: string
): Promise<{ accessToken: string; success: boolean }> {
try {
// Get stored credentials using raw query until Prisma schema is updated
const credentials = await prisma.$queryRaw`
SELECT "useOAuth", "refreshToken", "accessToken", "tokenExpiry"
FROM "MailCredentials"
WHERE "userId" = ${userId} AND "email" = ${email}
LIMIT 1
`;
const credData = Array.isArray(credentials) && credentials.length > 0
? credentials[0]
: null;
// If not OAuth or missing refresh token, return failure
if (!credData?.useOAuth || !credData.refreshToken) {
return { accessToken: '', success: false };
}
// If token is still valid, return current token
if (credData.tokenExpiry && credData.accessToken &&
new Date(credData.tokenExpiry) > new Date(Date.now() + 5 * 60 * 1000)) {
return { accessToken: credData.accessToken, success: true };
}
// Token is expired or about to expire, refresh it
console.log(`Refreshing token for user ${userId}, account ${email}`);
const tokens = await refreshAccessToken(credData.refreshToken);
// Update database with new token information using raw query
await prisma.$executeRaw`
UPDATE "MailCredentials"
SET
"accessToken" = ${tokens.access_token},
"refreshToken" = ${tokens.refresh_token || credData.refreshToken},
"tokenExpiry" = ${new Date(Date.now() + (tokens.expires_in * 1000))}
WHERE "userId" = ${userId} AND "email" = ${email}
`;
// Update Redis cache
const redis = getRedisClient();
const key = KEYS.CREDENTIALS(userId, email);
const credStr = await redis.get(key);
if (credStr) {
const creds = JSON.parse(credStr);
creds.accessToken = tokens.access_token;
if (tokens.refresh_token) {
creds.refreshToken = tokens.refresh_token;
}
creds.tokenExpiry = Date.now() + (tokens.expires_in * 1000);
await redis.set(key, JSON.stringify(creds), 'EX', 86400); // 24 hours
}
return { accessToken: tokens.access_token, success: true };
} catch (error) {
console.error(`Error refreshing token for user ${userId}:`, error);
return { accessToken: '', success: false };
}
}

View File

@ -7,6 +7,12 @@ export interface EmailCredentials {
secure?: boolean;
encryptedPassword?: string;
// OAuth Settings (for Microsoft accounts)
useOAuth?: boolean;
accessToken?: string;
refreshToken?: string;
tokenExpiry?: number;
// SMTP Settings
smtp_host?: string;
smtp_port?: number;

126
node_modules/.package-lock.json generated vendored
View File

@ -2622,6 +2622,12 @@
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
@ -2683,6 +2689,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -2841,7 +2858,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@ -3028,6 +3044,18 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@ -3338,6 +3366,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
@ -3461,7 +3498,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@ -3537,7 +3573,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -3547,7 +3582,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -3557,7 +3591,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@ -3566,6 +3599,21 @@
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
@ -3742,6 +3790,26 @@
"node": ">=8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@ -3773,6 +3841,42 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/form-data/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/form-data/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@ -3836,7 +3940,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@ -3869,7 +3972,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@ -3913,7 +4015,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -3939,7 +4040,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -3952,7 +4052,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@ -4655,7 +4754,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -5643,6 +5741,12 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

127
package-lock.json generated
View File

@ -49,6 +49,7 @@
"@types/xmldom": "^0.1.34",
"@xmldom/xmldom": "^0.9.8",
"autoprefixer": "^10.4.20",
"axios": "^1.9.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
@ -3597,6 +3598,12 @@
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
@ -3658,6 +3665,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -3816,7 +3834,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@ -4003,6 +4020,18 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@ -4313,6 +4342,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
@ -4436,7 +4474,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@ -4512,7 +4549,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -4522,7 +4558,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -4532,7 +4567,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@ -4541,6 +4575,21 @@
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
@ -4717,6 +4766,26 @@
"node": ">=8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@ -4748,6 +4817,42 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/form-data/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/form-data/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@ -4811,7 +4916,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@ -4844,7 +4948,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@ -4888,7 +4991,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -4914,7 +5016,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -4927,7 +5028,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@ -5630,7 +5730,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -6618,6 +6717,12 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@ -50,6 +50,7 @@
"@types/xmldom": "^0.1.34",
"@xmldom/xmldom": "^0.9.8",
"autoprefixer": "^10.4.20",
"axios": "^1.9.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",

View File

@ -0,0 +1,7 @@
-- AlterTable to make password optional and add OAuth fields
ALTER TABLE "MailCredentials"
ALTER COLUMN "password" DROP NOT NULL,
ADD COLUMN "useOAuth" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "refreshToken" TEXT,
ADD COLUMN "accessToken" TEXT,
ADD COLUMN "tokenExpiry" TIMESTAMP(3);

View File

@ -60,11 +60,17 @@ model MailCredentials {
id String @id @default(uuid())
userId String
email String
password String
password String? // Make password optional
host String
port Int
secure Boolean @default(true)
// OAuth Settings
useOAuth Boolean @default(false)
refreshToken String?
accessToken String?
tokenExpiry DateTime?
// SMTP Settings
smtp_host String?
smtp_port Int?

View File

@ -1265,6 +1265,11 @@ async@^3:
resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz"
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
atomic-sleep@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz"
@ -1289,6 +1294,15 @@ available-typed-arrays@^1.0.7:
dependencies:
possible-typed-array-names "^1.0.0"
axios@^1.9.0:
version "1.9.0"
resolved "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz"
integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
@ -1469,6 +1483,13 @@ color@^4.2.3:
color-convert "^2.0.1"
color-string "^1.9.0"
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
@ -1663,6 +1684,11 @@ define-data-property@^1.1.4:
es-errors "^1.3.0"
gopd "^1.0.1"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
denque@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz"
@ -1817,6 +1843,16 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
dependencies:
es-errors "^1.3.0"
es-set-tostringtag@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz"
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
dependencies:
es-errors "^1.3.0"
get-intrinsic "^1.2.6"
has-tostringtag "^1.0.2"
hasown "^2.0.2"
esbuild-register@3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz"
@ -1925,6 +1961,11 @@ fill-range@^7.1.1:
dependencies:
to-regex-range "^5.0.1"
follow-redirects@^1.15.6:
version "1.15.9"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz"
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
for-each@^0.3.5:
version "0.3.5"
resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz"
@ -1940,6 +1981,16 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.0"
signal-exit "^4.0.1"
form-data@^4.0.0:
version "4.0.2"
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz"
integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
mime-types "^2.1.12"
formdata-polyfill@^4.0.10:
version "4.0.10"
resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz"
@ -1974,7 +2025,7 @@ function-bind@^1.1.2:
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-intrinsic@^1.2.4, get-intrinsic@^1.3.0:
get-intrinsic@^1.2.4, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
@ -2535,6 +2586,18 @@ mime-db@^1.54.0:
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mime-types@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz"
@ -3061,6 +3124,11 @@ prop-types@^15.6.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
punycode.js@2.3.1:
version "2.3.1"
resolved "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz"