S
Supabaseβ€’9mo ago
mauerbac

Supabase Auth - with slack alerts?

How can I setup a Slackbot so anytime a user signs up it's posted in Slack? Also, this is my first time using Auth. I notice so many spam accounts sign up via email. Does others see this? How can you avoid this?
62 Replies
j4
j4β€’9mo ago
It's recommended to implement captcha for password-based login.
legenden
legendenβ€’9mo ago
I would assume something like a Custom Access Token hook can solve your need of posting to slack. https://supabase.com/docs/guides/auth/auth-hooks/custom-access-token-hook?queryGroups=language&language=http It just points to an http endpoint, either in your app/server or you can make a supabase edge function that when triggered will post to the relevant slack channel.
Custom Access Token Hook | Supabase Docs
Customize the access token issued by Supabase Auth
tomaspozo
tomaspozoβ€’9mo ago
What I do for slack notifications for signups is to use a db function that is triggered by inserts in auth.users (when a user signs up a record is created). This function calls an external webhook and passes the email and id to it. That webhook will take care of the Slack notification (I use no-code make.com) this is my function code in case it is useful:
CREATE OR REPLACE FUNCTION public.notify_new_signup()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
BEGIN
PERFORM
net.http_post(
'https://api.your.com/slack',
jsonb_build_object(
'id', NEW.id,
'email', NEW.email,
'app', 'myapp.com'
)
);
RETURN NEW;
END;
$function$;
CREATE OR REPLACE FUNCTION public.notify_new_signup()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
BEGIN
PERFORM
net.http_post(
'https://api.your.com/slack',
jsonb_build_object(
'id', NEW.id,
'email', NEW.email,
'app', 'myapp.com'
)
);
RETURN NEW;
END;
$function$;
you later set up the trigger on auth.users to call this function
mauerbac
mauerbacOPβ€’8mo ago
Thanks @j4 , adding captcha was pretty easy @TomΓ‘s P. - i'm confused you cant trigger on auth.users though ? I tried setting up an auth hook which calls an HTTP endpoint which is an edge function
import { serve } from 'https://deno.land/std@0.165.0/http/server.ts'
import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'

serve(async (req: Request) => {
try {
const payload = await req.text() // Get the raw request body as text
const hookSecret = Deno.env.get('HOOK_SECRETS') // Secret set in your environment
const headers = Object.fromEntries(req.headers) // Extract headers

// Verify the webhook signature
const wh = new Webhook(hookSecret) // Initialize the Webhook verifier
const data = wh.verify(payload, headers) // Verify the payload and parse it

const user = data.user // The user object is in the `data` field
const slackWebhook = Deno.env.get('SLACK_WEBHOOK_URL') // Slack webhook URL from environment

// Send Slack alert if user data is available
if (user && slackWebhook) {
const message = {
text: `πŸŽ‰ New user signup!\n- Email: ${user.email}\n- Signed up at: ${user.created_at}`,
}

const slackResponse = await fetch(slackWebhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message),
})

if (!slackResponse.ok) {
console.error(
'Failed to send Slack message:',
await slackResponse.text()
)
}
}

// Return any custom claims (optional)
return new Response(
JSON.stringify({
customClaims: {
example_claim: 'new_user', // Example custom claim
},
}),
{ headers: { 'Content-Type': 'application/json' } }
)
} catch (error) {
console.error('Error verifying or processing the webhook:', error)
return new Response('Unauthorized', { status: 401 })
}
})
import { serve } from 'https://deno.land/std@0.165.0/http/server.ts'
import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'

serve(async (req: Request) => {
try {
const payload = await req.text() // Get the raw request body as text
const hookSecret = Deno.env.get('HOOK_SECRETS') // Secret set in your environment
const headers = Object.fromEntries(req.headers) // Extract headers

// Verify the webhook signature
const wh = new Webhook(hookSecret) // Initialize the Webhook verifier
const data = wh.verify(payload, headers) // Verify the payload and parse it

const user = data.user // The user object is in the `data` field
const slackWebhook = Deno.env.get('SLACK_WEBHOOK_URL') // Slack webhook URL from environment

// Send Slack alert if user data is available
if (user && slackWebhook) {
const message = {
text: `πŸŽ‰ New user signup!\n- Email: ${user.email}\n- Signed up at: ${user.created_at}`,
}

const slackResponse = await fetch(slackWebhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message),
})

if (!slackResponse.ok) {
console.error(
'Failed to send Slack message:',
await slackResponse.text()
)
}
}

// Return any custom claims (optional)
return new Response(
JSON.stringify({
customClaims: {
example_claim: 'new_user', // Example custom claim
},
}),
{ headers: { 'Content-Type': 'application/json' } }
)
} catch (error) {
console.error('Error verifying or processing the webhook:', error)
return new Response('Unauthorized', { status: 401 })
}
})
mauerbac
mauerbacOPβ€’8mo ago
this didn't work. and when i tried to login it broke my app
No description
mauerbac
mauerbacOPβ€’8mo ago
but the edge function logs for this show nothing
mauerbac
mauerbacOPβ€’8mo ago
I also tried to use your postgres function and got errors anytime i tried to save it
No description
mauerbac
mauerbacOPβ€’8mo ago
It said net wasn't enabled in my logs or an error around that
silentworks
silentworksβ€’8mo ago
Go to your database extensions and enable pg_net.
mauerbac
mauerbacOPβ€’8mo ago
thanks @silentworks will try this now. Should I try the database function method or the edge functions route?
CREATE OR REPLACE FUNCTION public.notify_new_signup()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
payload JSONB;
BEGIN
-- Construct the payload
payload := jsonb_build_object(
'id', NEW.id,
'email', NEW.email,
'created_at', NEW.created_at,
'app', 'myapp.com' -- Include app information if needed
);

-- Notify the Edge Function
PERFORM pg_notify('edge_function_event', payload::TEXT);

RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION public.notify_new_signup()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
payload JSONB;
BEGIN
-- Construct the payload
payload := jsonb_build_object(
'id', NEW.id,
'email', NEW.email,
'created_at', NEW.created_at,
'app', 'myapp.com' -- Include app information if needed
);

-- Notify the Edge Function
PERFORM pg_notify('edge_function_event', payload::TEXT);

RETURN NEW;
END;
$$;
Will something like this work with pg_net enabled?
silentworks
silentworksβ€’8mo ago
As a developer you should explore your options and see what works for you. It's a part of the journey.
mauerbac
mauerbacOPβ€’8mo ago
Yeah, that's fair. I spent like 1.5hrs last night exploring every option so was curious for a bit of a suggestion lol When I tried the edge function route I never saw the function was invoked or any logs in the supabase edge function tab. But ill try this again when that setting enabled
silentworks
silentworksβ€’8mo ago
1.5hrs is a small amount of time to explore. Implement all the ways you've learned about and see which works best for you. Your own personal experience trumps any suggestion you will be given.
mauerbac
mauerbacOPβ€’8mo ago
Let me ask one question then. If i call PERFORM pg_notify('edge_function_event', payload::TEXT); and call my edge function how would you debug that function not being invoked?
silentworks
silentworksβ€’8mo ago
I've never used PERFORM nor pg_notify before, so I cannot advice you on that.
garyaustin
garyaustinβ€’8mo ago
Perform is just Select with no requirement to return data. pg_notify will not work with edge functions. It is normally a call between postgres and another postgres or a server using a direct database connection.
mauerbac
mauerbacOPβ€’8mo ago
Gotcha. Does this make sense to ppl?
CREATE OR REPLACE FUNCTION public.notify_slack()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
slack_webhook_url TEXT := 'https://hooks.slack.com/services/{my_webhook}'; -- Replace with your Slack webhook URL
slack_message TEXT;
BEGIN
-- Construct the Slack message
slack_message := jsonb_build_object(
'text', 'πŸŽ‰ A new record was added!\\n' ||
'- Table: ' || TG_TABLE_NAME || '\\n' ||
'- ID: ' || NEW.id || '\\n' ||
'- Data: ' || row_to_json(NEW)::TEXT
)::TEXT;

-- Log the payload for debugging
RAISE NOTICE 'Slack message payload: %', slack_message;

-- Send the message to Slack
PERFORM http_post(
slack_webhook_url,
slack_message,
'application/json'
);

RETURN NEW; -- Required for triggers
END;
$function$;
CREATE OR REPLACE FUNCTION public.notify_slack()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
slack_webhook_url TEXT := 'https://hooks.slack.com/services/{my_webhook}'; -- Replace with your Slack webhook URL
slack_message TEXT;
BEGIN
-- Construct the Slack message
slack_message := jsonb_build_object(
'text', 'πŸŽ‰ A new record was added!\\n' ||
'- Table: ' || TG_TABLE_NAME || '\\n' ||
'- ID: ' || NEW.id || '\\n' ||
'- Data: ' || row_to_json(NEW)::TEXT
)::TEXT;

-- Log the payload for debugging
RAISE NOTICE 'Slack message payload: %', slack_message;

-- Send the message to Slack
PERFORM http_post(
slack_webhook_url,
slack_message,
'application/json'
);

RETURN NEW; -- Required for triggers
END;
$function$;
with this trigger
CREATE TRIGGER notify_new_signup_trigger
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION public.notify_slack();
CREATE TRIGGER notify_new_signup_trigger
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION public.notify_slack();
This doesnt work 😿
garyaustin
garyaustinβ€’8mo ago
Use raise log 'blah = %',var; to get messages in postgres log of dashboard. Also check for postgres errors. I would use pg_net if you are going to do perform (no reponse).
mauerbac
mauerbacOPβ€’8mo ago
You can do triggers off Auth.users right? In the UI it says you cant which confused me
garyaustin
garyaustinβ€’8mo ago
You can. You have to do it with SQL. Your function will also have to be security definer type.
mauerbac
mauerbacOPβ€’8mo ago
So i just tried again with log statements but i dont see anything in postgres logs
No description
mauerbac
mauerbacOPβ€’8mo ago
So maybe the trigger isnt working? Tried with pg_net too
CREATE OR REPLACE FUNCTION public.notify_slack()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
DECLARE
slack_webhook_url TEXT := 'https://hooks.slack.com/services/TFK99FKKP/blahj';

slack_message JSONB;
BEGIN
-- Log the trigger firing
RAISE LOG 'notify_slack trigger fired for table: %', TG_TABLE_NAME;

-- Construct the Slack message
slack_message := jsonb_build_object(
'text', 'πŸŽ‰ A new record was added!\\n' ||
'- Table: ' || TG_TABLE_NAME || '\\n' ||
'- ID: ' || NEW.id || '\\n' ||
'- Data: ' || row_to_json(NEW)::TEXT
);

-- Log the constructed Slack message
RAISE LOG 'Slack message constructed: %', slack_message;

-- Send the message using pg_net
PERFORM net.http_post(
url := slack_webhook_url,
body := slack_message::TEXT,
headers := jsonb_build_object('Content-Type', 'application/json')
);

-- Log that the HTTP POST was performed
RAISE LOG 'HTTP POST request sent to Slack webhook URL: %', slack_webhook_url;

RETURN NEW; -- Required for triggers
END;
$function$;
CREATE OR REPLACE FUNCTION public.notify_slack()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
DECLARE
slack_webhook_url TEXT := 'https://hooks.slack.com/services/TFK99FKKP/blahj';

slack_message JSONB;
BEGIN
-- Log the trigger firing
RAISE LOG 'notify_slack trigger fired for table: %', TG_TABLE_NAME;

-- Construct the Slack message
slack_message := jsonb_build_object(
'text', 'πŸŽ‰ A new record was added!\\n' ||
'- Table: ' || TG_TABLE_NAME || '\\n' ||
'- ID: ' || NEW.id || '\\n' ||
'- Data: ' || row_to_json(NEW)::TEXT
);

-- Log the constructed Slack message
RAISE LOG 'Slack message constructed: %', slack_message;

-- Send the message using pg_net
PERFORM net.http_post(
url := slack_webhook_url,
body := slack_message::TEXT,
headers := jsonb_build_object('Content-Type', 'application/json')
);

-- Log that the HTTP POST was performed
RAISE LOG 'HTTP POST request sent to Slack webhook URL: %', slack_webhook_url;

RETURN NEW; -- Required for triggers
END;
$function$;
garyaustin
garyaustinβ€’8mo ago
If you are not getting errors and not getting log messages then your function is likely not being called. Your trigger SQL looks correct.
mauerbac
mauerbacOPβ€’8mo ago
Yeah, so strange 😦
garyaustin
garyaustinβ€’8mo ago
Are you getting a new user showing up in the auth.users table?
mauerbac
mauerbacOPβ€’8mo ago
No description
mauerbac
mauerbacOPβ€’8mo ago
Yeah, in the uathentication tab i see a users table
garyaustin
garyaustinβ€’8mo ago
And a new user gets added to it?
mauerbac
mauerbacOPβ€’8mo ago
Yeah the user is being created I should see it in the postgres log collection right?
garyaustin
garyaustinβ€’8mo ago
Yes. those are raise log messages from a function I have.
No description
mauerbac
mauerbacOPβ€’8mo ago
yeah, i dont think the function or trigger is being called
garyaustin
garyaustinβ€’8mo ago
Where did you get your trigger screen from? Mine looks different but there may be more than one spot.
No description
mauerbac
mauerbacOPβ€’8mo ago
No description
mauerbac
mauerbacOPβ€’8mo ago
looks similar can i see what function you used?
garyaustin
garyaustinβ€’8mo ago
No that is different than the first one you showed. And the function name is not the one you are showing.
mauerbac
mauerbacOPβ€’8mo ago
oh, the other screenshot was from a query showing all triggers i have
garyaustin
garyaustinβ€’8mo ago
So you are calling a different function. notify_new_signup
mauerbac
mauerbacOPβ€’8mo ago
oh wait weird
garyaustin
garyaustinβ€’8mo ago
versus notify_slack
mauerbac
mauerbacOPβ€’8mo ago
Right lol let me rename the function to notify_new-signup When you enabled pg_net extension which table did you enable it on? Extensions, public or auth?
garyaustin
garyaustinβ€’8mo ago
extensions then you call it with net.http_post
mauerbac
mauerbacOPβ€’8mo ago
enabled it with extensions. So now i see errors in the log which is good. it said function net.http_post(url => text, body => text, headers => jsonb) does not exist but strange b/c i have pg_net enabled
garyaustin
garyaustinβ€’8mo ago
Your parameters are likely not the right types.
mauerbac
mauerbacOPβ€’8mo ago
ok it worked!! 🎈 i switched back to http_post , got an error then tried public.http_post which worked. I bet for net i had to do extensions.net.http_post but in any event thank you so much πŸ™‚
garyaustin
garyaustinβ€’8mo ago
It would be net.http_post. But the parameters are not the same for the two calls. Some are jsonb instead of text. I would use pg_net unless you are going to check for error in an auth function. The issue is if your webhook takes several seconds to respond, auth will timeout and error back to the client and then you create user friction.
mauerbac
mauerbacOPβ€’8mo ago
Oh that’s a good point. Is your function calling slack? Curious to see what parameters you used?
garyaustin
garyaustinβ€’8mo ago
I don't call an external endpoint from my auth trigger.
mauerbac
mauerbacOPβ€’8mo ago
Do you post to Slack?
garyaustin
garyaustinβ€’8mo ago
No. Another user commented here that they do.
mauerbac
mauerbacOPβ€’8mo ago
Im using this now
CREATE OR REPLACE FUNCTION public.notify_new_signup()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$

BEGIN
-- Log the trigger firing
RAISE LOG 'notify_slack trigger fired for table: %', TG_TABLE_NAME;
PERFORM net.http_post(
'https://hooks.slack.com/services/TFK99FKKP/B086TQY1CG4/blah',
jsonb_build_object(
'id', NEW.id,
'email', NEW.email,
'app', 'speedmoji'
)
);


RETURN NEW;
END;
$function$;
CREATE OR REPLACE FUNCTION public.notify_new_signup()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$

BEGIN
-- Log the trigger firing
RAISE LOG 'notify_slack trigger fired for table: %', TG_TABLE_NAME;
PERFORM net.http_post(
'https://hooks.slack.com/services/TFK99FKKP/B086TQY1CG4/blah',
jsonb_build_object(
'id', NEW.id,
'email', NEW.email,
'app', 'speedmoji'
)
);


RETURN NEW;
END;
$function$;
but now i see no logs again 😦 and it snot working why is this so complicated lol
garyaustin
garyaustinβ€’8mo ago
You changed the function name?
mauerbac
mauerbacOPβ€’8mo ago
kept it notify_new_signup and see that in my auth db triggers so thats not an issue
garyaustin
garyaustinβ€’8mo ago
If the function is being called you will see your log message or an error in the Postgres log. I've noticed sometimes the log can be delayed a minute or so.
mauerbac
mauerbacOPβ€’8mo ago
ok, i see the log there. reviewing these docs . i think slack requires a header for app json https://supabase.com/docs/guides/database/extensions/pg_net
pg_net: Async Networking | Supabase Docs
pg_net: an async networking extension for PostgreSQL.
mauerbac
mauerbacOPβ€’8mo ago
ok i got it to work!!! If it helps anyone this worked
CREATE OR REPLACE FUNCTION public.notify_new_signup()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
DECLARE
request_id BIGINT;
BEGIN
-- Log the trigger firing
RAISE LOG 'notify_slack trigger fired for table: %', TG_TABLE_NAME;

-- Send the message using net.http_post with proper Slack payload
request_id := net.http_post(
url := 'YOUR_SLACK_WEBHOOK',
body := jsonb_build_object(
'text', 'πŸŽ‰ A new user signed up!\\n' ||
'- ID: ' || NEW.id || '\\n' ||
'- Email: ' || NEW.email || '\\n' ||
'- App: speedmoji'
),
headers := jsonb_build_object('Content-Type', 'application/json')
);

-- Log the request ID
RAISE LOG 'HTTP POST request sent with request_id: %', request_id;

RETURN NEW; -- Required for triggers
END;
$function$;
CREATE OR REPLACE FUNCTION public.notify_new_signup()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
DECLARE
request_id BIGINT;
BEGIN
-- Log the trigger firing
RAISE LOG 'notify_slack trigger fired for table: %', TG_TABLE_NAME;

-- Send the message using net.http_post with proper Slack payload
request_id := net.http_post(
url := 'YOUR_SLACK_WEBHOOK',
body := jsonb_build_object(
'text', 'πŸŽ‰ A new user signed up!\\n' ||
'- ID: ' || NEW.id || '\\n' ||
'- Email: ' || NEW.email || '\\n' ||
'- App: speedmoji'
),
headers := jsonb_build_object('Content-Type', 'application/json')
);

-- Log the request ID
RAISE LOG 'HTTP POST request sent with request_id: %', request_id;

RETURN NEW; -- Required for triggers
END;
$function$;
mauerbac
mauerbacOPβ€’8mo ago
Thank you @garyaustin again! Also put my learnings here -> https://github.com/mauerbac/supabase_auth_slackbot/tree/main
GitHub
GitHub - mauerbac/supabase_auth_slackbot: Tutorial to get alerts in...
Tutorial to get alerts in Slack when users signup with Supabase Auth - mauerbac/supabase_auth_slackbot
mauerbac
mauerbacOPβ€’8mo ago
funnily i broke my email auth from testing so much
No description
garyaustin
garyaustinβ€’8mo ago
Are you using the built in SMTP?
mauerbac
mauerbacOPβ€’8mo ago
Yes that should reset with time right?
garyaustin
garyaustinβ€’8mo ago
If so you should get your own provider. Yes 1 hour.
mauerbac
mauerbacOPβ€’8mo ago
yeah i should set that up thanks again
homj
homjβ€’8mo ago
thanks, this really helped me!

Did you find this page helpful?