mirror of
https://github.com/HendrikRauh/inventree-app.git
synced 2026-02-04 11:13:17 +00:00
Implement token-based login method and update localization for token prompts
This commit is contained in:
parent
8f18e328e0
commit
2aa745cf69
3 changed files with 188 additions and 69 deletions
55
lib/api.dart
55
lib/api.dart
|
|
@ -2,32 +2,31 @@ import "dart:async";
|
||||||
import "dart:convert";
|
import "dart:convert";
|
||||||
import "dart:io";
|
import "dart:io";
|
||||||
|
|
||||||
|
import "package:cached_network_image/cached_network_image.dart";
|
||||||
import "package:flutter/foundation.dart";
|
import "package:flutter/foundation.dart";
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_cache_manager/flutter_cache_manager.dart";
|
||||||
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
import "package:http/http.dart" as http;
|
import "package:http/http.dart" as http;
|
||||||
import "package:http/io_client.dart";
|
import "package:http/io_client.dart";
|
||||||
import "package:intl/intl.dart";
|
import "package:intl/intl.dart";
|
||||||
import "package:inventree/main.dart";
|
|
||||||
import "package:inventree/widget/progress.dart";
|
|
||||||
import "package:one_context/one_context.dart";
|
|
||||||
import "package:open_filex/open_filex.dart";
|
|
||||||
import "package:cached_network_image/cached_network_image.dart";
|
|
||||||
import "package:flutter/material.dart";
|
|
||||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
|
||||||
import "package:flutter_cache_manager/flutter_cache_manager.dart";
|
|
||||||
import "package:path_provider/path_provider.dart";
|
|
||||||
|
|
||||||
import "package:inventree/api_form.dart";
|
import "package:inventree/api_form.dart";
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/preferences.dart";
|
|
||||||
import "package:inventree/l10.dart";
|
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/inventree/model.dart";
|
import "package:inventree/inventree/model.dart";
|
||||||
import "package:inventree/inventree/notification.dart";
|
import "package:inventree/inventree/notification.dart";
|
||||||
import "package:inventree/inventree/status_codes.dart";
|
|
||||||
import "package:inventree/inventree/sentry.dart";
|
import "package:inventree/inventree/sentry.dart";
|
||||||
|
import "package:inventree/inventree/status_codes.dart";
|
||||||
|
import "package:inventree/l10.dart";
|
||||||
|
import "package:inventree/main.dart";
|
||||||
|
import "package:inventree/preferences.dart";
|
||||||
import "package:inventree/user_profile.dart";
|
import "package:inventree/user_profile.dart";
|
||||||
import "package:inventree/widget/dialogs.dart";
|
import "package:inventree/widget/dialogs.dart";
|
||||||
|
import "package:inventree/widget/progress.dart";
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
|
import "package:one_context/one_context.dart";
|
||||||
|
import "package:open_filex/open_filex.dart";
|
||||||
|
import "package:path_provider/path_provider.dart";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class representing an API response from the server
|
* Class representing an API response from the server
|
||||||
|
|
@ -163,6 +162,7 @@ class InvenTreeFileService extends FileService {
|
||||||
*
|
*
|
||||||
* InvenTree implements token-based authentication, which is
|
* InvenTree implements token-based authentication, which is
|
||||||
* initialised using a username:password combination.
|
* initialised using a username:password combination.
|
||||||
|
* Alternatively, an existing token provided by the user can be used for authentication.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -540,6 +540,35 @@ class InvenTreeAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<APIResponse> checkToken(UserProfile userProfile, String token) async {
|
||||||
|
debug("Checking token @ ${_URL_ME}");
|
||||||
|
|
||||||
|
userProfile.token = token;
|
||||||
|
profile = userProfile;
|
||||||
|
|
||||||
|
await UserProfileDBManager().updateProfile(userProfile);
|
||||||
|
|
||||||
|
final response = await get(_URL_ME);
|
||||||
|
|
||||||
|
if (!response.successful()) {
|
||||||
|
switch (response.statusCode) {
|
||||||
|
case 401:
|
||||||
|
case 403:
|
||||||
|
showServerError(
|
||||||
|
apiUrl,
|
||||||
|
L10().serverAuthenticationError,
|
||||||
|
L10().invalidToken,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
showStatusCodeError(apiUrl, response.statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Request failed: STATUS ${response.statusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fetch a token from the server,
|
* Fetch a token from the server,
|
||||||
* with a temporary authentication header
|
* with a temporary authentication header
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,9 @@
|
||||||
"enterPassword": "Enter password",
|
"enterPassword": "Enter password",
|
||||||
"@enterPassword": {},
|
"@enterPassword": {},
|
||||||
|
|
||||||
|
"enterToken": "Enter API token",
|
||||||
|
"@enterToken": {},
|
||||||
|
|
||||||
"enterUsername": "Enter username",
|
"enterUsername": "Enter username",
|
||||||
"@enterUsername": {},
|
"@enterUsername": {},
|
||||||
|
|
||||||
|
|
@ -665,6 +668,9 @@
|
||||||
"invalidSupplierPart": "Invalid Supplier Part",
|
"invalidSupplierPart": "Invalid Supplier Part",
|
||||||
"@invalidSupplierPart": {},
|
"@invalidSupplierPart": {},
|
||||||
|
|
||||||
|
"invalidToken": "Invalid Token",
|
||||||
|
"@invalidToken": {},
|
||||||
|
|
||||||
"invalidUsernamePassword": "Invalid username / password combination",
|
"invalidUsernamePassword": "Invalid username / password combination",
|
||||||
"@invalidUsernamePassword": {},
|
"@invalidUsernamePassword": {},
|
||||||
|
|
||||||
|
|
@ -770,6 +776,9 @@
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"@login": {},
|
"@login": {},
|
||||||
|
|
||||||
|
"loginMethod": "Login method",
|
||||||
|
"@loginMethod": {},
|
||||||
|
|
||||||
"loginEnter": "Enter login details",
|
"loginEnter": "Enter login details",
|
||||||
"@loginEnter": {},
|
"@loginEnter": {},
|
||||||
|
|
||||||
|
|
@ -1644,6 +1653,12 @@
|
||||||
"toggleTorch": "Toggle Torch",
|
"toggleTorch": "Toggle Torch",
|
||||||
"@toggleTorch": {},
|
"@toggleTorch": {},
|
||||||
|
|
||||||
|
"token": "Token",
|
||||||
|
"@token": {},
|
||||||
|
|
||||||
|
"tokenEmpty": "Token cannot be empty",
|
||||||
|
"@tokenEmpty": {},
|
||||||
|
|
||||||
"tokenError": "Token Error",
|
"tokenError": "Token Error",
|
||||||
"@tokenError": {},
|
"@tokenError": {},
|
||||||
|
|
||||||
|
|
@ -1720,6 +1735,9 @@
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"@username": {},
|
"@username": {},
|
||||||
|
|
||||||
|
"usernameAndPassword": "Username & Password",
|
||||||
|
"@usernameAndPassword": {},
|
||||||
|
|
||||||
"usernameEmpty": "Username cannot be empty",
|
"usernameEmpty": "Username cannot be empty",
|
||||||
"@usernameEmpty": {},
|
"@usernameEmpty": {},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
|
|
||||||
import "package:inventree/app_colors.dart";
|
|
||||||
import "package:inventree/user_profile.dart";
|
|
||||||
import "package:inventree/l10.dart";
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
|
import "package:inventree/app_colors.dart";
|
||||||
|
import "package:inventree/l10.dart";
|
||||||
|
import "package:inventree/user_profile.dart";
|
||||||
import "package:inventree/widget/dialogs.dart";
|
import "package:inventree/widget/dialogs.dart";
|
||||||
import "package:inventree/widget/progress.dart";
|
import "package:inventree/widget/progress.dart";
|
||||||
|
|
||||||
|
enum LoginMethod { credentials, token }
|
||||||
|
|
||||||
class InvenTreeLoginWidget extends StatefulWidget {
|
class InvenTreeLoginWidget extends StatefulWidget {
|
||||||
const InvenTreeLoginWidget(this.profile) : super();
|
const InvenTreeLoginWidget(this.profile) : super();
|
||||||
|
|
||||||
|
|
@ -22,9 +23,12 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
||||||
|
|
||||||
String username = "";
|
String username = "";
|
||||||
String password = "";
|
String password = "";
|
||||||
|
String token = "";
|
||||||
|
|
||||||
bool _obscured = true;
|
bool _obscured = true;
|
||||||
|
|
||||||
|
LoginMethod _method = LoginMethod.credentials;
|
||||||
|
|
||||||
String error = "";
|
String error = "";
|
||||||
|
|
||||||
// Attempt login
|
// Attempt login
|
||||||
|
|
@ -44,12 +48,19 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
||||||
|
|
||||||
showLoadingOverlay();
|
showLoadingOverlay();
|
||||||
|
|
||||||
|
final APIResponse response;
|
||||||
|
|
||||||
|
if (_method == LoginMethod.credentials) {
|
||||||
// Attempt login
|
// Attempt login
|
||||||
final response = await InvenTreeAPI().fetchToken(
|
response = await InvenTreeAPI().fetchToken(
|
||||||
widget.profile,
|
widget.profile,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Check token validity
|
||||||
|
response = await InvenTreeAPI().checkToken(widget.profile, token);
|
||||||
|
}
|
||||||
|
|
||||||
hideLoadingOverlay();
|
hideLoadingOverlay();
|
||||||
|
|
||||||
|
|
@ -123,6 +134,35 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
...before,
|
...before,
|
||||||
|
// Dropdown to select login method
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: DropdownButtonFormField<LoginMethod>(
|
||||||
|
value: _method,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: L10().loginMethod,
|
||||||
|
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
child: Text(L10().usernameAndPassword),
|
||||||
|
value: LoginMethod.credentials,
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
child: Text(L10().token),
|
||||||
|
value: LoginMethod.token,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (val) {
|
||||||
|
setState(() {
|
||||||
|
_method = val ?? LoginMethod.credentials;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Show fields depending on selected login method
|
||||||
|
if (_method == LoginMethod.credentials) ...[
|
||||||
TextFormField(
|
TextFormField(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: L10().username,
|
labelText: L10().username,
|
||||||
|
|
@ -172,6 +212,38 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
] else ...[
|
||||||
|
TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: L10().token,
|
||||||
|
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
hintText: L10().enterToken,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: _obscured
|
||||||
|
? Icon(TablerIcons.eye)
|
||||||
|
: Icon(TablerIcons.eye_off),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_obscured = !_obscured;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
initialValue: "",
|
||||||
|
keyboardType: TextInputType.visiblePassword,
|
||||||
|
obscureText: _obscured,
|
||||||
|
onSaved: (value) {
|
||||||
|
token = value?.trim() ?? "";
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return L10().tokenEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
...after,
|
...after,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue