restrict download-bus-stops to authenticated admin user
This commit is contained in:
parent
273a0cd6d6
commit
b4da3350db
|
@ -1,5 +1,5 @@
|
|||
TRIGGER_API_KEY=tr_dev_f9kjPUxxfZO79OSjy1su
|
||||
TRIGGER_API_URL=https://api.trigger.dev
|
||||
NEXT_PUBLIC_TRIGGER_PUBLIC_API_KEY=pk_dev_0wewTNYuGo93XkoHaU9s
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://yexfstvjfxhursmqcqcu.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlleGZzdHZqZnhodXJzbXFjcWN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTI2ODg2OTMsImV4cCI6MjAyODI2NDY5M30.Mq1aKeO_SlwS-kEdsSn7VhHxHxNx1eeejd6jnBC7VJw
|
||||
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU
|
||||
|
|
|
@ -12,12 +12,13 @@ client.defineJob({
|
|||
id: "bus-stop-sanity-check",
|
||||
name: "Sanity Check Potential Job Arrivals",
|
||||
version: "0.1.1",
|
||||
enabled: false, // 1 run per insert is a bit too insane.
|
||||
trigger: db.onInserted({
|
||||
table: 'potential_arrivals',
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.runTask("log-payload", async function() {
|
||||
console.log(payload);
|
||||
io.logger.log(JSON.stringify(payload));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
import { SupabaseClient, createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
|
||||
interface StraetoVariables {
|
||||
[key: string]: number | string;
|
||||
|
@ -97,31 +97,48 @@ async function getStops(): Promise<Array<StraetoBusStop>> {
|
|||
return body.data.GtfsStops.results;
|
||||
}
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const busStops = await getStops();
|
||||
async function verifyUserIsAdmin(userClient: SupabaseClient): Promise<boolean> {
|
||||
const user = await userClient.auth.getUser();
|
||||
const metadata = user.data.user?.app_metadata;
|
||||
|
||||
if (!metadata || (metadata && !metadata.claims_admin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const authHeader = req.headers.get("Authorization")!;
|
||||
const supabaseClient = createClient(
|
||||
|
||||
const userClient = createClient(
|
||||
Deno.env.get("SUPABASE_URL") ?? "",
|
||||
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
|
||||
{ global: { headers: { Authorization: authHeader } } },
|
||||
);
|
||||
|
||||
const secureClient = createClient(
|
||||
Deno.env.get("SUPABASE_URL") ?? "",
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
|
||||
);
|
||||
|
||||
if (!(await verifyUserIsAdmin(userClient))) {
|
||||
return new Response(
|
||||
JSON.stringify({ status: "denied", error: "must be an admin" }),
|
||||
{ status: 403, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
|
||||
const busStops = await getStops();
|
||||
|
||||
console.log("inserting", busStops.length, "rows");
|
||||
const toInsert = busStops.map(toDbBusStop);
|
||||
|
||||
const insert = await supabaseClient.from("bus_stops")
|
||||
const insert = await secureClient.from("bus_stops")
|
||||
.upsert(toInsert, { ignoreDuplicates: true });
|
||||
|
||||
console.log(insert.statusText, insert.error?.code, insert.error?.message);
|
||||
|
||||
console.log("rows inserted", insert.count);
|
||||
|
||||
// const { name } = await req.json()
|
||||
// const data = {
|
||||
// message: `Hello ${name}!`,
|
||||
// }
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
"status": insert.statusText,
|
||||
|
@ -139,7 +156,6 @@ Deno.serve(async (req) => {
|
|||
|
||||
curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/download-bus-stops' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{"name":"Functions"}'
|
||||
--header 'Content-Type: application/json'
|
||||
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
CREATE OR REPLACE FUNCTION is_claims_admin() RETURNS "bool"
|
||||
LANGUAGE "plpgsql"
|
||||
AS $$
|
||||
BEGIN
|
||||
IF session_user = 'authenticator' THEN
|
||||
--------------------------------------------
|
||||
-- To disallow any authenticated app users
|
||||
-- from editing claims, delete the following
|
||||
-- block of code and replace it with:
|
||||
-- RETURN FALSE;
|
||||
--------------------------------------------
|
||||
IF extract(epoch from now()) > coalesce((current_setting('request.jwt.claims', true)::jsonb)->>'exp', '0')::numeric THEN
|
||||
return false; -- jwt expired
|
||||
END IF;
|
||||
If current_setting('request.jwt.claims', true)::jsonb->>'role' = 'service_role' THEN
|
||||
RETURN true; -- service role users have admin rights
|
||||
END IF;
|
||||
IF coalesce((current_setting('request.jwt.claims', true)::jsonb)->'app_metadata'->'claims_admin', 'false')::bool THEN
|
||||
return true; -- user has claims_admin set to true
|
||||
ELSE
|
||||
return false; -- user does NOT have claims_admin set to true
|
||||
END IF;
|
||||
--------------------------------------------
|
||||
-- End of block
|
||||
--------------------------------------------
|
||||
ELSE -- not a user session, probably being called from a trigger or something
|
||||
return true;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_my_claims() RETURNS "jsonb"
|
||||
LANGUAGE "sql" STABLE
|
||||
AS $$
|
||||
select
|
||||
coalesce(nullif(current_setting('request.jwt.claims', true), '')::jsonb -> 'app_metadata', '{}'::jsonb)::jsonb
|
||||
$$;
|
||||
CREATE OR REPLACE FUNCTION get_my_claim(claim TEXT) RETURNS "jsonb"
|
||||
LANGUAGE "sql" STABLE
|
||||
AS $$
|
||||
select
|
||||
coalesce(nullif(current_setting('request.jwt.claims', true), '')::jsonb -> 'app_metadata' -> claim, null)
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_claims(uid uuid) RETURNS "jsonb"
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER SET search_path = public
|
||||
AS $$
|
||||
DECLARE retval jsonb;
|
||||
BEGIN
|
||||
IF NOT is_claims_admin() THEN
|
||||
RETURN '{"error":"access denied"}'::jsonb;
|
||||
ELSE
|
||||
select raw_app_meta_data from auth.users into retval where id = uid::uuid;
|
||||
return retval;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_claim(uid uuid, claim text) RETURNS "jsonb"
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER SET search_path = public
|
||||
AS $$
|
||||
DECLARE retval jsonb;
|
||||
BEGIN
|
||||
IF NOT is_claims_admin() THEN
|
||||
RETURN '{"error":"access denied"}'::jsonb;
|
||||
ELSE
|
||||
select coalesce(raw_app_meta_data->claim, null) from auth.users into retval where id = uid::uuid;
|
||||
return retval;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_claim(uid uuid, claim text, value jsonb) RETURNS "text"
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER SET search_path = public
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NOT is_claims_admin() THEN
|
||||
RETURN 'error: access denied';
|
||||
ELSE
|
||||
update auth.users set raw_app_meta_data =
|
||||
raw_app_meta_data ||
|
||||
json_build_object(claim, value)::jsonb where id = uid;
|
||||
return 'OK';
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION delete_claim(uid uuid, claim text) RETURNS "text"
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER SET search_path = public
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NOT is_claims_admin() THEN
|
||||
RETURN 'error: access denied';
|
||||
ELSE
|
||||
update auth.users set raw_app_meta_data =
|
||||
raw_app_meta_data - claim where id = uid;
|
||||
return 'OK';
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
NOTIFY pgrst, 'reload schema';
|
Loading…
Reference in New Issue