@ -1,44 +1,14 @@
import ' dart:convert ' ;
import ' dart:convert ' ;
import ' dart:typed_data ' ;
import ' dart:typed_data ' ;
import ' package:Envelope/components/flash_message.dart ' ;
import ' package:Envelope/models/my_profile.dart ' ;
import ' package:flutter/material.dart ' ;
import ' package:flutter/material.dart ' ;
import ' package:flutter_dotenv/flutter_dotenv.dart ' ;
import ' package:http/http.dart ' as http ;
import ' package:http/http.dart ' as http ;
import ' /utils/encryption/aes_helper.dart ' ;
import ' /utils/encryption/aes_helper.dart ' ;
import ' /utils/encryption/crypto_utils.dart ' ;
import ' /utils/encryption/crypto_utils.dart ' ;
Future < SignupResponse > signUp ( context , String username , String password , String confirmPassword ) async {
var keyPair = CryptoUtils . generateRSAKeyPair ( ) ;
var rsaPubPem = CryptoUtils . encodeRSAPublicKeyToPem ( keyPair . publicKey ) ;
var rsaPrivPem = CryptoUtils . encodeRSAPrivateKeyToPem ( keyPair . privateKey ) ;
String encRsaPriv = AesHelper . aesEncrypt ( password , Uint8List . fromList ( rsaPrivPem . codeUnits ) ) ;
/ / TODO: Check for timeout here
final resp = await http . post (
Uri . parse ( ' ${ dotenv . env [ " SERVER_URL " ] } api/v1/signup ' ) ,
headers: < String , String > {
' Content-Type ' : ' application/json; charset=UTF-8 ' ,
} ,
body: jsonEncode ( < String , String > {
' username ' : username ,
' password ' : password ,
' confirm_password ' : confirmPassword ,
' asymmetric_public_key ' : rsaPubPem ,
' asymmetric_private_key ' : encRsaPriv ,
} ) ,
) ;
SignupResponse response = SignupResponse . fromJson ( jsonDecode ( resp . body ) ) ;
if ( resp . statusCode ! = 201 ) {
throw Exception ( response . message ) ;
}
return response ;
}
class Signup extends StatelessWidget {
class Signup extends StatelessWidget {
const Signup ( { Key ? key } ) : super ( key: key ) ;
const Signup ( { Key ? key } ) : super ( key: key ) ;
@ -47,17 +17,17 @@ class Signup extends StatelessWidget {
Widget build ( BuildContext context ) {
Widget build ( BuildContext context ) {
return Scaffold (
return Scaffold (
appBar: AppBar (
appBar: AppBar (
title: null ,
automaticallyImplyLeading: true ,
/ / ` true ` if you want Flutter to automatically add Back Button when needed ,
/ / or ` false ` if you want to force your own back button every where
leading: IconButton ( icon: const Icon ( Icons . arrow_back ) ,
onPressed: ( ) = > {
Navigator . pop ( context )
}
) ,
backgroundColor: Colors . transparent ,
shadowColor: Colors . transparent ,
title: null ,
automaticallyImplyLeading: true ,
/ / ` true ` if you want Flutter to automatically add Back Button when needed ,
/ / or ` false ` if you want to force your own back button every where
leading: IconButton ( icon: const Icon ( Icons . arrow_back ) ,
onPressed: ( ) = > {
Navigator . pop ( context )
}
) ,
backgroundColor: Colors . transparent ,
shadowColor: Colors . transparent ,
) ,
) ,
body: const SafeArea (
body: const SafeArea (
child: SignupWidget ( ) ,
child: SignupWidget ( ) ,
@ -77,8 +47,8 @@ class SignupResponse {
factory SignupResponse . fromJson ( Map < String , dynamic > json ) {
factory SignupResponse . fromJson ( Map < String , dynamic > json ) {
return SignupResponse (
return SignupResponse (
status: json [ ' status ' ] ,
message: json [ ' message ' ] ,
status: json [ ' status ' ] ,
message: json [ ' message ' ] ,
) ;
) ;
}
}
}
}
@ -93,22 +63,26 @@ class SignupWidget extends StatefulWidget {
class _SignupWidgetState extends State < SignupWidget > {
class _SignupWidgetState extends State < SignupWidget > {
final _formKey = GlobalKey < FormState > ( ) ;
final _formKey = GlobalKey < FormState > ( ) ;
TextEditingController usernameController = TextEditingController ( ) ;
TextEditingController passwordController = TextEditingController ( ) ;
TextEditingController passwordConfirmController = TextEditingController ( ) ;
final TextEditingController _usernameController = TextEditingController ( ) ;
final TextEditingController _passwordController = TextEditingController ( ) ;
final TextEditingController _passwordConfirmController = TextEditingController ( ) ;
final TextEditingController _serverUrlController = TextEditingController ( ) ;
bool showUrlInput = false ;
final OutlineInputBorder inputBorderStyle = OutlineInputBorder (
borderRadius: BorderRadius . circular ( 5 ) ,
borderSide: const BorderSide (
color: Colors . transparent ,
)
) ;
final TextStyle inputTextStyle = const TextStyle (
fontSize: 18 ,
) ;
@ override
@ override
Widget build ( BuildContext context ) {
Widget build ( BuildContext context ) {
const TextStyle inputTextStyle = TextStyle (
fontSize: 18 ,
) ;
final OutlineInputBorder inputBorderStyle = OutlineInputBorder (
borderRadius: BorderRadius . circular ( 5 ) ,
borderSide: const BorderSide (
color: Colors . transparent ,
)
) ;
final ButtonStyle buttonStyle = ElevatedButton . styleFrom (
final ButtonStyle buttonStyle = ElevatedButton . styleFrom (
primary: Theme . of ( context ) . colorScheme . surface ,
primary: Theme . of ( context ) . colorScheme . surface ,
@ -123,115 +97,209 @@ class _SignupWidgetState extends State<SignupWidget> {
) ;
) ;
return Center (
return Center (
child: SingleChildScrollView (
child: Form (
child: Form (
key: _formKey ,
child: Center (
child: Padding (
padding: const EdgeInsets . only (
left: 20 ,
right: 20 ,
top: 0 ,
bottom: 100 ,
key: _formKey ,
child: Center (
child: Padding (
padding: const EdgeInsets . only (
left: 20 ,
right: 20 ,
top: 0 ,
bottom: 100 ,
) ,
child: Column (
mainAxisAlignment: MainAxisAlignment . center ,
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Text (
' Sign Up ' ,
style: TextStyle (
fontSize: 35 ,
color: Theme . of ( context ) . colorScheme . onBackground ,
) ,
) ,
child: Column (
mainAxisAlignment: MainAxisAlignment . center ,
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Text (
' Sign Up ' ,
style: TextStyle (
fontSize: 35 ,
color: Theme . of ( context ) . colorScheme . onBackground ,
) ,
) ,
const SizedBox ( height: 30 ) ,
TextFormField (
controller: usernameController ,
decoration: InputDecoration (
hintText: ' Username ' ,
enabledBorder: inputBorderStyle ,
focusedBorder: inputBorderStyle ,
) ,
style: inputTextStyle ,
/ / The validator receives the text that the user has entered .
validator: ( value ) {
if ( value = = null | | value . isEmpty ) {
return ' Create a username ' ;
}
return null ;
} ,
) ,
const SizedBox ( height: 10 ) ,
TextFormField (
controller: passwordController ,
obscureText: true ,
enableSuggestions: false ,
autocorrect: false ,
decoration: InputDecoration (
hintText: ' Password ' ,
enabledBorder: inputBorderStyle ,
focusedBorder: inputBorderStyle ,
) ,
style: inputTextStyle ,
/ / The validator receives the text that the user has entered .
validator: ( value ) {
if ( value = = null | | value . isEmpty ) {
return ' Enter a password ' ;
}
return null ;
} ,
) ,
const SizedBox ( height: 10 ) ,
TextFormField (
controller: passwordConfirmController ,
obscureText: true ,
enableSuggestions: false ,
autocorrect: false ,
decoration: InputDecoration (
hintText: ' Password ' ,
enabledBorder: inputBorderStyle ,
focusedBorder: inputBorderStyle ,
) ,
style: inputTextStyle ,
/ / The validator receives the text that the user has entered .
validator: ( value ) {
if ( value = = null | | value . isEmpty ) {
return ' Confirm your password ' ;
}
if ( value ! = passwordController . text ) {
return ' Passwords do not match ' ;
}
return null ;
} ,
) ,
const SizedBox ( height: 15 ) ,
ElevatedButton (
style: buttonStyle ,
onPressed: ( ) {
if ( _formKey . currentState ! . validate ( ) ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
const SnackBar ( content: Text ( ' Processing Data ' ) ) ,
) ;
signUp (
context ,
usernameController . text ,
passwordController . text ,
passwordConfirmController . text
) . then ( ( value ) {
Navigator . of ( context ) . popUntil ( ( route ) = > route . isFirst ) ;
} ) . catchError ( ( error ) {
print ( error ) ; / / TODO: Show error on interface
} ) ;
}
} ,
child: const Text ( ' Submit ' ) ,
) ,
] ,
)
) ,
const SizedBox ( height: 30 ) ,
input (
_usernameController ,
' Username ' ,
false ,
( value ) {
if ( value = = null | | value . isEmpty ) {
return ' Create a username ' ;
}
return null ;
} ,
) ,
const SizedBox ( height: 10 ) ,
input (
_passwordController ,
' Password ' ,
true ,
( value ) {
if ( value = = null | | value . isEmpty ) {
return ' Enter a password ' ;
}
return null ;
} ,
) ,
const SizedBox ( height: 10 ) ,
input (
_passwordConfirmController ,
' Confirm Password ' ,
true ,
( value ) {
if ( value = = null | | value . isEmpty ) {
return ' Confirm your password ' ;
}
if ( value ! = _passwordController . text ) {
return ' Passwords do not match ' ;
}
return null ;
} ,
) ,
const SizedBox ( height: 15 ) ,
serverUrl ( ) ,
const SizedBox ( height: 15 ) ,
ElevatedButton (
style: buttonStyle ,
onPressed: ( ) {
if ( ! _formKey . currentState ! . validate ( ) ) {
return ;
}
ScaffoldMessenger . of ( context ) . showSnackBar (
const SnackBar ( content: Text ( ' Processing Data ' ) ) ,
) ;
signUp ( )
. then ( ( dynamic ) {
Navigator . of ( context ) . popUntil ( ( route ) = > route . isFirst ) ;
} ) . catchError ( ( error ) {
showMessage ( ' Failed to signup to Envelope, please try again later ' , context ) ;
} ) ;
} ,
child: const Text ( ' Submit ' ) ,
) ,
] ,
)
)
)
)
)
)
)
)
) ;
) ;
}
}
Widget input (
TextEditingController textController ,
String hintText ,
bool password ,
String ? Function ( dynamic ) validationFunction ,
) {
return TextFormField (
controller: textController ,
obscureText: password ,
enableSuggestions: false ,
autocorrect: false ,
decoration: InputDecoration (
hintText: hintText ,
enabledBorder: inputBorderStyle ,
focusedBorder: inputBorderStyle ,
) ,
style: inputTextStyle ,
validator: validationFunction ,
) ;
}
Widget serverUrl ( ) {
if ( ! showUrlInput ) {
return
Padding (
padding: const EdgeInsets . only ( top: 0 , bottom: 10 ) ,
child: Row (
children: [
SizedBox (
height: 10 ,
child: IconButton (
onPressed: ( ) {
setState ( ( ) {
showUrlInput = true ;
} ) ;
} ,
icon: Icon (
Icons . edit ,
color: Theme . of ( context ) . disabledColor ,
) ,
splashRadius: 2 ,
padding: const EdgeInsets . all ( 2 ) ,
iconSize: 15 ,
) ,
) ,
const SizedBox ( width: 2 ) ,
Column (
children: [
const SizedBox ( height: 10 ) ,
Text (
' Server URL - $ defaultServerUrl ' ,
style: TextStyle (
color: Theme . of ( context ) . disabledColor ,
fontSize: 12 ,
) ,
) ,
] ,
) ,
] ,
) ,
) ;
}
if ( _serverUrlController . text = = ' ' ) {
_serverUrlController . text = defaultServerUrl ;
}
return input (
_serverUrlController ,
' Server URL ' ,
false ,
( dynamic ) {
return null ;
} ,
) ;
}
Future < SignupResponse > signUp ( ) async {
await MyProfile . setServerUrl ( _serverUrlController . text ) ;
var keyPair = CryptoUtils . generateRSAKeyPair ( ) ;
var rsaPubPem = CryptoUtils . encodeRSAPublicKeyToPem ( keyPair . publicKey ) ;
var rsaPrivPem = CryptoUtils . encodeRSAPrivateKeyToPem ( keyPair . privateKey ) ;
String encRsaPriv = AesHelper . aesEncrypt (
_passwordController . text ,
Uint8List . fromList ( rsaPrivPem . codeUnits ) ,
) ;
final resp = await http . post (
await MyProfile . getServerUrl ( ' api/v1/signup ' ) ,
headers: < String , String > {
' Content-Type ' : ' application/json; charset=UTF-8 ' ,
} ,
body: jsonEncode ( < String , String > {
' username ' : _usernameController . text ,
' password ' : _passwordController . text ,
' confirm_password ' : _passwordConfirmController . text ,
' asymmetric_public_key ' : rsaPubPem ,
' asymmetric_private_key ' : encRsaPriv ,
} ) ,
) ;
SignupResponse response = SignupResponse . fromJson ( jsonDecode ( resp . body ) ) ;
if ( resp . statusCode ! = 201 ) {
throw Exception ( response . message ) ;
}
return response ;
}
}
}