Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(supabase)!: make Supabase credentials private in SupabaseClient #649

Merged
merged 7 commits into from
Oct 4, 2023
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
42 changes: 13 additions & 29 deletions packages/supabase/lib/src/supabase_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ import 'auth_http_client.dart';
/// `AuthFlowType.pkce`in order to perform auth actions with pkce flow.
/// {@endtemplate}
class SupabaseClient {
final String supabaseUrl;
final String supabaseKey;
final String _supabaseKey;
final PostgrestClientOptions _postgrestOptions;

final String _restUrl;
Expand Down Expand Up @@ -99,35 +98,19 @@ class SupabaseClient {
..addAll(_headers);
}

/// Creates a Supabase client to interact with your Supabase instance.
///
/// [supabaseUrl] and [supabaseKey] can be found on your Supabase dashboard.
///
/// You can access none public schema by passing different [schema].
///
/// Default headers can be overridden by specifying [headers].
///
/// Custom http client can be used by passing [httpClient] parameter.
///
/// [storageRetryAttempts] specifies how many retry attempts there should be to
/// upload a file to Supabase storage when failed due to network interruption.
///
/// [realtimeClientOptions] specifies different options you can pass to `RealtimeClient`.
///
/// Pass an instance of `YAJsonIsolate` to [isolate] to use your own persisted
/// isolate instance. A new instance will be created if [isolate] is omitted.
Comment on lines -102 to -118
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This chunk of comment is included in the macro below.

/// {@macro supabase_client}
SupabaseClient(
this.supabaseUrl,
this.supabaseKey, {
String supabaseUrl,
String supabaseKey, {
PostgrestClientOptions postgrestOptions = const PostgrestClientOptions(),
AuthClientOptions authOptions = const AuthClientOptions(),
StorageClientOptions storageOptions = const StorageClientOptions(),
RealtimeClientOptions realtimeClientOptions = const RealtimeClientOptions(),
Map<String, String>? headers,
Client? httpClient,
YAJsonIsolate? isolate,
}) : _restUrl = '$supabaseUrl/rest/v1',
}) : _supabaseKey = supabaseKey,
_restUrl = '$supabaseUrl/rest/v1',
_realtimeUrl = '$supabaseUrl/realtime/v1'.replaceAll('http', 'ws'),
_authUrl = '$supabaseUrl/auth/v1',
_storageUrl = '$supabaseUrl/storage/v1',
Expand All @@ -144,7 +127,8 @@ class SupabaseClient {
gotrueAsyncStorage: authOptions.pkceAsyncStorage,
authFlowType: authOptions.authFlowType,
);
_authHttpClient = AuthHttpClient(supabaseKey, httpClient ?? Client(), auth);
_authHttpClient =
AuthHttpClient(_supabaseKey, httpClient ?? Client(), auth);
rest = _initRestClient();
functions = _initFunctionsClient();
storage = _initStorageClient(storageOptions.retryAttempts);
Expand Down Expand Up @@ -218,8 +202,8 @@ class SupabaseClient {
required AuthFlowType authFlowType,
}) {
final authHeaders = {...headers};
authHeaders['apikey'] = supabaseKey;
authHeaders['Authorization'] = 'Bearer $supabaseKey';
authHeaders['apikey'] = _supabaseKey;
authHeaders['Authorization'] = 'Bearer $_supabaseKey';

return GoTrueClient(
url: _authUrl,
Expand Down Expand Up @@ -266,7 +250,7 @@ class SupabaseClient {
return RealtimeClient(
_realtimeUrl,
params: {
'apikey': supabaseKey,
'apikey': _supabaseKey,
if (eventsPerSecond != null) 'eventsPerSecond': '$eventsPerSecond'
},
headers: headers,
Expand All @@ -275,9 +259,9 @@ class SupabaseClient {
}

Map<String, String> _getAuthHeaders() {
final authBearer = auth.currentSession?.accessToken ?? supabaseKey;
final authBearer = auth.currentSession?.accessToken ?? _supabaseKey;
final defaultHeaders = {
'apikey': supabaseKey,
'apikey': _supabaseKey,
'Authorization': 'Bearer $authBearer',
};
final headers = {...defaultHeaders, ..._headers};
Expand Down Expand Up @@ -305,7 +289,7 @@ class SupabaseClient {
event == AuthChangeEvent.userDeleted) {
// Token is removed

realtime.setAuth(supabaseKey);
realtime.setAuth(_supabaseKey);
}
}
}
78 changes: 66 additions & 12 deletions packages/supabase/test/client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,44 @@ import 'package:test/test.dart';
import 'utils.dart';

void main() {
/// Extracts a single request sent to the realtime server
Future<HttpRequest> getRealtimeRequest({
required HttpServer server,
required SupabaseClient supabaseClient,
}) async {
supabaseClient.channel('name').subscribe();

return server.first;
}

group('Standard Header', () {
const supabaseUrl = 'https://nlbsnpoablmsiwndbmer.supabase.co';
late String supabaseUrl;
const supabaseKey =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im53emxkenlsb2pyemdqemloZHJrIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODQxMzI2ODAsImV4cCI6MTk5OTcwODY4MH0.MU-LVeAPic93VLcRsHktxzYtBKBUMWAQb8E-0AQETPs';
late SupabaseClient client;
late HttpServer mockServer;

setUp(() async {
mockServer = await HttpServer.bind('localhost', 0);
supabaseUrl = 'http://${mockServer.address.host}:${mockServer.port}';

setUp(() {
client = SupabaseClient(supabaseUrl, supabaseKey);
});

tearDown(() async {
await client.removeAllChannels();
await client.dispose();
});

test('X-Client-Info header is set properly on realtime', () {
test('X-Client-Info header is set properly on realtime', () async {
final request = await getRealtimeRequest(
server: mockServer,
supabaseClient: client,
);

final xClientHeaderBeforeSlash =
client.realtime.headers['X-Client-Info']!.split('/').first;
request.headers['X-Client-Info']?.first.split('/').first;

expect(xClientHeaderBeforeSlash, 'supabase-dart');
});

Expand All @@ -32,19 +53,33 @@ void main() {
expect(xClientHeaderBeforeSlash, 'supabase-dart');
});

test('realtime URL is properly being set', () {
var realtimeWebsocketURL = Uri.parse(client.realtime.endPointURL);
test('realtime URL is properly being set', () async {
final request = await getRealtimeRequest(
server: mockServer,
supabaseClient: client,
);

var realtimeWebsocketURL = request.uri;

expect(
realtimeWebsocketURL.queryParameters,
containsPair('apikey', supabaseKey),
);
expect(realtimeWebsocketURL.queryParameters['log_level'], isNull);
});

test('log_level query parameter is properly set', () async {
client = SupabaseClient(supabaseUrl, supabaseKey,
realtimeClientOptions:
RealtimeClientOptions(logLevel: RealtimeLogLevel.info));

realtimeWebsocketURL = Uri.parse(client.realtime.endPointURL);
final request = await getRealtimeRequest(
server: mockServer,
supabaseClient: client,
);

final realtimeWebsocketURL = request.uri;

expect(
realtimeWebsocketURL.queryParameters,
containsPair('apikey', supabaseKey),
Expand All @@ -55,8 +90,13 @@ void main() {
);
});

test('realtime access token is set properly', () {
expect(client.realtime.accessToken, supabaseKey);
test('realtime access token is set properly', () async {
final request = await getRealtimeRequest(
server: mockServer,
supabaseClient: client,
);

expect(request.uri.queryParameters['apikey'], supabaseKey);
});
});

Expand Down Expand Up @@ -163,9 +203,23 @@ void main() {
);
});

test('X-Client-Info header is set properly on realtime', () {
final xClientInfoHeader = client.realtime.headers['X-Client-Info'];
expect(xClientInfoHeader, 'supabase-flutter/0.0.0');
test('X-Client-Info header is set properly on realtime', () async {
final mockServer = await HttpServer.bind('localhost', 0);

final client = SupabaseClient(
'http://${mockServer.address.host}:${mockServer.port}',
supabaseKey,
headers: {
'X-Client-Info': 'supabase-flutter/0.0.0',
},
);

final request = await getRealtimeRequest(
server: mockServer,
supabaseClient: client,
);

expect(request.headers['X-Client-Info']?.first, 'supabase-flutter/0.0.0');
});

test('X-Client-Info header is set properly on storage', () {
Expand Down
4 changes: 2 additions & 2 deletions packages/supabase/test/mock_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ void main() {
await customHeadersClient.dispose();

//Manually disconnect the socket channel to avoid automatic retrying to reconnect. This caused failing in later executed tests.
client.realtime.disconnect();
customHeadersClient.realtime.disconnect();
await client.removeAllChannels();
await customHeadersClient.removeAllChannels();

// Wait for the realtime updates to come through
await Future.delayed(Duration(milliseconds: 100));
Expand Down