diff --git a/supabase/functions/_shared/checks.ts b/supabase/functions/_shared/checks.ts new file mode 100644 index 00000000..f1628fd7 --- /dev/null +++ b/supabase/functions/_shared/checks.ts @@ -0,0 +1,124 @@ +import { SupabaseClient } from "npm:@supabase/supabase-js"; +import { sub } from "npm:date-fns"; + +export interface CheckResult { + isAllowed: boolean; + reason: string | undefined; + lookupData: ContactRequestLookupData | undefined; +} + +export interface ContactRequestLookupData { + senderUsername: string; + senderEmail: string; + senderUserId: string; + recipientUserId: string; +} + +export async function checkIfContactRequestIsAllowed( + recipientContactName: string, + token: string, + supabaseClient: SupabaseClient, + supabaseServiceRoleClient: SupabaseClient +): Promise { + // Get the user (= sender) data from the token + const { data: senderData, error: senderDataError } = + await supabaseClient.auth.getUser(token); + + console.log(senderData); + + if (senderDataError) { + console.log(senderDataError); + return { isAllowed: false, reason: "unauthorized", lookupData: undefined }; + } + + // Lookup the sender username + const { data: senderLookupData, error: senderLookupDataError } = + await supabaseServiceRoleClient + .from("profiles") + .select("*") + .eq("id", senderData.user.id) + .single(); + + console.log(senderLookupData); + + if (senderLookupDataError) { + console.log(senderLookupDataError); + return { isAllowed: false, reason: "not_found", lookupData: undefined }; + } + + // Lookup the recipient user id + const { data: recipientData, error: recipientDataError } = + await supabaseServiceRoleClient + .from("profiles") + .select("*") + .eq("username", recipientContactName) + .single(); + + if (recipientDataError) { + console.log(recipientDataError); + return { isAllowed: false, reason: "not_found", lookupData: undefined }; + } + + // Check if the user has already tried to contact the recipient + const { data: requestsToRecipient, error: requestsToRecipientError } = + await supabaseClient + .from("contact_requests") + .select("*") + .eq("user_id", senderData.user.id) + .eq("contact_id", recipientData.id) + .not("contact_mail_id", "is", null); // only count sent emails + + if (requestsToRecipientError) { + console.log(requestsToRecipientError); + return { + isAllowed: false, + reason: "internal_server_error", + lookupData: undefined, + }; + } + + if (requestsToRecipient.length > 0) { + return { + isAllowed: false, + reason: "already_contacted_the_recipient_before", + lookupData: undefined, + }; + } + + // Check if the user has sent 3 contact requests in the last 24 hours + const { data: requestsOfLast24h, error: requestsOfLast24hError } = + await supabaseClient + .from("contact_requests") + .select("*") + .eq("user_id", senderData.user.id) + .not("contact_mail_id", "is", null) // only count sent emails + .gt("created_at", sub(new Date(), { days: 1 }).toISOString()); + + if (requestsOfLast24hError) { + console.log(requestsOfLast24hError); + return { + isAllowed: false, + reason: "internal_server_error", + lookupData: undefined, + }; + } + + if (requestsOfLast24h.length >= 3) { + return { + isAllowed: false, + reason: "already_sent_more_than_3_contact_requests", + lookupData: undefined, + }; + } + + return { + isAllowed: true, + reason: undefined, + lookupData: { + senderUsername: senderLookupData.username, + senderEmail: senderData.user.email, + senderUserId: senderData.user.id, + recipientUserId: recipientData.id, + }, + }; +} diff --git a/supabase/functions/check_contact_request/index.ts b/supabase/functions/check_contact_request/index.ts new file mode 100644 index 00000000..727604ab --- /dev/null +++ b/supabase/functions/check_contact_request/index.ts @@ -0,0 +1,67 @@ +import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; +import { checkIfContactRequestIsAllowed } from "../_shared/checks.ts"; +import { corsHeaders } from "../_shared/cors.ts"; + +const SUPABASE_URL = Deno.env.get("SUPABASE_URL"); +const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY"); +const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"); + +const handler = async (_request: Request): Promise => { + if (_request.method === "OPTIONS") { + return new Response(null, { headers: corsHeaders, status: 204 }); + } + + const { recipientContactName } = await _request.json(); + + const authHeader = _request.headers.get("Authorization")!; + + const supabaseClient = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { + global: { headers: { Authorization: authHeader } }, + }); + + const supabaseServiceRoleClient = createClient( + SUPABASE_URL, + SUPABASE_SERVICE_ROLE_KEY + ); + + const token = authHeader.replace("Bearer ", ""); + + const { isAllowed, reason } = await checkIfContactRequestIsAllowed( + recipientContactName, + token, + supabaseClient, + supabaseServiceRoleClient + ); + + if (!isAllowed) { + return new Response( + JSON.stringify({ + isContactRequestAllowed: false, + reason, + }), + { + status: 200, // We have to use 200 here to allow the client to read the response body + headers: { + ...corsHeaders, + "Content-Type": "application/json", + }, + } + ); + } + + return new Response( + JSON.stringify({ + isContactRequestAllowed: true, + reason: undefined, + }), + { + status: 200, + headers: { + ...corsHeaders, + "Content-Type": "application/json", + }, + } + ); +}; + +Deno.serve(handler); diff --git a/supabase/functions/submit_contact_request/index.ts b/supabase/functions/submit_contact_request/index.ts index 2084cd68..b18bfb45 100644 --- a/supabase/functions/submit_contact_request/index.ts +++ b/supabase/functions/submit_contact_request/index.ts @@ -1,6 +1,6 @@ import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; -import { sub } from "npm:date-fns"; import nodemailer from "npm:nodemailer"; +import { checkIfContactRequestIsAllowed } from "../_shared/checks.ts"; import { corsHeaders } from "../_shared/cors.ts"; import { mailTemplate } from "./mail-template.ts"; @@ -35,106 +35,23 @@ const handler = async (_request: Request): Promise => { // Get the user (= sender) data from the token const token = authHeader.replace("Bearer ", ""); - const { data: senderData, error: senderDataError } = - await supabaseClient.auth.getUser(token); - if (senderDataError) { - console.log(senderDataError); - return new Response(JSON.stringify({}), { status: 401 }); - } - - // Lookup the sender username - const { data: senderLookupData, error: senderLookupDataError } = - await supabaseServiceRoleClient - .from("profiles") - .select("*") - .eq("id", senderData.user.id) - .single(); - - if (senderLookupDataError) { - console.log(senderLookupDataError); - return new Response(JSON.stringify(senderLookupDataError), { - status: 404, - headers: corsHeaders, - }); - } - - // Lookup the recipient user id - const { data: recipientData, error: recipientDataError } = - await supabaseServiceRoleClient - .from("profiles") - .select("*") - .eq("username", recipientContactName) - .single(); - - if (recipientDataError) { - console.log(recipientDataError); - return new Response(JSON.stringify(recipientDataError), { - status: 404, - headers: corsHeaders, - }); - } - - // Check if the user has already tried to contact the recipient - const { data: requestsToRecipient, error: requestsToRecipientError } = - await supabaseClient - .from("contact_requests") - .select("*") - .eq("user_id", senderData.user.id) - .eq("contact_id", recipientData.id) - .not("contact_mail_id", "is", null); // only count sent emails - - if (requestsToRecipientError) { - console.log(requestsToRecipientError); - return new Response(JSON.stringify(requestsToRecipientError), { - status: 500, - headers: corsHeaders, - }); - } - - if (requestsToRecipient.length > 0) { - return new Response( - JSON.stringify({ - code: "already_contacted_the_recipient_before", - message: - "User has already sent a contact request to the recipient, not allowed to send another one.", - }), - { - status: 200, - headers: { - ...corsHeaders, - "Content-Type": "application/json", - }, - } + const { isAllowed, reason, lookupData } = + await checkIfContactRequestIsAllowed( + recipientContactName, + token, + supabaseClient, + supabaseServiceRoleClient ); - } - - // Check if the user has sent 3 contact requests in the last 24 hours - const { data: requestsOfLast24h, error: requestsOfLast24hError } = - await supabaseClient - .from("contact_requests") - .select("*") - .eq("user_id", senderData.user.id) - .not("contact_mail_id", "is", null) // only count sent emails - .gt("created_at", sub(new Date(), { days: 1 }).toISOString()); - - if (requestsOfLast24hError) { - console.log(requestsOfLast24hError); - return new Response(JSON.stringify(requestsOfLast24hError), { - status: 500, - headers: corsHeaders, - }); - } - if (requestsOfLast24h.length >= 3) { + if (!isAllowed || !lookupData) { return new Response( JSON.stringify({ - code: "already_sent_more_than_3_contact_requests", - message: - "User has already sent more than 3 contact requests in the last 24 hours, not allowed to send another one.", + isContactRequestAllowed: false, + reason, }), { - status: 200, + status: 403, headers: { ...corsHeaders, "Content-Type": "application/json", @@ -146,7 +63,7 @@ const handler = async (_request: Request): Promise => { // Lookup the recipient email address via serviceRoleClient const { data: fullRecipientData, error: fullRecipientDataError } = await supabaseServiceRoleClient - .rpc("get_user_data_for_id", { u_id: recipientData.id }) + .rpc("get_user_data_for_id", { u_id: lookupData.recipientUserId }) .select("email") .single(); @@ -163,8 +80,8 @@ const handler = async (_request: Request): Promise => { await supabaseClient .from("contact_requests") .insert({ - user_id: senderData.user.id, - contact_id: recipientData.id, + user_id: lookupData.senderUserId, + contact_id: lookupData.recipientUserId, contact_message: message, }) .select("*") @@ -198,11 +115,12 @@ const handler = async (_request: Request): Promise => { const mailOptions = { from: SMTP_FROM, to: fullRecipientData.email, + replyTo: lookupData.senderEmail, subject: "[Gieß den Kiez] Kontaktanfrage / Contact request", html: mailTemplate( - senderLookupData.username, + lookupData.senderUsername, message, - senderData.user.email + lookupData.senderEmail ), }; diff --git a/supabase/functions/submit_contact_request/mail-template.ts b/supabase/functions/submit_contact_request/mail-template.ts index 0629d2f5..385e6044 100644 --- a/supabase/functions/submit_contact_request/mail-template.ts +++ b/supabase/functions/submit_contact_request/mail-template.ts @@ -174,7 +174,7 @@ export const mailTemplate = ( box-sizing: border-box; " > - Der Gieß den Kiez User ${username} möchte sich + ${username} möchte sich mit Dir vernetzen und hat Dir folgende Nachricht gesendet:
- The Gieß den Kiez user ${username} would like to connect with you + ${username} would like to connect with you and has sent you the following message:
- To reply to the user by e-mail, please write to: + To reply to the user via e-mail, please write to: If the contact request contains inappropriate content, we are very sorry. Please inform our team immediately via info@citylab-berlin.org. + text-align: center;">We apologize if the contact request contains inappropriate content. Please notify our team immediately via info@citylab-berlin.org.