од Gabriel L. Manor Supabase olakšava dodavanje autentifikacije u vašu aplikaciju uz ugrađenu podršku za e-poštu, OAuth i magične veze, ali dok Supabase Auth upravlja ko su vaši korisnici, često vam je potreban i autorizacioni sloj. Supabase nudi odličan backend sa ugrađenim auth i Row Level Security (RLS), upravljanjem Посебно оне засноване на Daleko je od lakoće. fine-grained permissions relationships between users and data Možda želite da ograničite radnje kao što su uređivanje ili brisanje podataka vlasnicima resursa, da sprečite korisnike da glasaju za svoj sadržaj ili da primenite različite dozvole za različite uloge korisnika. Овај туториал пролази кроз како имплементирати у а апликације . Supabase authentication and authorization Next.js We'll start with за логин и управљање сесијом, а затим додајте Коришћење Наметнута кроз и а . . Supabase Auth authorization rules Kontrola pristupa zasnovanog na vezama (ReBAC) Supabase Edge Functions local Policy Decision Point (PDP) На крају, имаћете апликацију за заједничко гласање у реалном времену која подржава и јавне и заштићене акције - и флексибилан систем овлашћења који се може еволуирати како ваша апликација расте. Šta mi gradimo У овом водичу, ми ћемо изградити апликацију за гласање у реалном времену користећи и То показује и аутентификацију и овлашћење у акцији. Supabase Next.js Апликација омогућава корисницима да креирају анкете, гласају за друге и управљају само својим садржајем. za login/signup i kako izvršiti који контролише ко може гласати, уредити или избрисати. Supabase Auth authorization policies Користићемо суштинске карактеристике Супасеа - , , , , и У комбинацији са а Модел за спровођење правила приступа по кориснику и по ресурсу. Auth Postgres RLS Realtime Edge Functions Relationship-Based Access Control (ReBAC) Технички стацк Supabase – Backend-as-a-service za bazu podataka, autentifikaciju, funkcije u realnom vremenu i funkcije Edge Next.js – Фронтенд оквир за изградњу апликације УИ и АПИ руте Permit.io – (за РеБАЦ) да дефинише и процени логику овлашћења преко ПДП-а Субасе ЦЛИ – За управљање и распоређивање Едге Функција локално и у производњи суппозиторије Следећи: JS Дозволите ми Преузети CLI Предуслови Node.js је инсталиран База рачуна Дозволите.io налог Упознавање са React/Next.js Početak projekta Repo Šta ova aplikacija može da uradi? Демо апликација је платформа за гласање у реалном времену изграђена са Next.js и Supabase, где корисници могу креирати анкете и гласати за друге. Сваки корисник (аутентификован или не) може да прегледа листу јавних истраживања Само аутентични корисници могу креирати анкете и гласати Корисник не може гласати на анкети коју је креирао Samo kreator ankete može da ga uređuje ili briše Tutorial Pregled Пратићемо ове опште кораке: Подешавање пројекта Субасе, шеме, аутх и РЛС Изградите основне функције апликације као што су креирање анкета и гласање Правила о одобрењу модела дефинишу улоге и правила у Permit.io Креирајте функције Субасе Едге за синхронизацију корисника, додељивање улога и проверавање дозвола Имплементирати политике у апликацији фронтенд користећи те функције ивице Hajde da počnemo - Setting up Supabase in the Project Postavljanje supbaze u projektu Опционално: Clone the Starter Template I've already created a је са свим кодом који вам је потребан да бисте започели тако да се можемо усредсредити на имплементацију Субасе и Пермит.ио. Почетак Храма GitHub Projekat možete klonirati tako što ćete pokrenuti sledeću komandu: git clone <https://github.com/permitio/supabase-fine-grained-authorization> Након што сте клонирали пројекат, пређите у директоријум пројекта и инсталирајте зависности: cd realtime-polling-app-nextjs-supabase-permitio npm install Creating a new Project in Supabase Да бисте започели: Идите на https://supabase.com и пријавите се или креирајте налог. Кликните на "Нови пројекат" и попуните име пројекта, лозинку и регион. Jednom kada se kreira, idite na Project Settings → API i zapišite svoj Project URL i Anon Key – potrebni su vam kasnije. Подешавање аутентификације и базе података у Субаси Користићемо уграђену е-пошту / лозинку аутх Супабесе: У бочној траци идите на аутентификацију → провајдери Омогућите провајдера е-поште (Opcionalno) Deaktivirajte potvrdu e-pošte za testiranje, ali držite je uključenu za proizvodnju Креирање шеме базе података Ова апликација користи три главне табеле: , , и • Користите u Supabase Dashboard i pokrenite sledeće: polls options votes SQL Editor -- Create a polls table CREATE TABLE polls ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, question TEXT NOT NULL, created_by UUID REFERENCES auth.users(id) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), creator_name TEXT NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, ); -- Create an options table CREATE TABLE options ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, poll_id UUID REFERENCES polls(id) ON DELETE CASCADE, text TEXT NOT NULL, ); -- Create a votes table CREATE TABLE votes ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, poll_id UUID REFERENCES polls(id) ON DELETE CASCADE, option_id UUID REFERENCES options(id) ON DELETE CASCADE, user_id UUID REFERENCES auth.users(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), UNIQUE(poll_id, user_id) ); Omogućavanje bezbednosti na nivou linije (RLS) омогућава за сваку табелу и дефинисати политике: РЛС -- Polls policies ALTER TABLE polls ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view polls" ON polls FOR SELECT USING (true); CREATE POLICY "Authenticated users can create polls" ON polls FOR INSERT TO authenticated WITH CHECK (auth.uid() = created_by); -- Options policies ALTER TABLE options ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view options" ON options FOR SELECT USING (true); CREATE POLICY "Poll creators can add options" ON options FOR INSERT TO authenticated WITH CHECK ( EXISTS ( SELECT 1 FROM polls WHERE id = options.poll_id AND created_by = auth.uid() ) ); -- Votes policies ALTER TABLE votes ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view votes" ON votes FOR SELECT USING (true); CREATE POLICY "Authenticated users can vote once" ON votes FOR INSERT TO authenticated WITH CHECK ( auth.uid() = user_id AND NOT EXISTS ( SELECT 1 FROM polls WHERE id = votes.poll_id AND created_by = auth.uid() ) ); Да бисте користили функције Супабесе у реалном времену: У бочној траци, идите на Табела Едитор За сваку од три табеле (испитивања, опције, гласови): Кликните на три тачке → Еди Табела Toggle "Омогући Реално време" Сачувајте промене Implementacija Supabase Email Authentication u aplikaciji У овој демо апликацији, свако може да прегледа листу анкета доступних на апликацији, и активних и истекалих. Да бисте прегледали детаље анкете, управљали или гласали за било коју анкету, корисник мора бити пријављен. Ми ћемо користити е-пошту и лозинку као средство аутентификације за овај пројекат. У вашем пројекту Next.js чувајте своје поверења Субасе у • : .env.local NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key Ажурирајте компоненту за пријављивање да бисте управљали и регистрацијом и пријављивањем путем е-поште / лозинке: import { useState } from "react"; import { createClient } from "@/utils/supabase/component"; const LogInButton = () => { const supabase = createClient(); async function logIn() { const { error } = await supabase.auth.signInWithPassword({ email, password, }); if (error) { setError(error.message); } else { setShowModal(false); } } async function signUp() { const { error } = await supabase.auth.signUp({ email, password, options: { data: { user_name: userName, }, }, }); if (error) { setError(error.message); } else { setShowModal(false); } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); if (isLogin) { await logIn(); } else { await signUp(); } }; return ( <> <button onClick={() => setShowModal(true)} className="flex items-center gap-2 p-2 bg-gray-800 text-white rounded-md"> Log In </button> ... </> ); }; export default LogInButton; Овде, ми користимо Супбасе'с метод за пријављивање у корисника и метод за пријављивање новог корисника са њиховом е-поштом и лозинком. Такође чувамо корисничко име у Поље у метаподацима корисника. signInWithPassword signUp user_name You can also use да се пријавите за кориснике и преусмерите их: supabase.auth.signOut() import { createClient } from "@/utils/supabase/component"; import { useRouter } from "next/router"; const LogOutButton = ({ closeDropdown }: { closeDropdown: () => void }) => { const router = useRouter(); const supabase = createClient(); const handleLogOut = async () => { await supabase.auth.signOut(); closeDropdown(); router.push("/"); }; return ( ... ); }; export default LogOutButton; Овде, ми користимо метод из Субасе да се пријавите корисника и преусмери их на почетну страницу. signOut Слушање за промене у статусу аутентификације корисника Слушање промена у статусу аутентификације корисника омогућава нам да ажурирамо УИ на основу статуса аутентификације корисника. Прикажи / сакриј елементе корисничког интерфејса као што су дугмад за пријављивање / пријављивање Conditionally restrict access to protected pages (like voting or managing polls) Обезбедите да само аутентификовани корисници могу да обављају ограничене радње Mi ćemo koristiti да слушате ове догађаје и ажурирате апликацију у складу с тим. supabase.auth.onAuthStateChange() In the Layout.tsx file: Track Global Auth State import React, { useEffect, useState } from "react"; import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Layout = ({ children }: { children: React.ReactNode }) => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); }; export default Layout; Ограничити приступ заштићеним страницама На страницама као or , you should also listen for authentication state changes to prevent unauthenticated users from accessing them. poll details poll management Evo kako izgleda u : pages/polls/[id].tsx import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Page = () => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); setLoading(false); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); export default Page; Сличан модел се примењује у , где корисници треба да виде своје анкете само ако су пријављени: pages/polls/manage.tsx import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Page = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() => { const fetchUser = async () => { const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); if (!session?.user) { setLoading(false); } }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); }; export default Page; Ови обрасци осигуравају да ваш кориснички интерфејс одражава тренутни статус аутентификације корисника и да представља основу за провере овлашћења које ћемо додати касније. object when calling the Edge Function to determine whether a user is allowed to vote or manage a specific poll. user checkPermission Изградња функционалности апликације Polling With Supabase configured and authentication working, we can now build the core functionality of the polling app. In this section, we’ll cover: Креирање нових анкета Претраживање и приказивање анкета у реалном времену Uvođenje sistema glasanja Ово нам даје основно понашање апликација које ћемо ускоро заштитити са фино зрнатим дозволама. Креирање нових анкета Корисници морају бити пријављени да би креирали анкете. Свака анкета укључује питање, датум истека и скуп опција. Такође снимамо ко је креирао анкету тако да касније можемо користити ту везу за контролу приступа. unutra , преузмите аутентификованог корисника и користите Субасе да бисте убацили анкету и његове опције: NewPoll.tsx import React, { useEffect, useState } from "react"; import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const NewPoll = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (question.trim() && options.filter(opt => opt.trim()).length < 2) { setErrorMessage("Please provide a question and at least two options."); return; } // Create the poll const { data: poll, error: pollError } = await supabase .from("polls") .insert({ question, expires_at: new Date(expiryDate).toISOString(), created_by: user?.id, creator_name: user?.user_metadata?.user_name, }) .select() .single(); if (pollError) { console.error("Error creating poll:", pollError); setErrorMessage(pollError.message); return; } // Create the options const { error: optionsError } = await supabase.from("options").insert( options .filter(opt => opt.trim()) .map(text => ({ poll_id: poll.id, text, })) ); if (!optionsError) { setSuccessMessage("Poll created successfully!"); handleCancel(); } else { console.error("Error creating options:", optionsError); setErrorMessage(optionsError.message); } }; return ( ... ); }; export default NewPoll; Касније ћемо позвати функцију Едге овде да бисмо доделили улогу "креатора" у Permit.io. Fetching and Displaying Polls Polls are divided into (not yet expired) and Можете их преузети помоћу Субасе упита филтрираних по тренутној временској ознаци и поставити претплате у реалном времену да одражавају промене одмах. active past Пример из : pages/index.tsx import { PollProps } from "@/helpers"; import { createClient } from "@/utils/supabase/component"; export default function Home() { const supabase = createClient(); useEffect(() => { const fetchPolls = async () => { setLoading(true); const now = new Date().toISOString(); try { // Fetch active polls const { data: activePolls, error: activeError } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .gte("expires_at", now) .order("created_at", { ascending: false }); if (activeError) { console.error("Error fetching active polls:", activeError); return; } // Fetch past polls const { data: expiredPolls, error: pastError } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .lt("expires_at", now) .order("created_at", { ascending: false }); if (pastError) { console.error("Error fetching past polls:", pastError); return; } setCurrentPolls(activePolls); setPastPolls(expiredPolls); } catch (error) { console.error("Unexpected error fetching polls:", error); } finally { setLoading(false); } }; fetchPolls(); // Set up real-time subscription on the polls table: const channel = supabase .channel("polls") .on( "postgres_changes", { event: "*", schema: "public", table: "polls", }, fetchPolls ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, []); return ( ... ); } Viewing and Managing User Polls Here, we are fetching active and past polls from the Табела у Субаси. Такође постављамо претплату у реалном времену да бисмо слушали промене у table so that we can update the UI with the latest poll data. To differentiate between active and past polls, we are comparing the expiry date of each poll with the current date. polls polls Ажурирање на страница за преузимање и приказивање само анкета које је креирао корисник: pages/manage.tsx import { PollProps } from "@/helpers"; const Page = () => { useEffect(() => { if (!user?.id) return; const fetchPolls = async () => { try { const { data, error } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .eq("created_by", user.id) .order("created_at", { ascending: false }); if (error) { console.error("Error fetching polls:", error); return; } setPolls(data || []); } catch (error) { console.error("Unexpected error fetching polls:", error); } finally { setLoading(false); } }; fetchPolls(); // Set up real-time subscription const channel = supabase .channel(`polls_${user.id}`) .on( "postgres_changes", { event: "*", schema: "public", table: "polls", filter: `created_by=eq.${user.id}`, }, fetchPolls ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [user]); return ( ... ); }; export default Page; Овде добијамо само анкете које је креирао корисник и слушамо ажурирања у реалном времену у Табела тако да је кориснички интерфејс ажуриран са најновијим подацима анкете. polls Такође, ажурирајте компоненту тако да ако је пријављени корисник креатор анкете, иконе за уређивање и брисање анкете ће им се приказати на анкети. PollCard import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const PollCard = ({ poll }: { poll: PollProps }) => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const supabase = createClient(); const fetchUser = async () => { const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); setLoading(false); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... )} </Link> ); }; export default PollCard; Дакле, сада, на картици анкете, ако је пријављени корисник креатор анкете, иконе за уређивање и брисање анкете ће им се приказати. Имплементација система гласања Логика гласања спроводи: Only one vote per user per poll Kreatori ne mogu da glasaju na sopstvenim anketama Votes are stored in the table votes Rezultati se prikazuju i ažuriraju u realnom vremenu Хајде да разбијемо како то функционише у Компоненте : ViewPoll.tsx Потребан нам је идентификатор тренутног корисника да бисмо утврдили право гласа и снимили њихов глас. Fetch the Logged-In User import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const ViewPoll = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() const fetchUser = async () => { const { data: { user }, } = await supabase.auth.getUser(); setUser(user); }; fetchUser(); }, []); Када имамо корисника, добијамо: Load Poll Details and Check Voting Status Сама анкета (укључујући опције и бројање гласова) Da li je ovaj korisnik već glasao Такође их поново позивамо касније у ажурирањима у реалном времену. useEffect(() => { if (!user) { return; } const checkUserVote = async () => { const { data: votes } = await supabase .from("votes") .select("id") .eq("poll_id", query.id) .eq("user_id", user.id) .single(); setHasVoted(!!votes); setVoteLoading(false); }; const fetchPoll = async () => { const { data } = await supabase .from("polls") .select( ` *, options ( id, text, votes (count) ) ` ) .eq("id", query.id) .single(); setPoll(data); setPollLoading(false); checkUserVote(); }; fetchPoll(); Listen for Real-Time Updates Прихватамо промене у Табела, намењена овом гласању.Када се гласа ново гласање, добијамо ажуриране податке о гласању и статус гласања. votes const channel = supabase .channel(`poll-${query.id}`) .on( "postgres_changes", { event: "*", schema: "public", table: "votes", filter: `poll_id=eq.${query.id}`, }, () => { fetchPoll(); checkUserVote(); } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [query.id, user]); Handle the Vote Submission If the user hasn’t voted and is allowed to vote (we’ll add a permission check later), we insert their vote. const handleVote = async (optionId: string) => { if (!user) return; try { const { error } = await supabase.from("votes").insert({ poll_id: query.id, option_id: optionId, user_id: user.id, }); if (!error) { setHasVoted(true); } } catch (error) { console.error("Error voting:", error); } }; Израчунавамо укупан број гласова и одбројавање до времена истека. Затим можете да користите ово да бисте приказали шипке напретка или статистику. Display the Poll Results if (!poll || pollLoading || voteLoading) return <div>Loading...</div>; // 6. calculate total votes const totalVotes = calculateTotalVotes(poll.options); const countdown = getCountdown(poll.expires_at); return ( ... ); }; export default ViewPoll; Са овим подешавањем, ваш систем за гласање је у потпуности функционалан. Али тренутно, свако ко је пријављен може технички покушати да гласа – чак и на сопственој анкети. using и da bi se ta pravila primenjivala. authorization checks Permit.io Supabase Edge Functions Пре него што то урадимо, прво погледајмо тип слоја овлашћења који ћемо имплементирати. Understanding ReBAC (Relationship-Based Access Control) Supabase handles authentication and basic row-level permissions well, but it doesn’t support complex rules like: Preventing users from voting on their own polls Додјељивање улога по ресурсима (као што је "креатор" за одређену анкету) Managing access via external policies To support these kinds of relationship-based permissions, we’ll implement ReBAC with Permit.io. je model za upravljanje dozvolama na osnovu odnosa između korisnika i resursa. Umesto da se oslanja isključivo na uloge ili atribute (kao u RBAC ili ABAC), ReBAC određuje pristup tako što procenjuje kako je korisnik povezan sa resursom koji pokušavaju da pristupe. Relationship-Based Access Control (ReBAC) Relationship-Based Access Control (ReBAC) У овом туторијалу, примењујемо РеБАЦ на апликацију за гласање: Корисник који је креирао анкету је једини који може да га управља (уреди / избрише) Корисник не може гласати на сопственој анкети Остали аутентификовани корисници могу гласати једном по анкети Моделирањем ових односа у Permit.io-у можемо дефинисати фино зрнана правила приступа која превазилазе уграђену сигурност нивоа рова (РЛС) Супабасе-а. За више о РеБАЦ-у, погледајте . Permit.io’s ReBAC docs Access Control Design За нашу апликацију за гласање дефинишемо: Један ресурс са акцијама специфичним за ресурсе: анкете: креирање, читање, брисање, ажурирање. Dve uloge za dodelu nivoa dozvola na osnovu odnosa korisnika sa resursima: autentični: Može da izvrši radnje za kreiranje i čitanje u anketama. Ne može da izbriše ili ažurira radnje u anketama. kreator: Može da kreira, čita, briše i ažurira radnje u anketama. Može da izvrši radnje za čitanje i kreiranje u anketama. Ne može da koristi radnje za kreiranje u svojim anketama. Подешавање дозволе.io Хајде да прођемо кроз подешавање модела овлашћења у Дозвола. Креирајте нови пројекат у Permit.io Име га нешто попут supabase-polling Дефинисати ресурс истраживања Иди на картицу Политика → Ресурси Кликните на "Креирај ресурс" Именовање истраживања и додајте акције: прочитати, креирати, ажурирати, избрисати Омогућавање РеБАЦ-а за ресурс У одељку „РеБАЦ опције“ дефинишите следеће улоге: аутентификовани креатор Кликните на Сачувај Kreirajte novi projekat Пређите на картицу "Улоге" да бисте видели улоге из ресурса које смо управо креирали. Имајте на уму да је дозвола створила подразумеване улоге ( , , ) који су непотребни за овај туториал. admin editor user Дефинисати политике приступа Идите на картицу Политике → Политике Користите визуелну матрицу да бисте дефинисали: аутентификовани може да чита и креира анкете креатор може да чита, ажурира и брише анкете (опционално) Можете да конфигуришете дозволе за гласање као део овог или преко другог ресурса ако моделирате гласање одвојено Add resource instances Go to Directory → Instances Add individual poll IDs as resource instances (you’ll automate this later when new polls are created) Assign roles to users per poll (e.g. is of ) user123 creator poll456 Ова структура нам даје моћ да напишемо флексибилна правила приступа и спроводимо их по кориснику, по анкети. Сада када смо завршили почетну инсталацију на тањиру Дозволи, хајде да га користимо у нашој апликацији. на наш Субасе пројекат преко Едге Функције које: Дозволите ми Синхронизација нових корисника Assign creator roles Check access on demand Setting up Permit in the Polling Application Permit offers multiple ways to integrate with your application, but we'll use the Container PDP for this tutorial. You have to host the container online to access it in Supabase Edge functions. You can use services like Када сте га хостовали, сачувајте URL за ваш контејнер. railway.com Добијте API кључ за дозволу тако што ћете кликнути на "Пројекти" у приборној траци за дозволу, прећи на пројекат на којем радите, кликнути на три тачке и одабрати "Копирај API кључ". Creating Supabase Edge Function APIs for Authorization савршени су за интеграцију услуга треће стране као што је Permit.io. користићемо их за спровођење наших правила РеБАЦ-а у тренутку покретања проверавањем да ли корисницима је дозвољено да обављају одређене акције на анкетама. Supabase Edge Functions Create Functions in Supabase Иницијализујте Субасе у свом пројекту и креирајте три различите функције користећи Ово ће бити почетна тачка за ваше функције: supabase functions new npx supabase init npx supabase functions new syncUser npx supabase functions new updateCreatorRole npx supabase functions new checkPermission Ово ће створити а Folder u папка заједно са крајњим тачкама. Сада, хајде да напишемо кодове за сваку крајњу тачку. functions supabase Препоручује се да се узимају у обзир упутства за употребу ( ) syncUser.ts Ova funkcija sluša za Supabase’s Када се нови корисник региструје, синхронизујемо њихов идентитет са Permit.io и доделимо им подразумевани улоге SIGNED_UP authenticated import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { Permit } from "npm:permitio"; const corsHeaders = { 'Access-Control-Allow-Origin': "*", 'Access-Control-Allow-Headers': 'Authorization, x-client-info, apikey, Content-Type', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', } // Supabase Edge Function to sync new users with Permit.io Deno.serve(async (req) => { const permit = new Permit({ token: Deno.env.get("PERMIT_API_KEY"), pdp: "<https://real-time-polling-app-production.up.railway.app>", }); try { const { event, user } = await req.json(); // Only proceed if the event type is "SIGNED_UP" if (event === "SIGNED_UP" && user) { const newUser = { key: user.id, email: user.email, name: user.user_metadata?.name || "Someone", }; // Sync the user to Permit.io await permit.api.createUser(newUser); await permit.api.assignRole({ role: "authenticated", tenant: "default", user: user.id, }); console.log(`User ${user.email} synced to Permit.io successfully.`); } // Return success response return new Response( JSON.stringify({ message: "User synced successfully!" }), { status: 200, headers: corsHeaders }, ); } catch (error) { console.error("Error syncing user to Permit: ", error); return new Response( JSON.stringify({ message: "Error syncing user to Permit.", "error": error }), { status: 500, headers: { "Content-Type": "application/json" } }, ); } }); Додељивање улоге креатора ( ) updateCreatorRole.ts Када корисник креира анкету, ова функција се позива на: Синхронизовати анкету као нову Permit.io инстанцу ресурса Assign the user the role for that poll creator import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { Permit } from "npm:permitio"; const corsHeaders = { 'Access-Control-Allow-Origin': "*", 'Access-Control-Allow-Headers': 'Authorization, x-client-info, apikey, Content-Type', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', } Deno.serve(async (req) => { const permit = new Permit({ token: Deno.env.get("PERMIT_API_KEY"), pdp: "<https://real-time-polling-app-production.up.railway.app>", }); try { const { userId, pollId } = await req.json(); // Validate input parameters if (!userId || !pollId) { return new Response( JSON.stringify({ error: "Missing required parameters." }), { status: 400, headers: { "Content-Type": "application/json" } }, ); } // Sync the resource (poll) to Permit.io await permit.api.syncResource({ type: "polls", key: pollId, tenant: "default", attributes: { createdBy: userId } }); // Assign the creator role to the user for this specific poll await permit.api.assignRole({ role: "creator", tenant: "default", user: userId, resource: { type: "polls", key: pollId, } }); return new Response( JSON.stringify({ message: "Creator role assigned successfully", success: true }), { status: 200, headers: corsHeaders }, ); } catch (error) { console.error("Error assigning creator role: ", error); return new Response( JSON.stringify({ message: "Error occurred while assigning creator role.", error: error }), { status: 500, headers: { "Content-Type": "application/json" } }, ); } }); Провера дозвола ( ) checkPermission.ts Ова функција делује као врата - проверава да ли је кориснику дозвољено да изврши одређену акцију ( , , , ) на одређеном анкету. create read update delete import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { Permit } from "npm:permitio"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "Authorization, x-client-info, apikey, Content-Type", "Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE", }; Deno.serve(async req => { const permit = new Permit({ token: Deno.env.get("PERMIT_API_KEY"), pdp: "<https://real-time-polling-app-production.up.railway.app>", }); try { const { userId, operation, key } = await req.json(); // Validate input parameters if (!userId || !operation || !key) { return new Response( JSON.stringify({ error: "Missing required parameters." }), { status: 400, headers: { "Content-Type": "application/json" } } ); } // Check permissions using Permit's ReBAC const permitted = await permit.check(userId, operation, { type: "polls", key, tenant: "default", // Include any additional attributes that Permit needs for relationship checking attributes: { createdBy: userId, // This will be used in Permit's policy rules }, }); return new Response(JSON.stringify({ permitted }), { status: 200, headers: corsHeaders, }); } catch (error) { console.error("Error checking user permission: ", error); return new Response( JSON.stringify({ message: "Error occurred while checking user permission.", error: error, }), { status: 500, headers: { "Content-Type": "application/json" } } ); } }); Локални тестови Покрените сервер Supabase dev да бисте тестирали функције локално: npx supabase start npx supabase functions serve Tada možete da dodirnete svoje funkcije na: <http://localhost:54321/functions/v1/><function-name> Пример : <http://localhost:54321/functions/v1/checkPermission> Integrating Authorization Checks in the UI Сада када смо креирали нашу логику овлашћења са Permit.io и изложили је преко Субасе Едге Функције, време је да се оне провере спроведу унутар компоненти апликације. У овом одељку, ажурираћемо кључне компоненте корисничког интерфејса како бисмо позвали те функције и условно дозволили или блокирали радње корисника као што је гласање или управљање анкетама на основу провера дозвола. : Доделити улогу креатора након креирања истраживања NewPoll.tsx Претраживање.tsx Након што креирате анкету и сачувате је на Субаси, зовемо function to: updateCreatorRole Синхронизовати ново истраживање као ресурс у Permit.io Потврдите да је корисник креирао тренутну улогу креирача за ову конкретну анкету.Поставите питање и најмање две опције.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понека Ограничење гласања на основу дозвола ViewPoll.tsx Пре него што кориснику дозволимо да гласа на анкети, позивамо функција да провери да имају Дозвола на Ресурс. ево како се примењује правило: checkPermission create votes “A creator cannot vote on their own poll.” Check voting permission: const [canVote, setCanVote] = useState(false); useEffect(() => { const checkPermission = async () => { if (!user || !query.id) return; try { const response = await fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "create", key: query.id, }), }); const { permitted } = await response.json(); setCanVote(permitted); } catch (error) { console.error("Error checking permission:", error); setCanVote(false); } }; checkPermission(); }, [user, query.id]); Disable vote buttons if user isn’t allowed: <button onClick={() => handleVote(option.id)} disabled={!user || !canVote}} className="w-full text-left p-4 rounded-md hover:bg-slate-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"> {option.text} </button> Show a message if the user is not allowed to vote: {user && !canVote && ( <p className="mt-4 text-gray-600">You cannot vote on your own poll</p> )} : Контрола приступа за уређивање / брисање PollCard.tsx Такође ограничавамо акције управљања анкетама (уређивање и брисање) проверавајући да ли корисник има или дозволу за ову анкету. update delete Check management permissions: const [canManagePoll, setCanManagePoll] = useState(false); useEffect(() => { const checkPollPermissions = async () => { if (!user || !poll.id) return; try { // Check for both edit and delete permissions const [editResponse, deleteResponse] = await Promise.all([ fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "update", key: poll.id, }), }), fetch("/api/checkPermission", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "delete", key: poll.id, }), }), ]); const [{ permitted: canEdit }, { permitted: canDelete }] = await Promise.all([editResponse.json(), deleteResponse.json()]); // User can manage poll if they have either edit or delete permission setCanManagePoll(canEdit || canDelete); } catch (error) { console.error("Error checking permissions:", error); setCanManagePoll(false); } }; checkPollPermissions(); }, [user, poll.id]); Conditionally show management buttons: Замените га: {user?.id === poll?.created_by && ( са : {canManagePoll && ( <div className="flex justify-start gap-4 mt-4"> <button type="button" onClick={handleEdit}> </button> <button type="button" onClick={handleDelete}> </button> </div> )} Тестирање интеграције Када сте интегрисани, требало би да видите следеће понашање у апликацији: Isključeni korisnici mogu da gledaju ankete, ali ne i da interaktuju Аутентификовани корисници могу гласати за анкете које нису креирали Kreatori ne mogu da glasaju na sopstvenim anketama Само креатори виде опције за уређивање / брисање у својим анкетама На почетном екрану, корисници могу да виде листу активних и прошлих анкета, без обзира да ли су пријављени или не. Међутим, када кликну на анкету, неће моћи да виде детаље анкете или гласају за њу. Када се пријавите, корисник може да прегледа детаље анкете и гласа на њој. Међутим, ако је корисник креатор анкете, неће моћи да гласају на њој. Закључак In this tutorial, we explored how to implement U realnom svetu application. Supabase authentication and authorization Next.js Почели смо са постављањем за пријављивање и пријављивање, креирао је релациона шему са заштитом нивоа рова и додао динамичку логику овлашћења користећи Уз помоћ и а , спровели смо дозволе за проверу директно са фронта. Supabase Auth ReBAC Supabase Edge Functions Policy Decision Point (PDP) Комбинујући Са флексибилном контролом приступа, могли смо: Supabase Auth Аутентификовање корисника путем е-поште и лозинке Ограничити управљање гласањем и анкетама овлашћеним корисницима Спречити креаторе да гласају за своје анкете Dodelite i ocenite uloge korisnika na osnovu odnosa sa podacima This setup gives you a scalable foundation for building apps that require both authentication and fine-grained authorization. Dalje čitanje Permit.io Ребац Водич Дозвола + провајдер аутентификације Елементи дозвола: Уграђени кориснички интерфејс за управљање улогама Филтрирање података са дозволом Audit Logs Imate pitanja? pridružite nam se , где стотине програмера граде и разговарају о овлашћењу. Слаба заједница Слаба заједница