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.

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