Add an API to reset the password

This commit is contained in:
Nikolai Rodionov 2024-05-22 13:03:44 +02:00
parent 20b2f7df0a
commit 469381f595
Signed by: allanger
GPG Key ID: 0AA46A90E25592AD
9 changed files with 242 additions and 30 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
SOFTPLAYER_BACKEND_URL=https://softplayer-backend.badhouseplants.net:8080

View File

@ -1,7 +1,4 @@
# Environemnt to install flutter and build web
FROM debian:latest AS build-env FROM debian:latest AS build-env
# install all needed stuff
RUN apt-get update RUN apt-get update
RUN apt-get install -y curl tar xz-utils git 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 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 RUN tar -xf /tmp/flutter.tar.xz -C /usr/local
ENV PATH="$FLUTTER_SDK/bin:$FLUTTER_SDK/bin/cache/dart-sdk/bin:${PATH}" ENV PATH="$FLUTTER_SDK/bin:$FLUTTER_SDK/bin/cache/dart-sdk/bin:${PATH}"
RUN mkdir $APP
COPY . $APP
WORKDIR $APP WORKDIR $APP
COPY . $APP
RUN flutter build web --release RUN flutter build web --release
# once heare the app will be compiled and ready to deploy # once heare the app will be compiled and ready to deploy

View File

@ -1,5 +1,4 @@
import 'package:grpc/grpc_web.dart'; import 'package:grpc/grpc_web.dart';
import 'package:softplayer_dart_proto/accounts/accounts_v1.pbgrpc.dart';
import 'package:softplayer_dart_proto/main.dart'; import 'package:softplayer_dart_proto/main.dart';
class AccountLocalData { class AccountLocalData {
@ -57,4 +56,33 @@ class AccountsGrpc {
rethrow; rethrow;
} }
} }
Future<Empty> 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<Empty> 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;
}
}
} }

View File

@ -53,8 +53,8 @@ class _EnvirnomentCardState extends State<EnvirnomentCard> {
position: RelativeRect.fromSize(Rect.largest, Size.infinite), position: RelativeRect.fromSize(Rect.largest, Size.infinite),
context: context, context: context,
items: [ items: [
PopupMenuItem(child: Text("test")), const PopupMenuItem(child: Text("test")),
PopupMenuItem(child: Text("text")), const PopupMenuItem(child: Text("text")),
], ],
), ),
onTap: () => showDialog( onTap: () => showDialog(

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:softplayer_web/api/grpc/environments.dart'; import 'package:softplayer_web/api/grpc/environments.dart';
import 'package:softplayer_web/helpers/providers/common.dart'; import 'package:softplayer_web/helpers/providers/common.dart';
@ -61,7 +60,7 @@ class _EnvirnomentPreviewState extends State<EnvirnomentPreiview> {
]), ]),
], ],
), ),
Divider(), const Divider(),
Table( Table(
border: const TableBorder( border: const TableBorder(
bottom: BorderSide.none, bottom: BorderSide.none,
@ -69,13 +68,13 @@ class _EnvirnomentPreviewState extends State<EnvirnomentPreiview> {
right: BorderSide.none, right: BorderSide.none,
top: BorderSide.none, top: BorderSide.none,
), ),
children: [ children: const [
TableRow(children: [ TableRow(children: [
const Text("Price per hour"), Text("Price per hour"),
Text("0.52"), Text("0.52"),
]), ]),
TableRow(children: [ TableRow(children: [
const Text("Current usage"), Text("Current usage"),
Text("10.5"), Text("10.5"),
]), ]),
], ],
@ -84,8 +83,8 @@ class _EnvirnomentPreviewState extends State<EnvirnomentPreiview> {
), ),
), ),
actions: [ actions: [
TextButton(onPressed: () => print("lala"), child: Text("test")), TextButton(onPressed: () => print("lala"), child: const Text("test")),
TextButton(onPressed: () => print("lala"), child: Text("test")), TextButton(onPressed: () => print("lala"), child: const Text("test")),
]); ]);
} }
} }

View File

@ -16,7 +16,7 @@ class LoginForm extends StatefulWidget {
State<StatefulWidget> createState() => _LoginFormState(); State<StatefulWidget> createState() => _LoginFormState();
} }
enum Action { singIn, signUp } enum Action { singIn, signUp, resetPassword }
class _LoginFormState extends State<LoginForm> { class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
@ -25,9 +25,11 @@ class _LoginFormState extends State<LoginForm> {
final passwordCtrl = TextEditingController(); final passwordCtrl = TextEditingController();
final passwordVerifyCtrl = TextEditingController(); final passwordVerifyCtrl = TextEditingController();
final emailCtrl = TextEditingController(); final emailCtrl = TextEditingController();
final codeCtrl = TextEditingController();
late AccountsGrpc accountsGrpc; late AccountsGrpc accountsGrpc;
Action action = Action.singIn; Action action = Action.singIn;
bool codeEnabled = false;
static const dialogName = "Login"; static const dialogName = "Login";
void submitSignUp() { void submitSignUp() {
@ -71,7 +73,6 @@ class _LoginFormState extends State<LoginForm> {
widget.notifyParent(); widget.notifyParent();
// Navigator.of(context, rootNavigator: true).pop(); // Navigator.of(context, rootNavigator: true).pop();
}).catchError((e) { }).catchError((e) {
print(e);
GrpcError error = e; GrpcError error = e;
String msg; String msg;
if (error.message != null) { if (error.message != null) {
@ -90,6 +91,61 @@ class _LoginFormState extends State<LoginForm> {
} }
} }
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( Widget signInForm() => SizedBox(
width: 420, width: 420,
height: 280, height: 280,
@ -122,6 +178,11 @@ class _LoginFormState extends State<LoginForm> {
cursorHeight: 18, cursorHeight: 18,
cursorRadius: const Radius.circular(10), cursorRadius: const Radius.circular(10),
), ),
TextButton(
onPressed: () => setState(() {
action = Action.resetPassword;
}),
child: const Text("reset yomakeur password")),
])))); ]))));
List<Widget> signInActions() => [ List<Widget> signInActions() => [
@ -142,13 +203,33 @@ class _LoginFormState extends State<LoginForm> {
onPressed: () => setState(() { onPressed: () => setState(() {
action = Action.singIn; action = Action.singIn;
}), }),
child: const Text('Sing In'), child: const Text('Sign In'),
), ),
TextButton( TextButton(
onPressed: submitSignUp, onPressed: submitSignUp,
child: const Text('OK'), child: const Text('OK'),
), ),
]; ];
List<Widget> 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( Widget signUpForm() => SizedBox(
width: 420, width: 420,
height: 280, height: 280,
@ -207,6 +288,86 @@ class _LoginFormState extends State<LoginForm> {
cursorRadius: const Radius.circular(10), 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 @override
void initState() { void initState() {
@ -218,7 +379,25 @@ class _LoginFormState extends State<LoginForm> {
@override @override
Widget build(BuildContext context) => AlertDialog( Widget build(BuildContext context) => AlertDialog(
title: const Text(dialogName), title: const Text(dialogName),
content: action == Action.singIn ? signInForm() : signUpForm(), // content: action == Action.singIn ? signInForm() : signUpForm(),
actions: action == Action.singIn ? signInActions() : signUpActions(), 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();
}
}());
} }

View File

@ -1,5 +1,6 @@
import 'dart:html'; import 'dart:html';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:grpc/grpc_web.dart'; import 'package:grpc/grpc_web.dart';
import 'package:softplayer_web/api/grpc/accounts.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'; import 'package:softplayer_web/components/login_form.dart';
void main() async { void main() async {
const String backendURL = String.fromEnvironment( await dotenv.load(fileName: ".env");
'SOFTPLAYER_BACKEND_URL', String backendURL = dotenv.env['SOFTPLAYER_BACKEND_URL']!;
defaultValue: 'https://softplayer-backend.badhouseplants.net:8080',
);
GrpcWebClientChannel grpcChannel = GrpcWebClientChannel grpcChannel =
GrpcWebClientChannel.xhr(Uri.parse(backendURL)); GrpcWebClientChannel.xhr(Uri.parse(backendURL));

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373" sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.0" version: "3.5.1"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -102,6 +102,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -245,7 +253,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "3bf6e1bef80350848f0f0407685e4eaf868d4c7e" resolved-ref: "5ee911c92eee4ee7db58a2da0407da4f7db2f994"
url: "https://git.badhouseplants.net/softplayer/softplayer-dart-proto.git" url: "https://git.badhouseplants.net/softplayer/softplayer-dart-proto.git"
source: git source: git
version: "1.0.0+1" version: "1.0.0+1"

View File

@ -18,6 +18,7 @@ dependencies:
grpc: ^3.2.4 grpc: ^3.2.4
http: ^1.2.1 http: ^1.2.1
dio: ^5.4.2 dio: ^5.4.2
flutter_dotenv: ^5.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -28,3 +29,4 @@ flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- assets/ - assets/
- .env