отGabriel L. Manor
Supabase улеснява добавянето на удостоверяване към приложението с вградена поддръжка за имейл, OAuth и магически връзки.Но докато Supabase Auth се занимава с това кои са вашите потребители, често се нуждаете и от разрешителен слой.
Supabase предлага страхотен backend с вградена Auth и Row Level Security (RLS), управлениеfine-grained permissionsОсобено тези, които се основават наrelationships between users and dataДалеч е от лесно.
Може да искате да ограничите действия като редактиране или изтриване на данни до собствениците на ресурси, да предотвратите потребителите да гласуват за собственото си съдържание или да наложите различни разрешения за различни потребителски роли.
Този урок разказва как да се прилагаSupabase authentication and authorizationВ АNext.jsна приложението.
Ще започнем сSupabase Authза регистрация и управление на сесията, след това добаветеauthorization rulesИзползванеРелационен контрол на достъпа (ReBAC)Налага се чрезSupabase Edge Functionsи аlocal Policy Decision Point (PDP). от
В края на краищата ще имате приложение за съвместно гласуване в реално време, което поддържа както публични, така и защитени действия – и гъвкава система за разрешаване, която можете да развиете, докато приложението ви расте.
Какво изграждаме
В това ръководство ще изградим приложение за гласуване в реално време, използващоSupabaseиNext.jsТова показва както автентичността, така и авторизацията в действие.
Приложението позволява на потребителите да създават проучвания, да гласуват за други и да управляват само собственото си съдържание.Supabase Authза login/signup и как да се прилагаauthorization policiesкойто контролира кой може да гласува, редактира или изтрива.
Ще използваме основните функции на Supabase -Auth,Postgres,RLS,RealtimeиEdge FunctionsВ комбинация с аRelationship-Based Access Control (ReBAC)Модел за прилагане на правилата за достъп на потребител и на ресурс.
Технически стак
- на
- Supabase – Backend-as-a-service за бази данни, удостоверяване, функции в реално време и Edge на
- Next.js – Frontend Framework за изграждане на приложения UI и API маршрути на
- Permit.io – (за ReBAC) за определяне и оценка на логиката на разрешаване чрез PDP на
- Създаване на клип – To manage and deploy Edge Functions locally and in production на
Предпоставки
- на
- Node.js е инсталиран на
- Счетоводна база на
- Разреши.io акаунт на
- Използване на React/Next.js на
- Стартиране на проекта Repo на
Какво може да направи това приложение?
Демо-приложението е платформа за гласуване в реално време, изградена с Next.js и Supabase, където потребителите могат да създават анкети и да гласуват за други.
- на
- Всеки потребител (автентичен или не) може да преглежда списъка с обществените проучвания на
- Само автентични потребители могат да създават анкети и да гласуват на
- Потребителят не може да гласува в анкета, която е създал на
- Само създателят на анкетата може да го редактира или изтрие на
Tutorial Преглед
Ще следваме тези общи стъпки:
- на
- Настройване на Supabase проект, схема, auth и RLS на
- Изграждане на основни функции на приложението, като например създаване на проучвания и гласуване на
- Правилата за модел на разрешение определят ролите и правилата в Permit.io на
- Създаване на функции на Supabase Edge за синхронизиране на потребители, възлагане на роли и проверка на разрешения на
- Прилагане на политики в приложението с помощта на тези функции Edge на
Нека да започнем -
Setting up Supabase in the Project
Създаване на база данни в проектаОпционално: Clone the Starter Template
Вече съм създал еднаНачалото на храмаеGitHubс целия код, който трябва да започнете, за да можем да се съсредоточим върху внедряването на Supabase и Permit.io.
Можете да клонирате проекта, като изпълните следната команда:
git clone <https://github.com/permitio/supabase-fine-grained-authorization>
След като сте клонирали проекта, преминете към директорията на проекта и инсталирайте зависимостите:
cd realtime-polling-app-nextjs-supabase-permitio
npm install
Създаване на нов проект в Supabase
За да започнете:
- на
- Отидете на https://supabase.com и влезте или създайте акаунт. на
- Щракнете върху "Нов проект" и попълнете името на проекта, паролата и региона. на
- След като сте го създали, отидете в Настройки на проекта → API и отбеляжете вашия URL адрес на проекта и Anon Key – ще ви трябват по-късно. на
Настройване на удостоверяване и база данни в Supabase
Ще използваме вградения имейл/парола на Supabase:
- на
- В страничната лента отидете на Автентичност → Доставчици на
- Активирайте имейл доставчика на
- (Необходимо) Деактивирайте имейл потвърждение за тестване, но го запазете включен за производство на
Създаване на схема на базата данни
Това приложение използва три основни таблици:polls
,options
иvotes
• ИзползвайтеSQL Editorв таблото на Supabase и изпълнете следното:
-- 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)
);
Осигуряване на безопасност на нивото на релсите (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()
)
);
За да използвате функциите в реално време на Supabase:
- на
- В страничната лента отидете на Table Editor на
- За всяка от трите таблици (попитания, опции, гласове): Кликнете върху трите точки → Редактиране на таблица Toggle "Enable Realtime" Запиши промените на
Използване на Supabase Email Authentication в приложението
В това демо приложение всеки може да преглежда списъка с наличните в приложението проучвания, както активни, така и изтекли.За да преглеждате подробностите за проучване, да управлявате или да гласувате за всяко проучване, потребителят трябва да е влязъл.Ние ще използваме имейл и парола като средство за удостоверяване за този проект..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;
Тук, ние използваме SupabasesignInWithPassword
метод за влизане в потребител иsignUp
метод за регистриране на нов потребител с неговия имейл и парола. Също така съхраняваме името на потребителя вuser_name
поле в метаданните на потребителя.
Можете също да използвате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
метод от Supabase да се отпишете от потребителя и да ги пренасочите към началната страница.
Слушане за промени в състоянието на удостоверяване на потребителя
Слушането на промени в състоянието на удостоверяване на потребителя ни позволява да актуализираме потребителския интерфейс въз основа на състоянието на удостоверяване на потребителя.
- на
- Показване/скриване на елементи на потребителския интерфейс като бутони за вход/вход на
- Условно ограничаване на достъпа до защитени страници (като гласуване или управление на проучвания) на
- Уверете се, че само авторизирани потребители могат да извършват ограничени действия на
We’ll use 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;
Restrict Access on Protected Pages
Страници катоpoll detailsилиpoll management, you should also listen for authentication state changes to prevent unauthenticated users from accessing them.
Ето как изглежда в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;
Тези шаблони гарантират, че вашият потребителски интерфейс отразява текущото състояние на удостоверяване на потребителя и образува основата за проверките за упълномощаване, които ще добавим по-късно.user
Обектът, когато се обаждаcheckPermission
Функция за определяне дали на потребителя е позволено да гласува или да управлява конкретно проучване.
Изграждане на функционалност на Polling App
С конфигурирането на Supabase и работата по удостоверяване, сега можем да изградим основната функционалност на приложението за гласуване.
- Creating new polls на
- Fetching and displaying polls in real-time на
- Implementing a voting system на
Това ни дава основното поведение на приложението, което скоро ще защитим с фини разрешения.
Създаване на нови анкети
Потребителите трябва да са влезли, за да създават проучвания.Всяко проучване включва въпрос, дата на изтичане и набор от опции.Ние също така записваме кой е създал проучването, за да можем по-късно да използваме тази връзка за контрол на достъпа.
Inside NewPoll.tsx
, вземете автентифицирания потребител и използвайте Supabase, за да вмъкнете анкетата и нейните опции:
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 Function тук, за да присвоим ролята "креатор" в Permit.io.
Изтегляне и показване на анкети
Проучванията са разделени наactive(все още не е изтекъл) иpast (expired). You can fetch them using Supabase queries filtered by the current timestamp, and set up real-time subscriptions to reflect changes instantly.
Пример от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
таблица, така че да можем да актуализираме потребителския интерфейс с най-новите данни от проучванията.За да разграничим между активни и минали проучвания, ние сравняваме датата на изтичане на срока на годност на всяко проучване с текущата дата.
Актуализиране на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
table so that the UI is updated with the latest poll data.
Актуализиране на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 на
- Създателите не могат да гласуват за собствените си проучвания на
- Votes are stored in the
votes
table на - Резултатите се показват и актуализират в реално време на
Нека да разберем как работи това вViewPoll.tsx
component:
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След като имаме потребителя, ние изтегляме:
- на
- Самата анкета (включително опциите и преброяването на гласовете) на
- Whether this user has already voted на
We also call these again later in real-time updates.
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
table, scoped to this poll. When a new vote is cast, we fetch updated poll data and voting status.
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
Ако потребителят не е гласувал и му е позволено да гласува (по-късно ще добавим проверка за разрешение), ще вмъкнем гласа му.
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;
С тази настройка вашата система за гласуване е напълно функционална, но в момента всеки, който е влязъл в системата, може технически да се опита да гласува – дори в собствения си одит.authorization checksИзползванеPermit.ioиSupabase Edge Functionsза прилагане на тези правила.
Преди да направим това, нека първо разгледаме вида на разрешителния слой, който ще изпълним.
Релационен контрол на достъпа (Relationship-Based Access Control)
Supabase handles authentication and basic row-level permissions well, but it doesn’t support complex rules like:
- Предотвратяване на потребителите да гласуват в собствените си проучвания на
- Assigning per-resource roles (like “creator” for a specific poll) на
- Управление на достъпа чрез външни политики
За да поддържаме тези видове разрешения, базирани на взаимоотношения, ще внедрим ReBAC с Permit.io.
Relationship-Based Access Control (ReBAC)Вместо да разчита само на роли или атрибути (като в RBAC или ABAC), ReBAC определя достъпа, като оценява как потребителят е свързан с ресурса, към който се опитва да получи достъп.
Релационен контрол на достъпа (ReBAC)
В този урок прилагаме ReBAC към приложение за гласуване:
- на
- Потребителят, който е създал анкетата, е единственият, който може да я управлява (редактира/изтрива) на
- Потребителят не може да гласува за собствената си анкета на
- Други авторизирани потребители могат да гласуват веднъж на анкета
Чрез моделиране на тези взаимоотношения в Permit.io можем да дефинираме фини правила за достъп, които надхвърлят вградената защита на нивото на нивото на нивото (RLS) на Supabase.
За повече информация за ReBAC, проверетеДосиетата на Permit.io ReBAC. от
Access Control Design
For our Polling app, we will define:
- на
- Един ресурс със специфични за ресурса действия: анкети: създаване, четене, изтриване, актуализиране. на
- Две роли за предоставяне на нива на разрешения въз основа на връзката на потребителя с ресурсите: автентичен: Може да изпълнява действия за създаване и четене в проучвания. Не може да изтрива или актуализира действия в проучвания. създател: Може да създава, чете, изтрива и актуализира действия в проучвания. Може да изпълнява действия за четене и създаване на гласове. Не може да използва създаване в собствените си проучвания. на
Настройване на Permit.io
Нека да преминем през настройката на модела за разрешение в Разрешение.
- на
- Създаване на нов проект in Permit.io
- Name it something like
supabase-polling
на - Name it something like
- Определяне на ресурса на анкетите Отидете на раздела Политика → Ресурси Кликнете върху "Създаване на ресурс" Назовете анкетите и добавете действията: четене, създаване, актуализиране, изтриване на
- Активиране на ReBAC за ресурса В раздела "Опции за ReBAC" дефинирайте следните роли: автентичен създател Кликнете върху Запиши на
Натиснете в раздела "Роли", за да видите ролите от ресурсите, които току-що създадохме.admin
, editor
,user
Тези неща не са необходими за този урок.
- на
- Определяне на политики за достъп Отидете на раздела Правила Използвайте визуалната матрица, за да дефинирате: автентифициран може да чете и създава проучвания създателят може да чете, актуализира и изтрива проучвания (Необходимо) Можете да конфигурирате разрешения за гласуване като част от това или чрез втори ресурс, ако моделирате гласуването отделно на
- Добавяне на инстанции на ресурси Отидете в директория → Инстанции Добавяне на индивидуални идентификатори на проучвания като инстанции на ресурси (ще автоматизирате това по-късно, когато се създават нови проучвания) Присвояване на роли на потребители на проучване (например user123 е създател на проучване456) на
Тази структура ни дава правомощието да пишем гъвкави правила за достъп и да ги прилагаме на потребител, на анкета.
Сега, когато сме завършили първоначалната настройка на таблото Разреши, нека я използваме в нашето приложение.Разрешителнокъм нашия проект Supabase чрез Edge Functions, който:
- на
- Sync new users на
- Присвояване на ролите на създателя на
- Проверка на достъпа по заявка на
Setting up Permit in the Polling Application
Разрешение предлага множество начини за интегриране с вашето приложение, но ние ще използваме Контейнер PDP за този урок. Трябва да хоствате контейнера онлайн, за да получите достъп до него в функциите на Supabase Edge. Можете да използвате услуги катоЖелезопътен транспорт.comСлед като го хоствате, запишете URL адреса за вашия контейнер.
Получете API клавиша Разреши, като щракнете върху "Проекти" в страничната лента на таблото Разреши, навигация към проекта, върху който работите, щракнете върху трите точки и изберете "Копирай API ключ".
Създаване на API за функции на Supabase Edge за разрешаване
Supabase Edge Functions are perfect for integrating third-party services like Permit.io. We’ll use them to enforce our ReBAC rules at runtime by checking whether users are allowed to perform specific actions on polls.
Create Functions in Supabase
Инициализирайте Supabase в проекта си и създайте три различни функции, като използватеsupabase functions new
Това ще бъде отправна точка за вашите функции:
npx supabase init
npx supabase functions new syncUser
npx supabase functions new updateCreatorRole
npx supabase functions new checkPermission
Това ще създаде аfunctions
Файлът вsupabase
Сега, нека напишем кодовете за всяка крайна точка.
Изтегляне на отметка за изтегляне на отметка (syncUser.ts
)
This function listens for Supabase’s SIGNED_UP
Когато нов потребител се регистрира, ние синхронизираме тяхната самоличност с Permit.io и им присвояваме по подразбиране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 на
- Присвояване на потребителското съдържание на съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобщение за съобще
Проверка на разрешенията (checkPermission.ts
) на
Тази функция действа като вратовръзка – проверява дали на потребителя е позволено да извърши определено действие (create
,read
,update
,delete
) on a specific poll.
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
След това можете да натиснете функциите си на:
<http://localhost:54321/functions/v1/><function-name>
Пример за:
<http://localhost:54321/functions/v1/checkPermission>
Интегриране на проверките за разрешение в UI
Сега, когато създадохме нашата логика за разрешаване с Permit.io и я разкрихме чрез функциите на Supabase Edge, е време да наложим тези проверки в компонентите на приложението.
В този раздел ще актуализираме ключовите компоненти на потребителския интерфейс, за да се обадим на тези функции и условно да разрешаваме или блокираме действия на потребителя, като гласуване или управление на проучвания въз основа на проверки на разрешенията.
NewPoll.tsx
: Присвояване на роля на създател след създаване на проучване
Съвпадение за: tsx
След като създадем анкета и я запазим в Supabase, ние наричамеupdateCreatorRole
Функцията е:
- на
- Синхронизиране на новата анкета като ресурс в Permit.io на
- За да се предотврати възникването на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на възникване на
ViewPoll.tsx
Ограничаване на гласуването въз основа на разрешения
Преди да позволим на потребителя да гласува в проучване, ние наричамеcheckPermission
функция, за да се провери дали те иматcreate
permission on the 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
: Control Access to Edit/Delete
Ние също така ограничаваме действията за управление на проучванията (редактиране и изтриване) чрез проверка дали потребителят има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>
)}
Тестване на интеграцията
След като сте интегрирани, трябва да видите следните поведения в приложението:
- на
- Изключените потребители могат да разглеждат анкети, но не и да си взаимодействат на
- Автентифицираните потребители могат да гласуват за проучвания, които не са създали на
- Създателите не могат да гласуват за собствените си проучвания на
- Само създателите виждат опциите за редактиране/изтриване в анкетите си на
Трябва да можете да видите промените на приложението, като отидете в браузъра. На началния екран потребителите могат да видят списъка с активни и минали проучвания, независимо дали са влезли или не. Въпреки това, когато кликнат върху проучване, те няма да могат да видят подробностите за проучването или да гласуват за него.
След като са влезли, потребителят може да прегледа подробностите за анкетата и да гласува за нея.Въпреки това, ако потребителят е създателят на анкетата, те няма да могат да гласуват за нея.Те ще видят съобщение, което показва, че не могат да гласуват за собствената си анкета.
Заключението
В това урок, ние разгледаме как да се прилагаSupabase authentication and authorizationВ реалния святNext.jsна приложението.
Започнахме със създаването наSupabase Authза вход и вход, създайте релационна схема с Row Level Security и добавете динамична логика за упълномощаване с помощта наReBACС помощта наSupabase Edge Functionsи аPolicy Decision Point (PDP), we enforced permission checks directly from the frontend.
Чрез комбиниранеSupabase AuthС гъвкав контрол на достъпа можем да:
- на
- Автентифициране на потребителите чрез имейл и парола на
- Ограничаване на гласуването и управлението на анкетите до упълномощени потребители на
- Предотвратяване на създателите да гласуват за собствените си проучвания на
- Присвояване и оценка на потребителски роли въз основа на взаимоотношенията с данните на
Тази настройка ви дава мащабируема основа за изграждане на приложения, които изискват както удостоверяване, така и фино разрешение.
По-нататъшно четене
- на
- Permit.io Ребак Ръководство на
- Разрешение + Доставчици на удостоверяване
- Разрешителни елементи: Вграден интерфейс за управление на роли на
- Филтриране на данни с разрешение на
- Проверка на дневниците на
Имате въпроси?Присъединете се към насSlack community, където стотици разработчици изграждат и обсъждат разрешаването.
Слаба общност