Chore/postgrest v9 (#3988)

* using slack clone to test

* updates auth functions to handle new GUC commands

* move postgREST to new version

* testing with todo list

* makeing our mount setup far more robust

* Adds some usage commands

* cleans up the auth functions

* test with todos app

* fix env var for GUC

* new auth functions - changed for performance improvement

* Adds some integration tests for RLS

* anon volume on postgres

* remove unused helpers

* fix broken docusaurus build

* Fix complaining vercel

* test instructions

* Use named imports

* Fixes imports

* all relative

* chore: add in tsconfig.json to /web

* finding these all over the place

* Update docker/docker-compose.yml

Co-authored-by: Steve Chavez <stevechavezast@gmail.com>

Co-authored-by: Jonathan Summers-Muir <MildTomato@users.noreply.github.com>
Co-authored-by: Steve Chavez <stevechavezast@gmail.com>
This commit is contained in:
Copple
2021-11-24 00:31:07 +08:00
committed by GitHub
parent a8c899cc99
commit 614faed4e4
35 changed files with 9055 additions and 21600 deletions

3
babel.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
}

View File

@@ -13,7 +13,7 @@ DISABLE_SIGNUP=false
## Email auth
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=false
ENABLE_EMAIL_AUTOCONFIRM=true
SMTP_ADMIN_EMAIL=admin@example.com
SMTP_HOST=mail
SMTP_PORT=2500

48
docker/dev/data.sql Normal file
View File

@@ -0,0 +1,48 @@
create table profiles (
id uuid references auth.users not null,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,
primary key (id),
unique(username),
constraint username_length check (char_length(username) >= 3)
);
alter table profiles enable row level security;
create policy "Public profiles are viewable by the owner."
on profiles for select
using ( auth.uid() = id );
create policy "Users can insert their own profile."
on profiles for insert
with check ( auth.uid() = id );
create policy "Users can update own profile."
on profiles for update
using ( auth.uid() = id );
-- Set up Realtime
begin;
drop publication if exists supabase_realtime;
create publication supabase_realtime;
commit;
alter publication supabase_realtime add table profiles;
-- Set up Storage
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
create policy "Avatar images are publicly accessible."
on storage.objects for select
using ( bucket_id = 'avatars' );
create policy "Anyone can upload an avatar."
on storage.objects for insert
with check ( bucket_id = 'avatars' );
create policy "Anyone can update an avatar."
on storage.objects for update
with check ( bucket_id = 'avatars' );

View File

@@ -7,4 +7,9 @@ services:
ports:
- '2500:2500' # SMTP
- '9000:9000' # web interface
- '1100:1100' # POP3
- '1100:1100' # POP3
db:
volumes:
- type: bind
source: ./dev/data.sql
target: /docker-entrypoint-initdb.d/data.sql

View File

@@ -65,7 +65,7 @@ services:
rest:
container_name: supabase-rest
image: postgrest/postgrest:v8.0.0
image: postgrest/postgrest:v8.0.0.20211102
depends_on:
- db
restart: unless-stopped
@@ -74,7 +74,7 @@ services:
PGRST_DB_SCHEMA: public, storage
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET}
PGRST_DB_USE_LEGACY_GUCS: "false"
realtime:
container_name: supabase-realtime
image: supabase/realtime:v0.15.0
@@ -141,20 +141,12 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- type: volume
source: volume_database
target: /var/lib/postgresql/data
- /var/lib/postgresql/data/pgdata
- type: bind
source: ./volumes/db/init
target: /docker-entrypoint-initdb.d
volumes:
volume_database:
driver: local
driver_opts:
type: none
device: ./volumes/db/data
o: bind
volume_storage:
driver: local
driver_opts:

View File

@@ -129,11 +129,11 @@ services:
- name: cors
## Secure Database routes
- name: pg-meta
- name: meta
_comment: "pg-meta: /pg/* -> http://pg-meta:8080/*"
url: http://pg-meta:8080/
url: http://meta:8080/
routes:
- name: pg-meta-all
- name: meta-all
strip_path: true
paths:
- /pg/

View File

@@ -88,21 +88,39 @@ VALUES ('20171026211738'),
('20180108183307'),
('20180119214651'),
('20180125194653');
-- Gets the User ID from the request cookie
create or replace function auth.uid() returns uuid as $$
select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid;
$$ language sql stable;
-- Gets the User ID from the request cookie
create or replace function auth.role() returns text as $$
select nullif(current_setting('request.jwt.claim.role', true), '')::text;
$$ language sql stable;
create or replace function auth.uid()
returns uuid
language sql stable
as $$
select
coalesce(
current_setting('request.jwt.claim.sub', true),
(current_setting('request.jwt.claims', true)::jsonb ->> 'sub')
)::uuid
$$;
-- Gets the User email
create or replace function auth.email() returns text as $$
select nullif(current_setting('request.jwt.claim.email', true), '')::text;
$$ language sql stable;
create or replace function auth.role()
returns text
language sql stable
as $$
select
coalesce(
current_setting('request.jwt.claim.role', true),
(current_setting('request.jwt.claims', true)::jsonb ->> 'role')
)::text
$$;
create or replace function auth.email()
returns text
language sql stable
as $$
select
coalesce(
current_setting('request.jwt.claim.email', true),
(current_setting('request.jwt.claims', true)::jsonb ->> 'email')
)::text
$$;
-- usage on auth functions to API roles
GRANT USAGE ON SCHEMA auth TO anon, authenticated, service_role;

View File

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@
"clean": "rm package-lock.json & rm -rf node_modules/ dist/ .next/"
},
"dependencies": {
"@supabase/supabase-js": "^1.3.2",
"@supabase/ui": "^0.6.1",
"@supabase/supabase-js": "^1.28.0",
"@supabase/ui": "^0.36.0",
"next": "^11.1.1",
"react": "^17.0.1",
"react-dom": "^17.0.1"

194
jest.config.ts Normal file
View File

@@ -0,0 +1,194 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
export default {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/62/vmlm9g556_n1v120hqkjrp8r0000gn/T/jest_dx",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: 'tests/coverage',
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',
// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: [
'json',
'text',
'lcov',
// "clover"
],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
setupFiles: ['<rootDir>/tests/jest-env'],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// testEnvironment: "jest-environment-node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// // The glob patterns Jest uses to detect test files
testMatch: [
'<rootDir>/tests/**/*.test.ts',
// "**/?(*.)+(spec|test).[tj]s?(x)"
],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
testPathIgnorePatterns: ['/node_modules/', 'docker'],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
}

4496
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

47
package.json Normal file
View File

@@ -0,0 +1,47 @@
{
"name": "Supabase",
"description": "The open source Firebase alternative.",
"version": "0.0.0",
"author": "Supabase, Inc.",
"license": "Apache-2.0",
"private": true,
"scripts": {
"docker:dev": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up --renew-anon-volumes",
"docker:up": "cd docker && docker-compose up",
"docker:down": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down --remove-orphans",
"docker:remove": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml rm -vfs",
"test": "jest"
},
"dependencies": {},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.4",
"@babel/preset-typescript": "^7.16.0",
"@supabase/supabase-js": "^1.28.1",
"@types/jest": "^27.0.3",
"@types/node": "^14.14.14",
"axios": "^0.24.0",
"babel-jest": "^27.3.1",
"dotenv": "^10.0.0",
"faker": "^5.5.3",
"jest": "^27.3.1",
"ts-jest": "^27.0.7",
"ts-node": "^9.1.1",
"typescript": "^4.5.2"
},
"directories": {
"doc": "docs"
},
"repository": {
"type": "git",
"url": "git+https://github.com/supabase/supabase.git"
},
"keywords": [
"postgres",
"firebase",
"storage",
"functions",
"database",
"auth"
]
}

14
tests/README.md Normal file
View File

@@ -0,0 +1,14 @@
## Tests
These tests can be run with Docker.
### Steps
In the parent folder:
- `npm run docker:dev`
- `npm run test`
### Clean up
- `npm run docker:remove`

View File

@@ -0,0 +1,64 @@
import * as faker from 'faker'
import { createClient } from '@supabase/supabase-js'
const unauthorized = createClient(process.env.SUPABASE_URL, 'FAKE_KEY')
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const admin = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
test('Unauthorized', async () => {
const { error } = await unauthorized.from('profiles').select()
expect(error.message).toBe('Invalid authentication credentials')
})
test('Simple test', async () => {
const fakeOne = {
email: faker.internet.email().toLowerCase(),
password: faker.internet.password(),
username: faker.internet.userName(),
}
const fakeTwo = {
email: faker.internet.email().toLowerCase(),
password: faker.internet.password(),
username: faker.internet.userName(),
}
const first = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const second = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const { user: firstUser } = await first.auth.signUp({
email: fakeOne.email,
password: fakeOne.password,
})
const { user: secondUser } = await second.auth.signUp({
email: fakeTwo.email,
password: fakeTwo.password,
})
expect(firstUser.email).toEqual(fakeOne.email)
expect(secondUser.email).toEqual(fakeTwo.email)
const { data: firstProfile } = await first
.from('profiles')
.insert({
id: firstUser.id,
username: fakeOne.username,
})
.single()
expect(firstProfile.username).toMatch(fakeOne.username)
// Cannot insert the second user on the first client
const { error: secondProfile } = await first
.from('profiles')
.insert({
id: secondUser.id,
username: fakeTwo.username,
})
.single()
expect(secondProfile.message).toMatch(/new row violates row-level security policy for table/)
const { data: firstProfileList } = await first.from('profiles').select()
const { data: secondProfileList } = await second.from('profiles').select()
const { data: adminProfileList } = await admin.from('profiles').select()
expect(firstProfileList.length).toBe(1)
expect(secondProfileList.length).toBe(0)
expect(adminProfileList.length).toBeGreaterThanOrEqual(1)
})

5
tests/jest-env.js Normal file
View File

@@ -0,0 +1,5 @@
process.env.SUPABASE_URL = 'http://localhost:8000'
process.env.SUPABASE_KEY_ANON =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYyNzIwODU0MCwiZXhwIjoxOTc0MzYzNzQwfQ.zcaQfHd3VA7XgJmdGfmV86OLVJT9s2MTmSy-e69BpUY'
process.env.SUPABASE_KEY_ADMIN =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjI3MjA4NTQwLCJleHAiOjE5NzQzNjM3NDB9.pkT3PNpO4DtO45Ac5HK_TKCx8sGLgNtV__pr_ZrRSAU'

5
tsconfig.json Normal file
View File

@@ -0,0 +1,5 @@
{
"compilerOptions": {
"skipLibCheck": true
}
}

View File

@@ -4,7 +4,6 @@ title: Phone Auth with MessageBird
description: How to set up and use Mobile OTP with MessageBird and Supabase.
---
import ExtensionsComponent from '../../../../web/src/components/Extensions'
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'

View File

@@ -4,7 +4,6 @@ title: Phone Auth with Twilio
description: How to set up and use Mobile OTP with Twilio and Supabase.
---
import ExtensionsComponent from '../../../../web/src/components/Extensions'
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'

View File

@@ -4,7 +4,7 @@ title: Database
description: Use Supabase to manage your data.
---
import ExtensionsComponent from '../../src/components/Extensions'
import ExtensionsComponent from '@site/src/components/Extensions'
Supabase is built on top of [Postgres](/docs/postgres/server/about), an extremely scalable Relational Database.

View File

@@ -134,7 +134,7 @@ Your first array data!
To query an array, PostgreSQL uses 1-based arrays, so be careful, since you're probably used to 0-based arrays in Javascript.
<Tabs
defaultValue="UI"
defaultValue="SQL"
values={[
{label: 'SQL', value: 'SQL'},
{label: 'JS', value: 'JavaScript'},

View File

@@ -4,7 +4,7 @@ title: Overview
description: Using Postgres extensions.
---
import ExtensionsComponent from '../../../src/components/Extensions'
import ExtensionsComponent from '@site/src/components/Extensions'
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

View File

@@ -4,7 +4,6 @@ title: "http: RESTful Client"
description: An HTTP Client for PostgreSQL Functions.
---
import ExtensionsComponent from '../../../../src/components/Extensions'
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

View File

@@ -4,7 +4,6 @@ title: "pgTAP: Unit Testing"
description: Unit testing in PostgreSQL.
---
import ExtensionsComponent from '../../../../src/components/Extensions'
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

View File

@@ -4,7 +4,6 @@ title: "plv8: Javascript Language"
description: Javascript language for PostgreSQL.
---
import ExtensionsComponent from '../../../../src/components/Extensions'
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

View File

@@ -4,7 +4,6 @@ title: "uuid-ossp: Unique Identifiers"
description: A UUID generator for PostgreSQL.
---
import ExtensionsComponent from '../../../../src/components/Extensions'
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

View File

@@ -4,7 +4,6 @@ title: "Full Text Search"
description: How to use full text search in PostgreSQL.
---
import ExtensionsComponent from '../../../../web/src/components/Extensions'
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

View File

@@ -5,7 +5,6 @@ sidebar_label: Overview
description: Getting started with Self Hosting.
---
import ExtensionsComponent from '../../../src/components/Extensions'
import JwtGenerator from '@site/src/components/JwtGenerator'
There are several ways to use Supabase:

View File

@@ -4,7 +4,7 @@ title: Contributing
description: Want to help?
---
import Sponsors from '../../src/components/Sponsors'
import Sponsors from '@site/src/components/Sponsors'
## How to contribute

23287
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@
]
},
"devDependencies": {
"@types/node": "^14.14.14",
"@types/node": "^14.17.34",
"axios": "^0.21.1",
"babel-plugin-css-modules-transform": "^1.6.2",
"clsx": "^1.1.1",

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'
import extensions from '../data/extensions.json'
import extensions from '@site/src/data/extensions.json'
export default function Extensions() {
const [filter, setFilter] = useState('')

View File

@@ -1,5 +1,5 @@
import React from 'react'
import sponsors from '../data/sponsors.json'
import sponsors from '@site/src/data/sponsors.json'
export default function Sponsors() {

5
web/tsconfig.json Normal file
View File

@@ -0,0 +1,5 @@
{
"compilerOptions": {
"module": "CommonJS"
}
}