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.

237 lines
8.9 KiB

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