diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 4cea890356d54..0ca56c3328495 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1,8 +1,9 @@ -import type { ComponentProps } from 'react' // End of third-party imports import { isFeatureEnabled } from 'common/enabled-features' +import type { ComponentProps } from 'react' import type { IconPanel } from 'ui-patterns/IconPanel' + import type { GlobalMenuItems, NavMenuConstant, NavMenuSection } from '../Navigation.types' const { @@ -2849,10 +2850,19 @@ export const self_hosting: NavMenuConstant = { name: 'How-to Guides', items: [ { name: 'Self-Hosted Functions', url: '/guides/self-hosting/self-hosted-functions' }, - { name: 'Restore from Platform', url: '/guides/self-hosting/restore-from-platform' }, + { + name: 'Add Reverse Proxy with HTTPS', + url: '/guides/self-hosting/self-hosted-proxy-https', + }, + { + name: 'Restore Project from Platform', + url: '/guides/self-hosting/restore-from-platform', + }, { name: 'Configure S3 Storage', url: '/guides/self-hosting/self-hosted-s3' }, { name: 'Copy Storage from Platform', url: '/guides/self-hosting/copy-from-platform-s3' }, - { name: 'Enabling MCP server', url: '/guides/self-hosting/enable-mcp' }, + { name: 'Configure Social Login (OAuth)', url: '/guides/self-hosting/self-hosted-oauth' }, + { name: 'Configure Phone Login & MFA', url: '/guides/self-hosting/self-hosted-phone-mfa' }, + { name: 'Enable MCP server', url: '/guides/self-hosting/enable-mcp' }, ], }, { diff --git a/apps/docs/content/guides/self-hosting/docker.mdx b/apps/docs/content/guides/self-hosting/docker.mdx index 74146dcdc8db2..a196650d77b21 100644 --- a/apps/docs/content/guides/self-hosting/docker.mdx +++ b/apps/docs/content/guides/self-hosting/docker.mdx @@ -428,6 +428,20 @@ By default all files are stored locally on the server. You can connect Storage t See the [Configure S3 Storage](/docs/guides/self-hosting/self-hosted-s3) guide for detailed setup instructions. +#### Configuring HTTPS + +By default, Supabase is accessible over HTTP. For production deployments, especially when using OAuth providers, you need HTTPS with a valid TLS certificate. The recommended approach is to place a reverse proxy (such as Caddy or Nginx) in front of Kong. + +See the [Configure HTTPS](/docs/guides/self-hosting/self-hosted-proxy-https) guide for setup instructions. + +#### Configuring social login (OAuth) providers + +See the [Configure Social Login (OAuth) Providers](/docs/guides/self-hosting/self-hosted-oauth) guide for setup instructions. + +#### Configuring phone login, SMS, and MFA + +See the [Configure Phone Login & MFA](/docs/guides/self-hosting/self-hosted-phone-mfa) guide for SMS provider setup, OTP settings, and multi-factor authentication configuration. + #### Configuring Supabase AI Assistant Configuring the Supabase AI Assistant is optional. By adding **your own** `OPENAI_API_KEY` to `.env` you can enable AI services, which help with writing SQL queries, statements, and policies. diff --git a/apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx b/apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx new file mode 100644 index 0000000000000..702cdd8756547 --- /dev/null +++ b/apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx @@ -0,0 +1,458 @@ +--- +title: 'Configure Social Login (OAuth) Providers' +description: 'Set up social login (OAuth/OIDC) providers for self-hosted Supabase with Docker.' +subtitle: 'Set up social login (OAuth/OIDC) providers for self-hosted Supabase with Docker.' +--- + +This guide covers the **server-side configuration** required to enable social login providers on a self-hosted Supabase instance running with Docker Compose. This applies to all OAuth and OIDC-based providers, including third-party identity providers like Keycloak. + +## Before you begin + +You need: + +- A working self-hosted Supabase installation. See [Self-Hosting with Docker](/docs/guides/self-hosting/docker). +- `API_EXTERNAL_URL` set to the publicly reachable URL of your Supabase instance (e.g., `https://`). + + + +HTTPS is strongly recommended in production. Most OAuth providers reject `http://` callback URLs (except `localhost`). + + + +Your **OAuth callback URL** is built from `API_EXTERNAL_URL`. For example, if `API_EXTERNAL_URL` is `https://`, the callback URL will become: + +``` +https:///auth/v1/callback +``` + +You will have to register this URL with each OAuth provider. + +## OAuth request flow + +When a user signs in with an OAuth provider, the following flow occurs: + +1. Your app calls `supabase.auth.signInWithOAuth()` and the browser redirects to the Auth service +2. API gateway (Kong) routes the request to the Auth container (`/auth/v1/authorize`) +3. Auth redirects the user to the OAuth provider (e.g., Google) for consent +4. The provider redirects back to `https:///auth/v1/callback` +5. Auth exchanges the authorization code for tokens and redirects the user to your `SITE_URL` or an allowed redirect URL + +## Auth environment variables + +The Auth service (GoTrue) uses the prefix `GOTRUE_EXTERNAL_` followed by a provider name for all OAuth configuration. For example, when using Google: + +- `GOTRUE_EXTERNAL_GOOGLE_ENABLED` +- `GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID` +- `GOTRUE_EXTERNAL_GOOGLE_SECRET` +- `GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI` + +## Step-by-step configuration + +The default `.env.example` and `docker-compose.yml` include commented-out placeholders for Google, GitHub, and Azure. + +### Step 1: Register your app with the provider + +1. Go to the OAuth provider's developer console and create an application. +2. Set the **authorized redirect URL**, e.g., `https:///auth/v1/callback` +3. Copy the **client ID** and **client secret** into your `.env` file. + +### Step 2: Configure variables in the `.env` file + +Uncomment the lines for your provider in `.env` and add your client ID and secret, e.g., for Google: + +``` +GOOGLE_ENABLED=true +GOOGLE_CLIENT_ID=your-client-id +GOOGLE_SECRET=your-client-secret +``` + +### Step 3: Enable the matching lines in `docker-compose.yml` + +Uncomment the corresponding `GOTRUE_EXTERNAL_` lines in the `auth` service's `environment`: + +``` +GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED} +GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} +GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_SECRET} +GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback +``` + + + +For providers **not** pre-configured in the files (see the [full provider list](#other-supported-providers) below), add the lines manually following the same pattern: variables in `.env`, passthrough with `GOTRUE_EXTERNAL_PROVIDER_` in `docker-compose.yml`. + + + +### Step 4: Restart the auth service + +```sh +docker compose up -d --force-recreate --no-deps auth +``` + +### Step 5: Verify the configuration + +Check that the provider is enabled: + +```sh +curl https:///auth/v1/settings +``` + +The response should include your provider under `external`: + +``` +{ + "external": { + "google": true + } +} +``` + +## Provider-specific setup + + + + + +**Google Cloud Console setup:** + +1. Go to [Google Cloud Console](https://console.cloud.google.com/) +2. Create or select a project +3. Select **Solutions** > **All products** in the navigation menu on the left +4. Go to **APIs & services** > **OAuth consent screen** and click **Get started** +5. Follow the configuration steps and add an **External** app +6. Go to **APIs & Services** > **Credentials** +7. Click **Create Credentials** > **OAuth client ID** +8. Set application type to **Web application** +9. Under **Authorized redirect URIs**, add: `https:///auth/v1/callback` +10. Click **Create** and copy the client ID and client secret + +**`.env`:** + +``` +GOOGLE_ENABLED=true +GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com +GOOGLE_SECRET=your-google-client-secret +``` + +**`docker-compose.yml`:** + +``` +GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED} +GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} +GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_SECRET} +GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback +``` + + + + + +**GitHub setup:** + +1. Go to [GitHub Developer Settings](https://github.com/settings/developers) +2. Click **New OAuth app** +3. Fill in **Homepage URL**, e.g., `https://` +4. Set **Authorization callback URL** to: `https:///auth/v1/callback` +5. Click **Register application** +6. Copy the client ID, generate and copy a client secret + +**`.env`:** + +``` +GITHUB_ENABLED=true +GITHUB_CLIENT_ID=your-github-client-id +GITHUB_SECRET=your-github-client-secret +``` + +**`docker-compose.yml`:** + +``` +GOTRUE_EXTERNAL_GITHUB_ENABLED: ${GITHUB_ENABLED} +GOTRUE_EXTERNAL_GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} +GOTRUE_EXTERNAL_GITHUB_SECRET: ${GITHUB_SECRET} +GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback +``` + + + + + +**Azure Portal setup:** + +1. Go to [Azure Portal](https://portal.azure.com/) +2. Go to **All services** > **Identity** > **App registrations** via the navigation menu on the left +3. Click **New registration** +4. Add application **Name** +5. Under **Redirect URI**, select **Web** and enter: `https:///auth/v1/callback` +6. Click **Register** +7. Copy the **Application (client) ID** +8. Click on **Client credentials** > **Add a certificate or secret** +9. Click on **New client secret** and add a client secret +10. Copy the secret value (not "secret ID") + +**`.env`:** + +``` +AZURE_ENABLED=true +AZURE_CLIENT_ID=your-azure-application-client-id +AZURE_SECRET=your-azure-client-secret +## Optional: restrict to a specific tenant (defaults to 'common') +# AZURE_URL=https://login.microsoftonline.com/your-tenant-id +``` + +**`docker-compose.yml`:** + +``` +GOTRUE_EXTERNAL_AZURE_ENABLED: ${AZURE_ENABLED} +GOTRUE_EXTERNAL_AZURE_CLIENT_ID: ${AZURE_CLIENT_ID} +GOTRUE_EXTERNAL_AZURE_SECRET: ${AZURE_SECRET} +GOTRUE_EXTERNAL_AZURE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback +## Optional: uncomment for tenant-specific Azure login +# GOTRUE_EXTERNAL_AZURE_URL: ${AZURE_URL} +``` + + + + + +**Apple Developer setup:** + +1. Refer to [Apple Developer documentation](https://developer.apple.com/documentation/signinwithapple/configuring-your-environment-for-sign-in-with-apple) to learn how to enable App ID and create a Services ID +2. Create a private key for sign in with Apple +3. Generate a client secret JWT from your private key. See [Apple Developer documentation](https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret) for details. + +**`.env`:** + +``` +APPLE_ENABLED=true +APPLE_CLIENT_ID=com.example.your-services-id +APPLE_SECRET=your-generated-jwt-client-secret +``` + +**`docker-compose.yml`:** + +``` +GOTRUE_EXTERNAL_APPLE_ENABLED: ${APPLE_ENABLED} +GOTRUE_EXTERNAL_APPLE_CLIENT_ID: ${APPLE_CLIENT_ID} +GOTRUE_EXTERNAL_APPLE_SECRET: ${APPLE_SECRET} +GOTRUE_EXTERNAL_APPLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback +``` + + + +Apple uses `response_mode=form_post` for its OAuth flow. The Auth service handles this automatically - no additional configuration is needed. + + + + + + + +**Keycloak setup:** + +1. Open your Keycloak admin console +2. Select (or create) the realm you want to use +3. Go to **Clients** > **Create client** +4. Set **Client type** to **OpenID Connect** +5. Set **Client ID** (e.g., `supabase`) +6. On the next screen, enable **Client authentication** +7. Under **Valid redirect URIs**, add: `https:///auth/v1/callback` +8. Save, then go to the **Credentials** tab and copy the **Client secret** + +**`.env` variables:** + +``` +KEYCLOAK_ENABLED=true +KEYCLOAK_CLIENT_ID=supabase +KEYCLOAK_SECRET=your-keycloak-client-secret +## Required: your Keycloak realm URL +KEYCLOAK_URL=https://keycloak.example.com/realms/myrealm +``` + +**`docker-compose.yml` passthrough:** + +``` +GOTRUE_EXTERNAL_KEYCLOAK_ENABLED: ${KEYCLOAK_ENABLED} +GOTRUE_EXTERNAL_KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID} +GOTRUE_EXTERNAL_KEYCLOAK_SECRET: ${KEYCLOAK_SECRET} +GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback +GOTRUE_EXTERNAL_KEYCLOAK_URL: ${KEYCLOAK_URL} +``` + + + +`KEYCLOAK_URL` is **required**. It must be the full realm URL (e.g., `https://keycloak.example.com/realms/myrealm`). The Auth service uses this to discover the OIDC endpoints (`.well-known/openid-configuration`). Without it, Keycloak login will not work. + + + + + + + +## Other supported providers + +Supabase Auth supports the following OAuth providers: + +| Provider | Env prefix | Additional variables | Docs | +| ----------------- | ---------------- | ------------------------------- | --------------------------------------------------------------------- | +| Apple | `APPLE_` | - | [Login with Apple](/docs/guides/auth/social-login/auth-apple) | +| Azure (Microsoft) | `AZURE_` | `URL` (tenant URL) | [Login with Azure](/docs/guides/auth/social-login/auth-azure) | +| Bitbucket | `BITBUCKET_` | - | [Login with Bitbucket](/docs/guides/auth/social-login/auth-bitbucket) | +| Discord | `DISCORD_` | - | [Login with Discord](/docs/guides/auth/social-login/auth-discord) | +| Facebook | `FACEBOOK_` | - | [Login with Facebook](/docs/guides/auth/social-login/auth-facebook) | +| Figma | `FIGMA_` | - | [Login with Figma](/docs/guides/auth/social-login/auth-figma) | +| GitHub | `GITHUB_` | `URL` (for GitHub Enterprise) | [Login with GitHub](/docs/guides/auth/social-login/auth-github) | +| GitLab | `GITLAB_` | `URL` (for self-hosted GitLab) | [Login with GitLab](/docs/guides/auth/social-login/auth-gitlab) | +| Google | `GOOGLE_` | - | [Login with Google](/docs/guides/auth/social-login/auth-google) | +| Kakao | `KAKAO_` | - | [Login with Kakao](/docs/guides/auth/social-login/auth-kakao) | +| Keycloak (OIDC) | `KEYCLOAK_` | `URL` (realm URL, **required**) | [Login with Keycloak](/docs/guides/auth/social-login/auth-keycloak) | +| LinkedIn (OIDC) | `LINKEDIN_OIDC_` | - | [Login with LinkedIn](/docs/guides/auth/social-login/auth-linkedin) | +| Notion | `NOTION_` | - | [Login with Notion](/docs/guides/auth/social-login/auth-notion) | +| Slack (OIDC) | `SLACK_OIDC_` | - | [Login with Slack](/docs/guides/auth/social-login/auth-slack) | +| Snapchat | `SNAPCHAT_` | - | - | +| Spotify | `SPOTIFY_` | - | [Login with Spotify](/docs/guides/auth/social-login/auth-spotify) | +| Twitch | `TWITCH_` | - | [Login with Twitch](/docs/guides/auth/social-login/auth-twitch) | +| Twitter | `TWITTER_` | - | [Login with Twitter](/docs/guides/auth/social-login/auth-twitter) | +| WorkOS | `WORKOS_` | - | [Login with WorkOS](/docs/guides/auth/social-login/auth-workos) | +| Zoom | `ZOOM_` | - | [Login with Zoom](/docs/guides/auth/social-login/auth-zoom) | + +For each provider, you need at minimum `ENABLED`, `CLIENT_ID`, `SECRET`, and `REDIRECT_URI` in `.env` and `docker-compose.yml`. + + + +**LinkedIn (OIDC)** and **Slack (OIDC)** use multi-word env prefixes. The full Docker Compose variables are `GOTRUE_EXTERNAL_LINKEDIN_OIDC_CLIENT_ID` and `GOTRUE_EXTERNAL_SLACK_OIDC_CLIENT_ID` respectively - not `LINKEDIN_CLIENT_ID` or `SLACK_CLIENT_ID`. + + + +## Test the login flow + +You can test OAuth with the following minimal `index.html` served, e.g., via `python -m http.server 3000`: + +```html + + + +

Supabase OAuth Test

+ +

+
+    
+    
+  
+
+```
+
+
+
+Make sure `SITE_URL` or `ADDITIONAL_REDIRECT_URLS` includes the URL where you open this test page. Otherwise the Auth service will reject the redirect after login.
+
+
+
+For detailed client-side integration, see [Social Login](/docs/guides/auth/social-login).
+
+## Troubleshooting
+
+### "Provider not enabled" or provider shows `false` in `/auth/v1/settings`
+
+- Check that `GOTRUE_EXTERNAL_*_ENABLED` is set to `true` in `docker-compose.yml`
+- Verify the `.env` variable is not empty, e.g., check with `docker compose exec auth env | grep GOOGLE`
+
+### Variables added to `.env` but provider still not working
+
+Configuration variables from `.env` are **not** automatically available inside the container unless there's a matching passthrough definition in `docker-compose.yml`. Check, e.g., for:
+
+```
+GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED}
+```
+
+Run `docker compose exec auth env | grep GOTRUE_EXTERNAL` to verify the variables are reaching the container.
+
+### `SITE_URL` or redirect URL errors after login
+
+After a successful OAuth login, the Auth service redirects to `SITE_URL` or a URL from `ADDITIONAL_REDIRECT_URLS`. Ensure:
+
+- `SITE_URL` in `.env` is set to your application's URL
+- If your app uses a different redirect URL, add it to `ADDITIONAL_REDIRECT_URLS` (comma-separated)
+
+{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
+
+### Nonce check failure on mobile (Google Sign In)
+
+When using Google Sign In on mobile with ID tokens, nonce verification may fail because mobile SDKs don't always support the nonce flow that the Auth service expects.
+
+
+
+`GOTRUE_EXTERNAL_SKIP_NONCE_CHECK` disables nonce validation on ID tokens, which weakens replay-attack protection. Treat it as a **short-lived troubleshooting workaround**, not a permanent fix:
+
+- Enable it only in the environment where you're debugging the issue.
+- Revert it as soon as the issue is resolved.
+- Prefer fixing the client-side nonce handling or switching to an OAuth flow (authorization code with PKCE) that avoids ID-token nonce issues entirely.
+
+
+
+To enable it, uncomment the following line in `docker-compose.yml`:
+
+```
+GOTRUE_EXTERNAL_SKIP_NONCE_CHECK: true
+```
+
+### Auth service fails to start
+
+Check the auth container logs:
+
+```sh
+docker compose logs auth
+```
+
+Common causes:
+
+- Missing required environment variable (e.g., `CLIENT_ID` or `SECRET` is empty)
+- Invalid `API_EXTERNAL_URL` (must be a valid URL with protocol)
+
+## Environment variable reference
+
+All OAuth-related environment variables for the `auth` service in `docker-compose.yml`:
+
+| Variable                         | Description                                                              | Required |
+| -------------------------------- | ------------------------------------------------------------------------ | -------- |
+| `GOTRUE_EXTERNAL_*_ENABLED`      | Enable the provider (`true`/`false`)                                     | Yes      |
+| `GOTRUE_EXTERNAL_*_CLIENT_ID`    | OAuth client ID from the provider                                        | Yes      |
+| `GOTRUE_EXTERNAL_*_SECRET`       | OAuth client secret from the provider                                    | Yes      |
+| `GOTRUE_EXTERNAL_*_REDIRECT_URI` | Callback URL: `${API_EXTERNAL_URL}/auth/v1/callback`                     | Yes      |
+| `GOTRUE_SITE_URL`                | Default redirect URL after authentication (set via `SITE_URL` in `.env`) | Yes      |
+
+## Additional resources
+
+- [Redirect URLs](/docs/guides/auth/redirect-urls)
+- [Auth server on GitHub](https://github.com/supabase/auth) (check README and `example.env`)
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx b/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx
new file mode 100644
index 0000000000000..64d2d13ba748e
--- /dev/null
+++ b/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx
@@ -0,0 +1,222 @@
+---
+title: 'Configure Phone Login & MFA'
+description: 'Set up phone login SMS providers, OTP settings, and multi-factor authentication for self-hosted Supabase with Docker.'
+subtitle: 'Set up phone login SMS providers, OTP settings, and multi-factor authentication for self-hosted Supabase with Docker.'
+---
+
+This guide covers the **server-side configuration** for phone login and multi-factor authentication (MFA) on a self-hosted Supabase instance running with Docker Compose.
+
+For client-side implementation, see [Phone Login](/docs/guides/auth/phone-login) and [Multi-Factor Authentication](/docs/guides/auth/auth-mfa).
+
+## Before you begin
+
+You need:
+
+- A working self-hosted Supabase installation. See [Self-Hosting with Docker](/docs/guides/self-hosting/docker).
+- An account with an SMS provider (e.g., Twilio)
+
+Phone auth is **enabled by default** in the Docker setup (`ENABLE_PHONE_SIGNUP=true` in `.env`). However, without an SMS provider configured, the Auth service has no way to deliver OTP codes.
+
+## SMS provider configuration
+
+The default `.env.example` and `docker-compose.yml` include commented-out SMS provider placeholders. The example below uses Twilio - you'll need a Twilio account with an account SID, auth token, and message service SID. See [Twilio's documentation](https://www.twilio.com/docs/messaging) for how to obtain these credentials.
+
+To enable SMS delivery:
+
+### Step 1: Uncomment and configure the settings in `.env`
+
+```
+SMS_PROVIDER=twilio
+SMS_OTP_EXP=60
+SMS_OTP_LENGTH=6
+SMS_MAX_FREQUENCY=60s
+SMS_TEMPLATE=Your code is {{ .Code }}
+
+## Twilio credentials
+SMS_TWILIO_ACCOUNT_SID=your-account-sid
+SMS_TWILIO_AUTH_TOKEN=your-auth-token
+SMS_TWILIO_MESSAGE_SERVICE_SID=your-message-service-sid
+```
+
+### Step 2: Uncomment the matching lines in `docker-compose.yml`
+
+Uncomment the `GOTRUE_SMS_*` lines in the `auth` service's `environment` block:
+
+```
+GOTRUE_SMS_PROVIDER: ${SMS_PROVIDER}
+GOTRUE_SMS_OTP_EXP: ${SMS_OTP_EXP}
+GOTRUE_SMS_OTP_LENGTH: ${SMS_OTP_LENGTH}
+GOTRUE_SMS_MAX_FREQUENCY: ${SMS_MAX_FREQUENCY}
+GOTRUE_SMS_TEMPLATE: ${SMS_TEMPLATE}
+GOTRUE_SMS_TWILIO_ACCOUNT_SID: ${SMS_TWILIO_ACCOUNT_SID}
+GOTRUE_SMS_TWILIO_AUTH_TOKEN: ${SMS_TWILIO_AUTH_TOKEN}
+GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID: ${SMS_TWILIO_MESSAGE_SERVICE_SID}
+```
+
+### Step 3: Restart the auth service
+
+```sh
+docker compose up -d --force-recreate --no-deps auth
+```
+
+### Step 4: Verify
+
+```sh
+docker compose exec auth env | grep GOTRUE_SMS
+```
+
+Confirm your provider and credentials appear in the output.
+
+
+
+For providers other than Twilio, add the provider-specific `GOTRUE_SMS_*` lines manually to `docker-compose.yml`.
+
+
+
+## OTP settings
+
+### Expiration
+
+
+
+The default OTP expiration is **60 seconds**. This is often too short for production use, consider increasing it.
+
+
+
+Set `SMS_OTP_EXP` in `.env` (value is in seconds):
+
+```
+# Set expiration to 5 minutes
+SMS_OTP_EXP=300
+```
+
+And ensure `GOTRUE_SMS_OTP_EXP: ${SMS_OTP_EXP}` is uncommented in `docker-compose.yml`.
+
+### Length
+
+The default OTP length is 6 digits. You can set it to any value between 6 and 10:
+
+```
+SMS_OTP_LENGTH=8
+```
+
+### Rate limiting
+
+`SMS_MAX_FREQUENCY` controls the minimum interval between SMS sends to the same phone number. The default is 60 seconds:
+
+```
+## Allow one SMS every 30 seconds
+SMS_MAX_FREQUENCY=30s
+```
+
+## Test OTPs for development
+
+To avoid sending real SMS during development, use `SMS_TEST_OTP` to map phone numbers to fixed OTP codes:
+
+```
+SMS_TEST_OTP=16505551234:123456,16505555678:654321
+```
+
+And uncomment `GOTRUE_SMS_TEST_OTP: ${SMS_TEST_OTP}` in `docker-compose.yml`.
+
+When a test phone number requests an OTP, the Auth service skips SMS delivery and accepts only the mapped code. Other phone numbers continue to use the real SMS provider.
+
+
+
+Remove test OTPs before deploying to production. You can also set an expiration with `SMS_TEST_OTP_VALID_UNTIL` (ISO 8601 datetime, e.g., `2026-12-31T23:59:59Z`) so they stop working automatically.
+
+
+
+## Multi-factor authentication (MFA)
+
+The Auth service supports three MFA factor types. Configure them by uncommenting variables in `.env` and the matching `GOTRUE_MFA_*` lines in `docker-compose.yml`.
+
+### App authenticator (TOTP)
+
+TOTP is **enabled by default** - users can enroll with apps like Google Authenticator or Authy without any additional configuration.
+
+To disable TOTP:
+
+```
+MFA_TOTP_ENROLL_ENABLED=false
+MFA_TOTP_VERIFY_ENABLED=false
+```
+
+### Phone MFA
+
+Phone MFA is **disabled by default** (opt-in). It uses the same SMS provider configuration as phone login.
+
+To enable:
+
+```
+MFA_PHONE_ENROLL_ENABLED=true
+MFA_PHONE_VERIFY_ENABLED=true
+```
+
+### Maximum enrolled factors
+
+By default, a user can enroll up to 10 MFA factors. To change this:
+
+```
+MFA_MAX_ENROLLED_FACTORS=5
+```
+
+## Troubleshooting
+
+### OTP expires too quickly
+
+The default `SMS_OTP_EXP` is 60 seconds. Increase it in `.env`:
+
+```
+SMS_OTP_EXP=300
+```
+
+Ensure `GOTRUE_SMS_OTP_EXP: ${SMS_OTP_EXP}` is uncommented in `docker-compose.yml`, then restart:
+
+```sh
+docker compose up -d --force-recreate --no-deps auth
+```
+
+### SMS not being delivered
+
+Check the auth container logs for errors:
+
+```sh
+docker compose logs auth --tail 50
+```
+
+Verify provider credentials reach the container:
+
+```sh
+docker compose exec auth env | grep GOTRUE_SMS
+```
+
+Common causes:
+
+- Provider credentials are in `.env` but the matching `GOTRUE_SMS_*` line is still commented out in `docker-compose.yml`
+- Provider credentials are wrong
+- Phone number format is wrong (use E.164 format: `+1234567890`)
+
+### Variables are configured in `.env` but not working
+
+Configuration variables from `.env` are **not** automatically available inside the container unless there's a matching passthrough definition in `docker-compose.yml`. Check, e.g., for:
+
+```sh
+docker compose exec auth env | grep -E 'GOTRUE_SMS|GOTRUE_MFA'
+```
+
+After changing the configuration environment variables, recreate the Auth service container:
+
+```sh
+docker compose up -d --force-recreate --no-deps auth
+```
+
+### Rate limit errors
+
+If users see "rate limit exceeded" errors, check `SMS_MAX_FREQUENCY` (minimum interval between sends) and the global rate limit `GOTRUE_RATE_LIMIT_SMS_SENT` (default: 30 per hour).
+
+### Additional resources
+
+- [Multi-Factor Authentication (Phone)](/docs/guides/auth/auth-mfa/phone)
+- [Multi-Factor Authentication (TOTP)](/docs/guides/auth/auth-mfa/totp)
+- [Auth server on GitHub](https://github.com/supabase/auth) (check README and `example.env`)
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx b/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx
new file mode 100644
index 0000000000000..25723468ba838
--- /dev/null
+++ b/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx
@@ -0,0 +1,228 @@
+---
+title: 'Configure Reverse Proxy and HTTPS'
+description: 'Set up a reverse proxy with HTTPS for self-hosted Supabase.'
+subtitle: 'Set up a reverse proxy with HTTPS for self-hosted Supabase.'
+---
+
+HTTPS is required for production self-hosted Supabase deployments. This guide covers two production approaches using a reverse proxy in front of self-hosted Supabase API gateway, plus a self-signed certificate option for development environment.
+
+## Before you begin
+
+You need:
+
+- A working self-hosted Supabase installation. See [Self-Hosting with Docker](/docs/guides/self-hosting/docker).
+- A domain name with DNS pointing to your server's public IP address (to obtain Let's Encrypt certificate).
+- Ports 80 and 443 open.
+
+## Set up HTTPS
+
+Below are two options for adding a reverse proxy with automatic HTTPS in front of your self-hosted Supabase: **Caddy** (simpler, zero-config TLS) and **Nginx + Let's Encrypt** (more control over proxy settings). Both sit in front of Kong and terminate TLS, so internal traffic stays on HTTP.
+
+
+
+If you already run [HAProxy](https://www.haproxy.com/), [Traefik](https://traefik.io/), [Nginx Proxy Manager](https://nginxproxymanager.com/), or another reverse proxy for your infrastructure, you can use it instead of Caddy or Nginx above. The key requirements are:
+
+- Proxy to Kong on port `8000` (or `:8000` if the proxy runs outside the Docker network)
+- Enable WebSocket support (required for Realtime)
+- Proxy traffic to Storage directly to the container, bypassing Kong
+- Add `X-Forwarded` headers to all requests
+- Comment out Kong's host port bindings in `docker-compose.yml` if the proxy runs in the same Docker network
+- Update `SUPABASE_PUBLIC_URL`, `API_EXTERNAL_URL`, and `SITE_URL` in `.env` to your HTTPS URL
+
+
+
+### Step 1: Remove public port bindings for API gateway
+
+Comment out Kong's host port mappings in `docker-compose.yml` so that it's not exposed to the Internet:
+
+```yaml
+kong:
+  # ...
+  ports:
+    # - ${KONG_HTTP_PORT}:8000/tcp
+    # - ${KONG_HTTPS_PORT}:8443/tcp
+```
+
+Kong remains accessible to other containers on the internal Docker network.
+
+### Step 2: Update environment variables
+
+Update the URL configuration in your `.env` file to use your HTTPS domain:
+
+```
+SUPABASE_PUBLIC_URL=https://
+API_EXTERNAL_URL=https://
+SITE_URL=https://
+```
+
+Change the following to your domain name and a **valid** email address:
+
+```
+PROXY_DOMAIN=your-domain.example.com
+CERTBOT_EMAIL=admin@your-domain.example.com
+```
+
+### Step 3: Start the reverse proxy
+
+Pick one of the options below and use the corresponding Docker Compose overlay.
+
+ 
+
+[Caddy](https://caddyserver.com/) automatically provisions and renews Let's Encrypt TLS certificates with zero configuration. It also handles HTTP-to-HTTPS redirects, WebSocket upgrades, and HTTP/2 and HTTP/3 out of the box.
+
+Start Caddy by using the pre-configured `docker-compose.caddy.yml` overlay:
+
+```sh
+docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
+```
+
+Caddy configuration is in `volumes/proxy/caddy/Caddyfile`.
+
+
+
+
+This option uses a 3rd party Nginx Docker image ([`jonasal/nginx-certbot`](https://github.com/JonasAlfredsson/docker-nginx-certbot)), which includes Certbot for automatic Let's Encrypt certificate issuance and renewal in a single container.
+
+Start Nginx by using the pre-configured `docker-compose.nginx.yml` overlay:
+
+```sh
+docker compose -f docker-compose.yml -f docker-compose.nginx.yml up -d
+```
+
+Nginx configuration template is in `volumes/proxy/nginx/supabase-nginx.conf.tpl`. On container startup, `${NGINX_SERVER_NAME}` is substituted using the environment variable from the `.env` file. The [`jonasal/nginx-certbot`](https://github.com/JonasAlfredsson/docker-nginx-certbot) image reads the resolved `server_name` to determine which domain to request a Let's Encrypt certificate for.
+
+HTTP-to-HTTPS redirects are handled automatically by the `jonasal/nginx-certbot` image.
+
+
+
+
+### Step 4: Verify HTTPS connection
+
+```sh
+curl -I https:///auth/v1/
+```
+
+You should receive a `401` response confirming you could connect to Auth.
+
+## Self-signed certificates (development only)
+
+
+
+Self-signed certificates trigger browser warnings and are rejected by most OAuth providers. Use this approach only in development environment or internal networks.
+
+
+
+For development or internal networks where you cannot use Let's Encrypt, you can configure Kong to serve HTTPS directly using self-signed certificates.
+
+### Step 1: Generate a self-signed certificate
+
+Change `` in the example below, and create certificates with `openssl`:
+
+```sh
+openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+  -keyout volumes/api/server.key \
+  -out volumes/api/server.crt \
+  -subj "/CN=" && \
+  chmod 640 volumes/api/server.key && \
+  chgrp 65533 volumes/api/server.key
+```
+
+{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
+
+### Step 2: Configure Kong for SSL
+
+Comment out Kong's HTTP port mapping in `docker-compose.yml`:
+
+```yaml
+kong:
+  # ...
+  ports:
+    # - ${KONG_HTTP_PORT}:8000/tcp
+```
+
+Uncomment the certificate volume mounts and SSL environment variables in `docker-compose.yml`:
+
+```yaml
+kong:
+  # ... existing configuration ...
+  volumes:
+    - ./volumes/api/kong.yml:/home/kong/temp.yml:ro,z
+    - ./volumes/api/server.crt:/home/kong/server.crt:ro
+    - ./volumes/api/server.key:/home/kong/server.key:ro
+  environment:
+    # ... existing environment variables ...
+    KONG_SSL_CERT: /home/kong/server.crt
+    KONG_SSL_CERT_KEY: /home/kong/server.key
+```
+
+### Step 3: Update configuration variables in `.env`
+
+Edit your `.env` file to use HTTPS with the Kong HTTPS port:
+
+```
+SUPABASE_PUBLIC_URL=https://:8443
+API_EXTERNAL_URL=https://:8443
+SITE_URL=https://:8443
+```
+
+### Step 4: Restart and verify
+
+```sh
+docker compose down && docker compose up -d
+```
+
+```sh
+curl -I -k https://:8443/auth/v1/
+```
+
+The `-k` flag tells curl to accept the self-signed certificate.
+
+## Troubleshooting
+
+### Certificate not issued
+
+If Caddy or Certbot fails to obtain a certificate:
+
+- Verify that ports 80 and 443 are open on your firewall
+- Verify that your domain's DNS A record points to your server's public IP
+- Check proxy logs via `docker logs supabase-caddy` or `docker logs supabase-nginx`
+- Let's Encrypt has [rate limits](https://letsencrypt.org/docs/rate-limits/) - if you hit them, wait before retrying
+
+### WebSocket connection failed
+
+If Realtime subscriptions fail to connect:
+
+- **Caddy** handles WebSocket upgrades automatically - check that Kong is healthy
+- **Nginx** requires explicit `Upgrade` and `Connection` headers on the `/realtime/v1/` location. Verify your `nginx.conf` includes these headers as shown above
+
+### OAuth callback URL mismatch
+
+If OAuth redirects fail with a callback URL error:
+
+- Verify `API_EXTERNAL_URL` in `.env` is set to your HTTPS URL
+- Verify the callback URL registered with your OAuth provider matches `API_EXTERNAL_URL` followed by `/auth/v1/callback`
+- After changing `API_EXTERNAL_URL`, restart all services with `docker compose down && docker compose up -d`
+
+### Mixed content warnings
+
+If the browser console shows mixed content errors:
+
+- Verify `SUPABASE_PUBLIC_URL` is set to your HTTPS URL
+- Verify `SITE_URL` is also set to HTTPS
+- Clear your browser cache after making changes
+
+### ERR_CERT_AUTHORITY_INVALID
+
+This is expected when using self-signed certificates. For production, use Caddy or Nginx with Let's Encrypt. If you need to use self-signed certificates, add the certificate to your system's trust store or use a browser flag to bypass the warning.
+
+## Additional resources
+
+- [Caddy documentation](https://caddyserver.com/docs/)
+- [Nginx documentation](https://nginx.org/en/docs/) (on nginx.org)
+- [docker-nginx-certbot on GitHub](https://github.com/JonasAlfredsson/docker-nginx-certbot)
diff --git a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx
index f1273b31f1d69..13b450a398848 100644
--- a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx
+++ b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx
@@ -11,8 +11,8 @@ import NoPermission from 'components/ui/NoPermission'
 import { UpgradeToPro } from 'components/ui/UpgradeToPro'
 import { useAuthConfigQuery } from 'data/auth/auth-config-query'
 import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
+import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
 import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
-import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
 import { IS_PLATFORM } from 'lib/constants'
 import {
   Button,
@@ -83,9 +83,9 @@ export const SessionsAuthSettingsForm = () => {
     'custom_config_gotrue'
   )
 
-  const { data: organization } = useSelectedOrganizationQuery()
-  const isProPlanAndUp = organization?.plan?.id !== 'free'
-  const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp
+  const { hasAccess: hasUserSessionsEntitlement, isLoading: isLoadingEntitlements } =
+    useCheckEntitlements('auth.user_sessions')
+  const promptProPlanUpgrade = IS_PLATFORM && !hasUserSessionsEntitlement
 
   const refreshTokenForm = useForm>({
     resolver: zodResolver(RefreshTokenSchema),
@@ -182,7 +182,7 @@ export const SessionsAuthSettingsForm = () => {
     )
   }
 
-  if (isLoading) {
+  if (isLoading || isLoadingEntitlements) {
     return (
       
         
@@ -304,7 +304,7 @@ export const SessionsAuthSettingsForm = () => {
                           
                         
                       
@@ -328,7 +328,7 @@ export const SessionsAuthSettingsForm = () => {
                               type="number"
                               min={0}
                               {...field}
-                              disabled={!canUpdateConfig || !isProPlanAndUp}
+                              disabled={!canUpdateConfig || !hasUserSessionsEntitlement}
                             />
                           
                         
@@ -353,7 +353,7 @@ export const SessionsAuthSettingsForm = () => {
                               type="number"
                               {...field}
                               className="flex-1"
-                              disabled={!canUpdateConfig || !isProPlanAndUp}
+                              disabled={!canUpdateConfig || !hasUserSessionsEntitlement}
                             />
                           
                         
diff --git a/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/CPUWarnings.tsx b/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/CPUWarnings.tsx
index 70449d706c430..7c8a8328fbbf2 100644
--- a/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/CPUWarnings.tsx
+++ b/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/CPUWarnings.tsx
@@ -6,12 +6,16 @@ import { DOCS_URL } from 'lib/constants'
 import { AlertDescription_Shadcn_, Alert_Shadcn_, Button } from 'ui'
 
 interface CPUWarningsProps {
-  isFreePlan: boolean
+  hasAccessToComputeSizes: boolean
   upgradeUrl: string
   severity?: 'warning' | 'critical' | null
 }
 
-export const CPUWarnings = ({ isFreePlan, upgradeUrl, severity }: CPUWarningsProps) => {
+export const CPUWarnings = ({
+  hasAccessToComputeSizes,
+  upgradeUrl,
+  severity,
+}: CPUWarningsProps) => {
   if (severity === 'warning') {
     return (
       
@@ -28,7 +32,7 @@ export const CPUWarnings = ({ isFreePlan, upgradeUrl, severity }: CPUWarningsPro
           
           
         
@@ -52,7 +56,7 @@ export const CPUWarnings = ({ isFreePlan, upgradeUrl, severity }: CPUWarningsPro
           
           
         
diff --git a/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx b/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx
index 000e1b9257302..10a3c7844232d 100644
--- a/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx
+++ b/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/DiskIOBandwidthWarnings.tsx
@@ -4,7 +4,7 @@ import { Admonition } from 'ui-patterns'
 
 // [Joshen] In the future, conditionals should be from resource exhaustion endpoint as single source of truth
 interface DiskIOBandwidthWarningsProps {
-  isFreePlan: boolean
+  hasAccessToComputeSizes: boolean
   hasLatest: boolean
   upgradeUrl: string
   currentBillingCycleSelected: boolean
@@ -13,7 +13,7 @@ interface DiskIOBandwidthWarningsProps {
 }
 
 export const DiskIOBandwidthWarnings = ({
-  isFreePlan,
+  hasAccessToComputeSizes,
   hasLatest,
   currentBillingCycleSelected,
   upgradeUrl,
@@ -34,7 +34,7 @@ export const DiskIOBandwidthWarnings = ({
             

@@ -58,7 +58,7 @@ export const DiskIOBandwidthWarnings = ({

@@ -81,7 +81,7 @@ export const DiskIOBandwidthWarnings = ({

@@ -105,7 +105,7 @@ export const DiskIOBandwidthWarnings = ({

diff --git a/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/RAMWarnings.tsx b/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/RAMWarnings.tsx index d2099e53b2036..c2c37f79ef852 100644 --- a/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/RAMWarnings.tsx +++ b/apps/studio/components/interfaces/Billing/Usage/UsageWarningAlerts/RAMWarnings.tsx @@ -6,12 +6,16 @@ import { DOCS_URL } from 'lib/constants' import { AlertDescription_Shadcn_, Alert_Shadcn_, Button } from 'ui' interface RAMWarningsProps { - isFreePlan: boolean + hasAccessToComputeSizes: boolean upgradeUrl: string severity?: 'warning' | 'critical' | null } -export const RAMWarnings = ({ isFreePlan, upgradeUrl, severity }: RAMWarningsProps) => { +export const RAMWarnings = ({ + hasAccessToComputeSizes, + upgradeUrl, + severity, +}: RAMWarningsProps) => { if (severity === 'warning') { return ( @@ -28,7 +32,7 @@ export const RAMWarnings = ({ isFreePlan, upgradeUrl, severity }: RAMWarningsPro @@ -52,7 +56,7 @@ export const RAMWarnings = ({ isFreePlan, upgradeUrl, severity }: RAMWarningsPro diff --git a/apps/studio/components/interfaces/Organization/SecuritySettings.tsx b/apps/studio/components/interfaces/Organization/SecuritySettings.tsx index 51af92d27de89..2777bfa9a7d5e 100644 --- a/apps/studio/components/interfaces/Organization/SecuritySettings.tsx +++ b/apps/studio/components/interfaces/Organization/SecuritySettings.tsx @@ -15,8 +15,8 @@ import { useOrganizationMembersQuery } from 'data/organizations/organization-mem import { useOrganizationMfaToggleMutation } from 'data/organizations/organization-mfa-mutation' import { useOrganizationMfaQuery } from 'data/organizations/organization-mfa-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useProfile } from 'lib/profile' import { Button, @@ -41,7 +41,6 @@ const schema = z.object({ export const SecuritySettings = () => { const { slug } = useParams() const { profile } = useProfile() - const { data: selectedOrganization } = useSelectedOrganizationQuery() const { data: members } = useOrganizationMembersQuery({ slug }) const { can: canReadMfaConfig, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( @@ -54,7 +53,8 @@ export const SecuritySettings = () => { ) const { mutate: sendEvent } = useSendEventMutation() - const isPaidPlan = selectedOrganization?.plan.id !== 'free' + const { hasAccess: hasAccessToEnforceMfa, isLoading: isLoadingEntitlement } = + useCheckEntitlements('security.enforce_mfa') const { data: mfaConfig, @@ -62,7 +62,7 @@ export const SecuritySettings = () => { isPending: isLoadingMfa, isError: isErrorMfa, isSuccess: isSuccessMfa, - } = useOrganizationMfaQuery({ slug }, { enabled: isPaidPlan && canReadMfaConfig }) + } = useOrganizationMfaQuery({ slug }, { enabled: hasAccessToEnforceMfa && canReadMfaConfig }) const { mutate: toggleMfa, isPending: isUpdatingMfa } = useOrganizationMfaToggleMutation({ onError: (error) => { @@ -100,14 +100,14 @@ export const SecuritySettings = () => { members?.find((member) => member.primary_email == profile?.primary_email)?.mfa_enabled || false const onSubmit = (values: { enforceMfa: boolean }) => { - if (!slug || !isPaidPlan) return + if (!slug || !hasAccessToEnforceMfa) return toggleMfa({ slug, setEnforced: values.enforceMfa }) } return ( - {!isPaidPlan ? ( + {!hasAccessToEnforceMfa && !isLoadingEntitlement ? ( { /> ) : ( <> - {isLoadingMfa || isLoadingPermissions ? ( + {isLoadingMfa || isLoadingPermissions || isLoadingEntitlement ? ( @@ -126,11 +126,11 @@ export const SecuritySettings = () => { ) : null} - {(isErrorMfa || mfaError) && isPaidPlan && ( + {(isErrorMfa || mfaError) && hasAccessToEnforceMfa && ( )} - {isSuccessMfa && isPaidPlan && ( + {isSuccessMfa && hasAccessToEnforceMfa && (
@@ -151,7 +151,7 @@ export const SecuritySettings = () => { checked={field.value} onCheckedChange={field.onChange} disabled={ - !isPaidPlan || + !hasAccessToEnforceMfa || !canUpdateMfaConfig || !hasMFAEnabled || isUpdatingMfa @@ -181,7 +181,9 @@ export const SecuritySettings = () => { @@ -190,7 +192,7 @@ export const SecuritySettings = () => { type="primary" htmlType="submit" disabled={ - !isPaidPlan || + !hasAccessToEnforceMfa || !canUpdateMfaConfig || isUpdatingMfa || isLoadingMfa || diff --git a/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx b/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx index dbd847a86b025..01d1073c2c752 100644 --- a/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx +++ b/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx @@ -15,6 +15,7 @@ import { useOrganizationRolesV2Query } from 'data/organization-members/organizat import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query' import { useHasAccessToProjectLevelPermissions } from 'data/subscriptions/org-subscription-query' import { doPermissionsCheck, useGetPermissions } from 'hooks/misc/useCheckPermissions' +import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { DOCS_URL } from 'lib/constants' @@ -78,8 +79,7 @@ export const InviteMemberButton = () => { const { data: allRoles, isSuccess } = useOrganizationRolesV2Query({ slug }) const orgScopedRoles = allRoles?.org_scoped_roles ?? [] - const currentPlan = organization?.plan - const isFreeOrProPlan = currentPlan?.id === 'free' || currentPlan?.id === 'pro' + const { hasAccess: hasAccessToSso } = useCheckEntitlements('auth.platform.sso') const hasAccessToProjectLevelPermissions = useHasAccessToProjectLevelPermissions(slug as string) const userMemberData = members?.find((m) => m.gotrue_id === profile?.gotrue_id) @@ -308,7 +308,7 @@ export const InviteMemberButton = () => { type="note" showIcon={false} title="Single Sign-On (SSO) available" - layout={isFreeOrProPlan ? 'vertical' : 'horizontal'} + layout={!hasAccessToSso ? 'vertical' : 'horizontal'} className="rounded-none border-t-0 border-x-0 px-5" description="Enforce login via your company identity provider for added security and access control. Available on Team plan and above." actions={ @@ -318,7 +318,7 @@ export const InviteMemberButton = () => { Learn more - {isFreeOrProPlan ? ( + {!hasAccessToSso ? ( - {organization?.plan?.id === 'free' + {hasAccessToDiskSizeConfig === false ? 'Disk size configuration is not available for projects on the Free Plan' : 'Disk size configuration is only available when the spend cap has been disabled'} - {organization?.plan?.id === 'free' ? ( + {hasAccessToDiskSizeConfig === false ? (

If you are intending to use more than 500MB of disk space, then you will need to upgrade to at least the Pro Plan. @@ -191,11 +196,11 @@ Read more about [disk management](${DOCS_URL}/guides/platform/database-size#disk