Нова история

DIY Real-Time Polling App блокира достъпа с Supabase и Permit.io

от Permit.io40m2025/04/16
Read on Terminal Reader

Твърде дълго; Чета

Пълно ръководство за изграждане на безопасно приложение за гласуване в реално време с удостоверяване, разрешения на потребител и динамични проверки на правилата – с помощта на Supabase + Permit.io.
featured image - DIY Real-Time Polling App блокира достъпа с Supabase и Permit.io
Permit.io HackerNoon profile picture
0-item


От Габриел Л. Манор

Габриел Л. Манор


Supabase улеснява добавянето на удостоверяване към приложението с вградена поддръжка за имейл, OAuth и магически връзки. но докато Supabase Auth се занимава с това кои са вашите потребители, често се нуждаете и от разрешителен слой.


Supabase предлага страхотен backend с вградена Auth и Row Level Security (RLS), управлението на финозърнести разрешения - особено тези, базирани на взаимоотношения между потребителите и данните - далеч не е лесно.

финозърнести разрешенияотношения между потребители и данни


Може да искате да ограничите действия като редактиране или изтриване на данни до собствениците на ресурси, да попречите на потребителите да гласуват за собственото си съдържание или да налагате различни разрешения за различни потребителски роли.


Този урок разглежда как да се приложи Supabase аутентификация и авторизация в Next.js приложение.

Автентизиране и разрешаване на база данниСледваща.js

We'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)

за прилагане на правилата за достъп на потребител и на ресурс.


СърбияСледваща страницаRLS СърбияРеално времеФункции на ръбаРелационен контрол на достъпа (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
  • Supabase – Backend-as-a-service for database, authentication, realtime, and edge functions
  • SupabaseSupabase
  • Next.js – Frontend framework for building the app UI and API routes
  • Next.jsNext.js
  • Permit.io – (for ReBAC) to define and evaluate authorization logic via PDP
  • Permit.ioPermit.io
  • Supabase CLI – To manage and deploy Edge Functions locally and in production
  • Supabase CLISupabase CLI

    Предварителни изисквания

  • Node.js е инсталиран
  • Акаунт в базата данни
  • Permit.io account
  • Permit.io
  • Познаване на React/Next.js
  • Starter project repo
  • Starter project repo

    Какво може да направи това приложение?

    Демонстрационното приложение е платформа за гласуване в реално време, изградена с Next.js и Supabase, където потребителите могат да създават анкети и да гласуват за други.


    • Всеки потребител (автентичен или не) може да преглежда списъка с публични проучвания
    • Само автентични потребители могат да създават проучвания и да гласуват
    • Един потребител не може да гласува за проучване, което е създал
    • Само създателят на проучване
    може да го редактира или изтрие
  • Всеки потребител (автентичен или не) може да преглежда списъка с обществените проучвания
  • Само авторизирани потребители могат да създават анкети и да гласуват
  • Потребител не може да гласува в анкета, която е създалне могат да гласуват в проучване, което са създали
  • Само създателят на проучване може да го редактира или изтрие
  • създател на анкета

    Съвети за отслабване

    Ние ще следваме тези общи стъпки:


    1. Настройте Supabase проект, схема, аут и RLS
    2. Създаване на основни функции на приложението като например създаване на анкети и гласуване
    3. Правила за разрешаване на моделиОпределяне на ролите и правилата в Permit.io
    4. Създаване на функции на Supabase Edge за синхронизиране на потребители, възлагане на роли и проверка на разрешения
    5. Прилагане на политики в предния край на приложението с помощта на тези функции
  • Настройте Supabase проект, схема, auth и RLS
  • Създаване на база данни
  • Създаване на основни функции на приложението като създаване на анкети и гласуване
  • Създаване на основни функции на приложението
  • Правила за разрешаване на модели определят ролите и правилата в Permit.io
  • Правила за разрешаване на модела
  • Създаване на функции на Supabase Edge за синхронизиране на потребители, възлагане на роли и проверка на разрешения
  • Създаване на функции на 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

    • Enable the Email provider

    • (Optional) Деактивирайте имейл потвърждение за тестване, но го запазете включен за производство


  • В страничната лента отидете на Authentication → Providers

  • В страничната лента отидете на Authentication → Providers

    Аутентификация → Доставчици
  • Активирайте доставчика на Email

  • Активирайте доставчика на Email

    Електронна поща
  • (незадължително) Деактивирайте имейл потвърждението за тестване, но го запазете включен за производство


  • (Незадължително) Деактивирайте имейл потвърждението за тестване, но го запазете за производство


    Създаване на схема на база данни

    Това приложение използва три основни таблици: polls, options и votes. Използвайте SQL Editor в таблото на Supabase и изпълнете следното:

    pollsoptionsvotes 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):

    pollsoptionsvotes
    • 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 за влизане в нов потребител с техния имейл и парола.signInWithPasswordsignUpuser_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.tsxfiles: 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 функция, за да определите дали на потребителя е позволено да гласува или да управлява конкретно проучване.

    usercheckPermission

    Създаване на функционалност на приложението 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, така че да можем да актуализираме потребителския интерфейс с най-новите данни от анкетите.pollspolls


    Актуализирайте 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
    • Резултатите се показват и актуализират в реално време
  • Само един глас на потребител на анкета
  • Създателите не могат да гласуват за собствените си проучвания
  • Гласовете се съхраняват в таблицата votesvotes
  • Резултатите се показват и актуализират в реално време

  • Нека разделим как това работи в 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.
        Заглавие на публикацията: Кодекс, Кодекс, Кодекс, Кодекс, Кодекс, Кодекс, Кодекс, КодексИзследванияcreatereaddeleteupdate
      • Две роли за предоставяне на нива на разрешение въз основа на връзката на потребителя с ресурсите:
        • 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 действия в анкети. автентичен:createreaddeleteupdate
  • creator: Може create, read, delete, и update действия в анкети. Може да изпълнява read и create действия в гласувания. Не може да използва create в собствените си анкети.
  • създател:createreaddeleteupdatereadcreatecreate

    Настройване на 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
  • pollsreadcreateupdatedelete
  • Активирайте ReBAC за ресурса
    • Под „Опции за ReBAC“, задайте следните роли:

      • authenticated
      • creator
  • Click Save


  • Активиране на ReBAC за ресурса
    • Под „Опции на ReBAC“ дефинират следните роли:

      • authenticated
      • creator
    • Click Save


  • Под „Опции за ReBAC“ дефинирайте следните роли:

    • authenticated
    • creator
  • Под „Ребак опции“ дефинирайте следните роли:

  • оторизиран
  • authenticated
  • създател
  • creatorКликнете върху Save



  • Кликнете върху Save


    Спаси


    Навигация до раздела "Роли", за да видите ролите от ресурсите, които току-що създадохме.Забележете, че Разрешение е създал ролите по подразбиране (admin, editor, user), които не са необходими за това урок.

    admineditoruser


    • 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

      authenticatedreadcreate
    • creator може да read, update и delete polls

    • creator може да read, update и delete polls

      creatorreadupdatedelete
    • (Необходимо) Можете да конфигурирате разрешения за гласуване като част от това или чрез втори ресурс, ако моделирате гласуването отделно


    • (Необходимо) Можете да конфигурирате разрешения за гласуване като част от това или чрез втори ресурс, ако моделирате гласуването отделно



    • Добавете ресурсни инстанции

      • Отидете на Директива → Инстанции

      • Добавете индивидуални идентификатори на проучвания като ресурсни инстанции (ще автоматизирате това по-късно, когато се създават нови проучвания)

      • Присвояване на роли на потребители на проучване (напр. user123 е creator на poll456)


    • Добавяне на инстанции на ресурси

      Добавяне на инстанции на ресурси
      • Отидете на Директива → Инстанции

      • Добавете индивидуални идентификатори на проучвания като инстанции на ресурси (ще автоматизирате това по-късно, когато се създават нови проучвания)

      • Присвояване на роли на потребители на проучване (напр. user123 е creator на poll456)


    • Отидете на Директива → Инстанции

    • Отидете на Директива → Инстанции

      Директории → Инстанции
    • Добавете индивидуални идентификатори на проучвания като екземпляри на ресурси (ще автоматизирате това по-късно, когато се създават нови проучвания)

    • Добавете индивидуални идентификатори на проучвания като екземпляри на ресурси (ще автоматизирате това по-късно, когато се създават нови проучвания)

    • Присвояване на роли на потребители на анкета (напр. user123 е creator на poll456)


    • Присвояване на роли на потребители на анкета (напр. user123 е creator на poll456)

      user123creatorpoll456


      Тази структура ни дава правомощието да пишем гъвкави правила за достъп и да ги прилагаме на потребител, на анкета.


      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 заедно с крайните точки.functionssupabase

      Синхронизиране на потребителите с Permit.io на Signup (syncUser.ts)

      syncUser.ts

      Тази функция слуша за събитието SIGNED_UP auth на Supabase.Когато нов потребител се регистрира, ние синхронизираме тяхната самоличност с Permit.io и им присвояваме ролята authenticated по подразбиране.

      SIGNED_UPauthenticated


      Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забележка: Забеле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) в конкретен одит.

      createreadupdatedelete


      За да се отървете от този проблем, трябва да се консултирате с вашия лекар, за да се уверите, че е възможно да се отървете от този проблем, но не е необходимо да се откажете от употребата на лекарства, които могат да причинят възпаление на лигавицата на белите дробове.За да се отървете от този проблем, трябва да се консултирате с лекар, за да се предотврати възпалението на белите дробове.За да се отървете от възпалението на белите дробове, трябва да се консултирате с лекар, за да се предотврати възпалението на белите дробове.За да се предотврати възпалението на белите дробове, трябва да се консултирате с лекар, за да се предотврати възпалението.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.checkPermissioncreatevotes „Създателят не може да гласува за собственото си проучване.“


      Проверете разрешението за гласуване:

      Проверете разрешението за гласуване:


      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 разрешение за това проучване.

      updatedelete


      Проверете разрешенията за управление:

      Проверете разрешенията за управление:


      Проверка на разрешението <пре>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.io ReBAC Guide

      Permit.io ReBAC Guide
    • Permit + Authentication Providers

    • Permit + Authentication Providers

      Permit + Authentication Providers
    • Permit Elements: Embedded UI for Role Management

    • Permit Elements: Embedded UI for Role Management

      Permit Elements: Embedded UI for Role Management
    • Data Filtering with Permit

    • Data Filtering with Permit

      Data Filtering with Permit
    • Audit Logs


    • Audit Logs

      Audit Logs


      Got questions? Join our Slack community, where hundreds of developers are building and discussing authorization.

      Slack communitySlack community

    Trending Topics

    blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks