diff --git a/lib/oauth2_client.dart b/lib/oauth2_client.dart index 3e13a6e..761018d 100644 --- a/lib/oauth2_client.dart +++ b/lib/oauth2_client.dart @@ -6,6 +6,7 @@ import 'package:oauth2_client/access_token_response.dart'; import 'package:oauth2_client/authorization_response.dart'; import 'package:oauth2_client/oauth2_response.dart'; import 'package:oauth2_client/src/oauth2_utils.dart'; +import 'package:oauth2_client/tiktok_oauth2_client.dart'; import 'package:random_string/random_string.dart'; // import 'package:oauth2_client/src/web_auth.dart'; @@ -48,6 +49,7 @@ class OAuth2Client { String? revokeUrl; String authorizeUrl; String scopeSeparator; + late String clientKey; BaseWebAuth webAuthClient = createWebAuth(); CredentialsLocation credentialsLocation; @@ -70,7 +72,13 @@ class OAuth2Client { required this.redirectUri, required this.customUriScheme, this.credentialsLocation = CredentialsLocation.header, - this.scopeSeparator = ' '}); + this.scopeSeparator = ' '}) { + clientKey = _getClientKey(this); + } + + static String _getClientKey(OAuth2Client client) { + return client is TikTokOAuth2Client ? 'client_key' : 'client_id'; + } /// Requests an Access Token to the OAuth2 endpoint using the Implicit grant flow (https://tools.ietf.org/html/rfc6749#page-31) Future getTokenWithImplicitGrantFlow( @@ -334,7 +342,7 @@ class OAuth2Client { Map? customParams}) { final params = { 'response_type': responseType, - 'client_id': clientId + clientKey: clientId }; if (redirectUri != null && redirectUri.isNotEmpty) { @@ -380,7 +388,7 @@ class OAuth2Client { //If a client secret has been specified, it will be sent in the "Authorization" header instead of a body parameter... if (clientSecret == null || clientSecret.isEmpty) { if (clientId != null && clientId.isNotEmpty) { - params['client_id'] = clientId; + params[clientKey] = clientId; } } */ @@ -413,7 +421,7 @@ class OAuth2Client { //If a client secret has been specified, it will be sent in the "Authorization" header instead of a body parameter... if (clientSecret == null) { if (clientId.isNotEmpty) { - params['client_id'] = clientId; + params[clientKey] = clientId; } } else { switch (credentialsLocation) { @@ -424,7 +432,7 @@ class OAuth2Client { )); break; case CredentialsLocation.body: - params['client_id'] = clientId; + params[clientKey] = clientId; params['client_secret'] = clientSecret; break; } @@ -486,7 +494,7 @@ class OAuth2Client { if (token != null) { var params = {'token': token, 'token_type_hint': tokenType}; - if (clientId != null) params['client_id'] = clientId; + if (clientId != null) params[clientKey] = clientId; if (clientSecret != null) params['client_secret'] = clientSecret; http.Response response = diff --git a/lib/tiktok_oauth2_client.dart b/lib/tiktok_oauth2_client.dart new file mode 100644 index 0000000..1daed5f --- /dev/null +++ b/lib/tiktok_oauth2_client.dart @@ -0,0 +1,95 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:http/http.dart' as http; +import 'package:oauth2_client/access_token_response.dart'; +import 'package:oauth2_client/oauth2_client.dart'; + +class TikTokOAuth2Client extends OAuth2Client { + TikTokOAuth2Client( + {required String redirectUri, required String customUriScheme}) + : super( + authorizeUrl: 'https://www.tiktok.com/v2/auth/authorize', + tokenUrl: 'https://open.tiktokapis.com/v2/oauth/token/', + revokeUrl: 'https://open.tiktokapis.com/v2/oauth/revoke/', + scopeSeparator: ',', + credentialsLocation: CredentialsLocation.body, + redirectUri: redirectUri, + customUriScheme: customUriScheme, + ); + + @override + Future requestAccessToken({ + required String code, + required String clientId, + String? clientSecret, + String? codeVerifier, + List? scopes, + Map? customParams, + Map? customHeaders, + httpClient, + }) async { + final params = getTokenUrlParams( + code: code, + redirectUri: redirectUri, + codeVerifier: codeVerifier, + customParams: customParams, + ); + + final headers = { + ...?customHeaders, + ...{'Content-Type': 'application/x-www-form-urlencoded'} + }; + + var response = await _performAuthorizedRequest( + url: tokenUrl, + clientId: clientId, + clientSecret: clientSecret, + params: params, + headers: headers, + httpClient: httpClient, + ); + + return http2TokenResponse(response, requestedScopes: scopes); + } + + Future _performAuthorizedRequest({ + required String url, + required String clientId, + String? clientSecret, + Map? params, + Map? headers, + http.Client? httpClient, + }) async { + final dio = Dio(); + + headers ??= {}; + params ??= {}; + + //If a client secret has been specified, it will be sent in the "Authorization" header instead of a body parameter... + if (clientSecret == null) { + if (clientId.isNotEmpty) { + params[clientKey] = clientId; + } + } else { + switch (credentialsLocation) { + case CredentialsLocation.header: + headers.addAll(getAuthorizationHeader( + clientId: clientId, + clientSecret: clientSecret, + )); + break; + case CredentialsLocation.body: + params[clientKey] = clientId; + params['client_secret'] = clientSecret; + break; + } + } + + var response = await dio.post>(url, + data: params, + options: Options(headers: headers, responseType: ResponseType.json)); + + return http.Response(jsonEncode(response.data), response.statusCode ?? 0); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 9dd6cfd..75b5247 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: http: ^1.1.0 meta: ^1.12.0 random_string: ^2.3.1 + dio: ^5.8.0+1 dev_dependencies: flutter_test: