Files
supabase__supabase/apps/studio/lib/gotrue.test.ts
kemal.earth 41e6d489f8 feat(studio): mcp elicitation url mode ui (#49954)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Initial build of the URL mode interstitial. Has complete mock data to be
able to view different states (this will be stripped out in the end). A
starting point for us to use as an intercept.

This is linked with @barryroodt ticket in the MCP project.
https://github.com/supabase/mcp/pull/412


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added an authenticated MCP Secrets page for securely storing API
secrets requested by AI tools.
- Added provider-aware guidance for OpenAI, Anthropic, Resend, and
Stripe keys, including validation warnings and dashboard links.
- Added clear success, cancellation, expiration, error, and
wrong-account states with account switching.
- Added loading placeholders, secret visibility controls, overwrite
warnings, and accessibility announcements.
- **Bug Fixes**
- Improved interstitial animations to respect reduced-motion
preferences.
- Preserved return destinations and related parameters during sign-in
flows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:40:36 +01:00

175 lines
5.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
buildSignUpReturnPath,
DEFAULT_FALLBACK_PATH,
DEFAULT_SIGNUP_RETURN_PATH,
getReturnToPath,
getSignUpReturnTo,
validateReturnTo,
} from './gotrue'
describe('getSignUpReturnTo', () => {
it('defaults to /new when returnTo is missing', () => {
expect(getSignUpReturnTo(undefined)).toBe(DEFAULT_SIGNUP_RETURN_PATH)
expect(getSignUpReturnTo('')).toBe(DEFAULT_SIGNUP_RETURN_PATH)
})
it('rewrites /org and /organizations to /new', () => {
expect(getSignUpReturnTo('/org')).toBe(DEFAULT_SIGNUP_RETURN_PATH)
expect(getSignUpReturnTo('/organizations')).toBe(DEFAULT_SIGNUP_RETURN_PATH)
})
it('rewrites org list paths while preserving query params', () => {
expect(getSignUpReturnTo('/org?foo=bar')).toBe('/new?foo=bar')
expect(getSignUpReturnTo('/organizations?foo=bar')).toBe('/new?foo=bar')
})
it('preserves explicit destinations including invite paths', () => {
expect(getSignUpReturnTo('/join?token=abc')).toBe('/join?token=abc')
expect(getSignUpReturnTo('/project/ref')).toBe('/project/ref')
})
it('uses the first value when returnTo is an array', () => {
expect(getSignUpReturnTo(['/join?token=abc', '/other'])).toBe('/join?token=abc')
expect(getSignUpReturnTo(['/org'])).toBe(DEFAULT_SIGNUP_RETURN_PATH)
expect(getSignUpReturnTo(['/organizations'])).toBe(DEFAULT_SIGNUP_RETURN_PATH)
})
})
describe('buildSignUpReturnPath', () => {
it('defaults to /new when returnTo is missing', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '' }
expect(buildSignUpReturnPath(undefined)).toBe(DEFAULT_SIGNUP_RETURN_PATH)
})
it('rewrites /org and /organizations to /new', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '' }
expect(buildSignUpReturnPath('/org')).toBe(DEFAULT_SIGNUP_RETURN_PATH)
expect(buildSignUpReturnPath('/organizations')).toBe(DEFAULT_SIGNUP_RETURN_PATH)
})
it('rewrites org list paths while preserving query params', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '' }
expect(buildSignUpReturnPath('/org?foo=bar')).toBe('/new?foo=bar')
expect(buildSignUpReturnPath('/organizations?foo=bar')).toBe('/new?foo=bar')
})
it('preserves invite returnTo paths', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '' }
expect(buildSignUpReturnPath('/join?token=abc')).toBe('/join?token=abc')
})
it('falls back to /new for invalid returnTo values', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '' }
expect(buildSignUpReturnPath('https://evil.com')).toBe(DEFAULT_SIGNUP_RETURN_PATH)
expect(buildSignUpReturnPath('//evil.com')).toBe(DEFAULT_SIGNUP_RETURN_PATH)
})
it('strips returnTo from the current location search params', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '?returnTo=/org&foo=bar' }
expect(buildSignUpReturnPath('/new')).toBe('/new?foo=bar')
})
it('uses the first value when returnTo is an array', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '' }
expect(buildSignUpReturnPath(['/join?token=abc', '/other'])).toBe('/join?token=abc')
})
})
describe('getReturnToPath', () => {
it('rebuilds a nested returnTo path with the params that rode alongside it', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '?ref=abc&name=OPENAI_API_KEY&returnTo=%2Fmcp%2Fsecrets' }
expect(getReturnToPath()).toBe('/mcp/secrets?ref=abc&name=OPENAI_API_KEY')
})
it('accepts a multi-segment returnTo rather than falling back', () => {
// @ts-ignore
delete window.location
// @ts-ignore
window.location = { search: '?returnTo=%2Fmcp%2Fsecrets' }
expect(getReturnToPath()).toBe('/mcp/secrets')
})
})
describe('validateReturnTo', () => {
const fallback = DEFAULT_FALLBACK_PATH
it('should return the path if it is a valid internal path', () => {
expect(validateReturnTo('/dashboard')).toBe('/dashboard')
expect(validateReturnTo('/settings/profile')).toBe('/settings/profile')
expect(validateReturnTo('/projects?id=123')).toBe('/projects?id=123')
})
it('should return fallback if given an external URL', () => {
expect(validateReturnTo('https://example.com')).toBe(fallback)
expect(validateReturnTo('http://malicious-site.com')).toBe(fallback)
expect(validateReturnTo('//evil.com')).toBe(fallback)
})
it('should return fallback for potentially malicious paths', () => {
expect(validateReturnTo('/%2e%2e/etc/passwd')).toBe(fallback)
expect(validateReturnTo('/..')).toBe(fallback)
expect(validateReturnTo('/@evil/path')).toBe(fallback)
expect(validateReturnTo('/$malicious')).toBe(fallback)
})
it('should use custom fallback when provided', () => {
const customFallback = '/custom-fallback'
expect(validateReturnTo('https://example.com', customFallback)).toBe(customFallback)
expect(validateReturnTo('/%2e%2e/etc/passwd', customFallback)).toBe(customFallback)
})
it('should handle paths with query parameters correctly', () => {
expect(validateReturnTo('/dashboard?param1=value1&param2=value2')).toBe(
'/dashboard?param1=value1&param2=value2'
)
})
it('should handle nextjs dynamic path js', () => {
expect(
validateReturnTo(
'%2F%5B%5Bx%5D%5Djavascript%3Aalert(%22H4CK3D%22)%2F%5By%5D%2F%5B%5Bx%5D%5D%2F%5By%5D%3Fx%26y'
)
).toBe(fallback)
expect(
validateReturnTo(
'/%2F%5B%5Bx%5D%5Djavascript%3Aalert(%22H4CK3D%22)%2F%5By%5D%2F%5B%5Bx%5D%5D%2F%5By%5D%3Fx%26y'
)
).toBe(fallback)
})
})