supabase_auth_service.dart 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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. await _auth.signOut();
  140. }
  141. await _appFlowyAuthService.signOut(
  142. authType: authType,
  143. );
  144. }
  145. @override
  146. Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
  147. AuthTypePB authType = AuthTypePB.Supabase,
  148. Map<String, String> map = const {},
  149. }) async {
  150. // supabase don't support guest login.
  151. // so, just forward to our backend.
  152. return _appFlowyAuthService.signUpAsGuest();
  153. }
  154. // @override
  155. // Future<Either<FlowyError, UserProfilePB>> getUser() async {
  156. // final loginType = await getIt<KeyValueStorage>()
  157. // .get(KVKeys.loginType)
  158. // .then((value) => value.toOption().toNullable());
  159. // if (!isSupabaseEnable || (loginType != null && loginType != 'supabase')) {
  160. // return _appFlowyAuthService.getUser();
  161. // }
  162. // final user = await getSupabaseUser();
  163. // return user.map((r) => r.toUserProfile());
  164. // }
  165. @override
  166. Future<Either<FlowyError, UserProfilePB>> getUser() async {
  167. return UserBackendService.getCurrentUserProfile();
  168. }
  169. Future<Either<FlowyError, User>> getSupabaseUser() async {
  170. final user = _auth.currentUser;
  171. if (user == null) {
  172. return left(AuthError.supabaseGetUserError);
  173. }
  174. return Right(user);
  175. }
  176. Future<Either<FlowyError, UserProfilePB>> setupAuth({
  177. required Map<String, String> map,
  178. }) async {
  179. final payload = ThirdPartyAuthPB(
  180. authType: AuthTypePB.Supabase,
  181. map: map,
  182. );
  183. return UserEventThirdPartyAuth(payload)
  184. .send()
  185. .then((value) => value.swap());
  186. }
  187. }
  188. extension on String {
  189. Provider toProvider() {
  190. switch (this) {
  191. case 'github':
  192. return Provider.github;
  193. case 'google':
  194. return Provider.google;
  195. case 'discord':
  196. return Provider.discord;
  197. default:
  198. throw UnimplementedError();
  199. }
  200. }
  201. }