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.

234 lines
8.9 KiB

  1. import 'dart:typed_data';
  2. import 'dart:convert';
  3. import 'package:flutter/material.dart';
  4. import 'package:http/http.dart' as http;
  5. import '/utils/encryption/aes_helper.dart';
  6. import '/utils/encryption/crypto_utils.dart';
  7. class SignupResponse {
  8. final String status;
  9. final String message;
  10. const SignupResponse({
  11. required this.status,
  12. required this.message,
  13. });
  14. factory SignupResponse.fromJson(Map<String, dynamic> json) {
  15. return SignupResponse(
  16. status: json['status'],
  17. message: json['message'],
  18. );
  19. }
  20. }
  21. Future<SignupResponse> signUp(context, String username, String password, String confirmPassword) async {
  22. var keyPair = CryptoUtils.generateRSAKeyPair();
  23. var rsaPubPem = CryptoUtils.encodeRSAPublicKeyToPem(keyPair.publicKey);
  24. var rsaPrivPem = CryptoUtils.encodeRSAPrivateKeyToPem(keyPair.privateKey);
  25. var encRsaPriv = AesHelper.aesEncrypt(password, Uint8List.fromList(rsaPrivPem.codeUnits));
  26. // TODO: Check for timeout here
  27. final resp = await http.post(
  28. Uri.parse('http://192.168.1.5:8080/api/v1/signup'),
  29. headers: <String, String>{
  30. 'Content-Type': 'application/json; charset=UTF-8',
  31. },
  32. body: jsonEncode(<String, String>{
  33. 'username': username,
  34. 'password': password,
  35. 'confirm_password': confirmPassword,
  36. 'asymmetric_public_key': rsaPubPem,
  37. 'asymmetric_private_key': encRsaPriv,
  38. }),
  39. );
  40. SignupResponse response = SignupResponse.fromJson(jsonDecode(resp.body));
  41. if (resp.statusCode != 201) {
  42. throw Exception(response.message);
  43. }
  44. return response;
  45. }
  46. class Signup extends StatelessWidget {
  47. const Signup({Key? key}) : super(key: key);
  48. @override
  49. Widget build(BuildContext context) {
  50. return Scaffold(
  51. appBar: AppBar(
  52. title: null,
  53. automaticallyImplyLeading: true,
  54. //`true` if you want Flutter to automatically add Back Button when needed,
  55. //or `false` if you want to force your own back button every where
  56. leading: IconButton(icon: const Icon(Icons.arrow_back),
  57. onPressed:() => {
  58. Navigator.pop(context)
  59. }
  60. ),
  61. backgroundColor: Colors.transparent,
  62. shadowColor: Colors.transparent,
  63. ),
  64. body: const SafeArea(
  65. child: SignupWidget(),
  66. )
  67. );
  68. }
  69. }
  70. class SignupWidget extends StatefulWidget {
  71. const SignupWidget({Key? key}) : super(key: key);
  72. @override
  73. State<SignupWidget> createState() => _SignupWidgetState();
  74. }
  75. class _SignupWidgetState extends State<SignupWidget> {
  76. final _formKey = GlobalKey<FormState>();
  77. TextEditingController usernameController = TextEditingController();
  78. TextEditingController passwordController = TextEditingController();
  79. TextEditingController passwordConfirmController = TextEditingController();
  80. @override
  81. Widget build(BuildContext context) {
  82. const TextStyle inputTextStyle = TextStyle(
  83. fontSize: 18,
  84. );
  85. final OutlineInputBorder inputBorderStyle = OutlineInputBorder(
  86. borderRadius: BorderRadius.circular(5),
  87. borderSide: const BorderSide(
  88. color: Colors.transparent,
  89. )
  90. );
  91. final ButtonStyle buttonStyle = ElevatedButton.styleFrom(
  92. primary: Theme.of(context).colorScheme.surface,
  93. onPrimary: Theme.of(context).colorScheme.onSurface,
  94. minimumSize: const Size.fromHeight(50),
  95. padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
  96. textStyle: TextStyle(
  97. fontSize: 20,
  98. fontWeight: FontWeight.bold,
  99. color: Theme.of(context).colorScheme.error,
  100. ),
  101. );
  102. return Center(
  103. child: Form(
  104. key: _formKey,
  105. child: Center(
  106. child: Padding(
  107. padding: const EdgeInsets.only(
  108. left: 20,
  109. right: 20,
  110. top: 0,
  111. bottom: 100,
  112. ),
  113. child: Column(
  114. mainAxisAlignment: MainAxisAlignment.center,
  115. crossAxisAlignment: CrossAxisAlignment.center,
  116. children: [
  117. Text(
  118. 'Sign Up',
  119. style: TextStyle(
  120. fontSize: 35,
  121. color: Theme.of(context).colorScheme.onBackground,
  122. ),
  123. ),
  124. const SizedBox(height: 30),
  125. TextFormField(
  126. controller: usernameController,
  127. decoration: InputDecoration(
  128. hintText: 'Username',
  129. enabledBorder: inputBorderStyle,
  130. focusedBorder: inputBorderStyle,
  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 'Create a username';
  137. }
  138. return null;
  139. },
  140. ),
  141. const SizedBox(height: 10),
  142. TextFormField(
  143. controller: passwordController,
  144. obscureText: true,
  145. enableSuggestions: false,
  146. autocorrect: false,
  147. decoration: InputDecoration(
  148. hintText: 'Password',
  149. enabledBorder: inputBorderStyle,
  150. focusedBorder: inputBorderStyle,
  151. ),
  152. style: inputTextStyle,
  153. // The validator receives the text that the user has entered.
  154. validator: (value) {
  155. if (value == null || value.isEmpty) {
  156. return 'Enter a password';
  157. }
  158. return null;
  159. },
  160. ),
  161. const SizedBox(height: 10),
  162. TextFormField(
  163. controller: passwordConfirmController,
  164. obscureText: true,
  165. enableSuggestions: false,
  166. autocorrect: false,
  167. decoration: InputDecoration(
  168. hintText: 'Password',
  169. enabledBorder: inputBorderStyle,
  170. focusedBorder: inputBorderStyle,
  171. ),
  172. style: inputTextStyle,
  173. // The validator receives the text that the user has entered.
  174. validator: (value) {
  175. if (value == null || value.isEmpty) {
  176. return 'Confirm your password';
  177. }
  178. if (value != passwordController.text) {
  179. return 'Passwords do not match';
  180. }
  181. return null;
  182. },
  183. ),
  184. const SizedBox(height: 15),
  185. ElevatedButton(
  186. style: buttonStyle,
  187. onPressed: () {
  188. if (_formKey.currentState!.validate()) {
  189. ScaffoldMessenger.of(context).showSnackBar(
  190. const SnackBar(content: Text('Processing Data')),
  191. );
  192. signUp(
  193. context,
  194. usernameController.text,
  195. passwordController.text,
  196. passwordConfirmController.text
  197. ).then((value) {
  198. Navigator.of(context).popUntil((route) => route.isFirst);
  199. }).catchError((error) {
  200. print(error); // TODO: Show error on interface
  201. });
  202. }
  203. },
  204. child: const Text('Submit'),
  205. ),
  206. ],
  207. )
  208. )
  209. )
  210. )
  211. );
  212. }
  213. }