1차 보완(0.1.1+14) 내용 합치기
This commit is contained in:
@@ -27,8 +27,8 @@ final class FirebaseAccountLinker implements AccountLinker {
|
||||
AuthProviderKind.google: 'google.com',
|
||||
AuthProviderKind.apple: 'apple.com',
|
||||
// 카카오·LINE 은 Firebase 기본 제공자가 아니라 OIDC 커스텀 제공자로 붙는다.
|
||||
// 접두사 `oidc.` 는 Firebase 콘솔 등록 시 강제되는 형식이다. 카카오는 로그인을
|
||||
// 서버 커스텀 토큰으로 옮겼지만, 서버(fnAuthKakao)가 같은 `oidc.kakao` 로 연결을 남긴다.
|
||||
// 접두사 `oidc.` 는 Firebase 콘솔 등록 시 강제되는 형식이다. 두 제공자 모두 로그인을
|
||||
// 서버 커스텀 토큰으로 옮겼지만, 서버(fnAuthKakao · fnAuthLine)가 같은 ID 로 연결을 남긴다.
|
||||
AuthProviderKind.kakao: 'oidc.kakao',
|
||||
AuthProviderKind.line: 'oidc.line',
|
||||
AuthProviderKind.email: 'password',
|
||||
@@ -59,38 +59,28 @@ final class FirebaseAccountLinker implements AccountLinker {
|
||||
if (missing.isNotEmpty) return AuthNotConfigured(provider, missing);
|
||||
if (impl is! CredentialProvider &&
|
||||
impl is! OidcSignInProvider &&
|
||||
impl is! KakaoSignInProvider) {
|
||||
impl is! KakaoSignInProvider &&
|
||||
impl is! LineSignInProvider) {
|
||||
return AuthNotConfigured(provider, const ['자격증명 구현']);
|
||||
}
|
||||
|
||||
try {
|
||||
if (impl is KakaoSignInProvider) return await impl.link(user);
|
||||
if (impl is LineSignInProvider) return await impl.link(user);
|
||||
if (impl is OidcSignInProvider) return await _linkOidc(user, impl);
|
||||
final source = impl as CredentialProvider;
|
||||
final obtained = await source.obtainCredential();
|
||||
// 사용자가 소셜 창을 닫았다. 실패로 세지 않는다.
|
||||
if (obtained == null) return const AuthCancelled();
|
||||
|
||||
// 명세 `554:22797` — *동일 이메일만 연동 가능*. 붙이기 **전에** 막는다.
|
||||
// 붙인 뒤 되돌리는 방식은 unlink 가 실패하면 남의 계정이 붙은 채로 남는다.
|
||||
//
|
||||
// ponytail: 제공자가 이메일을 알려 줄 때만 판정한다. Apple 은 최초 인가
|
||||
// 때만 이메일을 주므로 재연동 시 통과한다 — 서버(fn-account-link)에서
|
||||
// 다시 보려면 Functions 층에 같은 판정을 둔다.
|
||||
final mine = user.email;
|
||||
final theirs = obtained.email;
|
||||
if (mine != null &&
|
||||
theirs != null &&
|
||||
!sameAccountIdentity(mine, theirs)) {
|
||||
return AuthFailed('E-AUTH-08', appL10n.authDifferentEmail);
|
||||
}
|
||||
|
||||
// 이메일을 비교하지 않는다(Q-E8). Firebase 가 지금 로그인 토큰과 새 자격증명을 서버에서 함께
|
||||
// 검증하고, 그 Provider 가 다른 UID 에 붙어 있으면 credential-already-in-use 로 거부한다.
|
||||
final result = await user.linkWithCredential(obtained.credential);
|
||||
return AuthSucceeded(_accountOf(result.user, provider));
|
||||
} on fb.FirebaseAuthException catch (error) {
|
||||
if (isWebFlowCancelled(error)) return const AuthCancelled();
|
||||
return AuthFailed(
|
||||
authCatalogCode(error),
|
||||
authCatalogCode(error, flow: AuthFlow.link),
|
||||
authMessage(error, flow: AuthFlow.link),
|
||||
);
|
||||
} on Object catch (error) {
|
||||
@@ -120,39 +110,20 @@ final class FirebaseAccountLinker implements AccountLinker {
|
||||
return AuthSucceeded(_accountOf(_firebaseAuth.currentUser, provider));
|
||||
} on fb.FirebaseAuthException catch (error) {
|
||||
return AuthFailed(
|
||||
authCatalogCode(error),
|
||||
authCatalogCode(error, flow: AuthFlow.link),
|
||||
authMessage(error, flow: AuthFlow.link),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 카카오·LINE(OIDC) 연결.
|
||||
///
|
||||
/// ponytail: OIDC 웹 흐름은 자격증명을 먼저 내주지 않고 연결까지 한 번에 끝낸다.
|
||||
/// 그래서 「동일 이메일만 연동」을 **붙인 뒤** 판정하고 어긋나면 바로 뗀다.
|
||||
/// 떼기가 실패하면 남의 이메일 계정이 붙은 채 남는다 — 막아야 하면 Functions 의
|
||||
/// blocking 함수(beforeSignIn)로 서버에서 거른다.
|
||||
/// 웹 흐름(OIDC — Android Apple) 연결 — 웹 흐름이 인증과 연결을 한 번에 끝낸다. 검증 · 충돌 거부는 Firebase 가 한다(Q-E8).
|
||||
Future<AuthOutcome> _linkOidc(fb.User user, OidcSignInProvider impl) async {
|
||||
final mine = user.email;
|
||||
final result = await untilAbandoned(
|
||||
user.linkWithProvider(impl.authProvider),
|
||||
final result = await retryStaleWebContext(
|
||||
() => untilAbandoned(user.linkWithProvider(impl.authProvider)),
|
||||
);
|
||||
// 연결된 제공자 정보에 그 계정의 이메일이 실린다(동의 항목에 이메일이 있을 때).
|
||||
final theirs = _emailOf(result.user, impl.providerId);
|
||||
if (mine != null && theirs != null && !sameAccountIdentity(mine, theirs)) {
|
||||
await user.unlink(impl.providerId);
|
||||
return AuthFailed('E-AUTH-08', appL10n.authDifferentEmail);
|
||||
}
|
||||
return AuthSucceeded(_accountOf(result.user, impl.provider));
|
||||
}
|
||||
|
||||
static String? _emailOf(fb.User? user, String providerId) {
|
||||
for (final info in user?.providerData ?? const <fb.UserInfo>[]) {
|
||||
if (info.providerId == providerId) return info.email;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
SocialAuthProvider? _implOf(AuthProviderKind provider) {
|
||||
for (final candidate in _providers) {
|
||||
if (candidate.provider == provider) return candidate;
|
||||
|
||||
Reference in New Issue
Block a user