Files
fanuzu/app/lib/features/auth/auth.dart
T

129 lines
4.6 KiB
Dart

/// 인증 계약 — 제공자 인터페이스와 결과 타입.
///
/// 원칙: **"키 미투입"은 예외가 아니라 상태다.** 카카오·네이버는 발주사 사업자 정보로
/// 개발자 콘솔에 앱을 등록해야 키가 나오고(Q3), Google·Apple 은 Firebase 설정 파일이
/// 있어야 한다(S1·S2). 둘 다 "아직 준비가 안 됐다"이지 "지원하지 않는다"가 아니므로
/// 예외를 던지지 않고 [AuthNotConfigured] 로 돌려준다. 화면은 이걸 안내로 보여 준다.
library;
/// 가입 수단 — **정책 설계서 §P-01 확정값 5종**(답변서 v1.1 Q7).
///
/// 초안 당시 CD-18 이 미확정이라 소셜 4종(카카오·네이버·Apple·Google)만 두었는데,
/// 답변서가 **이메일 · Google · Apple · Kakao · LINE** 으로 확정했다.
/// **네이버는 확정 목록에 없고, 이메일과 LINE 이 새로 들어왔다.**
///
/// 앱 Figma 에는 WeChat · X 버튼도 그려져 있으나 답변서가 5종으로 정했으므로
/// 1차는 5종이다. 추가 시 재작업이 없도록 제공자를 인터페이스 뒤에 둔다.
enum AuthProviderKind {
email('이메일로 시작하기'),
kakao('카카오로 시작하기'),
line('LINE으로 시작하기'),
apple('Apple로 시작하기'),
google('Google로 시작하기');
const AuthProviderKind(this.label);
/// 버튼 라벨. 임의로 바꾸지 마라 — 화면 대조 대상이다.
final String label;
/// 이메일은 소셜이 아니라 입력 폼이 붙는다. 화면이 이걸로 갈린다.
bool get needsForm => this == AuthProviderKind.email;
}
/// 이전 이름. 한 번에 다 바꾸지 않아도 되게 남겨 둔다.
typedef SocialProvider = AuthProviderKind;
/// 로그인된 계정.
class AuthAccount {
const AuthAccount({
required this.uid,
required this.provider,
this.displayName,
this.email,
this.isMock = false,
});
final String uid;
final AuthProviderKind? provider;
final String? displayName;
final String? email;
/// Firebase 미설정 상태에서 만든 임시 세션인지. 실 세션과 절대 섞이면 안 된다.
final bool isMock;
}
/// 로그인 시도 결과.
sealed class AuthOutcome {
const AuthOutcome();
}
final class AuthSucceeded extends AuthOutcome {
const AuthSucceeded(this.account);
final AuthAccount account;
}
/// 사용자가 취소함. 에러로 취급하지 않는다.
final class AuthCancelled extends AuthOutcome {
const AuthCancelled();
}
/// 실패. [code] 는 `wireframe/planning/error-catalog.yaml` 의 ID 다(소셜 실패 = E-AUTH-03).
final class AuthFailed extends AuthOutcome {
const AuthFailed(this.code, this.message);
final String code;
final String message;
}
/// 아직 쓸 수 없음 — 키·설정 파일 미투입.
final class AuthNotConfigured extends AuthOutcome {
const AuthNotConfigured(this.provider, this.missing);
final AuthProviderKind provider;
/// 투입해야 하는 것들(파일명·키명). 화면과 README 에 그대로 노출한다.
final List<String> missing;
}
/// 로그인 제공자 인터페이스. 이메일도 같은 인터페이스를 쓴다.
abstract interface class SocialAuthProvider {
AuthProviderKind get provider;
/// 실 연동 코드 경로를 가지고 있는지. 스텁이면 false.
bool get isLive;
/// 비어 있지 않으면 지금은 로그인할 수 없다. [signIn] 은 [AuthNotConfigured] 를 낸다.
List<String> get missingConfig;
Future<AuthOutcome> signIn();
Future<void> signOut();
}
/// 이메일 제공자만 쓰는 확장 계약.
///
/// 소셜은 외부 창이 열리고 끝이지만 이메일은 **앱 안에서 값을 받아야 한다.**
/// [SocialAuthProvider.signIn] 하나로 묶으면 화면이 제공자 종류를 보고 분기하게
/// 되므로, 입력이 필요한 쪽만 별도 계약으로 뺀다.
abstract interface class EmailAuthProvider implements SocialAuthProvider {
Future<AuthOutcome> signInWithEmail(String email, String password);
Future<AuthOutcome> signUpWithEmail(String email, String password);
/// 비밀번호 재설정 메일 발송. Figma 「비밀번호 재설정」 화면이 이걸 부른다.
Future<AuthOutcome> sendPasswordReset(String email);
}
/// 앱 재실행·토큰 만료·다른 기기 로그아웃을 인증 상태에 반영하는 세션 경계.
///
/// 저장소는 이 인터페이스만 알며 Firebase SDK 자체는 어댑터 뒤에 둔다. 덕분에
/// 외부 설정 파일 없이도 세션 복원과 만료 전이를 단위 테스트할 수 있다.
abstract interface class AuthSession {
AuthAccount? get currentAccount;
Stream<AuthAccount?> get accountChanges;
Future<void> signOut();
}