Login page checks URL and credentials
This commit is contained in:
parent
3f8599a3e9
commit
e3c15d687b
|
@ -34,6 +34,29 @@ class FeverAPI {
|
||||||
return md5.convert(bytes).toString();
|
return md5.convert(bytes).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the given API URL is a valid Fever API endpoint
|
||||||
|
static Future<bool> isValidAPI(String url) async {
|
||||||
|
try {
|
||||||
|
final response = jsonDecode((await http.post(Uri.parse(url))).body);
|
||||||
|
return response['api_version'] == 3;
|
||||||
|
} catch(e) {
|
||||||
|
print(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether the given API URL is a valid Fever API endpoint and the given API key is authorized to access it
|
||||||
|
static Future<bool> isAuthenticated(String url, String key) async {
|
||||||
|
try {
|
||||||
|
final response = jsonDecode((await http.post(Uri.parse(url), body: { 'api_key': key })).body);
|
||||||
|
return response['api_version'] == 3 && response['auth'] == 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether we have an API URL and API key set
|
||||||
bool loggedIn() {
|
bool loggedIn() {
|
||||||
return apiKey != null && apiUrl != null;
|
return apiKey != null && apiUrl != null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,9 @@ class MyApp extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class MyHomePage extends StatefulWidget {
|
||||||
MyHomePage({ super.key, required this.title, required this.api });
|
|
||||||
final String title;
|
final String title;
|
||||||
final FeverAPI api;
|
final FeverAPI api;
|
||||||
|
const MyHomePage({ super.key, required this.title, required this.api });
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MyHomePage> createState() => _MyHomePageState();
|
State<MyHomePage> createState() => _MyHomePageState();
|
||||||
|
@ -52,7 +52,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
),
|
),
|
||||||
body: widget.api.loggedIn()
|
body: widget.api.loggedIn()
|
||||||
? const Center(child: Text('Meep'))
|
? const Center(child: Text('Meep'))
|
||||||
: const Center(child: LoginPrompt()),
|
: Center(child: LoginPrompt(api: widget.api)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,55 @@
|
||||||
|
import 'package:feet/api/api.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
LoginPage({ super.key });
|
final FeverAPI api;
|
||||||
|
const LoginPage({ super.key, required this.api });
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LoginPage> createState() => _LoginPageState();
|
State<LoginPage> createState() => _LoginPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginPageState extends State<LoginPage> {
|
class _LoginPageState extends State<LoginPage> {
|
||||||
|
bool? _validAPI;
|
||||||
|
|
||||||
|
final _urlController = TextEditingController();
|
||||||
|
final _usernameController = TextEditingController();
|
||||||
|
final _passwordController = TextEditingController();
|
||||||
|
|
||||||
|
final _urlFocus = FocusNode();
|
||||||
|
|
||||||
|
Future<bool> checkUrl() async {
|
||||||
|
if (_urlController.text.isNotEmpty) {
|
||||||
|
var value = await FeverAPI.isValidAPI(_urlController.text);
|
||||||
|
setState(() {
|
||||||
|
_validAPI = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_urlFocus.addListener(() {
|
||||||
|
if (!_urlFocus.hasFocus) {
|
||||||
|
checkUrl();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_urlController.dispose();
|
||||||
|
_usernameController.dispose();
|
||||||
|
_passwordController.dispose();
|
||||||
|
_urlFocus.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -20,18 +62,88 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: const [
|
children: [
|
||||||
Text("Fever compatible API URL"),
|
const Text("Fever compatible API URL"),
|
||||||
TextField(
|
TextField(
|
||||||
|
controller: _urlController,
|
||||||
|
focusNode: _urlFocus,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
contentPadding: EdgeInsets.all(8.0),
|
contentPadding: const EdgeInsets.all(8.0),
|
||||||
hintText: 'https://among-us.morbius.sus/api/fever.php',
|
hintText: 'https://among-us.morbius.sus/api/fever.php',
|
||||||
|
errorText: _validAPI == false ? 'Invalid or unsupported API' : null,
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text("Authentication"),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Flexible(child: TextField(
|
||||||
|
controller: _usernameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
contentPadding: EdgeInsets.all(8.0),
|
||||||
|
hintText: 'Username',
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Flexible(child: TextField(
|
||||||
|
controller: _passwordController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
contentPadding: EdgeInsets.all(8.0),
|
||||||
|
hintText: 'Password',
|
||||||
|
),
|
||||||
|
obscureText: true,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (_urlController.text.isEmpty) return;
|
||||||
|
_validAPI ??= await checkUrl();
|
||||||
|
if (_validAPI != true || _usernameController.text.isEmpty || _passwordController.text.isEmpty) return;
|
||||||
|
|
||||||
|
var apiKey = FeverAPI.generateApiKey(_usernameController.text, _passwordController.text);
|
||||||
|
var isAuthenticated = await FeverAPI.isAuthenticated(_urlController.text, apiKey);
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Authentication failed'),
|
||||||
|
content: const Text('The API URL seems to be valid, but the provided credentials appear to be incorrect.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Got it!')),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.api.apiUrl = _urlController.text;
|
||||||
|
widget.api.apiKey = apiKey;
|
||||||
|
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Continue'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:feet/pages/login.dart';
|
import 'package:feet/pages/login.dart';
|
||||||
|
import '../api/api.dart';
|
||||||
|
|
||||||
class LoginPrompt extends StatelessWidget {
|
class LoginPrompt extends StatelessWidget {
|
||||||
const LoginPrompt({ super.key });
|
final FeverAPI api;
|
||||||
|
const LoginPrompt({ super.key, required this.api });
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -11,9 +13,9 @@ class LoginPrompt extends StatelessWidget {
|
||||||
const Text('Please log in'),
|
const Text('Please log in'),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LoginPage()));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => LoginPage(api: api)));
|
||||||
},
|
},
|
||||||
child: const Text('Log in')
|
child: const Text('Log in'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue