diff --git a/lib/auth/device_code.dart b/lib/auth/device_code.dart new file mode 100644 index 0000000..4707a60 --- /dev/null +++ b/lib/auth/device_code.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:open_media_server_app/globals.dart'; + +class DeviceCode { + Future> getDeviceCode( + String clientId, String scope, String deviceCodeUrl) async { + final response = await http.post( + Uri.parse(deviceCodeUrl), // Replace with your provider's URL + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: {'client_id': clientId, 'scope': scope}, + ); + + if (response.statusCode == 200) { + return json.decode(response.body); + } else { + throw Exception('Failed to get device code'); + } + } + + Future authenticateUser( + String clientId, String scope, String deviceCodeUrl) async { + try { + final deviceCodeResponse = + await getDeviceCode(clientId, scope, deviceCodeUrl); + final userCode = deviceCodeResponse['user_code']; + final verificationUri = deviceCodeResponse['verification_uri']; + + print('Please go to $verificationUri?code=$userCode'); + + final code = deviceCodeResponse['device_code']; + final interval = deviceCodeResponse['interval']; + var token = await pollForToken( + code, interval, Globals.ClientId, Globals.TokenUrl); + + return token; + } catch (e) { + print('Error: $e'); + } + + return null; + } + + Future pollForToken( + String deviceCode, int interval, String clientId, String tokenUrl) async { + while (true) { + await Future.delayed(Duration(seconds: interval)); + + final response = await http.post( + Uri.parse(tokenUrl), // Replace with your provider's token URL + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: { + 'client_id': clientId, + 'grant_type': 'urn:ietf:params:oauth:grant-type:device_code', + 'device_code': deviceCode, + }, + ); + + if (response.statusCode == 200) { + final tokenResponse = json.decode(response.body); + print('Access token: ${tokenResponse['access_token']}'); + + return tokenResponse['access_token']; + } else { + final errorResponse = json.decode(response.body); + if (errorResponse['error'] == 'authorization_pending') { + print('Authorization pending...'); + continue; + } else if (errorResponse['error'] == 'authorization_declined') { + print('Authorization declined by user'); + break; + } else if (errorResponse['error'] == 'expired_token') { + print('Device code expired'); + break; + } else { + print('Unknown error: ${errorResponse['error']}'); + break; + } + } + } + + return null; + } +} diff --git a/lib/auth/login_manager.dart b/lib/auth/login_manager.dart index f39bfc1..74a7659 100644 --- a/lib/auth/login_manager.dart +++ b/lib/auth/login_manager.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; 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.dart'; import 'package:open_media_server_app/helpers/Preferences.dart'; import 'package:random_string/random_string.dart'; @@ -15,7 +16,9 @@ class LoginManager { BaseWebAuth? baseWebAuth; LoginManager() { - if (!Globals.isWeb) { + if (Globals.isTv) { + // Do nothing + } else if (!Globals.isWeb) { client = OAuth2Client( authorizeUrl: Globals.AuthorizeUrl, tokenUrl: Globals.TokenUrl, @@ -40,6 +43,15 @@ class LoginManager { } Future login(String clientId, String clientSecret) async { + if (Globals.isTv) { + DeviceCode deviceCode = DeviceCode(); + var token = await deviceCode.authenticateUser(Globals.ClientId, "offline_access", Globals.DeviceCodeUrl); + + Preferences.prefs?.setString("AccessToken", token!); + + return token; + } + var state = Preferences.prefs?.getString("OAuth_State"); var codeVerifier = Preferences.prefs?.getString("OAuth_CodeVerifier"); @@ -82,4 +94,4 @@ class LoginManager { return tknResponse.accessToken; } -} \ No newline at end of file +} diff --git a/lib/globals.dart b/lib/globals.dart index 699d940..9b9b90f 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -5,12 +5,14 @@ class Globals { static String ClientSecret = ""; static String AuthorizeUrl = ""; static String TokenUrl = ""; + static String DeviceCodeUrl = ""; static String Title = "Open Media Station"; static String PictureNotFoundUrl = "https://static.vecteezy.com/system/resources/previews/005/337/799/original/icon-image-not-found-free-vector.jpg"; static bool isTv = false; + static bool isAndroidTv = false; static bool isMobile = false; static bool isWeb = false; } diff --git a/lib/main.dart b/lib/main.dart index bd90d8b..4fbb1d4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,6 +25,10 @@ Future main() async { Globals.isMobile = false; } + if (Globals.isTv && defaultTargetPlatform == TargetPlatform.android) { + Globals.isAndroidTv = true; + } + if (kIsWeb) { Globals.isWeb = true; }