Skip to content

Commit

Permalink
feat: register & biometric added (#24) (#29)
Browse files Browse the repository at this point in the history
* feat: new type connection to proxy (#9)

* refactor: change type variable .env

* feat: connection to the proxy via HTTP & check new endpoints

* feat: terms and conditions modal

Terms and conditions modal is added at the time of user registration.
In addition, the auto_size_text package is added for text management on different devices

* feat: validation of fields in the registration form

Implementation of the registration form fields with their validation.

* feat: validating equal passwords in form.

Password validation is done on the registration form, in addition to using modal when validating fields.

* feat: register options with merge biometricts login

registration functionality through username and password

* feat: register added (#26) & change to secure storage

* feat: customErros added of proxy response (#25)

* refactor: recome closeModalCallback

* fix: temporary authentication & authentication with biometrics under states (#24)
  • Loading branch information
SantiagoGaonaC authored Oct 10, 2023
1 parent 55d934c commit b9e87f4
Show file tree
Hide file tree
Showing 30 changed files with 810 additions and 227 deletions.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
buildscript {
buildscript{
ext.kotlin_version = '1.7.10'
repositories {
google()
Expand Down
5 changes: 3 additions & 2 deletions lib/config/router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ final goRouterProvider = Provider((ref) {
return '/login';
}

if (authStatus == AuthStatus.authenticated) {
if ((authStatus == AuthStatus.authenticated)) {
if (isGoingTo == '/login' ||
isGoingTo == '/register' ||
isGoingTo == '/check-auth-status') {
Expand All @@ -64,5 +64,6 @@ final goRouterProvider = Provider((ref) {

return null;
},

);
});
});
10 changes: 10 additions & 0 deletions lib/features/auth/domain/entities/folder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Folder{
//final int id;
final String name;
final int color;

Folder({
required this.name,
required this.color,
});
}
2 changes: 1 addition & 1 deletion lib/features/auth/domain/entities/user.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class User {
//final String uuid;
final String? username;
String? username;
//final int statusCode;
//final String? message;
final String token;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class AuthDataSourceImpl extends AuthDataSource {
@override
Future<User> checkAuthStatus(String token) async {
try {
final response = await dio.post('/refreshtoken',
final response = await dio.post('/auth/refresh',
data: {'token': token},
options: Options(headers: {'Authorization': 'Bearer $token'}));
final user = UserMapper.userJsonToEntity(response.data);
Expand All @@ -30,13 +30,15 @@ class AuthDataSourceImpl extends AuthDataSource {
@override
Future<User> login(String username, String password) async {
try {
final responde = await dio
.post('/login', data: {'username': username, 'password': password});
final responde = await dio.post('/auth/login',
data: {'username': username, 'password': password});
final user = UserMapper.userJsonToEntity(responde.data);
return user;
} on DioException catch (e) {
if (e.response?.statusCode == 401) {
throw CustomError(e.response?.data['message'] ?? 'Credentials wrong');
throw CustomError(
/*e.response?.data ?? */
'Username or Password wrong');
}
if (e.type == DioExceptionType.connectionTimeout) {
throw CustomError('Review your internet connection');
Expand All @@ -48,8 +50,24 @@ class AuthDataSourceImpl extends AuthDataSource {
}

@override
Future<User> register(String username, String password) {
// TODO: implement register
throw UnimplementedError();
Future<User> register(String username, String password) async {
try {
final responde = await dio.post('/account/register',
data: {'username': username, 'password': password});
final user = UserMapper.userJsonToEntity(responde.data);
return user;
} on DioException catch (e) {
if (e.response?.statusCode == 409) {
throw CustomError(
/*e.response?.data['message'] ?? */
'Username already registered');
}
if (e.type == DioExceptionType.connectionTimeout) {
throw CustomError('Review your internet connection');
}
throw Exception();
} catch (e) {
throw Exception();
}
}
}
14 changes: 6 additions & 8 deletions lib/features/auth/infrastructure/errors/auth_errors.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
class WrongCredentials implements Exception {

}

class InvalidToken implements Exception {

final String message;

WrongCredentials(this.message);
}

class ConectionTimeout implements Exception {
class InvalidToken implements Exception {}

}
class ConectionTimeout implements Exception {}

class CustomError implements Exception {
final String message;
Expand All @@ -19,4 +17,4 @@ class CustomError implements Exception {
CustomError(this.message, [this.loggedRequired = false]);
*/
CustomError(this.message);
}
}
110 changes: 87 additions & 23 deletions lib/features/auth/presentation/providers/auth_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ Nos permite a nosotros llegar a nuestro backend directamente
(Repositorio -> DataSource -> Backend)
****************************************************************
DataSource tiene las conexiones e implementaciones necesarias
token -> biometric activate
tempToken -> login/register
*/

import 'dart:async';

import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:local_auth/local_auth.dart';
Expand All @@ -33,12 +34,10 @@ class AuthNotifier extends StateNotifier<AuthState> {
final AuthRepository authRepository;
final KeyValueStorageService keyValueStorageService;
final LocalAuthentication auth = LocalAuthentication();
final Function? closeModalCallback;

AuthNotifier({
required this.authRepository,
required this.keyValueStorageService,
this.closeModalCallback,
}) : super(AuthState()) {
checkAuthStatus();
} //no hace falta mandar nada porque todo son valores opcinonales
Expand All @@ -49,16 +48,15 @@ class AuthNotifier extends StateNotifier<AuthState> {
await Future.delayed(const Duration(milliseconds: 500));
try {
final user = await authRepository.login(username, password);
_setUsername(username);
//De momento el repositorio no devuelve el username solo token
/// se asigna el username al User
user.username = username;
if (biometric == false) {
_setTempLoggedUser(user);
}
if (biometric == true && state.user != null) {
_setBiometricLoggedUser(user);
}
if (closeModalCallback != null) {
closeModalCallback!();
}
} on CustomError catch (e) {
if (biometric == false) {
logout(e.message);
Expand All @@ -74,28 +72,74 @@ class AuthNotifier extends StateNotifier<AuthState> {
}
}

//TODO: implementar el registro
void registerUser(String email, String password) async {}
Future<void> registerUser(String username, String password) async {
await Future.delayed(const Duration(milliseconds: 500));
try {
final user = await authRepository.register(username, password);
user.username = username;
_setTempLoggedUser(user);
} on CustomError catch (e) {
noRegister(e.message);
} catch (e) {
noRegister('Something went wrong');
}
}

void noRegister([String? errorMessage]) async {
state = state.copyWith(
user: null,
errorMessage: errorMessage,
authStatus: AuthStatus.notAuthenticated);
}

//ver si es valido el token que contiene el usuario
void checkAuthStatus() async {
final token = await keyValueStorageService.getValue<String>('token');
if (token == null) return logout();
//Manejamos los estados de auth dependiendo del token si existe o no
//Si existe el token, lo verificamos
//Primero intentamos con el token de biometría
final biometricToken =
await keyValueStorageService.getValue<String>('token');
if (biometricToken != null) {
_verifyToken(biometricToken);
return;
}
//Después intentamos con el token temporal
final tempToken =
await keyValueStorageService.getValue<String>('tempToken');
if (tempToken != null) {
// Se hace el logout para que se borre el token temporal
// Si desea un usuario temp volver a entrar se borra
// Si se quiere cambiar el estado entonces hacer un _verifyTokenTemp(tempToken)
logout();
//_verifyToken(tempToken);
return;
}
//Si llegamos hasta aquí, no hay token.
logout();
}

void _verifyToken(String token) async {
try {
final user = await authRepository.checkAuthStatus(token);
// Set new token
await keyValueStorageService.setKeyValue('token', user.token);
_setBiometricLoggedUser(user);
} catch (e) {
logout();
}
}

void _setTempLoggedUser(User user) async {
// Si es temp no se guarda el username pero se guarda el token y el state
await keyValueStorageService.setKeyValue('tempToken', user.token);
state = state.copyWith(
user: user, errorMessage: '', authStatus: AuthStatus.authenticated);
}

Future<void> _setBiometricLoggedUser(User user) async {
String usernameAsString = user.username.toString();
await keyValueStorageService.setKeyValue('token', user.token);
await keyValueStorageService.setKeyValue('username', usernameAsString);
await keyValueStorageService.setKeyValue('hasBiometric', true);
state = state.copyWith(
user: user,
Expand All @@ -108,10 +152,12 @@ class AuthNotifier extends StateNotifier<AuthState> {
void disableBiometric(ref) async {
await keyValueStorageService.removeKey('token');
await keyValueStorageService.removeKey('hasBiometric');
await keyValueStorageService.removeKey('username');
await keyValueStorageService.removeKey('tempToken');
state = state.copyWith(
user: null,
errorMessage: '',
authStatus: AuthStatus.authenticated,
authStatus: AuthStatus.notAuthenticated,
hasBiometric: false);
//Aqui puedo abrir otro modal
// showModalBottomSheet(
Expand All @@ -120,13 +166,17 @@ class AuthNotifier extends StateNotifier<AuthState> {
// builder: (context) => const CustomLogin());
}

void _setUsername(String username) async {
await keyValueStorageService.setKeyValue('username', username);
}

//Elimina el token solamente cuando la biometría no está activa
Future<void> logout([String? errorMessage]) async {
//await keyValueStorageService.removeKey('token');
await keyValueStorageService.removeKey('username');
final hasBiometric = state.hasBiometric;
// Always delete the 'tempToken'
await keyValueStorageService.removeKey('tempToken');
// If the user has biometric, delete the token and username
if (hasBiometric != true) {
await keyValueStorageService.removeKey('token');
await keyValueStorageService.removeKey('username');
}

state = state.copyWith(
user: null,
errorMessage: errorMessage,
Expand All @@ -135,14 +185,15 @@ class AuthNotifier extends StateNotifier<AuthState> {

Future<void> biometricError([String? errorMessage]) async {
state = state.copyWith(
errorMessage: errorMessage,
);
user: null,
errorMessage: errorMessage,
authStatus: AuthStatus.notAuthenticated);
}

// se usa en handleBiometricAuthentication() de side_menu.dart
Future<bool> authWithBiometrics() async {
bool authenticated = false;
try {
state = state.copyWith(authStatus: AuthStatus.checking);
authenticated = await auth.authenticate(
localizedReason:
'Scan your fingerprint (or face or whatever) to authenticate',
Expand All @@ -154,10 +205,14 @@ class AuthNotifier extends StateNotifier<AuthState> {
if (authenticated) {
state = state.copyWith(authStatus: AuthStatus.authenticated);
} else {
state = state.copyWith(authStatus: AuthStatus.notAuthenticated);
state = state.copyWith(
authStatus: AuthStatus.notAuthenticated,
user: null,
errorMessage: 'Biometric authentication failed');
}
} on PlatformException catch (e) {
state = state.copyWith(
user: null,
errorMessage: 'Error - ${e.message}',
authStatus: AuthStatus.notAuthenticated);
}
Expand All @@ -168,13 +223,22 @@ class AuthNotifier extends StateNotifier<AuthState> {
try {
final token = await keyValueStorageService.getValue<String>('token');
if (token == null) {
state = state.copyWith(
errorMessage: 'Token not found',
authStatus: AuthStatus.notAuthenticated,
);
return false;
}
//!TODO: Validar si es necesario guardar el token acá
final user = await authRepository.checkAuthStatus(token);
await keyValueStorageService.setKeyValue('token', user.token);
final username =
await keyValueStorageService.getValue<String>('username');
user.username = username;
state = state.copyWith(
user: user,
errorMessage: '',
authStatus: AuthStatus.authenticated,
authStatus: AuthStatus.checking,
);
authWithBiometrics();
return true;
Expand Down
48 changes: 48 additions & 0 deletions lib/features/auth/presentation/providers/data_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
String termsandcondition = '''
September 2023
Welcome to CapyFile.
These terms and conditions describe the rules and regulations for the use of CapyFile.
By registering for this application, we assume that you accept these terms and conditions in full. Do not continue to use CapyFile if you do not agree to all the terms and conditions set forth below
Access
To participate in the CapyFile experience, you must allow the app to obtain information and access:
• Mobile device camera
• Device storage
• Image gallery
Age requirements
To use CapyFile you must be of legal age (18 years old). If for any reason we detect that you do not comply with this requirement, we have the authority to disable your account.
Service
Using CapyFile you can store, send, download, receive and delete files. The files you choose to store are your property, at no time will we claim credits for the content you store, share or receive, in addition to this, we will not use your files for advertising or promotional campaigns.
You have the control to decide who accesses your files, if you decide to share any files, the user who receives the content must be registered in our application, in addition to being logged in to view the file and enjoy the other features offered by CapyFile.
Restrictions
We may access your files for the sole purpose of ensuring that the content does not violate the rules for Prohibited content and if we find a violation, you may not be able to upload files, share them, or even your account may be inactive.
It is important that you know the content not allowed so as not to fall into infractions that bring sanctions that may bother you.
Inactivity
To keep your account active, you need to use the app at least once every 6 months, Otherwise, we have the permission to inactivate your account which gives way to delete the files and in general all the content related to your account.
Prohibited content
You have the freedom to use CapyFile to upload and share content from different areas, however, it is not allowed to upload and share content related to
• Violent content.
• Harassment and threats.
• Sexual exploitation and abuse.
• Non-consensual explicit images.
• Unauthorized images of minors.
• Criminal activity.
• Identity theft.
• Malicious software.
If we detect content of the points exposed above, your account will be inactive and the content you have uploaded to your account, which relates to the topics not allowed, as well as files that do not contain these themes will be deleted.
Storage.
Each user has a storage limit to upload files and save them in CapyFile, if you exceed this limit, you will not be able to upload more files to your account, in the same way, you always have the option to delete to manage your storage.
''';
Loading

0 comments on commit b9e87f4

Please sign in to comment.