From 7df7c4eec36bf6bcb7e3e59ce99b3b146f926584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemal=20K=C4=B1l=C4=B1=C3=A7?= Date: Tue, 21 Oct 2025 15:13:30 +0300 Subject: [PATCH 1/7] feat(auth): use fetch with timeout for auth calls (#39714) --- packages/common/gotrue.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/common/gotrue.ts b/packages/common/gotrue.ts index 6ab6fb2ec72b7..caf25993d0056 100644 --- a/packages/common/gotrue.ts +++ b/packages/common/gotrue.ts @@ -175,13 +175,27 @@ async function debuggableNavigatorLock( } } +// Wrap fetch with 30-second timeout to prevent indefinite hangs +const fetchWithTimeout: typeof fetch = async (input, init) => { + const timeoutSignal = AbortSignal.timeout(30000); // 30 seconds + const existingSignal = init?.signal; + const combinedSignal = existingSignal + ? AbortSignal.any([existingSignal, timeoutSignal]) + : timeoutSignal; + + return fetch(input, { + ...init, + signal: combinedSignal, + }); +}; + export const gotrueClient = new AuthClient({ url: process.env.NEXT_PUBLIC_GOTRUE_URL, storageKey: STORAGE_KEY, detectSessionInUrl: shouldDetectSessionInUrl, debug: debug ? (persistedDebug ? logIndexedDB : true) : false, lock: navigatorLockEnabled ? debuggableNavigatorLock : undefined, - + fetch: fetchWithTimeout, ...('localStorage' in globalThis ? { storage: globalThis.localStorage, userStorage: globalThis.localStorage } : null), From 5f599a9f4fdac51b5d08992553e7b4e52c110037 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 21 Oct 2025 09:37:14 -0300 Subject: [PATCH 2/7] docs: add note regarding supporting full name when sign in with apple (#39672) * docs: general improve apple sign in guide * docs: add note regarding supporting full name when sign in with apple * add ComposeAuth to spelling list * Formatter * apply code review fixes # Conflicts: # apps/docs/content/guides/auth/social-login/auth-apple.mdx --------- Co-authored-by: Chris Chinchilla --- .../guides/auth/social-login/auth-apple.mdx | 392 ++++++++++++++++-- supa-mdx-lint/Rule003Spelling.toml | 1 + 2 files changed, 354 insertions(+), 39 deletions(-) diff --git a/apps/docs/content/guides/auth/social-login/auth-apple.mdx b/apps/docs/content/guides/auth/social-login/auth-apple.mdx index 9cf1b1ddc909d..4c343ce1b54b9 100644 --- a/apps/docs/content/guides/auth/social-login/auth-apple.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-apple.mdx @@ -22,6 +22,50 @@ In some cases you're able to use the OAuth flow within web-based native apps suc When developing with Expo, you can test Sign in with Apple via the Expo Go app, in all other cases you will need to obtain an [Apple Developer](https://developer.apple.com) account to enable the capability. + + +If you're using the OAuth flow (web, Flutter web, Kotlin non-iOS platforms), Apple requires you to generate a new secret key every 6 months using the signing key (`.p8` file). This is a critical maintenance task that will cause authentication failures if missed. + +- Set a recurring calendar reminder for every 6 months to rotate your secret key +- Store the `.p8` file securely - you'll need it for each rotation +- If you lose the `.p8` file or it's compromised, immediately revoke it in the Apple Developer Console and create a new one +- Consider automating this process if possible to prevent service disruptions + +This requirement only applies if you're configuring OAuth settings (Services ID, signing key, etc.). Native-only implementations don't require secret key rotation. + + + + + +Apple's identity token does not include the user's full name in its claims. This means the Supabase Auth server cannot automatically populate the user's name metadata when users sign in with Apple. + +- Apple only provides the user's full name during the **first sign-in attempt** (when the user initially authorizes your app) +- All subsequent sign-ins return `null` for the full name fields +- The full name must be captured from Apple's native authentication response and manually saved using the `updateUser` method + +**Recommended Approach:** +After a successful Sign in with Apple, check if the full name is available in the authentication response, and if so, use the `updateUser` method to save it to the user's metadata: + +```typescript +// Example: Handling full name after successful sign in +if (credential.fullName) { + // Full name is only provided on first sign-in + await supabase.auth.updateUser({ + data: { + full_name: `${credential.fullName.givenName} ${credential.fullName.familyName}`, + given_name: credential.fullName.givenName, + family_name: credential.fullName.familyName, + }, + }) +} +``` + +If a user revokes your app's access and then re-authorizes it, Apple will provide the full name again as if it were a first sign-in. + +The platform-specific examples below demonstrate how to implement this pattern for each SDK. + + + + + When using the OAuth flow, the user's full name is not accessible from Apple's response. Apple only provides the full name through native authentication methods (Sign in with Apple JS, or native iOS/macOS SDKs) during the first sign-in. + + If you need to collect user names, consider: + - Using Sign in with Apple JS instead (see below) + - Collecting the name through a separate onboarding form + - Using a profiles table to store user information + + + <$Partial path="oauth_pkce_flow.mdx" /> - ### Configuration [#configuration-web] + ### Configuration [#configuration-web-oauth] You will require the following information: 1. Your Apple Developer account's **Team ID**, which is an alphanumeric string of 10 characters that uniquely identifies the developer of the app. It's often accessible in the upper right-side menu on the Apple Developer Console. - 2. Register email sources for _Sign in with Apple for Email Communication_ which can be found in the [Services](https://developer.apple.com/account/resources/services/list) section of the Apple Developer Console. + 2. Register email sources for _Sign in with Apple for Email Communication_ which can be found in the [Services](https://developer.apple.com/account/resources/services/list) section of the Apple Developer Console. This enables Apple to send relay emails through your domain when users choose to hide their email addresses. 3. An **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple once you create an App ID in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._) 4. A **Services ID** which uniquely identifies the web services provided by the app you registered in the previous step. You can create a new Services ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/serviceId) section in the Apple Developer Console (use the filter menu in the upper right side to see all Services IDs). These usually are a reverse domain name string, for example `com.example.app.web`. 5. Configure Website URLs for the newly created **Services ID**. The web domain you should use is the domain your Supabase project is hosted on. This is usually `.supabase.co` while the redirect URL is `https://.supabase.co/auth/v1/callback`. - 6. Create a signing **Key** in the [Keys](https://developer.apple.com/account/resources/authkeys/list) section of the Apple Developer Console. You can use this key to generate a secret key using the tool below, which is added to your Supabase project's Auth configuration. Make sure you safely store the `AuthKey_XXXXXXXXXX.p8` file. If you ever lose access to it, or make it public accidentally, revoke it from the Apple Developer Console and create a new one immediately. You will have to generate a new secret key using this file every 6 months, so make sure you schedule a recurring meeting in your calendar! + 6. Create a signing **Key** in the [Keys](https://developer.apple.com/account/resources/authkeys/list) section of the Apple Developer Console. You can use this key to generate a secret key using the tool below, which is added to your Supabase project's Auth configuration. Make sure you safely store the `AuthKey_XXXXXXXXXX.p8` file. If you ever lose access to it, or make it public accidentally, revoke it from the Apple Developer Console and create a new one immediately. 7. Finally, add the information you configured above to the [Apple provider configuration in the Supabase dashboard](/dashboard/project/_/auth/providers). You can also configure the Apple auth provider using the Management API: @@ -100,33 +155,102 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ You can use the `signInWithIdToken()` method from the Supabase JavaScript library on the website to obtain an access and refresh token once the user has given consent using Sign in with Apple JS: ```ts - function signIn() { - const data = await AppleID.auth.signIn() - - await supabase.auth.signInWithIdToken({ - provider: 'apple', - token: data.id_token, - nonce: '', - }) + async function signIn() { + try { + // Generate a nonce for security + const nonce = crypto.randomUUID() // or use your preferred nonce generation method + + const data = await AppleID.auth.signIn() + + const { data: authData, error } = await supabase.auth.signInWithIdToken({ + provider: 'apple', + token: data.id_token, + nonce: nonce, + }) + + if (error) { + throw error + } + + // Apple only provides the user's name on the first sign-in + // The user object contains name information from Apple's response + if (data.user && data.user.name) { + const fullName = [ + data.user.name.firstName, + data.user.name.middleName, + data.user.name.lastName + ].filter(Boolean).join(' ') + + // Save the name to user metadata for future use + await supabase.auth.updateUser({ + data: { + full_name: fullName, + given_name: data.user.name.firstName, + family_name: data.user.name.lastName, + } + }) + } + } catch (error) { + console.error('Apple sign in failed:', error) + // Handle sign-in errors appropriately + } } ``` Alternatively, you can use the `AppleIDSignInOnSuccess` event with the `usePopup` option: ```ts - // Listen for authorization success. + // Generate and store nonce for verification + const nonce = crypto.randomUUID() + + // Initialize Apple ID with nonce + AppleID.auth.init({ + clientId: 'your-services-id', + scope: 'name email', + redirectURI: 'https://your-domain.com/auth/callback', + usePopup: true, + nonce: nonce, + }) + + // Listen for authorization success document.addEventListener('AppleIDSignInOnSuccess', async (event) => { - await supabase.auth.signInWithIdToken({ - provider: 'apple', - token: event.data.id_token, - nonce: '', - }) + try { + const { data: authData, error } = await supabase.auth.signInWithIdToken({ + provider: 'apple', + token: event.detail.authorization.id_token, + nonce: nonce, + }) + + if (error) { + throw error + } + + // Apple only provides the user's name on the first sign-in + if (event.detail.user && event.detail.user.name) { + const fullName = [ + event.detail.user.name.firstName, + event.detail.user.name.middleName, + event.detail.user.name.lastName + ].filter(Boolean).join(' ') + + // Save the name to user metadata for future use + await supabase.auth.updateUser({ + data: { + full_name: fullName, + given_name: event.detail.user.name.firstName, + family_name: event.detail.user.name.lastName, + } + }) + } + } catch (error) { + console.error('Apple sign in failed:', error) + } }) ``` - Make sure you request for the scope `name email` when initializing the library. + Make sure you request the scope `name email` when initializing the library, as shown in the example above. - ### Configuration [#configuration-apple-js] + ### Configuration [#configuration-web-apple-js] To use Sign in with Apple JS you need to configure these options: @@ -183,6 +307,24 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ }) console.log(JSON.stringify({ error, user }, null, 2)) if (!error) { + // Apple only provides the user's full name on the first sign-in + // Save it to user metadata if available + if (credential.fullName) { + const nameParts = [] + if (credential.fullName.givenName) nameParts.push(credential.fullName.givenName) + if (credential.fullName.middleName) nameParts.push(credential.fullName.middleName) + if (credential.fullName.familyName) nameParts.push(credential.fullName.familyName) + + const fullName = nameParts.join(' ') + + await supabase.auth.updateUser({ + data: { + full_name: fullName, + given_name: credential.fullName.givenName, + family_name: credential.fullName.familyName, + } + }) + } // User is signed in. } } else { @@ -198,13 +340,49 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ }} /> ) - return <>{/* Implement Android Auth options. */} + return ( + <> + {/* + On Android, Sign in with Apple is not natively supported. + You have two options: + 1. Use the OAuth flow via signInWithOAuth (see Flutter Android example below) + 2. Use a web-based solution like react-native-app-auth + + For most cases, we recommend using the OAuth flow: + */} + + + ) } ``` - When working with bare React Native, you can use [invertase/react-native-apple-authentication](https://github.com/invertase/react-native-apple-authentication) to obtain the ID token. + + + - Sign in with Apple is not natively available on Android devices + - The OAuth flow opens a browser window for authentication + - You must configure [deep linking](/docs/guides/auth/native-mobile-deep-linking) for the callback to work properly + - The OAuth configuration (Services ID, etc.) must be set up as described in the [Web OAuth Configuration section](#configuration-web-oauth) + + + + When working with bare React Native, you can use [invertase/react-native-apple-authentication](https://github.com/invertase/react-native-apple-authentication) to obtain the ID token on iOS. For Android, use the OAuth flow as shown above. - ### Configuration [#expo-configuration-native-app] + ### Configuration [#configuration-expo-native] @@ -231,10 +409,10 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ - ## Apple sign in on iOS and macOS + ## Sign in with Apple on iOS and macOS - You can perform Apple sign in using the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package on Flutter apps running on iOS or macOS. - Follow the instructions in the package README to set up native Apple sign in on iOS and macOS. + You can perform Sign in with Apple using the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package on Flutter apps running on iOS or macOS. + Follow the instructions in the package README to set up native Sign in with Apple on iOS and macOS. Once the setup is complete on the Flutter app, add the bundle ID of your app to your Supabase dashboard in `Authentication -> Providers -> Apple` in order to register your app with Supabase. @@ -262,22 +440,44 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ 'Could not find ID Token from generated credential.'); } - return supabase.auth.signInWithIdToken( + final authResponse = await supabase.auth.signInWithIdToken( provider: OAuthProvider.apple, idToken: idToken, nonce: rawNonce, ); + + // Apple only provides the user's full name on the first sign-in + // Save it to user metadata if available + if (credential.givenName != null || credential.familyName != null) { + final nameParts = []; + if (credential.givenName != null) nameParts.add(credential.givenName!); + if (credential.familyName != null) nameParts.add(credential.familyName!); + + final fullName = nameParts.join(' '); + + await supabase.auth.updateUser( + UserAttributes( + data: { + 'full_name': fullName, + 'given_name': credential.givenName, + 'family_name': credential.familyName, + }, + ), + ); + } + + return authResponse; } ``` - ### Configuration [#flutter-configuration-native-app] + ### Configuration [#configuration-flutter-native] 1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._) 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](/dashboard/project/_/auth/providers) under _Client IDs_. - ## Apple sign in on Android, Web, Windows and Linux + ## Sign in with Apple on Android, Web, Windows and Linux - For platforms that doesn't support native Apple sign in, you can use the `signInWithOAuth()` method to perform the Apple sign in. + For platforms that don't support native Sign in with Apple, you can use the `signInWithOAuth()` method to perform Sign in with Apple. @@ -298,7 +498,7 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ This call takes the user to Apple's consent screen. Once the flow ends, the user's profile information is exchanged and validated with Supabase Auth before it redirects back to your Flutter application with an access and refresh token representing the user's session. - ### Configuration [#flutter-configuration-web] + ### Configuration [#configuration-flutter-web] You will require the following information: @@ -352,14 +552,47 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ else { return } - try await client.auth.signInWithIdToken( + + try await client.auth.signInWithIdToken( credentials: .init( provider: .apple, idToken: idToken ) ) + + // Apple only provides the user's full name on the first sign-in + // Save it to user metadata if available + if let fullName = credential.fullName { + var nameParts: [String] = [] + if let givenName = fullName.givenName { + nameParts.append(givenName) + } + if let middleName = fullName.middleName { + nameParts.append(middleName) + } + if let familyName = fullName.familyName { + nameParts.append(familyName) + } + + let fullNameString = nameParts.joined(separator: " ") + + try await client.auth.update( + user: UserAttributes( + data: [ + "full_name": .string(fullNameString), + "given_name": .string(fullName.givenName ?? ""), + "family_name": .string(fullName.familyName ?? "") + ] + ) + ) + } + + // User successfully signed in + print("Sign in with Apple successful!") } catch { - dump(error) + // Handle sign-in errors + print("Sign in with Apple failed: \(error.localizedDescription)") + // Show error alert to user } } } @@ -368,7 +601,7 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ } ``` - ### Configuration [#swift-configuration-native-app] + ### Configuration [#configuration-swift-native] 1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._) 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](/dashboard/project/_/auth/providers) under _Client IDs_. @@ -408,12 +641,55 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ ```kotlin val authState = supabaseClient.composeAuth.rememberLoginWithApple( - onResult = { - when(it) { //handle errors - NativeSignInResult.ClosedByUser -> TODO() - is NativeSignInResult.Error -> TODO() - is NativeSignInResult.NetworkError -> TODO() - NativeSignInResult.Success -> TODO() + onResult = { result -> + when(result) { + NativeSignInResult.ClosedByUser -> { + // User cancelled the sign-in flow + println("User cancelled Apple sign in") + } + is NativeSignInResult.Error -> { + // An error occurred during sign in + println("Apple sign in error: ${result.message}") + // Show error to user + } + is NativeSignInResult.NetworkError -> { + // Network error occurred + println("Network error during Apple sign in: ${result.error}") + // Show network error to user + } + is NativeSignInResult.Success -> { + // User successfully signed in + println("Apple sign in successful!") + + // Apple only provides the user's full name on the first sign-in (iOS only) + // Save it to user metadata if available + result.data?.let { appleData -> + appleData.fullName?.let { fullName -> + val nameParts = mutableListOf() + fullName.givenName?.let { nameParts.add(it) } + fullName.middleName?.let { nameParts.add(it) } + fullName.familyName?.let { nameParts.add(it) } + + val fullNameString = nameParts.joinToString(" ") + + scope.launch { + try { + supabaseClient.auth.updateUser { + data = buildJsonObject { + put("full_name", fullNameString) + fullName.givenName?.let { put("given_name", it) } + fullName.familyName?.let { put("family_name", it) } + } + } + } catch (e: Exception) { + println("Failed to update user metadata: ${e.message}") + } + } + } + } + + // Navigate to home screen or update UI + } } } ) @@ -423,6 +699,44 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ } ``` + ### Configuration [#configuration-kotlin] + + **For iOS (native Sign in with Apple):** + + 1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. + 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](/dashboard/project/_/auth/providers) under _Client IDs_. + + **For other platforms (Android, Desktop, Web):** + + On non-iOS platforms, ComposeAuth automatically falls back to the OAuth flow. You need to configure the OAuth settings as described in the [Web OAuth Configuration section](#configuration-web-oauth) above, including: + + - Team ID + - Email sources registration + - Services ID + - Signing Key and secret generation + + **Dependencies:** + + Add the following to your `build.gradle.kts`: + + ```kotlin + dependencies { + implementation("io.github.jan-tennert.supabase:gotrue-kt:VERSION") + implementation("io.github.jan-tennert.supabase:compose-auth:VERSION") + implementation("io.github.jan-tennert.supabase:compose-auth-ui:VERSION") // Optional, for UI components + } + ``` + + + + **Platform-Specific Notes** + + - **iOS**: Uses native Apple Authentication Services automatically + - **Android/Desktop/Web**: Falls back to OAuth flow (requires web OAuth configuration) + - **Minimum Versions**: Kotlin 1.9.0+, Compose Multiplatform 1.5.0+ + + + diff --git a/supa-mdx-lint/Rule003Spelling.toml b/supa-mdx-lint/Rule003Spelling.toml index 76d95a9201060..eb8c2c207c2fc 100644 --- a/supa-mdx-lint/Rule003Spelling.toml +++ b/supa-mdx-lint/Rule003Spelling.toml @@ -36,6 +36,7 @@ allow_list = [ "[Cc]onsecutiveness", "[Cc]ooldowns?", "[Cc]oroutines?", + "ComposeAuth", "[Cc]ron", "[Cc]rypto", "[Cc]ryptography", From 541b6fc4f5eb5bcd9e712eae5186347f7ebca563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20Caba=C3=A7o?= Date: Tue, 21 Oct 2025 14:56:49 +0100 Subject: [PATCH 3/7] fix: improve realtime inspector (#39626) * Add better error descriptions when the channel fails to join * If there are no publications, database changes will be disabled and untoggled to prevent errors --- .../Inspector/RealtimeFilterPopover/index.tsx | 24 +++++++++++++++---- .../interfaces/Realtime/Inspector/index.tsx | 24 ++++++++++++++++--- .../Realtime/Inspector/useRealtimeMessages.ts | 10 ++++---- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx index 1e5ac85c02584..e001132c53529 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx @@ -1,6 +1,6 @@ import { PlusCircle } from 'lucide-react' import Link from 'next/link' -import { Dispatch, SetStateAction, useState } from 'react' +import { Dispatch, SetStateAction, useState, useEffect } from 'react' import { useParams } from 'common' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' @@ -38,6 +38,11 @@ export const RealtimeFilterPopover = ({ config, onChangeConfig }: RealtimeFilter const { data: org } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() + // Update tempConfig when config changes to ensure consistency + useEffect(() => { + setTempConfig(config) + }, [config]) + const onOpen = (v: boolean) => { // when opening, copy the outside config into the intermediate one if (v === true) { @@ -127,9 +132,15 @@ export const RealtimeFilterPopover = ({ config, onChangeConfig }: RealtimeFilter
-
@@ -137,17 +148,20 @@ export const RealtimeFilterPopover = ({ config, onChangeConfig }: RealtimeFilter id="toggle-db-changes" size="tiny" checked={tempConfig.enableDbChanges} + disabled={!config.enableDbChanges} onChange={() => setTempConfig({ ...tempConfig, enableDbChanges: !tempConfig.enableDbChanges }) } />

- Listen for Database inserts, updates, deletes and more + {config.enableDbChanges + ? 'Listen for Database inserts, updates, deletes and more' + : 'Enable realtime publications to listen for database changes'}

- {tempConfig.enableDbChanges && ( + {tempConfig.enableDbChanges && config.enableDbChanges && ( <>
Filter messages from database changes diff --git a/apps/studio/components/interfaces/Realtime/Inspector/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/index.tsx index 116ce210093a3..098da67887cb7 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/index.tsx @@ -1,10 +1,10 @@ import { useParams } from 'common' import { useState, useEffect } from 'react' -import { motion } from 'framer-motion' -import { MousePointer2 } from 'lucide-react' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useDatabasePublicationsQuery } from 'data/database-publications/database-publications-query' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' +import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Header } from './Header' import MessagesTable from './MessagesTable' import { SendMessageModal } from './SendMessageModal' @@ -17,6 +17,19 @@ import { EmptyRealtime } from './EmptyRealtime' export const RealtimeInspector = () => { const { ref } = useParams() const { data: org } = useSelectedOrganizationQuery() + const { data: project } = useSelectedProjectQuery() + + // Check if realtime publications are available + const { data: publications } = useDatabasePublicationsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + const realtimePublication = (publications ?? []).find( + (publication) => publication.name === 'supabase_realtime' + ) + const isRealtimeAvailable = + !!realtimePublication && + ((realtimePublication?.tables ?? []).length > 0 || realtimePublication?.tables === null) const [sendMessageShown, setSendMessageShown] = useState(false) const [realtimeConfig, setRealtimeConfig] = useState({ @@ -31,13 +44,18 @@ export const RealtimeInspector = () => { filter: undefined, bearer: null, enablePresence: true, - enableDbChanges: true, + enableDbChanges: isRealtimeAvailable, // Initialize based on publications availability enableBroadcast: true, }) const { mutate: sendEvent } = useSendEventMutation() const { logData, sendMessage } = useRealtimeMessages(realtimeConfig, setRealtimeConfig) + // Update enableDbChanges when publications change + useEffect(() => { + setRealtimeConfig((prev) => ({ ...prev, enableDbChanges: isRealtimeAvailable })) + }, [isRealtimeAvailable]) + return (
diff --git a/apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts b/apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts index adfff8d9a1e9b..40643867f1b20 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts +++ b/apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts @@ -168,7 +168,7 @@ export const useRealtimeMessages = ( } // Finally, subscribe to the Channel we just setup - newChannel.subscribe(async (status) => { + newChannel.subscribe(async (status, err) => { if (status === 'SUBSCRIBED') { // Let LiveView know we connected so we can update the button text // pushMessageTo('#conn_info', 'broadcast_subscribed', { host: host }) @@ -192,9 +192,11 @@ export const useRealtimeMessages = ( }) } } else if (status === 'CHANNEL_ERROR') { - toast.error( - `Failed to connect to the channel ${channelName}: This may be due to restrictive RLS policies. Check your role and try again.` - ) + if (err?.message) { + toast.error(`Failed to connect with the following error: ${err.message}`) + } else { + toast.error(`Failed to connect. Please check your RLS policies and try again.`) + } newChannel.unsubscribe() setChannel(undefined) From 31368be3c1cb648f5912f54f03a4a3c2fe24e3d2 Mon Sep 17 00:00:00 2001 From: Prashant Sridharan <914007+CoolAssPuppy@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:28:38 +0100 Subject: [PATCH 4/7] Added new case study for Rally (#39721) * Added new case study for Rally * ci: Autofix updates from GitHub workflow * chore: colour correct rally logos --------- Co-authored-by: kemal --- apps/www/_customers/rally.mdx | 138 ++++++++++++++++++ apps/www/public/customers-rss.xml | 9 +- .../blog/avatars/thiago-peres-rally.jpeg | Bin 0 -> 52112 bytes .../images/customers/logos/light/rally.png | Bin 0 -> 17702 bytes .../public/images/customers/logos/rally.png | Bin 0 -> 15493 bytes packages/common/gotrue.ts | 10 +- 6 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 apps/www/_customers/rally.mdx create mode 100644 apps/www/public/images/blog/avatars/thiago-peres-rally.jpeg create mode 100644 apps/www/public/images/customers/logos/light/rally.png create mode 100644 apps/www/public/images/customers/logos/rally.png diff --git a/apps/www/_customers/rally.mdx b/apps/www/_customers/rally.mdx new file mode 100644 index 0000000000000..5851e5fa1d670 --- /dev/null +++ b/apps/www/_customers/rally.mdx @@ -0,0 +1,138 @@ +--- +name: Rally +title: Rally builds a pan-European fleet payments platform on Supabase +# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories': +# meta_title: +description: Rally went from first line of code to fully licensed fintech in three months, processing live fleet payments across Europe. +# Use meta_description to add a custom meta description. Otherwise it defaults to {description}: +meta_description: Discover how Rally went from first line of code to fully licensed fintech in three months, processing live fleet payments across Europe. +author: prashant +author_title: Prashant Sridharan +author_url: https://github.com/CoolAssPuppy +author_image_url: https://avatars.githubusercontent.com/u/914007?v=4 +logo: /images/customers/logos/rally.png +logo_inverse: /images/customers/logos/light/rally.png +og_image: /images/customers/og/rally.jpg +tags: + - supabase +date: '2025-10-21' +company_url: https://www.getrally.com +stats: [{ stat: '3 months', label: Time to market }, { stat: '1 week', label: SOC 2 compliance }] +misc: [{ label: 'Founded', text: 'Europe' }] +about: Rally is building a financial platform for fleets across Europe, starting with a modern fuel card that lets businesses pay for fuel, tolls, parking, and EV charging. +# "healthcare" | "fintech" | "ecommerce" | "education" | "gaming" | "media" | "real-estate" | "saas" | "social" | "analytics" | "ai" | "developer-tools" +industry: ['fintech'] +# "startup" | "enterprise" | "indie_dev" +company_size: 'startup' +# "Asia" | "Europe" | "North America" | "South America" | "Africa" | "Oceania" +region: 'Europe' +# "database" | "auth" | "storage" | "realtime" | "functions" | "vector" +supabase_products: ['database', 'auth', 'storage', 'realtime'] +--- + + + We could not have built this company without Supabase. If I had to go and build all these + components myself, we wouldn't even have launched. + + +## Introduction + +[Rally](https://www.getrally.com) is building a financial platform for fleets across Europe, starting with a modern fuel card that lets businesses pay for everything from fuel to tolls, parking, and EV charging. + +Founded by former Stripe, Meta, and Booking product manager **[Thiago Peres](https://www.linkedin.com/in/thiagoperespm/)**, Rally serves medium to large European businesses with vehicles and drivers on the road, from delivery and service companies to quick-service restaurant chains. + +What began as a solo project quickly became a fully licensed fintech company processing live transactions across Europe, all built on Supabase. + +## The challenge + +[Rally](https://www.getrally.com) set out to modernize fleet payments by simplifying expense management, reducing fraud, and giving fleet managers real-time visibility into spend. + +To achieve that, they needed a backend that was robust, secure, and compliant, but also fast to build with. As a first-time builder returning to code after a decade in product management, Peres wanted to focus on the product experience rather than setting up infrastructure. + + + We're dealing with financial information. You can't afford data inconsistencies or downtime. We + needed something resilient from day one. + + +Building a fintech platform typically means orchestrating databases, file storage, auth systems, and messaging queues under strict compliance requirements. For a small team, that complexity could have made Rally's launch impossible. + +## Choosing Supabase + +When Peres began experimenting with prototypes, Supabase stood out immediately. + + + It was very developer centric. The documentation was so good that it felt like part of the + product. Everything worked together — database, storage, functions — in a cohesive way. + + +Supabase offered the power and reliability of Postgres combined with real-time APIs, Auth, Storage, and Edge Functions integrated out of the box. For a founder-CTO building solo, that cohesion made Rally possible. + +Peres also valued Supabase's open-source model, which ensured flexibility and long-term control. + + + Having seen how lock-in works elsewhere, open source gave me peace of mind. If Supabase works, + amazing, but if it didn't, I wouldn't be stuck. + + +Compared to alternatives like Firebase or traditional cloud databases, Supabase offered the right balance of scalability, transparency, and developer experience with a predictable cost structure that is critical for a fast-moving fintech startup. + +## The approach + +Rally's MVP moved from concept to production in just three months. Built entirely by Peres, the company's first banking-backed card went live to paying customers by January, just weeks after he began writing TypeScript and experimenting with Supabase. + +Supabase now powers Rally's entire backend: + +- **[Database](/database) and resilience** – Postgres serves as the source of truth for every transaction, driver, and policy rule. Features like point-in-time recovery and managed load balancing give Rally confidence in handling critical financial data. +- **[Storage](/storage)** – Rally processes thousands of invoices and receipts, leveraging Supabase Storage and image transformations to manage and display documents securely. +- **[Realtime](/realtime)** – Used in Rally's in-house messaging system, especially its WhatsApp-based DriverLink, which lets drivers submit receipts and mileage instantly. +- **[Auth](/auth)** – Manages user access across roles, from fleet managers to finance teams. + +A key Rally innovation built on Supabase is Automatch, an AI-driven system that matches any WhatsApp-submitted receipt to the right transaction automatically. + + + Supabase made it easy to build Automatch. Storage handles receipts securely, and Postgres keeps + every workflow in sync, which is critical when you're dealing with money. + + +## The results + +In less than a year, Rally scaled from prototype to production fintech serving fleets across Europe without a dedicated backend team. Supabase helped Rally move fast, stay compliant, and deliver reliability to customers who depend on uninterrupted access to funds. + +- Three months to market from first line of code to live card transactions +- Zero downtime during incidents, even during upstream AWS outages +- SOC 2 compliance in one week, supported by Supabase's infrastructure and security controls + + + One of the reasons we got our SOC 2 very, very quickly is because we were using Supabase. When all + the requirements came on top of us, and also all the laws and regulations that we had to comply + with, it was actually not very hard. We got our SOC 2 in a week. We did the work upfront to put + the safeguards in place. + + +Rally continues to expand, adding EV charging payments and analytics dashboards powered by Supabase. The next phase includes sustainability benchmarking and AI-driven insights for fleet managers. + + + Every time I think of a feature I'd love Supabase to have, it shows up a few months later. It's + become my database provider, period. + + +## Why Supabase? + +Rally's journey reflects the reasons developers and fast-moving teams choose Supabase: + +- **Faster time to market** – Supabase eliminates backend friction, enabling teams to build and ship faster +- **Integrated suite of tools** – Developers get a complete platform including Postgres, Auth, Storage, Realtime, and more +- **Developer-centric experience** – Comprehensive documentation and cohesive APIs make building intuitive +- **Built for compliance** – Infrastructure and security controls help fintech companies meet regulatory requirements +- **Open-source foundation** – Flexibility and control without vendor lock-in + + + Don't try to reinvent the wheel, especially on fintech. Security and compliance is really at the + core of every fintech. If you go with a provider like Supabase, you can leverage a lot of things + like the backups and distribution and infrastructure. That can give you a lot of peace of mind + when something goes wrong. + + +## **Ready to build and scale with Supabase?** + +Start your journey today at [www.supabase.com](/) diff --git a/apps/www/public/customers-rss.xml b/apps/www/public/customers-rss.xml index 4432958691517..395ff6b4a9317 100644 --- a/apps/www/public/customers-rss.xml +++ b/apps/www/public/customers-rss.xml @@ -5,9 +5,16 @@ https://supabase.com Latest news from Supabase en - Sun, 17 Aug 2025 00:00:00 -0700 + Tue, 21 Oct 2025 00:00:00 -0700 + https://supabase.com/customers/rally + Rally builds a pan-European fleet payments platform on Supabase + https://supabase.com/customers/rally + Rally went from first line of code to fully licensed fintech in three months, processing live fleet payments across Europe. + Tue, 21 Oct 2025 00:00:00 -0700 + + https://supabase.com/customers/soshi From Hackathon to Funded Startup: How Soshi Built an AI Social Media Manager on Supabase https://supabase.com/customers/soshi diff --git a/apps/www/public/images/blog/avatars/thiago-peres-rally.jpeg b/apps/www/public/images/blog/avatars/thiago-peres-rally.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..47add5a9e6d1fefb73ed299bedf86f54dfacb338 GIT binary patch literal 52112 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 // Wrap fetch with 30-second timeout to prevent indefinite hangs const fetchWithTimeout: typeof fetch = async (input, init) => { - const timeoutSignal = AbortSignal.timeout(30000); // 30 seconds - const existingSignal = init?.signal; + const timeoutSignal = AbortSignal.timeout(30000) // 30 seconds + const existingSignal = init?.signal const combinedSignal = existingSignal ? AbortSignal.any([existingSignal, timeoutSignal]) - : timeoutSignal; + : timeoutSignal return fetch(input, { ...init, signal: combinedSignal, - }); -}; + }) +} export const gotrueClient = new AuthClient({ url: process.env.NEXT_PUBLIC_GOTRUE_URL, From 91645ade090f1336924c440b86f44d81cb735b2d Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:08:57 -0400 Subject: [PATCH 5/7] feat(region selector): get region status by desired size (#39631) * feat(region selector): get region status by desired size The available-regions endpoint now takes a desired_instance_size query parameter to return more accurate status data. * Align call of useOrganizationAvailableRegionsQuery in [slug] with RegionSelector, and rearrange some consts --------- Co-authored-by: Joshen Lim --- .../ProjectCreation/RegionSelector.tsx | 11 +- apps/studio/data/organizations/keys.ts | 9 +- .../organization-available-regions-query.ts | 22 ++- apps/studio/pages/new/[slug].tsx | 174 +++++++++--------- packages/api-types/types/api.d.ts | 22 +++ packages/api-types/types/platform.d.ts | 21 +++ packages/common/telemetry-constants.ts | 4 +- 7 files changed, 165 insertions(+), 98 deletions(-) diff --git a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx index 9d1b038e27897..83e752106725a 100644 --- a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx +++ b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx @@ -1,9 +1,10 @@ -import { ControllerRenderProps, UseFormReturn } from 'react-hook-form' +import { ControllerRenderProps } from 'react-hook-form' import { useFlag, useParams } from 'common' import AlertError from 'components/ui/AlertError' import { useDefaultRegionQuery } from 'data/misc/get-default-region-query' import { useOrganizationAvailableRegionsQuery } from 'data/organizations/organization-available-regions-query' +import type { DesiredInstanceSize } from 'data/projects/new-project.constants' import { BASE_PATH, PROVIDERS } from 'lib/constants' import type { CloudProvider } from 'shared-data' import { @@ -27,7 +28,7 @@ import { getAvailableRegions } from './ProjectCreation.utils' interface RegionSelectorProps { cloudProvider: CloudProvider field: ControllerRenderProps - form: UseFormReturn + instanceSize?: DesiredInstanceSize layout?: 'vertical' | 'horizontal' } @@ -38,6 +39,7 @@ interface RegionSelectorProps { export const RegionSelector = ({ cloudProvider, field, + instanceSize, layout = 'horizontal', }: RegionSelectorProps) => { const { slug } = useParams() @@ -54,7 +56,10 @@ export const RegionSelector = ({ isLoading: isLoadingAvailableRegions, isError: isErrorAvailableRegions, error: errorAvailableRegions, - } = useOrganizationAvailableRegionsQuery({ slug, cloudProvider }, { enabled: smartRegionEnabled }) + } = useOrganizationAvailableRegionsQuery( + { slug, cloudProvider, desiredInstanceSize: instanceSize }, + { enabled: smartRegionEnabled, staleTime: 1000 * 60 * 5 } // 5 minutes + ) const smartRegions = availableRegionsData?.all.smartGroup ?? [] const allRegions = availableRegionsData?.all.specific ?? [] diff --git a/apps/studio/data/organizations/keys.ts b/apps/studio/data/organizations/keys.ts index a20248ab4052b..64dabc7a7f67a 100644 --- a/apps/studio/data/organizations/keys.ts +++ b/apps/studio/data/organizations/keys.ts @@ -1,3 +1,5 @@ +import type { DesiredInstanceSizeForAvailableRegions } from './organization-available-regions-query' + export const organizationKeys = { list: () => ['organizations'] as const, detail: (slug?: string) => ['organizations', slug] as const, @@ -20,6 +22,9 @@ export const organizationKeys = { ['organizations', slug, 'validate-token', token] as const, projectClaim: (slug: string, token: string) => ['organizations', slug, 'project-claim', token] as const, - availableRegions: (slug: string | undefined, cloudProvider: string) => - ['organizations', slug, 'available-regions', cloudProvider] as const, + availableRegions: ( + slug: string | undefined, + cloudProvider: string, + size?: DesiredInstanceSizeForAvailableRegions + ) => ['organizations', slug, 'available-regions', cloudProvider, size] as const, } diff --git a/apps/studio/data/organizations/organization-available-regions-query.ts b/apps/studio/data/organizations/organization-available-regions-query.ts index 01bcd9af28f14..7d037845ae965 100644 --- a/apps/studio/data/organizations/organization-available-regions-query.ts +++ b/apps/studio/data/organizations/organization-available-regions-query.ts @@ -1,16 +1,21 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' +import type { operations } from 'api-types' import { get, handleError } from 'data/fetchers' import type { ResponseError } from 'types' import { organizationKeys } from './keys' +export type DesiredInstanceSizeForAvailableRegions = + operations['v1-get-available-regions']['parameters']['query']['desired_instance_size'] + export type OrganizationAvailableRegionsVariables = { slug?: string cloudProvider: 'AWS' | 'FLY' | 'AWS_K8S' | 'AWS_NIMBUS' + desiredInstanceSize?: DesiredInstanceSizeForAvailableRegions } export async function getOrganizationAvailableRegions( - { slug, cloudProvider }: OrganizationAvailableRegionsVariables, + { slug, cloudProvider, desiredInstanceSize }: OrganizationAvailableRegionsVariables, signal?: AbortSignal ) { if (!slug) throw new Error('slug is required') @@ -20,6 +25,7 @@ export async function getOrganizationAvailableRegions( query: { cloud_provider: cloudProvider, organization_slug: slug, + desired_instance_size: desiredInstanceSize, }, }, signal, @@ -34,7 +40,7 @@ export type OrganizationAvailableRegionsData = Awaited< export type OrganizationAvailableRegionsError = ResponseError export const useOrganizationAvailableRegionsQuery = ( - { slug, cloudProvider }: OrganizationAvailableRegionsVariables, + { slug, cloudProvider, desiredInstanceSize }: OrganizationAvailableRegionsVariables, { enabled = true, ...options @@ -44,8 +50,10 @@ export const useOrganizationAvailableRegionsQuery = = {} ) => - useQuery( - organizationKeys.availableRegions(slug, cloudProvider), - ({ signal }) => getOrganizationAvailableRegions({ slug, cloudProvider }, signal), - { enabled: enabled && typeof slug !== 'undefined', ...options } - ) + useQuery({ + queryKey: organizationKeys.availableRegions(slug, cloudProvider, desiredInstanceSize), + queryFn: ({ signal }) => + getOrganizationAvailableRegions({ slug, cloudProvider, desiredInstanceSize }, signal), + enabled: enabled && typeof slug !== 'undefined', + ...options, + }) diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx index 76f66587a5868..9d4dbad820c3d 100644 --- a/apps/studio/pages/new/[slug].tsx +++ b/apps/studio/pages/new/[slug].tsx @@ -1,7 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { debounce } from 'lodash' -import { ExternalLink } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { PropsWithChildren, useEffect, useMemo, useRef, useState } from 'react' @@ -121,7 +120,7 @@ const FormSchema = z.object({ dbPass: z .string({ required_error: 'Please enter a database password.' }) .min(1, 'Password is required.'), - instanceSize: z.string(), + instanceSize: z.string().optional(), dataApi: z.boolean(), useApiSchema: z.boolean(), postgresVersionSelection: z.string(), @@ -133,17 +132,29 @@ export type CreateProjectForm = z.infer const Wizard: NextPageWithLayout = () => { const router = useRouter() const { slug, projectName } = useParams() + const defaultProvider = useDefaultProvider() + const { data: currentOrg } = useSelectedOrganizationQuery() const isFreePlan = currentOrg?.plan?.id === 'free' + const canChooseInstanceSize = !isFreePlan + const [lastVisitedOrganization] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION, '' ) + const { can: isAdmin } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') - const showAdvancedConfig = useIsFeatureEnabled('project_creation:show_advanced_config') + const smartRegionEnabled = useFlag('enableSmartRegion') + const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate') + const showPostgresVersionSelector = useFlag('showPostgresVersionSelector') + const cloudProviderEnabled = useFlag('enableFlyCloudProvider') + const showAdvancedConfig = useIsFeatureEnabled('project_creation:show_advanced_config') const { infraCloudProviders: validCloudProviders } = useCustomContent(['infra:cloud_providers']) + const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' + const isManagedByVercel = currentOrg?.managed_by === 'vercel-marketplace' + // This is to make the database.new redirect work correctly. The database.new redirect should be set to supabase.com/dashboard/new/last-visited-org if (slug === 'last-visited-org') { if (lastVisitedOrganization) { @@ -153,33 +164,69 @@ const Wizard: NextPageWithLayout = () => { } } + const [allProjects, setAllProjects] = useState(undefined) + const [passwordStrengthMessage, setPasswordStrengthMessage] = useState('') + const [passwordStrengthWarning, setPasswordStrengthWarning] = useState('') + const [isComputeCostsConfirmationModalVisible, setIsComputeCostsConfirmationModalVisible] = + useState(false) + const { mutate: sendEvent } = useSendEventMutation() - const smartRegionEnabled = useFlag('enableSmartRegion') - const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate') - const showPostgresVersionSelector = useFlag('showPostgresVersionSelector') - const cloudProviderEnabled = useFlag('enableFlyCloudProvider') + FormSchema.superRefine(({ dbPassStrength }, refinementContext) => { + if (dbPassStrength < DEFAULT_MINIMUM_PASSWORD_STRENGTH) { + refinementContext.addIssue({ + code: z.ZodIssueCode.custom, + path: ['dbPass'], + message: passwordStrengthWarning || 'Password not secure enough', + }) + } + }) + + const form = useForm>({ + resolver: zodResolver(FormSchema), + mode: 'onChange', + defaultValues: { + organization: slug, + projectName: projectName || '', + postgresVersion: '', + cloudProvider: PROVIDERS[defaultProvider].id, + dbPass: '', + dbPassStrength: 0, + dbRegion: undefined, + instanceSize: canChooseInstanceSize ? sizes[0] : undefined, + dataApi: true, + useApiSchema: false, + postgresVersionSelection: '', + useOrioleDb: false, + }, + }) + const { instanceSize: watchedInstanceSize, cloudProvider, dbRegion, organization } = form.watch() + + // [Charis] Since the form is updated in a useEffect, there is an edge case + // when switching from free to paid, where canChooseInstanceSize is true for + // an in-between render, but watchedInstanceSize is still undefined from the + // form state carried over from the free plan. To avoid this, we set a + // default instance size in this case. + const instanceSize = canChooseInstanceSize ? watchedInstanceSize ?? sizes[0] : undefined const { data: membersExceededLimit } = useFreeProjectLimitCheckQuery( { slug }, { enabled: isFreePlan } ) + const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0 + const freePlanWithExceedingLimits = isFreePlan && hasMembersExceedingFreeTierLimit + + const { data: organizations, isSuccess: isOrganizationsSuccess } = useOrganizationsQuery() + const isInvalidSlug = isOrganizationsSuccess && currentOrg === undefined + const orgNotFound = isOrganizationsSuccess && (organizations?.length ?? 0) > 0 && isInvalidSlug + const isEmptyOrganizations = (organizations?.length ?? 0) <= 0 && isOrganizationsSuccess const { data: approvedOAuthApps } = useAuthorizedAppsQuery( { slug }, { enabled: !isFreePlan && slug !== '_' } ) - const hasOAuthApps = approvedOAuthApps && approvedOAuthApps.length > 0 - const [passwordStrengthMessage, setPasswordStrengthMessage] = useState('') - const [passwordStrengthWarning, setPasswordStrengthWarning] = useState('') - - const [isComputeCostsConfirmationModalVisible, setIsComputeCostsConfirmationModalVisible] = - useState(false) - - const { data: organizations, isSuccess: isOrganizationsSuccess } = useOrganizationsQuery() - const isNotOnTeamOrEnterprisePlan = useMemo( () => !['team', 'enterprise'].includes(currentOrg?.plan.id ?? ''), [currentOrg] @@ -188,7 +235,6 @@ const Wizard: NextPageWithLayout = () => { const { data: allOverdueInvoices } = useOverdueInvoicesQuery({ enabled: isNotOnTeamOrEnterprisePlan, }) - const overdueInvoices = (allOverdueInvoices ?? []).filter( (x) => x.organization_id === currentOrg?.id ) @@ -219,14 +265,9 @@ const Wizard: NextPageWithLayout = () => { () => orgProjectsFromApi?.pages.flatMap((page) => page.projects), [orgProjectsFromApi?.pages] ) - - const [allProjects, setAllProjects] = useState(undefined) - const organizationProjects = allProjects?.filter((project) => project.status !== PROJECT_STATUS.INACTIVE) ?? [] - const defaultProvider = useDefaultProvider() - const { data: _defaultRegion, error: defaultRegionError } = useDefaultRegionQuery( { cloudProvider: PROVIDERS[defaultProvider].id, @@ -246,6 +287,7 @@ const Wizard: NextPageWithLayout = () => { { slug: slug, cloudProvider: PROVIDERS[defaultProvider].id, + desiredInstanceSize: instanceSize as DesiredInstanceSize, }, { enabled: smartRegionEnabled, @@ -255,7 +297,6 @@ const Wizard: NextPageWithLayout = () => { refetchOnReconnect: false, } ) - const regionError = smartRegionEnabled && defaultProvider !== 'AWS_NIMBUS' ? availableRegionsError @@ -267,68 +308,9 @@ const Wizard: NextPageWithLayout = () => { ? availableRegionsData?.recommendations.smartGroup.name : _defaultRegion - const { can: isAdmin } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') - - const isInvalidSlug = isOrganizationsSuccess && currentOrg === undefined - const orgNotFound = isOrganizationsSuccess && (organizations?.length ?? 0) > 0 && isInvalidSlug - const isEmptyOrganizations = (organizations?.length ?? 0) <= 0 && isOrganizationsSuccess - - const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0 - - const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' - - const freePlanWithExceedingLimits = isFreePlan && hasMembersExceedingFreeTierLimit - - const isManagedByVercel = currentOrg?.managed_by === 'vercel-marketplace' - const canCreateProject = isAdmin && !freePlanWithExceedingLimits && !isManagedByVercel && !hasOutstandingInvoices - const delayedCheckPasswordStrength = useRef( - debounce((value) => checkPasswordStrength(value), 300) - ).current - - async function checkPasswordStrength(value: any) { - const { message, warning, strength } = await passwordStrength(value) - - form.setValue('dbPassStrength', strength) - form.trigger('dbPassStrength') - form.trigger('dbPass') - - setPasswordStrengthWarning(warning) - setPasswordStrengthMessage(message) - } - - FormSchema.superRefine(({ dbPassStrength }, refinementContext) => { - if (dbPassStrength < DEFAULT_MINIMUM_PASSWORD_STRENGTH) { - refinementContext.addIssue({ - code: z.ZodIssueCode.custom, - path: ['dbPass'], - message: passwordStrengthWarning || 'Password not secure enough', - }) - } - }) - - const form = useForm>({ - resolver: zodResolver(FormSchema), - mode: 'onChange', - defaultValues: { - organization: slug, - projectName: projectName || '', - postgresVersion: '', - cloudProvider: PROVIDERS[defaultProvider].id, - dbPass: '', - dbPassStrength: 0, - dbRegion: defaultRegion || undefined, - instanceSize: sizes[0], - dataApi: true, - useApiSchema: false, - postgresVersionSelection: '', - useOrioleDb: false, - }, - }) - - const { instanceSize, cloudProvider, dbRegion, organization } = form.watch() const dbRegionExact = smartRegionToExactRegion(dbRegion) const availableOrioleVersion = useAvailableOrioleImageVersion( @@ -359,6 +341,20 @@ const Wizard: NextPageWithLayout = () => { ? 0 : instanceSizeSpecs[instanceSize as DesiredInstanceSize]!.priceMonthly - availableComputeCredits + async function checkPasswordStrength(value: any) { + const { message, warning, strength } = await passwordStrength(value) + + form.setValue('dbPassStrength', strength) + form.trigger('dbPassStrength') + form.trigger('dbPass') + + setPasswordStrengthWarning(warning) + setPasswordStrengthMessage(message) + } + const delayedCheckPasswordStrength = useRef( + debounce((value) => checkPasswordStrength(value), 300) + ).current + // [Refactor] DB Password could be a common component used in multiple pages with repeated logic function generatePassword() { const password = generateStrongPassword() @@ -479,6 +475,16 @@ const Wizard: NextPageWithLayout = () => { } }, [regionError]) + useEffect(() => { + if (watchedInstanceSize !== instanceSize) { + form.setValue('instanceSize', instanceSize, { + shouldDirty: false, + shouldValidate: false, + shouldTouch: false, + }) + } + }, [instanceSize, watchedInstanceSize, form]) + return (

@@ -747,7 +753,7 @@ const Wizard: NextPageWithLayout = () => { )} - {currentOrg?.plan && currentOrg?.plan.id !== 'free' && ( + {canChooseInstanceSize && ( { render={({ field }) => ( )} /> diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts index e43f935e651da..2f259388f73e2 100644 --- a/packages/api-types/types/api.d.ts +++ b/packages/api-types/types/api.d.ts @@ -10809,6 +10809,28 @@ export interface operations { query: { /** @description Continent code to determine regional recommendations: NA (North America), SA (South America), EU (Europe), AF (Africa), AS (Asia), OC (Oceania), AN (Antarctica) */ continent?: 'NA' | 'SA' | 'EU' | 'AF' | 'AS' | 'OC' | 'AN' + /** @description Desired instance size */ + desired_instance_size?: + | 'pico' + | 'nano' + | 'micro' + | 'small' + | 'medium' + | 'large' + | 'xlarge' + | '2xlarge' + | '4xlarge' + | '8xlarge' + | '12xlarge' + | '16xlarge' + | '24xlarge' + | '24xlarge_optimized_memory' + | '24xlarge_optimized_cpu' + | '24xlarge_high_memory' + | '48xlarge' + | '48xlarge_optimized_memory' + | '48xlarge_optimized_cpu' + | '48xlarge_high_memory' /** @description Slug of your organization */ organization_slug: string } diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index 15dde790e7c2c..335e57b6bb1d7 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -24236,6 +24236,27 @@ export interface operations { parameters: { query: { cloud_provider: 'AWS' | 'FLY' | 'AWS_K8S' | 'AWS_NIMBUS' + desired_instance_size?: + | 'pico' + | 'nano' + | 'micro' + | 'small' + | 'medium' + | 'large' + | 'xlarge' + | '2xlarge' + | '4xlarge' + | '8xlarge' + | '12xlarge' + | '16xlarge' + | '24xlarge' + | '24xlarge_optimized_memory' + | '24xlarge_optimized_cpu' + | '24xlarge_high_memory' + | '48xlarge' + | '48xlarge_optimized_memory' + | '48xlarge_optimized_cpu' + | '48xlarge_high_memory' organization_slug: string } header?: never diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index 497ec44f9de42..5d699a3d4c884 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -239,7 +239,7 @@ export interface ProjectCreationSimpleVersionSubmittedEvent { * the instance size selected in the project creation form */ properties: { - instanceSize: string + instanceSize?: string } groups: TelemetryGroups } @@ -257,7 +257,7 @@ export interface ProjectCreationSimpleVersionConfirmModalOpenedEvent { * the instance size selected in the project creation form */ properties: { - instanceSize: string + instanceSize?: string } groups: Omit } From 7646c167d61a8236a56e29ad133ca01c5cb8c02b Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Tue, 21 Oct 2025 17:20:55 +0200 Subject: [PATCH 6/7] feat: bump debug time of auth lock to 10s (#39724) --- packages/common/gotrue.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/common/gotrue.ts b/packages/common/gotrue.ts index 407b2153e9466..0d1db5f69a20f 100644 --- a/packages/common/gotrue.ts +++ b/packages/common/gotrue.ts @@ -129,7 +129,7 @@ async function debuggableNavigatorLock( let stackException: any try { - throw new Error('Lock is being held for over 2s here') + throw new Error('Lock is being held for over 10s here') } catch (e: any) { stackException = e } @@ -144,12 +144,12 @@ async function debuggableNavigatorLock( } console.error( - `Waited for over 2s to acquire an Auth client lock`, + `Waited for over 10s to acquire an Auth client lock`, await navigator.locks.query(), stackException ) })() - }, 2000) + }, 10000) try { return await navigatorLock(name, acquireTimeout, async () => { From 0c8b200ac0aae389687e1d21c7add61b7089dd67 Mon Sep 17 00:00:00 2001 From: Ali Waseem Date: Tue, 21 Oct 2025 09:59:54 -0600 Subject: [PATCH 7/7] Updated docs to use pnpm for studio (#39727) updated docs to use pnpm --- apps/studio/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/studio/README.md b/apps/studio/README.md index 8ca189e0fa76e..7f8fd0a63bb33 100644 --- a/apps/studio/README.md +++ b/apps/studio/README.md @@ -40,11 +40,11 @@ Project settings are managed outside of the Dashboard. If you use docker compose # You'll need to be on Node v20 # in /studio -npm i # install dependencies -npm run dev:secrets:pull # Supabase internal use: if you are working on the platform version of the Studio -npm run dev # start dev server -npm run test # run tests -npm run -- --watch # run tests in watch mode +pnpmn install # install dependencies +mise studio # Supabase internal use: if you are working on the platform version of the Studio +pnpm run dev # start dev server +pnpm run test # run tests +pnpm run test -- --watch # run tests in watch mode ``` ## Running within a self-hosted environment