supabase_auth_service.dart 6.8 KB

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