Skip to content

Commit

Permalink
feat(gotrue): Add signInAnonymously() method (#883)
Browse files Browse the repository at this point in the history
* align _callRefreshSession

* complete fetch rework

* Update the methods on gotrue-dart

* update the app lifecycle change listener

* Complete ticking behavior except tests

* minor comment update

* update the behavior when the SDK failed to refresh the token when making request

* adjust gotrue tests

* fix supabase test

* fix supabase_flutter tests

* Fix supabase-flutter test

* stop autorefresh timer within widget test

* Update packages/gotrue/lib/src/constants.dart

Co-authored-by: Vinzent <vinzent03@proton.me>

* Update packages/gotrue/lib/src/gotrue_client.dart

Co-authored-by: Vinzent <vinzent03@proton.me>

* Update packages/gotrue/lib/src/gotrue_client.dart

Co-authored-by: Vinzent <vinzent03@proton.me>

* Update packages/gotrue/lib/src/gotrue_client.dart

Co-authored-by: Vinzent <vinzent03@proton.me>

* Update packages/gotrue/lib/src/gotrue_client.dart

* Update packages/gotrue/lib/src/gotrue_client.dart

* minor refactor within _autoRefreshTokenTick

* rename errors to exceptions

* feat: Add anonymous sign-in

* Add the isAnonymous flag to the user object

* Add tests

* Update sign out code to ignore 403

* remove unnecessary code

* remove try catch block from signInAnonymously

* add check to see if anonnymous users have isAnonymous set to true

---------

Co-authored-by: Vinzent <vinzent03@proton.me>
  • Loading branch information
dshukertjr and Vinzent03 committed Apr 9, 2024
1 parent 9993168 commit 2e63613
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 7 deletions.
3 changes: 2 additions & 1 deletion infra/gotrue/docker-compose.yml
Expand Up @@ -2,7 +2,7 @@
version: '3'
services:
gotrue: # Signup enabled, autoconfirm on
image: supabase/gotrue:v2.129.0
image: supabase/gotrue:v2.146.0
ports:
- '9998:9998'
environment:
Expand All @@ -26,6 +26,7 @@ services:
GOTRUE_EXTERNAL_GOOGLE_SECRET: Sm3s8RE85rDcS36iMy8YjrpC
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: http://localhost:9998/callback
GOTRUE_SECURITY_MANUAL_LINKING_ENABLED: 'true'
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: 'true'

depends_on:
- db
Expand Down
38 changes: 37 additions & 1 deletion packages/gotrue/lib/src/gotrue_client.dart
Expand Up @@ -132,6 +132,39 @@ class GoTrueClient {
/// Returns the current session, if any;
Session? get currentSession => _currentSession;

/// Creates a new anonymous user.
///
/// Returns An `AuthResponse` with a session where the `is_anonymous` claim
/// in the access token JWT is set to true
Future<AuthResponse> signInAnonymously({
Map<String, dynamic>? data,
String? captchaToken,
}) async {
_removeSession();

final response = await _fetch.request(
'$_url/signup',
RequestMethodType.post,
options: GotrueRequestOptions(
headers: _headers,
body: {
'data': data ?? {},
'gotrue_meta_security': {'captcha_token': captchaToken},
},
),
);

final authResponse = AuthResponse.fromJson(response);

final session = authResponse.session;
if (session != null) {
_saveSession(session);
notifyAllSubscribers(AuthChangeEvent.signedIn);
}

return authResponse;
}

/// Creates a new user.
///
/// Be aware that if a user account exists in the system you may get back an
Expand Down Expand Up @@ -823,8 +856,11 @@ class GoTrueClient {
await admin.signOut(accessToken, scope: scope);
} on AuthException catch (error) {
// ignore 401s since an invalid or expired JWT should sign out the current session
// ignore 403s since user might not exist anymore
// ignore 404s since user might not exist anymore
if (error.statusCode != '401' && error.statusCode != '404') {
if (error.statusCode != '401' &&
error.statusCode != '403' &&
error.statusCode != '404') {
rethrow;
}
}
Expand Down
12 changes: 9 additions & 3 deletions packages/gotrue/lib/src/types/user.dart
Expand Up @@ -26,6 +26,7 @@ class User {
final String? updatedAt;
final List<UserIdentity>? identities;
final List<Factor>? factors;
final bool isAnonymous;

const User({
required this.id,
Expand All @@ -49,6 +50,7 @@ class User {
this.updatedAt,
this.identities,
this.factors,
this.isAnonymous = false,
});

/// Returns a `User` object from a map of json
Expand Down Expand Up @@ -85,6 +87,7 @@ class User {
factors: json['factors'] != null
? List<Factor>.from(json['factors']?.map((x) => Factor.fromJson(x)))
: null,
isAnonymous: json['is_anonymous'] ?? false,
);
}

Expand All @@ -111,12 +114,13 @@ class User {
'updated_at': updatedAt,
'identities': identities?.map((identity) => identity.toJson()).toList(),
'factors': factors?.map((factor) => factor.toJson()).toList(),
'is_anonymous': isAnonymous,
};
}

@override
String toString() {
return 'User(id: $id, appMetadata: $appMetadata, userMetadata: $userMetadata, aud: $aud, confirmationSentAt: $confirmationSentAt, recoverySentAt: $recoverySentAt, emailChangeSentAt: $emailChangeSentAt, newEmail: $newEmail, invitedAt: $invitedAt, actionLink: $actionLink, email: $email, phone: $phone, createdAt: $createdAt, confirmedAt: $confirmedAt, emailConfirmedAt: $emailConfirmedAt, phoneConfirmedAt: $phoneConfirmedAt, lastSignInAt: $lastSignInAt, role: $role, updatedAt: $updatedAt, identities: $identities, factors: $factors)';
return 'User(id: $id, appMetadata: $appMetadata, userMetadata: $userMetadata, aud: $aud, confirmationSentAt: $confirmationSentAt, recoverySentAt: $recoverySentAt, emailChangeSentAt: $emailChangeSentAt, newEmail: $newEmail, invitedAt: $invitedAt, actionLink: $actionLink, email: $email, phone: $phone, createdAt: $createdAt, confirmedAt: $confirmedAt, emailConfirmedAt: $emailConfirmedAt, phoneConfirmedAt: $phoneConfirmedAt, lastSignInAt: $lastSignInAt, role: $role, updatedAt: $updatedAt, identities: $identities, factors: $factors, isAnonymous: $isAnonymous)';
}

@override
Expand Down Expand Up @@ -145,7 +149,8 @@ class User {
other.role == role &&
other.updatedAt == updatedAt &&
collectionEquals(other.identities, identities) &&
collectionEquals(other.factors, factors);
collectionEquals(other.factors, factors) &&
other.isAnonymous == isAnonymous;
}

@override
Expand All @@ -170,7 +175,8 @@ class User {
role.hashCode ^
updatedAt.hashCode ^
identities.hashCode ^
factors.hashCode;
factors.hashCode ^
isAnonymous.hashCode;
}
}

Expand Down
13 changes: 11 additions & 2 deletions packages/gotrue/test/client_test.dart
Expand Up @@ -80,6 +80,15 @@ void main() {
);
});

test('anonymous sign-in', () async {
final response = await client.signInAnonymously(
data: {'Hello': 'World'},
);
expect(response.session?.accessToken, isA<String>());
expect(response.user?.isAnonymous, isTrue);
expect(response.user?.userMetadata, {'Hello': 'World'});
});

test('signUp() with email', () async {
final response = await client.signUp(
email: newEmail,
Expand All @@ -91,7 +100,7 @@ void main() {
expect(data?.accessToken, isA<String>());
expect(data?.refreshToken, isA<String>());
expect(data?.user.id, isA<String>());
expect(data?.user.userMetadata, {'Hello': 'World'});
expect(data?.user.userMetadata!['Hello'], 'World');
});

test('Parsing invalid URL should throw', () async {
Expand Down Expand Up @@ -153,7 +162,7 @@ void main() {
expect(data?.accessToken, isA<String>());
expect(data?.refreshToken, isA<String>());
expect(data?.user.id, isA<String>());
expect(data?.user.userMetadata, {'Hello': 'World'});
expect(data?.user.userMetadata!['Hello'], 'World');
});

test('signUp() with autoConfirm off with email', () async {
Expand Down

0 comments on commit 2e63613

Please sign in to comment.