Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion apps/backend/src/lib/redirect-urls.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@ describe('validateRedirectUrl', () => {
expect(validateRedirectUrl('stack-auth-mobile-oauth-url://success', tenancy)).toBe(false);
expect(validateRedirectUrl('stack-auth-mobile-oauth-url://error', tenancy)).toBe(false);
expect(validateRedirectUrl('stack-auth-mobile-oauth-url://oauth-callback', tenancy)).toBe(false);
expect(validateRedirectUrl('hexclave-mobile-oauth-url://success', tenancy)).toBe(false);
expect(validateRedirectUrl('hexclave-mobile-oauth-url://error', tenancy)).toBe(false);
expect(validateRedirectUrl('hexclave-mobile-oauth-url://oauth-callback', tenancy)).toBe(false);
});

it('should not accept other custom schemes without trusted domain config', () => {
Expand All @@ -631,15 +634,23 @@ describe('validateRedirectUrl', () => {
});

describe('isAcceptedNativeAppUrl', () => {
it('should accept the native app OAuth URL scheme', () => {
it('should accept the legacy native app OAuth URL scheme', () => {
Comment thread
BilalG1 marked this conversation as resolved.
expect(isAcceptedNativeAppUrl('stack-auth-mobile-oauth-url://success')).toBe(true);
expect(isAcceptedNativeAppUrl('stack-auth-mobile-oauth-url://error')).toBe(true);
});
Comment thread
BilalG1 marked this conversation as resolved.

it('should accept the canonical Hexclave native app OAuth URL scheme', () => {
expect(isAcceptedNativeAppUrl('hexclave-mobile-oauth-url://success')).toBe(true);
expect(isAcceptedNativeAppUrl('hexclave-mobile-oauth-url://error')).toBe(true);
expect(isAcceptedNativeAppUrl('hexclave-mobile-oauth-url://oauth-callback')).toBe(true);
});

it('should reject other custom schemes', () => {
expect(isAcceptedNativeAppUrl('myapp://callback')).toBe(false);
expect(isAcceptedNativeAppUrl('stackauth-myapp://callback')).toBe(false);
expect(isAcceptedNativeAppUrl('stack-auth://callback')).toBe(false);
expect(isAcceptedNativeAppUrl('hexclave://callback')).toBe(false);
expect(isAcceptedNativeAppUrl('hexclave-mobile-oauth-url-extra://callback')).toBe(false);
expect(isAcceptedNativeAppUrl('https://example.com/callback')).toBe(false);
expect(isAcceptedNativeAppUrl('http://localhost:3000/callback')).toBe(false);
});
Expand Down
4 changes: 3 additions & 1 deletion packages/stack-shared/src/utils/redirect-urls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,9 @@ export function isAcceptedNativeAppUrl(urlOrString: string): boolean {
const url = createUrlIfValid(urlOrString);
if (!url) return false;

return url.protocol === 'stack-auth-mobile-oauth-url:';
// Legacy scheme accepted indefinitely; baked into already-shipped Swift SDK binaries.
return url.protocol === 'stack-auth-mobile-oauth-url:'
|| url.protocol === 'hexclave-mobile-oauth-url:';
}

export function validateRedirectUrl(
Expand Down
7 changes: 5 additions & 2 deletions packages/template/src/dev-tool/dev-tool-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ const DEFAULT_STATE: DevToolState = {
panelHeight: 520,
};

const STACK_LOGO_SVG = '<svg width="14" height="17" viewBox="0 0 131 156" fill="currentColor"><path d="M124.447 28.6459L70.1382 1.75616C67.3472 0.374284 64.0715 0.372197 61.279 1.75051L0.740967 31.6281V87.6369L65.7101 119.91L117.56 93.675V112.414L65.7101 138.44L0.740967 106.584V119.655C0.740967 122.359 2.28151 124.827 4.71097 126.015L62.282 154.161C65.0966 155.538 68.3938 155.515 71.1888 154.099L130.47 124.074V79.7105C130.47 74.8003 125.34 71.5769 120.915 73.7077L79.4531 93.675V75.9771L130.47 50.1589V38.3485C130.47 34.2325 128.137 30.4724 124.447 28.6459Z"/></svg>';
// Hexclave mark — hexagon outline with three radial bars, monochrome via currentColor
// so it inherits the trigger logo's color. Sourced from apps/dashboard/public/hexclave-icon.svg
// (gradient + glow stripped; this is a tiny trigger glyph, not the full brand mark).
const HEXCLAVE_LOGO_SVG = '<svg width="16" height="16" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="miter"><path d="M 24 4 L 41.32 14 L 41.32 34 L 24 44 L 6.68 34 L 6.68 14 Z"/><path d="M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z" fill="currentColor" stroke="none"/><path d="M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z" fill="currentColor" stroke="none" transform="rotate(120 24 24)"/><path d="M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z" fill="currentColor" stroke="none" transform="rotate(240 24 24)"/></svg>';

// ---------------------------------------------------------------------------
// State management
Expand Down Expand Up @@ -454,7 +457,7 @@ function createTrigger(onClick: () => void): { element: HTMLElement; cleanup: ()
title: 'Hexclave Dev Tools',
});
const logoSpan = h('span', { className: 'sdt-trigger-logo' });
setHtml(logoSpan, STACK_LOGO_SVG);
setHtml(logoSpan, HEXCLAVE_LOGO_SVG);
btn.appendChild(logoSpan);

let placement = loadPlacement() ?? { corner: 'bottom-right' as TriggerCorner };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1238,8 +1238,8 @@ class MacOSPresentationContextProvider: NSObject, ASWebAuthenticationPresentatio
struct OAuthView: View {
@Bindable var viewModel: SDKTestViewModel
@State private var provider = "google"
@State private var redirectUrl = "stack-auth-mobile-oauth-url://success"
@State private var errorRedirectUrl = "stack-auth-mobile-oauth-url://error"
@State private var redirectUrl = "hexclave-mobile-oauth-url://success"
@State private var errorRedirectUrl = "hexclave-mobile-oauth-url://error"
@State private var isSigningIn = false
private let presentationProvider = MacOSPresentationContextProvider()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1252,8 +1252,8 @@ struct ContactChannelsView: View {
struct OAuthView: View {
@Bindable var viewModel: SDKTestViewModel
@State private var provider = "google"
@State private var redirectUrl = "stack-auth-mobile-oauth-url://success"
@State private var errorRedirectUrl = "stack-auth-mobile-oauth-url://error"
@State private var redirectUrl = "hexclave-mobile-oauth-url://success"
@State private var errorRedirectUrl = "hexclave-mobile-oauth-url://error"
@State private var isSigningIn = false
private let presentationProvider = iOSPresentationContextProvider()

Expand Down
8 changes: 4 additions & 4 deletions sdks/implementations/swift/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,20 @@ Two approaches for OAuth authentication:

```swift
// Opens auth session, handles callback automatically
// Uses fixed callback scheme: stack-auth-mobile-oauth-url://
// Uses fixed callback scheme: hexclave-mobile-oauth-url://
try await stack.signInWithOAuth(provider: "google")
```

**2. Manual URL handling** - For custom implementations:

> **Note:** The `stack-auth-mobile-oauth-url://` scheme is automatically accepted.
> **Note:** The `hexclave-mobile-oauth-url://` scheme is automatically accepted (the legacy `stack-auth-mobile-oauth-url://` scheme also remains accepted for backwards compatibility).

```swift
// Get the OAuth URL (must provide absolute URLs)
let oauth = try await stack.getOAuthUrl(
provider: "google",
redirectUrl: "stack-auth-mobile-oauth-url://success",
errorRedirectUrl: "stack-auth-mobile-oauth-url://error"
redirectUrl: "hexclave-mobile-oauth-url://success",
errorRedirectUrl: "hexclave-mobile-oauth-url://error"
)

// Open oauth.url in your own browser/webview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ public actor StackClientApp {
) async throws -> OAuthUrlResult {
// Validate that URLs are absolute URLs (panic if not - these are programmer errors)
guard redirectUrl.contains("://") else {
fatalError("redirectUrl must be an absolute URL (e.g., 'stack-auth-mobile-oauth-url://success')")
fatalError("redirectUrl must be an absolute URL (e.g., 'hexclave-mobile-oauth-url://success')")
}
guard errorRedirectUrl.contains("://") else {
fatalError("errorRedirectUrl must be an absolute URL (e.g., 'stack-auth-mobile-oauth-url://error')")
fatalError("errorRedirectUrl must be an absolute URL (e.g., 'hexclave-mobile-oauth-url://error')")
}

let actualState = state ?? generateRandomString(length: 32)
Expand Down Expand Up @@ -186,7 +186,7 @@ public actor StackClientApp {
return
}

let callbackScheme = "stack-auth-mobile-oauth-url"
let callbackScheme = "hexclave-mobile-oauth-url"
let oauth = try await getOAuthUrl(
provider: provider,
redirectUrl: callbackScheme + "://success",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import Foundation
struct OAuthTests {

// Default test URLs (must be absolute URLs)
let testRedirectUrl = "stack-auth-mobile-oauth-url://success"
let testErrorRedirectUrl = "stack-auth-mobile-oauth-url://error"
let testRedirectUrl = "hexclave-mobile-oauth-url://success"
let testErrorRedirectUrl = "hexclave-mobile-oauth-url://error"

// MARK: - OAuth URL Generation Tests

Expand Down
6 changes: 3 additions & 3 deletions sdks/spec/src/apps/client-app.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Note: Additional provider scopes are configured via oauthScopesOnSignIn construc

Implementation:
1. Construct full redirect URLs using a fixed callback scheme:
- Native apps: "stack-auth-mobile-oauth-url://success" and "stack-auth-mobile-oauth-url://error"
- Native apps: "hexclave-mobile-oauth-url://success" and "hexclave-mobile-oauth-url://error"
Comment thread
vercel[bot] marked this conversation as resolved.
- Browser: Use the configured OAuth callback handler URL as redirect_uri and window.location to construct absolute URLs
- Browser: If options.returnTo is provided, pass it as afterCallbackRedirectUrl, not as redirect_uri

Expand All @@ -82,7 +82,7 @@ Implementation:

4. Open the authorization URL:
- Browser: perform redirect according to redirectMethod
- iOS/macOS: ASWebAuthenticationSession with callbackURLScheme: "stack-auth-mobile-oauth-url"
- iOS/macOS: ASWebAuthenticationSession with callbackURLScheme: "hexclave-mobile-oauth-url"
- Android: Custom Tabs with callback URL registered as deep link
- Desktop: Open system browser with registered URL scheme for callback

Expand Down Expand Up @@ -166,7 +166,7 @@ Returns: { url: string, state: string, codeVerifier: string, redirectUrl: string
redirectUrl: The redirect URL (same as input, needed for token exchange - must match exactly)

Note on URL schemes:
- The "stack-auth-mobile-oauth-url://" scheme is automatically accepted by the backend without any configuration.
- The "hexclave-mobile-oauth-url://" scheme is automatically accepted by the backend without any configuration.

Implementation:
1. Generate or use provided state and codeVerifier
Expand Down
Loading