mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
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" } }
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user