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.

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