|
@ -9,198 +9,201 @@ import '/utils/storage/encryption_keys.dart'; |
|
|
import '/utils/storage/session_cookie.dart'; |
|
|
import '/utils/storage/session_cookie.dart'; |
|
|
|
|
|
|
|
|
class LoginResponse { |
|
|
class LoginResponse { |
|
|
final String status; |
|
|
|
|
|
final String message; |
|
|
|
|
|
final String asymmetricPublicKey; |
|
|
|
|
|
final String asymmetricPrivateKey; |
|
|
|
|
|
|
|
|
|
|
|
const LoginResponse({ |
|
|
|
|
|
required this.status, |
|
|
|
|
|
required this.message, |
|
|
|
|
|
required this.asymmetricPublicKey, |
|
|
|
|
|
required this.asymmetricPrivateKey, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
factory LoginResponse.fromJson(Map<String, dynamic> json) { |
|
|
|
|
|
return LoginResponse( |
|
|
|
|
|
status: json['status'], |
|
|
|
|
|
message: json['message'], |
|
|
|
|
|
asymmetricPublicKey: json['asymmetric_public_key'], |
|
|
|
|
|
asymmetricPrivateKey: json['asymmetric_private_key'], |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
final String status; |
|
|
|
|
|
final String message; |
|
|
|
|
|
final String asymmetricPublicKey; |
|
|
|
|
|
final String asymmetricPrivateKey; |
|
|
|
|
|
final String userId; |
|
|
|
|
|
|
|
|
|
|
|
const LoginResponse({ |
|
|
|
|
|
required this.status, |
|
|
|
|
|
required this.message, |
|
|
|
|
|
required this.asymmetricPublicKey, |
|
|
|
|
|
required this.asymmetricPrivateKey, |
|
|
|
|
|
required this.userId, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
factory LoginResponse.fromJson(Map<String, dynamic> json) { |
|
|
|
|
|
return LoginResponse( |
|
|
|
|
|
status: json['status'], |
|
|
|
|
|
message: json['message'], |
|
|
|
|
|
asymmetricPublicKey: json['asymmetric_public_key'], |
|
|
|
|
|
asymmetricPrivateKey: json['asymmetric_private_key'], |
|
|
|
|
|
userId: json['user_id'], |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Future<LoginResponse> login(context, String username, String password) async { |
|
|
Future<LoginResponse> login(context, String username, String password) async { |
|
|
final resp = await http.post( |
|
|
|
|
|
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/login'), |
|
|
|
|
|
headers: <String, String>{ |
|
|
|
|
|
'Content-Type': 'application/json; charset=UTF-8', |
|
|
|
|
|
}, |
|
|
|
|
|
body: jsonEncode(<String, String>{ |
|
|
|
|
|
'username': username, |
|
|
|
|
|
'password': password, |
|
|
|
|
|
}), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
final resp = await http.post( |
|
|
|
|
|
Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/login'), |
|
|
|
|
|
headers: <String, String>{ |
|
|
|
|
|
'Content-Type': 'application/json; charset=UTF-8', |
|
|
|
|
|
}, |
|
|
|
|
|
body: jsonEncode(<String, String>{ |
|
|
|
|
|
'username': username, |
|
|
|
|
|
'password': password, |
|
|
|
|
|
}), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
if (resp.statusCode != 200) { |
|
|
|
|
|
throw Exception(resp.body); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (resp.statusCode != 200) { |
|
|
|
|
|
throw Exception(resp.body); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
String? rawCookie = resp.headers['set-cookie']; |
|
|
|
|
|
if (rawCookie != null) { |
|
|
|
|
|
int index = rawCookie.indexOf(';'); |
|
|
|
|
|
setSessionCookie((index == -1) ? rawCookie : rawCookie.substring(0, index)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
String? rawCookie = resp.headers['set-cookie']; |
|
|
|
|
|
if (rawCookie != null) { |
|
|
|
|
|
int index = rawCookie.indexOf(';'); |
|
|
|
|
|
setSessionCookie((index == -1) ? rawCookie : rawCookie.substring(0, index)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
LoginResponse response = LoginResponse.fromJson(jsonDecode(resp.body)); |
|
|
|
|
|
|
|
|
LoginResponse response = LoginResponse.fromJson(jsonDecode(resp.body)); |
|
|
|
|
|
|
|
|
var rsaPrivPem = AesHelper.aesDecrypt(password, base64.decode(response.asymmetricPrivateKey)); |
|
|
|
|
|
|
|
|
var rsaPrivPem = AesHelper.aesDecrypt(password, base64.decode(response.asymmetricPrivateKey)); |
|
|
|
|
|
|
|
|
debugPrint(rsaPrivPem); |
|
|
|
|
|
|
|
|
debugPrint(rsaPrivPem); |
|
|
|
|
|
|
|
|
var rsaPriv = CryptoUtils.rsaPrivateKeyFromPem(rsaPrivPem); |
|
|
|
|
|
setPrivateKey(rsaPriv); |
|
|
|
|
|
|
|
|
var rsaPriv = CryptoUtils.rsaPrivateKeyFromPem(rsaPrivPem); |
|
|
|
|
|
setPrivateKey(rsaPriv); |
|
|
|
|
|
|
|
|
final preferences = await SharedPreferences.getInstance(); |
|
|
|
|
|
preferences.setBool('islogin', true); |
|
|
|
|
|
|
|
|
final preferences = await SharedPreferences.getInstance(); |
|
|
|
|
|
preferences.setBool('islogin', true); |
|
|
|
|
|
preferences.setString('userId', response.userId); |
|
|
|
|
|
|
|
|
return response; |
|
|
|
|
|
|
|
|
return response; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class Login extends StatelessWidget { |
|
|
class Login extends StatelessWidget { |
|
|
const Login({Key? key}) : super(key: key); |
|
|
|
|
|
|
|
|
|
|
|
static const String _title = 'Envelope'; |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
Widget build(BuildContext context) { |
|
|
|
|
|
return Scaffold( |
|
|
|
|
|
backgroundColor: Colors.cyan, |
|
|
|
|
|
appBar: AppBar( |
|
|
|
|
|
title: null, |
|
|
|
|
|
automaticallyImplyLeading: true, |
|
|
|
|
|
//`true` if you want Flutter to automatically add Back Button when needed, |
|
|
|
|
|
//or `false` if you want to force your own back button every where |
|
|
|
|
|
leading: IconButton(icon: const Icon(Icons.arrow_back), |
|
|
|
|
|
onPressed:() => { |
|
|
|
|
|
Navigator.pop(context) |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
), |
|
|
|
|
|
body: const SafeArea( |
|
|
|
|
|
child: LoginWidget(), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const Login({Key? key}) : super(key: key); |
|
|
|
|
|
|
|
|
|
|
|
static const String _title = 'Envelope'; |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
Widget build(BuildContext context) { |
|
|
|
|
|
return Scaffold( |
|
|
|
|
|
backgroundColor: Colors.cyan, |
|
|
|
|
|
appBar: AppBar( |
|
|
|
|
|
title: null, |
|
|
|
|
|
automaticallyImplyLeading: true, |
|
|
|
|
|
//`true` if you want Flutter to automatically add Back Button when needed, |
|
|
|
|
|
//or `false` if you want to force your own back button every where |
|
|
|
|
|
leading: IconButton(icon: const Icon(Icons.arrow_back), |
|
|
|
|
|
onPressed:() => { |
|
|
|
|
|
Navigator.pop(context) |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
), |
|
|
|
|
|
body: const SafeArea( |
|
|
|
|
|
child: LoginWidget(), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class LoginWidget extends StatefulWidget { |
|
|
class LoginWidget extends StatefulWidget { |
|
|
const LoginWidget({Key? key}) : super(key: key); |
|
|
|
|
|
|
|
|
const LoginWidget({Key? key}) : super(key: key); |
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
State<LoginWidget> createState() => _LoginWidgetState(); |
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
State<LoginWidget> createState() => _LoginWidgetState(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class _LoginWidgetState extends State<LoginWidget> { |
|
|
class _LoginWidgetState extends State<LoginWidget> { |
|
|
final _formKey = GlobalKey<FormState>(); |
|
|
|
|
|
|
|
|
|
|
|
TextEditingController usernameController = TextEditingController(); |
|
|
|
|
|
TextEditingController passwordController = TextEditingController(); |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
Widget build(BuildContext context) { |
|
|
|
|
|
const TextStyle _inputTextStyle = TextStyle(fontSize: 18, color: Colors.black); |
|
|
|
|
|
|
|
|
|
|
|
final ButtonStyle _buttonStyle = ElevatedButton.styleFrom( |
|
|
|
|
|
primary: Colors.white, |
|
|
|
|
|
onPrimary: Colors.cyan, |
|
|
|
|
|
minimumSize: const Size.fromHeight(50), |
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), |
|
|
|
|
|
textStyle: const TextStyle( |
|
|
|
|
|
fontSize: 20, |
|
|
|
|
|
fontWeight: FontWeight.bold, |
|
|
|
|
|
color: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return Center( |
|
|
|
|
|
child: Form( |
|
|
|
|
|
key: _formKey, |
|
|
|
|
|
child: Center( |
|
|
|
|
|
child: Padding( |
|
|
|
|
|
padding: const EdgeInsets.all(15), |
|
|
|
|
|
child: Column( |
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center, |
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center, |
|
|
|
|
|
children: [ |
|
|
|
|
|
const Text('Login', style: TextStyle(fontSize: 35, color: Colors.white),), |
|
|
|
|
|
const SizedBox(height: 30), |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: usernameController, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
hintText: 'Username', |
|
|
|
|
|
), |
|
|
|
|
|
style: _inputTextStyle, |
|
|
|
|
|
// The validator receives the text that the user has entered. |
|
|
|
|
|
validator: (value) { |
|
|
|
|
|
if (value == null || value.isEmpty) { |
|
|
|
|
|
return 'Create a username'; |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 5), |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: passwordController, |
|
|
|
|
|
obscureText: true, |
|
|
|
|
|
enableSuggestions: false, |
|
|
|
|
|
autocorrect: false, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
hintText: 'Password', |
|
|
|
|
|
), |
|
|
|
|
|
style: _inputTextStyle, |
|
|
|
|
|
// The validator receives the text that the user has entered. |
|
|
|
|
|
validator: (value) { |
|
|
|
|
|
if (value == null || value.isEmpty) { |
|
|
|
|
|
return 'Enter a password'; |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 5), |
|
|
|
|
|
ElevatedButton( |
|
|
|
|
|
style: _buttonStyle, |
|
|
|
|
|
onPressed: () { |
|
|
|
|
|
if (_formKey.currentState!.validate()) { |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
const SnackBar(content: Text('Processing Data')), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
login( |
|
|
|
|
|
context, |
|
|
|
|
|
usernameController.text, |
|
|
|
|
|
passwordController.text, |
|
|
|
|
|
).then((value) { |
|
|
|
|
|
Navigator. |
|
|
|
|
|
pushNamedAndRemoveUntil( |
|
|
|
|
|
context, |
|
|
|
|
|
'/home', |
|
|
|
|
|
ModalRoute.withName('/home'), |
|
|
|
|
|
); |
|
|
|
|
|
}).catchError((error) { |
|
|
|
|
|
print(error); // TODO: Show error on interface |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
child: const Text('Submit'), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
final _formKey = GlobalKey<FormState>(); |
|
|
|
|
|
|
|
|
|
|
|
TextEditingController usernameController = TextEditingController(); |
|
|
|
|
|
TextEditingController passwordController = TextEditingController(); |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
Widget build(BuildContext context) { |
|
|
|
|
|
const TextStyle _inputTextStyle = TextStyle(fontSize: 18, color: Colors.black); |
|
|
|
|
|
|
|
|
|
|
|
final ButtonStyle _buttonStyle = ElevatedButton.styleFrom( |
|
|
|
|
|
primary: Colors.white, |
|
|
|
|
|
onPrimary: Colors.cyan, |
|
|
|
|
|
minimumSize: const Size.fromHeight(50), |
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), |
|
|
|
|
|
textStyle: const TextStyle( |
|
|
|
|
|
fontSize: 20, |
|
|
|
|
|
fontWeight: FontWeight.bold, |
|
|
|
|
|
color: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return Center( |
|
|
|
|
|
child: Form( |
|
|
|
|
|
key: _formKey, |
|
|
|
|
|
child: Center( |
|
|
|
|
|
child: Padding( |
|
|
|
|
|
padding: const EdgeInsets.all(15), |
|
|
|
|
|
child: Column( |
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center, |
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center, |
|
|
|
|
|
children: [ |
|
|
|
|
|
const Text('Login', style: TextStyle(fontSize: 35, color: Colors.white),), |
|
|
|
|
|
const SizedBox(height: 30), |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: usernameController, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
hintText: 'Username', |
|
|
|
|
|
), |
|
|
|
|
|
style: _inputTextStyle, |
|
|
|
|
|
// The validator receives the text that the user has entered. |
|
|
|
|
|
validator: (value) { |
|
|
|
|
|
if (value == null || value.isEmpty) { |
|
|
|
|
|
return 'Create a username'; |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 5), |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: passwordController, |
|
|
|
|
|
obscureText: true, |
|
|
|
|
|
enableSuggestions: false, |
|
|
|
|
|
autocorrect: false, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
hintText: 'Password', |
|
|
|
|
|
), |
|
|
|
|
|
style: _inputTextStyle, |
|
|
|
|
|
// The validator receives the text that the user has entered. |
|
|
|
|
|
validator: (value) { |
|
|
|
|
|
if (value == null || value.isEmpty) { |
|
|
|
|
|
return 'Enter a password'; |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 5), |
|
|
|
|
|
ElevatedButton( |
|
|
|
|
|
style: _buttonStyle, |
|
|
|
|
|
onPressed: () { |
|
|
|
|
|
if (_formKey.currentState!.validate()) { |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
const SnackBar(content: Text('Processing Data')), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
login( |
|
|
|
|
|
context, |
|
|
|
|
|
usernameController.text, |
|
|
|
|
|
passwordController.text, |
|
|
|
|
|
).then((value) { |
|
|
|
|
|
Navigator. |
|
|
|
|
|
pushNamedAndRemoveUntil( |
|
|
|
|
|
context, |
|
|
|
|
|
'/home', |
|
|
|
|
|
ModalRoute.withName('/home'), |
|
|
|
|
|
); |
|
|
|
|
|
}).catchError((error) { |
|
|
|
|
|
print(error); // TODO: Show error on interface |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
child: const Text('Submit'), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
} |
|
|
} |