Skip to content

Commit

Permalink
Merge pull request #61 from KosukeSaigusa/45_create_oauth
Browse files Browse the repository at this point in the history
#45 GoogleとAppleでのログイン処理作成
  • Loading branch information
RikitoNoto committed Jul 23, 2023
2 parents a4e1e5e + 293e492 commit e74ad82
Show file tree
Hide file tree
Showing 11 changed files with 326 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,24 @@ class WorkerRepository {
/// 指定した [Worker] を購読する。
Stream<ReadWorker?> subscribeWorker({required String workerId}) =>
_query.subscribeDocument(workerId: workerId);

/// 指定した [Worker] を取得する。
Future<ReadWorker?> fetchWorker({required String workerId}) =>
_query.fetchDocument(workerId: workerId);

/// [Worker] を作成する。
Future<void> setWorker({
required String workerId,
required String displayName,
String imageUrl = '',
bool isHost = false,
}) =>
_query.set(
workerId: workerId,
createWorker: CreateWorker(
displayName: displayName,
imageUrl: imageUrl,
isHost: isHost,
),
);
}
6 changes: 6 additions & 0 deletions packages/mottai_flutter_app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ PODS:
- SDWebImageWebPCoder (0.13.0):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- sign_in_with_apple (0.0.1):
- Flutter
- sqflite (0.0.3):
- Flutter
- FMDB (>= 2.7.5)
Expand All @@ -194,6 +196,7 @@ DEPENDENCIES:
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)

SPEC REPOS:
Expand Down Expand Up @@ -256,6 +259,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
sign_in_with_apple:
:path: ".symlinks/plugins/sign_in_with_apple/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"

Expand Down Expand Up @@ -305,6 +310,7 @@ SPEC CHECKSUMS:
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
SDWebImageWebPCoder: af09429398d99d524cae2fe00f6f0f6e491ed102
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a

PODFILE CHECKSUM: e5e57377bd5fff00d6d1c5fa3559e4346fd8c03e
Expand Down
19 changes: 15 additions & 4 deletions packages/mottai_flutter_app/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
Expand All @@ -20,10 +22,23 @@
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.709197089170-9hsmfhj3ovc79m8f982j7lot5n7iq3rh</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -43,9 +58,5 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
4 changes: 4 additions & 0 deletions packages/mottai_flutter_app/ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
102 changes: 98 additions & 4 deletions packages/mottai_flutter_app/lib/auth/auth.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';

import '../user/worker.dart';

/// Firebase Console の Authentication で設定できるサインイン方法の種別。
enum SignInMethod {
google,
apple,
line,
// TODO: 後で削除する予定
email,
;
}

/// [FirebaseAuth] のインスタンスを提供する [Provider].
final _authProvider = Provider<FirebaseAuth>((_) => FirebaseAuth.instance);
Expand All @@ -25,14 +42,20 @@ final isSignedInProvider = Provider<bool>(
(ref) => ref.watch(userIdProvider) != null,
);

final authServiceProvider =
Provider.autoDispose<AuthService>((_) => const AuthService());
final authServiceProvider = Provider.autoDispose<AuthService>((ref) {
return AuthService(
workerService: ref.watch(workerServiceProvider),
);
});

/// [FirebaseAuth] の認証関係の振る舞いを記述するモデル。
class AuthService {
const AuthService();
const AuthService({
required WorkerService workerService,
}) : _workerService = workerService;

static final _auth = FirebaseAuth.instance;
final WorkerService _workerService;

// TODO: 開発中のみ使用する。リリース時には消すか、あとで デバッグモード or
// 開発環境接続時のみ使用可能にする。
Expand All @@ -43,6 +66,77 @@ class AuthService {
}) =>
_auth.signInWithEmailAndPassword(email: email, password: password);

/// [FirebaseAuth] に Google でサインインする。
/// https://firebase.flutter.dev/docs/auth/social/#google に従っている。
Future<UserCredential> signInWithGoogle() async {
final googleUser = await GoogleSignIn().signIn(); // サインインダイアログの表示
final googleAuth = await googleUser?.authentication; // アカウントからトークン生成
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);

final userCredential = await _auth.signInWithCredential(credential);
await _maybeCreateWorkerByUserCredential(userCredential: userCredential);
return userCredential;
}

/// [FirebaseAuth] に Apple でサインインする。
/// https://firebase.flutter.dev/docs/auth/social/#apple に従っている。
Future<UserCredential> signInWithApple() async {
final rawNonce = generateNonce();
final nonce = _sha256ofString(rawNonce);

final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);

final oauthCredential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);

final userCredential =
await FirebaseAuth.instance.signInWithCredential(oauthCredential);
await _maybeCreateWorkerByUserCredential(userCredential: userCredential);
return userCredential;
}

/// 文字列から SHA-256 ハッシュを作成する。
String _sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}

/// サインイン時に、まだ Worker ドキュメントが存在していなければ、Firebase
/// の [UserCredential] をもとに生成する。
/// Google や Apple によるはじめてのログインのときに相当する。
Future<void> _maybeCreateWorkerByUserCredential({
required UserCredential userCredential,
}) async {
final user = userCredential.user;
if (user == null) {
// UserCredential
return;
}
final workerExists = await _workerService.workerExists(workerId: user.uid);
if (workerExists) {
return;
}
await _workerService.createWorker(
workerId: user.uid,
displayName: user.displayName ?? '',
);
}

/// [FirebaseAuth] からサインアウトする。
Future<void> signOut() => _auth.signOut();
Future<void> signOut() async {
await _auth.signOut();
await GoogleSignIn().signOut();
}
}
50 changes: 50 additions & 0 deletions packages/mottai_flutter_app/lib/auth/ui/auth_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../../../scaffold_messenger_controller.dart';
import '../auth.dart';

final authControllerProvider = Provider.autoDispose<AuthController>(
(ref) => AuthController(
authService: ref.watch(authServiceProvider),
appScaffoldMessengerController:
ref.watch(appScaffoldMessengerControllerProvider),
),
);

class AuthController {
const AuthController({
required AuthService authService,
required AppScaffoldMessengerController appScaffoldMessengerController,
}) : _authService = authService,
_appScaffoldMessengerController = appScaffoldMessengerController;

final AuthService _authService;
final AppScaffoldMessengerController _appScaffoldMessengerController;

/// 選択した [SignInMethod] でサインインする。
Future<void> signIn(SignInMethod authenticator) async {
switch (authenticator) {
case SignInMethod.google:
try {
await _authService.signInWithGoogle();
}
// キャンセル時
on PlatformException catch (e) {
if (e.code == 'network_error') {
_appScaffoldMessengerController
.showSnackBar('接続できませんでした。\nネットワーク状況を確認してください。');
}
_appScaffoldMessengerController.showSnackBar('キャンセルしました。');
}

case SignInMethod.apple:
// Apple はキャンセルやネットワークエラーの判定ができないので、try-catchしない
await _authService.signInWithApple();
case SignInMethod.line:
case SignInMethod.email:
throw UnimplementedError();
}
return;
}
}
46 changes: 46 additions & 0 deletions packages/mottai_flutter_app/lib/auth/ui/sign_in_buttons.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sign_in_button/sign_in_button.dart';

import '../auth.dart';
import 'auth_controller.dart';

class GoogleAppleSignin extends ConsumerWidget {
const GoogleAppleSignin({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('サインインページ'),
),
body: Center(
child: Column(
children: [
// Google
SizedBox(
height: 50,
child: SignInButton(
Buttons.google,
text: 'Google でサインイン',
onPressed: () async => ref
.read(authControllerProvider)
.signIn(SignInMethod.google),
),
),
// Apple
SizedBox(
height: 50,
child: SignInButton(
Buttons.apple,
text: 'Apple でサインイン',
onPressed: () async =>
ref.read(authControllerProvider).signIn(SignInMethod.apple),
),
),
],
),
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../../../auth/auth.dart';
import '../../../auth/ui/sign_in_buttons.dart';
import '../../../chat/ui/chat_room.dart';
import '../../../map/ui/map.dart';
import '../../../scaffold_messenger_controller.dart';
Expand All @@ -11,6 +12,7 @@ import '../../../user/user_mode.dart';
import '../../color/ui/color.dart';
import '../../sample_todo/ui/sample_todos.dart';


/// 開発中の各ページへの導線を表示するページ。
class DevelopmentItemsPage extends ConsumerWidget {
const DevelopmentItemsPage({super.key});
Expand Down Expand Up @@ -189,15 +191,15 @@ class DevelopmentItemsPage extends ConsumerWidget {
// ),
// ),
),
const ListTile(
title: Text('サインイン (Google, Apple)'),
ListTile(
title: const Text('サインイン (Google, Apple)'),
// TODO: 後に auto_route を採用して Navigator.pushNamed を使用する予定
// onTap: () => Navigator.push<void>(
// context,
// MaterialPageRoute<void>(
// builder: (context) => FooPage(),
// ),
// ),
onTap: () => Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (context) => const GoogleAppleSignin(),
),
),
),
const ListTile(
title: Text('サインイン (LINE, Firebase Functions)'),
Expand Down
Loading

0 comments on commit e74ad82

Please sign in to comment.