supabase_auth_service.dart 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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: {
  110. AuthServiceMapKeys.uuid: user.id,
  111. AuthServiceMapKeys.email: user.email ?? user.newEmail ?? ''
  112. },
  113. );
  114. completer.complete(response);
  115. }
  116. subscription.cancel();
  117. });
  118. final Map<String, String> query = {};
  119. if (provider == Provider.google) {
  120. query['access_type'] = 'offline';
  121. query['prompt'] = 'consent';
  122. }
  123. final response = await _auth.signInWithOAuth(
  124. provider,
  125. queryParams: query,
  126. redirectTo:
  127. 'io.appflowy.appflowy-flutter://login-callback', // can't use underscore here.
  128. );
  129. if (!response) {
  130. completer.complete(left(AuthError.supabaseSignInWithOauthError));
  131. }
  132. return completer.future;
  133. }
  134. @override
  135. Future<void> signOut({
  136. AuthTypePB authType = AuthTypePB.Supabase,
  137. }) async {
  138. if (!isSupabaseEnable) {
  139. return _appFlowyAuthService.signOut();
  140. }
  141. await _auth.signOut();
  142. await _appFlowyAuthService.signOut(
  143. authType: authType,
  144. );
  145. }
  146. @override
  147. Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
  148. AuthTypePB authType = AuthTypePB.Supabase,
  149. Map<String, String> map = const {},
  150. }) async {
  151. // supabase don't support guest login.
  152. // so, just forward to our backend.
  153. return _appFlowyAuthService.signUpAsGuest();
  154. }
  155. // @override
  156. // Future<Either<FlowyError, UserProfilePB>> getUser() async {
  157. // final loginType = await getIt<KeyValueStorage>()
  158. // .get(KVKeys.loginType)
  159. // .then((value) => value.toOption().toNullable());
  160. // if (!isSupabaseEnable || (loginType != null && loginType != 'supabase')) {
  161. // return _appFlowyAuthService.getUser();
  162. // }
  163. // final user = await getSupabaseUser();
  164. // return user.map((r) => r.toUserProfile());
  165. // }
  166. @override
  167. Future<Either<FlowyError, UserProfilePB>> getUser() async {
  168. return UserBackendService.getCurrentUserProfile();
  169. }
  170. Future<Either<FlowyError, User>> getSupabaseUser() async {
  171. final user = _auth.currentUser;
  172. if (user == null) {
  173. return left(AuthError.supabaseGetUserError);
  174. }
  175. return Right(user);
  176. }
  177. Future<Either<FlowyError, UserProfilePB>> setupAuth({
  178. required Map<String, String> map,
  179. }) async {
  180. final payload = ThirdPartyAuthPB(
  181. authType: AuthTypePB.Supabase,
  182. map: map,
  183. );
  184. return UserEventThirdPartyAuth(payload)
  185. .send()
  186. .then((value) => value.swap());
  187. }
  188. }
  189. extension on String {
  190. Provider toProvider() {
  191. switch (this) {
  192. case 'github':
  193. return Provider.github;
  194. case 'google':
  195. return Provider.google;
  196. case 'discord':
  197. return Provider.discord;
  198. default:
  199. throw UnimplementedError();
  200. }
  201. }
  202. }