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.

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