mail page imap connection mime 5 bis rest 16 login page

This commit is contained in:
alma 2025-04-15 23:28:06 +02:00
parent 926bd48b5e
commit 5b2eb5286a
5 changed files with 244 additions and 42 deletions

2
.env
View File

@ -67,6 +67,6 @@ DB_NAME=rivacube
# IMAP Configuration # IMAP Configuration
IMAP_USER=alma@governance-labs.org IMAP_USER=alma@governance-labs.org
IMAP_PASSWORD=your_imap_password_here IMAP_PASSWORD=8s-hN8u37-IP#-y
IMAP_HOST=mail.infomaniak.com IMAP_HOST=mail.infomaniak.com
IMAP_PORT=993 IMAP_PORT=993

View File

@ -11,42 +11,58 @@ console.log('Environment Variables:', {
NODE_ENV: process.env.NODE_ENV NODE_ENV: process.env.NODE_ENV
}); });
// Helper function to get stored credentials
function getStoredCredentials() {
if (typeof window === 'undefined') {
// Server-side: use environment variables
return {
user: process.env.IMAP_USER,
password: process.env.IMAP_PASSWORD,
host: process.env.IMAP_HOST,
port: process.env.IMAP_PORT
};
}
// Client-side: use localStorage
const stored = localStorage.getItem('imapCredentials');
return stored ? JSON.parse(stored) : null;
}
// IMAP configuration // IMAP configuration
const imapConfig: Imap.Config = { const getImapConfig = () => {
user: process.env.IMAP_USER || 'alma@governance-labs.org', const credentials = getStoredCredentials();
password: process.env.IMAP_PASSWORD || '',
host: process.env.IMAP_HOST || 'mail.infomaniak.com', if (!credentials?.user || !credentials?.password) {
port: parseInt(process.env.IMAP_PORT || '993', 10), throw new Error('Email credentials not found. Please log in first.');
tls: true, }
tlsOptions: {
rejectUnauthorized: false, return {
servername: process.env.IMAP_HOST || 'mail.infomaniak.com' user: credentials.user,
}, password: credentials.password,
authTimeout: 10000, host: credentials.host || 'mail.infomaniak.com',
connTimeout: 10000, port: parseInt(credentials.port || '993', 10),
debug: console.log // Enable IMAP debug logging tls: true,
tlsOptions: {
rejectUnauthorized: false,
servername: credentials.host || 'mail.infomaniak.com'
},
authTimeout: 10000,
connTimeout: 10000,
debug: console.log
};
}; };
// Debug logging for IMAP configuration // Debug logging for IMAP configuration
console.log('IMAP Configuration:', { console.log('IMAP Configuration:', {
user: imapConfig.user, user: getImapConfig().user,
host: imapConfig.host, host: getImapConfig().host,
port: imapConfig.port, port: getImapConfig().port,
tls: imapConfig.tls, tls: getImapConfig().tls,
hasPassword: !!imapConfig.password, hasPassword: !!getImapConfig().password,
authTimeout: imapConfig.authTimeout, authTimeout: getImapConfig().authTimeout,
connTimeout: imapConfig.connTimeout connTimeout: getImapConfig().connTimeout
}); });
// Validate IMAP configuration
if (!imapConfig.user || !imapConfig.password) {
console.error('IMAP configuration error:', {
user: imapConfig.user,
hasPassword: !!imapConfig.password
});
throw new Error('IMAP credentials are not properly configured. Please check your .env file.');
}
interface ImapMessage { interface ImapMessage {
header: { header: {
from?: string[]; from?: string[];
@ -68,7 +84,7 @@ interface ImapError extends Error {
} }
// Helper function to create a promise-based IMAP connection // Helper function to create a promise-based IMAP connection
function createImapConnection() { function createImapConnection(imapConfig: Imap.Config) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log('Creating new IMAP connection with config:', { console.log('Creating new IMAP connection with config:', {
user: imapConfig.user, user: imapConfig.user,
@ -179,17 +195,17 @@ export async function GET(request: Request) {
try { try {
console.log('Starting email fetch process...'); console.log('Starting email fetch process...');
// Validate IMAP configuration const imapConfig = getImapConfig();
if (!imapConfig.user || !imapConfig.password) { console.log('IMAP Configuration:', {
console.error('IMAP configuration error:', { user: imapConfig.user,
user: imapConfig.user, host: imapConfig.host,
hasPassword: !!imapConfig.password port: imapConfig.port,
}); tls: imapConfig.tls,
throw new Error('IMAP credentials are not properly configured. Please check your .env file.'); hasPassword: !!imapConfig.password
} });
console.log('Creating IMAP connection...'); console.log('Creating IMAP connection...');
const imap = await createImapConnection() as Imap; const imap = await createImapConnection(imapConfig) as Imap;
console.log('Fetching messages...'); console.log('Fetching messages...');
const messages = await fetchMessages(imap, 'INBOX'); const messages = await fetchMessages(imap, 'INBOX');
@ -226,7 +242,7 @@ export async function GET(request: Request) {
// Add endpoint to get mailboxes // Add endpoint to get mailboxes
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const imap = await createImapConnection() as Imap; const imap = await createImapConnection(getImapConfig()) as Imap;
const mailboxes = await new Promise((resolve, reject) => { const mailboxes = await new Promise((resolve, reject) => {
imap.getBoxes((err, boxes) => { imap.getBoxes((err, boxes) => {

View File

@ -0,0 +1,50 @@
import { NextResponse } from 'next/server';
import Imap from 'imap';
export async function POST(request: Request) {
try {
const { email, password, host, port } = await request.json();
// IMAP configuration
const imapConfig: Imap.Config = {
user: email,
password: password,
host: host,
port: parseInt(port, 10),
tls: true,
tlsOptions: {
rejectUnauthorized: false,
servername: host
},
authTimeout: 10000,
connTimeout: 10000
};
// Create a promise-based IMAP connection
const imap = new Imap(imapConfig);
return new Promise((resolve, reject) => {
imap.once('ready', () => {
imap.end();
resolve(NextResponse.json({ success: true }));
});
imap.once('error', (err: Error) => {
console.error('IMAP connection error:', err);
reject(NextResponse.json({
error: 'Failed to connect to email server',
details: err.message
}, { status: 401 }));
});
imap.connect();
});
} catch (error) {
console.error('Error in test connection:', error);
return NextResponse.json({
error: 'Failed to test connection',
details: error instanceof Error ? error.message : 'Unknown error'
}, { status: 500 });
}
}

112
app/mail/login/page.tsx Normal file
View File

@ -0,0 +1,112 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { toast } from 'sonner';
export default function EmailLoginPage() {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [credentials, setCredentials] = useState({
email: '',
password: '',
host: 'mail.infomaniak.com',
port: '993'
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
// Store credentials in localStorage
localStorage.setItem('imapCredentials', JSON.stringify(credentials));
// Test the connection
const response = await fetch('/api/mail/test-connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
if (!response.ok) {
throw new Error('Failed to connect to email server');
}
toast.success('Successfully connected to email server');
router.push('/mail');
} catch (error) {
console.error('Login error:', error);
toast.error('Failed to connect to email server. Please check your credentials.');
} finally {
setLoading(false);
}
};
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Email Login</CardTitle>
<CardDescription>
Enter your email credentials to access your mailbox
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email Address</Label>
<Input
id="email"
type="email"
placeholder="your@email.com"
value={credentials.email}
onChange={(e) => setCredentials({ ...credentials, email: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
value={credentials.password}
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="host">IMAP Host</Label>
<Input
id="host"
type="text"
value={credentials.host}
onChange={(e) => setCredentials({ ...credentials, host: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="port">IMAP Port</Label>
<Input
id="port"
type="text"
value={credentials.port}
onChange={(e) => setCredentials({ ...credentials, port: e.target.value })}
required
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? 'Connecting...' : 'Connect'}
</Button>
</form>
</CardContent>
</Card>
</div>
);
}

View File

@ -19,6 +19,7 @@ import {
import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { MoreVertical, Settings, Plus as PlusIcon, Trash2, Edit, Mail, Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight, Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll, MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare } from 'lucide-react'; import { MoreVertical, Settings, Plus as PlusIcon, Trash2, Edit, Mail, Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight, Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll, MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare } from 'lucide-react';
import { useRouter } from 'next/navigation';
interface Account { interface Account {
id: number; id: number;
@ -386,6 +387,30 @@ function decodeMimeContent(content: string): string {
} }
export default function MailPage() { export default function MailPage() {
const router = useRouter();
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check for stored credentials
const storedCredentials = localStorage.getItem('imapCredentials');
if (!storedCredentials) {
router.push('/mail/login');
} else {
setLoading(false);
}
}, [router]);
if (loading) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 mx-auto"></div>
<p className="mt-4 text-gray-600">Loading...</p>
</div>
</div>
);
}
// Single IMAP account for now // Single IMAP account for now
const [accounts, setAccounts] = useState<Account[]>([ const [accounts, setAccounts] = useState<Account[]>([
{ id: 1, name: 'Mail', email: 'alma@governance-labs.org', color: 'bg-blue-500' }, { id: 1, name: 'Mail', email: 'alma@governance-labs.org', color: 'bg-blue-500' },
@ -411,7 +436,6 @@ export default function MailPage() {
const [showCc, setShowCc] = useState(false); const [showCc, setShowCc] = useState(false);
const [showBcc, setShowBcc] = useState(false); const [showBcc, setShowBcc] = useState(false);
const [emails, setEmails] = useState<Email[]>([]); const [emails, setEmails] = useState<Email[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [composeSubject, setComposeSubject] = useState(''); const [composeSubject, setComposeSubject] = useState('');
const [composeRecipient, setComposeRecipient] = useState(''); const [composeRecipient, setComposeRecipient] = useState('');