mail page imap connection mime 5 bis rest 16 login page 11
This commit is contained in:
parent
d9a57b694d
commit
6a5e1bcec6
91
app/api/mail/login/route.ts
Normal file
91
app/api/mail/login/route.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import Imap from 'imap';
|
||||
|
||||
interface StoredCredentials {
|
||||
user: string;
|
||||
password: string;
|
||||
host: string;
|
||||
port: string;
|
||||
}
|
||||
|
||||
export let storedCredentials: StoredCredentials | null = null;
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { email, password, host, port } = await request.json();
|
||||
|
||||
if (!email || !password || !host || !port) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing required fields' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Test IMAP connection
|
||||
const imap = new Imap({
|
||||
user: email,
|
||||
password,
|
||||
host,
|
||||
port: parseInt(port),
|
||||
tls: true,
|
||||
tlsOptions: {
|
||||
rejectUnauthorized: false,
|
||||
servername: host
|
||||
},
|
||||
authTimeout: 10000,
|
||||
connTimeout: 10000,
|
||||
debug: console.log
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
imap.once('ready', () => {
|
||||
imap.end();
|
||||
// Store credentials
|
||||
storedCredentials = { user: email, password, host, port };
|
||||
resolve(NextResponse.json({ success: true }));
|
||||
});
|
||||
|
||||
imap.once('error', (err: Error) => {
|
||||
imap.end();
|
||||
if (err.message.includes('Invalid login or password')) {
|
||||
reject(new Error('Invalid login or password'));
|
||||
} else {
|
||||
reject(new Error(`IMAP connection error: ${err.message}`));
|
||||
}
|
||||
});
|
||||
|
||||
imap.connect();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in login handler:', error);
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('Invalid login or password')) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid login or password', details: error.message },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to connect to email server', details: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: 'Unknown error occurred' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
if (!storedCredentials) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No stored credentials found' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Return credentials without password
|
||||
const { password, ...safeCredentials } = storedCredentials;
|
||||
return NextResponse.json(safeCredentials);
|
||||
}
|
||||
@ -1,424 +1,179 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import Imap from 'imap';
|
||||
import { simpleParser } from 'mailparser';
|
||||
import { parseEmailHeaders } from '@/lib/email-parser';
|
||||
|
||||
// Debug logging for environment variables
|
||||
console.log('Environment Variables:', {
|
||||
IMAP_USER: process.env.IMAP_USER,
|
||||
IMAP_HOST: process.env.IMAP_HOST,
|
||||
IMAP_PORT: process.env.IMAP_PORT,
|
||||
IMAP_PASSWORD: process.env.IMAP_PASSWORD ? '***' : undefined,
|
||||
NODE_ENV: process.env.NODE_ENV
|
||||
});
|
||||
|
||||
// Store for credentials (in memory, you might want to use a proper storage solution)
|
||||
let storedCredentials: {
|
||||
user: string;
|
||||
interface StoredCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
host: string;
|
||||
port: string;
|
||||
} | null = null;
|
||||
|
||||
// Helper function to get stored credentials
|
||||
function getStoredCredentials() {
|
||||
// Server-side: use stored credentials
|
||||
if (typeof window === 'undefined') {
|
||||
return storedCredentials;
|
||||
}
|
||||
|
||||
// Client-side: use localStorage
|
||||
const stored = localStorage.getItem('imapCredentials');
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
}
|
||||
|
||||
// IMAP configuration
|
||||
const getImapConfig = () => {
|
||||
const credentials = getStoredCredentials();
|
||||
|
||||
if (!credentials?.user || !credentials?.password) {
|
||||
throw new Error('Email credentials not found. Please log in first.');
|
||||
}
|
||||
|
||||
return {
|
||||
user: credentials.user,
|
||||
password: credentials.password,
|
||||
host: credentials.host || 'mail.infomaniak.com',
|
||||
port: parseInt(credentials.port || '993', 10),
|
||||
tls: true,
|
||||
tlsOptions: {
|
||||
rejectUnauthorized: false,
|
||||
servername: credentials.host || 'mail.infomaniak.com'
|
||||
},
|
||||
authTimeout: 10000,
|
||||
connTimeout: 10000,
|
||||
debug: console.log
|
||||
};
|
||||
};
|
||||
|
||||
// Debug logging for IMAP configuration
|
||||
console.log('IMAP Configuration:', {
|
||||
user: getImapConfig().user,
|
||||
host: getImapConfig().host,
|
||||
port: getImapConfig().port,
|
||||
tls: getImapConfig().tls,
|
||||
hasPassword: !!getImapConfig().password,
|
||||
authTimeout: getImapConfig().authTimeout,
|
||||
connTimeout: getImapConfig().connTimeout
|
||||
});
|
||||
|
||||
interface ImapMessage {
|
||||
header: {
|
||||
from?: string[];
|
||||
to?: string[];
|
||||
subject?: string[];
|
||||
date?: string[];
|
||||
[key: string]: string[] | undefined;
|
||||
};
|
||||
body: string;
|
||||
attributes: {
|
||||
flags: string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ImapError extends Error {
|
||||
type?: string;
|
||||
textCode?: string;
|
||||
source?: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
interface Email {
|
||||
id: string;
|
||||
from: string;
|
||||
subject: string;
|
||||
date: string;
|
||||
body: string;
|
||||
date: Date;
|
||||
read: boolean;
|
||||
starred: boolean;
|
||||
body: string;
|
||||
}
|
||||
|
||||
interface EmailHeaders {
|
||||
from: string;
|
||||
subject: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
function parseEmailHeaders(buffer: string): EmailHeaders {
|
||||
const headers: EmailHeaders = {
|
||||
from: '',
|
||||
subject: '',
|
||||
date: ''
|
||||
interface ImapBox {
|
||||
messages: {
|
||||
total: number;
|
||||
};
|
||||
}
|
||||
|
||||
const lines = buffer.split('\r\n');
|
||||
for (const line of lines) {
|
||||
if (line.toLowerCase().startsWith('from:')) {
|
||||
headers.from = line.substring(5).trim();
|
||||
} else if (line.toLowerCase().startsWith('subject:')) {
|
||||
headers.subject = line.substring(8).trim();
|
||||
} else if (line.toLowerCase().startsWith('date:')) {
|
||||
headers.date = line.substring(5).trim();
|
||||
interface ImapMessage {
|
||||
on: (event: string, callback: (data: any) => void) => void;
|
||||
once: (event: string, callback: (data: any) => void) => void;
|
||||
attributes: {
|
||||
uid: number;
|
||||
flags: string[];
|
||||
size: number;
|
||||
};
|
||||
body: {
|
||||
[key: string]: {
|
||||
on: (event: string, callback: (data: any) => void) => void;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface ImapConfig {
|
||||
user: string;
|
||||
password: string;
|
||||
host: string;
|
||||
port: number;
|
||||
tls: boolean;
|
||||
authTimeout: number;
|
||||
connTimeout: number;
|
||||
debug?: (info: string) => void;
|
||||
}
|
||||
|
||||
function getStoredCredentials(): StoredCredentials | null {
|
||||
if (typeof window === 'undefined') {
|
||||
// Server-side
|
||||
const email = process.env.IMAP_USER;
|
||||
const password = process.env.IMAP_PASSWORD;
|
||||
const host = process.env.IMAP_HOST;
|
||||
const port = process.env.IMAP_PORT ? parseInt(process.env.IMAP_PORT) : 993;
|
||||
|
||||
if (!email || !password || !host) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { email, password, host, port };
|
||||
} else {
|
||||
// Client-side
|
||||
const stored = localStorage.getItem('imapCredentials');
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
// Helper function to create a promise-based IMAP connection
|
||||
function createImapConnection(config: any): Promise<Imap> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const imap = new Imap({
|
||||
user: config.user,
|
||||
password: config.password,
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
tls: true,
|
||||
tlsOptions: {
|
||||
rejectUnauthorized: false,
|
||||
servername: config.host
|
||||
},
|
||||
authTimeout: 10000,
|
||||
connTimeout: 10000,
|
||||
debug: console.log,
|
||||
autotls: 'always',
|
||||
keepalive: true
|
||||
});
|
||||
|
||||
imap.once('ready', () => {
|
||||
console.log('IMAP connection established successfully');
|
||||
resolve(imap);
|
||||
});
|
||||
|
||||
imap.once('error', (err: ImapError) => {
|
||||
console.error('IMAP connection error:', err);
|
||||
console.error('Error details:', {
|
||||
type: err.type,
|
||||
textCode: err.textCode,
|
||||
source: err.source
|
||||
});
|
||||
reject(err);
|
||||
});
|
||||
|
||||
imap.once('end', () => console.log('[connection] Ended'));
|
||||
imap.once('close', () => console.log('[connection] Closed'));
|
||||
|
||||
try {
|
||||
console.log('Attempting to connect to IMAP server...');
|
||||
console.log('Using credentials:', {
|
||||
user: config.user,
|
||||
passwordLength: config.password.length,
|
||||
host: config.host,
|
||||
port: config.port
|
||||
});
|
||||
imap.connect();
|
||||
} catch (err) {
|
||||
console.error('Error during IMAP connection:', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to promisify the message fetching
|
||||
function fetchMessages(imap: Imap, box: string): Promise<ImapMessage[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
imap.openBox(box, false, (err, mailbox) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for all messages
|
||||
imap.search(['ALL'], (err, results) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// No messages found
|
||||
if (!results || !results.length) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetch = imap.fetch(results, {
|
||||
bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)', 'TEXT'],
|
||||
struct: true
|
||||
});
|
||||
|
||||
const messages: ImapMessage[] = [];
|
||||
|
||||
fetch.on('message', (msg) => {
|
||||
const message: Partial<ImapMessage> = {};
|
||||
|
||||
msg.on('body', (stream, info) => {
|
||||
let buffer = '';
|
||||
stream.on('data', (chunk) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
stream.once('end', () => {
|
||||
if (info.which === 'TEXT') {
|
||||
message.body = buffer;
|
||||
} else {
|
||||
message.header = Imap.parseHeader(buffer);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
msg.once('attributes', (attrs) => {
|
||||
message.attributes = attrs;
|
||||
});
|
||||
|
||||
msg.once('end', () => {
|
||||
messages.push(message as ImapMessage);
|
||||
});
|
||||
});
|
||||
|
||||
fetch.once('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
fetch.once('end', () => {
|
||||
resolve(messages);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Get stored credentials
|
||||
const credentials = getStoredCredentials();
|
||||
if (!credentials) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No IMAP credentials found. Please configure your email account.' },
|
||||
{ error: 'No stored credentials found' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log('Starting email fetch process...');
|
||||
console.log('IMAP Configuration:', {
|
||||
user: credentials.user,
|
||||
const imapConfig: ImapConfig = {
|
||||
user: credentials.email,
|
||||
password: credentials.password,
|
||||
host: credentials.host,
|
||||
port: credentials.port,
|
||||
tls: true,
|
||||
hasPassword: !!credentials.password
|
||||
});
|
||||
|
||||
// Create IMAP connection
|
||||
const imap = await createImapConnection({
|
||||
user: credentials.user,
|
||||
password: credentials.password,
|
||||
host: credentials.host,
|
||||
port: parseInt(credentials.port),
|
||||
tls: true
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
imap.once('ready', () => {
|
||||
console.log('IMAP connection ready');
|
||||
imap.openBox('INBOX', false, (err, box) => {
|
||||
if (err) {
|
||||
console.error('Error opening inbox:', err);
|
||||
imap.end();
|
||||
reject(new Error('Failed to open inbox'));
|
||||
return;
|
||||
}
|
||||
|
||||
const fetch = imap.seq.fetch('1:10', {
|
||||
bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)', 'TEXT'],
|
||||
struct: true
|
||||
});
|
||||
|
||||
const messages: Email[] = [];
|
||||
|
||||
fetch.on('message', (msg) => {
|
||||
const email: Email = {
|
||||
id: '',
|
||||
from: '',
|
||||
subject: '',
|
||||
date: '',
|
||||
body: '',
|
||||
read: false,
|
||||
starred: false
|
||||
};
|
||||
|
||||
msg.on('body', (stream, info) => {
|
||||
let buffer = '';
|
||||
stream.on('data', (chunk) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
stream.on('end', () => {
|
||||
if (info.which === 'HEADER.FIELDS (FROM SUBJECT DATE)') {
|
||||
const headers = parseEmailHeaders(buffer);
|
||||
email.from = headers.from;
|
||||
email.subject = headers.subject;
|
||||
email.date = headers.date;
|
||||
} else if (info.which === 'TEXT') {
|
||||
email.body = buffer;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
msg.once('attributes', (attrs) => {
|
||||
email.id = attrs.uid.toString();
|
||||
email.read = !attrs.flags.includes('\\Unseen');
|
||||
email.starred = attrs.flags.includes('\\Flagged');
|
||||
});
|
||||
|
||||
msg.once('end', () => {
|
||||
messages.push(email);
|
||||
});
|
||||
});
|
||||
|
||||
fetch.once('error', (err) => {
|
||||
console.error('Fetch error:', err);
|
||||
imap.end();
|
||||
reject(new Error('Failed to fetch messages'));
|
||||
});
|
||||
|
||||
fetch.once('end', () => {
|
||||
imap.end();
|
||||
resolve(NextResponse.json(messages));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
imap.once('error', (err: ImapError) => {
|
||||
console.error('IMAP connection error:', err);
|
||||
console.error('Error details:', {
|
||||
type: err.type,
|
||||
textCode: err.textCode,
|
||||
source: err.source
|
||||
});
|
||||
imap.end();
|
||||
reject(new Error(err.message));
|
||||
});
|
||||
|
||||
imap.connect();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in GET handler:', error);
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('Invalid login or password')) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid login or password', details: error.message },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch emails', details: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: 'Unknown error occurred' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { email, password, host, port } = body;
|
||||
|
||||
if (!email || !password || !host || !port) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing required fields' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Store credentials
|
||||
storedCredentials = {
|
||||
user: email,
|
||||
password,
|
||||
host,
|
||||
port
|
||||
authTimeout: 10000,
|
||||
connTimeout: 10000,
|
||||
debug: (info: string) => console.log('IMAP Debug:', info)
|
||||
};
|
||||
|
||||
// Test the connection
|
||||
const imap = await createImapConnection(storedCredentials);
|
||||
console.log('IMAP Config:', {
|
||||
...imapConfig,
|
||||
password: '***'
|
||||
});
|
||||
|
||||
const imap = new Imap(imapConfig);
|
||||
|
||||
const connectPromise = new Promise((resolve, reject) => {
|
||||
imap.once('ready', () => resolve(true));
|
||||
imap.once('error', (err: Error) => reject(err));
|
||||
imap.connect();
|
||||
});
|
||||
|
||||
await connectPromise;
|
||||
|
||||
const openBoxPromise = new Promise<ImapBox>((resolve, reject) => {
|
||||
imap.openBox('INBOX', false, (err: Error | null, box: ImapBox) => {
|
||||
if (err) reject(err);
|
||||
else resolve(box);
|
||||
});
|
||||
});
|
||||
|
||||
const box = await openBoxPromise;
|
||||
|
||||
const fetchPromise = (imap: Imap, seqno: number): Promise<ImapMessage> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const f = imap.fetch(seqno, { bodies: ['HEADER', 'TEXT'] });
|
||||
f.on('message', (msg: ImapMessage) => {
|
||||
resolve(msg);
|
||||
});
|
||||
f.once('error', reject);
|
||||
});
|
||||
};
|
||||
|
||||
const processMessage = (msg: ImapMessage): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let body = '';
|
||||
msg.body['TEXT'].on('data', (chunk: Buffer) => {
|
||||
body += chunk.toString('utf8');
|
||||
});
|
||||
msg.body['TEXT'].on('end', () => {
|
||||
const headers = parseEmailHeaders(body);
|
||||
resolve({
|
||||
uid: msg.attributes.uid,
|
||||
flags: msg.attributes.flags,
|
||||
size: msg.attributes.size,
|
||||
...headers
|
||||
});
|
||||
});
|
||||
msg.body['TEXT'].on('error', reject);
|
||||
});
|
||||
};
|
||||
|
||||
const messages: any[] = [];
|
||||
for (let seqno = 1; seqno <= 10; seqno++) {
|
||||
const msg = await fetchPromise(imap, seqno);
|
||||
const processedMsg = await processMessage(msg);
|
||||
messages.push(processedMsg);
|
||||
}
|
||||
imap.end();
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
const emails: Email[] = messages.map((msg) => {
|
||||
return {
|
||||
id: msg.uid.toString(),
|
||||
from: msg.from,
|
||||
subject: msg.subject,
|
||||
date: msg.date,
|
||||
read: !msg.flags?.includes('\\Unseen'),
|
||||
starred: msg.flags?.includes('\\Flagged') || false,
|
||||
body: msg.body
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json(emails);
|
||||
} catch (error) {
|
||||
console.error('Error in POST handler:', error);
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('Invalid login or password')) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid login or password', details: error.message },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to connect to email server', details: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
console.error('Error fetching emails:', error);
|
||||
const status = error instanceof Error && error.message.includes('Invalid login')
|
||||
? 401
|
||||
: 500;
|
||||
return NextResponse.json(
|
||||
{ error: 'Unknown error occurred' },
|
||||
{ status: 500 }
|
||||
{ error: error instanceof Error ? error.message : 'Failed to fetch emails' },
|
||||
{ status }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
lib/email-parser.ts
Normal file
23
lib/email-parser.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export function parseEmailHeaders(emailBody: string): { from: string; subject: string; date: string } {
|
||||
const headers: { [key: string]: string } = {};
|
||||
|
||||
// Split the email body into lines
|
||||
const lines = emailBody.split('\r\n');
|
||||
|
||||
// Parse headers
|
||||
for (const line of lines) {
|
||||
if (line.trim() === '') break; // Stop at the first empty line (end of headers)
|
||||
|
||||
const match = line.match(/^([^:]+):\s*(.+)$/);
|
||||
if (match) {
|
||||
const [, key, value] = match;
|
||||
headers[key.toLowerCase()] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
from: headers['from'] || '',
|
||||
subject: headers['subject'] || '',
|
||||
date: headers['date'] || new Date().toISOString()
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user