Skip to content

Commit

Permalink
Set Auth Header for Authentication (#507)
Browse files Browse the repository at this point in the history
Should close #499

Token is valid for one hour, we can keep refreshing the token as long as
the refresh token is not revoked. The refresh token gets revoked only if
the users account is deleted/disabled or revoked manually from the
backend. When the token is expired, Firebase Auth will automatically
prompt the user to log in when they open the app. The `getAuthHeader`
func refreshes the token only if it has expired or is empty or is about
to expire in 5 minutes.
  • Loading branch information
josancamon19 committed Aug 3, 2024
2 parents e56cc9f + 1bc8690 commit 747535a
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 24 deletions.
25 changes: 14 additions & 11 deletions app/lib/backend/api_requests/api/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Future<List<TranscriptSegment>> transcribe(File file) async {
);
request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path)));
request.headers.addAll({
'Authorization': await getAuthHeader(),
'authorization': await getAuthHeader(),
});

try {
Expand All @@ -42,9 +42,9 @@ Future<List<TranscriptSegment>> transcribe(File file) async {
}
}

Future<bool> userHasSpeakerProfile(String uid) async {
Future<bool> userHasSpeakerProfile() async {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/speech-profile?uid=$uid',
url: '${Env.apiBaseUrl}v1/speech-profile?uid=${SharedPreferencesUtil().uid}',
// url: 'https://5818-107-3-134-29.ngrok-free.app/v1/speech-profile',
headers: {},
method: 'GET',
Expand All @@ -55,10 +55,10 @@ Future<bool> userHasSpeakerProfile(String uid) async {
return jsonDecode(response.body)['has_profile'] ?? false;
}

Future<List<SpeakerIdSample>> getUserSamplesState(String uid) async {
debugPrint('getUserSamplesState for uid: $uid');
Future<List<SpeakerIdSample>> getUserSamplesState() async {
debugPrint('getUserSamplesState');
var response = await makeApiCall(
url: '${Env.apiBaseUrl}samples?uid=$uid',
url: '${Env.apiBaseUrl}samples?uid=${SharedPreferencesUtil().uid}',
headers: {},
method: 'GET',
body: '',
Expand All @@ -68,12 +68,15 @@ Future<List<SpeakerIdSample>> getUserSamplesState(String uid) async {
return SpeakerIdSample.fromJsonList(jsonDecode(response.body));
}

Future<bool> uploadSample(File file, String uid) async {
debugPrint('uploadSample ${file.path} for uid: $uid');
Future<bool> uploadSample(File file) async {
debugPrint('uploadSample ${file.path}');
var request = http.MultipartRequest(
'POST',
Uri.parse('${Env.apiBaseUrl}samples/upload?uid=$uid'),
Uri.parse('${Env.apiBaseUrl}samples/upload?uid=${SharedPreferencesUtil().uid}'),
);
request.headers.addAll({
'authorization': await getAuthHeader(),
});
request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path)));

try {
Expand Down Expand Up @@ -113,9 +116,9 @@ Future<void> migrateMemoriesToBackend(List<dynamic> memories) async {
debugPrint('migrateMemoriesToBackend: ${response?.body}');
}

Future<String> downloadBackupApi(String uid) async {
Future<String> downloadBackupApi() async {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/backups?uid=$uid',
url: '${Env.apiBaseUrl}v1/backups?uid=${SharedPreferencesUtil().uid}',
headers: {},
method: 'GET',
body: '',
Expand Down
6 changes: 5 additions & 1 deletion app/lib/backend/api_requests/api/shared.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import 'package:instabug_http_client/instabug_http_client.dart';
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';

Future<String> getAuthHeader() async {
if (SharedPreferencesUtil().authToken == '') {
DateTime? expiry = DateTime.fromMillisecondsSinceEpoch(SharedPreferencesUtil().tokenExpirationTime);
if (SharedPreferencesUtil().authToken == '' ||
expiry.isBefore(DateTime.now()) ||
expiry.isAtSameMomentAs(DateTime.fromMillisecondsSinceEpoch(0)) ||
(expiry.isBefore(DateTime.now().add(const Duration(minutes: 5))) && expiry.isAfter(DateTime.now()))) {
SharedPreferencesUtil().authToken = await getIdToken() ?? '';
}
if (SharedPreferencesUtil().authToken == '') {
Expand Down
22 changes: 16 additions & 6 deletions app/lib/backend/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,28 @@ Future<UserCredential> signInWithGoogle() async {
listenAuthTokenChanges() {
FirebaseAuth.instance.idTokenChanges().listen((User? user) async {
// SharedPreferencesUtil().authToken = '123:/';
try {
var token = await getIdToken();
SharedPreferencesUtil().authToken = token ?? '';
} catch (e) {
debugPrint('Error getting token: $e');
if (user == null) {
debugPrint('User is currently signed out or the token has been revoked!');
SharedPreferencesUtil().authToken = '';
} else {
debugPrint('User is signed in!');
try {
SharedPreferencesUtil().authToken = await getIdToken() ?? '';
} catch (e) {
debugPrint('Failed to get token: $e');
}
}
});
}

Future<String?> getIdToken() async {
try {
IdTokenResult? newToken = await FirebaseAuth.instance.currentUser?.getIdTokenResult(true);
if (newToken?.token != null) SharedPreferencesUtil().uid = FirebaseAuth.instance.currentUser!.uid;
if (newToken?.token != null) {
SharedPreferencesUtil().uid = FirebaseAuth.instance.currentUser!.uid;
SharedPreferencesUtil().tokenExpirationTime = newToken?.expirationTime?.millisecondsSinceEpoch ?? 0;
SharedPreferencesUtil().authToken = newToken?.token ?? '';
}
return newToken?.token;
} catch (e) {
print(e);
Expand All @@ -140,6 +149,7 @@ listenAuthStateChanges() {
if (user == null) {
debugPrint('User is currently signed out!');
SharedPreferencesUtil().onboardingCompleted = false;
SharedPreferencesUtil().authToken = '';
} else {
debugPrint('User is signed in!');
}
Expand Down
4 changes: 4 additions & 0 deletions app/lib/backend/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ class SharedPreferencesUtil {

set authToken(String value) => saveString('authToken', value);

int get tokenExpirationTime => getInt('tokenExpirationTime') ?? 0;

set tokenExpirationTime(int value) => saveInt('tokenExpirationTime', value);

String get email => getString('email') ?? '';

set email(String value) => saveString('email', value);
Expand Down
2 changes: 1 addition & 1 deletion app/lib/pages/home/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class _HomePageWrapperState extends State<HomePageWrapper> with WidgetsBindingOb
}

_setupHasSpeakerProfile() async {
SharedPreferencesUtil().hasSpeakerProfile = await userHasSpeakerProfile(SharedPreferencesUtil().uid);
SharedPreferencesUtil().hasSpeakerProfile = await userHasSpeakerProfile();
print('_setupHasSpeakerProfile: ${SharedPreferencesUtil().hasSpeakerProfile}');
MixpanelManager().setUserProperty('Speaker Profile', SharedPreferencesUtil().hasSpeakerProfile);
setState(() {});
Expand Down
3 changes: 1 addition & 2 deletions app/lib/pages/speaker_id/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:friend_private/backend/api_requests/api/server.dart';
import 'package:friend_private/backend/mixpanel.dart';
import 'package:friend_private/backend/preferences.dart';
import 'package:friend_private/backend/schema/bt_device.dart';
import 'package:friend_private/backend/schema/sample.dart';
import 'package:friend_private/pages/home/page.dart';
Expand Down Expand Up @@ -36,7 +35,7 @@ class _SpeakerIdPageState extends State<SpeakerIdPage> with TickerProviderStateM
_init() async {
_device = await getConnectedDevice();
_device ??= await scanAndConnectDevice(timeout: true);
_samples = await getUserSamplesState(SharedPreferencesUtil().uid);
_samples = await getUserSamplesState();
_controller = TabController(length: 2 + _samples.length, vsync: this);
_initiateConnectionListener();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Expand Down
2 changes: 1 addition & 1 deletion app/lib/pages/speaker_id/tabs/record_sample.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class _RecordSampleTabState extends State<RecordSampleTab> with TickerProviderSt
audioBytesStream?.cancel();
Tuple2<File, List<List<int>>> file = await audioStorage!.createWavFile(filename: '${widget.sample.id}.wav');
changeLoadingState();
await uploadSample(file.item1, SharedPreferencesUtil().uid); // optimistic request
await uploadSample(file.item1); // optimistic request
// TODO: handle failures + url: null, retry sample
}

Expand Down
10 changes: 8 additions & 2 deletions app/lib/utils/websockets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:friend_private/backend/api_requests/api/shared.dart';
import 'package:friend_private/backend/database/transcript_segment.dart';
import 'package:friend_private/backend/preferences.dart';
import 'package:friend_private/env/env.dart';
Expand Down Expand Up @@ -35,11 +36,16 @@ Future<IOWebSocketChannel?> _initWebsocketStream(
) async {
debugPrint('Websocket Opening');
final recordingsLanguage = SharedPreferencesUtil().recordingsLanguage;
var params = '?language=$recordingsLanguage&uid=${SharedPreferencesUtil().uid}&sample_rate=$sampleRate&codec=$codec';
var params = '?language=$recordingsLanguage&sample_rate=$sampleRate&codec=$codec';

IOWebSocketChannel channel = IOWebSocketChannel.connect(
Uri.parse('${Env.apiBaseUrl!.replaceAll('https', 'wss')}listen$params'),
headers: {
'authorization': await getAuthHeader(),
},
);
channel.ready.then((_) {

await channel.ready.then((_) {
channel.stream.listen(
(event) {
if (event == 'ping') return;
Expand Down
1 change: 1 addition & 0 deletions backend/routers/transcribe.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pydub import AudioSegment
from starlette.websockets import WebSocketState


from utils.stt.deepgram_util import transcribe_file_deepgram, process_audio_dg, send_initial_file, \
get_speaker_audio_file, remove_downloaded_samples
from utils.stt.vad import vad_is_empty, is_speech_present, window_size_samples
Expand Down

0 comments on commit 747535a

Please sign in to comment.