supabase_auth_service.dart 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import 'dart:async';
  2. import 'package:appflowy/env/env.dart';
  3. import 'package:appflowy/user/application/auth/appflowy_auth_service.dart';
  4. import 'package:appflowy/user/application/auth/auth_service.dart';
  5. import 'package:appflowy/user/application/user_service.dart';
  6. import 'package:appflowy_backend/dispatch/dispatch.dart';
  7. import 'package:appflowy_backend/log.dart';
  8. import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
  9. import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
  10. import 'package:dartz/dartz.dart';
  11. import 'package:supabase_flutter/supabase_flutter.dart';
  12. import 'auth_error.dart';
  13. class SupabaseAuthService implements AuthService {
  14. SupabaseAuthService();
  15. SupabaseClient get _client => Supabase.instance.client;
  16. GoTrueClient get _auth => _client.auth;
  17. final AppFlowyAuthService _appFlowyAuthService = AppFlowyAuthService();
  18. @override
  19. Future<Either<FlowyError, UserProfilePB>> signUp({
  20. required String name,
  21. required String email,
  22. required String password,
  23. AuthTypePB authType = AuthTypePB.Supabase,
  24. Map<String, String> map = const {},
  25. }) async {
  26. if (!isSupabaseEnable) {
  27. return _appFlowyAuthService.signUp(
  28. name: name,
  29. email: email,
  30. password: password,
  31. );
  32. }
  33. // fetch the uuid from supabase.
  34. final response = await _auth.signUp(
  35. email: email,
  36. password: password,
  37. );
  38. final uuid = response.user?.id;
  39. if (uuid == null) {
  40. return left(AuthError.supabaseSignUpError);
  41. }
  42. // assign the uuid to our backend service.
  43. // and will transfer this logic to backend later.
  44. return _appFlowyAuthService.signUp(
  45. name: name,
  46. email: email,
  47. password: password,
  48. authType: authType,
  49. map: {
  50. AuthServiceMapKeys.uuid: uuid,
  51. },
  52. );
  53. }
  54. @override
  55. Future<Either<FlowyError, UserProfilePB>> signIn({
  56. required String email,
  57. required String password,
  58. AuthTypePB authType = AuthTypePB.Supabase,
  59. Map<String, String> map = const {},
  60. }) async {
  61. if (!isSupabaseEnable) {
  62. return _appFlowyAuthService.signIn(
  63. email: email,
  64. password: password,
  65. );
  66. }
  67. try {
  68. final response = await _auth.signInWithPassword(
  69. email: email,
  70. password: password,
  71. );
  72. final uuid = response.user?.id;
  73. if (uuid == null) {
  74. return Left(AuthError.supabaseSignInError);
  75. }
  76. return _appFlowyAuthService.signIn(
  77. email: email,
  78. password: password,
  79. authType: authType,
  80. map: {
  81. AuthServiceMapKeys.uuid: uuid,
  82. },
  83. );
  84. } on AuthException catch (e) {
  85. Log.error(e);
  86. return Left(AuthError.supabaseSignInError);
  87. }
  88. }
  89. @override
  90. Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
  91. required String platform,
  92. AuthTypePB authType = AuthTypePB.Supabase,
  93. Map<String, String> map = const {},
  94. }) async {
  95. if (!isSupabaseEnable) {
  96. return _appFlowyAuthService.signUpWithOAuth(
  97. platform: platform,
  98. );
  99. }
  100. final provider = platform.toProvider();
  101. final completer = Completer<Either<FlowyError, UserProfilePB>>();
  102. late final StreamSubscription<AuthState> subscription;
  103. subscription = _auth.onAuthStateChange.listen((event) async {
  104. final user = event.session?.user;
  105. if (event.event != AuthChangeEvent.signedIn || user == null) {
  106. completer.complete(left(AuthError.supabaseSignInWithOauthError));
  107. } else {
  108. final Either<FlowyError, UserProfilePB> response = await setupAuth(
  109. map: {AuthServiceMapKeys.uuid: user.id},
  110. );
  111. completer.complete(response);
  112. }
  113. subscription.cancel();
  114. });
  115. final Map<String, String> query = {};
  116. if (provider == Provider.google) {
  117. query['access_type'] = 'offline';
  118. query['prompt'] = 'consent';
  119. }
  120. final response = await _auth.signInWithOAuth(
  121. provider,
  122. queryParams: query,
  123. redirectTo:
  124. 'io.appflowy.appflowy-flutter://login-callback', // can't use underscore here.
  125. );
  126. if (!response) {
  127. completer.complete(left(AuthError.supabaseSignInWithOauthError));
  128. }
  129. return completer.future;
  130. }
  131. @override
  132. Future<void> signOut({
  133. AuthTypePB authType = AuthTypePB.Supabase,
  134. }) async {
  135. if (!isSupabaseEnable) {
  136. return _appFlowyAuthService.signOut();
  137. }
  138. await _auth.signOut();
  139. await _appFlowyAuthService.signOut(
  140. authType: authType,
  141. );
  142. }
  143. @override
  144. Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
  145. AuthTypePB authType = AuthTypePB.Supabase,
  146. Map<String, String> map = const {},
  147. }) async {
  148. // supabase don't support guest login.
  149. // so, just forward to our backend.
  150. return _appFlowyAuthService.signUpAsGuest();
  151. }
  152. // @override
  153. // Future<Either<FlowyError, UserProfilePB>> getUser() async {
  154. // final loginType = await getIt<KeyValueStorage>()
  155. // .get(KVKeys.loginType)
  156. // .then((value) => value.toOption().toNullable());
  157. // if (!isSupabaseEnable || (loginType != null && loginType != 'supabase')) {
  158. // return _appFlowyAuthService.getUser();
  159. // }
  160. // final user = await getSupabaseUser();
  161. // return user.map((r) => r.toUserProfile());
  162. // }
  163. @override
  164. Future<Either<FlowyError, UserProfilePB>> getUser() async {
  165. return UserBackendService.getCurrentUserProfile();
  166. }
  167. Future<Either<FlowyError, User>> getSupabaseUser() async {
  168. final user = _auth.currentUser;
  169. if (user == null) {
  170. return left(AuthError.supabaseGetUserError);
  171. }
  172. return Right(user);
  173. }
  174. Future<Either<FlowyError, UserProfilePB>> setupAuth({
  175. required Map<String, String> map,
  176. }) async {
  177. final payload = ThirdPartyAuthPB(
  178. authType: AuthTypePB.Supabase,
  179. map: map,
  180. );
  181. return UserEventThirdPartyAuth(payload)
  182. .send()
  183. .then((value) => value.swap());
  184. }
  185. }
  186. extension on String {
  187. Provider toProvider() {
  188. switch (this) {
  189. case 'github':
  190. return Provider.github;
  191. case 'google':
  192. return Provider.google;
  193. case 'discord':
  194. return Provider.discord;
  195. default:
  196. throw UnimplementedError();
  197. }
  198. }
  199. }