mirror of
https://github.com/HendrikRauh/inventree-app.git
synced 2026-02-27 04:23:15 +00:00
Implement token-based login
Added drop-down to switch between username & password login or token login so login if using OAuth is possible.
This commit is contained in:
parent
3c8c263327
commit
540c70fe30
3 changed files with 182 additions and 52 deletions
35
lib/api.dart
35
lib/api.dart
|
|
@ -163,6 +163,7 @@ class InvenTreeFileService extends FileService {
|
|||
*
|
||||
* InvenTree implements token-based authentication, which is
|
||||
* initialised using a username:password combination.
|
||||
* Alternatively, an existing token provided by the user can be used for authentication.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
|
@ -540,6 +541,40 @@ class InvenTreeAPI {
|
|||
}
|
||||
}
|
||||
|
||||
Future<APIResponse> checkToken(UserProfile userProfile, String token) async {
|
||||
debug("Checking token @ ${_URL_ME}");
|
||||
|
||||
userProfile.token = token;
|
||||
profile = 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}");
|
||||
|
||||
// reset token
|
||||
userProfile.token = "";
|
||||
profile = userProfile;
|
||||
} else {
|
||||
// save token
|
||||
await UserProfileDBManager().updateProfile(userProfile);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch a token from the server,
|
||||
* with a temporary authentication header
|
||||
|
|
|
|||
|
|
@ -430,6 +430,9 @@
|
|||
"enterPassword": "Enter password",
|
||||
"@enterPassword": {},
|
||||
|
||||
"enterToken": "Enter API token",
|
||||
"@enterToken": {},
|
||||
|
||||
"enterUsername": "Enter username",
|
||||
"@enterUsername": {},
|
||||
|
||||
|
|
@ -677,6 +680,9 @@
|
|||
"invalidSupplierPart": "Invalid Supplier Part",
|
||||
"@invalidSupplierPart": {},
|
||||
|
||||
"invalidToken": "Invalid token",
|
||||
"@invalidToken": {},
|
||||
|
||||
"invalidUsernamePassword": "Invalid username / password combination",
|
||||
"@invalidUsernamePassword": {},
|
||||
|
||||
|
|
@ -782,6 +788,9 @@
|
|||
"login": "Login",
|
||||
"@login": {},
|
||||
|
||||
"loginMethod": "Login method",
|
||||
"@loginMethod": {},
|
||||
|
||||
"loginEnter": "Enter login details",
|
||||
"@loginEnter": {},
|
||||
|
||||
|
|
@ -1665,6 +1674,12 @@
|
|||
"toggleTorch": "Toggle Torch",
|
||||
"@toggleTorch": {},
|
||||
|
||||
"token": "Token",
|
||||
"@token": {},
|
||||
|
||||
"tokenEmpty": "Token cannot be empty",
|
||||
"@tokenEmpty": {},
|
||||
|
||||
"tokenError": "Token Error",
|
||||
"@tokenError": {},
|
||||
|
||||
|
|
@ -1741,6 +1756,9 @@
|
|||
"username": "Username",
|
||||
"@username": {},
|
||||
|
||||
"usernameAndPassword": "Username & Password",
|
||||
"@usernameAndPassword": {},
|
||||
|
||||
"usernameEmpty": "Username cannot be empty",
|
||||
"@usernameEmpty": {},
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import "package:inventree/api.dart";
|
|||
import "package:inventree/widget/dialogs.dart";
|
||||
import "package:inventree/widget/progress.dart";
|
||||
|
||||
enum LoginMethod { credentials, token }
|
||||
|
||||
class InvenTreeLoginWidget extends StatefulWidget {
|
||||
const InvenTreeLoginWidget(this.profile) : super();
|
||||
|
||||
|
|
@ -22,9 +24,12 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
|||
|
||||
String username = "";
|
||||
String password = "";
|
||||
String token = "";
|
||||
|
||||
bool _obscured = true;
|
||||
|
||||
LoginMethod _method = LoginMethod.credentials;
|
||||
|
||||
String error = "";
|
||||
|
||||
// Attempt login
|
||||
|
|
@ -44,12 +49,19 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
|||
|
||||
showLoadingOverlay();
|
||||
|
||||
// Attempt login
|
||||
final response = await InvenTreeAPI().fetchToken(
|
||||
widget.profile,
|
||||
username,
|
||||
password,
|
||||
);
|
||||
final APIResponse response;
|
||||
|
||||
if (_method == LoginMethod.credentials) {
|
||||
// Attempt login
|
||||
response = await InvenTreeAPI().fetchToken(
|
||||
widget.profile,
|
||||
username,
|
||||
password,
|
||||
);
|
||||
} else {
|
||||
// Check token validity
|
||||
response = await InvenTreeAPI().checkToken(widget.profile, token);
|
||||
}
|
||||
|
||||
hideLoadingOverlay();
|
||||
|
||||
|
|
@ -123,55 +135,120 @@ class _InvenTreeLoginState extends State<InvenTreeLoginWidget> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...before,
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: L10().username,
|
||||
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
hintText: L10().enterUsername,
|
||||
),
|
||||
initialValue: "",
|
||||
keyboardType: TextInputType.text,
|
||||
onSaved: (value) {
|
||||
username = value?.trim() ?? "";
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return L10().usernameEmpty;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: L10().password,
|
||||
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
hintText: L10().enterPassword,
|
||||
suffixIcon: IconButton(
|
||||
icon: _obscured
|
||||
? Icon(TablerIcons.eye)
|
||||
: Icon(TablerIcons.eye_off),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscured = !_obscured;
|
||||
});
|
||||
},
|
||||
// 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;
|
||||
error = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
initialValue: "",
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: _obscured,
|
||||
onSaved: (value) {
|
||||
password = value?.trim() ?? "";
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return L10().passwordEmpty;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
// Show fields depending on selected login method
|
||||
if (_method == LoginMethod.credentials) ...[
|
||||
TextFormField(
|
||||
key: ValueKey("input-username"),
|
||||
decoration: InputDecoration(
|
||||
labelText: L10().username,
|
||||
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
hintText: L10().enterUsername,
|
||||
),
|
||||
initialValue: "",
|
||||
keyboardType: TextInputType.text,
|
||||
onSaved: (value) {
|
||||
username = value?.trim() ?? "";
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return L10().usernameEmpty;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
key: ValueKey("input-password"),
|
||||
decoration: InputDecoration(
|
||||
labelText: L10().password,
|
||||
labelStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
hintText: L10().enterPassword,
|
||||
suffixIcon: IconButton(
|
||||
icon: _obscured
|
||||
? Icon(TablerIcons.eye)
|
||||
: Icon(TablerIcons.eye_off),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscured = !_obscured;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
initialValue: "",
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: _obscured,
|
||||
onSaved: (value) {
|
||||
password = value?.trim() ?? "";
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return L10().passwordEmpty;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
] else ...[
|
||||
TextFormField(
|
||||
key: ValueKey("input-token"),
|
||||
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,
|
||||
],
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue