mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
306 lines
11 KiB
TypeScript
306 lines
11 KiB
TypeScript
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.1";
|
|
|
|
const corsHeaders = {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Headers":
|
|
"authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version",
|
|
};
|
|
|
|
Deno.serve(async (req) => {
|
|
if (req.method === "OPTIONS") {
|
|
return new Response(null, { headers: corsHeaders });
|
|
}
|
|
|
|
try {
|
|
// Verify auth
|
|
const authHeader = req.headers.get("Authorization");
|
|
if (!authHeader) {
|
|
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), {
|
|
status: 401,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
|
|
const supabaseAnonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
|
|
const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
|
global: { headers: { Authorization: authHeader } },
|
|
});
|
|
|
|
const { data: { user }, error: userError } = await supabase.auth.getUser();
|
|
if (userError || !user) {
|
|
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), {
|
|
status: 401,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
const body = await req.json();
|
|
const { action, smtp_host, smtp_port, smtp_username, smtp_password, test_recipient } = body;
|
|
|
|
if (!smtp_host || !smtp_port || !smtp_username || !smtp_password) {
|
|
return new Response(JSON.stringify({ success: false, error: "Missing SMTP credentials" }), {
|
|
status: 400,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
if (action === "test_connection") {
|
|
try {
|
|
const useSSL = Number(smtp_port) === 465;
|
|
let conn: Deno.Conn | Deno.TlsConn;
|
|
|
|
if (useSSL) {
|
|
conn = await Deno.connectTls({
|
|
hostname: smtp_host,
|
|
port: Number(smtp_port),
|
|
});
|
|
} else {
|
|
conn = await Deno.connect({
|
|
hostname: smtp_host,
|
|
port: Number(smtp_port),
|
|
});
|
|
}
|
|
|
|
const read = async (): Promise<string> => {
|
|
const buf = new Uint8Array(4096);
|
|
const n = await conn.read(buf);
|
|
return new TextDecoder().decode(buf.subarray(0, n || 0));
|
|
};
|
|
|
|
const write = async (cmd: string) => {
|
|
await conn.write(new TextEncoder().encode(cmd + "\r\n"));
|
|
};
|
|
|
|
const greeting = await read();
|
|
if (!greeting.startsWith("220")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `Bad greeting: ${greeting.trim()}` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
await write("EHLO test.local");
|
|
let ehloResponse = await read();
|
|
|
|
if (!useSSL && ehloResponse.includes("STARTTLS")) {
|
|
await write("STARTTLS");
|
|
const tlsResp = await read();
|
|
if (!tlsResp.startsWith("220")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `STARTTLS rejected: ${tlsResp.trim()}` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
conn = await Deno.startTls(conn as Deno.TcpConn, { hostname: smtp_host });
|
|
await write("EHLO test.local");
|
|
ehloResponse = await read();
|
|
}
|
|
|
|
await write("QUIT");
|
|
conn.close();
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: true,
|
|
message: `Connection successful to ${smtp_host}:${smtp_port}`,
|
|
details: {
|
|
greeting: greeting.trim(),
|
|
ehlo: ehloResponse.trim(),
|
|
secure: useSSL ? "implicit_tls" : ehloResponse.includes("STARTTLS") ? "starttls_available" : "plain_smtp",
|
|
},
|
|
}),
|
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
);
|
|
} catch (connError) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: false,
|
|
error: `Connection failed: ${connError.message}`,
|
|
message: `Could not connect to ${smtp_host}:${smtp_port}. Please verify the host/port and SSL/TLS mode.`,
|
|
}),
|
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
);
|
|
}
|
|
}
|
|
|
|
if (action === "send_test_email") {
|
|
const recipient = test_recipient || user.email;
|
|
if (!recipient) {
|
|
return new Response(JSON.stringify({ success: false, error: "No recipient email available" }), {
|
|
status: 400,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
try {
|
|
const useSSL = Number(smtp_port) === 465;
|
|
let conn: Deno.Conn;
|
|
|
|
if (useSSL) {
|
|
conn = await Deno.connectTls({
|
|
hostname: smtp_host,
|
|
port: Number(smtp_port),
|
|
});
|
|
} else {
|
|
conn = await Deno.connect({
|
|
hostname: smtp_host,
|
|
port: Number(smtp_port),
|
|
});
|
|
}
|
|
|
|
const read = async (): Promise<string> => {
|
|
const buf = new Uint8Array(4096);
|
|
const n = await conn.read(buf);
|
|
return new TextDecoder().decode(buf.subarray(0, n || 0));
|
|
};
|
|
|
|
const write = async (cmd: string) => {
|
|
await conn.write(new TextEncoder().encode(cmd + "\r\n"));
|
|
};
|
|
|
|
// Read greeting
|
|
const greeting = await read();
|
|
if (!greeting.startsWith("220")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `Bad greeting: ${greeting.trim()}` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// EHLO
|
|
await write(`EHLO test.local`);
|
|
let ehloResp = await read();
|
|
|
|
// STARTTLS for non-SSL ports
|
|
if (!useSSL && ehloResp.includes("STARTTLS")) {
|
|
await write("STARTTLS");
|
|
const tlsResp = await read();
|
|
if (tlsResp.startsWith("220")) {
|
|
conn = await Deno.startTls(conn as Deno.TcpConn, { hostname: smtp_host });
|
|
await write(`EHLO test.local`);
|
|
ehloResp = await read();
|
|
}
|
|
}
|
|
|
|
// AUTH LOGIN
|
|
await write("AUTH LOGIN");
|
|
const authResp = await read();
|
|
if (!authResp.startsWith("334")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `Auth not supported: ${authResp.trim()}` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// Send username (base64)
|
|
await write(btoa(smtp_username));
|
|
const userResp = await read();
|
|
if (!userResp.startsWith("334")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `Username rejected: ${userResp.trim()}` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// Send password (base64)
|
|
await write(btoa(smtp_password));
|
|
const passResp = await read();
|
|
if (!passResp.startsWith("235")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `Authentication failed. Please check your username and password.` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// MAIL FROM
|
|
await write(`MAIL FROM:<${smtp_username}>`);
|
|
const fromResp = await read();
|
|
if (!fromResp.startsWith("250")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `MAIL FROM rejected: ${fromResp.trim()}` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// RCPT TO
|
|
await write(`RCPT TO:<${recipient}>`);
|
|
const rcptResp = await read();
|
|
if (!rcptResp.startsWith("250")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `RCPT TO rejected: ${rcptResp.trim()}` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// DATA
|
|
await write("DATA");
|
|
const dataResp = await read();
|
|
if (!dataResp.startsWith("354")) {
|
|
conn.close();
|
|
return new Response(JSON.stringify({ success: false, error: `DATA rejected: ${dataResp.trim()}` }), {
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
const now = new Date().toUTCString();
|
|
const emailContent = [
|
|
`From: ${smtp_username}`,
|
|
`To: ${recipient}`,
|
|
`Subject: SMTP Test Email`,
|
|
`Date: ${now}`,
|
|
`Content-Type: text/html; charset=utf-8`,
|
|
``,
|
|
`<html><body>`,
|
|
`<h2>SMTP Test Successful</h2>`,
|
|
`<p>This is a test email sent from your email server settings.</p>`,
|
|
`<p><strong>Server:</strong> ${smtp_host}:${smtp_port}</p>`,
|
|
`<p><strong>Username:</strong> ${smtp_username}</p>`,
|
|
`<p><strong>Sent at:</strong> ${now}</p>`,
|
|
`<hr/>`,
|
|
`<p style="color:#888;font-size:12px;">This email confirms your SMTP configuration is working correctly.</p>`,
|
|
`</body></html>`,
|
|
`.`,
|
|
].join("\r\n");
|
|
|
|
await conn.write(new TextEncoder().encode(emailContent + "\r\n"));
|
|
const sendResp = await read();
|
|
|
|
await write("QUIT");
|
|
conn.close();
|
|
|
|
const emailSent = sendResp.startsWith("250");
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: emailSent,
|
|
message: emailSent
|
|
? `Test email sent successfully to ${recipient}`
|
|
: `Email delivery uncertain: ${sendResp.trim()}`,
|
|
}),
|
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
);
|
|
} catch (sendError) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: false,
|
|
error: `Failed to send test email: ${sendError.message}`,
|
|
}),
|
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
);
|
|
}
|
|
}
|
|
|
|
return new Response(
|
|
JSON.stringify({ success: false, error: "Invalid action. Use 'test_connection' or 'send_test_email'." }),
|
|
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
);
|
|
} catch (error) {
|
|
return new Response(
|
|
JSON.stringify({ success: false, error: error.message }),
|
|
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
);
|
|
}
|
|
});
|