От Габриел Л. Манор
Габриел Л. Манор
Supabase улеснява добавянето на удостоверяване към приложението с вградена поддръжка за имейл, OAuth и магически връзки. но докато Supabase Auth се занимава с това кои са вашите потребители, често се нуждаете и от разрешителен слой.
Supabase предлага страхотен backend с вградена Auth и Row Level Security (RLS), управлението на финозърнести разрешения - особено тези, базирани на взаимоотношения между потребителите и данните - далеч не е лесно.
финозърнести разрешенияотношения между потребители и данни
Може да искате да ограничите действия като редактиране или изтриване на данни до собствениците на ресурси, да попречите на потребителите да гласуват за собственото си съдържание или да налагате различни разрешения за различни потребителски роли.
Този урок разглежда как да се приложи Supabase аутентификация и авторизация в Next.js приложение.
Автентизиране и разрешаване на база данниСледваща.jsWe'll start with Supabase Auth for login and session management, then add authorization rules using Relationship-Based Access Control (ReBAC), enforced through Supabase Edge Functions and a local Policy Decision Point (PDP).
Създаване на база данниПравила за разрешаванеRelationship-Based Access Control (ReBAC)Supabase Edge ФункцииLocal Policy Decision Point (PDP)
В крайна сметка ще имате приложение за съвместно гласуване в реално време, което поддържа както публични, така и защитени действия - и гъвкава система за разрешаване, която можете да развиете, докато приложението ви расте.
Какво правим
В това ръководство ще изградим приложение за гласуване в реално време, използващо Supabase и Next.js, което показва както автентичността, така и авторизацията в действие.
Създаване на база данниСледваща.js
Приложението позволява на потребителите да създават проучвания, да гласуват за други и да управляват само собственото си съдържание.Той демонстрира как да се прилага Supabase Auth за вход/подписване и как да се прилагат оторизационни политики, които контролират кой може да гласува, редактира или изтрива.
Създаване на база данниПолитики за разрешаване
Ние ще използваме основните функции на Supabase – Auth, Postgres, RLS, Realtime и Edge Functions – в комбинация с модел за контрол на достъпа на базата на релации (ReBAC)
за прилагане на правилата за достъп на потребител и на ресурс.
Технически стак
- Supabase – Backend-as-a-service for database, authentication, realtime, and edge functions
- Next.js – Frontend framework for building the app UI and API routes
- Permit.io – (for ReBAC) to define and evaluate authorization logic via PDP
- Supabase CLI – To manage and deploy Edge Functions locally and in production
Предварителни изисквания
- Node.js installed
- Supabase account
- Permit.io account
- Familiarity with React/Next.js
- Starter project repo
Какво може да направи това приложение?
Демонстрационното приложение е платформа за гласуване в реално време, изградена с Next.js и Supabase, където потребителите могат да създават анкети и да гласуват за други.
- Всеки потребител (автентичен или не) може да преглежда списъка с публични проучвания
- Само автентични потребители могат да създават проучвания и да гласуват
- Един потребител не може да гласува за проучване, което е създал
- Само създателят на проучване
Съвети за отслабване
Ние ще следваме тези общи стъпки:
- Настройте Supabase проект, схема, аут и RLS
- Създаване на основни функции на приложението като например създаване на анкети и гласуване
- Правила за разрешаване на моделиОпределяне на ролите и правилата в Permit.io
- Създаване на функции на Supabase Edge за синхронизиране на потребители, възлагане на роли и проверка на разрешения
- Прилагане на политики в предния край на приложението с помощта на тези функции
Нека да започнем -
Настройване на Supabase в проекта
Настройване на Supabase в проектаНеобходимо: Клониране на шаблона за стартиране
I've already created a starter template on GitHub with all the code you need to start so we can focus on implementing Supabase and Permit.io.
starter templateGitHub Заглавие на страницата
Можете да клонирате проекта, като изпълните следната команда:
HTTPS://github.com/permitio/supabase-fine-grained-authorization>
git clone <https://github.com/permitio/supabase-fine-grained-authorization>
След като сте клонирали проекта, преминете към директорията на проекта и инсталирайте зависимостите:
cd realtime-polling-app-nextjs-supabase-permitio npm install
cd realtime-polling-app-nextjs-supabase-permitio
npm install
Създаване на нов проект в Supabase
За да започнете:
-
Go to https://supabase.com and sign in or create an account.
-
Click "New Project" and fill in your project name, password, and region.
-
Once created, go to Project Settings → API and note your Project URL and Anon Key — you’ll need them later.
Go to https://supabase.com and sign in or create an account.
Go to https://supabase.com and sign in or create an account.
https://supabase.comКликнете върху "Нов проект" и въведете името на проекта, паролата и региона.
Кликнете върху "Нов проект" и попълнете името на проекта, паролата и региона.
„Новият проект“Веднъж създаден, отидете на Project Settings → API и отбеляжете вашия Project URL и Anon Key - ще ви трябват по-късно.
Веднъж създаден, отидете на Project Settings → API и отбеляжете вашия Project URL и Anon Key - ще ви трябват по-късно.
Настройки на проекта → APIУебсайт на проектаАнон Ключ
Настройване на удостоверяване и база данни в Supabase
Ние ще използваме вградения имейл/парола на Supabase:
-
В страничната лента отидете на Authentication → Providers
-
(Optional) Деактивирайте имейл потвърждение за тестване, но го запазете включен за производство
Enable the Email provider
В страничната лента отидете на Authentication → Providers
В страничната лента отидете на Authentication → Providers
Аутентификация → ДоставчициАктивирайте доставчика на Email
Активирайте доставчика на Email
Електронна поща(незадължително) Деактивирайте имейл потвърждението за тестване, но го запазете включен за производство
(Незадължително) Деактивирайте имейл потвърждението за тестване, но го запазете за производство
Създаване на схема на база данни
Това приложение използва три основни таблици: polls
, options
и votes
. Използвайте SQL Editor в таблото на Supabase и изпълнете следното:
polls
options
votes
SQL редактор
-- Създайте таблица за проучвания 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 TABLE options ( UUID IDFAULT uuid_generate_v4() PRIMARY KEY, pollid UUID REFERENCES polls(id) ON DELETE CAS-- 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)
);
Enabling Row Level Security (RLS)
Enable RLS for each table and define policies:
RLS-- Опроси политики ALTER TABLE polls ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view polls" ON polls FOR SELECT USING (true); CREATE POLICY "Anyone can view options" ON options FOR SELECT USING (true); CREATE POLICY "Poll creators can add options" ON OPTIONS FOR INSERT TO AUTHENTIZED WITH CHECK (auth.uid() = created_by); -- OPTIONS POLICY ALTER TABLE options ENABLE ROW LEVEL SECURITY (EXISTS (SELECT 1 FROM polls WHERE votes idERE = options.poll_idECECT AND created auth_by auth.-- 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()
)
);
За да използвате функциите в реално време на Supabase:
-
In the sidebar, go to Table Editor
-
For each of the three tables (polls
, options
, votes
):
-
Click the three dots → Edit Table
-
Toggle "Enable Realtime"
-
Save changes
In the sidebar, go to Table Editor
В страничната лента отидете на Table Editor
Редактор на таблици
For each of the three tables (polls
, options
, votes
):
-
Click the three dots → Edit Table
-
Toggle "Enable Realtime"
-
Save changes
За всяка от трите таблици (polls
, options
, votes
):
polls
options
votes
-
Click the three dots → Edit Table
-
Toggle "Enable Realtime"
-
Save changes
Кликнете върху трите точки → Edit Table
Кликнете върху трите точки → Edit Table
Редактиране на таблица Toggle "Enable Realtime"
Toggle "Enable Realtime"
"Възможност за реално време"
Save changes
Запазване на промените
Въвеждане на Supabase Email Authentication в приложението
В това демо приложение всеки може да преглежда списъка с наличните в приложението проучвания, както активни, така и изтекли.За да преглеждате подробностите за проучване, да управлявате или да гласувате за всяко проучване, потребителят трябва да е влязъл.Ние ще използваме имейл и парола като средство за удостоверяване за този проект.В вашия проект Next.js съхранявайте поверителностите на Supabase в .env.local
:
.env.local
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
Актуализирайте компонента си за влизане, за да се справите както с регистрацията, така и с влизането чрез имейл/парола:
import { useState } от "react"; import { createClient } от "@/utils/supabase/component"; const LogInButton = () => { const supabase = createClient(); async function logIn() { const { error } = await supabase.auth.signUp email({ data: user_name: userName, }, }); ако (error) { setError(error.message); } else { setShowModal(false); } } else async function signUp() { const { error } = await supabase.auth.signUp email({ defait.signInWithPassword, { data: user_name: userName, }, }); ако (errorimport { 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
на Supabase за влизане в потребител и метода signUp
за влизане в нов потребител с техния имейл и парола.signInWithPassword
signUp
user_name
Можете също така да използвате supabase.auth.signOut()
, за да изключите потребителите и да ги пренасочите:
supabase.auth.signOut()
import { createClient } от "@/utils/supabase/component"; import { useRouter } от "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;
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
от Supabase, за да се отпишете от потребителя и да го пренасочите към началната страница.
signOut
Слушане за промени в състоянието на удостоверяване на потребителя
Слушането за промени в състоянието на удостоверяване на потребителя ни позволява да актуализираме потребителския интерфейс въз основа на състоянието на удостоверяване на потребителя.
- Показване/скриване на елементи на потребителския интерфейс като бутони за вход/вход
- Условно ограничаване на достъпа до защитени страници (като гласуване или управление на проучвания)
- Уверете се, че само авторизирани потребители могат да извършват ограничени действия
Показване/скриване на елементи на потребителския интерфейс като бутони за вход/вход Условно ограничаване на достъпа до защитени страници (като гласуване или управление на проучвания) Уверете се, че само авторизирани потребители могат да извършват ограничени действия
Ние ще използваме supabase.auth.onAuthStateChange()
за да слушаме тези събития и да актуализираме приложението съответно.
supabase.auth.onAuthStateChange()
В Layout.tsx
file: Track Global Auth State
В рамките наLayout.tsx
files: Track Global Auth State Прочетете повече
import React, { useEffect, useState } от "react"; import { createClient } от "@/utils/supabase/component"; import { User } от "@supabase/supabase-js"; const Layout = ({ деца }: { деца: React.ReactNode }) => { data supabase.authonAuthonStateChange((event, session) => useEffect(() => { const fetchUser = async () => { const supabase = createClient(); { data = supabase.authon.AuthonStateChange((event, session) => { set(Useression?.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;
Ограничаване на достъпа до защитени страници
На страници като подробности за проучвания или управление на проучвания, трябва също така да слушате за промени в състоянието на удостоверяване, за да предотвратите достъпа до тях от неоторизирани потребители.
подробности за проучванетоУправление на проучвания
Ето как изглежда в pages/polls/[id.tsx
:
pages/polls/[id].tsx
import { createClient } от "@/utils/supabase/component"; import { User } от "@supabase/supabase-js"; const Page = () => { const [user, setUser] = useState<User Átha null>(null); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, сесия) => { setUser(session?.userimport { 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
, където потребителите трябва да виждат собствените си проучвания само ако са влезли в:
pages/polls/manage.tsx
import { createClient } от "@/utils/supabase/component"; import { User } от "@supabase/supabase-js"; const Page = () => { const [user, setUser] = useState<User Átha null> (null); const supabase = createClient(); useEffect(() => { const fetchUser = async () => { const { data } = supabase.auth.onAuthStateChangeevent((сесия, сесия) => { setUser(session?.user at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at atimport { 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;
Тези шаблони гарантират, че вашият потребителски интерфейс отразява текущото състояние на удостоверяване на потребителя и образуват основата за проверките за упълномощаване, които ще добавим по-късно. например, по-късно ще използвате този user
обект, когато се обадите на checkPermission
Edge функция, за да определите дали на потребителя е позволено да гласува или да управлява конкретно проучване.
user
checkPermission
Създаване на функционалност на приложението Polling
С конфигурирането на Supabase и работата по удостоверяване, сега можем да изградим основната функционалност на приложението за гласуване.
- Създаване на нови допитвания
- Създаване и показване на допитвания в реално време
- Създаване на система за гласуване
Създаване на нови анкети Свързване и показване на анкети в реално време Прилагане на система за гласуване
Това ни дава основното поведение на приложението, което скоро ще защитим с фини разрешения.
Създаване на нови проучвания
Потребителите трябва да са влезли, за да създават проучвания.Всяко проучване включва въпрос, дата на изтичане на срока и набор от опции.Ние също така записваме кой е създал проучването, за да можем по-късно да използваме тази връзка за контрол на достъпа.
Вътре NewPoll.tsx
, изтеглете автентифицирания потребител и използвайте Supabase, за да вмъкнете анкетата и нейните опции:
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;
По-късно ще се обадим на Функция Edge тук, за да присвоим ролята "креатор" в Permit.io.
Изтегляне и показване на проучвания
Опросите са разделени на active (все още не е изтекъл) и past (изтекъл). Можете да ги изтеглите с помощта на запитвания в Supabase, филтрирани от текущия час, и да настроите абонаменти в реално време, за да отразявате промените незабавно.
активен предишна
Пример от pages/index.tsx
:
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 (
...
);
}
Преглед и управление на потребителски проучвания
Тук извличаме активни и минали анкети от таблицата polls
в Supabase. Също така създаваме абонамент в реално време, за да слушаме промените в таблицата polls
, така че да можем да актуализираме потребителския интерфейс с най-новите данни от анкетите.polls
polls
Актуализирайте pages/manage.tsx
страница, за да изтеглите и покажете само проучвания, създадени от потребителя:
pages/manage.tsx
import { PollProps } от "@/helpers"; const Page = () => { useEffect(() => { if (!user?.id) return; const fetchPolls = async () => { try { const_at", { ascending: 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: error }); if (error) default.default.default.default.default.default.default.default.defaultimport { 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
, така че интерфейсът на потребителя да се актуализира с най-новите данни от проучванията.
polls
Също така актуализирайте компонента PollCard
, така че ако вписан потребител е създателят на проучването, иконите за редактиране и изтриване на проучването ще им се показват в проучването.
PollCard
import { createClient } от "@/utils/supabase/component"; import { User } от "@supabase/supabase-js"; const PollCard = ({ poll }: { poll: PollProps }) => { const data } = supabase.auth.onAuthStateChangeevent(( сесия, сесия) => setUserState null> setUserState null> setLoading(> setLoading(false); }); return () => { data.subcription.unsubscribe( }); }; fetUser( }); fetUser(); [(]); return ( ...> setUser(session?.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;
Така че сега, на карта с проучвания, ако вписаният потребител е създателят на проучването, ще им бъдат показани икони за редактиране и изтриване на проучването.
Прилагане на системата за гласуване
Съгласно логиката на гласуване се налага:
- Само един глас на потребител на анкета
- Създателите не могат да гласуват за собствените си анкети
- Гласовете се съхраняват в таблицата
votes
- Резултатите се показват и актуализират в реално време
Само един глас на потребител на анкета Създателите не могат да гласуват за собствените си проучвания Гласовете се съхраняват в таблицата votes
votes
Резултатите се показват и актуализират в реално време
Нека разделим как това работи в ViewPoll.tsx
компонент:
ViewPoll.tsx
Заредете вписания потребителНие се нуждаем от идентификационния номер на текущия потребител, за да определим допустимостта за гласуване и да запишем гласа му.
Fetch the Logged-In потребител
import { createClient } от "@/utils/supabase/component"; import { User } от "@supabase/supabase-js"; const ViewPoll = () => { const [user, setUser] = useState<User Átha null>(null); const supabase = createClient(); useEffect(() const fetchUser = async () => { const { data: { user }, } = await supabase.auth.getUser(); setUser(user); }; fetchUser(); }, []; </>code</>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();
}, []);
Заредете детайлите на проучването и проверете статуса на гласуванетоСлед като имаме потребителя, ние вземаме:
Заредете детайлите на проучването и проверете статуса на гласуването - Самата анкета (включително опциите и броенето на гласовете)
- Дали този потребител вече е гласувал
Самата анкета (включително опциите и броенето на гласовете) Дали този потребител вече е гласувал
Ние също така ги наричаме отново по-късно в актуализации в реално време.
useEffect(() => { ако (!user) { return; } const checkUserVote = async () => { const { data: votes } = await supabase .from("votes") .eq("poll_id", query.id) .eq("user_id", user.id) .single(); setHasVoted(!!votes); setVoteLoading(false); }; constchPoll = async () => { const } data = await supabase .from("polls", query.id) .eq(options, * (id, text, votes (count) ) ` ) .eq("quid", queryid.lesingle(); set 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();
Слушайте актуализации в реално време
Слушайте актуализации в реално времеWe subscribe to changes in the votes
table, scoped to this poll. When a new vote is cast, we fetch updated poll data and voting status.
votes
const channel = supabase .channel(`poll-${query.id}`) .on( "postgres_changes", { event: "*", схема: "public", таблица: "votes", филтър: `poll_id=eq.${query.id}`, }, () => { fetchPoll(); checkUserVote(); } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [query.id, user]);
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]);
Следваща статияСледваща статияСледваща статия
Управление на гласуванетоАко потребителят не е гласувал и е позволено да гласува (ще добавим проверка за разрешение по-късно), ние вмъкваме гласа му.
const handleVote = async (optionId: string) => { ако (!user) се върне; опитайте { const { error } = await supabase.from("votes").insert({ poll_id: query.id, option_id: optionId, user_id: user.id, }); ако (!error) { setHasVoted(true); } } catch (error) { console.error("Error voting:", error); };
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);
}
};
Показване на резултатите от анкетатаИзчисляваме общия брой гласове и отчитаме до изтичане на срока на валидност.Показване на резултатите от анкетата
ако (!pollannoo pollLoadingannoo voteLoading) върнете <div>Loading...</div> // 6. изчислете общия брой гласове конст totalVotes = calculateTotalVotes(poll.options); конст countdown = getCountdown(poll.expires_at); return ( ... ); }; export default ViewPoll;
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;
С тази настройка вашата система за гласуване е напълно функционална, но в момента всеки, който е влязъл в системата, може технически да се опита да гласува – дори в собствения си одит.проверка на разрешениятаПозволявам сиSupabase Edge Функции
Преди да направим това, нека първо разгледаме вида на слоя за разрешение, който ще изпълним.
Understanding ReBAC (Relationship-Based Access Control)
Supabase се справя добре с удостоверяването и основните разрешения на ниво ред, но не поддържа сложни правила като:
- Предотвратяване на потребителите да гласуват за собствените си проучвания
- Присвояване на роли за всеки ресурс (като „креатор“ за конкретно проучване)
- Управление на достъпа чрез външни политики
Предотвратяване на потребителите да гласуват в собствените си проучвания Присвояване на роли за всеки ресурс (като „креатор“ за конкретно проучване) Управление на достъпа чрез външни политики
За да поддържаме тези видове разрешения, базирани на взаимоотношения, ще внедрим ReBAC с Permit.io.
Relationship-Based Access Control (ReBAC) is a model for managing permissions based on the relationships between users and resources. Instead of relying solely on roles or attributes (as in RBAC or ABAC), ReBAC determines access by evaluating how a user is connected to the resource they’re trying to access.
Relationship-Based Access Control (ReBAC)Relationship-Based Access Control (ReBAC)
В този урок ние прилагаме ReBAC към приложение за гласуване:
- Потребител, който създаде проучване, е единственият, който може да го управлява (редактиране/изтриване)
- Потребител, който не може да гласува в своето own проучване
- Други авторизирани потребители могат да гласуват веднъж на проучване
Потребител, който създаде анкета, е единственият, който може да я управлява (редактира/изтрие) създаден отПотребител не може да гласува за своя ownне може онова, което силноДруги авторизирани потребители могат да гласуват веднъж на анкета
Чрез моделиране на тези взаимоотношения в Permit.io можем да дефинираме фини правила за достъп, които надхвърлят вградената защита на нивото на нивото на нивото (RLS) на Supabase.
Ние ще ги прилагаме по време на изпълнение, като използваме функциите на Supabase Edge и двигателя за политики на Permit.
For more on ReBAC, check out Permit.io’s ReBAC docs.
Permit.io’s ReBAC docsПроектиране на контрол на достъпа
За нашето приложение за гласуване ще дефинираме:
- One resource with resource-specific actions:
- polls:
create
, read
, delete
, update
.
- Two roles for granting permission levels based on a user's relationship with the resources:
- authenticated: Може да изпълнява
create
и read
действия в анкетите. Може да изпълнява delete
или update
действия в анкетите. - Един ресурс със специфични за ресурса действия:
- polls:
create
, read
, delete
, update
.
- polls:
create
, read
, delete
, update
.
Заглавие на публикацията: Кодекс, Кодекс, Кодекс, Кодекс, Кодекс, Кодекс, Кодекс, КодексИзследванияcreate
read
delete
update
- Две роли за предоставяне на нива на разрешение въз основа на връзката на потребителя с ресурсите:
- authenticated: Може да изпълнява
create
и read
действия в проучвания. Не може да изпълнява delete
или update
действия в проучвания. - creator: Може да изпълнява
create
, read
, delete
и update
действия в проучвания. Може да изпълнява read
и create
действия в гласувания. Не може да използва create
в собстве - authenticated: Може да изпълнява
create
и read
действия в анкети. Не може delete
действия в анкети.
creator: Може create
, read
, delete
и update
действия в анкети. Може да изпълнява read
и create
действия в гласувания. Не може да използва create
в собствените си анкети. authenticated: Може да изпълнява create
и read
действия в анкети. Не може delete
или update
действия в анкети. автентичен:create
read
delete
update
creator: Може create
, read
, delete
, и update
действия в анкети. Може да изпълнява read
и create
действия в гласувания. Не може да използва create
в собствените си анкети. създател:create
read
delete
update
read
create
create
Настройване на Permit.io
Нека преминем през настройката на модела за разрешение в Разреши.
- Create a new project in Permit.io
- Name it something like
supabase-polling
- Define the
polls
resource
- Go to the Policy → Resources tab
- Click “Create Resource”
- Name it
polls
, and add the actions: read
, create
, update
, delete
- Enable ReBAC for the resource
-
Under “ReBAC Options,” define the following roles:
authenticated
creator
-
Click Save
Create a new project in Permit.io
- Name it something like
supabase-polling
Create a new projectCreate a new project in Permit.io - Име на нещо като
supabase-polling
Назови го нещо като supabase-polling
supabase-polling
Определете polls
resource - Go to Policy → Resources tab
- Click “Create Resource”
- Name it
polls
и добавете следните действия: read
, create
, update
, delete
Определяне наpolls
Ресурс - Отидете на Политика → Ресурси раздел
- Кликнете „Създаване на ресурс“
- Назовете го
polls
и добавете следните действия: read
, create
, update
, delete
Отидете на раздела Политика → РесурсиПолитика → Ресурси Кликнете върху “Create Resource” „Създаване на ресурси“Name it polls
и добавете следните действия: read
, create
, update
, delete
polls
read
create
update
delete
Активирайте ReBAC за ресурса -
Под „Опции за ReBAC“, задайте следните роли:
authenticated
creator
Click Save
Активиране на ReBAC за ресурса -
Под „Опции на ReBAC“ дефинират следните роли:
authenticated
creator
-
Click Save
Под „Опции за ReBAC“ дефинирайте следните роли:
authenticated
creator
Под „Ребак опции“ дефинирайте следните роли:
оторизиран
authenticated
създател
creator
Кликнете върху Save
Кликнете върху Save
Спаси
Навигация до раздела "Роли", за да видите ролите от ресурсите, които току-що създадохме.Забележете, че Разрешение е създал ролите по подразбиране (admin
, editor
, user
), които не са необходими за това урок.
admin
editor
user
-
Define access policies
- Go to the Policy → Policies tab
- Use the visual matrix to define:
-
authenticated
can read
and create
polls
-
creator
can read
, update
, and delete
polls
-
(Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately
-
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. user123
is creator
of poll456
)
Define access policies
- Go to the Policy → Policies tab
- Use the visual matrix to define:
-
authenticated
can read
and create
polls
-
creator
can read
, update
, and delete
polls
-
(Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately
Определяне на правилата за достъп
Определяне на политики за достъп - Отидете на Policy → Policies tab
- Използвайте визуалната матрица, за да дефинирате:
-
authenticated
може read
и create
polls
-
creator
може read
, update
и delete
polls
-
(Опционално) Можете да конфигурирате разрешения за гласуване като част от този или чрез втори ресурс, ако моделирате гласуването поотделно
< - Отидете на раздела Политика → ПолитикиПолитика → Политика
- Използвайте визуалната матрица, за да дефинирате:
-
authenticated
може read
и create
polls
-
creator
може read
, update
и delete
polls
-
(По избор) Можете да конфигурирате правомощия за гласуване като част от това или чрез втори ресурс, ако моделирате гласуванията отделно
-
authenticated
може read
и create
polls
-
creator
може read
, update
и delete
polls
(По избор) Можете да конфигурирате разрешения за гласуване като част от това или чрез втори ресурс, ако гласувате отделно
-
authenticated
може read
и create
polls
authenticated
може read
и create
polls
authenticated
read
create
-
creator
може да read
, update
и delete
polls
creator
може да read
, update
и delete
polls
creator
read
update
delete
-
(Необходимо) Можете да конфигурирате разрешения за гласуване като част от това или чрез втори ресурс, ако моделирате гласуването отделно
(Необходимо) Можете да конфигурирате разрешения за гласуване като част от това или чрез втори ресурс, ако моделирате гласуването отделно
-
Добавете ресурсни инстанции
-
Отидете на Директива → Инстанции
-
Добавете индивидуални идентификатори на проучвания като ресурсни инстанции (ще автоматизирате това по-късно, когато се създават нови проучвания)
-
Присвояване на роли на потребители на проучване (напр. user123
е creator
на poll456
)
Добавяне на инстанции на ресурси
Добавяне на инстанции на ресурси -
Отидете на Директива → Инстанции
-
Добавете индивидуални идентификатори на проучвания като инстанции на ресурси (ще автоматизирате това по-късно, когато се създават нови проучвания)
-
Присвояване на роли на потребители на проучване (напр. user123
е creator
на poll456
)
-
Отидете на Директива → Инстанции
Отидете на Директива → Инстанции
Директории → Инстанции-
Добавете индивидуални идентификатори на проучвания като екземпляри на ресурси (ще автоматизирате това по-късно, когато се създават нови проучвания)
Добавете индивидуални идентификатори на проучвания като екземпляри на ресурси (ще автоматизирате това по-късно, когато се създават нови проучвания)
-
Присвояване на роли на потребители на анкета (напр. user123
е creator
на poll456
)
Присвояване на роли на потребители на анкета (напр. user123
е creator
на poll456
)
user123
creator
poll456
Тази структура ни дава правомощието да пишем гъвкави правила за достъп и да ги прилагаме на потребител, на анкета.
Now that we have completed the initial setup on the Permit dashboard, let's use it in our application. Next, we’ll connect Permit.io to our Supabase project via Edge Functions that:
Permit.io - Sync нови потребители
- Assign creator roles
- Check access on demand
- Sync нови потребители
- Присвояване на ролите на създателя
- Проверка на достъпа по заявка
Настройване на разрешение в заявлението за гласуване
Настройване на разрешение в заявлението за гласуване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 railway.com. Once you have hosted it, save the url for your container.
railway.com
Получете API клавиша за разрешение, като щракнете върху "Проекти" в страничната лента на таблото за разрешение, навигация към проекта, върху който работите, щракнете върху трите точки и изберете "Копиране на API ключ".
Създаване на API за функции на Supabase Edge за разрешаване
Функциите на Supabase Edge са идеални за интегриране на услуги на трети страни като Permit.io. Ние ще ги използваме, за да налагаме правилата на ReBAC по време на изпълнение, като проверяваме дали на потребителите е разрешено да изпълняват конкретни действия в анкетите.
Supabase Edge ФункцииСъздаване на функции в Supabase
Създаване на функции в SupabaseИнициализирайте Supabase в проекта си и създайте три различни функции, като използвате supabase функции new
команда.supabase functions new
npx supabase init npx supabase функции new syncUser npx supabase функции new updateCreatorRole npx supabase функции new checkPermission
npx supabase init
npx supabase functions new syncUser
npx supabase functions new updateCreatorRole
npx supabase functions new checkPermission
Това ще създаде functions
папка в папката supabase
заедно с крайните точки.functions
supabase
Синхронизиране на потребителите с Permit.io на Signup (syncUser.ts
)
syncUser.ts
Тази функция слуша за събитието SIGNED_UP
auth на Supabase.Когато нов потребител се регистрира, ние синхронизираме тяхната самоличност с Permit.io и им присвояваме ролята authenticated
по подразбиране.
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
)
updateCreatorRole.ts
Когато потребителят създаде анкета, тази функция се нарича:
Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забеле Синхронизирайте анкетата като нова инстанция на ресурса Permit.io
Синхронизирайте анкетата като нова инстанция на ресурса Permit.io
Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: ЗабелеПрисвояване на потребителя на ролята creator
за това проучване
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
)
checkPermission.ts
Тази функция действа като gatekeeper – проверява дали на потребителя е позволено да изпълни определено действие (create
, read
, update
, delete
) в конкретен одит.
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 функции serve
npx supabase start
npx supabase functions serve
След това можете да натиснете функциите си на:
<http://localhost:54321/functions/v1/><function-name>
<http://localhost:54321/functions/v1/><function-name>
Пример за това:
<http://localhost:54321/functions/v1/checkПермисия>
<http://localhost:54321/functions/v1/checkPermission>
Интегриране на проверките за разрешение в UI
Сега, когато създадохме нашата логика за разрешаване с Permit.io и я разкрихме чрез функциите на Supabase Edge, е време да наложим тези проверки в компонентите на приложението.
В този раздел ще актуализираме ключовите компоненти на потребителския интерфейс, за да се обадим на тези функции и условно да разрешаваме или блокираме действия на потребителя, като гласуване или управление на проучвания въз основа на проверки на разрешенията.
NewPoll.tsx
: Присвояване на роля на създател след създаване на проучване
NewPoll.tsx
След създаването на проучване и запазването му в Supabase, ние наричаме функцията updateCreatorRole
на:
updateCreatorRole
Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забеле Синхронизирайте новото проучване като ресурс в Permit.io
Синхронизирайте новото проучване като ресурс в Permit.io
Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: ЗабелеПрисвойте на текущия потребител ролята creator
за този конкретен въпросник
creator
Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забеле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;
}
try {
// 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) {
console.error("Error creating options:", optionsError);
setErrorMessage(optionsError.message);
return;
}
// Update the creator role in Permit.io
const response = await fetch(
"<http://127.0.0.1:54321/functions/v1//updateCreatorRole>",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: user?.id,
pollId: poll.id,
}),
}
);
const { success, error } = await response.json();
if (!success) {
console.error("Error updating creator role:", error);
// Note: We don't set an error message here as the poll was still created successfully
}
setSuccessMessage("Poll created successfully!");
handleCancel();
} catch (error) {
console.error("Error in poll creation process:", error);
setErrorMessage("An unexpected error occurred while creating the poll.");
}
};
ViewPoll.tsx
: Ограничаване на гласуването въз основа на разрешения
ViewPoll.tsx
Преди да позволим на потребителя да гласува за проучване, ние се обаждаме на функцията checkPermission
, за да проверим дали те имат create
разрешение за ресурса votes
.checkPermission
create
votes
„Създателят не може да гласува за собственото си проучване.“
Проверете разрешението за гласуване:
Проверете разрешението за гласуване:
const [canVote, setCanVote] = useState(false); useEffect(() => { const checkPermission = async () => { ако (!user Mediateca !query.id) се върне; опитайте { 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, }); { permitted = await response.json(); SetVote(permitted); } catconst [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]);
Изключете бутоните за гласуване, ако потребителят не е разрешен:
Изключете бутоните за гласуване, ако потребителят не е разрешен:
<button onClick={() => handleVote(option.id)} disabled={!user Mediateca !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>
<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>
Показване на съобщение, ако потребителят не може да гласува:
Показване на съобщение, ако потребителят не може да гласува:
{user && !canVote && ( <p className="mt-4 text-gray-600">Не можете да гласувате за своя собствена анкета</p> )}
{user && !canVote && (
<p className="mt-4 text-gray-600">You cannot vote on your own poll</p>
)}
PollCard.tsx
: Контрол на достъпа до Редактиране/Изтриване
PollCard.tsx
Ние също така ограничаваме действията за управление на проучванията (редактиране и изтриване), като проверяваме дали потребителят има update
или delete
разрешение за това проучване.
update
delete
Проверете разрешенията за управление:
Проверете разрешенията за управление:
Проверка на разрешението <пре>const [canManagePoll, setCanManagePoll] = useState(false); useEffect(() => {const checkPollPermissions = async () => { ако (!user-in-law!poll.id) връща; опитайте { // Проверка за редактиране и изтриване на разрешения constedit [Response, deleteResponse] = wait Promise.all([ fetch(<http://127.0.1:54321/functions/v1/checkPermission>), { method: "POST", header: {"Content-Type": "Application/json error", body: JSON.stringify(userIdconst [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]);
Условно показване на бутоните за управление:
Условно показване на бутоните за управление:
Заместване на:
{user?.id === poll?.created_by && (
{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> )}
{canManagePoll && (
<div className="flex justify-start gap-4 mt-4">
<button type="button" onClick={handleEdit}>
</button>
<button type="button" onClick={handleDelete}>
</button>
</div>
)}
Тестване на интеграцията
Веднъж интегриран, трябва да видите следното поведение в приложението:
-
Изключени потребители могат да преглеждат анкети, но не могат да си взаимодействат
-
Автентизирани потребители могат да гласуват за анкети, които те не са създали
Creators cannot vote on their own polls
Only creators see edit/delete options on their polls
Изключените потребители могат да преглеждат анкети, но не и да си взаимодействат
Изключените потребители могат да преглеждат анкети, но не и да си взаимодействат
Автентизираните потребители могат да гласуват за проучвания, които не са създали
Автентизирани потребители могат да гласуват за проучвания, които не са създали
не искам да бъда Създателите не могат да гласуват в собствените си анкети
Създателите не могат да гласуват в собствените си анкети
не мога да гласувам Само създателите виждат edit/delete опции в техните проучвания
Само създателите виждат edit/delete опции в анкетите си
редактиране / премахване
Трябва да можете да видите промените на приложението, като отидете в браузъра.На началния екран потребителите могат да видят списъка с активни и минали проучвания, независимо дали са влезли или не.Но когато кликнат върху проучване, те няма да могат да видят подробностите за проучването или да гласуват за него.
Веднъж влезли, потребителят може да прегледа подробностите на анкетата и да гласува за нея.Въпреки това, ако потребителят е създателят на анкетата, те няма да могат да гласуват за нея.Те ще видят съобщение, което показва, че не могат да гласуват за собствената си анкета.
Заключение
В това урок, ние изследвахме как да се прилагат Субаза аутентификация и авторизация в реалния свят Next.js приложение.
Автентизиране и разрешаване на база данниСледваща.jsПървоначално създадохме Supabase Auth за вход и регистрация, създадохме релационна схема с Row Level Security и добавихме динамична логика за авторизация с помощта на ReBAC.С помощта на Supabase Edge Functions и Policy Decision Point (PDP) наложихме проверки на разрешенията директно от предния край.
Създаване на база данниРедактиранеSupabase Edge ФункцииПолитическа точка за вземане на решения (PDP)
Съчетавайки Supabase Auth с гъвкав контрол на достъпа, успяхме да:
Създаване на база данни - Автентизиране на потребителите чрез имейл и парола
- Ограничаване на гласуването и управлението на проучванията до упълномощени потребители
- Предотвратяване на създателите да гласуват за собствените си проучвания
- Присвояване и оценяване на потребителски роли въз основа на взаимоотношенията с данните
Автентизиране на потребителите чрез имейл и парола Ограничаване на гласуването и управлението на анкетите до упълномощени потребители Предотвратяване на създателите да гласуват за собствените си проучвания Присвояване и оценка на потребителски роли въз основа на взаимоотношенията с данните
Тази настройка ви дава мащабируема основа за изграждане на приложения, които изискват както удостоверяване, така и фино оторизиране.
За по-нататъшно четене
-
-
-
-
-
Permit.io ReBAC Guide
Permit + Authentication Providers
Permit + Authentication Providers
Permit Elements: Embedded UI for Role Management
Permit Elements: Embedded UI for Role Management
Data Filtering with Permit
Audit Logs
Got questions? Join our Slack community, where hundreds of developers are building and discussing authorization.
Slack communitySlack community