Phase 3.4: mitra_app self-managed auth cutover
Rips firebase_auth; phone OTP flow now talks directly to the new backend endpoints, JWT access token lives in memory, refresh token persists via flutter_secure_storage. WebSocket handshakes read the access token from AuthBridge instead of Firebase. Smoke-tested end-to-end against the backend via curl: - otp/request → read stub code from backend log → otp/verify - /api/mitra/auth/me + /api/shared/auth/refresh rotation - logout → post-logout refresh correctly fails REFRESH_INVALID - ACCOUNT_INACTIVE (403) + WRONG_FLOW (400) error paths verified - Debug APK links cleanly - pubspec: drop firebase_auth, add flutter_secure_storage - core/auth/auth_bridge.dart: shared mutable state (access token + refresh callback + in-flight de-dup) as keepAlive provider - core/auth/token_storage.dart: flutter_secure_storage wrapper - core/auth/auth_notifier.dart: bootstrap → refresh; requestOtp + verifyOtp via /api/mitra/auth/*; logout; granular OTP error codes - core/api/api_client.dart: Bearer from bridge + postRaw(skipAuth) for auth endpoints + single-retry 401 refresh - core/chat/*_notifier.dart: WS auth frame reads bridge.accessToken - features/auth/screens/otp_screen.dart: verificationId → otpRequestId - mitra_app/CLAUDE.md: Auth section rewritten (was stale on Firebase) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import '../api/api_client.dart';
|
||||
import '../api/api_client_provider.dart';
|
||||
import '../auth/auth_bridge.dart';
|
||||
import '../constants.dart';
|
||||
import '../notifications/notification_service.dart';
|
||||
|
||||
@@ -147,10 +147,9 @@ class ChatRequest extends _$ChatRequest {
|
||||
|
||||
Future<void> _connectWebSocket() async {
|
||||
try {
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
if (user == null) return;
|
||||
final token = ref.read(authBridgeProvider).accessToken;
|
||||
if (token == null) return;
|
||||
|
||||
final token = await user.getIdToken();
|
||||
final wsUrl = ApiClient.baseUrl
|
||||
.replaceFirst('https://', 'wss://')
|
||||
.replaceFirst('http://', 'ws://');
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'chat_request_notifier.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatRequestHash() => r'c80b16e371658fbbaca88a75b48e16a3c0e057b3';
|
||||
String _$chatRequestHash() => r'52aeaca594a44be3eeef7d264a1f311004b38416';
|
||||
|
||||
/// See also [ChatRequest].
|
||||
@ProviderFor(ChatRequest)
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'extension_notifier.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$mitraExtensionHash() => r'4eed73b51454238e2cd40a255c148f232f281913';
|
||||
String _$mitraExtensionHash() => r'e1346601df43c42aea6f2bc984b507547507a57c';
|
||||
|
||||
/// See also [MitraExtension].
|
||||
@ProviderFor(MitraExtension)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import '../api/api_client.dart';
|
||||
import '../api/api_client_provider.dart';
|
||||
import '../auth/auth_bridge.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
part 'mitra_chat_notifier.g.dart';
|
||||
@@ -137,8 +137,11 @@ class MitraChat extends _$MitraChat {
|
||||
createdAt: DateTime.parse(m['created_at'] as String),
|
||||
)).toList();
|
||||
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
final token = await user?.getIdToken();
|
||||
final token = ref.read(authBridgeProvider).accessToken;
|
||||
if (token == null) {
|
||||
state = const MitraChatErrorData('Sesi berakhir. Silakan login ulang.');
|
||||
return;
|
||||
}
|
||||
final wsUrl = ApiClient.baseUrl
|
||||
.replaceFirst('https://', 'wss://')
|
||||
.replaceFirst('http://', 'ws://');
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'mitra_chat_notifier.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$mitraChatHash() => r'827aa874dbcf49c17f94c0507f5e0a4064bcede3';
|
||||
String _$mitraChatHash() => r'd5f4819264b9c71ce29a640ee2cfee608ead5e9e';
|
||||
|
||||
/// See also [MitraChat].
|
||||
@ProviderFor(MitraChat)
|
||||
|
||||
Reference in New Issue
Block a user