From 469381f5952e19a4aedef5d20116134b224f42d3 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Wed, 22 May 2024 13:03:44 +0200 Subject: [PATCH] Add an API to reset the password --- .env | 1 + Containerfile | 8 +- lib/api/grpc/accounts.dart | 30 +++- lib/components/environment_card.dart | 4 +- lib/components/environment_preview.dart | 13 +- lib/components/login_form.dart | 193 +++++++++++++++++++++++- lib/main.dart | 7 +- pubspec.lock | 14 +- pubspec.yaml | 2 + 9 files changed, 242 insertions(+), 30 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..ba7db22 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +SOFTPLAYER_BACKEND_URL=https://softplayer-backend.badhouseplants.net:8080 diff --git a/Containerfile b/Containerfile index c9973ef..f50acb0 100644 --- a/Containerfile +++ b/Containerfile @@ -1,7 +1,4 @@ -# Environemnt to install flutter and build web FROM debian:latest AS build-env - -# install all needed stuff RUN apt-get update RUN apt-get install -y curl tar xz-utils git @@ -13,12 +10,11 @@ ARG APP=/app/ RUN curl -l -o /tmp/flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz RUN tar -xf /tmp/flutter.tar.xz -C /usr/local - ENV PATH="$FLUTTER_SDK/bin:$FLUTTER_SDK/bin/cache/dart-sdk/bin:${PATH}" -RUN mkdir $APP -COPY . $APP WORKDIR $APP +COPY . $APP + RUN flutter build web --release # once heare the app will be compiled and ready to deploy diff --git a/lib/api/grpc/accounts.dart b/lib/api/grpc/accounts.dart index d8ae062..0fb6882 100644 --- a/lib/api/grpc/accounts.dart +++ b/lib/api/grpc/accounts.dart @@ -1,5 +1,4 @@ import 'package:grpc/grpc_web.dart'; -import 'package:softplayer_dart_proto/accounts/accounts_v1.pbgrpc.dart'; import 'package:softplayer_dart_proto/main.dart'; class AccountLocalData { @@ -57,4 +56,33 @@ class AccountsGrpc { rethrow; } } + + Future resetPassword(String username, String email) async { + final request = AccountData( + name: username, + email: email, + ); + try { + final response = await accountsStub.resetPassword(request); + return response; + } catch (e) { + rethrow; + } + } + + Future newPassword( + String username, String code, String newPassword) async { + final request = AccountWithPasswordAndCode( + data: AccountData( + name: username, + ), + code: code, + password: AccountPassword(password: newPassword)); + try { + final response = await accountsStub.newPassword(request); + return response; + } catch (e) { + rethrow; + } + } } diff --git a/lib/components/environment_card.dart b/lib/components/environment_card.dart index 948a9cf..bfcf857 100644 --- a/lib/components/environment_card.dart +++ b/lib/components/environment_card.dart @@ -53,8 +53,8 @@ class _EnvirnomentCardState extends State { position: RelativeRect.fromSize(Rect.largest, Size.infinite), context: context, items: [ - PopupMenuItem(child: Text("test")), - PopupMenuItem(child: Text("text")), + const PopupMenuItem(child: Text("test")), + const PopupMenuItem(child: Text("text")), ], ), onTap: () => showDialog( diff --git a/lib/components/environment_preview.dart b/lib/components/environment_preview.dart index d684283..685a453 100644 --- a/lib/components/environment_preview.dart +++ b/lib/components/environment_preview.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:softplayer_web/api/grpc/environments.dart'; import 'package:softplayer_web/helpers/providers/common.dart'; @@ -61,7 +60,7 @@ class _EnvirnomentPreviewState extends State { ]), ], ), - Divider(), + const Divider(), Table( border: const TableBorder( bottom: BorderSide.none, @@ -69,13 +68,13 @@ class _EnvirnomentPreviewState extends State { right: BorderSide.none, top: BorderSide.none, ), - children: [ + children: const [ TableRow(children: [ - const Text("Price per hour"), + Text("Price per hour"), Text("0.52"), ]), TableRow(children: [ - const Text("Current usage"), + Text("Current usage"), Text("10.5"), ]), ], @@ -84,8 +83,8 @@ class _EnvirnomentPreviewState extends State { ), ), actions: [ - TextButton(onPressed: () => print("lala"), child: Text("test")), - TextButton(onPressed: () => print("lala"), child: Text("test")), + TextButton(onPressed: () => print("lala"), child: const Text("test")), + TextButton(onPressed: () => print("lala"), child: const Text("test")), ]); } } diff --git a/lib/components/login_form.dart b/lib/components/login_form.dart index a990818..f909198 100644 --- a/lib/components/login_form.dart +++ b/lib/components/login_form.dart @@ -16,7 +16,7 @@ class LoginForm extends StatefulWidget { State createState() => _LoginFormState(); } -enum Action { singIn, signUp } +enum Action { singIn, signUp, resetPassword } class _LoginFormState extends State { final _formKey = GlobalKey(); @@ -25,9 +25,11 @@ class _LoginFormState extends State { final passwordCtrl = TextEditingController(); final passwordVerifyCtrl = TextEditingController(); final emailCtrl = TextEditingController(); + final codeCtrl = TextEditingController(); late AccountsGrpc accountsGrpc; Action action = Action.singIn; + bool codeEnabled = false; static const dialogName = "Login"; void submitSignUp() { @@ -71,7 +73,6 @@ class _LoginFormState extends State { widget.notifyParent(); // Navigator.of(context, rootNavigator: true).pop(); }).catchError((e) { - print(e); GrpcError error = e; String msg; if (error.message != null) { @@ -90,6 +91,61 @@ class _LoginFormState extends State { } } + void subminResetPassword() { + // Validate returns true if the form is valid, or false otherwise. + if (_formKey.currentState!.validate()) { + final username = usernameCtrl.text; + final password = passwordCtrl.text; + final code = codeCtrl.text; + accountsGrpc.newPassword(username, code, password).then((rs) { + action = Action.singIn; + // Navigator.of(context, rootNavigator: true).pop(); + }).catchError((e) { + GrpcError error = e; + String msg; + if (error.message != null) { + msg = error.message!; + } else { + msg = error.toString(); + } + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(msg), + backgroundColor: Colors.red, + showCloseIcon: true, + behavior: SnackBarBehavior.floating, + )); + passwordCtrl.clear(); + }); + } + } + + void sendCode() { + if (_formKey.currentState!.validate()) { + final username = usernameCtrl.text; + final email = emailCtrl.text; + accountsGrpc + .resetPassword(username, email) + .then((_) => setState(() { + codeEnabled = true; + })) + .catchError((e) { + GrpcError error = e; + String msg; + if (error.message != null) { + msg = error.message!; + } else { + msg = error.toString(); + } + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(msg), + backgroundColor: Colors.red, + showCloseIcon: true, + behavior: SnackBarBehavior.floating, + )); + }); + } + } + Widget signInForm() => SizedBox( width: 420, height: 280, @@ -122,6 +178,11 @@ class _LoginFormState extends State { cursorHeight: 18, cursorRadius: const Radius.circular(10), ), + TextButton( + onPressed: () => setState(() { + action = Action.resetPassword; + }), + child: const Text("reset yomakeur password")), ])))); List signInActions() => [ @@ -142,13 +203,33 @@ class _LoginFormState extends State { onPressed: () => setState(() { action = Action.singIn; }), - child: const Text('Sing In'), + child: const Text('Sign In'), ), TextButton( onPressed: submitSignUp, child: const Text('OK'), ), ]; + + List resetPasswordActions() => [ + TextButton( + onPressed: () => setState(() { + action = Action.singIn; + }), + child: const Text('Sign In'), + ), + TextButton( + onPressed: () => setState(() { + action = Action.signUp; + }), + child: const Text('Sing Up'), + ), + TextButton( + onPressed: subminResetPassword, + child: const Text('OK'), + ), + ]; + Widget signUpForm() => SizedBox( width: 420, height: 280, @@ -207,6 +288,86 @@ class _LoginFormState extends State { cursorRadius: const Radius.circular(10), ), ])))); + Widget resetPasswordForm() => SizedBox( + width: 420, + height: 420, + child: Form( + key: _formKey, + child: Center( + child: Column(children: [ + TextFormField( + // onFieldSubmitted: (value) => submitSignUp(), + autofocus: true, + controller: usernameCtrl, + decoration: const InputDecoration( + hintText: "Enter your username", + icon: Icon(Icons.account_circle), + label: Text("Username"), + ), + cursorWidth: 1, + cursorHeight: 18, + cursorRadius: const Radius.circular(10), + ), + TextFormField( + // onFieldSubmitted: (value) => submitSignUp(), + controller: emailCtrl, + autofocus: true, + decoration: const InputDecoration( + hintText: "Enter your email", + icon: Icon(Icons.email), + label: Text("Email"), + ), + cursorWidth: 1, + cursorHeight: 18, + cursorRadius: const Radius.circular(10), + ), + TextButton( + onPressed: () => sendCode(), child: const Text("send code to email")), + TextFormField( + // onFieldSubmitted: (value) => submitSignUp(), + controller: codeCtrl, + autofocus: false, + enabled: codeEnabled, + decoration: const InputDecoration( + hintText: "Enter code that you've received via the email", + icon: Icon(Icons.numbers), + label: Text("Code"), + ), + cursorWidth: 1, + cursorHeight: 18, + cursorRadius: const Radius.circular(10), + ), + TextFormField( + // onFieldSubmitted: (value) => submitSignUp(), + controller: passwordCtrl, + autofocus: false, + enabled: codeEnabled, + obscureText: true, + decoration: const InputDecoration( + hintText: "Enter a new password", + icon: Icon(Icons.password), + label: Text("Password"), + ), + cursorWidth: 1, + cursorHeight: 18, + cursorRadius: const Radius.circular(10), + ), + TextFormField( + // onFieldSubmitted: (value) => submitSignUp(), + controller: passwordVerifyCtrl, + autofocus: false, + enabled: codeEnabled, + obscureText: true, + decoration: const InputDecoration( + hintText: "Enter a new password", + icon: Icon(Icons.password), + label: Text("Password"), + ), + cursorWidth: 1, + cursorHeight: 18, + cursorRadius: const Radius.circular(10), + ), + ])))); @override void initState() { @@ -217,8 +378,26 @@ class _LoginFormState extends State { @override Widget build(BuildContext context) => AlertDialog( - title: const Text(dialogName), - content: action == Action.singIn ? signInForm() : signUpForm(), - actions: action == Action.singIn ? signInActions() : signUpActions(), - ); + title: const Text(dialogName), + // content: action == Action.singIn ? signInForm() : signUpForm(), + content: () { + switch (action) { + case Action.signUp: + return signUpForm(); + case Action.resetPassword: + return resetPasswordForm(); + default: + return signInForm(); + } + }(), + actions: () { + switch (action) { + case Action.signUp: + return signUpActions(); + case Action.resetPassword: + return resetPasswordActions(); + default: + return signInActions(); + } + }()); } diff --git a/lib/main.dart b/lib/main.dart index 2bb4f2a..e2c89aa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:html'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter/material.dart'; import 'package:grpc/grpc_web.dart'; import 'package:softplayer_web/api/grpc/accounts.dart'; @@ -9,10 +10,8 @@ import 'package:softplayer_web/components/environments.dart'; import 'package:softplayer_web/components/login_form.dart'; void main() async { - const String backendURL = String.fromEnvironment( - 'SOFTPLAYER_BACKEND_URL', - defaultValue: 'https://softplayer-backend.badhouseplants.net:8080', - ); + await dotenv.load(fileName: ".env"); + String backendURL = dotenv.env['SOFTPLAYER_BACKEND_URL']!; GrpcWebClientChannel grpcChannel = GrpcWebClientChannel.xhr(Uri.parse(backendURL)); diff --git a/pubspec.lock b/pubspec.lock index 96f3714..096e92c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: archive - sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373" + sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 url: "https://pub.dev" source: hosted - version: "3.5.0" + version: "3.5.1" args: dependency: transitive description: @@ -102,6 +102,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" flutter_lints: dependency: "direct dev" description: @@ -245,7 +253,7 @@ packages: description: path: "." ref: main - resolved-ref: "3bf6e1bef80350848f0f0407685e4eaf868d4c7e" + resolved-ref: "5ee911c92eee4ee7db58a2da0407da4f7db2f994" url: "https://git.badhouseplants.net/softplayer/softplayer-dart-proto.git" source: git version: "1.0.0+1" diff --git a/pubspec.yaml b/pubspec.yaml index 23607ed..52ee53e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: grpc: ^3.2.4 http: ^1.2.1 dio: ^5.4.2 + flutter_dotenv: ^5.1.0 dev_dependencies: flutter_test: @@ -28,3 +29,4 @@ flutter: uses-material-design: true assets: - assets/ + - .env \ No newline at end of file