Skip to content

Commit

Permalink
🛂 Refresh token support
Browse files Browse the repository at this point in the history
  • Loading branch information
LNA-DEV committed Nov 2, 2024
1 parent 22fdbcc commit 4aac293
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 18 deletions.
2 changes: 1 addition & 1 deletion lib/apis/auth_info_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class AuthInfoApi {
Future<AuthInfo> getAuthInfo() async {
String apiUrl = "${Preferences.prefs?.getString("BaseUrl")}/auth/info";

var headers = BaseApi.getHeaders();
var headers = await BaseApi.getRefreshedHeaders();

var response = await http.get(
Uri.parse(apiUrl),
Expand Down
48 changes: 48 additions & 0 deletions lib/apis/base_api.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'dart:convert';
import 'package:open_media_server_app/auth/login_manager.dart';
import 'package:open_media_server_app/globals/auth_globals.dart';
import 'package:open_media_server_app/helpers/preferences.dart';

class BaseApi {
Expand All @@ -6,4 +9,49 @@ class BaseApi {
"Authorization": "Bearer ${Preferences.prefs?.getString("AccessToken")}"
};
}

static Future<Map<String, String>> getRefreshedHeaders() async {
var token = Preferences.prefs?.getString("AccessToken");

if (AuthGlobals.authInfo != null && token != null) {
if (isJwtAboutToExpire(token)) {
LoginManager loginManager = LoginManager(AuthGlobals.authInfo!);
token = await loginManager.refreshAsync(AuthGlobals.authInfo!);
}
}

return {"Authorization": "Bearer $token"};
}

static bool isJwtAboutToExpire(String token, {int bufferInSeconds = 120}) {
try {
// Decode the JWT to get its payload
final parts = token.split('.');
if (parts.length != 3) {
throw Exception("Invalid token format");
}

// Base64 decode the payload part of the token
final payload = json
.decode(utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))));

// Check if the `exp` field exists in the payload
if (!payload.containsKey('exp')) {
throw Exception("Token does not contain expiration date");
}

// `exp` is the expiration time in seconds since epoch
final exp = payload['exp'] as int;

// Get the current time in seconds since epoch
final currentTimeInSeconds =
DateTime.now().millisecondsSinceEpoch ~/ 1000;

// Check if the token is about to expire within the buffer period
return exp - currentTimeInSeconds <= bufferInSeconds;
} catch (e) {
print("Error decoding token: $e");
return true; // Assuming it's expired if there's an error
}
}
}
10 changes: 5 additions & 5 deletions lib/apis/inventory_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class InventoryApi {
Future<List<InventoryItem>> listItems(String category) async {
String apiUrl = "${Preferences.prefs?.getString("BaseUrl")}/api/inventory/items?";

var headers = BaseApi.getHeaders();
var headers = await BaseApi.getRefreshedHeaders();

var response = await http.get(Uri.parse("${apiUrl}category=$category"),
headers: headers);
Expand All @@ -28,7 +28,7 @@ class InventoryApi {
Future<Movie> getMovie(String id) async {
String apiUrl = "${Preferences.prefs?.getString("BaseUrl")}/api/inventory/movie?";

var headers = BaseApi.getHeaders();
var headers = await BaseApi.getRefreshedHeaders();

var response =
await http.get(Uri.parse("${apiUrl}id=$id"), headers: headers);
Expand All @@ -44,7 +44,7 @@ class InventoryApi {
Future<Show> getShow(String id) async {
String apiUrl = "${Preferences.prefs?.getString("BaseUrl")}/api/inventory/show?";

var headers = BaseApi.getHeaders();
var headers = await BaseApi.getRefreshedHeaders();

var response =
await http.get(Uri.parse("${apiUrl}id=$id"), headers: headers);
Expand All @@ -60,7 +60,7 @@ class InventoryApi {
Future<Season> getSeason(String id) async {
String apiUrl = "${Preferences.prefs?.getString("BaseUrl")}/api/inventory/season?";

var headers = BaseApi.getHeaders();
var headers = await BaseApi.getRefreshedHeaders();

var response =
await http.get(Uri.parse("${apiUrl}id=$id"), headers: headers);
Expand All @@ -76,7 +76,7 @@ class InventoryApi {
Future<Episode> getEpisode(String id) async {
String apiUrl = "${Preferences.prefs?.getString("BaseUrl")}/api/inventory/episode?";

var headers = BaseApi.getHeaders();
var headers = await BaseApi.getRefreshedHeaders();

var response =
await http.get(Uri.parse("${apiUrl}id=$id"), headers: headers);
Expand Down
2 changes: 1 addition & 1 deletion lib/apis/metadata_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class MetadataApi {
Future<MetadataModel> getMetadata(String id, String category) async {
String apiUrl = "${Preferences.prefs?.getString("BaseUrl")}/api/metadata?";

var headers = BaseApi.getHeaders();
var headers = await BaseApi.getRefreshedHeaders();

var response = await http
.get(Uri.parse("${apiUrl}id=$id&category=$category"), headers: headers);
Expand Down
9 changes: 2 additions & 7 deletions lib/auth/login_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:oauth2_client/access_token_response.dart';
import 'package:oauth2_client/interfaces.dart';
import 'package:oauth2_client/oauth2_client.dart';
import 'package:open_media_server_app/auth/device_code.dart';
import 'package:open_media_server_app/globals/globals.dart';
import 'package:open_media_server_app/globals/platform_globals.dart';
import 'package:open_media_server_app/helpers/preferences.dart';
import 'package:open_media_server_app/models/auth/auth_info.dart';
Expand Down Expand Up @@ -80,14 +79,10 @@ class LoginManager {
return tknResponse.accessToken;
}

Future<String?> refreshAsync() async {
String clientId = Preferences.prefs!.getString("ClientId")!;
String clientSecret = Preferences.prefs!.getString("ClientSecret")!;

Future<String?> refreshAsync(AuthInfo authInfo) async {
var tknResponse = await client.refreshToken(
Preferences.prefs!.getString("RefreshToken")!,
clientId: clientId,
clientSecret: Uri.encodeQueryComponent(clientSecret),
clientId: authInfo.clientId,
);

Preferences.prefs?.setString("AccessToken", tknResponse.accessToken!);
Expand Down
4 changes: 3 additions & 1 deletion lib/globals/auth_globals.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:open_media_server_app/helpers/preferences.dart';
import 'package:open_media_server_app/models/auth/auth_info.dart';

class AuthGlobals {
static String get redirectUriWeb {
Expand All @@ -12,4 +13,5 @@ class AuthGlobals {

static String? appLoginCodeRoute;

}
static AuthInfo? authInfo;
}
14 changes: 12 additions & 2 deletions lib/views/login.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:open_media_server_app/apis/auth_info_api.dart';
import 'package:open_media_server_app/auth/login_manager.dart';
import 'package:open_media_server_app/globals/auth_globals.dart';
import 'package:open_media_server_app/globals/globals.dart';
import 'package:open_media_server_app/helpers/preferences.dart';

Expand Down Expand Up @@ -76,7 +77,8 @@ class LoginView extends StatelessWidget {
return 'Please enter some text';
}

if (!value.contains("http://") && !value.contains("https://")) {
if (!value.contains("http://") &&
!value.contains("https://")) {
return 'You need to specify the host in this format\nExample: https://example.com';
}

Expand Down Expand Up @@ -120,12 +122,20 @@ class LoginView extends StatelessWidget {
}

Future authenticate(BuildContext context) async {
var refreshToken = Preferences.prefs?.getString("RefreshToken");
var accessToken = Preferences.prefs?.getString("AccessToken");

AuthInfoApi authInfoApi = AuthInfoApi();
var info = await authInfoApi.getAuthInfo();
AuthGlobals.authInfo = info;

LoginManager loginManager = LoginManager(info);

var token = await loginManager.login(info, context);
if (refreshToken != null && accessToken != null) {
var token = await loginManager.refreshAsync(info);
} else {
var token = await loginManager.login(info, context);
}

Navigator.pushReplacement(
context,
Expand Down
1 change: 0 additions & 1 deletion lib/views/player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:open_media_server_app/apis/base_api.dart';
import 'package:open_media_server_app/globals/platform_globals.dart';
import 'package:open_media_server_app/widgets/player_controls/material_tv.dart';
import 'package:open_media_server_app/helpers/wrapper.dart';
import 'package:open_media_server_app/globals/globals.dart';

class PlayerView extends StatefulWidget {
const PlayerView({super.key, required this.url});
Expand Down

0 comments on commit 4aac293

Please sign in to comment.