diff --git a/apps/docs/components/AppleSecretGenerator.tsx b/apps/docs/components/AppleSecretGenerator.tsx new file mode 100644 index 00000000000..7bfeed67dcc --- /dev/null +++ b/apps/docs/components/AppleSecretGenerator.tsx @@ -0,0 +1,193 @@ +import { useState } from 'react' +import { Input, Button } from 'ui' +import Admonition from '~/components/Admonition' + +function base64URL(value: string) { + return globalThis.btoa(value).replace(/[=]/g, '').replace(/[+]/g, '-').replace(/[\/]/g, '_') +} + +/* +Convert a string into an ArrayBuffer +from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String +*/ +function stringToArrayBuffer(value: string) { + const buf = new ArrayBuffer(value.length) + const bufView = new Uint8Array(buf) + for (let i = 0; i < value.length; i++) { + bufView[i] = value.charCodeAt(i) + } + return buf +} + +function arrayBufferToString(buf) { + return String.fromCharCode.apply(null, new Uint8Array(buf)) +} + +const generateAppleSecretKey = async ( + kid: string, + iss: string, + sub: string, + file: File +): Promise<{ kid: string; jwt: string; exp: number }> => { + if (!kid) { + const match = file.name.match(/AuthKey_([^.]+)[.].*$/i) + if (match && match[1]) { + kid = match[1] + } + } + + if (!kid) { + throw new Error( + `No Key ID provided. The file "${file.name}" does not follow the AuthKey_XXXXXXXXXX.p8 pattern. Please provide a Key ID manually.` + ) + } + + const contents = await file.text() + + if (!contents.match(/^\s*-+BEGIN PRIVATE KEY-+[^-]+-+END PRIVATE KEY-+\s*$/i)) { + throw new Error(`Chosen file does not appear to be a PEM encoded PKCS8 private key file.`) + } + + // remove PEM headers and spaces + const pkcs8 = stringToArrayBuffer( + globalThis.atob(contents.replace(/-+[^-]+-+/g, '').replace(/\s+/g, '')) + ) + + const privateKey = await globalThis.crypto.subtle.importKey( + 'pkcs8', + pkcs8, + { + name: 'ECDSA', + namedCurve: 'P-256', + }, + true, + ['sign'] + ) + + const iat = Math.floor(Date.now() / 1000) + const exp = iat + 180 * 24 * 60 * 60 + + const jwt = [ + base64URL(JSON.stringify({ typ: 'JWT', kid, alg: 'ES256' })), + base64URL( + JSON.stringify({ + iss, + sub, + iat, + exp, + aud: 'https://appleid.apple.com', + }) + ), + ] + + const signature = await globalThis.crypto.subtle.sign( + { + name: 'ECDSA', + hash: 'SHA-256', + }, + privateKey, + stringToArrayBuffer(jwt.join('.')) + ) + + jwt.push(base64URL(arrayBufferToString(signature))) + + return { kid, jwt: jwt.join('.'), exp } +} + +const AppleSecretGenerator = () => { + const [file, setFile] = useState({ file: null as File | null }) + const [teamID, setTeamID] = useState('') + const [serviceID, setServiceID] = useState('') + const [keyID, setKeyID] = useState('') + const [secretKey, setSecretKey] = useState('') + const [expiresAt, setExpiresAt] = useState('') + const [error, setError] = useState('') + + return ( + <> + setTeamID(e.target.value.trim())} + /> + setServiceID(e.target.value.trim())} + /> + setKeyID(e.target.value.trim())} + /> +
+ { + setFile({ file: e.target.files[0] }) + }} + /> +
+
+ + + + {error && {error}} + + {secretKey && ( + <> +
+ + + )} + + ) +} + +export default AppleSecretGenerator diff --git a/apps/docs/components/MDX/database_setup.mdx b/apps/docs/components/MDX/database_setup.mdx new file mode 100644 index 00000000000..2a6e853f9c2 --- /dev/null +++ b/apps/docs/components/MDX/database_setup.mdx @@ -0,0 +1,18 @@ +import { Tabs } from 'ui' +export const TabPanel = Tabs.Panel + +## Project setup + +Let's create a new Postgres database. This is as simple as starting a new Project in Supabase: + +1. [Create a new project](https://database.new/) in the Supabase dashboard. +1. Enter your project details. Remember to store your password somewhere safe. + +Your database will be available in less than a minute. + +**Finding your credentials:** + +You can find your project credentials inside the project [settings](https://app.supabase.com/project/_/settings/), including: + +- [Database credentials](https://app.supabase.com/project/_/settings/database): connection strings and connection pooler details. +- [API credentials](https://app.supabase.com/project/_/settings/database): your serverless API URL and `anon` / `service_role` keys. diff --git a/apps/docs/components/Navigation/NavigationMenu/HomeMenuIcons.tsx b/apps/docs/components/Navigation/NavigationMenu/HomeMenuIcons.tsx index 5449df60828..afa9088498b 100644 --- a/apps/docs/components/Navigation/NavigationMenu/HomeMenuIcons.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/HomeMenuIcons.tsx @@ -373,10 +373,11 @@ export function IconMenuAI({ width = 16, height = 16 }: HomeMenuIcon) { xmlns="http://www.w3.org/2000/svg" > ) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 665897064c0..7720cbbe8b8 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -508,14 +508,33 @@ export const database: NavMenuConstant = { title: 'Database', url: '/guides/database', items: [ - { name: 'Database Connections', url: '/guides/database/connecting-to-postgres' }, - { name: 'Tables and Data', url: '/guides/database/tables' }, - { name: 'Database Functions', url: '/guides/database/functions' }, - { name: 'Database Webhooks', url: '/guides/database/webhooks' }, - { name: 'Full Text Search', url: '/guides/database/full-text-search' }, - { name: 'Database Testing', url: '/guides/database/testing' }, - { name: 'Managing Secrets with Vault', url: '/guides/database/vault' }, - { name: 'Column Encryption', url: '/guides/database/column-encryption' }, + { name: 'Overview', url: '/guides/database' }, + { + name: 'Fundamentals', + url: undefined, + items: [ + { name: 'Connecting to your database', url: '/guides/database/connecting-to-postgres' }, + { name: 'Managing tables, views, and data', url: '/guides/database/tables' }, + { name: 'Managing database functions', url: '/guides/database/functions' }, + { name: 'Managing indexes', url: '/guides/database/postgres/indexes' }, + { name: 'Managing database webhooks', url: '/guides/database/webhooks' }, + { name: 'Managing database replication', url: '/guides/database/replication' }, + { name: 'Managing secrets with Vault', url: '/guides/database/vault' }, + ], + }, + { + name: 'Postgres Guides', + url: undefined, + items: [ + { name: 'Implementing Full Text Search', url: '/guides/database/full-text-search' }, + { name: 'Implementing Cascade Deletes', url: '/guides/database/postgres/cascade-deletes' }, + { name: 'Implementing column encryption', url: '/guides/database/column-encryption' }, + { name: 'Testing your database', url: '/guides/database/testing' }, + { name: 'Managing Timeouts', url: '/guides/database/timeouts' }, + { name: 'Managing Passwords', url: '/guides/database/managing-passwords' }, + { name: 'Configuring Timezones', url: '/guides/database/managing-timezones' }, + ], + }, { name: 'Extensions', url: undefined, @@ -622,17 +641,9 @@ export const database: NavMenuConstant = { ], }, { - name: 'Postgres resources', + name: 'Examples', url: undefined, items: [ - { - name: 'Managing Indexes', - url: '/guides/database/postgres/indexes', - }, - { - name: 'Cascade Deletes', - url: '/guides/database/postgres/cascade-deletes', - }, { name: 'Drop All Tables in Schema', url: '/guides/database/postgres/dropping-all-tables-in-schema', @@ -647,16 +658,6 @@ export const database: NavMenuConstant = { }, ], }, - { - name: 'Configuration', - url: undefined, - items: [ - { name: 'Timeouts', url: '/guides/database/timeouts' }, - { name: 'Replication', url: '/guides/database/replication' }, - { name: 'Passwords', url: '/guides/database/managing-passwords' }, - { name: 'Timezones', url: '/guides/database/managing-timezones' }, - ], - }, ], } @@ -726,7 +727,7 @@ export const functions: NavMenuConstant = { url: undefined, items: [ { name: 'Developing Functions locally', url: '/guides/functions/local-development' }, - { name: 'Deploying with Git', url: '/guides/functions/cicd-workflow' }, + { name: 'Deploying with GitHub', url: '/guides/functions/cicd-workflow' }, { name: 'Managing Secrets and Environment Variables', url: '/guides/functions/secrets' }, { name: 'Integrating With Supabase Auth', url: '/guides/functions/auth' }, { @@ -862,13 +863,18 @@ export const ai: NavMenuConstant = { { name: 'Overview', url: '/guides/ai' }, { name: 'Concepts', url: '/guides/ai/concepts' }, { - name: 'Structured & unstructured embeddings', - url: '/guides/ai/structured-unstructured-embeddings', + name: 'Structured & unstructured', + url: '/guides/ai/structured-unstructured', }, { name: 'Quickstarts', url: undefined, - items: [{ name: 'Python client', url: '/guides/ai/vecs-python-client' }], + items: [ + { name: 'Developing locally with Vecs', url: '/guides/ai/vecs-python-client' }, + { name: 'Creating and managing collections', url: '/guides/ai/quickstarts/hello-world' }, + { name: 'Text Deduplication', url: '/guides/ai/quickstarts/text-deduplication' }, + { name: 'Face similarity search', url: '/guides/ai/quickstarts/face-similarity' }, + ], }, { name: 'Guides', diff --git a/apps/docs/components/index.tsx b/apps/docs/components/index.tsx index 831b1fa4027..92b92392a38 100644 --- a/apps/docs/components/index.tsx +++ b/apps/docs/components/index.tsx @@ -16,6 +16,7 @@ import FunctionsExamples from './FunctionsExamples' import { Mermaid } from 'mdx-mermaid/lib/Mermaid' import RefSubLayout from '~/layouts/ref/RefSubLayout' import { Heading } from './CustomHTMLElements' +import DatabaseSetup from './MDX/database_setup.mdx' import ProjectSetup from './MDX/project_setup.mdx' import QuickstartIntro from './MDX/quickstart_intro.mdx' import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx' @@ -66,6 +67,7 @@ const components = { FunctionsExamples, JwtGenerator, QuickstartIntro, + DatabaseSetup, ProjectSetup, SocialProviderSetup, SocialProviderSettingsSupabase, diff --git a/apps/docs/pages/guides/ai/concepts.mdx b/apps/docs/pages/guides/ai/concepts.mdx index 7a62aa7e8cf..c35a6e72aed 100644 --- a/apps/docs/pages/guides/ai/concepts.mdx +++ b/apps/docs/pages/guides/ai/concepts.mdx @@ -53,14 +53,14 @@ Compared to our 2-dimensional example above, most embedding models will output m Why is this useful? Once we have generated embeddings on multiple texts, it is trivial to calculate how similar they are using vector math operations like cosine distance. A common use case for this is search. Your process might look something like this: 1. Pre-process your knowledge base and generate embeddings for each page -2. Store your embeddings to be referenced later (more on this) +2. Store your embeddings to be referenced later 3. Build a search page that prompts your user for input 4. Take user's input, generate a one-time embedding, then perform a similarity search against your pre-processed embeddings. 5. Return the most similar pages to the user ## See also -- [Structured and Unstructured embeddings](/docs/guides/ai/structured-unstructured-embeddings) +- [Structured and Unstructured embeddings](/docs/guides/ai/structured-unstructured) export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/ai/engineering-for-scale.mdx b/apps/docs/pages/guides/ai/engineering-for-scale.mdx index a74bfe39d50..663550fc4a8 100644 --- a/apps/docs/pages/guides/ai/engineering-for-scale.mdx +++ b/apps/docs/pages/guides/ai/engineering-for-scale.mdx @@ -16,7 +16,18 @@ For small workloads it's typical to store your data in a single database. If you've used [Vecs](/docs/guides/ai/vecs-python-client) to create 3 different collections, you can expose collections to your web or mobile application using [views](/docs/guides/database/tables#views): -![Single Database](/docs/img/ai/scaling/single-database.png) +
+ single database + single database +
For example, with 3 collections, called `docs`, `posts`, and `images`, we could expose the "docs" inside the public schema like this: @@ -44,7 +55,18 @@ const { data, error } = await supabase As you move into production, we recommend running splitting your collections into separate projects. This is because it allows your vector stores to scale independently of your production data. Vectors typically grow faster than operational data, and they have different resource requirements. Running them on separate databases removes the single-point-of-failure. -![With secondaries](/docs/img/ai/scaling/with-secondaries.png) +
+ With secondaries + With secondaries +
You can use as many secondary databases as you need to manage your collections. With this architecture, you have 2 options for accessing collections within your application: @@ -120,7 +142,18 @@ const { data, error } = await supabase This diagram provides an example architecture, allowing you to access the collections either with our client libraries or using Vecs. You can add as many secondary databases as you need, in this example we show one only: -![Multi Database](/docs/img/ai/scaling/multi-database.png) +
+ multi database + multi database +
export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/ai/google-colab.mdx b/apps/docs/pages/guides/ai/google-colab.mdx index a3438246727..c3e02029842 100644 --- a/apps/docs/pages/guides/ai/google-colab.mdx +++ b/apps/docs/pages/guides/ai/google-colab.mdx @@ -8,7 +8,14 @@ export const meta = { sidebar_label: 'Google Colab', } -Google Colab is a hosted Jupyter Notebook service. It provides free access to computing resources, including GPUs and TPUs, and is well-suited to machine learning, data science, and education. We can use Colab to manage collections using [Supabase Vecs](/docs/ai/vecs-python-client). + + + + +Google Colab is a hosted Jupyter Notebook service. It provides free access to computing resources, including GPUs and TPUs, and is well-suited to machine learning, data science, and education. We can use Colab to manage collections using [Supabase Vecs](/docs/guides/ai/vecs-python-client). In this tutorial we'll connect to a database running on the Supabase [platform](https://app.supabase.com/). If you don't already have a database, you can create one here: [database.new](https://database.new). diff --git a/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx b/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx new file mode 100644 index 00000000000..e32dc1a7f21 --- /dev/null +++ b/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx @@ -0,0 +1,63 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' + +export const meta = { + id: 'ai-vecs-python-client', + title: 'Face similarity search', + subtitle: 'Identify the celebrities you looks most similar to using Supabase Vecs.', + breadcrumb: 'AI Quickstarts', +} + +This guide will walk you through a ["Face Similarity Search"](https://github.com/supabase/supabase/blob/master/examples/ai/face_similarity.ipynb) example using Colab and Supabase Vecs. You'll identify the celebrities you (or any other person) looks most similar to. You will: + +1. Launch a Postgres database that uses pgvector to store embeddings +1. Launch a notebook that connects to your database +1. Load the "`ashraq/tmdb-people-image`" celebrity dataset +1. Use the `face_recognition` model to create an embedding for every celebrity photo. +1. Search for similar faces inside the dataset. + + + +## Launching a notebook + +Launch our [`semantic_text_deduplication`](https://github.com/supabase/supabase/blob/master/examples/ai/face_similarity.ipynb) notebook in Colab: + + + + + +At the top of the notebook, you'll see a button `Copy to Drive`. Click this button to copy the notebook to your Google Drive. + +## Connecting to your database + +Inside the Notebook, find the cell which specifies the `DB_CONNECTION`. It will contain some code like this: + +```python +import vecs + +DB_CONNECTION = "postgresql://:@:/" + +# create vector store client +vx = vecs.create_client(DB_CONNECTION) +``` + +Replace the `DB_CONNECTION` with your own connection string for your database, which you set up in first step of this guide. + +## Stepping through the notebook + +Now all that's left is to step through the notebook. You can do this by clicking the "execute" button (`ctrl+enter`) at the top left of each code cell. The notebook guides you through the process of creating a collection, adding data to it, and querying it. + +You can view the inserted items in the [Table Editor](https://app.supabase.com/project/_/editor/), by selecting the `vecs` schema from the schema dropdown. + +![Colab documents](/docs/img/ai/google-colab/colab-documents.png) + +## Next steps + +You can now start building your own applications with Vecs. Check our [examples](/docs/guides/ai#examples) for ideas. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/ai/quickstarts/hello-world.mdx b/apps/docs/pages/guides/ai/quickstarts/hello-world.mdx new file mode 100644 index 00000000000..61c2d3fd448 --- /dev/null +++ b/apps/docs/pages/guides/ai/quickstarts/hello-world.mdx @@ -0,0 +1,63 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' + +export const meta = { + id: 'ai-vecs-python-client', + title: 'Creating and managing collections', + subtitle: 'Connecting to your database with Colab.', + breadcrumb: 'AI Quickstarts', +} + +This guide will walk you through a basic ["Hello World"](https://github.com/supabase/supabase/blob/master/examples/ai/vector_hello_world.ipynb) example using Colab and Supabase Vecs. You'll learn how to: + +1. Launch a Postgres database that uses pgvector to store embeddings +1. Launch a notebook that connects to your database +1. Create a vector collection +1. Add data to the collection +1. Query the collection + + + +## Launching a notebook + +Launch our [`vector_hello_world`](https://github.com/supabase/supabase/blob/master/examples/ai/vector_hello_world.ipynb) notebook in Colab: + + + + + +At the top of the notebook, you'll see a button `Copy to Drive`. Click this button to copy the notebook to your Google Drive. + +## Connecting to your database + +Inside the Notebook, find the cell which specifies the `DB_CONNECTION`. It will contain some code like this: + +```python +import vecs + +DB_CONNECTION = "postgresql://:@:/" + +# create vector store client +vx = vecs.create_client(DB_CONNECTION) +``` + +Replace the `DB_CONNECTION` with your own connection string for your database, which you set up in first step of this guide. + +## Stepping through the notebook + +Now all that's left is to step through the notebook. You can do this by clicking the "execute" button (`ctrl+enter`) at the top left of each code cell. The notebook guides you through the process of creating a collection, adding data to it, and querying it. + +You can view the inserted items in the [Table Editor](https://app.supabase.com/project/_/editor/), by selecting the `vecs` schema from the schema dropdown. + +![Colab documents](/docs/img/ai/google-colab/colab-documents.png) + +## Next steps + +You can now start building your own applications with Vecs. Check our [examples](/docs/guides/ai#examples) for ideas. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/ai/quickstarts/text-deduplication.mdx b/apps/docs/pages/guides/ai/quickstarts/text-deduplication.mdx new file mode 100644 index 00000000000..1a417bc6719 --- /dev/null +++ b/apps/docs/pages/guides/ai/quickstarts/text-deduplication.mdx @@ -0,0 +1,63 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' + +export const meta = { + id: 'ai-vecs-python-client', + title: 'Semantic Text Deduplication', + subtitle: 'Finding duplicate movie reviews with Supabase Vecs.', + breadcrumb: 'AI Quickstarts', +} + +This guide will walk you through a ["Semantic Text Deduplication"](https://github.com/supabase/supabase/blob/master/examples/ai/semantic_text_deduplication.ipynb) example using Colab and Supabase Vecs. You'll learn how to find similar movie reviews using embeddings, and remove any that seem like duplicates. You will: + +1. Launch a Postgres database that uses pgvector to store embeddings +1. Launch a notebook that connects to your database +1. Load the IMDB dataset +1. Use the `sentence-transformers/all-MiniLM-L6-v2` model to create an embedding representing the semantic meaning of each review. +1. Search for all duplicates. + + + +## Launching a notebook + +Launch our [`semantic_text_deduplication`](https://github.com/supabase/supabase/blob/master/examples/ai/semantic_text_deduplication.ipynb) notebook in Colab: + + + + + +At the top of the notebook, you'll see a button `Copy to Drive`. Click this button to copy the notebook to your Google Drive. + +## Connecting to your database + +Inside the Notebook, find the cell which specifies the `DB_CONNECTION`. It will contain some code like this: + +```python +import vecs + +DB_CONNECTION = "postgresql://:@:/" + +# create vector store client +vx = vecs.create_client(DB_CONNECTION) +``` + +Replace the `DB_CONNECTION` with your own connection string for your database, which you set up in first step of this guide. + +## Stepping through the notebook + +Now all that's left is to step through the notebook. You can do this by clicking the "execute" button (`ctrl+enter`) at the top left of each code cell. The notebook guides you through the process of creating a collection, adding data to it, and querying it. + +You can view the inserted items in the [Table Editor](https://app.supabase.com/project/_/editor/), by selecting the `vecs` schema from the schema dropdown. + +![Colab documents](/docs/img/ai/google-colab/colab-documents.png) + +## Next steps + +You can now start building your own applications with Vecs. Check our [examples](/docs/guides/ai#examples) for ideas. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/ai/structured-unstructured-embeddings.mdx b/apps/docs/pages/guides/ai/structured-unstructured-embeddings.mdx deleted file mode 100644 index e1a18bc4126..00000000000 --- a/apps/docs/pages/guides/ai/structured-unstructured-embeddings.mdx +++ /dev/null @@ -1,61 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'structured-unstructured-embeddings', - title: 'Structured and unstructured embeddings', - description: - 'Supabase is flexible enough to provide structured and unstructured embeddings using pgvector.', - subtitle: - 'Supabase is flexible enough to provide structured and unstructured embeddings using pgvector.', - sidebar_label: 'Structured and unstructured embeddings', -} - -Most vector stores treat embeddings like NoSQL, unstructured data. Supabase is flexible enough to fit either a structured or an unstructured approach. - -Compare these code snippets: - -## Structured - -```sql -create table docs ( - id uuid primary key, - content text, - url string, - embedding vector(1536) -); - -insert into docs - (id, content, url, embedding) -values - ('79409372-7556-4ccc-ab8f-5786a6cfa4f7', 'Hello world', '/hello-world', [100, 200, 300]); -``` - -A structured approach is usually defined in SQL, and managed via database [migrations](/docs/guides/getting-started/local-development#database-migrations). - -## Unstructured - -```py -import vecs - -docs = vx.create_collection(name="docs", dimension=1536) - -docs.upsert(vectors=[ - ('79409372-7556-4ccc-ab8f-5786a6cfa4f7', [100, 200, 300], { url: '/hello-world' }) -]) - -``` - -An unstructured approach is usually defined in Python and has a looser table definition, storing metadata as a json document along with the embedding. - -## Choosing the right model - -Both approaches create a table where you can store your embeddings and some metadata. You should choose the best approach for your use-case. - -- Structured embeddings are typically co-located with some content that is already stored in your database. -- Unstructured embeddings are typically defined at runtime, better-suited for a large body of external content. - -Both approaches are fine, and the one you should choose depends on your use-case. - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/ai/structured-unstructured.mdx b/apps/docs/pages/guides/ai/structured-unstructured.mdx new file mode 100644 index 00000000000..0c392c20e09 --- /dev/null +++ b/apps/docs/pages/guides/ai/structured-unstructured.mdx @@ -0,0 +1,114 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'structured-unstructured-embeddings', + title: 'Structured and Unstructured', + description: + 'Supabase is flexible enough to associate structured and unstructured metadata with embeddings.', + subtitle: + 'Supabase is flexible enough to associate structured and unstructured metadata with embeddings.', + sidebar_label: 'Structured and unstructured embeddings', +} + +Most vector stores treat metadata associated with embeddings like NoSQL, unstructured data. Supabase is flexible enough to store unstructured and structured metadata. + +## Structured + +```sql +create table docs ( + id uuid primary key, + embedding vector(3), + content text, + url string +); + +insert into docs + (id, content, url, embedding) +values + ('79409372-7556-4ccc-ab8f-5786a6cfa4f7', array[0.1, 0.2, 0.3], 'Hello world', '/hello-world'); +``` + +Notice that we've associated two pieces of metadata, `content` and `url`, with the embedding. Those fields can be filtered, constrained, indexed, and generally operated on using the full power of SQL. Structured metadata fits naturally with a traditional Supabase application, and can be managed via database [migrations](/docs/guides/getting-started/local-development#database-migrations). + +## Unstructured + +```sql +create table docs ( + id uuid primary key, + embedding vector(3), + meta jsonb +); + +insert into docs + (id, embedding, meta) +values + ( + '79409372-7556-4ccc-ab8f-5786a6cfa4f7', + array[0.1, 0.2, 0.3], + '{"content": "Hello world", "url": "/hello-world"}' + ); +``` + +An unstructured approach does not specify the metadata fields that are expected. It stores all metadata in a flexible `json`/`jsonb` column. The tradeoff is that the querying/filtering capabilities of a schemaless data type are less flexible than when each field has a dedicated column. It also pushes the burden of metadata data integrity onto application code, which is more error prone than enforcing constraints in the database. + +The unstructured approach is recommended: + +- for ephemeral/interactive workloads e.g. data science or scientific research +- when metadata fields are user-defined or unknown +- during rapid prototyping + +Client libraries like python's [vecs](https://github.com/supabase/vecs) use this structure. For example, running: + +```py +#!/usr/bin/env python3 +import vecs + +docs = vx.create_collection(name="docs", dimension=1536) + +docs.upsert(vectors=[ + ('79409372-7556-4ccc-ab8f-5786a6cfa4f7', [100, 200, 300], { url: '/hello-world' }) +]) + +``` + +automatically creates the unstructured SQL table during the call to `create_collection`. + +Note that when working with client libraries that emit SQL DDL, like `create table ...`, you should add that SQL to your migrations when moving to production to maintain a single source of truth for your database's schema. + +## Hybrid + +The structured metadata style is recommended when the fields being tracked are known in advance. If you have a combination of known and unknown metadata fields, you can accommodate the unknown fields by adding a `json`/`jsonb` column to the table. In that situation, known fields should continue to use dedicated columns for best query performance and throughput. + +```sql +create table docs ( + id uuid primary key, + embedding vector(3), + content text, + url string, + meta jsonb +); + +insert into docs + (id, embedding, meta) +values + ( + '79409372-7556-4ccc-ab8f-5786a6cfa4f7', + array[0.1, 0.2, 0.3], + 'Hello world', + '/hello-world', + '{"key": "value"}' + ); +``` + +## Choosing the right model + +Both approaches create a table where you can store your embeddings and some metadata. You should choose the best approach for your use-case. In summary: + +- Structured metadata is best when fields are known in advance or query patterns are predictable e.g. a production Supabase application +- Unstructured metadata is best when fields are unknown/user-defined or when working with data interactively e.g. exploratory research + +Both approaches are valid, and the one you should choose depends on your use-case. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/api.mdx b/apps/docs/pages/guides/api.mdx index 390dcadd33d..7ca9052585d 100644 --- a/apps/docs/pages/guides/api.mdx +++ b/apps/docs/pages/guides/api.mdx @@ -74,6 +74,17 @@ Supabase provides a Realtime API using [Realtime](https://github.com/supabase/re Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications. Go to your project's [Replication section](https://app.supabase.com/project/_/database/replication) to get started. +## API URL and Keys + +You can find the API URL and Keys in the [Dashboard](https://app.supabase.com/project/_/settings/api). + + + export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/auth/auth-email.mdx b/apps/docs/pages/guides/auth/auth-email.mdx index 0f833876253..ce29b1e970b 100644 --- a/apps/docs/pages/guides/auth/auth-email.mdx +++ b/apps/docs/pages/guides/auth/auth-email.mdx @@ -95,7 +95,7 @@ Future signOut() async { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Supabase Flutter Client](https://github.com/supabase/supabase-flutter) diff --git a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx index e90e43abc4e..9544beb30fe 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx @@ -79,6 +79,22 @@ const App = () => ( ) ``` +### Options + +Options are available via 'queryParams': + +``` +``` + ### Supported Views The Auth component is currently shipped with the following views: diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index f7ad292e39d..4ffafc921aa 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -521,6 +521,14 @@ export default function Home() { > check out [this repo](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs) for more examples, including [realtime subscriptions](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx). +#### Singleton + +The `createClientComponentClient` function implements a [Singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to simplify instantiating Supabase clients. If you need multiple Supabase instances across Client Components - for example, when using multiple schemas - you can pass an additional configuration option for `{ isSingleton: false }` to get a new client every time this function is called. + +```jsx +const supabase = createClientComponentClient({ isSingleton: false }) +``` + ### Server Component [Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components) allow for asynchronous data to be fetched server-side. @@ -736,7 +744,7 @@ With v0.7.x of the Next.js Auth Helpers a new naming convention has been impleme #### createClientComponentClient returns singleton -You no longer need to implement logic to ensure there is only a single instance of the Supabase Client shared across all Client Components - this is now handled by the `createClientComponentClient` function. Call it as many times as you want! +You no longer need to implement logic to ensure there is only a single instance of the Supabase Client shared across all Client Components - this is now the default and handled by the `createClientComponentClient` function. Call it as many times as you want! ```jsx "use client"; @@ -749,6 +757,8 @@ export default function() { } ``` +For an example of creating multiple Supabase clients, check [Singleton section](/docs/guides/auth/auth-helpers/nextjs#singleton) above. + export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/auth/auth-magic-link.mdx b/apps/docs/pages/guides/auth/auth-magic-link.mdx index e6a74aa8354..ab8e12e488a 100644 --- a/apps/docs/pages/guides/auth/auth-magic-link.mdx +++ b/apps/docs/pages/guides/auth/auth-magic-link.mdx @@ -88,7 +88,7 @@ Future signOut() async { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Supabase Flutter Client](https://github.com/supabase/supabase-flutter) diff --git a/apps/docs/pages/guides/auth/auth-password-reset.mdx b/apps/docs/pages/guides/auth/auth-password-reset.mdx index a6ecc280009..c4ecc97d2b7 100644 --- a/apps/docs/pages/guides/auth/auth-password-reset.mdx +++ b/apps/docs/pages/guides/auth/auth-password-reset.mdx @@ -65,7 +65,7 @@ We are using `next` as our query parameter, but this can name whatever you like. The email link you receive will behave like a magic link. When the link is clicked you will be sent to the `redirectTo` URL you specified that points to the path with the exchange code. ### Exchange authorization code -After redirecting to the server page, we need to retrieve the code from the query parameter called `code` and pass it to the `.exchangeAuthCodeForSession` function. +After redirecting to the server page, we need to retrieve the code from the query parameter called `code` and pass it to the `.exchangeCodeForSession` function. ```ts // api/auth/callback.ts diff --git a/apps/docs/pages/guides/auth/social-login/auth-apple.mdx b/apps/docs/pages/guides/auth/social-login/auth-apple.mdx index a41cc292f61..8f72a47a5d2 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-apple.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-apple.mdx @@ -1,4 +1,5 @@ import Layout from '~/layouts/DefaultGuideLayout' +import AppleSecretGenerator from '~/components/AppleSecretGenerator' export const meta = { id: 'auth-apple', @@ -89,69 +90,15 @@ Now you'll need to download a `secret key` file from Apple that will be used to - Save the downloaded file -- this contains your "secret key" that will be used to generate your `client_secret`. - Click `Done` at the top right. -## Generate a `client_secret` +## Generate a client secret -The `secret key` you downloaded is used to create the `client_secret` string you'll need to authenticate your users. +You need to configure a client secret when using Sign in with Apple for Web. This is a specially crafted [JWT signed with a secret key downloaded from Apple's Developer Center](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens). -According to the [Apple Docs](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens) it needs to be a JWT -token encrypted using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm. + + Use this tool to generate a new Apple client secret. No keys leave your browser! + -At this time, the easiest way to generate this JWT token is with [Ruby](https://www.ruby-lang.org/en/). -If you don't have Ruby installed, you can [Download Ruby Here](https://www.ruby-lang.org/en/downloads). - -- Install Ruby (or check to make sure it's installed on your system). -- Install [ruby-jwt](https://github.com/jwt/ruby-jwt). -- From the command line, run: `sudo gem install jwt`. - -Create the script below using a text editor: `secret_gen.rb` - -```ruby -require "jwt" - -key_file = "Path to the private key" -team_id = "Your Team ID" -client_id = "The Service ID of the service you created" -key_id = "The Key ID of the private key" - -validity_period = 180 # In days. Max 180 (6 months) according to Apple docs. - -private_key = OpenSSL::PKey::EC.new IO.read key_file - -token = JWT.encode( - { - iss: team_id, - iat: Time.now.to_i, - exp: Time.now.to_i + 86400 * validity_period, - aud: "https://appleid.apple.com", - sub: client_id - }, - private_key, - "ES256", - header_fields= - { - kid: key_id - } -) -puts token -``` - -1. Edit the `secret_gen.rb` file: - -- `key_file` = "Path to the private key you downloaded from Apple". It should look like this: `AuthKey_XXXXXXXXXX.p8`. -- `team_id` = "Your Team ID". This is found at the Apple Developer website, under Membership details. This is a 10-character alphanumeric string called "Team ID". Alternatively, this can be seen next to your name in the upper right when viewing your Certificates, Identifiers & Profiles. -- `client_id` = "The Service ID of the service you created". This is the `Services ID` you created in the above step `Obtain a Services ID`. If you've lost this ID, you can find it in the Apple Developer Site: - - Go to `Certificates, Identifiers & Profiles`. - - Click `Identifiers` at the left. - - At the top right drop-down, select `Services IDs`. - - Find your Identifier in the list (i.e. app.com.acme.roadrunner). -- `key_id` = "The Key ID of the private key". This can be found in the name of your downloaded secret file (For a file named `AuthKey_XXXXXXXXXX.p8` your key_id is `XXXXXXXXXX`). If you've lost this ID, you can find it in the Apple Developer Site: - - Go to `Certificates, Identifiers & Profiles`. - - Click `Keys` at the left. - - Click on your newly-created key in the list. - - Look under `Key ID` to find your key_id. - -2. From the command line, run: `ruby secret_gen.rb > client_secret.txt`. -3. Your `client_secret` is now stored in this `client_secret.txt` file. + ## Add your OAuth credentials to Supabase @@ -180,8 +127,6 @@ async function signout() { ## Resources - [Apple Developer Account](https://developer.apple.com). -- [Ruby](https://www.ruby-lang.org/en/) Docs. -- [ruby-jwt](https://github.com/jwt/ruby-jwt) library. - Thanks to [Janak Amarasena](https://medium.com/@janakda) who did all the heavy lifting in [How to configure Sign In with Apple](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003). export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/auth/social-login/auth-bitbucket.mdx b/apps/docs/pages/guides/auth/social-login/auth-bitbucket.mdx index 2347e4e0985..57ae235d2bb 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-bitbucket.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-bitbucket.mdx @@ -68,7 +68,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Bitbucket Account](https://bitbucket.org) diff --git a/apps/docs/pages/guides/auth/social-login/auth-discord.mdx b/apps/docs/pages/guides/auth/social-login/auth-discord.mdx index 4d8185d74e3..061f93b43e2 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-discord.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-discord.mdx @@ -69,7 +69,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Discord Account](https://discord.com) - [Discord Developer Portal](https://discord.com/developers) diff --git a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx index 72f6c46df46..f5c7deac8df 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx @@ -83,7 +83,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Facebook Developers Dashboard](https://developers.facebook.com/) diff --git a/apps/docs/pages/guides/auth/social-login/auth-github.mdx b/apps/docs/pages/guides/auth/social-login/auth-github.mdx index 31c5cf47b2a..5ba9d43c440 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-github.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-github.mdx @@ -79,7 +79,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [GitHub Developer Settings](https://github.com/settings/developers) diff --git a/apps/docs/pages/guides/auth/social-login/auth-gitlab.mdx b/apps/docs/pages/guides/auth/social-login/auth-gitlab.mdx index 2e4e234c21c..8934a57b5db 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-gitlab.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-gitlab.mdx @@ -65,7 +65,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [GitLab Account](https://gitlab.com) diff --git a/apps/docs/pages/guides/auth/social-login/auth-google.mdx b/apps/docs/pages/guides/auth/social-login/auth-google.mdx index f4afe453b52..4365bec4b5b 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-google.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-google.mdx @@ -105,6 +105,7 @@ async function signInWithGoogle() { queryParams: { access_type: 'offline', prompt: 'consent', + hd: 'domain.com //google will also allowo OAuth logins to be restricted to a specified domain using the 'hd' parameter }, }, }) @@ -113,7 +114,7 @@ async function signInWithGoogle() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Google Cloud Platform Console](https://console.cloud.google.com/home/dashboard) diff --git a/apps/docs/pages/guides/auth/social-login/auth-linkedin.mdx b/apps/docs/pages/guides/auth/social-login/auth-linkedin.mdx index a8fe5099b96..30dc5a41d73 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-linkedin.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-linkedin.mdx @@ -64,7 +64,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [LinkedIn Developer Dashboard](https://api.LinkedIn.com/apps) diff --git a/apps/docs/pages/guides/auth/social-login/auth-notion.mdx b/apps/docs/pages/guides/auth/social-login/auth-notion.mdx index ec50ee2cbe7..fcdecce78ee 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-notion.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-notion.mdx @@ -68,7 +68,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Notion Account](https://notion.so) - [Notion Developer Portal](https://www.notion.so/my-integrations) diff --git a/apps/docs/pages/guides/auth/social-login/auth-slack.mdx b/apps/docs/pages/guides/auth/social-login/auth-slack.mdx index 38b3cf46b64..78c4429aea1 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-slack.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-slack.mdx @@ -80,7 +80,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Slack Developer Dashboard](https://api.slack.com/apps) diff --git a/apps/docs/pages/guides/auth/social-login/auth-spotify.mdx b/apps/docs/pages/guides/auth/social-login/auth-spotify.mdx index 8358ece55dc..ac6099c8c1b 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-spotify.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-spotify.mdx @@ -72,7 +72,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/) diff --git a/apps/docs/pages/guides/auth/social-login/auth-twitch.mdx b/apps/docs/pages/guides/auth/social-login/auth-twitch.mdx index da36be88d84..9b6b30acf26 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-twitch.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-twitch.mdx @@ -83,7 +83,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Twitch Account](https://twitch.tv) - [Twitch Developer Console](https://dev.twitch.tv/console) diff --git a/apps/docs/pages/guides/auth/social-login/auth-twitter.mdx b/apps/docs/pages/guides/auth/social-login/auth-twitter.mdx index fbf98ba1b1c..a2ce6204ec2 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-twitter.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-twitter.mdx @@ -73,7 +73,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Twitter Developer Dashboard](https://developer.twitter.com/en/portal/dashboard) diff --git a/apps/docs/pages/guides/auth/social-login/auth-zoom.mdx b/apps/docs/pages/guides/auth/social-login/auth-zoom.mdx index 8027ec80661..fe4c9bec315 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-zoom.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-zoom.mdx @@ -81,7 +81,7 @@ async function signout() { ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Zoom App Marketplace](https://marketplace.zoom.us/) diff --git a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx index 1e922686c78..f5752957ead 100644 --- a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx +++ b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx @@ -25,7 +25,7 @@ You can use the `supabase sso` [subcommands](/docs/reference/cli/supabase-sso) t SAML 2.0 support is disabled by default on Supabase projects. You can configure this on the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page on your project. -Please note that SAML 2.0 support is offered on tiers Pro and above. Check the [Pricing](https://supabase.com/pricing) page for more information. +Please note that SAML 2.0 support is offered on plans Pro and above. Check the [Pricing](https://supabase.com/pricing) page for more information. ## Terminology diff --git a/apps/docs/pages/guides/database/arrays.mdx b/apps/docs/pages/guides/database/arrays.mdx index 45f258195e4..5bef947cfa9 100644 --- a/apps/docs/pages/guides/database/arrays.mdx +++ b/apps/docs/pages/guides/database/arrays.mdx @@ -160,7 +160,7 @@ returns: ## Resources - [Supabase JS Client](https://github.com/supabase/supabase-js) -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [PostgreSQL Arrays](https://www.postgresql.org/docs/15/arrays.html) export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/database/column-encryption.mdx b/apps/docs/pages/guides/database/column-encryption.mdx index 80ed59f1705..262aee6762b 100644 --- a/apps/docs/pages/guides/database/column-encryption.mdx +++ b/apps/docs/pages/guides/database/column-encryption.mdx @@ -3,20 +3,17 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'column-encryption', title: 'Column Encryption', - description: 'Use Supabase to store and serve files.', + description: 'Encrypt columns using Transparent Column Encryption.', + subtitle: 'Encrypt columns using Transparent Column Encryption.', sidebar_label: 'Overview', video: 'https://www.youtube.com/v/J9mTPY8rIXE', } -Encrypted Columns for Tables +Supabase provides a secure method for encrypting columns using [Vault](/docs/guides/database/vault), our Postgres secrets manager. Vault is Postgres extension with an integrated UI intended to act as a secure global secrets management for you project. -## Transparent Column Encryption (TCE) +Vault enables an advanced feature called Transparent Column Encryption (TCE) which provides a safe way to encrypt your data so that it doesn't leak into logs and backups. It can also provide row-level authenticated encryption. -TCE provides a safe way to encrypt your data so that it doesn't leak into logs and backups. It can also provide row-level authenticated encryption. - -TCE is the primary building block of [Vault](/docs/guides/database/vault), Supabase's Postgres secrets manager. Vault is a built-in table with an integrated UI intended to act as a secure global secrets management for you project. However if you need more fine-grain control over your encrypted data, such as encrypting columns in your own tables, you can use TCE directly. Any Postgres value that can be cast to `text` or `bytea` can be encrypted using TCE. - -### Encrypting columns +## Encrypting columns When creating a new column in the Dashboard, you can choose to encrypt a `text` or `bytea` column. You will choose which key you would like to encrypt it with by selecting an existing key ID or creating a new one. @@ -26,10 +23,50 @@ Once you've created an encrypted column, you can insert data into the table like ![Encrypted data](/docs/img/guides/database/vault-encrypted-data.png) +## Decrypting data + Decrypted data is accessed using a special view that is automatically created after adding an encrypted column to a table. This view decrypts the data row-by-row as you access it. By default, this view is called `decrypted_`. In the example below, the decryption view for the `profiles` table is called `decrypted_profiles`. Notice there is a new column in the view called `decrypted_emails` that contains the decrypted email value. ![Decrypted data](/docs/img/guides/database/vault-decrypted-data.png) +## Using an Encrypted Table + +Now that you have TCE setup for a table, it's easy to use by simply inserting data into the table, and querying that data by looking at its generated view. The view is named `decrypted_` and by default is in the same schema as your table: + +```sql +insert into secrets + (secret, account_id) +values + ('1234-5678-8765-4321', 123); +``` + +Now that you have inserted data, look at the table and notice how the secret is encrypted. This is the data that is stored on disk, the encrypted card number, the key id, and the account id, **but the key itself is not stored**. This means if someone gets a backup or dump of your database, they cannot decrypt the secret, they do not have the key, only the key ID: + +```sql +> select * from secrets where account_id = 123; +-[ RECORD 1 ]------+--------------------------------------------------------------------- +id | 1 +secret | jf8KfImkKTr+j4gzyDZQtLDEFL9eSlFuKjNlNEJvDg+OIKUr2wjF/8NnYcLisb5F9xiN +account_id | 123 +key_id | 7f753c4f-8c68-457a-8801-1798b2e9f44d +nonce | \x300a14aa721184ff7cf0f6bf088da267 +``` + +For you, the developer, you need the unencrypted secret for you application. No problem, you can access that data using the dynamically generated decryption view `decrypted_secrets`: + +```sql +> select * from decrypted_secrets where account_id = 123; +-[ RECORD 1 ]----------------+--------------------------------------------------------------------- +id | 1 +secret | jf8KfImkKTr+j4gzyDZQtLDEFL9eSlFuKjNlNEJvDg+OIKUr2wjF/8NnYcLisb5F9xiN +decrypted_secret | 1234-5678-8765-4321 +account_id | 123 +key_id | 7f753c4f-8c68-457a-8801-1798b2e9f44d +nonce | \x300a14aa721184ff7cf0f6bf088da267 +``` + +Notice how there is a new column called `decrypted_secret`. This column is not stored in database or on disk at all, it is generated “on-the-fly” as you select from the view. Database dumps do not contain this information, only the view itself, and most importantly, **raw decryption keys are never stored**. + ## How Key Derivation Works The current state-of-the-art in encryption libraries is [libsodium](https://doc.libsodium.org/). @@ -202,43 +239,7 @@ security label for pgsodium The new label indicates which column is to be associated with the secret, and that's it! Your `account_id` and secret are now protected under the same authentication signature as the secret itself. -## Using an Encrypted Table - -Now that you have TCE setup for a table, it's easy to use by simply inserting data into the table, and querying that data by looking at its generated view. The view is named `decrypted_` and by default is in the same schema as your table: - -```sql -insert into secrets - (secret, account_id) -values - ('1234-5678-8765-4321', 123); -``` - -Now that you have inserted data, look at the table and notice how the secret is encrypted. This is the data that is stored on disk, the encrypted card number, the key id, and the account id, **but the key itself is not stored**. This means if someone gets a backup or dump of your database, they cannot decrypt the secret, they do not have the key, only the key ID: - -```sql -> select * from secrets where account_id = 123; --[ RECORD 1 ]------+--------------------------------------------------------------------- -id | 1 -secret | jf8KfImkKTr+j4gzyDZQtLDEFL9eSlFuKjNlNEJvDg+OIKUr2wjF/8NnYcLisb5F9xiN -account_id | 123 -key_id | 7f753c4f-8c68-457a-8801-1798b2e9f44d -nonce | \x300a14aa721184ff7cf0f6bf088da267 -``` - -For you, the developer, you need the unencrypted secret for you application. No problem, you can access that data using the dynamically generated decryption view `decrypted_secrets`: - -```sql -> select * from decrypted_secrets where account_id = 123; --[ RECORD 1 ]----------------+--------------------------------------------------------------------- -id | 1 -secret | jf8KfImkKTr+j4gzyDZQtLDEFL9eSlFuKjNlNEJvDg+OIKUr2wjF/8NnYcLisb5F9xiN -decrypted_secret | 1234-5678-8765-4321 -account_id | 123 -key_id | 7f753c4f-8c68-457a-8801-1798b2e9f44d -nonce | \x300a14aa721184ff7cf0f6bf088da267 -``` - -Notice how there is a new column called `decrypted_secret`. This column is not stored in database or on disk at all, it is generated “on-the-fly” as you select from the view. Database dumps do not contain this information, only the view itself, and most importantly, **raw decryption keys are never stored**. +## Resources - [Supabase Vault](/docs/guides/database/vault) - Read more about Supabase Vault in the [blog post](https://supabase.com/blog/vault-now-in-beta) diff --git a/apps/docs/pages/guides/database/connecting-to-postgres.mdx b/apps/docs/pages/guides/database/connecting-to-postgres.mdx index 0ac5493110d..e2c421054da 100644 --- a/apps/docs/pages/guides/database/connecting-to-postgres.mdx +++ b/apps/docs/pages/guides/database/connecting-to-postgres.mdx @@ -1,4 +1,5 @@ import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' export const meta = { id: 'connecting-to-postgres', @@ -8,54 +9,21 @@ export const meta = { Supabase provides several options for programmatically connecting to your Postgres database: -## Types of Connection +1. Direct connections using Postgres' standard connection system +2. Connection pooling using PgBouncer +3. Programmatic access uing the [Serverless APIs](/docs/guides/api) -- HTTP connections using the API. -- Direct connections using Postgres' standard connection system. -- Connection pooling using PgBouncer. +## Serverless APIs -### Direct vs Pooling vs API - -- A "direct connection" is when a connection is made to the database using Postgres' native connection implementation. You should use this for tools which are always alive - usually installed on a long-running server. -- A "connection pool" is a system (external to Postgres) which keeps connections "open". You should use this for serverless functions and tools which disconnect from the database frequently. -- The API is an auto-generated REST interface. You should use this for all browser and application interactions. The API server internally handles a connection pool. - -Why would you use a connection pool? Primarily because the way that Postgres handles connections isn't very scalable for a large number of _temporary_ connections. -You can use these simple questions to determine which connection method to use: - -- Are you connecting to a database and _maintaining_ a connection? If yes, use a direct connection. -- Are you connecting to your database and then _disconnecting_ immediately (e.g. a serverless environment)? If yes, use a connection pool. - -## API - -Supabase provides an auto-updating [API](/docs/guides/database/api). This is the easiest way to get started if you are managing data (fetching, inserting, updating). - -### Interfaces - -We provides several types of API to suit your preferences and use-case: +Supabase provides auto-updating [APIs](/docs/guides/database/api). This is the easiest way to get started if you are managing data (fetching, inserting, updating). We provides several types of API to suit your preferences: - [REST](/docs/guides/database/api#rest-api): interact with your database through a REST interface. - [GraphQL](/docs/guides/database/api#graphql-api): interact with your database through a GraphQL interface. - [Realtime](/docs/guides/database/api#realtime-api): listen to database changes over websockets. -You cannot manage the database schema via the API (for security reasons). To do that you can use the dashboard or connect directly to your database. - -### API URL and Keys - -You can find the API URL and Keys in the [Dashboard](https://app.supabase.com/project/_/settings/api). - - - ## Direct connections -Every Supabase project provides a full Postgres database. You can connect to the database using any tool which supports Postgres. - -### Finding your connection string +Every Supabase project provides a full Postgres database. You can connect to the database using [any tool which supports Postgres](#integrations). You can find the connection string in the [Database settings](https://app.supabase.com/project/_/settings/database) inside the dashboard: 1. Go to the `Settings` section. 2. Click `Database`. @@ -68,42 +36,9 @@ Every Supabase project provides a full Postgres database. You can connect to the /> -## Connection Pool +## Connection Pooler -Connection pools are useful for managing a large number of _temporary_ connections. For example, if you are using [Prisma](/docs/guides/integrations/prisma) deployed to a Serverless environment. - -### How connection pooling works - -A "connection pool" is a system (external to Postgres) which manages connections, rather than PostgreSQL's native system. Supabase uses [PgBouncer](https://www.pgbouncer.org/) for connection pooling. - -When a client makes a request, PgBouncer "allocates" an available connection to the client. -When the client transaction or session is completed the connection is returned to the pool and is free to be used by another client. - -![Connection pooling](/docs/img/guides/database/connection-pool.png) - -### Pool modes - -Pool Mode determines how PgBouncer handles a connection. - -#### Session - -When a new client connects, a connection is assigned to the client until it disconnects. Afterward, the connection is returned back to the pool. - -All PostgreSQL features can be used with this option. - -#### Transaction - -This is the suggested option for serverless functions. A connection is only assigned to the client for the duration of a transaction. Two consecutive transactions from the same client -could be executed over two different connections. - -Some session-based PostgreSQL features such as prepared statements are not available with this option. -A comprehensive list of incompatible features can be found [here](https://www.pgbouncer.org/features.html). - -#### Statement - -This is the most granular option. Connections are returned to the pool after every statement. Transactions with multiple statements are not allowed. This is best used when `AUTOCOMMIT` is in use. - -### Finding the connection pool config +Every Supabase project comes with PgBouncer for connection pooling. A connection pooler is useful for managing a large number of _temporary_ connections. For example, if you are using [Prisma](/docs/guides/integrations/prisma), Drizzle, Kysely, or anything deployed to a Serverless environment (AWS Lambdas or Edge Functions). You can find the connection pool config in the [Database settings](https://app.supabase.com/project/_/settings/database) inside the dashboard: 1. Go to the `Settings` section. 2. Click `Database`. @@ -116,34 +51,134 @@ This is the most granular option. Connections are returned to the pool after eve /> +## Choosing a connection method + +- The Serverless APIs provide programmatic access and have built-in connection pooling. You can use these for all browser and application interactions. We recommend using these wherever possible. +- A "direct connection" is Postgres' native connection system. You should use this for tools which are always alive - usually installed on a long-running server, like Node.js, Ruby, Python, etc. +- A "connection pooler" is a tool which keeps connections "alive". You should use this for serverless functions and tools which disconnect from the database frequently, like Prisma, Drizzle, Kysely, etc. + +Why would you use a connection pool? Primarily because the way that Postgres handles connections isn't very scalable for a large number of _temporary_ connections. You can use these simple questions to determine which connection method to use: + +- Are you connecting to a database and _maintaining_ a connection? If yes, use a direct connection. +- Are you connecting to your database and then _disconnecting_ immediately (e.g. a serverless environment)? If yes, use a connection pool. + ## Connecting with SSL -Use this when connecting to your database to prevent snooping and man-in-the-middle attacks. +You should connect to your database using SSL wherever possible, to prevent snooping and man-in-the-middle attacks. + +You can obtain your connection info and Server root certificate from your application's dashboard: -Obtain your connection info and Server root certificate from your application’s dashboard. ![Connection Info and Certificate.](/docs/img/guides/database/connection-info-cert.png) -Assuming you’ve downloaded your certificate and it’s located at `$HOME/Downloads/prod-ca-2021.cer`, and your Host address is `db.abcdefghijklm.supabase.co` you can connect to the DB with -SSL enabled as illustrated below: +## How connection pooling works -1. With `psql` +A "connection pool" is a system (external to Postgres) which manages connections, rather than PostgreSQL's native system. Supabase uses [PgBouncer](https://www.pgbouncer.org/) for connection pooling. -``` -psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-ca-2021.cer host=db.abcdefghijklm.supabase.co dbname=postgres user=postgres" +When a client makes a request, PgBouncer "allocates" an available connection to the client. When the client transaction or session is completed the connection is returned to the pool and is free to be used by another client. + +![Connection pooling](/docs/img/guides/database/connection-pool.png) + +Pgbounce provides several Pool Modes, each handling connections differently: + +#### Session + +When a new client connects, a connection is assigned to the client until it disconnects. Afterward, the connection is returned back to the pool. + +All PostgreSQL features can be used with this option. + +#### Transaction + +This is the suggested option for serverless functions. A connection is only assigned to the client for the duration of a transaction. Two consecutive transactions from the same client could be executed over two different connections. + +Some session-based PostgreSQL features such as prepared statements are not available with this option. A comprehensive list of incompatible features can be found [here](https://www.pgbouncer.org/features.html). + +#### Statement + +This is the most granular option. Connections are returned to the pool after every statement. Transactions with multiple statements are not allowed. This is best used when `AUTOCOMMIT` is in use. + +## Integrations + +### Connecting with psql + +[`psql`](https://www.postgresql.org/docs/current/app-psql.html) is a command-line tool that comes with Postgres. + +Assuming you've downloaded your SSL certificate to `$HOME/Downloads/prod-supabase.cer`, and your host address is `db.ref.supabase.co` you connect to your database via SSL: + +```shell +psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-supabase.cer host=db.ref.supabase.co dbname=postgres user=postgres" ``` -2. With `pgAdmin` - a. Register a new Postgres server - ![Register a new postgres server.](/docs/img/guides/database/register-server-pgAdmin.png) +### Connecting with pgAdmin - b. Name your server to your liking and add the connection info. - ![Name Postgres Server.](/docs/img/guides/database/name-pg-server.png) - ![Add Connection Info.](/docs/img/guides/database/add-pg-server-conn-info.png) +[`pgAdmin`](https://www.pgadmin.org/) is a GUI tool for managing Postgres databases. You can use it to connect to your database via SSL: -3. Navigate to the SSL tab and change the SSL mode to Require. Next navigate to the Root certificate input, it will open up a - file-picker modal. Select the certificate you downloaded from your Supabase dashboard and save the server details. PgAdmin - should now be able to connect to your Postgres via SSL. - ![Add Connection Info.](/docs/img/guides/database/add-ssl-config.png) + + + + + + + Register a new Postgres server. + + + + + + ![Register a new postgres server.](/docs/img/guides/database/register-server-pgAdmin.png) + + + + + + + + + + Name your server. + + + + + + ![Name Postgres Server.](/docs/img/guides/database/name-pg-server.png) + + + + + + + + + + Add the connection info. You can use the "Direct connection" config, which you can find in your Supabase dashboard. + + + + + + ![Add Connection Info.](/docs/img/guides/database/add-pg-server-conn-info.png) + + + + + + + + + + Navigate to the SSL tab and change the SSL mode to Require. Next navigate to the Root certificate input, it will open up a file-picker modal. Select the certificate you downloaded from your Supabase dashboard and save the server details. PgAdmin should now be able to connect to your Postgres via SSL. + + + + + + ![Add Connection Info.](/docs/img/guides/database/add-ssl-config.png) + + + + + + export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/database/sql-to-api.mdx b/apps/docs/pages/guides/database/sql-to-api.mdx index 6c17121d110..cd53049ddd5 100644 --- a/apps/docs/pages/guides/database/sql-to-api.mdx +++ b/apps/docs/pages/guides/database/sql-to-api.mdx @@ -79,7 +79,7 @@ const { data, error } = await supabase ## Resources -- [Supabase Account - Free Tier OK](https://supabase.com) +- [Supabase Account - Free Plan OK](https://supabase.com) - [Postgrest Operators](https://postgrest.org/en/stable/api.html#operators) - [Supabase API: JavaScript select](/docs/reference/javascript/select) - [Supabase API: JavaScript modifiers](/docs/reference/javascript/using-modifiers) diff --git a/apps/docs/pages/guides/database/vault.mdx b/apps/docs/pages/guides/database/vault.mdx index 683d383219b..0d32b241104 100644 --- a/apps/docs/pages/guides/database/vault.mdx +++ b/apps/docs/pages/guides/database/vault.mdx @@ -3,19 +3,15 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'vault', title: 'Vault', - description: 'Use Supabase to store and serve files.', + description: + 'Vault is a Postgres extension and accompanying Supabase UI that makes it safe and easy to store encrypted secrets.', + subtitle: 'Managing secrets in Postgres.', sidebar_label: 'Overview', video: 'https://www.youtube.com/v/J9mTPY8rIXE', } -Supabase Vault provides encrypted secret storage and Encrypted Columns for Tables. - Vault is a Postgres extension and accompanying Supabase UI that makes it safe and easy to store encrypted secrets and other data in your database. This opens up a lot of possibilities to use Postgres in ways that go beyond what is available in a stock distribution. -From a product perspective, Supabase groups a number of related features under the “Vault banner”. Let's explore a few of these features. - -## Secrets Management - Under the hood, the Vault is a table of Secrets and Encryption Keys that are stored using [Authenticated Encryption](https://en.wikipedia.org/wiki/Authenticated_encryption) on disk. They are then available in decrypted form through a Postgres view so that the secrets can be used by applications from SQL. Because the secrets are stored on disk encrypted and authenticated, any backups or replication streams also preserve this encryption in a way that can't be decrypted or forged. Supabase provides a dashboard UI for the Vault that makes storing secrets easy. Click a button, type in your secret, and save. Optionally create your own keys you can use to encrypt your secret. Your secret will then be stored on disk encrypted using the specified key. @@ -31,79 +27,48 @@ Supabase provides a dashboard UI for the Vault that makes storing secrets easy. There are two main parts to the Vault UI, Secrets and Encryption Keys: -- **Secrets:** Use the Vault to store Secrets - everything from Environment Variables to API Keys. You can use these Secrets anywhere in your database: Postgres [Functions](/docs/guides/database/functions), Triggers, and [Webhooks](/docs/guides/database/webhooks). From a SQL perspective, accessing secrets is as easy as querying a table (or in this case, a view). The underlying secrets tables will be stored in encrypted form. -- **Encryption Keys:** These are keys used to encrypt data inside your database. You can create different Encryption Keys for different purposes, for example: one for encrypting user-data, and another for application-data. Each key is encrypted itself using a root encryption key that lives outside of the database. See **[Encryption key location](#encryption-key-location)** for more details. +## Secrets -## Deep Dive on How The Vault works +You can use the Vault to store secrets - everything from Environment Variables to API Keys. You can then use these secrets anywhere in your database: Postgres [Functions](/docs/guides/database/functions), Triggers, and [Webhooks](/docs/guides/database/webhooks). From a SQL perspective, accessing secrets is as easy as querying a table (or in this case, a view). The underlying secrets tables will be stored in encrypted form. -
- -
+## Encryption Keys -As we mentioned, the Vault uses pgsodium's Transparent Column Encryption (TCE) to store secrets in an authenticated encrypted form. There are some details around that you may be curious about, what does authenticated mean, and where are encryption keys store? This section explains those details. - -### Authenticated Encryption with Associated Data - -The first important feature of TCE is that it uses an [Authenticated Encryption with Associated Data]() encryption algorithm (based on libsodium). - -### Encryption key location - -**Authenticated Encryption** means that in addition to the data being encrypted, it is also signed so that it cannot be forged. You can guarantee that the data was encrypted by someone you trust, which you wouldn't get with encryption alone. The decryption function verifies that the signature is valid _before decrypting the value_. - -**Associated Data** means that you can include any other columns from the same row as part of the signature computation. This doesn't encrypt those other columns - rather it ensures that your encrypted value is only associated with columns from that row. If an attacker were to copy an encrypted value from another row to the current one, the signature would be rejected (assuming you used a unique column in the associated data). - -Another important feature of pgsodium is that the encryption keys are never stored in the database alongside the encrypted data. Instead, only a **Key ID** is stored, which is a reference to the key that is only accessible outside of SQL. Even if an attacker can capture a dump of your entire database, they will see only encrypted data and key IDs, _never the raw key itself_. - -This is an important safety precaution - there is little value in storing the encryption key in the database itself as this would be like locking your front door but leaving the key in the lock! Storing the key outside the database fixes this issue. - -Where are the keys stored? Supabase creates and manages the root keys (from which all key IDs are derived) in our secured backend systems. We keep this root key safe and separate from your data. You remain in control of your keys - a separate API endpoint is available that you can use to access the key if you want to decrypt your data outside of Supabase. +These are keys used to encrypt data inside your database. You can create different Encryption Keys for different purposes, for example: one for encrypting user-data, and another for application-data. Each key is encrypted itself using a root encryption key that lives outside of the database. See **[Encryption key location](#encryption-key-location)** for more details. ## Using the Vault -Using the vault is as simple as `INSERT`ing data into the -`vault.secret` table. +You can manage secrets and encryption keys from the UI or using SQL. + +### Adding secrets + +There is also a handy function for creating secrets called `vault.create_secret()`: ```sql -postgres=> insert into vault.secrets (secret) values ('s3kre3t_k3y') returning *; --[ RECORD 1 ]------------------------------------------------------------- -id | d91596b8-1047-446c-b9c0-66d98af6d001 -name | -description | -secret | S02eXS9BBY+kE3r621IS8beAytEEtj+dDHjs9/0AoMy7HTbog+ylxcS22A== -key_id | 7f5ad44b-6bd5-4c99-9f68-4b6c7486f927 -nonce | \x3aa2e92f9808e496aa4163a59304b895 -created_at | 2022-12-14 02:29:21.3625+00 -updated_at | 2022-12-14 02:29:21.3625+00 -``` - -There is also a handy function for creating secrets called -`vault.create_secret()`: - -```sql -postgres=> select vault.create_secret('another_s3kre3t'); --[ RECORD 1 ]-+------------------------------------- -create_secret | c9b00867-ca8b-44fc-a81d-d20b8169be17 - +select vault.create_secret('my_s3kre3t'); ``` The function returns the UUID of the new secret. -## Name and Description - -Secrets can also have an optional _unique_ name, or an optional description. These are also arguments to `vault.create_secret()`: +
+Show Result ```sql -postgres=> select vault.create_secret('another_s3kre3t', 'unique_name', 'This is the description'); -[ RECORD 1 ]-+------------------------------------- -create_secret | 7095d222-efe5-4cd5-b5c6-5755b451e223 +create_secret | c9b00867-ca8b-44fc-a81d-d20b8169be17 +``` -postgres=> select * from vault.secrets where id = '7095d222-efe5-4cd5-b5c6-5755b451e223'; +
+ +Secrets can also have an optional _unique_ name and an optional description. These are also arguments to `vault.create_secret()`: + +```sql +select vault.create_secret('another_s3kre3t', 'unique_name', 'This is the description'); +``` + +
+Show Result + +```sql -[ RECORD 1 ]----------------------------------------------------------------- id | 7095d222-efe5-4cd5-b5c6-5755b451e223 name | unique_name @@ -115,12 +80,49 @@ created_at | 2022-12-14 02:34:23.85159+00 updated_at | 2022-12-14 02:34:23.85159+00 ``` -## Querying Data from the Vault +
+ +Alternatively, you can create a secret by `insert`ing data into the `vault.secret` table: + +{/* prettier-ignore */} +```sql +insert into vault.secrets (secret) +values ('s3kre3t_k3y') returning *; +``` + +
+Show Result + +```sql +-[ RECORD 1 ]------------------------------------------------------------- +id | d91596b8-1047-446c-b9c0-66d98af6d001 +name | +description | +secret | S02eXS9BBY+kE3r621IS8beAytEEtj+dDHjs9/0AoMy7HTbog+ylxcS22A== +key_id | 7f5ad44b-6bd5-4c99-9f68-4b6c7486f927 +nonce | \x3aa2e92f9808e496aa4163a59304b895 +created_at | 2022-12-14 02:29:21.3625+00 +updated_at | 2022-12-14 02:29:21.3625+00 +``` + +
+ +### Viewing secrets If you look in the `vault.secrets` table, you will see that your data is stored encrypted. To decrypt the data, there is an automatically created view `vault.decrypted_secrets`. This view will decrypt secret data on the fly: +{/* prettier-ignore */} +```sql +select * +from vault.decrypted_secrets +order by created_at desc +limit 3; +``` + +
+Show Result + ```sql -postgres=> select * from vault.decrypted_secrets order by created_at desc limit 3; -[ RECORD 1 ]----+----------------------------------------------------------------- id | 7095d222-efe5-4cd5-b5c6-5755b451e223 name | unique_name @@ -153,17 +155,30 @@ created_at | 2022-12-14 02:29:21.3625+00 updated_at | 2022-12-14 02:29:21.3625+00 ``` +
+ Notice how this view has a `decrypted_secret` column that contains the decrypted secrets. Views are not stored on disk, they are only run at query time, so the secret remains encrypted on disk, and in any backup dumps or replication streams. You should ensure that you protect access to this view with the appropriate SQL privilege settings at all times, as anyone that has access to the view has access to decrypted secrets. -## Updating Secrets +### Updating Secrets A secret can be updated with the `vault.update_secret()` function, this function makes updating secrets easy, just provide the secret UUID as the first argument, and then an updated secret, updated optional unique name, or updated description: ```sql -postgres=> select vault.update_secret('7095d222-efe5-4cd5-b5c6-5755b451e223', 'n3w_upd@ted_s3kret', - 'updated_unique_name', 'This is the updated description'); +select + vault.update_secret( + '7095d222-efe5-4cd5-b5c6-5755b451e223', + 'n3w_upd@ted_s3kret', + 'updated_unique_name', + 'This is the updated description' + ); +``` + +
+Show Result + +```sql -[ RECORD 1 ]-+- update_secret | @@ -180,6 +195,38 @@ created_at | 2022-12-14 02:34:23.85159+00 updated_at | 2022-12-14 02:51:13.938396+00 ``` +
+ +## Deep Dive on How The Vault works + +
+ +
+ +As we mentioned, the Vault uses pgsodium's Transparent Column Encryption (TCE) to store secrets in an authenticated encrypted form. There are some details around that you may be curious about, what does authenticated mean, and where are encryption keys store? This section explains those details. + +### Authenticated Encryption with Associated Data + +The first important feature of TCE is that it uses an [Authenticated Encryption with Associated Data]() encryption algorithm (based on libsodium). + +### Encryption key location + +**Authenticated Encryption** means that in addition to the data being encrypted, it is also signed so that it cannot be forged. You can guarantee that the data was encrypted by someone you trust, which you wouldn't get with encryption alone. The decryption function verifies that the signature is valid _before decrypting the value_. + +**Associated Data** means that you can include any other columns from the same row as part of the signature computation. This doesn't encrypt those other columns - rather it ensures that your encrypted value is only associated with columns from that row. If an attacker were to copy an encrypted value from another row to the current one, the signature would be rejected (assuming you used a unique column in the associated data). + +Another important feature of pgsodium is that the encryption keys are never stored in the database alongside the encrypted data. Instead, only a **Key ID** is stored, which is a reference to the key that is only accessible outside of SQL. Even if an attacker can capture a dump of your entire database, they will see only encrypted data and key IDs, _never the raw key itself_. + +This is an important safety precaution - there is little value in storing the encryption key in the database itself as this would be like locking your front door but leaving the key in the lock! Storing the key outside the database fixes this issue. + +Where are the keys stored? Supabase creates and manages the root keys (from which all key IDs are derived) in our secured backend systems. We keep this root key safe and separate from your data. You remain in control of your keys - a separate API endpoint is available that you can use to access the key if you want to decrypt your data outside of Supabase. + ## Internal Details To encrypt data, you need a _key id_. You can use the default key id created automatically for every project, or create your own key ids Using the `pgsodium.create_key()` function. Key ids are used to internally derive the encryption key used to encrypt secrets in the vault. Vault users typically do not have access to the key itself, only the key id. @@ -209,7 +256,7 @@ And then restart your project from the dashboard to enable that change. In the future we are researching various ways to refine the way statement logging interacts with sensitive columns. -## See also +## Resources - Read more about Supabase Vault in the [blog post](https://supabase.com/blog/vault-now-in-beta) - [Supabase Vault on GitHub](https://github.com/supabase/vault) diff --git a/apps/docs/pages/guides/functions/cicd-workflow.mdx b/apps/docs/pages/guides/functions/cicd-workflow.mdx index a1836c20b53..4827bde8473 100644 --- a/apps/docs/pages/guides/functions/cicd-workflow.mdx +++ b/apps/docs/pages/guides/functions/cicd-workflow.mdx @@ -24,16 +24,25 @@ jobs: env: SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} - PROJECT_ID: zdtdtxajzydjqzuktnqx + PROJECT_ID: your-project-id steps: - uses: actions/checkout@v3 - uses: supabase/setup-cli@v1 with: - version: 1.0.0 + version: latest - - run: supabase functions deploy your-function-name --project-ref $PROJECT_ID + - run: supabase functions deploy --project-ref $PROJECT_ID +``` + +Since Supabase CLI [v1.62.0](https://github.com/supabase/cli/releases/tag/v1.62.0) you can deploy all functions with a single command. + +Individual function configuration like [JWT verification](/docs/reference/cli/config#functions.function_name.verify_jwt) and [import map location](/docs/reference/cli/config#functions.function_name.import_map) can be set via the `config.toml` file. + +```toml +[functions.hello-world] +verify_jwt = false ```
diff --git a/apps/docs/pages/guides/functions/examples/github-actions.mdx b/apps/docs/pages/guides/functions/examples/github-actions.mdx index 94777f4366d..7baf600b40d 100644 --- a/apps/docs/pages/guides/functions/examples/github-actions.mdx +++ b/apps/docs/pages/guides/functions/examples/github-actions.mdx @@ -40,9 +40,18 @@ jobs: - uses: supabase/setup-cli@v1 with: - version: 1.0.0 + version: latest - - run: supabase functions deploy github-action-deploy --project-ref $PROJECT_ID + - run: supabase functions deploy --project-ref $PROJECT_ID +``` + +Since Supabase CLI [v1.62.0](https://github.com/supabase/cli/releases/tag/v1.62.0) you can deploy all functions with a single command. + +Individual function configuration like [JWT verification](/docs/reference/cli/config#functions.function_name.verify_jwt) and [import map location](/docs/reference/cli/config#functions.function_name.import_map) can be set via the `config.toml` file. + +```toml +[functions.hello-world] +verify_jwt = false ``` export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/functions/quickstart.mdx b/apps/docs/pages/guides/functions/quickstart.mdx index be7537510fd..a01448526ab 100644 --- a/apps/docs/pages/guides/functions/quickstart.mdx +++ b/apps/docs/pages/guides/functions/quickstart.mdx @@ -41,6 +41,8 @@ This creates a function stub in your `supabase` folder at `./functions/hello-wor ## Deploy to production +### Deploy a specific function + ```bash supabase functions deploy hello-world ``` @@ -56,6 +58,21 @@ If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webho +### Deploy all functions + +```bash +supabase functions deploy +``` + +Since Supabase CLI [v1.62.0](https://github.com/supabase/cli/releases/tag/v1.62.0) you can deploy all functions with a single command. This is useful for example when [deploying with GitHub Actions](/docs/guides/functions/cicd-workflow). + +Individual function configuration like [JWT verification](/docs/reference/cli/config#functions.function_name.verify_jwt) and [import map location](/docs/reference/cli/config#functions.function_name.import_map) can be set via the `config.toml` file. + +```toml +[functions.hello-world] +verify_jwt = false +``` + ## Invoking remote functions You can invoke Edge Functions using curl: diff --git a/apps/docs/pages/guides/getting-started.mdx b/apps/docs/pages/guides/getting-started.mdx index 233c550cd37..1a96f37615f 100644 --- a/apps/docs/pages/guides/getting-started.mdx +++ b/apps/docs/pages/guides/getting-started.mdx @@ -102,9 +102,9 @@ export const meta = { export const useCases = [ { - title: 'OpenAI Vector Search', - href: '/guides/getting-started/openai/vector-search', - description: `Build your own custom ChatGPT with Next.js, OpenAI and pg_vector.`, + title: 'AI, Vectors, and embeddings', + href: '/docs/guides/ai#examples', + description: `Build AI-enabled applications using our Vector toolkit.`, icon: '/docs/img/icons/openai_logo', }, { diff --git a/apps/docs/pages/guides/getting-started/local-development.mdx b/apps/docs/pages/guides/getting-started/local-development.mdx index a4b787487cc..61af856f0dd 100644 --- a/apps/docs/pages/guides/getting-started/local-development.mdx +++ b/apps/docs/pages/guides/getting-started/local-development.mdx @@ -19,7 +19,7 @@ The Dashboard provides a wide range of features for setting up your project: cre 2. **Easier Collaboration**: Developing locally can make it easier to collaborate with others on the same project. -3. **Cost-Effective**: Supabase provides a generous free tier and gives you two free projects to get started. But what if you need more than two? When you develop locally, you can spin up unlimited local projects and link them with live projects when you're ready to launch. +3. **Cost-Effective**: Supabase provides a generous free plan and gives you two free projects to get started. But what if you need more than two? When you develop locally, you can spin up unlimited local projects and link them with live projects when you're ready to launch. 4. **Configuration in code**: If you directly change your tables via the Dashboard, none of that gets captured in code. If you follow these local development practices, you'll store all of your table schemas in code. diff --git a/apps/docs/pages/guides/integrations/directus.mdx b/apps/docs/pages/guides/integrations/directus.mdx index 05601847348..2d62ee6dd28 100644 --- a/apps/docs/pages/guides/integrations/directus.mdx +++ b/apps/docs/pages/guides/integrations/directus.mdx @@ -13,7 +13,7 @@ In this guide, we will demonstrate how to create a new Supabase project, install ![Supabase App](/docs/img/guides/integrations/directus/supabase-20220608A.webp) -[Supabase](https://supabase.com/) is an open-source Firebase alternative that provides a PostgreSQL database, storage, authentication, and a dynamic REST API based on your schema. While it is possible to self-host Supabase on your own infrastructure, this article will focus on Supabase Cloud's Free tier, which is the fastest and easiest way to get started. +[Supabase](https://supabase.com/) is an open-source Firebase alternative that provides a PostgreSQL database, storage, authentication, and a dynamic REST API based on your schema. While it is possible to self-host Supabase on your own infrastructure, this article will focus on Supabase Cloud's Free plan, which is the fastest and easiest way to get started. ![Directus App](/docs/img/guides/integrations/directus/directus-20220608A.webp) diff --git a/apps/docs/pages/guides/integrations/polyscale.mdx b/apps/docs/pages/guides/integrations/polyscale.mdx index db29b37552e..6db1d4d2c61 100644 --- a/apps/docs/pages/guides/integrations/polyscale.mdx +++ b/apps/docs/pages/guides/integrations/polyscale.mdx @@ -30,7 +30,7 @@ PolyScale provides caching for TCP connections and GraphQL. Support for caching ## Step 0: Create a PolyScale account -If you do not already have a PolyScale account, you can create an account [here](https://app.polyscale.ai/signup). PolyScale offers a free tier and no credit card is required. +If you do not already have a PolyScale account, you can create an account [here](https://app.polyscale.ai/signup). PolyScale offers a free plan and no credit card is required. ## Step 1: Create your PolyScale Cache diff --git a/apps/docs/pages/guides/platform/access-control.mdx b/apps/docs/pages/guides/platform/access-control.mdx index 4c1788c28f9..fd33555ad4e 100644 --- a/apps/docs/pages/guides/platform/access-control.mdx +++ b/apps/docs/pages/guides/platform/access-control.mdx @@ -79,7 +79,7 @@ The table below shows the corresponding permissions for each available role you prevents accidental invites to accounts not managed by your company's enterprise systems. -[^2]: Available on the Teams and Enterprise Tiers. +[^2]: Available on the Teams and Enterprise Plans. export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/platform/backups.mdx b/apps/docs/pages/guides/platform/backups.mdx index 1a0d53cf5d0..d1932018d77 100644 --- a/apps/docs/pages/guides/platform/backups.mdx +++ b/apps/docs/pages/guides/platform/backups.mdx @@ -21,7 +21,7 @@ Daily Backups and PITR are mutually exclusive. If your project opts into using P ## Daily Backups -All Pro and Enterprise tier Supabase projects are backed up automatically on a daily basis. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data if disaster hits at the most inopportune time. If a lower RPO is required, enabling Point-in-Time Recovery should be considered. +All Pro and Enterprise plan Supabase projects are backed up automatically on a daily basis. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data if disaster hits at the most inopportune time. If a lower RPO is required, enabling Point-in-Time Recovery should be considered. For security purposes, passwords for custom roles are not stored in daily backups, and will not be @@ -35,7 +35,7 @@ The PostgreSQL utility [pg_dumpall](https://www.postgresql.org/docs/current/app- ![Scheduled backups dashboard](/docs/img/backups-daily-dashboard.png) -You can access daily backups in the [Scheduled backups](https://app.supabase.com/project/_/database/backups/scheduled) settings in the Dashboard. Pro tier projects can access the last 7 days’ worth of daily backups while Enterprise tier projects can access up to 30 days’ worth of daily backups. Users can restore their project to any one of the backups or download them as a zipped SQL file. +You can access daily backups in the [Scheduled backups](https://app.supabase.com/project/_/database/backups/scheduled) settings in the Dashboard. Pro plan projects can access the last 7 days’ worth of daily backups while Enterprise plan projects can access up to 30 days’ worth of daily backups. Users can restore their project to any one of the backups or download them as a zipped SQL file. ### Restoration Process [#daily-backups-restoration-process] @@ -52,7 +52,7 @@ The Dashboard will then prompt for a confirmation before proceeding with the res Point-in-Time Recovery (PITR) allows a project to be backed up at much shorter intervals. This provides users an option to restore to any chosen point of up to seconds in granularity. Even with daily backups, a day’s worth of data could still be lost. With PITR, backups could be performed up to the point of disaster. - This feature is available to all Enterprise tier projects. Pro tier projects can enable PITR as an + This feature is available to all Enterprise plan projects. Pro plan projects can enable PITR as an add-on. diff --git a/apps/docs/pages/guides/platform/compute-add-ons.mdx b/apps/docs/pages/guides/platform/compute-add-ons.mdx index 6706883a603..d517b4822f5 100644 --- a/apps/docs/pages/guides/platform/compute-add-ons.mdx +++ b/apps/docs/pages/guides/platform/compute-add-ons.mdx @@ -8,18 +8,20 @@ export const meta = { Every project on the Supabase Platform comes with its own dedicated Postgres instance running inside a virtual machine (VM). The following table describes the base instance with additional compute add-ons available if you need extra performance when scaling up Supabase. -| Plan | Pricing | CPU | Memory | Maximum Disk IO Bandwidth | Baseline Disk IO Bandwidth | Connections: Direct (recommended) | Connections: Pooler (recommended) | -| --------------- | ------- | ----------------------- | ------ | ------------------------- | -------------------------- | --------------------------------- | --------------------------------- | -| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 2,606 Mbps | 87 Mbps | 60 | 200 | -| Small | $5 | 2-core ARM (shared) | 2 GB | 2,606 Mbps | 174 Mbps | 90 | 200 | -| Medium | $50 | 2-core ARM (shared) | 4 GB | 2,606 Mbps | 347 Mbps | 120 | 200 | -| Large | $100 | 2-core ARM (dedicated) | 8 GB | 4,750 Mbps | 630 Mbps | 160 | 300 | -| XL | $200 | 4-core ARM (dedicated) | 16 GB | 4,750 Mbps | 1,188 Mbps | 240 | 700 | -| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 4,750 Mbps | 2,375 Mbps | 380 | 1500 | -| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 4,750 Mbps | 4,750 Mbps | 480 | 3000 | -| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 9,500 Mbps | 9,500 Mbps | 490 | 6000 | -| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 14,250 Mbps | 14,250 Mbps | 500 | 9000 | -| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 19,000 Mbps | 19,000 Mbps | 500 | 12,000 | +| Plan | Pricing | CPU | Memory | Connections: Direct | Connections: Pooler | +| --------------- | ------- | ----------------------- | ------ | ------------------- | ------------------- | +| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 60 | 200 | +| Small | $5 | 2-core ARM (shared) | 2 GB | 90 | 200 | +| Medium | $50 | 2-core ARM (shared) | 4 GB | 120 | 200 | +| Large | $100 | 2-core ARM (dedicated) | 8 GB | 160 | 300 | +| XL | $200 | 4-core ARM (dedicated) | 16 GB | 240 | 700 | +| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 380 | 1500 | +| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 480 | 3000 | +| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 490 | 6000 | +| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 500 | 9000 | +| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 | + +Number of connections above are recommended values. [Contact us](https://supabase.com/contact/enterprise) if you require a custom plan. @@ -31,11 +33,32 @@ All Postgres instances on Supabase are dedicated applications running inside ded When considering compute upgrades, assess whether your bottlenecks are hardware-constrained or software-constrained. For example, you may want to look into [optimizing the number of connections](/docs/guides/platform/performance#optimizing-the-number-of-connections) or [examining query performance](/docs/guides/platform/performance#examining-query-performance). When you're happy with your Postgres instance's performance, then you can focus on additional compute resources. For example, you can load test your application in staging to understand your compute requirements. You can also start out on a smaller tier, [create a report](https://app.supabase.com/project/_/reports) in the Dashboard to monitor your CPU utilization, and upgrade later as needed -## Disk IO bandwidth +## Disk Throughput and IOPS -SSD Disks are attached to your servers and the disk performance of your workload is determined by the Disk IO bandwidth of this connection. Smaller compute instances can burst up to the maximum disk IO bandwidth for 30 minutes in a day. Beyond that, the performance reverts to the baseline disk IO bandwidth. For example, the free tier can burst up to 2,606 Mbps for 30 minutes a day and reverts to the baseline performance of 87 Mbps. If you need consistent disk performance, choose the 4XL or larger compute add-on which has the same baseline and maximum disk IO bandwidth. +SSD Disks are attached to your servers and the disk performance depends on the compute add-on of your instance. -If you're unsure of how many IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://app.supabase.com/project/_/reports). If the `Daily Disk IO Budget % Remaining` stat is less than 100%, it indicates that your workload has burst beyond the baseline IO throughput during the day. If this metric drops to zero, the workload has used up all the burst IO throughput minutes during the day and is running at the baseline performance. These projects are good candidates for upgrading to a larger compute add with higher baseline throughput. +| Plan | Pricing | Max Disk Throughput | Baseline Disk Throughput | Max IOPS | Baseline IOPS | +| --------------- | ------- | ------------------- | ------------------------ | ----------- | ------------- | +| Free (Included) | $0 | 2,085 Mbps | 87 Mbps | 11,800 IOPS | 500 IOPS | +| Small | $5 | 2,085 Mbps | 174 Mbps | 11,800 IOPS | 1,000 IOPS | +| Medium | $50 | 2,085 Mbps | 347 Mbps | 11,800 IOPS | 2,000 IOPS | +| Large | $100 | 4,750 Mbps | 630 Mbps | 20,000 IOPS | 3,600 IOPS | +| XL | $200 | 4,750 Mbps | 1,188 Mbps | 20,000 IOPS | 6,000 IOPS | +| 2XL | $400 | 4,750 Mbps | 2,375 Mbps | 20,000 IOPS | 12,000 IOPS | +| 4XL | $950 | 4,750 Mbps | 4,750 Mbps | 20,000 IOPS | 20,000 IOPS | +| 8XL | $1,860 | 9,500 Mbps | 9,500 Mbps | 40,000 IOPS | 40,000 IOPS | +| 12XL | $2,790 | 14,250 Mbps | 14,250 Mbps | 50,000 IOPS | 50,000 IOPS | +| 16XL | $3,720 | 19,000 Mbps | 19,000 Mbps | 80,000 IOPS | 80,000 IOPS | + +[Contact us](https://supabase.com/contact/enterprise) if you require a custom plan. + +### Bursting and Daily Disk Budget + +Smaller compute instances can burst up to their largest throughput and IOPS for 30 minutes in a day. Beyond that, the performance reverts to the baseline. For example, the free tier can burst up to 2,085 Mbps for 30 minutes a day and reverts to the baseline performance of 87 Mbps. Your disk budget gets replenished throughout the day. + +If you need consistent disk performance, choose the 4XL or larger compute add-on which has the same baseline and maximum disk throughput and IOPS. + +If you're unsure of how much throughput or IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://app.supabase.com/project/_/reports). If the `Daily Disk IO Budget % Remaining` stat is less than 100%, it indicates that your workload has burst beyond the baseline IO throughput during the day. If this metric drops to zero, the workload has used up all the burst IO throughput minutes during the day and is running at the baseline performance. These projects are good candidates for upgrading to a larger compute add-on with higher baseline throughput. export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/platform/custom-domains.mdx b/apps/docs/pages/guides/platform/custom-domains.mdx index 0f9df7630d2..e6d9b67f6c3 100644 --- a/apps/docs/pages/guides/platform/custom-domains.mdx +++ b/apps/docs/pages/guides/platform/custom-domains.mdx @@ -7,7 +7,7 @@ export const meta = { video: 'https://www.youtube.com/v/6rcGnW_Mh-0', } -Custom domains allow you to present a branded experience to your users. Custom domains are available as a [add-on for projects on a paid tier](https://app.supabase.com/project/_/settings/billing/update). Setting up a custom domain requires [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project. Currently, you must use a subdomain (e.g., `api.example.com`, rather than `example.com`) for the purposes of this guide. +Custom domains allow you to present a branded experience to your users. Custom domains are available as a [add-on for projects on a paid plan](https://app.supabase.com/project/_/settings/billing/update). Setting up a custom domain requires [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project. Currently, you must use a subdomain (e.g., `api.example.com`, rather than `example.com`) for the purposes of this guide.