Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
9.7 KiB

  1. import 'dart:convert';
  2. import 'package:flutter/material.dart';
  3. import 'package:http/http.dart' as http;
  4. import 'package:shared_preferences/shared_preferences.dart';
  5. import '/utils/encryption/rsa_key_helper.dart';
  6. import '/utils/encryption/aes_helper.dart';
  7. import '/utils/storage/encryption_keys.dart';
  8. class LoginResponse {
  9. final String status;
  10. final String message;
  11. final String asymmetricPublicKey;
  12. final String asymmetricPrivateKey;
  13. const LoginResponse({
  14. required this.status,
  15. required this.message,
  16. required this.asymmetricPublicKey,
  17. required this.asymmetricPrivateKey,
  18. });
  19. factory LoginResponse.fromJson(Map<String, dynamic> json) {
  20. return LoginResponse(
  21. status: json['status'],
  22. message: json['message'],
  23. asymmetricPublicKey: json['asymmetric_public_key'],
  24. asymmetricPrivateKey: json['asymmetric_private_key'],
  25. );
  26. }
  27. }
  28. Future<LoginResponse> login(context, String username, String password) async {
  29. final resp = await http.post(
  30. Uri.parse('http://192.168.1.5:8080/api/v1/login'),
  31. headers: <String, String>{
  32. 'Content-Type': 'application/json; charset=UTF-8',
  33. },
  34. body: jsonEncode(<String, String>{
  35. 'username': username,
  36. 'password': password,
  37. }),
  38. );
  39. LoginResponse response = LoginResponse.fromJson(jsonDecode(resp.body));
  40. if (resp.statusCode != 200) {
  41. throw Exception(response.message);
  42. }
  43. var rsaPrivPem = AesHelper.aesDecrypt(password, base64.decode(response.asymmetricPrivateKey));
  44. var rsaPriv = RsaKeyHelper.parsePrivateKeyFromPem(rsaPrivPem);
  45. setPrivateKey(rsaPriv);
  46. final preferences = await SharedPreferences.getInstance();
  47. preferences.setBool('islogin', true);
  48. return response;
  49. }
  50. class Login extends StatelessWidget {
  51. const Login({Key? key}) : super(key: key);
  52. static const String _title = 'Envelope';
  53. @override
  54. Widget build(BuildContext context) {
  55. return Scaffold(
  56. backgroundColor: Colors.cyan,
  57. appBar: AppBar(
  58. title: null,
  59. automaticallyImplyLeading: true,
  60. //`true` if you want Flutter to automatically add Back Button when needed,
  61. //or `false` if you want to force your own back button every where
  62. leading: IconButton(icon: const Icon(Icons.arrow_back),
  63. onPressed:() => {
  64. Navigator.pop(context)
  65. }
  66. )
  67. ),
  68. body: const SafeArea(
  69. child: LoginWidget(),
  70. ),
  71. );
  72. }
  73. }
  74. class LoginWidget extends StatefulWidget {
  75. const LoginWidget({Key? key}) : super(key: key);
  76. @override
  77. State<LoginWidget> createState() => _LoginWidgetState();
  78. }
  79. class _LoginWidgetState extends State<LoginWidget> {
  80. final _formKey = GlobalKey<FormState>();
  81. TextEditingController usernameController = TextEditingController();
  82. TextEditingController passwordController = TextEditingController();
  83. @override
  84. Widget build(BuildContext context) {
  85. const TextStyle _inputTextStyle = TextStyle(fontSize: 18, color: Colors.black);
  86. final ButtonStyle _buttonStyle = ElevatedButton.styleFrom(
  87. primary: Colors.white,
  88. onPrimary: Colors.cyan,
  89. minimumSize: const Size.fromHeight(50),
  90. padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
  91. textStyle: const TextStyle(
  92. fontSize: 20,
  93. fontWeight: FontWeight.bold,
  94. color: Colors.red,
  95. ),
  96. );
  97. return Center(
  98. child: Form(
  99. key: _formKey,
  100. child: Center(
  101. child: Padding(
  102. padding: const EdgeInsets.all(15),
  103. child: Column(
  104. mainAxisAlignment: MainAxisAlignment.center,
  105. crossAxisAlignment: CrossAxisAlignment.center,
  106. children: [
  107. const Text('Login', style: TextStyle(fontSize: 35, color: Colors.white),),
  108. const SizedBox(height: 30),
  109. TextFormField(
  110. controller: usernameController,
  111. decoration: const InputDecoration(
  112. hintText: 'Username',
  113. ),
  114. style: _inputTextStyle,
  115. // The validator receives the text that the user has entered.
  116. validator: (value) {
  117. if (value == null || value.isEmpty) {
  118. return 'Create a username';
  119. }
  120. return null;
  121. },
  122. ),
  123. const SizedBox(height: 5),
  124. TextFormField(
  125. controller: passwordController,
  126. obscureText: true,
  127. enableSuggestions: false,
  128. autocorrect: false,
  129. decoration: const InputDecoration(
  130. hintText: 'Password',
  131. ),
  132. style: _inputTextStyle,
  133. // The validator receives the text that the user has entered.
  134. validator: (value) {
  135. if (value == null || value.isEmpty) {
  136. return 'Enter a password';
  137. }
  138. return null;
  139. },
  140. ),
  141. const SizedBox(height: 5),
  142. ElevatedButton(
  143. style: _buttonStyle,
  144. onPressed: () {
  145. if (_formKey.currentState!.validate()) {
  146. ScaffoldMessenger.of(context).showSnackBar(
  147. const SnackBar(content: Text('Processing Data')),
  148. );
  149. login(
  150. context,
  151. usernameController.text,
  152. passwordController.text,
  153. ).then((value) {
  154. /*
  155. Navigator.of(context).popUntil((route) {
  156. print(route.isFirst);
  157. return route.isFirst;
  158. });
  159. */
  160. Navigator.pushNamedAndRemoveUntil(context, '/home', ModalRoute.withName('/home'));
  161. }).catchError((error) {
  162. print(error); // TODO: Show error on interface
  163. });
  164. }
  165. },
  166. child: const Text('Submit'),
  167. ),
  168. ],
  169. )
  170. )
  171. )
  172. )
  173. );
  174. }
  175. }