Skip to content

Commit ac20109

Browse files
authored
Merge pull request #221 from supabase/restrict-accounts-and-orgs
Disallow users from viewing full accounts and organizations views
2 parents 21d2f9e + eecf485 commit ac20109

File tree

8 files changed

+134
-73
lines changed

8 files changed

+134
-73
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
-- Only allow authenticated users to view their own accounts.
2+
alter policy accounts_select_policy
3+
on app.accounts
4+
to authenticated
5+
using (id = auth.uid());
6+
7+
-- Only allow organization maintainers to view their own organizations.
8+
alter policy organizations_select_policy
9+
on app.organizations
10+
to authenticated
11+
using (app.is_organization_maintainer(auth.uid(), id));
12+
13+
-- Allow authenticated users to get an account by handle.
14+
create or replace function public.get_account(
15+
handle text
16+
)
17+
returns setof public.accounts
18+
language sql
19+
security definer
20+
strict
21+
as $$
22+
select id, handle, avatar_path, display_name, bio, created_at
23+
from public.accounts a
24+
where a.handle = get_account.handle
25+
and auth.uid() is not null;
26+
$$;
27+
28+
-- Allow authenticated users to get an organization by handle.
29+
create or replace function public.get_organization(
30+
handle text
31+
)
32+
returns setof public.organizations
33+
language sql
34+
security definer
35+
strict
36+
as $$
37+
select id, handle, avatar_path, display_name, bio, created_at
38+
from public.organizations o
39+
where o.handle = get_organization.handle
40+
and auth.uid() is not null;
41+
$$;
42+
43+
-- Allow service role to read all accounts and organizations.
44+
grant select on app.accounts to service_role;
45+
grant select on app.organizations to service_role;
46+
47+
-- Allow service role to read all packages.
48+
grant select on app.packages to service_role;
49+
grant select on app.package_versions to service_role;

website/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
NEXT_PUBLIC_SUPABASE_URL=""
22
NEXT_PUBLIC_SUPABASE_ANON_KEY=""
3+
SUPABASE_SERVICE_ROLE_KEY=""

website/data/database.types.ts

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,7 @@ export type Database = {
4646
handle: string | null
4747
id: string | null
4848
}
49-
Relationships: [
50-
{
51-
foreignKeyName: 'accounts_id_fkey'
52-
columns: ['id']
53-
isOneToOne: true
54-
referencedRelation: 'users'
55-
referencedColumns: ['id']
56-
},
57-
]
49+
Relationships: []
5850
}
5951
download_metrics: {
6052
Row: {
@@ -72,13 +64,6 @@ export type Database = {
7264
referencedRelation: 'packages'
7365
referencedColumns: ['id']
7466
},
75-
{
76-
foreignKeyName: 'downloads_package_id_fkey'
77-
columns: ['package_id']
78-
isOneToOne: false
79-
referencedRelation: 'packages'
80-
referencedColumns: ['id']
81-
},
8267
]
8368
}
8469
members: {
@@ -101,13 +86,6 @@ export type Database = {
10186
role?: 'maintainer' | null
10287
}
10388
Relationships: [
104-
{
105-
foreignKeyName: 'members_account_id_fkey'
106-
columns: ['account_id']
107-
isOneToOne: false
108-
referencedRelation: 'accounts'
109-
referencedColumns: ['id']
110-
},
11189
{
11290
foreignKeyName: 'members_account_id_fkey'
11391
columns: ['account_id']
@@ -122,13 +100,6 @@ export type Database = {
122100
referencedRelation: 'organizations'
123101
referencedColumns: ['id']
124102
},
125-
{
126-
foreignKeyName: 'members_organization_id_fkey'
127-
columns: ['organization_id']
128-
isOneToOne: false
129-
referencedRelation: 'organizations'
130-
referencedColumns: ['id']
131-
},
132103
]
133104
}
134105
organizations: {
@@ -161,13 +132,6 @@ export type Database = {
161132
referencedRelation: 'packages'
162133
referencedColumns: ['id']
163134
},
164-
{
165-
foreignKeyName: 'package_upgrades_package_id_fkey'
166-
columns: ['package_id']
167-
isOneToOne: false
168-
referencedRelation: 'packages'
169-
referencedColumns: ['id']
170-
},
171135
]
172136
}
173137
package_versions: {
@@ -191,13 +155,6 @@ export type Database = {
191155
referencedRelation: 'packages'
192156
referencedColumns: ['id']
193157
},
194-
{
195-
foreignKeyName: 'package_versions_package_id_fkey'
196-
columns: ['package_id']
197-
isOneToOne: false
198-
referencedRelation: 'packages'
199-
referencedColumns: ['id']
200-
},
201158
]
202159
}
203160
packages: {
@@ -214,15 +171,7 @@ export type Database = {
214171
package_name: string | null
215172
partial_name: string | null
216173
}
217-
Relationships: [
218-
{
219-
foreignKeyName: 'packages_handle_fkey'
220-
columns: ['handle']
221-
isOneToOne: false
222-
referencedRelation: 'handle_registry'
223-
referencedColumns: ['handle']
224-
},
225-
]
174+
Relationships: []
226175
}
227176
}
228177
Functions: {
@@ -242,6 +191,32 @@ export type Database = {
242191
Args: Record<PropertyKey, never>
243192
Returns: unknown[]
244193
}
194+
get_account: {
195+
Args: {
196+
handle: string
197+
}
198+
Returns: {
199+
avatar_path: string | null
200+
bio: string | null
201+
created_at: string | null
202+
display_name: string | null
203+
handle: string | null
204+
id: string | null
205+
}[]
206+
}
207+
get_organization: {
208+
Args: {
209+
handle: string
210+
}
211+
Returns: {
212+
avatar_path: string | null
213+
bio: string | null
214+
created_at: string | null
215+
display_name: string | null
216+
handle: string | null
217+
id: string | null
218+
}[]
219+
}
245220
new_access_token: {
246221
Args: {
247222
token_name: string
@@ -421,6 +396,7 @@ export type Database = {
421396
owner_id: string | null
422397
path_tokens: string[] | null
423398
updated_at: string | null
399+
user_metadata: Json | null
424400
version: string | null
425401
}
426402
Insert: {
@@ -434,6 +410,7 @@ export type Database = {
434410
owner_id?: string | null
435411
path_tokens?: string[] | null
436412
updated_at?: string | null
413+
user_metadata?: Json | null
437414
version?: string | null
438415
}
439416
Update: {
@@ -447,6 +424,7 @@ export type Database = {
447424
owner_id?: string | null
448425
path_tokens?: string[] | null
449426
updated_at?: string | null
427+
user_metadata?: Json | null
450428
version?: string | null
451429
}
452430
Relationships: [
@@ -468,6 +446,7 @@ export type Database = {
468446
key: string
469447
owner_id: string | null
470448
upload_signature: string
449+
user_metadata: Json | null
471450
version: string
472451
}
473452
Insert: {
@@ -478,6 +457,7 @@ export type Database = {
478457
key: string
479458
owner_id?: string | null
480459
upload_signature: string
460+
user_metadata?: Json | null
481461
version: string
482462
}
483463
Update: {
@@ -488,6 +468,7 @@ export type Database = {
488468
key?: string
489469
owner_id?: string | null
490470
upload_signature?: string
471+
user_metadata?: Json | null
491472
version?: string
492473
}
493474
Relationships: [
@@ -624,6 +605,10 @@ export type Database = {
624605
updated_at: string
625606
}[]
626607
}
608+
operation: {
609+
Args: Record<PropertyKey, never>
610+
Returns: string
611+
}
627612
search: {
628613
Args: {
629614
prefix: string
@@ -735,3 +720,18 @@ export type Enums<
735720
: PublicEnumNameOrOptions extends keyof PublicSchema['Enums']
736721
? PublicSchema['Enums'][PublicEnumNameOrOptions]
737722
: never
723+
724+
export type CompositeTypes<
725+
PublicCompositeTypeNameOrOptions extends
726+
| keyof PublicSchema['CompositeTypes']
727+
| { schema: keyof Database },
728+
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
729+
schema: keyof Database
730+
}
731+
? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes']
732+
: never = never,
733+
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
734+
? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName]
735+
: PublicCompositeTypeNameOrOptions extends keyof PublicSchema['CompositeTypes']
736+
? PublicSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions]
737+
: never

website/data/profiles/profile-query.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,12 @@ import {
88
import { useCallback } from 'react'
99
import { getAvatarUrl } from '~/lib/avatars'
1010
import supabase from '~/lib/supabase'
11-
import { NonNullableObject } from '~/lib/types'
12-
import { Database } from '../database.types'
1311
import { NotFoundError } from '../utils'
1412

1513
export type ProfileVariables = {
1614
handle?: string
1715
}
1816

19-
export type ProfileResponse =
20-
| NonNullableObject<Database['public']['Views']['accounts']['Row']>
21-
| NonNullableObject<Database['public']['Views']['organizations']['Row']>
22-
2317
export async function getProfile(
2418
{ handle }: ProfileVariables,
2519
signal?: AbortSignal
@@ -28,12 +22,8 @@ export async function getProfile(
2822
throw new Error('handle is required')
2923
}
3024

31-
let accountQuery = supabase.from('accounts').select('*').eq('handle', handle)
32-
33-
let organizationQuery = supabase
34-
.from('organizations')
35-
.select('*')
36-
.eq('handle', handle)
25+
let accountQuery = supabase.rpc('get_account', { handle })
26+
let organizationQuery = supabase.rpc('get_organization', { handle })
3727

3828
if (signal) {
3929
accountQuery = accountQuery.abortSignal(signal)
@@ -44,8 +34,8 @@ export async function getProfile(
4434
{ data: account, error: accountError },
4535
{ data: organization, error: organizationError },
4636
] = await Promise.all([
47-
accountQuery.maybeSingle<ProfileResponse>(),
48-
organizationQuery.maybeSingle<ProfileResponse>(),
37+
accountQuery.maybeSingle(),
38+
organizationQuery.maybeSingle(),
4939
])
5040

5141
if (accountError) {

website/data/static-path-queries.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import supabase from '~/lib/supabase'
1+
import supabaseAdmin from '~/lib/supabase-admin'
2+
3+
// [Alaister]: These functions are to be called server side only
4+
// as they bypass RLS. They will not work client side.
25

36
export async function getAllProfiles() {
47
const [{ data: organizations }, { data: accounts }] = await Promise.all([
5-
supabase
8+
supabaseAdmin
69
.from('organizations')
710
.select('handle')
811
.order('created_at', { ascending: false })
912
.limit(500)
1013
.returns<{ handle: string }[]>(),
11-
supabase
14+
supabaseAdmin
1215
.from('accounts')
1316
.select('handle')
1417
.order('created_at', { ascending: false })
@@ -20,7 +23,8 @@ export async function getAllProfiles() {
2023
}
2124

2225
export async function getAllPackages() {
23-
const { data } = await supabase
26+
const { data } = await supabaseAdmin
27+
2428
.from('packages')
2529
.select('handle,partial_name')
2630
.order('created_at', { ascending: false })

website/lib/supabase-admin.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createClient } from '@supabase/supabase-js'
2+
import { Database } from '~/data/database.types'
3+
4+
if (!process.env.NEXT_PUBLIC_SUPABASE_URL) {
5+
throw new Error('Missing NEXT_PUBLIC_SUPABASE_URL environment variable')
6+
}
7+
8+
if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
9+
throw new Error('Missing SUPABASE_SERVICE_ROLE_KEY environment variable')
10+
}
11+
12+
const supabaseAdmin = createClient<Database>(
13+
process.env.NEXT_PUBLIC_SUPABASE_URL,
14+
process.env.SUPABASE_SERVICE_ROLE_KEY
15+
)
16+
17+
export default supabaseAdmin

website/next.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @type {import('next').NextConfig} */
22

33
const cspHeader = `
4-
default-src 'self' ${process.env.NEXT_PUBLIC_SUPABASE_URL};
4+
default-src 'self' 'unsafe-eval' ${process.env.NEXT_PUBLIC_SUPABASE_URL};
55
style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com/ https://fonts.google.com/;
66
img-src 'self' data: ${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/;
77
object-src 'none';

website/pages/[handle]/_/edit.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ const EditAccountPage: NextPageWithLayout = () => {
7979
displayName: string
8080
bio: string
8181
}) => {
82-
if (!profile?.id) return console.error('Profile is required')
82+
if (!profile?.handle) return console.error('A profile handle is required')
8383

8484
try {
8585
if (uploadedFile) {

0 commit comments

Comments
 (0)