Skip to content
Open
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
14 changes: 14 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ example to hide it in an import.
Nothing about the wire format changes. The `X-Client-Info` header still identifies this client as
`gotrue-dart`, and the `gotrue_meta_security` field in captcha payloads is unchanged.

### `AuthClient.getSSOSignInUrl` returns a `Uri`

`getSSOSignInUrl()` now returns a `Future<Uri>` rather than a `Future<String>`.

```dart
// Before
final String ssoUrl = await supabase.auth.getSSOSignInUrl(domain: 'company.com');

// After
final Uri ssoUrl = await supabase.auth.getSSOSignInUrl(domain: 'company.com');
```

If you need the URL string, use `ssoUrl.toString()`.

### `RealtimeClient.connectionState` is now typed

`RealtimeClient` used to expose the socket state twice: a typed `connState` field and a stringly
Expand Down
4 changes: 2 additions & 2 deletions packages/supabase_auth/lib/src/auth_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ class AuthClient {
///
/// If you have built an organization-specific login page, you can use the
/// organization's SSO Identity Provider UUID directly instead.
Future<String> getSSOSignInUrl({
Future<Uri> getSSOSignInUrl({
String? providerId,
String? domain,
String? redirectTo,
Expand Down Expand Up @@ -799,7 +799,7 @@ class AuthClient {
),
);

return response['url'] as String;
return Uri.parse(response['url'] as String);
}

/// Returns a new session, regardless of expiry status. Takes in an optional
Expand Down
136 changes: 136 additions & 0 deletions packages/supabase_auth/test/src/sso_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import 'dart:convert';

import 'package:http/http.dart';
import 'package:supabase_auth/supabase_auth.dart';
import 'package:test/test.dart';

import '../utils.dart';

class _CapturingMockHttpClient extends BaseClient {
_CapturingMockHttpClient({required this.responseBody});

final Map<String, dynamic> responseBody;

Request? capturedRequest;
Map<String, dynamic>? capturedBody;

@override
Future<StreamedResponse> send(BaseRequest request) async {
if (request is Request) {
capturedRequest = request;
if (request.body.isNotEmpty) {
capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
}
}
return StreamedResponse(
Stream.value(utf8.encode(jsonEncode(responseBody))),
200,
request: request,
headers: {'content-type': 'application/json'},
);
}
}

void main() {
const authUrl = 'http://localhost:54321/auth/v1';

group('getSSOSignInUrl', () {
test('returns Uri when called with providerId', () async {
final mockClient = _CapturingMockHttpClient(
responseBody: {
'url':
'https://idp.example.com/sso/saml/login?id=test-id'
'&code_challenge=xyz',
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);

final client = AuthClient(
url: authUrl,
httpClient: mockClient,
asyncStorage: TestAsyncStorage(),
flowType: AuthFlowType.implicit,
);

final uri = await client.getSSOSignInUrl(
providerId: 'b7b84310-745a-4e3a-9721-cb233b8a1c97',
);

expect(uri, isA<Uri>());
expect(
uri,
equals(
Uri.parse(
'https://idp.example.com/sso/saml/login?id=test-id'
'&code_challenge=xyz',
),
),
);
expect(uri.host, equals('idp.example.com'));
expect(uri.queryParameters['id'], equals('test-id'));
expect(
mockClient.capturedBody?['provider_id'],
equals('b7b84310-745a-4e3a-9721-cb233b8a1c97'),
);
expect(mockClient.capturedBody?['skip_http_redirect'], isTrue);
});

test(
'returns Uri when called with domain, redirectTo, and captcha',
() async {
final mockClient = _CapturingMockHttpClient(
responseBody: {
'url':
'https://idp.example.com/sso/oidc/auth?domain=company.com'
'&redirect_to=my-app%3A%2F%2Fcallback',
},
);

final client = AuthClient(
url: authUrl,
httpClient: mockClient,
asyncStorage: TestAsyncStorage(),
flowType: AuthFlowType.pkce,
);

final uri = await client.getSSOSignInUrl(
domain: 'company.com',
redirectTo: 'my-app://callback',
captchaToken: 'test-captcha-token',
);

expect(uri, isA<Uri>());
expect(uri.scheme, equals('https'));
expect(uri.host, equals('idp.example.com'));
expect(mockClient.capturedBody?['domain'], equals('company.com'));
expect(
mockClient.capturedBody?['redirect_to'],
equals('my-app://callback'),
);
expect(
mockClient.capturedBody?['gotrue_meta_security'],
equals({'captcha_token': 'test-captcha-token'}),
);
expect(mockClient.capturedBody?['code_challenge'], isNotNull);
expect(
mockClient.capturedBody?['code_challenge_method'],
equals('s256'),
);
},
);

test(
'throws AssertionError if neither providerId nor domain is provided',
() async {
final client = AuthClient(
url: authUrl,
asyncStorage: TestAsyncStorage(),
);

expect(
() => client.getSSOSignInUrl(),
throwsA(isA<AssertionError>()),
);
},
);
});
}
2 changes: 1 addition & 1 deletion packages/supabase_flutter/lib/src/supabase_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ extension AuthClientSignInProvider on AuthClient {
captchaToken: captchaToken,
);
return await launchUrl(
Uri.parse(ssoUrl),
ssoUrl,
mode: launchMode,
webOnlyWindowName: '_self',
);
Expand Down
2 changes: 1 addition & 1 deletion sdk-compliance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ features:
- AuthClientSignInProvider.generateRawNonce
auth.sign_in.sign_in_with_sso:
status: implemented
note: "Split across two methods: getSSOSignInUrl returns Future<String> (the URL) rather than the structured {url} response used by JS, and supabase_flutter's signInWithSSO launches that URL in the browser and returns Future<bool>."
note: "Split across two methods: getSSOSignInUrl returns Future<Uri> (the URL) rather than the structured {url} response used by JS, and supabase_flutter's signInWithSSO launches that URL in the browser and returns Future<bool>."
symbols:
- AuthClient.getSSOSignInUrl
- AuthClientSignInProvider.signInWithSSO
Expand Down
Loading