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