Skip to content

Commit

Permalink
Fix user password encryption (#958)
Browse files Browse the repository at this point in the history
  • Loading branch information
limwa authored Sep 23, 2023
2 parents 610751e + c1e3c96 commit 743e4c6
Show file tree
Hide file tree
Showing 16 changed files with 66 additions and 71 deletions.
4 changes: 4 additions & 0 deletions uni/lib/controller/background_workers/notifications.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ class NotificationManager {
final userInfo = await AppSharedPreferences.getPersistentUserInfo();
final faculties = await AppSharedPreferences.getUserFaculties();

if (userInfo == null || faculties.isEmpty) {
return;
}

final session = await NetworkRouter.login(
userInfo.item1,
userInfo.item2,
Expand Down
38 changes: 19 additions & 19 deletions uni/lib/controller/local_storage/app_shared_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import 'package:uni/utils/favorite_widget_type.dart';
/// This database stores the user's student number, password and favorite
/// widgets.
class AppSharedPreferences {
static final iv = encrypt.IV.fromBase64('jF9jjdSEPgsKnf0jCl1GAQ==');
static final key =
encrypt.Key.fromBase64('DT3/GTNYldhwOD3ZbpVLoAwA/mncsN7U7sJxfFn3y0A=');

static const lastUpdateTimeKeySuffix = '_last_update_time';
static const String userNumber = 'user_number';
static const String userPw = 'user_password';
Expand All @@ -24,10 +28,6 @@ class AppSharedPreferences {
'tuition_notification_toogle';
static const String themeMode = 'theme_mode';
static const String locale = 'app_locale';
static const int keyLength = 32;
static const int ivLength = 16;
static final iv = encrypt.IV.fromLength(ivLength);

static const String favoriteCards = 'favorite_cards';
static final List<FavoriteWidgetType> defaultFavoriteCards = [
FavoriteWidgetType.schedule,
Expand Down Expand Up @@ -149,9 +149,12 @@ class AppSharedPreferences {
/// * the first element in the tuple is the user's student number.
/// * the second element in the tuple is the user's password, in plain text
/// format.
static Future<Tuple2<String, String>> getPersistentUserInfo() async {
static Future<Tuple2<String, String>?> getPersistentUserInfo() async {
final userNum = await getUserNumber();
final userPass = await getUserPassword();
if (userNum == null || userPass == null) {
return null;
}
return Tuple2(userNum, userPass);
}

Expand All @@ -164,22 +167,16 @@ class AppSharedPreferences {
}

/// Returns the user's student number.
static Future<String> getUserNumber() async {
static Future<String?> getUserNumber() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(userNumber) ??
''; // empty string for the case it does not exist
return prefs.getString(userNumber);
}

/// Returns the user's password, in plain text format.
static Future<String> getUserPassword() async {
static Future<String?> getUserPassword() async {
final prefs = await SharedPreferences.getInstance();
var pass = prefs.getString(userPw) ?? '';

if (pass != '') {
pass = decode(pass);
}

return pass;
final password = prefs.getString(userPw);
return password != null ? decode(password) : null;
}

/// Replaces the user's favorite widgets with [newFavorites].
Expand Down Expand Up @@ -270,15 +267,18 @@ class AppSharedPreferences {
}

/// Decrypts [base64Text].
static String decode(String base64Text) {
static String? decode(String base64Text) {
final encrypter = _createEncrypter();
return encrypter.decrypt64(base64Text, iv: iv);
try {
return encrypter.decrypt64(base64Text, iv: iv);
} catch (e) {
return null;
}
}

/// Creates an [encrypt.Encrypter] for encrypting and decrypting the user's
/// password.
static encrypt.Encrypter _createEncrypter() {
final key = encrypt.Key.fromLength(keyLength);
return encrypt.Encrypter(encrypt.AES(key));
}

Expand Down
7 changes: 7 additions & 0 deletions uni/lib/controller/networking/network_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class NetworkRouter {
faculties,
persistentSession: persistentSession,
);

if (session == null) {
Logger().e('Login failed: user not authenticated');
return null;
Expand All @@ -90,6 +91,12 @@ class NetworkRouter {
static Future<Session?> reLoginFromSession(Session session) async {
final username = session.username;
final password = await AppSharedPreferences.getUserPassword();

if (password == null) {
Logger().e('Re-login failed: password not found');
return null;
}

final faculties = session.faculties;
final persistentSession = session.persistentSession;

Expand Down
4 changes: 1 addition & 3 deletions uni/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,8 @@ SentryEvent? beforeSend(SentryEvent event) {

Future<String> firstRoute() async {
final userPersistentInfo = await AppSharedPreferences.getPersistentUserInfo();
final userName = userPersistentInfo.item1;
final password = userPersistentInfo.item2;

if (userName != '' && password != '') {
if (userPersistentInfo != null) {
return '/${DrawerItem.navPersonalArea.title}';
}

Expand Down
12 changes: 6 additions & 6 deletions uni/lib/model/providers/lazy/exam_provider.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:collection';

import 'package:tuple/tuple.dart';
import 'package:uni/controller/fetchers/exam_fetcher.dart';
import 'package:uni/controller/local_storage/app_exams_database.dart';
import 'package:uni/controller/local_storage/app_shared_preferences.dart';
Expand Down Expand Up @@ -42,27 +41,28 @@ class ExamProvider extends StateProviderNotifier {
Future<void> loadFromRemote(Session session, Profile profile) async {
await fetchUserExams(
ParserExams(),
await AppSharedPreferences.getPersistentUserInfo(),
profile,
session,
profile.courseUnits,
persistentSession:
(await AppSharedPreferences.getPersistentUserInfo()) != null,
);
}

Future<void> fetchUserExams(
ParserExams parserExams,
Tuple2<String, String> userPersistentInfo,
Profile profile,
Session session,
List<CourseUnit> userUcs,
) async {
List<CourseUnit> userUcs, {
required bool persistentSession,
}) async {
try {
final exams = await ExamFetcher(profile.courses, userUcs)
.extractExams(session, parserExams);

exams.sort((exam1, exam2) => exam1.begin.compareTo(exam2.begin));

if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') {
if (persistentSession) {
await AppExamsDatabase().saveNewExams(exams);
}

Expand Down
8 changes: 4 additions & 4 deletions uni/lib/model/providers/lazy/lecture_provider.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:collection';

import 'package:tuple/tuple.dart';
import 'package:uni/controller/fetchers/schedule_fetcher/schedule_fetcher.dart';
import 'package:uni/controller/fetchers/schedule_fetcher/schedule_fetcher_api.dart';
import 'package:uni/controller/fetchers/schedule_fetcher/schedule_fetcher_html.dart';
Expand Down Expand Up @@ -30,23 +29,24 @@ class LectureProvider extends StateProviderNotifier {
@override
Future<void> loadFromRemote(Session session, Profile profile) async {
await fetchUserLectures(
await AppSharedPreferences.getPersistentUserInfo(),
session,
profile,
persistentSession:
(await AppSharedPreferences.getPersistentUserInfo()) != null,
);
}

Future<void> fetchUserLectures(
Tuple2<String, String> userPersistentInfo,
Session session,
Profile profile, {
required bool persistentSession,
ScheduleFetcher? fetcher,
}) async {
try {
final lectures =
await getLecturesFromFetcherOrElse(fetcher, session, profile);

if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') {
if (persistentSession) {
final db = AppLecturesDatabase();
await db.saveNewLectures(lectures);
}
Expand Down
8 changes: 4 additions & 4 deletions uni/lib/model/providers/startup/profile_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class ProfileProvider extends StateProviderNotifier {
final userPersistentInfo =
await AppSharedPreferences.getPersistentUserInfo();

if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') {
if (userPersistentInfo != null) {
final profileDb = AppUserDataDatabase();
await profileDb.saveUserFees(feesBalance, feesLimit);
}
Expand All @@ -95,7 +95,7 @@ class ProfileProvider extends StateProviderNotifier {

final userPersistentInfo =
await AppSharedPreferences.getPersistentUserInfo();
if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') {
if (userPersistentInfo != null) {
final profileDb = AppUserDataDatabase();
await profileDb.saveUserPrintBalance(printBalance);
}
Expand All @@ -119,7 +119,7 @@ class ProfileProvider extends StateProviderNotifier {

final userPersistentInfo =
await AppSharedPreferences.getPersistentUserInfo();
if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') {
if (userPersistentInfo != null) {
// Course units are saved later, so we don't it here
final profileDb = AppUserDataDatabase();
await profileDb.insertUserData(_profile);
Expand All @@ -144,7 +144,7 @@ class ProfileProvider extends StateProviderNotifier {

final userPersistentInfo =
await AppSharedPreferences.getPersistentUserInfo();
if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') {
if (userPersistentInfo != null) {
final coursesDb = AppCoursesDatabase();
await coursesDb.saveNewCourses(courses);

Expand Down
5 changes: 5 additions & 0 deletions uni/lib/model/providers/startup/session_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class SessionProvider extends StateProviderNotifier {
Future<void> loadFromStorage() async {
final userPersistentInfo =
await AppSharedPreferences.getPersistentUserInfo();

if (userPersistentInfo == null) {
return;
}

final userName = userPersistentInfo.item1;
final password = userPersistentInfo.item2;

Expand Down
9 changes: 0 additions & 9 deletions uni/lib/view/common_widgets/page_transition.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import 'package:flutter/material.dart';

import 'package:uni/controller/local_storage/app_shared_preferences.dart';
import 'package:uni/view/navigation_service.dart';
import 'package:uni/view/terms_and_condition_dialog.dart';

Expand Down Expand Up @@ -49,16 +47,9 @@ class PageTransition {
static Future<void> requestTermsAndConditionsAcceptanceIfNeeded(
BuildContext context,
) async {
final userPersistentInfo =
await AppSharedPreferences.getPersistentUserInfo();
final userName = userPersistentInfo.item1;
final password = userPersistentInfo.item2;

if (context.mounted) {
final termsAcceptance = await TermsAndConditionDialog.buildIfTermsChanged(
context,
userName,
password,
);

switch (termsAcceptance) {
Expand Down
2 changes: 2 additions & 0 deletions uni/lib/view/login/login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:logger/logger.dart';
import 'package:provider/provider.dart';
import 'package:uni/generated/l10n.dart';
import 'package:uni/model/entities/login_exceptions.dart';
Expand Down Expand Up @@ -67,6 +68,7 @@ class LoginPageViewState extends State<LoginPageView> {
} else if (error is WrongCredentialsException) {
unawaited(ToastMessage.error(context, error.message));
} else {
Logger().e(error);
unawaited(ToastMessage.error(context, S.of(context).failed_login));
}
}
Expand Down
7 changes: 1 addition & 6 deletions uni/lib/view/terms_and_condition_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,14 @@ class TermsAndConditionDialog {

static Future<TermsAndConditionsState> buildIfTermsChanged(
BuildContext context,
String userName,
String password,
) async {
final termsAreAccepted =
await updateTermsAndConditionsAcceptancePreference();

if (!termsAreAccepted) {
final routeCompleter = Completer<TermsAndConditionsState>();
SchedulerBinding.instance.addPostFrameCallback(
(timestamp) =>
_buildShowDialog(context, routeCompleter, userName, password),
(timestamp) => _buildShowDialog(context, routeCompleter),
);
return routeCompleter.future;
}
Expand All @@ -34,8 +31,6 @@ class TermsAndConditionDialog {
static Future<void> _buildShowDialog(
BuildContext context,
Completer<TermsAndConditionsState> userTermsDecision,
String userName,
String password,
) {
return showDialog(
context: context,
Expand Down
2 changes: 1 addition & 1 deletion uni/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies:
cupertino_icons: ^1.0.2
currency_text_input_formatter: ^2.1.5
email_validator: ^2.0.1
encrypt: ^5.0.0-beta.1
encrypt: ^5.0.3
expansion_tile_card: ^3.0.0
flutter:
sdk: flutter
Expand Down
5 changes: 2 additions & 3 deletions uni/test/integration/src/exams_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:http/http.dart' as http;
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
import 'package:uni/controller/networking/network_router.dart';
import 'package:uni/controller/parsers/parser_exams.dart';
import 'package:uni/model/entities/course.dart';
Expand Down Expand Up @@ -82,10 +81,10 @@ void main() {

await examProvider.fetchUserExams(
ParserExams(),
const Tuple2('', ''),
profile,
Session(username: '', cookies: '', faculties: ['feup']),
[sopeCourseUnit, sdisCourseUnit],
persistentSession: false,
);

await tester.pumpAndSettle();
Expand Down Expand Up @@ -118,10 +117,10 @@ void main() {

await examProvider.fetchUserExams(
ParserExams(),
const Tuple2('', ''),
profile,
Session(username: '', cookies: '', faculties: ['feup']),
[sopeCourseUnit, sdisCourseUnit],
persistentSession: false,
);

await tester.pumpAndSettle();
Expand Down
3 changes: 1 addition & 2 deletions uni/test/integration/src/schedule_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:http/http.dart' as http;
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
import 'package:uni/controller/networking/network_router.dart';
import 'package:uni/model/entities/course.dart';
import 'package:uni/model/entities/profile.dart';
Expand Down Expand Up @@ -71,9 +70,9 @@ void main() {
expect(find.byKey(const Key(scheduleSlotTimeKey2)), findsNothing);

await scheduleProvider.fetchUserLectures(
const Tuple2('', ''),
Session(username: '', cookies: '', faculties: ['feup']),
profile,
persistentSession: false,
);

await tester.tap(find.byKey(const Key('schedule-page-tab-2')));
Expand Down
Loading

0 comments on commit 743e4c6

Please sign in to comment.