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();
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
return apiKey != null && apiUrl != null;
|
||||
}
|
||||
|
|
|
@ -35,9 +35,9 @@ class MyApp extends StatelessWidget {
|
|||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
MyHomePage({ super.key, required this.title, required this.api });
|
||||
final String title;
|
||||
final FeverAPI api;
|
||||
const MyHomePage({ super.key, required this.title, required this.api });
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
|
@ -52,7 +52,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
),
|
||||
body: widget.api.loggedIn()
|
||||
? 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';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
LoginPage({ super.key });
|
||||
final FeverAPI api;
|
||||
const LoginPage({ super.key, required this.api });
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -20,18 +62,88 @@ class _LoginPageState extends State<LoginPage> {
|
|||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const [
|
||||
Text("Fever compatible API URL"),
|
||||
children: [
|
||||
const Text("Fever compatible API URL"),
|
||||
TextField(
|
||||
controller: _urlController,
|
||||
focusNode: _urlFocus,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.all(8.0),
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.all(8.0),
|
||||
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:feet/pages/login.dart';
|
||||
import '../api/api.dart';
|
||||
|
||||
class LoginPrompt extends StatelessWidget {
|
||||
const LoginPrompt({ super.key });
|
||||
final FeverAPI api;
|
||||
const LoginPrompt({ super.key, required this.api });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -11,9 +13,9 @@ class LoginPrompt extends StatelessWidget {
|
|||
const Text('Please log in'),
|
||||
ElevatedButton(
|
||||
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