From 4f03b874f80dccee5887ec7299db6ba4f45a8d34 Mon Sep 17 00:00:00 2001 From: Paul Copplestone Date: Mon, 6 Sep 2021 16:44:07 +0800 Subject: [PATCH 01/10] Adds jon's video --- web/docs/guides/auth/row-level-security.mdx | 292 ++++++++++++++++++++ web/sidebars.js | 9 +- 2 files changed, 299 insertions(+), 2 deletions(-) create mode 100644 web/docs/guides/auth/row-level-security.mdx diff --git a/web/docs/guides/auth/row-level-security.mdx b/web/docs/guides/auth/row-level-security.mdx new file mode 100644 index 00000000000..73012ca47f7 --- /dev/null +++ b/web/docs/guides/auth/row-level-security.mdx @@ -0,0 +1,292 @@ +--- +id: row-level-security +title: Row Level Security +description: Secure your data using Postgres Row Level Security. +--- + + + + +## Policies Are Like `WHERE` Clauses + +Policies are easy to understand once you get the hang of them. You can just think of them as adding a `WHERE` clause to every query. For example if you had a policy like this: + +```sql +create policy "Individuals can view their own todos." on todos for + select using (auth.uid() = user_id); +``` + +It would translate to this whenever a user tries to select from the todos table: + +```sql +select * +from todos +where auth.uid() = todos.user_id; -- Policy is implicitly added. +``` + +## How It Works + +1. A user signs up. Supabase creates a new user in the `auth.users` table. +2. Supabase returns a new JWT, which contains the user's `UUID`. +3. Every request to your database also sends the JWT. +4. Postgres inspects the JWT to determine the user making the request. +5. The user's UID can be used in policies to restrict access to rows. + +Supabase provides a special function in Postgres, `auth.uid()`, which extracts the user's UID from the JWT. This is especially useful when creating policies. + +## Policy Examples + +Here are some examples to show you the power of PostgreSQL's RLS. Each policy is attached to a table, and the policy is executed +every time a table is accessed. + +### Allow read access + +```sql +-- 1. Create table +create table profiles ( + id uuid references auth.users, + avatar_url text +); + +-- 2. Enable RLS +alter table profiles + enable row level security; + +-- 3. Create Policy +create policy "Public profiles are viewable by everyone." + on profiles for select using ( + true + ); +``` + +1. Creates a table called `profiles` in the public schema (default schema). +2. Enables Row Level Security. +3. Creates a policy which allows all `select` queries to run. + + +### Restrict updates + +```sql +-- 1. Create table +create table profiles ( + id uuid references auth.users, + avatar_url text +); + +-- 2. Enable RLS +alter table profiles + enable row level security; + +-- 3. Create Policy +create policy "Users can update their own profiles." + on profiles for update using ( + auth.uid() = id + ); +``` + +1. Creates a table called `profiles` in the public schema (default schema). +2. Enables RLS. +3. Creates a policy which allows logged in users to update their own data. + + +### Policies with joins + +Policies can even include table joins. This example shows how you can query "external" tables to build more advanced rules. + +```sql +create table teams ( + id serial primary key, + name text +); + +-- 2. Create many to many join +create table members ( + team_id bigint references teams, + user_id uuid references auth.users +); + +-- 3. Enable RLS +alter table teams + enable row level security; + +-- 4. Create Policy +create policy "Team members can update team details if they belong to the team." + on teams + for update using ( + auth.uid() in ( + select user_id from members + where team_id = id + ) + ); +``` + +### Policies with security definer functions + +Policies can also make use of `security definer functions`. This is useful in a many-to-many relationship where you want to restrict access to the linking table. Following the `teams` and `members` example from above, this example shows how you can use security definer function in combination with a policy to control access to the `members` table. + +```sql +-- 1. Follow example for 'Policies with joins' above + +-- 2. Enable RLS +alter table members + enable row level security + +-- 3. Create security definer function +create or replace function get_teams_for_user(user_id uuid) +returns setof bigint as $$ + select team_id + from members + where user_id = $1 +$$ stable language sql security definer; + +-- 4. Create Policy +create policy "Team members can update team members if they belong to the team." + on members + for all using ( + team_id in ( + select get_teams_for_user(auth.uid()) + ) + ); +``` + +### Verifying email domains + +Postgres has a function `right(string, n)` that returns the rightmost n characters of a string. +You could use this to match staff member's email domains. + +```sql +-- 1. Create table +create table leaderboard ( + id uuid references auth.users, + high_score bigint +); + +-- 2. Enable RLS +alter table leaderboard + enable row level security; + +-- 3. Create Policy +create policy "Only Blizzard staff can update leaderboard" + on leaderboard + for update using ( + right(auth.email(), 13) = '@blizzard.com' + ); +``` + +### Time to live for rows + +Policies can also be used to implement TTL or time to live feature that you see in Instagram stories or Snapchat. +In the following example, rows of `stories` table are available only if they have been created within the last 24 hours. + +```sql +-- 1. Create table +create table if not exists stories ( + id uuid not null primary key DEFAULT uuid_generate_v4(), + created_at timestamp with time zone default timezone('utc' :: text, now()) not null, + content text not null +); + +-- 2. Enable RLS +alter table stories + enable row level security; + +-- 3. Create Policy +create policy "Stories are live for a day" + on stories + for select using ( + created_at > (current_timestamp - interval '1 day') + ); +``` + +### Advanced policies + +Use the full power of SQL to build extremely advanced rules. + +In this example, we will create a `posts` and `comments` tables and then create a policy that depends on another policy. +(In this case, the comments policy depends on the posts policy.) + +```sql +create table posts ( + id serial primary key, + creator_id uuid not null references auth.users(id), + title text not null, + body text not null, + publish_date date not null default now(), + audience uuid[] null -- many to many table omitted for brevity +); + +create table comments ( + id serial primary key, + post_id int not null references posts(id) on delete cascade, + user_id uuid not null references auth.users(id), + body text not null, + comment_date date not null default now() +); + +create policy "Creator can see their own posts" +on posts +for select +using ( + auth.uid() = posts.creator_id +); + +create policy "Logged in users can see the posts if they belong to the post 'audience'." +on posts +for select +using ( + auth.uid() = any (posts.audience) +); + +create policy "Users can see all comments for posts they have access to." +on comments +for select +using ( + exists ( + select 1 from posts + where posts.id = comments.post_id + ) +); +``` + + +## Tips + +### You don't have to use policies + +You can also put your authorization rules in your middleware, similar to how you would create security rules with any other `backend <-> middleware <-> frontend` architecture. + +Policies are a tool. In the case of "serverless/Jamstack" setups, they are especially effective because you don't have to deploy any middleware at all. + +However, if you want to use another authorization method for your applications, that's also fine. Supabase is "just Postgres", so if your application +works with Postgres, then it also works with Supabase. + +Tip: Make sure to enable RLS for all your tables, so that your tables are inaccessible. Then use the "Service" which we provide, which is designed to bypass RLS. + + +### Never use a service key on the client + +Supabase provides special "Service" keys, which can be used to bypass all RLS. +These should never be used in the browser or exposed to customers, but they are useful for administrative tasks. + +### Disable realtime for private tables + +Our realtime server doesn't provide per-user security. Until we build a more robust auth system for WebSockets, you can disable realtime functionality for any private tables. To do this, you can manage the underlying Postgres replication publication: + +```sql +/** + * REALTIME SUBSCRIPTIONS + * Only allow realtime listening on public tables. + */ +begin; + -- remove the realtime publication + drop publication if exists supabase_realtime; + -- re-create the publication but don't enable it for any tables + create publication supabase_realtime; +commit; +-- add a table to the publication +alter publication supabase_realtime add table products; +-- add other tables to the publication +alter publication supabase_realtime add table posts; +``` + +We're in the process of building enhanced realtime security. \ No newline at end of file diff --git a/web/sidebars.js b/web/sidebars.js index af85d54856e..d8cd747e00d 100755 --- a/web/sidebars.js +++ b/web/sidebars.js @@ -120,10 +120,9 @@ module.exports = { label: 'Auth', collapsed: false, items: [ - 'guides/auth/managing-user-data', { type: 'category', - label: 'External Providers', + label: 'Authentication', collapsed: true, items: [ 'guides/auth/auth-apple', @@ -138,6 +137,12 @@ module.exports = { 'guides/auth/auth-twilio', ], }, + { + type: 'category', + label: 'Authorization', + collapsed: true, + items: ['guides/auth/row-level-security', 'guides/auth/managing-user-data'], + }, { type: 'category', label: 'Deep Dive', From f17256e9b57f97c4792cd25a9c617e6e23e2a90e Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:08:25 +0800 Subject: [PATCH 02/10] Adds plugins + auth intro --- web/docs/guides/auth.mdx | 53 +--------------- web/docs/guides/auth/intro.mdx | 46 ++++++++++++++ web/sidebars.js | 1 + web/src/css/custom.css | 34 ++++++++++ web/src/data/authProviders.js | 112 +++++++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+), 52 deletions(-) create mode 100644 web/docs/guides/auth/intro.mdx create mode 100644 web/src/data/authProviders.js diff --git a/web/docs/guides/auth.mdx b/web/docs/guides/auth.mdx index 13a0c4cb955..82e460e853b 100644 --- a/web/docs/guides/auth.mdx +++ b/web/docs/guides/auth.mdx @@ -7,58 +7,7 @@ description: Use Supabase to Authenticate and Authorize your users. import Link from '@docusaurus/Link' import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem' -const providers = [ - { - name: 'Apple', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-apple', - }, - { - name: 'Bitbucket', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-bitbucket', - }, - { - name: 'Discord', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-discord', - }, - { - name: 'Facebook', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-facebook', - }, - { - name: 'GitHub', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-github', - }, - { - name: 'GitLab', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-gitlab', - }, - { - name: 'Google', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-google', - }, - { - name: 'Twitter', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-twitter', - }, - { - name: 'Twitch', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-twitch', - }, - { - name: 'Twilio', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-twilio', - }, -] +import providers from '@site/src/data/authProviders' ## User Management diff --git a/web/docs/guides/auth/intro.mdx b/web/docs/guides/auth/intro.mdx new file mode 100644 index 00000000000..9921c3ec26e --- /dev/null +++ b/web/docs/guides/auth/intro.mdx @@ -0,0 +1,46 @@ +--- +id: intro +title: Supabase Auth +description: Use Supabase to Authenticate and Authorize your users. +--- + +import Link from '@docusaurus/Link' +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' +import providers from '@site/src/data/authProviders' + +
+
+ {providers.map((x) => ( +
+ +
+
+ {x.logo && {x.name}} +

{x.name}

+

+ {x.official ? + Official + : + + Unofficial + + } +

+
+
+
+ Platform: + {x.platform.toString()} +
+
+ Hosted: + {x.selfHosted.toString()} +
+
+
+ +
+ ))} +
+
diff --git a/web/sidebars.js b/web/sidebars.js index 174de26686b..e953f5bd5ec 100755 --- a/web/sidebars.js +++ b/web/sidebars.js @@ -136,6 +136,7 @@ module.exports = { label: 'Auth', collapsed: false, items: [ + 'guides/auth/intro', 'guides/auth/managing-user-data', { type: 'category', diff --git a/web/src/css/custom.css b/web/src/css/custom.css index 550c5e5f3bc..1c0ca65e2b1 100755 --- a/web/src/css/custom.css +++ b/web/src/css/custom.css @@ -61,6 +61,8 @@ --custom-primary-light: #65d9a5; --custom-primary-lighter: #9fe7c7; --custom-primary-lightest: #c5f1dd; + --custom-primary-rgba: RGBA(36, 180, 126, 1); + --custom-primary-rgb: 36, 180, 126; --custom-background-color: #1f1f1f; --custom-background-color-highlight: #fff; --custom-background-color-diff: #f5f6f7; @@ -1381,3 +1383,35 @@ h4.method-list-item-label .method-list-item-validation { overflow: hidden; border: 2px solid var(--custom-border-color); } + +.badge--official { + border-radius: 3px; + font-size: 0.6rem; + font-weight: 400; + text-transform: uppercase; + border-color: var(--custom-primary); + background-color: rgba(var(--custom-primary-rgb), 0.8); +} +.badge--unofficial { + border-radius: 3px; + font-size: 0.6rem; + font-weight: 400; + text-transform: uppercase; + color: var(--custom-content-color-lightest); + background: rgba(0, 0, 0, 0.1); + border-color: rgba(0, 0, 0, 0.05); +} + +:root[data-theme='dark'] .badge--official { + background-color: rgba(var(--custom-primary-rgb), 0.5); +} +:root[data-theme='dark'] .badge--unofficial { + border-color: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.05); +} +.code-block { + border-radius: 3px; + font-family: 'office code pro'; + border: 1px solid var(--custom-border-color); + padding: 2px 6px; +} \ No newline at end of file diff --git a/web/src/data/authProviders.js b/web/src/data/authProviders.js new file mode 100644 index 00000000000..dbaadd482bc --- /dev/null +++ b/web/src/data/authProviders.js @@ -0,0 +1,112 @@ +const authProviders = [ + { + name: 'Email', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-apple', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Apple', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-apple', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Bitbucket', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-bitbucket', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Discord', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-discord', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Facebook', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-facebook', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'GitHub', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-github', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'GitLab', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-gitlab', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Google', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-google', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Twitter', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-twitter', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Twitch', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-twitch', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Twilio', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-twilio', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, + { + name: 'Messagebird', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-twilio', + official: false, + supporter: 'Messagebird', + platform: false, + selfHosted: true, + }, +] + +export default authProviders \ No newline at end of file From d20b00061ec84407dc06d693326433ae2e4fb015 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:47:04 +0800 Subject: [PATCH 03/10] adds a description for our auth section --- web/docs/guides/auth/intro.mdx | 23 ++++++++++++++++++++++- web/src/data/authProviders.js | 11 ++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/web/docs/guides/auth/intro.mdx b/web/docs/guides/auth/intro.mdx index 9921c3ec26e..a600bf0a378 100644 --- a/web/docs/guides/auth/intro.mdx +++ b/web/docs/guides/auth/intro.mdx @@ -1,7 +1,9 @@ --- id: intro title: Supabase Auth +sidebar_label: Introduction description: Use Supabase to Authenticate and Authorize your users. +# hide_table_of_contents: true --- import Link from '@docusaurus/Link' @@ -9,7 +11,21 @@ import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem' import providers from '@site/src/data/authProviders' -
+Supabase Auth is designed to work with Postgres. There are two parts to every Auth system: + +- Authentication: should this person be allowed in? If yes, who are they? +- Authorization: once they are in, what are they allowed to do? + +## Authentication + +You can authenticate your users in several ways: + +- Email & password. +- Magic links (one-click logins). +- Social providers. +- Phone logins. + +
{providers.map((x) => (
@@ -44,3 +60,8 @@ import providers from '@site/src/data/authProviders' ))}
+ + +## Authorization + +- TBD \ No newline at end of file diff --git a/web/src/data/authProviders.js b/web/src/data/authProviders.js index dbaadd482bc..2f721055309 100644 --- a/web/src/data/authProviders.js +++ b/web/src/data/authProviders.js @@ -8,6 +8,15 @@ const authProviders = [ platform: true, selfHosted: true, }, + { + name: 'Magic Links', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-apple', + official: true, + supporter: 'Supabase', + platform: true, + selfHosted: true, + }, { name: 'Apple', // logo: '/img/libraries/dart-icon.svg', @@ -99,7 +108,7 @@ const authProviders = [ selfHosted: true, }, { - name: 'Messagebird', + name: 'MessageBird', // logo: '/img/libraries/dart-icon.svg', href: '/docs/guides/auth/auth-twilio', official: false, From 924d473bdd119d263fdb0a33219abe2ebbad3821 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:53:43 +0800 Subject: [PATCH 04/10] Updates the titles of all the authN pages so that they are more readable --- web/docs/guides/auth/auth-apple.mdx | 2 +- web/docs/guides/auth/auth-bitbucket.mdx | 2 +- web/docs/guides/auth/auth-discord.mdx | 2 +- web/docs/guides/auth/auth-facebook.mdx | 2 +- web/docs/guides/auth/auth-github.mdx | 2 +- web/docs/guides/auth/auth-gitlab.mdx | 2 +- web/docs/guides/auth/auth-google.mdx | 2 +- web/docs/guides/auth/auth-twilio.mdx | 2 +- web/docs/guides/auth/auth-twitch.mdx | 2 +- web/docs/guides/auth/auth-twitter.mdx | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/web/docs/guides/auth/auth-apple.mdx b/web/docs/guides/auth/auth-apple.mdx index 9ee35962e3a..0391b3d6be6 100644 --- a/web/docs/guides/auth/auth-apple.mdx +++ b/web/docs/guides/auth/auth-apple.mdx @@ -1,6 +1,6 @@ --- id: auth-apple -title: "OAuth with Apple" +title: "Login with Apple" description: Add Apple OAuth to your Supabase project --- diff --git a/web/docs/guides/auth/auth-bitbucket.mdx b/web/docs/guides/auth/auth-bitbucket.mdx index 625d9df8861..6ffc10c4760 100644 --- a/web/docs/guides/auth/auth-bitbucket.mdx +++ b/web/docs/guides/auth/auth-bitbucket.mdx @@ -1,6 +1,6 @@ --- id: auth-bitbucket -title: "OAuth with Bitbucket" +title: "Login with Bitbucket" description: Add Bitbucket OAuth to your Supabase project --- diff --git a/web/docs/guides/auth/auth-discord.mdx b/web/docs/guides/auth/auth-discord.mdx index c11ce5b1997..a76631bdae5 100644 --- a/web/docs/guides/auth/auth-discord.mdx +++ b/web/docs/guides/auth/auth-discord.mdx @@ -1,6 +1,6 @@ --- id: auth-discord -title: "OAuth with Discord" +title: "Login with Discord" description: Add Discord OAuth to your Supabase project --- diff --git a/web/docs/guides/auth/auth-facebook.mdx b/web/docs/guides/auth/auth-facebook.mdx index b0c7e7b1287..8b2c5fb8b9f 100644 --- a/web/docs/guides/auth/auth-facebook.mdx +++ b/web/docs/guides/auth/auth-facebook.mdx @@ -1,6 +1,6 @@ --- id: auth-facebook -title: "OAuth with Facebook" +title: "Login with Facebook" description: Add Facebook OAuth to your Supabase project --- diff --git a/web/docs/guides/auth/auth-github.mdx b/web/docs/guides/auth/auth-github.mdx index 065271573b1..166a78102a8 100644 --- a/web/docs/guides/auth/auth-github.mdx +++ b/web/docs/guides/auth/auth-github.mdx @@ -1,6 +1,6 @@ --- id: auth-github -title: "OAuth with GitHub" +title: "Login with GitHub" description: Add GitHub OAuth to your Supabase project --- diff --git a/web/docs/guides/auth/auth-gitlab.mdx b/web/docs/guides/auth/auth-gitlab.mdx index 350b9adf41a..eb3a9252f5f 100644 --- a/web/docs/guides/auth/auth-gitlab.mdx +++ b/web/docs/guides/auth/auth-gitlab.mdx @@ -1,6 +1,6 @@ --- id: auth-gitlab -title: "OAuth with GitLab" +title: "Login with GitLab" description: Add GitLab OAuth to your Supabase project --- diff --git a/web/docs/guides/auth/auth-google.mdx b/web/docs/guides/auth/auth-google.mdx index ea4d8f46344..481cd653cce 100644 --- a/web/docs/guides/auth/auth-google.mdx +++ b/web/docs/guides/auth/auth-google.mdx @@ -1,6 +1,6 @@ --- id: auth-google -title: "OAuth with Google" +title: "Login with Google" description: Add Google OAuth to your Supabase project --- diff --git a/web/docs/guides/auth/auth-twilio.mdx b/web/docs/guides/auth/auth-twilio.mdx index 172473afd60..a9ed349931a 100644 --- a/web/docs/guides/auth/auth-twilio.mdx +++ b/web/docs/guides/auth/auth-twilio.mdx @@ -1,6 +1,6 @@ --- id: auth-twilio -title: SMS OTP with Twilio +title: Phone Auth with Twilio description: How to set up and use Mobile OTP with Twilio and Supabase. --- diff --git a/web/docs/guides/auth/auth-twitch.mdx b/web/docs/guides/auth/auth-twitch.mdx index 98ab85cccf6..4bc9cc7ab9a 100644 --- a/web/docs/guides/auth/auth-twitch.mdx +++ b/web/docs/guides/auth/auth-twitch.mdx @@ -1,6 +1,6 @@ --- id: auth-twitch -title: 'OAuth with Twitch' +title: 'Login with Twitch' description: Add Twitch OAuth to your Supabase project --- diff --git a/web/docs/guides/auth/auth-twitter.mdx b/web/docs/guides/auth/auth-twitter.mdx index 3cc755cfd24..c422b2cc9ff 100644 --- a/web/docs/guides/auth/auth-twitter.mdx +++ b/web/docs/guides/auth/auth-twitter.mdx @@ -1,6 +1,6 @@ --- id: auth-twitter -title: "OAuth with Twitter" +title: "Login with Twitter" description: Add Twitter OAuth to your Supabase project --- From 1658d749c230eaa2beaf76d7768b805c569e6c16 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Thu, 9 Sep 2021 20:41:17 +0800 Subject: [PATCH 05/10] Moves some of the auth to the new auth section --- web/docs/guides/auth.mdx | 261 +------------------- web/docs/guides/auth/row-level-security.mdx | 70 ++++-- web/src/data/authProviders.js | 27 +- 3 files changed, 74 insertions(+), 284 deletions(-) diff --git a/web/docs/guides/auth.mdx b/web/docs/guides/auth.mdx index 82e460e853b..9fd357f9440 100644 --- a/web/docs/guides/auth.mdx +++ b/web/docs/guides/auth.mdx @@ -83,22 +83,6 @@ let user = await supabase.from('users').select('user_id, name') // Still => { id: 'd0714948', name: 'Jane' } ``` -## Policies Are Like `WHERE` Clauses - -Policies are easy to understand once you get the hang of them. You can just think of them as adding a `WHERE` clause to every query. For example if you had a policy like this: - -```sql -create policy "Individuals can view their own todos." on todos for - select using (auth.uid() = user_id); -``` - -It would translate to this whenever a user tries to select from the todos table: - -```sql -select * -from todos -where auth.uid() = todos.user_id; -- Policy is implicitly added. -``` ## How It Works @@ -110,252 +94,9 @@ where auth.uid() = todos.user_id; -- Policy is implicitly added. Supabase provides a special function in Postgres, `auth.uid()`, which extracts the user's UID from the JWT. This is especially useful when creating policies. -## Policy Examples - -Here are some examples to show you the power of PostgreSQL's RLS. Each policy is attached to a table, and the policy is executed -every time a table is accessed. - -### Allow read access - -```sql --- 1. Create table -create table profiles ( - id uuid references auth.users, - avatar_url text -); - --- 2. Enable RLS -alter table profiles - enable row level security; - --- 3. Create Policy -create policy "Public profiles are viewable by everyone." - on profiles for select using ( - true - ); -``` - -1. Creates a table called `profiles` in the public schema (default schema). -2. Enables Row Level Security. -3. Creates a policy which allows all `select` queries to run. - -### Restrict updates - -```sql --- 1. Create table -create table profiles ( - id uuid references auth.users, - avatar_url text -); - --- 2. Enable RLS -alter table profiles - enable row level security; - --- 3. Create Policy -create policy "Users can update their own profiles." - on profiles for update using ( - auth.uid() = id - ); -``` - -1. Creates a table called `profiles` in the public schema (default schema). -2. Enables RLS. -3. Creates a policy which allows logged in users to update their own data. - -### Policies with joins - -Policies can even include table joins. This example shows how you can query "external" tables to build more advanced rules. - -```sql -create table teams ( - id serial primary key, - name text -); - --- 2. Create many to many join -create table members ( - team_id bigint references teams, - user_id uuid references auth.users -); - --- 3. Enable RLS -alter table teams - enable row level security; - --- 4. Create Policy -create policy "Team members can update team details if they belong to the team." - on teams - for update using ( - auth.uid() in ( - select user_id from members - where team_id = id - ) - ); -``` - -### Policies with security definer functions - -Policies can also make use of `security definer functions`. This is useful in a many-to-many relationship where you want to restrict access to the linking table. Following the `teams` and `members` example from above, this example shows how you can use security definer function in combination with a policy to control access to the `members` table. - -```sql --- 1. Follow example for 'Policies with joins' above - --- 2. Enable RLS -alter table members - enable row level security - --- 3. Create security definer function -create or replace function get_teams_for_user(user_id uuid) -returns setof bigint as $$ - select team_id - from members - where user_id = $1 -$$ stable language sql security definer; - --- 4. Create Policy -create policy "Team members can update team members if they belong to the team." - on members - for all using ( - team_id in ( - select get_teams_for_user(auth.uid()) - ) - ); - -``` - -### Verifying email domains - -Postgres has a function `right(string, n)` that returns the rightmost n characters of a string. -You could use this to match staff member's email domains. - -```sql --- 1. Create table -create table leaderboard ( - id uuid references auth.users, - high_score bigint -); - --- 2. Enable RLS -alter table leaderboard - enable row level security; - --- 3. Create Policy -create policy "Only Blizzard staff can update leaderboard" - on leaderboard - for update using ( - right(auth.email(), 13) = '@blizzard.com' - ); -``` - -### Time to live for rows - -Policies can also be used to implement TTL or time to live feature that you see in Instagram stories or Snapchat. -In the following example, rows of `stories` table are available only if they have been created within the last 24 hours. - -```sql --- 1. Create table -create table if not exists stories ( - id uuid not null primary key DEFAULT uuid_generate_v4(), - created_at timestamp with time zone default timezone('utc' :: text, now()) not null, - content text not null -); - --- 2. Enable RLS -alter table stories - enable row level security; - --- 3. Create Policy -create policy "Stories are live for a day" - on stories - for select using ( - created_at > (current_timestamp - interval '1 day') - ); -``` - -### Advanced policies - -Use the full power of SQL to build extremely advanced rules. - -In this example, we will create a `posts` and `comments` tables and then create a policy that depends on another policy. -(In this case, the comments policy depends on the posts policy.) - -```sql -create table posts ( - id serial primary key, - creator_id uuid not null references auth.users(id), - title text not null, - body text not null, - publish_date date not null default now(), - audience uuid[] null -- many to many table omitted for brevity -); - -create table comments ( - id serial primary key, - post_id int not null references posts(id) on delete cascade, - user_id uuid not null references auth.users(id), - body text not null, - comment_date date not null default now() -); - -create policy "Creator can see their own posts" -on posts -for select -using ( - auth.uid() = posts.creator_id -); - -create policy "Logged in users can see the posts if they belong to the post 'audience'." -on posts -for select -using ( - auth.uid() = any (posts.audience) -); - -create policy "Users can see all comments for posts they have access to." -on comments -for select -using ( - exists ( - select 1 from posts - where posts.id = comments.post_id - ) -); -``` ## Tips -### You don't have to use policies - -You can also put your authorization rules in your middleware, similar to how you would create security rules with any other `backend <-> middleware <-> frontend` architecture. - -Policies are a tool. In the case of "serverless/Jamstack" setups, they are especially effective because you don't have to deploy any middleware at all. - -However, if you want to use another authorization method for your applications, that's also fine. Supabase is "just Postgres", so if your application -works with Postgres, then it also works with Supabase. - -Tip: Make sure to enable RLS for all your tables, so that your tables are inaccessible. Then use the "Service" which we provide, which is designed to bypass RLS. - -### Check authentication settings on Supabase - -Navigate to Authentication > Settings on [app.supabase.io](https://app.supabase.io), and you'll be able to change settings for things like: - -- SITE URL, which is used for determining where to redirect users after they confirm their email addresses or attempt to use a magic link to log in. -- Disabling email confirmations. -- Enabling external OAuth providers, such as Google and GitHub. - -### Never use a service key on the client - -Supabase provides special "Service" keys, which can be used to bypass all RLS. -These should never be used in the browser or exposed to customers, but they are useful for administrative tasks. - -### Managing User Data - -For security purposes, the `auth` schema is not exposed on the auto-generated API. -Even though Supabase provides an `auth.users` table, it can be helpful to create tables in the `public` schema for storing user data. - -You can read more about this in the [Managing User Data](/docs/guides/auth/managing-user-data) guide. - ### Disable realtime for private tables Our realtime server doesn't provide per-user security. Until we build a more robust auth system for WebSockets, you can disable realtime functionality for any private tables. To do this, you can manage the underlying Postgres replication publication: @@ -385,5 +126,5 @@ We're in the process of building enhanced realtime security. ## Next Steps -- Read more about [Row Level Security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). +- Read more about Auth in the [Guides](/docs/guides/auth/intro). - Sign in: [app.supabase.io](https://app.supabase.io) diff --git a/web/docs/guides/auth/row-level-security.mdx b/web/docs/guides/auth/row-level-security.mdx index 73012ca47f7..dfa15167199 100644 --- a/web/docs/guides/auth/row-level-security.mdx +++ b/web/docs/guides/auth/row-level-security.mdx @@ -4,19 +4,26 @@ title: Row Level Security description: Secure your data using Postgres Row Level Security. --- +When you need granular authorization rules, nothing beats PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). + +[Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs. -## Policies Are Like `WHERE` Clauses -Policies are easy to understand once you get the hang of them. You can just think of them as adding a `WHERE` clause to every query. For example if you had a policy like this: +## Policies + +Policies are easy to understand once you get the hang of them. Each policy is attached to a table, and the policy is executed +every time a table is accessed. +You can just think of them as adding a `WHERE` clause to every query. For example a policy like this ... ```sql -create policy "Individuals can view their own todos." on todos for - select using (auth.uid() = user_id); +create policy "Individuals can view their own todos." + on todos for select + using ( auth.uid() = user_id ); ``` -It would translate to this whenever a user tries to select from the todos table: +.. would translate to this whenever a user tries to select from the todos table: ```sql select * @@ -24,20 +31,26 @@ from todos where auth.uid() = todos.user_id; -- Policy is implicitly added. ``` -## How It Works +## Helper Functions -1. A user signs up. Supabase creates a new user in the `auth.users` table. -2. Supabase returns a new JWT, which contains the user's `UUID`. -3. Every request to your database also sends the JWT. -4. Postgres inspects the JWT to determine the user making the request. -5. The user's UID can be used in policies to restrict access to rows. +Supabase provides you with a few easy functions that you can use with your policies. -Supabase provides a special function in Postgres, `auth.uid()`, which extracts the user's UID from the JWT. This is especially useful when creating policies. -## Policy Examples +### `auth.uid()` -Here are some examples to show you the power of PostgreSQL's RLS. Each policy is attached to a table, and the policy is executed -every time a table is accessed. +Returns the ID of the user making the request. + +### `auth.role()` + +Returns the role of the user making the request. In most cases this is either `authenticated` or `anon`. + +### `auth.email()` + +Retuns the email of the user making the request. + +## Examples + +Here are some examples to show you the power of PostgreSQL's RLS. ### Allow read access @@ -251,6 +264,33 @@ using ( ## Tips +### Disable realtime for private tables + +Our realtime server doesn't provide per-user security. Until we build a more robust auth system for WebSockets, you can disable realtime functionality for any private tables. To do this, you can manage the underlying Postgres replication publication: + +```sql +/** + * REALTIME SUBSCRIPTIONS + * Only allow realtime listening on public tables. + */ + +begin; + -- remove the realtime publication + drop publication if exists supabase_realtime; + + -- re-create the publication but don't enable it for any tables + create publication supabase_realtime; +commit; + +-- add a table to the publication +alter publication supabase_realtime add table products; + +-- add other tables to the publication +alter publication supabase_realtime add table posts; +``` + +We're in the process of building enhanced realtime security. + ### You don't have to use policies You can also put your authorization rules in your middleware, similar to how you would create security rules with any other `backend <-> middleware <-> frontend` architecture. diff --git a/web/src/data/authProviders.js b/web/src/data/authProviders.js index 2f721055309..f510024487e 100644 --- a/web/src/data/authProviders.js +++ b/web/src/data/authProviders.js @@ -26,6 +26,15 @@ const authProviders = [ platform: true, selfHosted: true, }, + { + name: 'Azure', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-apple', + official: false, + supporter: 'TBD', + platform: false, + selfHosted: true, + }, { name: 'Bitbucket', // logo: '/img/libraries/dart-icon.svg', @@ -80,6 +89,15 @@ const authProviders = [ platform: true, selfHosted: true, }, + { + name: 'MessageBird', + // logo: '/img/libraries/dart-icon.svg', + href: '/docs/guides/auth/auth-twilio', + official: false, + supporter: 'Messagebird', + platform: false, + selfHosted: true, + }, { name: 'Twitter', // logo: '/img/libraries/dart-icon.svg', @@ -107,15 +125,6 @@ const authProviders = [ platform: true, selfHosted: true, }, - { - name: 'MessageBird', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-twilio', - official: false, - supporter: 'Messagebird', - platform: false, - selfHosted: true, - }, ] export default authProviders \ No newline at end of file From 82ff708f5b598a2006a57d997a0d0255d2021efa Mon Sep 17 00:00:00 2001 From: awalias Date: Tue, 14 Sep 2021 14:53:34 +0200 Subject: [PATCH 06/10] fix: remove duplicate section --- web/docs/guides/auth/row-level-security.mdx | 23 --------------------- 1 file changed, 23 deletions(-) diff --git a/web/docs/guides/auth/row-level-security.mdx b/web/docs/guides/auth/row-level-security.mdx index dfa15167199..83e38bba35a 100644 --- a/web/docs/guides/auth/row-level-security.mdx +++ b/web/docs/guides/auth/row-level-security.mdx @@ -307,26 +307,3 @@ Tip: Make sure to enable RLS for all your tables, so that your tables are inacce Supabase provides special "Service" keys, which can be used to bypass all RLS. These should never be used in the browser or exposed to customers, but they are useful for administrative tasks. - -### Disable realtime for private tables - -Our realtime server doesn't provide per-user security. Until we build a more robust auth system for WebSockets, you can disable realtime functionality for any private tables. To do this, you can manage the underlying Postgres replication publication: - -```sql -/** - * REALTIME SUBSCRIPTIONS - * Only allow realtime listening on public tables. - */ -begin; - -- remove the realtime publication - drop publication if exists supabase_realtime; - -- re-create the publication but don't enable it for any tables - create publication supabase_realtime; -commit; --- add a table to the publication -alter publication supabase_realtime add table products; --- add other tables to the publication -alter publication supabase_realtime add table posts; -``` - -We're in the process of building enhanced realtime security. \ No newline at end of file From 8b35d852a224057cf30f531d3f05a90e64ac29e0 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Wed, 22 Sep 2021 20:31:17 +0800 Subject: [PATCH 07/10] Adds the trigger back to the user docs --- web/docs/guides/auth/managing-user-data.mdx | 33 ++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/web/docs/guides/auth/managing-user-data.mdx b/web/docs/guides/auth/managing-user-data.mdx index bf55d3d18cf..ed1e169a1da 100644 --- a/web/docs/guides/auth/managing-user-data.mdx +++ b/web/docs/guides/auth/managing-user-data.mdx @@ -76,4 +76,35 @@ const { data: profile } = await supabase If you need to fetch a full list of user profiles, we supply a `service_key` which you can use with your API and Client Libraries to bypass Row Level Security. -Make sure you _NEVER_ expose this publicly. But it can be used on the server-side to fetch all of the profiles. \ No newline at end of file +Make sure you _NEVER_ expose this publicly. But it can be used on the server-side to fetch all of the profiles. + + +## Advanced techniques + +### Using triggers + +If you want to add a row to your `public.users` table every time a user signs up, you can use triggers. +If the trigger fails however, it could block the user sign ups - so make sure that the code is well-tested. + +For example: + +```sql +-- inserts a row into public.users +create function public.handle_new_user() +returns trigger +language plpgsql +security definer +search_path = public +as $$ +begin + insert into public.users (id) + values (new.id); + return new; +end; +$$; + +-- trigger the function every time a user is created +create trigger on_auth_user_created + after insert on auth.users + for each row execute procedure public.handle_new_user(); +``` \ No newline at end of file From 57c0f8704fb80f8065df2d1fba9cd418cf66f27c Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Wed, 22 Sep 2021 20:33:23 +0800 Subject: [PATCH 08/10] Hides auth providers which don't have any docs associated (for now) --- web/src/data/authProviders.js | 54 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/web/src/data/authProviders.js b/web/src/data/authProviders.js index f510024487e..047b2744fe1 100644 --- a/web/src/data/authProviders.js +++ b/web/src/data/authProviders.js @@ -1,22 +1,22 @@ const authProviders = [ - { - name: 'Email', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-apple', - official: true, - supporter: 'Supabase', - platform: true, - selfHosted: true, - }, - { - name: 'Magic Links', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-apple', - official: true, - supporter: 'Supabase', - platform: true, - selfHosted: true, - }, + // { + // name: 'Email', + // // logo: '/img/libraries/dart-icon.svg', + // href: '/docs/guides/auth/auth-apple', + // official: true, + // supporter: 'Supabase', + // platform: true, + // selfHosted: true, + // }, + // { + // name: 'Magic Links', + // // logo: '/img/libraries/dart-icon.svg', + // href: '/docs/guides/auth/auth-apple', + // official: true, + // supporter: 'Supabase', + // platform: true, + // selfHosted: true, + // }, { name: 'Apple', // logo: '/img/libraries/dart-icon.svg', @@ -89,15 +89,15 @@ const authProviders = [ platform: true, selfHosted: true, }, - { - name: 'MessageBird', - // logo: '/img/libraries/dart-icon.svg', - href: '/docs/guides/auth/auth-twilio', - official: false, - supporter: 'Messagebird', - platform: false, - selfHosted: true, - }, + // { + // name: 'MessageBird', + // // logo: '/img/libraries/dart-icon.svg', + // href: '/docs/guides/auth/auth-twilio', + // official: false, + // supporter: 'Messagebird', + // platform: false, + // selfHosted: true, + // }, { name: 'Twitter', // logo: '/img/libraries/dart-icon.svg', From 4108d5ce34ba657222ae3aadefa257683e7681f5 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Wed, 22 Sep 2021 20:42:49 +0800 Subject: [PATCH 09/10] Adds a link to the RLS guides in the intro --- web/docs/guides/auth/intro.mdx | 7 +- web/docs/guides/auth/row-level-security.mdx | 80 +++++++++++---------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/web/docs/guides/auth/intro.mdx b/web/docs/guides/auth/intro.mdx index a600bf0a378..1d52730c4bf 100644 --- a/web/docs/guides/auth/intro.mdx +++ b/web/docs/guides/auth/intro.mdx @@ -64,4 +64,9 @@ You can authenticate your users in several ways: ## Authorization -- TBD \ No newline at end of file + +When you need granular authorization rules, nothing beats PostgreSQL's Row Level Security (RLS). + +Policies are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs. + +Get started with our [Row Level Security Guides](/docs/guides/auth/row-level-security). \ No newline at end of file diff --git a/web/docs/guides/auth/row-level-security.mdx b/web/docs/guides/auth/row-level-security.mdx index 83e38bba35a..6cc880c451e 100644 --- a/web/docs/guides/auth/row-level-security.mdx +++ b/web/docs/guides/auth/row-level-security.mdx @@ -52,7 +52,7 @@ Retuns the email of the user making the request. Here are some examples to show you the power of PostgreSQL's RLS. -### Allow read access +### Allow read access ```sql -- 1. Create table @@ -62,11 +62,11 @@ create table profiles ( ); -- 2. Enable RLS -alter table profiles +alter table profiles enable row level security; -- 3. Create Policy -create policy "Public profiles are viewable by everyone." +create policy "Public profiles are viewable by everyone." on profiles for select using ( true ); @@ -74,10 +74,9 @@ create policy "Public profiles are viewable by everyone." 1. Creates a table called `profiles` in the public schema (default schema). 2. Enables Row Level Security. -3. Creates a policy which allows all `select` queries to run. +3. Creates a policy which allows all `select` queries to run. - -### Restrict updates +### Restrict updates ```sql -- 1. Create table @@ -87,11 +86,11 @@ create table profiles ( ); -- 2. Enable RLS -alter table profiles +alter table profiles enable row level security; -- 3. Create Policy -create policy "Users can update their own profiles." +create policy "Users can update their own profiles." on profiles for update using ( auth.uid() = id ); @@ -101,7 +100,6 @@ create policy "Users can update their own profiles." 2. Enables RLS. 3. Creates a policy which allows logged in users to update their own data. - ### Policies with joins Policies can even include table joins. This example shows how you can query "external" tables to build more advanced rules. @@ -119,16 +117,16 @@ create table members ( ); -- 3. Enable RLS -alter table teams +alter table teams enable row level security; -- 4. Create Policy create policy "Team members can update team details if they belong to the team." on teams for update using ( - auth.uid() in ( - select user_id from members - where team_id = id + auth.uid() in ( + select user_id from members + where team_id = id ) ); ``` @@ -141,31 +139,37 @@ Policies can also make use of `security definer functions`. This is useful in a -- 1. Follow example for 'Policies with joins' above -- 2. Enable RLS -alter table members +alter table members enable row level security -- 3. Create security definer function -create or replace function get_teams_for_user(user_id uuid) -returns setof bigint as $$ - select team_id - from members - where user_id = $1 -$$ stable language sql security definer; +create or replace function get_teams_for_authenticated_user() +returns setof bigint +language sql +security definer +set search_path = public +stable +as $$ + select team_id + from members + where user_id = auth.uid() +$$; -- 4. Create Policy create policy "Team members can update team members if they belong to the team." on members for all using ( team_id in ( - select get_teams_for_user(auth.uid()) + select get_teams_for_authenticated_user() ) ); + ``` ### Verifying email domains -Postgres has a function `right(string, n)` that returns the rightmost n characters of a string. -You could use this to match staff member's email domains. +Postgres has a function `right(string, n)` that returns the rightmost n characters of a string. +You could use this to match staff member's email domains. ```sql -- 1. Create table @@ -175,7 +179,7 @@ create table leaderboard ( ); -- 2. Enable RLS -alter table leaderboard +alter table leaderboard enable row level security; -- 3. Create Policy @@ -188,8 +192,8 @@ create policy "Only Blizzard staff can update leaderboard" ### Time to live for rows -Policies can also be used to implement TTL or time to live feature that you see in Instagram stories or Snapchat. -In the following example, rows of `stories` table are available only if they have been created within the last 24 hours. +Policies can also be used to implement TTL or time to live feature that you see in Instagram stories or Snapchat. +In the following example, rows of `stories` table are available only if they have been created within the last 24 hours. ```sql -- 1. Create table @@ -200,7 +204,7 @@ create table if not exists stories ( ); -- 2. Enable RLS -alter table stories +alter table stories enable row level security; -- 3. Create Policy @@ -208,31 +212,31 @@ create policy "Stories are live for a day" on stories for select using ( created_at > (current_timestamp - interval '1 day') - ); + ); ``` ### Advanced policies Use the full power of SQL to build extremely advanced rules. -In this example, we will create a `posts` and `comments` tables and then create a policy that depends on another policy. +In this example, we will create a `posts` and `comments` tables and then create a policy that depends on another policy. (In this case, the comments policy depends on the posts policy.) ```sql create table posts ( - id serial primary key, - creator_id uuid not null references auth.users(id), - title text not null, - body text not null, - publish_date date not null default now(), + id serial primary key, + creator_id uuid not null references auth.users(id), + title text not null, + body text not null, + publish_date date not null default now(), audience uuid[] null -- many to many table omitted for brevity ); create table comments ( - id serial primary key, - post_id int not null references posts(id) on delete cascade, - user_id uuid not null references auth.users(id), - body text not null, + id serial primary key, + post_id int not null references posts(id) on delete cascade, + user_id uuid not null references auth.users(id), + body text not null, comment_date date not null default now() ); From b19382cace1cfcf6630857bb1505e9f3634ee8fc Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Wed, 22 Sep 2021 20:49:38 +0800 Subject: [PATCH 10/10] Adds link to WALRUS --- web/docs/guides/auth.mdx | 2 +- web/docs/guides/auth/row-level-security.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/docs/guides/auth.mdx b/web/docs/guides/auth.mdx index 20e3209c334..38aa8a671b8 100644 --- a/web/docs/guides/auth.mdx +++ b/web/docs/guides/auth.mdx @@ -121,7 +121,7 @@ alter publication supabase_realtime add table products; alter publication supabase_realtime add table posts; ``` -We're in the process of building enhanced realtime security. +We're in the process of building [enhanced realtime security](https://github.com/supabase/walrus). ## Next Steps diff --git a/web/docs/guides/auth/row-level-security.mdx b/web/docs/guides/auth/row-level-security.mdx index 6cc880c451e..12ec835ad4d 100644 --- a/web/docs/guides/auth/row-level-security.mdx +++ b/web/docs/guides/auth/row-level-security.mdx @@ -293,7 +293,7 @@ alter publication supabase_realtime add table products; alter publication supabase_realtime add table posts; ``` -We're in the process of building enhanced realtime security. +We're in the process of building [enhanced realtime security](https://github.com/supabase/walrus). ### You don't have to use policies