From b9398618539d843563a8df4f23150a70f690b4bb Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Thu, 26 Sep 2024 08:55:52 +0530 Subject: [PATCH 1/9] Add init logger definition for debug logging --- lib/src/logger.dart | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lib/src/logger.dart diff --git a/lib/src/logger.dart b/lib/src/logger.dart new file mode 100644 index 0000000..4d9197d --- /dev/null +++ b/lib/src/logger.dart @@ -0,0 +1,29 @@ +import 'version.dart'; + +const String supertokensDebugNamespace = 'com.supertokens'; + +bool _supertokensWebsiteLogging = false; + +// Enable debug logging +void enableLogging() { + _supertokensWebsiteLogging = true; +} + +// Disable debug logging +void disableLogging() { + _supertokensWebsiteLogging = false; +} + +// Logs a debug message +// +// This function will only log the debug message if debug logging +// is enabled. +// +// It can be enabled/disabled through `enableLogging` & `disableLogging` +// functions +void logDebugMessage(String message) { + if (_supertokensWebsiteLogging) { + print( + '$supertokensDebugNamespace {t: "${DateTime.now().toIso8601String()}", message: "$message", supertokens-react-native: "${Version.sdkVersion}"}'); + } +} From 8e3f253b2db0367c0bd1149405247b1bf272ee3d Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Thu, 26 Sep 2024 09:28:30 +0530 Subject: [PATCH 2/9] Add logs for config in init method --- lib/src/supertokens.dart | 16 ++++++++++++++++ lib/src/utilities.dart | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/src/supertokens.dart b/lib/src/supertokens.dart index 8b79fd9..f12993a 100644 --- a/lib/src/supertokens.dart +++ b/lib/src/supertokens.dart @@ -4,6 +4,7 @@ import 'package:supertokens_flutter/src/front-token.dart'; import 'package:supertokens_flutter/src/utilities.dart'; import 'package:http/http.dart' as http; import 'package:supertokens_flutter/src/supertokens-http-client.dart'; +import 'package:supertokens_flutter/src/logger.dart'; enum Eventype { SIGN_OUT, @@ -48,11 +49,19 @@ class SuperTokens { Function(Eventype)? eventHandler, http.Request Function(APIAction, http.Request)? preAPIHook, Function(APIAction, http.Request, http.Response)? postAPIHook, + debug? bool, }) { if (SuperTokens.isInitCalled) { return; } + // Enable debug mode if that is specified by the user. + if (debug) { + enableLogging() + } + + logDebugMessage("Started SuperTokens with debug logging (supertokens.init called)"); + SuperTokens.config = NormalisedInputType.normaliseInputType( apiDomain, apiBasePath, @@ -65,11 +74,18 @@ class SuperTokens { postAPIHook, ); + logDebugMessage('config: ${jsonEncode(config.toJson())}'); + SuperTokens.refreshTokenUrl = config.apiDomain + (config.apiBasePath ?? '') + "/session/refresh"; SuperTokens.signOutUrl = config.apiDomain + (config.apiBasePath ?? '') + "/signout"; SuperTokens.rid = "session"; + + logDebugMessage('refreshTokenUrl: ${refreshTokenUrl}') + logDebugMessage('signOutUrl: ${signOutUrl}') + logDebugMessage('rid: ${rid}') + SuperTokens.isInitCalled = true; } diff --git a/lib/src/utilities.dart b/lib/src/utilities.dart index f5b651c..a501d1b 100644 --- a/lib/src/utilities.dart +++ b/lib/src/utilities.dart @@ -456,4 +456,16 @@ class NormalisedInputType { throw SuperTokensException("Please provide a valid SessionScope"); } } + + Map toJson() { + return { + 'apiDomain': apiDomain, + 'apiBasePath': apiBasePath, + 'sessionExpiredStatusCode': sessionExpiredStatusCode, + 'maxRetryAttemptsForSessionRefresh': maxRetryAttemptsForSessionRefresh, + 'sessionTokenBackendDomain': sessionTokenBackendDomain, + 'tokenTransferMethod': tokenTransferMethod.getValue(), + 'userDefaultSuiteName': userDefaultSuiteName, + }; + } } From 49c5ced39e6f6e21255d256adc458ac8f64a5eb7 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Thu, 26 Sep 2024 10:09:53 +0530 Subject: [PATCH 3/9] Add debug logs to anti-csrf, cookie and dio related code --- lib/src/anti-csrf.dart | 6 ++++++ lib/src/cookie-store.dart | 15 +++++++++++++++ lib/src/dio-interceptor-wrapper.dart | 12 ++++++++++++ lib/src/supertokens.dart | 4 ++++ 4 files changed, 37 insertions(+) diff --git a/lib/src/anti-csrf.dart b/lib/src/anti-csrf.dart index d0a54ba..479c493 100644 --- a/lib/src/anti-csrf.dart +++ b/lib/src/anti-csrf.dart @@ -1,4 +1,5 @@ import 'package:shared_preferences/shared_preferences.dart'; +import 'package:supertokens_flutter/src/logger.dart'; class _AntiCSRFInfo { String? antiCSRF; @@ -15,6 +16,8 @@ class AntiCSRF { static String _sharedPreferencesKey = "supertokens-flutter-anti-csrf"; static Future getToken(String? associatedAccessTokenUpdate) async { + logDebugMessage('Getting token...') + logDebugMessage('associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}') if (associatedAccessTokenUpdate == null) { AntiCSRF._antiCSRFInfo = null; return null; @@ -43,6 +46,8 @@ class AntiCSRF { static Future setToken( String antiCSRFToken, String? associatedAccessTokenUpdate) async { + logDebugMessage('Setting token...') + logDebugMessage('associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}') if (associatedAccessTokenUpdate == null) { AntiCSRF._antiCSRFInfo = null; return; @@ -58,6 +63,7 @@ class AntiCSRF { } static Future removeToken() async { + logDebugMessage('Removing token...') SharedPreferences preferences = await SharedPreferences.getInstance(); await preferences.remove(AntiCSRF._sharedPreferencesKey); await preferences.reload(); diff --git a/lib/src/cookie-store.dart b/lib/src/cookie-store.dart index 2ab6837..c422cae 100644 --- a/lib/src/cookie-store.dart +++ b/lib/src/cookie-store.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:supertokens_flutter/src/logger.dart'; class SuperTokensCookieStore { static Map>? _allCookies; @@ -24,10 +25,12 @@ class SuperTokensCookieStore { /// Loads all cookies stored in shared preferences into the in memory map [_allCookies] static Future _loadFromPersistence() async { + logDebugMessage('Trying to load cookies from memory') _allCookies = {}; String cookiesStringInStorage = _sharedPreferences?.getString(_cookieSharedPrefsKey) ?? "{}"; Map cookiesInStorage = jsonDecode(cookiesStringInStorage); + logDebugMessage('cookies found: ${jsonEncode(cookiesInStorage)}') cookiesInStorage.forEach((key, value) { Uri uri = Uri.parse(key); List cookieStrings = List.from(value); @@ -49,6 +52,8 @@ class SuperTokensCookieStore { /// /// If you are trying to store cookies from a "set-cookie" header response, consider using the [saveFromSetCookieHeader] utility method which parses the header string. Future saveFromResponse(Uri uri, List cookies) async { + logDebugMessage('Saving cookies against: ${uri}') + logDebugMessage('Passed cookies: ${jsonEncode(cookies)}') await Future.forEach(cookies, (element) async { Uri uriToStore = await _getCookieUri(uri, element); List currentCookies = _allCookies?[uriToStore] ?? List.from([]); @@ -76,6 +81,7 @@ class SuperTokensCookieStore { /// Returns a Uri to use when saving the cookie Future _getCookieUri(Uri requestUri, Cookie cookie) async { + logDebugMessage('Creating cookie uri from: ${requestUri}') Uri cookieUri = Uri.parse( // ignore: unnecessary_null_comparison "${requestUri.scheme == null ? "http" : requestUri.scheme}://${requestUri.host}${cookie.path == null ? "" : cookie.path}"); @@ -98,6 +104,7 @@ class SuperTokensCookieStore { } } + logDebugMessage('Generated cookie uri: ${cookieUri}') return cookieUri; } @@ -105,6 +112,7 @@ class SuperTokensCookieStore { /// /// Strips expired cookies before storing in shared preferences Future _updatePersistentStorage() async { + logDebugMessage('Updating persistent storage with cookies...') Map> mapToStore = {}; _allCookies?.forEach((key, value) { String uriString = key.toString(); @@ -123,10 +131,12 @@ class SuperTokensCookieStore { /// /// If you are trying to add cookies to a "cookie" header for a network call, consider using the [getCookieHeaderStringForRequest] which creates a semi-colon separated cookie string for a given Uri. Future> getForRequest(Uri uri) async { + logDebugMessage('Getting cookies for request from uri: ${uri}') List cookiesToReturn = []; List allValidCookies = []; if (_allCookies == null) { + logDebugMessage('No cookies found') return cookiesToReturn; } @@ -155,6 +165,7 @@ class SuperTokensCookieStore { } } + logDebugMessage('Total cookies found ${cookiesToReturn.length}') return cookiesToReturn; } @@ -175,6 +186,7 @@ class SuperTokensCookieStore { /// Removes a list of cookies from persistent storage Future _removeFromPersistence( Uri uri, List cookiesToRemove) async { + logDebugMessage('Removing cookies from persistent storage...') List _cookiesToRemove = List.from(cookiesToRemove); List currentCookies = _allCookies?[uri] ?? List.from([]); @@ -192,6 +204,7 @@ class SuperTokensCookieStore { /// /// Does not return expired cookies and will remove them from persistent storage if any are found. Future getCookieHeaderStringForRequest(Uri uri) async { + logDebugMessage('Getting cookie header for request from uri: ${uri}') List cookies = await getForRequest(uri); // ignore: unnecessary_null_comparison if (cookies != null && cookies.isNotEmpty) { @@ -214,6 +227,7 @@ class SuperTokensCookieStore { /// /// Expired cookies are not saved. Future saveFromSetCookieHeader(Uri uri, String? setCookieHeader) async { + logDebugMessage('Saving cookie from header against uri: ${uri}') if (setCookieHeader != null) { await saveFromResponse(uri, getCookieListFromHeader(setCookieHeader)); } @@ -224,6 +238,7 @@ class SuperTokensCookieStore { setCookieHeader.split(RegExp(r',(?=[^ ])')); List setCookiesList = setCookiesStringList.map((e) => Cookie.fromSetCookieValue(e)).toList(); + logDebugMessage('Total cookies found in header: ${setCookiesList.length}') return setCookiesList; } } diff --git a/lib/src/dio-interceptor-wrapper.dart b/lib/src/dio-interceptor-wrapper.dart index 966e6ef..dd79d09 100644 --- a/lib/src/dio-interceptor-wrapper.dart +++ b/lib/src/dio-interceptor-wrapper.dart @@ -10,6 +10,7 @@ import 'package:supertokens_flutter/src/front-token.dart'; import 'package:supertokens_flutter/src/supertokens-http-client.dart'; import 'package:supertokens_flutter/src/supertokens.dart'; import 'package:supertokens_flutter/src/utilities.dart'; +import 'package:supertokens_flutter/src/logger.dart'; class SuperTokensInterceptorWrapper extends Interceptor { ReadWriteMutex _refreshAPILock = ReadWriteMutex(); @@ -22,6 +23,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { @override void onRequest( RequestOptions options, RequestInterceptorHandler handler) async { + logDebugMessage('Intercepting request call') if (!SuperTokens.isInitCalled) { handler.reject(DioException( requestOptions: options, @@ -33,10 +35,13 @@ class SuperTokensInterceptorWrapper extends Interceptor { } if (!shouldRunDioInterceptor(options)) { + logDebugMessage('Skipping dio interceptor') return super.onRequest(options, handler); } + logDebugMessage('Running dio interceptor') if (Client.cookieStore == null) { + logDebugMessage('Initializing cookie store') Client.cookieStore = SuperTokensCookieStore(); } @@ -70,6 +75,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { // If the request already has a "cookie" header, combine it with persistent cookies if (existingCookieHeader != null) { + logDebugMessage('Combining cookie header values') options.headers[HttpHeaders.cookieHeader] = "$existingCookieHeader;${newCookiesToAdd ?? ""}"; } else { @@ -90,9 +96,13 @@ class SuperTokensInterceptorWrapper extends Interceptor { @override void onResponse(Response response, ResponseInterceptorHandler handler) async { + logDebugMessage('Intercepting response call') if (!shouldRunDioInterceptor(response.requestOptions)) { + logDebugMessage('Skipping dio interceptor') return handler.next(response); } + + logDebugMessage('Running dio interceptor') _refreshAPILock.acquireWrite(); await saveTokensFromHeaders(response); String? frontTokenFromResponse = @@ -122,6 +132,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { requestOptions.extra["__supertokensSessionRefreshAttempts"] ?? 0; if (sessionRefreshAttempts >= SuperTokens.config.maxRetryAttemptsForSessionRefresh) { + logDebugMessage('Max attempts of ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} reached for refreshing, cannot continue') handler.reject( DioException( requestOptions: response.requestOptions, @@ -139,6 +150,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { UnauthorisedResponse shouldRetry = await Client.onUnauthorisedResponse(_preRequestLocalSessionState); if (shouldRetry.status == UnauthorisedStatus.RETRY) { + logDebugMessage('Refreshing attempt: ${sessionRefreshAttempts + 1}') requestOptions.headers[HttpHeaders.cookieHeader] = userSetCookie; requestOptions.extra["__supertokensSessionRefreshAttempts"] = diff --git a/lib/src/supertokens.dart b/lib/src/supertokens.dart index f12993a..d9b7cde 100644 --- a/lib/src/supertokens.dart +++ b/lib/src/supertokens.dart @@ -93,6 +93,7 @@ class SuperTokens { static Future doesSessionExist() async { Map? tokenInfo = await FrontToken.getToken(); + logDebugMessage('Got token info: ${jsonEncode(tokenInfo)}') if (tokenInfo == null) { return false; } @@ -120,6 +121,7 @@ class SuperTokens { } static Future signOut({Function(Exception?)? completionHandler}) async { + logDebugMessage('Signing out user...') if (!(await doesSessionExist())) { SuperTokens.config.eventHandler(Eventype.SIGN_OUT); if (completionHandler != null) { @@ -139,6 +141,7 @@ class SuperTokens { return; } + logDebugMessage('Using signOutUrl: ${uri}') http.Request signOut = http.Request('post', uri); signOut = SuperTokens.config.preAPIHook(APIAction.SIGN_OUT, signOut); @@ -174,6 +177,7 @@ class SuperTokens { } static Future attemptRefreshingSession() async { + logDebugMessage('Attempting to refresh session...') LocalSessionState preRequestLocalSessionState = await SuperTokensUtils.getLocalSessionState(); bool shouldRetry = false; From b6ee6d469107ab2d64677c9b6f815bf71dbf006b Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 27 Sep 2024 17:16:54 +0530 Subject: [PATCH 4/9] Add debug logs to a lot of parts of the library --- lib/src/anti-csrf.dart | 10 ++++----- lib/src/cookie-store.dart | 28 +++++++++++++------------- lib/src/dio-interceptor-wrapper.dart | 20 +++++++++--------- lib/src/front-token.dart | 4 ++++ lib/src/normalised-url-domain.dart | 3 +++ lib/src/normalised-url-path.dart | 3 +++ lib/src/supertokens-http-client.dart | 9 ++++++++- lib/src/supertokens.dart | 20 +++++++++--------- lib/src/supertokens_dio_extension.dart | 2 ++ lib/src/utilities.dart | 1 - 10 files changed, 59 insertions(+), 41 deletions(-) diff --git a/lib/src/anti-csrf.dart b/lib/src/anti-csrf.dart index 479c493..b31144c 100644 --- a/lib/src/anti-csrf.dart +++ b/lib/src/anti-csrf.dart @@ -16,8 +16,8 @@ class AntiCSRF { static String _sharedPreferencesKey = "supertokens-flutter-anti-csrf"; static Future getToken(String? associatedAccessTokenUpdate) async { - logDebugMessage('Getting token...') - logDebugMessage('associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}') + logDebugMessage('Getting token'); + logDebugMessage('associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}'); if (associatedAccessTokenUpdate == null) { AntiCSRF._antiCSRFInfo = null; return null; @@ -46,8 +46,8 @@ class AntiCSRF { static Future setToken( String antiCSRFToken, String? associatedAccessTokenUpdate) async { - logDebugMessage('Setting token...') - logDebugMessage('associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}') + logDebugMessage('Setting token'); + logDebugMessage('associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}'); if (associatedAccessTokenUpdate == null) { AntiCSRF._antiCSRFInfo = null; return; @@ -63,7 +63,7 @@ class AntiCSRF { } static Future removeToken() async { - logDebugMessage('Removing token...') + logDebugMessage('Removing token'); SharedPreferences preferences = await SharedPreferences.getInstance(); await preferences.remove(AntiCSRF._sharedPreferencesKey); await preferences.reload(); diff --git a/lib/src/cookie-store.dart b/lib/src/cookie-store.dart index c422cae..1a858c5 100644 --- a/lib/src/cookie-store.dart +++ b/lib/src/cookie-store.dart @@ -25,12 +25,12 @@ class SuperTokensCookieStore { /// Loads all cookies stored in shared preferences into the in memory map [_allCookies] static Future _loadFromPersistence() async { - logDebugMessage('Trying to load cookies from memory') + logDebugMessage('Trying to load cookies from memory'); _allCookies = {}; String cookiesStringInStorage = _sharedPreferences?.getString(_cookieSharedPrefsKey) ?? "{}"; Map cookiesInStorage = jsonDecode(cookiesStringInStorage); - logDebugMessage('cookies found: ${jsonEncode(cookiesInStorage)}') + logDebugMessage('cookies found: ${jsonEncode(cookiesInStorage)}'); cookiesInStorage.forEach((key, value) { Uri uri = Uri.parse(key); List cookieStrings = List.from(value); @@ -52,8 +52,8 @@ class SuperTokensCookieStore { /// /// If you are trying to store cookies from a "set-cookie" header response, consider using the [saveFromSetCookieHeader] utility method which parses the header string. Future saveFromResponse(Uri uri, List cookies) async { - logDebugMessage('Saving cookies against: ${uri}') - logDebugMessage('Passed cookies: ${jsonEncode(cookies)}') + logDebugMessage('Saving cookies against: ${uri}'); + logDebugMessage('Passed cookies: ${jsonEncode(cookies)}'); await Future.forEach(cookies, (element) async { Uri uriToStore = await _getCookieUri(uri, element); List currentCookies = _allCookies?[uriToStore] ?? List.from([]); @@ -81,7 +81,7 @@ class SuperTokensCookieStore { /// Returns a Uri to use when saving the cookie Future _getCookieUri(Uri requestUri, Cookie cookie) async { - logDebugMessage('Creating cookie uri from: ${requestUri}') + logDebugMessage('Creating cookie uri from: ${requestUri}'); Uri cookieUri = Uri.parse( // ignore: unnecessary_null_comparison "${requestUri.scheme == null ? "http" : requestUri.scheme}://${requestUri.host}${cookie.path == null ? "" : cookie.path}"); @@ -104,7 +104,7 @@ class SuperTokensCookieStore { } } - logDebugMessage('Generated cookie uri: ${cookieUri}') + logDebugMessage('Generated cookie uri: ${cookieUri}'); return cookieUri; } @@ -112,7 +112,7 @@ class SuperTokensCookieStore { /// /// Strips expired cookies before storing in shared preferences Future _updatePersistentStorage() async { - logDebugMessage('Updating persistent storage with cookies...') + logDebugMessage('Updating persistent storage with cookies'); Map> mapToStore = {}; _allCookies?.forEach((key, value) { String uriString = key.toString(); @@ -131,12 +131,12 @@ class SuperTokensCookieStore { /// /// If you are trying to add cookies to a "cookie" header for a network call, consider using the [getCookieHeaderStringForRequest] which creates a semi-colon separated cookie string for a given Uri. Future> getForRequest(Uri uri) async { - logDebugMessage('Getting cookies for request from uri: ${uri}') + logDebugMessage('Getting cookies for request from uri: ${uri}'); List cookiesToReturn = []; List allValidCookies = []; if (_allCookies == null) { - logDebugMessage('No cookies found') + logDebugMessage('No cookies found'); return cookiesToReturn; } @@ -165,7 +165,7 @@ class SuperTokensCookieStore { } } - logDebugMessage('Total cookies found ${cookiesToReturn.length}') + logDebugMessage('Total cookies found ${cookiesToReturn.length}'); return cookiesToReturn; } @@ -186,7 +186,7 @@ class SuperTokensCookieStore { /// Removes a list of cookies from persistent storage Future _removeFromPersistence( Uri uri, List cookiesToRemove) async { - logDebugMessage('Removing cookies from persistent storage...') + logDebugMessage('Removing cookies from persistent storage'); List _cookiesToRemove = List.from(cookiesToRemove); List currentCookies = _allCookies?[uri] ?? List.from([]); @@ -204,7 +204,7 @@ class SuperTokensCookieStore { /// /// Does not return expired cookies and will remove them from persistent storage if any are found. Future getCookieHeaderStringForRequest(Uri uri) async { - logDebugMessage('Getting cookie header for request from uri: ${uri}') + logDebugMessage('Getting cookie header for request from uri: ${uri}'); List cookies = await getForRequest(uri); // ignore: unnecessary_null_comparison if (cookies != null && cookies.isNotEmpty) { @@ -227,7 +227,7 @@ class SuperTokensCookieStore { /// /// Expired cookies are not saved. Future saveFromSetCookieHeader(Uri uri, String? setCookieHeader) async { - logDebugMessage('Saving cookie from header against uri: ${uri}') + logDebugMessage('Saving cookie from header against uri: ${uri}'); if (setCookieHeader != null) { await saveFromResponse(uri, getCookieListFromHeader(setCookieHeader)); } @@ -238,7 +238,7 @@ class SuperTokensCookieStore { setCookieHeader.split(RegExp(r',(?=[^ ])')); List setCookiesList = setCookiesStringList.map((e) => Cookie.fromSetCookieValue(e)).toList(); - logDebugMessage('Total cookies found in header: ${setCookiesList.length}') + logDebugMessage('Total cookies found in header: ${setCookiesList.length}'); return setCookiesList; } } diff --git a/lib/src/dio-interceptor-wrapper.dart b/lib/src/dio-interceptor-wrapper.dart index dd79d09..74a5186 100644 --- a/lib/src/dio-interceptor-wrapper.dart +++ b/lib/src/dio-interceptor-wrapper.dart @@ -23,7 +23,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { @override void onRequest( RequestOptions options, RequestInterceptorHandler handler) async { - logDebugMessage('Intercepting request call') + logDebugMessage('Intercepting request call'); if (!SuperTokens.isInitCalled) { handler.reject(DioException( requestOptions: options, @@ -35,13 +35,13 @@ class SuperTokensInterceptorWrapper extends Interceptor { } if (!shouldRunDioInterceptor(options)) { - logDebugMessage('Skipping dio interceptor') + logDebugMessage('Skipping dio interceptor'); return super.onRequest(options, handler); } - logDebugMessage('Running dio interceptor') + logDebugMessage('Running dio interceptor'); if (Client.cookieStore == null) { - logDebugMessage('Initializing cookie store') + logDebugMessage('Initializing cookie store'); Client.cookieStore = SuperTokensCookieStore(); } @@ -75,7 +75,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { // If the request already has a "cookie" header, combine it with persistent cookies if (existingCookieHeader != null) { - logDebugMessage('Combining cookie header values') + logDebugMessage('Combining cookie header values'); options.headers[HttpHeaders.cookieHeader] = "$existingCookieHeader;${newCookiesToAdd ?? ""}"; } else { @@ -96,13 +96,13 @@ class SuperTokensInterceptorWrapper extends Interceptor { @override void onResponse(Response response, ResponseInterceptorHandler handler) async { - logDebugMessage('Intercepting response call') + logDebugMessage('Intercepting response call'); if (!shouldRunDioInterceptor(response.requestOptions)) { - logDebugMessage('Skipping dio interceptor') + logDebugMessage('Skipping dio interceptor'); return handler.next(response); } - logDebugMessage('Running dio interceptor') + logDebugMessage('Running dio interceptor'); _refreshAPILock.acquireWrite(); await saveTokensFromHeaders(response); String? frontTokenFromResponse = @@ -132,7 +132,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { requestOptions.extra["__supertokensSessionRefreshAttempts"] ?? 0; if (sessionRefreshAttempts >= SuperTokens.config.maxRetryAttemptsForSessionRefresh) { - logDebugMessage('Max attempts of ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} reached for refreshing, cannot continue') + logDebugMessage('Max attempts of ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} reached for refreshing, cannot continue'); handler.reject( DioException( requestOptions: response.requestOptions, @@ -150,7 +150,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { UnauthorisedResponse shouldRetry = await Client.onUnauthorisedResponse(_preRequestLocalSessionState); if (shouldRetry.status == UnauthorisedStatus.RETRY) { - logDebugMessage('Refreshing attempt: ${sessionRefreshAttempts + 1}') + logDebugMessage('Refreshing attempt: ${sessionRefreshAttempts + 1}'); requestOptions.headers[HttpHeaders.cookieHeader] = userSetCookie; requestOptions.extra["__supertokensSessionRefreshAttempts"] = diff --git a/lib/src/front-token.dart b/lib/src/front-token.dart index 9dc9b65..a45f64c 100644 --- a/lib/src/front-token.dart +++ b/lib/src/front-token.dart @@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:supertokens_flutter/src/utilities.dart'; import 'package:supertokens_flutter/supertokens.dart'; import 'package:supertokens_flutter/src/anti-csrf.dart'; +import 'package:supertokens_flutter/src/logger.dart'; class FrontToken { static String? tokenInMemory; @@ -110,6 +111,7 @@ class FrontToken { // Check the start and end of onUnauthorisedResponse // As a side-effect we reload the anti-csrf token to check if it was changed by another tab. await SuperTokensUtils.saveLastAccessTokenUpdate(); + logDebugMessage('Setting front token'); if (frontToken == "remove") { await FrontToken.removeToken(); @@ -127,6 +129,7 @@ class FrontToken { } static Future doesTokenExist() async { + logDebugMessage('Checking if token exists'); try { await _frontTokenMutex.acquire(); var frontToken = await FrontToken._getFronTokenFromStorage(); @@ -145,6 +148,7 @@ class FrontToken { } static Future removeToken() async { + logDebugMessage('Removing token'); await _tokenInfoMutex.acquireWrite(); await _removeTokenFromStorage(); await Utils.setToken(TokenType.ACCESS, ""); diff --git a/lib/src/normalised-url-domain.dart b/lib/src/normalised-url-domain.dart index 6adc69b..c08338e 100644 --- a/lib/src/normalised-url-domain.dart +++ b/lib/src/normalised-url-domain.dart @@ -1,5 +1,6 @@ import 'package:supertokens_flutter/src/errors.dart'; import 'package:supertokens_flutter/src/utilities.dart'; +import 'package:supertokens_flutter/src/logger.dart'; class NormalisedURLDomain { late String value; @@ -12,6 +13,7 @@ class NormalisedURLDomain { {bool ignoreProtocal = false}) { String trimmedInput = input.trim(); + logDebugMessage('Normalising url domain: ${input}'); try { if (!trimmedInput.startsWith("http://") && !trimmedInput.startsWith("https://")) { @@ -35,6 +37,7 @@ class NormalisedURLDomain { trimmedInput = scheme + "://" + hostSuffix; } + logDebugMessage('Normalised value: ${trimmedInput}'); return trimmedInput; } catch (e) {} diff --git a/lib/src/normalised-url-path.dart b/lib/src/normalised-url-path.dart index 10974b9..457e053 100644 --- a/lib/src/normalised-url-path.dart +++ b/lib/src/normalised-url-path.dart @@ -1,4 +1,5 @@ import 'package:supertokens_flutter/src/errors.dart'; +import 'package:supertokens_flutter/src/logger.dart'; class NormalisedURLPath { late String value; @@ -8,6 +9,7 @@ class NormalisedURLPath { } static String normaliseIRLPathOrThrowError(String input) { + logDebugMessage('Normalising URL path: ${input}'); String trimmedInput = input.trim(); try { @@ -21,6 +23,7 @@ class NormalisedURLPath { return trimmedInput.substring(0, trimmedInput.length - 1); } + logDebugMessage('Normalised value: ${trimmedInput}'); return trimmedInput; } catch (e) {} diff --git a/lib/src/supertokens-http-client.dart b/lib/src/supertokens-http-client.dart index 0ea4ca9..fd54d07 100644 --- a/lib/src/supertokens-http-client.dart +++ b/lib/src/supertokens-http-client.dart @@ -9,12 +9,13 @@ import 'package:supertokens_flutter/src/front-token.dart'; import 'package:supertokens_flutter/src/utilities.dart'; import 'package:supertokens_flutter/src/version.dart'; import 'package:supertokens_flutter/supertokens.dart'; +import 'package:supertokens_flutter/src/logger.dart'; import 'constants.dart'; /// An [http.BaseClient] implementation for using SuperTokens for your network requests. /// To make use of supertokens, use this as the client for making network calls instead of [http.Client] or your own custom clients. -/// If you use a custom client for your network calls pass an instance of it as a paramter when initialising [Client], pass [http.Client()] to use the default. +/// If you use a custom client for your network calls pass an instance of it as a parameter when initialising [Client], pass [http.Client()] to use the default. ReadWriteMutex _refreshAPILock = ReadWriteMutex(); class CustomRequest { @@ -48,6 +49,7 @@ class Client extends http.BaseClient { Future _sendWithRetry( CustomRequest customRequest) async { + logDebugMessage('Sending request'); if (Client.cookieStore == null) { Client.cookieStore = SuperTokensCookieStore(); } @@ -59,11 +61,13 @@ class Client extends http.BaseClient { if (SuperTokensUtils.getApiDomain(customRequest.request.url.toString()) != SuperTokens.config.apiDomain) { + logDebugMessage('Not matching api domain, using inner client'); return _innerClient.send(customRequest.request); } if (SuperTokensUtils.getApiDomain(customRequest.request.url.toString()) == SuperTokens.refreshTokenUrl) { + logDebugMessage('Refresh token URL matched'); return _innerClient.send(customRequest.request); } @@ -71,6 +75,7 @@ class Client extends http.BaseClient { customRequest.request.url.toString(), SuperTokens.config.apiDomain, SuperTokens.config.sessionTokenBackendDomain)) { + logDebugMessage('Skipping interceptions'); return _innerClient.send(customRequest.request); } @@ -145,10 +150,12 @@ class Client extends http.BaseClient { */ if (customRequest.sessionRefreshAttempts >= SuperTokens.config.maxRetryAttemptsForSessionRefresh) { + logDebugMessage('Max attempts of ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} reached for refreshing, cannot continue'); throw SuperTokensException( "Received a 401 response from ${customRequest.request.url}. Attempted to refresh the session and retry the request with the updated session tokens ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} times, but each attempt resulted in a 401 error. The maximum session refresh limit has been reached. Please investigate your API. To increase the session refresh attempts, update maxRetryAttemptsForSessionRefresh in the config."); } customRequest.sessionRefreshAttempts++; + logDebugMessage('Refreshing attempt: ${customRequest.sessionRefreshAttempts}'); customRequest.request = await _removeAuthHeaderIfMatchesLocalToken(copiedRequest); diff --git a/lib/src/supertokens.dart b/lib/src/supertokens.dart index d9b7cde..e2cf141 100644 --- a/lib/src/supertokens.dart +++ b/lib/src/supertokens.dart @@ -49,15 +49,15 @@ class SuperTokens { Function(Eventype)? eventHandler, http.Request Function(APIAction, http.Request)? preAPIHook, Function(APIAction, http.Request, http.Response)? postAPIHook, - debug? bool, + bool? debug, }) { if (SuperTokens.isInitCalled) { return; } // Enable debug mode if that is specified by the user. - if (debug) { - enableLogging() + if (debug != null && debug) { + enableLogging(); } logDebugMessage("Started SuperTokens with debug logging (supertokens.init called)"); @@ -82,9 +82,9 @@ class SuperTokens { config.apiDomain + (config.apiBasePath ?? '') + "/signout"; SuperTokens.rid = "session"; - logDebugMessage('refreshTokenUrl: ${refreshTokenUrl}') - logDebugMessage('signOutUrl: ${signOutUrl}') - logDebugMessage('rid: ${rid}') + logDebugMessage('refreshTokenUrl: ${refreshTokenUrl}'); + logDebugMessage('signOutUrl: ${signOutUrl}'); + logDebugMessage('rid: ${rid}'); SuperTokens.isInitCalled = true; } @@ -93,7 +93,7 @@ class SuperTokens { static Future doesSessionExist() async { Map? tokenInfo = await FrontToken.getToken(); - logDebugMessage('Got token info: ${jsonEncode(tokenInfo)}') + logDebugMessage('Got token info: ${jsonEncode(tokenInfo)}'); if (tokenInfo == null) { return false; } @@ -121,7 +121,7 @@ class SuperTokens { } static Future signOut({Function(Exception?)? completionHandler}) async { - logDebugMessage('Signing out user...') + logDebugMessage('Signing out user'); if (!(await doesSessionExist())) { SuperTokens.config.eventHandler(Eventype.SIGN_OUT); if (completionHandler != null) { @@ -141,7 +141,7 @@ class SuperTokens { return; } - logDebugMessage('Using signOutUrl: ${uri}') + logDebugMessage('Using signOutUrl: ${uri}'); http.Request signOut = http.Request('post', uri); signOut = SuperTokens.config.preAPIHook(APIAction.SIGN_OUT, signOut); @@ -177,7 +177,7 @@ class SuperTokens { } static Future attemptRefreshingSession() async { - logDebugMessage('Attempting to refresh session...') + logDebugMessage('Attempting to refresh session'); LocalSessionState preRequestLocalSessionState = await SuperTokensUtils.getLocalSessionState(); bool shouldRetry = false; diff --git a/lib/src/supertokens_dio_extension.dart b/lib/src/supertokens_dio_extension.dart index 313eb61..fd3d7fa 100644 --- a/lib/src/supertokens_dio_extension.dart +++ b/lib/src/supertokens_dio_extension.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:supertokens_flutter/src/dio-interceptor-wrapper.dart'; +import 'package:supertokens_flutter/src/logger.dart'; /// Dio extension for flexible Dio instance setup. /// @@ -15,6 +16,7 @@ import 'package:supertokens_flutter/src/dio-interceptor-wrapper.dart'; extension SuperTokensDioExtension on Dio { /// Adds the SuperTokens interceptor to the Dio instance. void addSupertokensInterceptor() { + logDebugMessage('Adding supertokens interceptor'); interceptors.add(SuperTokensInterceptorWrapper(client: this)); } } diff --git a/lib/src/utilities.dart b/lib/src/utilities.dart index a501d1b..053e9b5 100644 --- a/lib/src/utilities.dart +++ b/lib/src/utilities.dart @@ -465,7 +465,6 @@ class NormalisedInputType { 'maxRetryAttemptsForSessionRefresh': maxRetryAttemptsForSessionRefresh, 'sessionTokenBackendDomain': sessionTokenBackendDomain, 'tokenTransferMethod': tokenTransferMethod.getValue(), - 'userDefaultSuiteName': userDefaultSuiteName, }; } } From 560a263e507f2823cf181c1445b334c64218b5c3 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 27 Sep 2024 17:23:16 +0530 Subject: [PATCH 5/9] Updates changelog and bumps the version --- CHANGELOG.md | 4 ++++ lib/src/version.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc44fa..415e2e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.6.2] - 2024-09-27 + +- Adds support for debug logs using a `debug` option in the init() method of the SDK. + ## [0.6.1] - 2024-07-12 ### Changes diff --git a/lib/src/version.dart b/lib/src/version.dart index 72266fe..3505c2a 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -7,5 +7,5 @@ class Version { "2.0", "3.0" ]; - static String sdkVersion = "0.6.1"; + static String sdkVersion = "0.6.2"; } diff --git a/pubspec.yaml b/pubspec.yaml index 7ba9e41..f20af15 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: supertokens_flutter description: SuperTokens SDK for Flutter apps -version: 0.6.1 +version: 0.6.2 homepage: https://supertokens.com/ repository: https://github.com/supertokens/supertokens-flutter issue_tracker: https://github.com/supertokens/supertokens-flutter/issues From 35033e940597ff45c69582413116f352ba9832de Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 27 Sep 2024 17:25:25 +0530 Subject: [PATCH 6/9] Update pre-commit hook workflow to make it pass --- .github/workflows/pre-commit-hook-run.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index 5dd83ef..d7f0c89 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -12,8 +12,10 @@ jobs: pr-title: name: Pre commit hook check runs-on: ubuntu-latest - container: rishabhpoddar/supertokens_flutter_sdk_testing steps: - uses: actions/checkout@v2 + - name: Make a dummy change to README.md + run: | + echo "# Dummy change for PR check" >> README.md - run: git init && git add --all && git -c user.name='test' -c user.email='test@example.com' commit -m 'init for pr action' - run: ./hooks/pre-commit.sh From 7b25fbdac869f7857e7df47373aadef52af17cd4 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 27 Sep 2024 17:43:50 +0530 Subject: [PATCH 7/9] Update the debug var to be called enableDebugLogs --- lib/src/supertokens.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/supertokens.dart b/lib/src/supertokens.dart index e2cf141..93bc47c 100644 --- a/lib/src/supertokens.dart +++ b/lib/src/supertokens.dart @@ -49,14 +49,14 @@ class SuperTokens { Function(Eventype)? eventHandler, http.Request Function(APIAction, http.Request)? preAPIHook, Function(APIAction, http.Request, http.Response)? postAPIHook, - bool? debug, + bool? enableDebugLogs, }) { if (SuperTokens.isInitCalled) { return; } // Enable debug mode if that is specified by the user. - if (debug != null && debug) { + if (enableDebugLogs != null && enableDebugLogs) { enableLogging(); } From ae00a85dc63ba15b844fe151cbd03aa73e7769fe Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 27 Sep 2024 20:00:21 +0530 Subject: [PATCH 8/9] Add more logs and update logs with function name prefix --- lib/src/anti-csrf.dart | 18 ++++++--- lib/src/cookie-store.dart | 47 ++++++++++++++++------ lib/src/dio-interceptor-wrapper.dart | 60 +++++++++++++++++++++++----- lib/src/front-token.dart | 37 +++++++++++++++-- lib/src/supertokens.dart | 2 +- 5 files changed, 131 insertions(+), 33 deletions(-) diff --git a/lib/src/anti-csrf.dart b/lib/src/anti-csrf.dart index b31144c..fd67faa 100644 --- a/lib/src/anti-csrf.dart +++ b/lib/src/anti-csrf.dart @@ -16,14 +16,16 @@ class AntiCSRF { static String _sharedPreferencesKey = "supertokens-flutter-anti-csrf"; static Future getToken(String? associatedAccessTokenUpdate) async { - logDebugMessage('Getting token'); - logDebugMessage('associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}'); + logDebugMessage('AntiCSRF.getToken: Getting token'); + logDebugMessage('AntiCSRF.getToken: associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}'); if (associatedAccessTokenUpdate == null) { + logDebugMessage('AntiCSRF.getToken: associated token is null'); AntiCSRF._antiCSRFInfo = null; return null; } if (AntiCSRF._antiCSRFInfo == null) { + logDebugMessage('AntiCSRF.getToken: antiCSRFInfo is null'); SharedPreferences preferences = await SharedPreferences.getInstance(); String? antiCSRFToken = preferences.getString(AntiCSRF._sharedPreferencesKey); @@ -37,6 +39,7 @@ class AntiCSRF { } else if (AntiCSRF._antiCSRFInfo?.associatedAccessTokenUpdate != null && AntiCSRF._antiCSRFInfo?.associatedAccessTokenUpdate != associatedAccessTokenUpdate) { + logDebugMessage('AntiCSRF.getToken: associatedAccessTokenUpdate did not match'); AntiCSRF._antiCSRFInfo = null; return await AntiCSRF.getToken(associatedAccessTokenUpdate); } @@ -46,27 +49,32 @@ class AntiCSRF { static Future setToken( String antiCSRFToken, String? associatedAccessTokenUpdate) async { - logDebugMessage('Setting token'); - logDebugMessage('associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}'); + logDebugMessage('AntiCSRF.setToken: Setting token'); + logDebugMessage('AntiCSRF.setToken: associatedAccessTokenUpdate: ${associatedAccessTokenUpdate}'); if (associatedAccessTokenUpdate == null) { + logDebugMessage('AntiCSRF.setToken: associated token is null'); AntiCSRF._antiCSRFInfo = null; return; } SharedPreferences preferences = await SharedPreferences.getInstance(); + logDebugMessage('AntiCSRF.setToken: setting preferences for: ${AntiCSRF._sharedPreferencesKey}'); await preferences.setString(AntiCSRF._sharedPreferencesKey, antiCSRFToken); await preferences.reload(); + logDebugMessage('AntiCSRF.setToken: storing the token before returning'); AntiCSRF._antiCSRFInfo = _AntiCSRFInfo( antiCSRFToken: antiCSRFToken, associatedAccessTokenUpdate: associatedAccessTokenUpdate); } static Future removeToken() async { - logDebugMessage('Removing token'); + logDebugMessage('AntiCSRF.removeToken: Removing token'); + logDebugMessage('AntiCSRF.removeToken: Updating preferences'); SharedPreferences preferences = await SharedPreferences.getInstance(); await preferences.remove(AntiCSRF._sharedPreferencesKey); await preferences.reload(); + logDebugMessage('AntiCSRF.removeToken: Setting token to null'); AntiCSRF._antiCSRFInfo = null; } } diff --git a/lib/src/cookie-store.dart b/lib/src/cookie-store.dart index 1a858c5..a9390a4 100644 --- a/lib/src/cookie-store.dart +++ b/lib/src/cookie-store.dart @@ -25,12 +25,12 @@ class SuperTokensCookieStore { /// Loads all cookies stored in shared preferences into the in memory map [_allCookies] static Future _loadFromPersistence() async { - logDebugMessage('Trying to load cookies from memory'); + logDebugMessage('SuperTokensCookieStore._loadFromPersistence: Trying to load cookies from memory'); _allCookies = {}; String cookiesStringInStorage = _sharedPreferences?.getString(_cookieSharedPrefsKey) ?? "{}"; Map cookiesInStorage = jsonDecode(cookiesStringInStorage); - logDebugMessage('cookies found: ${jsonEncode(cookiesInStorage)}'); + logDebugMessage('SuperTokensCookieStore._loadFromPersistence: cookies found: ${jsonEncode(cookiesInStorage)}'); cookiesInStorage.forEach((key, value) { Uri uri = Uri.parse(key); List cookieStrings = List.from(value); @@ -52,10 +52,11 @@ class SuperTokensCookieStore { /// /// If you are trying to store cookies from a "set-cookie" header response, consider using the [saveFromSetCookieHeader] utility method which parses the header string. Future saveFromResponse(Uri uri, List cookies) async { - logDebugMessage('Saving cookies against: ${uri}'); - logDebugMessage('Passed cookies: ${jsonEncode(cookies)}'); + logDebugMessage('SuperTokensCookieStore.saveFromResponse: Saving cookies against: ${uri}'); + logDebugMessage('SuperTokensCookieStore.saveFromResponse: Passed cookies: ${jsonEncode(cookies)}'); await Future.forEach(cookies, (element) async { Uri uriToStore = await _getCookieUri(uri, element); + logDebugMessage('SuperTokensCookieStore.saveFromResponse: Setting based on uriToStore: ${uriToStore}'); List currentCookies = _allCookies?[uriToStore] ?? List.from([]); currentCookies = currentCookies // ignore: unnecessary_null_comparison @@ -73,6 +74,7 @@ class SuperTokensCookieStore { _allCookies?[uriToStore] = currentCookies; }); + logDebugMessage('SuperTokensCookieStore.saveFromResponse: Removing empty cookies'); _allCookies?.removeWhere((key, value) => value.isEmpty); await _updatePersistentStorage(); @@ -81,15 +83,17 @@ class SuperTokensCookieStore { /// Returns a Uri to use when saving the cookie Future _getCookieUri(Uri requestUri, Cookie cookie) async { - logDebugMessage('Creating cookie uri from: ${requestUri}'); + logDebugMessage('SuperTokensCookieStore._getCookieUri: Creating cookie uri from: ${requestUri}'); Uri cookieUri = Uri.parse( // ignore: unnecessary_null_comparison "${requestUri.scheme == null ? "http" : requestUri.scheme}://${requestUri.host}${cookie.path == null ? "" : cookie.path}"); if (cookie.domain != null) { String domain = cookie.domain ?? ""; + logDebugMessage('SuperTokensCookiesStore._getCookieUri: got domain: ${domain}'); if (domain[0] == ".") { domain = domain.substring(1); + logDebugMessage('SuperTokensCookiesStore._getCookieUri: Using domain substring: ${domain}'); } try { @@ -112,16 +116,19 @@ class SuperTokensCookieStore { /// /// Strips expired cookies before storing in shared preferences Future _updatePersistentStorage() async { - logDebugMessage('Updating persistent storage with cookies'); + logDebugMessage('SuperTokensCookieStore._updatePersistentStorage: Updating persistent storage with cookies'); Map> mapToStore = {}; _allCookies?.forEach((key, value) { String uriString = key.toString(); List cookieStrings = List.from(value.map((e) => e.toString()).toList()); + logDebugMessage('SuperTokensCookieStore._updatePersistentStorage: Setting to ${uriString}'); + logDebugMessage('SuperTokensCookieStore._updatePersistentStorage: Setting value ${cookieStrings}'); mapToStore[uriString] = cookieStrings; }); String stringToStore = jsonEncode(mapToStore); + logDebugMessage('SuperTokensCookieStore._updatePersistentStorage: Storing preferences: ${stringToStore}'); await _sharedPreferences?.setString(_cookieSharedPrefsKey, stringToStore); } @@ -131,12 +138,12 @@ class SuperTokensCookieStore { /// /// If you are trying to add cookies to a "cookie" header for a network call, consider using the [getCookieHeaderStringForRequest] which creates a semi-colon separated cookie string for a given Uri. Future> getForRequest(Uri uri) async { - logDebugMessage('Getting cookies for request from uri: ${uri}'); + logDebugMessage('SuperTokensCookieStore.getForRequest: Getting cookies for request from uri: ${uri}'); List cookiesToReturn = []; List allValidCookies = []; if (_allCookies == null) { - logDebugMessage('No cookies found'); + logDebugMessage('SuperTokensCookieStore.getForRequest: No cookies found'); return cookiesToReturn; } @@ -147,6 +154,7 @@ class SuperTokensCookieStore { allValidCookies.addAll(storedCookies); } } + logDebugMessage('SuperTokensCookieStore.getForRequest: Valid cookies found: ${allValidCookies.length}'); if (allValidCookies.isNotEmpty) { List cookiesToRemoveFromStorage = []; @@ -160,22 +168,29 @@ class SuperTokensCookieStore { } }); + logDebugMessage('SuperTokensCookieStore.getForRequest: Total cookies to remove: ${cookiesToRemoveFromStorage.length}'); if (cookiesToRemoveFromStorage.isNotEmpty) { await _removeFromPersistence(uri, cookiesToRemoveFromStorage); } } - logDebugMessage('Total cookies found ${cookiesToReturn.length}'); + logDebugMessage('SuperTokensCookieStore.getForRequest: Total cookies found ${cookiesToReturn.length}'); return cookiesToReturn; } /// Checks whether a network request's domain can be considered valid for a cookie to be sent bool _doesDomainMatch(String cookieHost, String requestHost) { + logDebugMessage('SuperTokensCookieStore._doesDomainMatch: Determining if domain matches'); + logDebugMessage('SuperTokensCookieStore._doesDomainMatch: cookiesHost: ${cookieHost}'); + logDebugMessage('SuperTokensCookieStore._doesDomainMatch: requestHost: ${requestHost}'); return requestHost == cookieHost || requestHost.endsWith(".$cookieHost"); } /// Checks whether a network request's path can be considered valid for a cookie to be sent bool _doesPathMatch(String cookiePath, String requestPath) { + logDebugMessage('SuperTokensCookieStore._doesPathMatch: Determining if path matches'); + logDebugMessage('SuperTokensCookieStore._doesPathMatch: cookiePath: ${cookiePath}'); + logDebugMessage('SuperTokensCookieStore._doesPathMatch: requestPath: ${requestPath}'); return (requestPath == cookiePath) || (requestPath.startsWith(cookiePath) && cookiePath[cookiePath.length - 1] == "/") || @@ -186,10 +201,13 @@ class SuperTokensCookieStore { /// Removes a list of cookies from persistent storage Future _removeFromPersistence( Uri uri, List cookiesToRemove) async { - logDebugMessage('Removing cookies from persistent storage'); + logDebugMessage('SuperTokensCookieStore._removeFromPersistence: Removing cookies from persistent storage'); + logDebugMessage('SuperTokensCookieStore._removeFromPersistence: Total cookies to remove: ${cookiesToRemove.length}'); + logDebugMessage('SuperTokensCookieStore._removeFromPersistence: uri: ${uri}'); List _cookiesToRemove = List.from(cookiesToRemove); List currentCookies = _allCookies?[uri] ?? List.from([]); + logDebugMessage('SuperTokensCookieStore._removeFromPersistence: Removing each cookie'); _cookiesToRemove.forEach((element) { currentCookies.remove(element); }); @@ -204,8 +222,9 @@ class SuperTokensCookieStore { /// /// Does not return expired cookies and will remove them from persistent storage if any are found. Future getCookieHeaderStringForRequest(Uri uri) async { - logDebugMessage('Getting cookie header for request from uri: ${uri}'); + logDebugMessage('SuperTokensCookieStore.getCookieHeaderStringForRequest: Getting cookie header for request from uri: ${uri}'); List cookies = await getForRequest(uri); + logDebugMessage('SuperTokensCookieStore.getCookieHeaderStringForRequest: Total cookies found: ${cookies.length}'); // ignore: unnecessary_null_comparison if (cookies != null && cookies.isNotEmpty) { List cookiesStringList = @@ -214,6 +233,7 @@ class SuperTokensCookieStore { return cookieHeaderString; } + logDebugMessage('SuperTokensCookieStore.getCookieHeaderStringForRequest: Returning empty value'); return ""; } @@ -227,18 +247,19 @@ class SuperTokensCookieStore { /// /// Expired cookies are not saved. Future saveFromSetCookieHeader(Uri uri, String? setCookieHeader) async { - logDebugMessage('Saving cookie from header against uri: ${uri}'); + logDebugMessage('SuperTokensCookieStore.saveFromSetCookieHeader: Saving cookie from header against uri: ${uri}'); if (setCookieHeader != null) { await saveFromResponse(uri, getCookieListFromHeader(setCookieHeader)); } } static List getCookieListFromHeader(String setCookieHeader) { + logDebugMessage('SuperTokensCookieStore.getCookieListFromHeader: Getting cookie list from header: ${setCookieHeader}'); List setCookiesStringList = setCookieHeader.split(RegExp(r',(?=[^ ])')); List setCookiesList = setCookiesStringList.map((e) => Cookie.fromSetCookieValue(e)).toList(); - logDebugMessage('Total cookies found in header: ${setCookiesList.length}'); + logDebugMessage('SuperTokensCookieStore.getCookieListFromHeader: Total cookies found in header: ${setCookiesList.length}'); return setCookiesList; } } diff --git a/lib/src/dio-interceptor-wrapper.dart b/lib/src/dio-interceptor-wrapper.dart index 74a5186..eff4b0b 100644 --- a/lib/src/dio-interceptor-wrapper.dart +++ b/lib/src/dio-interceptor-wrapper.dart @@ -23,7 +23,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { @override void onRequest( RequestOptions options, RequestInterceptorHandler handler) async { - logDebugMessage('Intercepting request call'); + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Intercepting request call'); if (!SuperTokens.isInitCalled) { handler.reject(DioException( requestOptions: options, @@ -35,24 +35,27 @@ class SuperTokensInterceptorWrapper extends Interceptor { } if (!shouldRunDioInterceptor(options)) { - logDebugMessage('Skipping dio interceptor'); + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Skipping dio interceptor'); return super.onRequest(options, handler); } - logDebugMessage('Running dio interceptor'); + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Running dio interceptor'); if (Client.cookieStore == null) { - logDebugMessage('Initializing cookie store'); + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Initializing cookie store'); Client.cookieStore = SuperTokensCookieStore(); } + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Removing auth header if it matches local token'); options = await _removeAuthHeaderIfMatchesLocalToken(options); + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Getting local session state'); _preRequestLocalSessionState = await SuperTokensUtils.getLocalSessionState(); String? antiCSRFToken = await AntiCSRF.getToken( _preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCSRFToken != null) { + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: antiCSRFToken is not null'); options.headers[antiCSRFHeaderKey] = antiCSRFToken; } @@ -60,6 +63,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { SuperTokens.config.tokenTransferMethod; options.headers["st-auth-mode"] = tokenTransferMethod.getValue(); + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Setting authorization header if required'); options = await _setAuthorizationHeaderIfRequired(options); userSetCookie = options.headers[HttpHeaders.cookieHeader]; @@ -68,6 +72,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { if (!uriForCookieString.endsWith("/")) { uriForCookieString += "/"; } + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: uriForCookieString: ${uriForCookieString}'); String? newCookiesToAdd = await Client.cookieStore ?.getCookieHeaderStringForRequest(Uri.parse(uriForCookieString)); @@ -75,13 +80,15 @@ class SuperTokensInterceptorWrapper extends Interceptor { // If the request already has a "cookie" header, combine it with persistent cookies if (existingCookieHeader != null) { - logDebugMessage('Combining cookie header values'); + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Combining cookie header values'); options.headers[HttpHeaders.cookieHeader] = "$existingCookieHeader;${newCookiesToAdd ?? ""}"; } else { + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Adding new cookie header'); options.headers[HttpHeaders.cookieHeader] = newCookiesToAdd ?? ""; } + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Injecting status check in validateStatus'); var oldValidate = options.validateStatus; options.validateStatus = (status) { if (status != null && @@ -91,22 +98,26 @@ class SuperTokensInterceptorWrapper extends Interceptor { return oldValidate(status); }; + logDebugMessage('SuperTokensInterceptorWrapper.onRequest: Calling next on handler'); handler.next(options); } @override void onResponse(Response response, ResponseInterceptorHandler handler) async { - logDebugMessage('Intercepting response call'); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Intercepting response call'); if (!shouldRunDioInterceptor(response.requestOptions)) { - logDebugMessage('Skipping dio interceptor'); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Skipping dio interceptor'); return handler.next(response); } - logDebugMessage('Running dio interceptor'); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Running dio interceptor'); _refreshAPILock.acquireWrite(); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: write acquired'); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: saving tokens from headers'); await saveTokensFromHeaders(response); String? frontTokenFromResponse = response.headers.map[frontTokenHeaderKey]?.first.toString(); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: frontTokenFromResponse: ${frontTokenFromResponse}'); SuperTokensUtils.fireSessionUpdateEventsIfNecessary( wasLoggedIn: _preRequestLocalSessionState.status == LocalSessionStateStatus.EXISTS, @@ -115,6 +126,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { ); List? setCookieFromResponse = response.headers.map[HttpHeaders.setCookieHeader]; + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: setCookieFromResponse length: ${setCookieFromResponse?.length}'); setCookieFromResponse?.forEach((element) async { await Client.cookieStore ?.saveFromSetCookieHeader(response.realUri, element); @@ -127,12 +139,13 @@ class SuperTokensInterceptorWrapper extends Interceptor { * To prevent this infinite loop, we break out of the loop after retrying the original request a specified number of times. * The maximum number of retry attempts is defined by maxRetryAttemptsForSessionRefresh config variable. */ + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Got expiry status code'); RequestOptions requestOptions = response.requestOptions; int sessionRefreshAttempts = requestOptions.extra["__supertokensSessionRefreshAttempts"] ?? 0; if (sessionRefreshAttempts >= SuperTokens.config.maxRetryAttemptsForSessionRefresh) { - logDebugMessage('Max attempts of ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} reached for refreshing, cannot continue'); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Max attempts of ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} reached for refreshing, cannot continue'); handler.reject( DioException( requestOptions: response.requestOptions, @@ -145,12 +158,14 @@ class SuperTokensInterceptorWrapper extends Interceptor { return; } + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: removing auth header if it matches token'); requestOptions = await _removeAuthHeaderIfMatchesLocalToken(requestOptions); UnauthorisedResponse shouldRetry = await Client.onUnauthorisedResponse(_preRequestLocalSessionState); if (shouldRetry.status == UnauthorisedStatus.RETRY) { - logDebugMessage('Refreshing attempt: ${sessionRefreshAttempts + 1}'); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Got RETRY status'); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Refreshing attempt: ${sessionRefreshAttempts + 1}'); requestOptions.headers[HttpHeaders.cookieHeader] = userSetCookie; requestOptions.extra["__supertokensSessionRefreshAttempts"] = @@ -159,14 +174,18 @@ class SuperTokensInterceptorWrapper extends Interceptor { Response res = await client.fetch(requestOptions); List? setCookieFromResponse = res.headers.map[HttpHeaders.setCookieHeader]; + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: setCookieFromResponse length: ${setCookieFromResponse?.length}'); setCookieFromResponse?.forEach((element) async { await Client.cookieStore ?.saveFromSetCookieHeader(res.realUri, element); }); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Saving tokens from headers'); await saveTokensFromHeaders(res); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Calling next on handler'); return handler.next(res); } else { if (shouldRetry.exception != null) { + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Got non null exception'); handler.reject( DioException( requestOptions: response.requestOptions, @@ -176,11 +195,13 @@ class SuperTokensInterceptorWrapper extends Interceptor { return; } else { _refreshAPILock.release(); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Calling next on handler'); return handler.next(response); } } } else { _refreshAPILock.release(); + logDebugMessage('SuperTokensInterceptorWrapper.onResponse: Calling next on handler'); return handler.next(response); } } on DioException catch (e) { @@ -196,10 +217,13 @@ class SuperTokensInterceptorWrapper extends Interceptor { } Future saveTokensFromHeaders(Response response) async { + logDebugMessage('SuperTokensInterceptorWrapper.saveTokensFromHeaders: Saving tokens from header'); String? frontTokenFromResponse = response.headers.map[frontTokenHeaderKey]?.first.toString(); + logDebugMessage('SuperTokensInterceptorWrapper.saveTokensFromHeaders: frontTokenFromResponse: ${frontTokenFromResponse}'); if (frontTokenFromResponse != null) { + logDebugMessage('SuperTokensInterceptorWrapper.saveTokensFromHeaders: Setting token since it is not null'); await FrontToken.setItem(frontTokenFromResponse); } @@ -210,6 +234,7 @@ class SuperTokensInterceptorWrapper extends Interceptor { LocalSessionState localSessionState = await SuperTokensUtils.getLocalSessionState(); + logDebugMessage('SuperTokensInterceptorWrapper.saveTokensFromHeaders: Setting token in AntiCSRF'); await AntiCSRF.setToken( antiCSRFFromResponse, localSessionState.lastAccessTokenUpdate, @@ -219,29 +244,39 @@ class SuperTokensInterceptorWrapper extends Interceptor { String? accessHeader = response.headers.map[ACCESS_TOKEN_NAME]?.first.toString(); + logDebugMessage('SuperTokensInterceptorWrapper.saveTokensFromHeaders: accessHeader: ${accessHeader}'); if (accessHeader != null) { + logDebugMessage('SuperTokensInterceptorWrapper.saveTokensFromHeaders: Setting access header'); await Utils.setToken(TokenType.ACCESS, accessHeader); } String? refreshHeader = response.headers.map[REFRESH_TOKEN_NAME]?.first.toString(); + logDebugMessage('SuperTokensInterceptorWrapper.saveTokensFromHeaders: refreshHeader: ${refreshHeader}'); if (refreshHeader != null) { + logDebugMessage('SuperTokensInterceptorWrapper.saveTokensFromHeaders: Setting refresh header'); await Utils.setToken(TokenType.REFRESH, refreshHeader); } } Future _removeAuthHeaderIfMatchesLocalToken( RequestOptions req) async { + logDebugMessage('SuperTokensInterceptorWrapper._removeAuthHeaderIfMatchesLocalToken: Removing auth header if it matches local token'); if (req.headers.containsKey("Authorization") || req.headers.containsKey("authorization")) { String? authValue = req.headers['Authorization']; + logDebugMessage('SuperTokensInterceptorWrapper._removeAuthHeaderIfMatchesLocalToken: authValue: ${authValue}'); if (authValue == null) { + logDebugMessage('SuperTokensInterceptorWrapper._removeAuthHeaderIfMatchesLocalToken: Setting auth header'); authValue = req.headers["authorization"]; } String? accessToken = await Utils.getTokenForHeaderAuth(TokenType.ACCESS); String? refreshToken = await Utils.getTokenForHeaderAuth(TokenType.REFRESH); + logDebugMessage('SuperTokensInterceptorWrapper._removeAuthHeaderIfMatchesLocalToken: accessToken: ${accessToken}'); + logDebugMessage('SuperTokensInterceptorWrapper._removeAuthHeaderIfMatchesLocalToken: refreshToken: ${refreshToken}'); if (accessToken != null && refreshToken != null && authValue != "Bearer $accessToken") { + logDebugMessage('SuperTokensInterceptorWrapper._removeAuthHeaderIfMatchesLocalToken: Removing authorization headers'); req.headers.remove('Authorization'); req.headers.remove('authorization'); } @@ -252,15 +287,20 @@ class SuperTokensInterceptorWrapper extends Interceptor { static Future _setAuthorizationHeaderIfRequired( RequestOptions options, {bool addRefreshToken = false}) async { + logDebugMessage('SuperTokensInterceptorWrapper._setAuthorizationHeaderIfRequired: Setting authorization header if required'); String? accessToken = await Utils.getTokenForHeaderAuth(TokenType.ACCESS); String? refreshToken = await Utils.getTokenForHeaderAuth(TokenType.REFRESH); + logDebugMessage('SuperTokensInterceptorWrapper._setAuthorizationHeaderIfRequired: accessToken: ${accessToken}'); + logDebugMessage('SuperTokensInterceptorWrapper._setAuthorizationHeaderIfRequired: refreshToken: ${refreshToken}'); if (accessToken != null && refreshToken != null) { if (options.headers["Authorization"] != null || options.headers["authorization"] != null) { // no-op + logDebugMessage('SuperTokensInterceptorWrapper._setAuthorizationHeaderIfRequired: Doing nothing as headers are already set'); } else { String tokenToAdd = addRefreshToken ? refreshToken : accessToken; + logDebugMessage('SuperTokensInterceptorWrapper._setAuthorizationHeaderIfRequired: Setting header to bearer: ${tokenToAdd}'); options.headers["Authorization"] = "Bearer $tokenToAdd"; } } diff --git a/lib/src/front-token.dart b/lib/src/front-token.dart index a45f64c..e61794b 100644 --- a/lib/src/front-token.dart +++ b/lib/src/front-token.dart @@ -14,50 +14,65 @@ class FrontToken { static Mutex _frontTokenMutex = Mutex(); static Future _getFronTokenFromStorage() async { + logDebugMessage('FrontToken._getFronTokenFromStorage: Getting front token from storage'); if (tokenInMemory == null) { + logDebugMessage('FrontToken._getFronTokenFromStorage: Fetching token from shared preferences'); String? token = (await SharedPreferences.getInstance()) .getString(FrontToken._sharedPreferencesKey); + logDebugMessage('FrontToken._getFronTokenFromStorage: Setting in memory: ${token}'); FrontToken.tokenInMemory = token; } + logDebugMessage('FrontToken._getFronTokenFromStorage: token from memory: ${FrontToken.tokenInMemory}'); return FrontToken.tokenInMemory; } static Future _getFrontToken() async { + logDebugMessage('FrontToken._getFrontToken: Getting front token'); LocalSessionState localSessionState = await SuperTokensUtils.getLocalSessionState(); if (localSessionState.status == LocalSessionStateStatus.NOT_EXISTS) { + logDebugMessage('FrontToken._getFrontToken: status is not exists'); return null; } + logDebugMessage('FrontToken._getFrontToken: Returning token from storage'); return _getFronTokenFromStorage(); } static Map _parseFrontToken(fronTokenDecoded) { + logDebugMessage('FrontToken._parseFrontToken: parsing front token: ${fronTokenDecoded}'); var base64Decoded = base64Decode(fronTokenDecoded); String decodedString = utf8.decode(base64Decoded); var result = jsonDecode(decodedString); + logDebugMessage('FrontToken._parseFrontToken: decoded value: ${result}'); return result; } static Future?> _getTokenInfo() async { Map? finalReturnValue; + logDebugMessage('FrontToken._getTokenInfo: getting token info'); while (true) { String? frontToken = await _getFrontToken(); + logDebugMessage('FrontToken._getTokenInfo: got frontToken: ${frontToken}'); if (frontToken == null) { + logDebugMessage('FrontToken._getTokenInfo: Fetching local session state since token is null'); LocalSessionState localSessionState = await SuperTokensUtils.getLocalSessionState(); - if (localSessionState.status == LocalSessionStateStatus.EXISTS) { + logDebugMessage('FrontToken._getTokenInfo: local session state status is exists'); await _frontTokenMutex.acquire(); } else { + logDebugMessage('FrontToken._getTokenInfo: local session state status is not exists'); finalReturnValue = null; if (_frontTokenMutex.isLocked) { _frontTokenMutex.release(); } + logDebugMessage('FrontToken._getTokenInfo: Breaking out of the loop'); break; } } else { + logDebugMessage('FrontToken._getTokenInfo: Parsing and returning token'); finalReturnValue = _parseFrontToken(frontToken); if (_frontTokenMutex.isLocked) { _frontTokenMutex.release(); @@ -73,20 +88,27 @@ class FrontToken { } static Future _setFrontTokenToStorage(String? frontToken) async { + logDebugMessage('FrontToken._setFrontTokenToStorage: Setting front token in storage'); + logDebugMessage('FrontToken._setFrontTokenToStorage: frontToken: ${frontToken}'); var instance = await SharedPreferences.getInstance(); if (frontToken == null) { + logDebugMessage('FrontToken._setFrontTokenToStorage: token is null, removing from preferences and memory'); instance.remove(FrontToken._sharedPreferencesKey); FrontToken.tokenInMemory = null; } else { + logDebugMessage('FrontToken._setFrontTokenToStorage: Setting updated token in preferences and memory'); instance.setString(FrontToken._sharedPreferencesKey, frontToken); FrontToken.tokenInMemory = frontToken; } } static Future _setFronToken(String? frontToken) async { + logDebugMessage('FrontToken._setFronToken: Setting front token: ${frontToken}'); String? oldToken = await _getFronTokenFromStorage(); + logDebugMessage('FrontToken._setFronToken: oldToken: ${oldToken}'); if (oldToken != null && frontToken != null) { + logDebugMessage('FrontToken._setFronToken: Both oldToken and frontToken are non null'); Map oldTokenPayload = _parseFrontToken(oldToken)['up'] as Map; Map newTokenPayload = @@ -96,6 +118,7 @@ class FrontToken { String newPayloadString = newTokenPayload.toString(); if (oldPayloadString != newPayloadString) { + logDebugMessage('FrontToken._setFronToken: payloads do not match, sending event: ACCESS_TOKEN_PAYLOAD_UPDATED'); SuperTokens.config.eventHandler(Eventype.ACCESS_TOKEN_PAYLOAD_UPDATED); } } @@ -111,15 +134,17 @@ class FrontToken { // Check the start and end of onUnauthorisedResponse // As a side-effect we reload the anti-csrf token to check if it was changed by another tab. await SuperTokensUtils.saveLastAccessTokenUpdate(); - logDebugMessage('Setting front token'); + logDebugMessage('FrontToken.setItem: Setting item for frontToken'); if (frontToken == "remove") { + logDebugMessage('FrontToken.setItem: Removing front token'); await FrontToken.removeToken(); return; } try { await _frontTokenMutex.acquire(); + logDebugMessage('FrontToken.setItem: Setting front token'); await FrontToken._setFronToken(frontToken); } finally { if (_frontTokenMutex.isLocked) { @@ -129,10 +154,11 @@ class FrontToken { } static Future doesTokenExist() async { - logDebugMessage('Checking if token exists'); + logDebugMessage('FrontToken.doesTokenExist: Checking if token exists'); try { await _frontTokenMutex.acquire(); var frontToken = await FrontToken._getFronTokenFromStorage(); + logDebugMessage('FrontToken.doesTokenExist: frontToken: ${frontToken}'); return frontToken != null; } finally { if (_frontTokenMutex.isLocked) { @@ -142,17 +168,20 @@ class FrontToken { } static Future _removeTokenFromStorage() async { + logDebugMessage('FrontToken._removeTokenFromStorage: Removing token from preferences and memory'); (await SharedPreferences.getInstance()) .remove(FrontToken._sharedPreferencesKey); FrontToken.tokenInMemory = null; } static Future removeToken() async { - logDebugMessage('Removing token'); + logDebugMessage('FrontToken.removeToken: Removing token'); await _tokenInfoMutex.acquireWrite(); await _removeTokenFromStorage(); + logDebugMessage('FrontToken.removeToken: Setting access and refresh token to empty value'); await Utils.setToken(TokenType.ACCESS, ""); await Utils.setToken(TokenType.REFRESH, ""); + logDebugMessage('FrontToken.removeToken: Removing token from AntiCSRF'); await AntiCSRF.removeToken(); if (_tokenInfoMutex.isLocked) { _tokenInfoMutex.release(); diff --git a/lib/src/supertokens.dart b/lib/src/supertokens.dart index 93bc47c..00a8f12 100644 --- a/lib/src/supertokens.dart +++ b/lib/src/supertokens.dart @@ -60,7 +60,7 @@ class SuperTokens { enableLogging(); } - logDebugMessage("Started SuperTokens with debug logging (supertokens.init called)"); + logDebugMessage("init: Started SuperTokens with debug logging (supertokens.init called)"); SuperTokens.config = NormalisedInputType.normaliseInputType( apiDomain, From 2686977d55a02eb9e8cd37089c3216f3350b519f Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 30 Sep 2024 10:32:00 +0530 Subject: [PATCH 9/9] Add more logs and make them have function as prefix --- lib/src/logger.dart | 2 +- lib/src/normalised-url-domain.dart | 11 ++++++-- lib/src/normalised-url-path.dart | 11 +++++--- lib/src/supertokens-http-client.dart | 35 +++++++++++++++++++++----- lib/src/supertokens.dart | 30 ++++++++++++++-------- lib/src/supertokens_dio_extension.dart | 2 +- 6 files changed, 68 insertions(+), 23 deletions(-) diff --git a/lib/src/logger.dart b/lib/src/logger.dart index 4d9197d..147c3cb 100644 --- a/lib/src/logger.dart +++ b/lib/src/logger.dart @@ -24,6 +24,6 @@ void disableLogging() { void logDebugMessage(String message) { if (_supertokensWebsiteLogging) { print( - '$supertokensDebugNamespace {t: "${DateTime.now().toIso8601String()}", message: "$message", supertokens-react-native: "${Version.sdkVersion}"}'); + '$supertokensDebugNamespace {t: "${DateTime.now().toIso8601String()}", message: "$message", supertokens-flutter: "${Version.sdkVersion}"}'); } } diff --git a/lib/src/normalised-url-domain.dart b/lib/src/normalised-url-domain.dart index c08338e..7ad133b 100644 --- a/lib/src/normalised-url-domain.dart +++ b/lib/src/normalised-url-domain.dart @@ -13,10 +13,11 @@ class NormalisedURLDomain { {bool ignoreProtocal = false}) { String trimmedInput = input.trim(); - logDebugMessage('Normalising url domain: ${input}'); + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: Normalising url domain: ${input}'); try { if (!trimmedInput.startsWith("http://") && !trimmedInput.startsWith("https://")) { + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: Does not start with http'); throw SuperTokensException("failable error"); } @@ -26,18 +27,24 @@ class NormalisedURLDomain { // Flutter returns one of these values if the URL does not have a port bool hasNoPort = !Utils.doesUrlHavePort(uri); String hostSuffix = hasNoPort ? hostName : hostName + ":${uri.port}"; + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: hostName: ${hostName}'); + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: scheme: ${scheme}'); + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: hasNoPort: ${hasNoPort}'); + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: hostSuffix: ${hostSuffix}'); if (ignoreProtocal) { + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: Ignoring protocol'); if (hostName.startsWith("localhost") || Utils.isIPAddress(input)) { trimmedInput = "https://$hostSuffix"; } else { trimmedInput = "https://" + hostSuffix; } } else { + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: Keeping protocol'); trimmedInput = scheme + "://" + hostSuffix; } - logDebugMessage('Normalised value: ${trimmedInput}'); + logDebugMessage('NormalisedURLDomain.normaliseUrlDomainOrThrowError: Normalised value: ${trimmedInput}'); return trimmedInput; } catch (e) {} diff --git a/lib/src/normalised-url-path.dart b/lib/src/normalised-url-path.dart index 457e053..b6782fd 100644 --- a/lib/src/normalised-url-path.dart +++ b/lib/src/normalised-url-path.dart @@ -9,21 +9,24 @@ class NormalisedURLPath { } static String normaliseIRLPathOrThrowError(String input) { - logDebugMessage('Normalising URL path: ${input}'); + logDebugMessage('NormalisedURLPath.normaliseIRLPathOrThrowError: Normalising URL path: ${input}'); String trimmedInput = input.trim(); try { - if (!trimmedInput.startsWith('http')) + if (!trimmedInput.startsWith('http')) { + logDebugMessage('NormalisedURLPath.normaliseIRLPathOrThrowError: Got invalid protocol'); throw SuperTokensException('Invalid protocol'); + } Uri url = Uri.parse(trimmedInput); trimmedInput = url.path; + logDebugMessage('NormalisedURLPath.normaliseIRLPathOrThrowError: trimmedInput: ${trimmedInput}'); if (trimmedInput.endsWith('/')) { return trimmedInput.substring(0, trimmedInput.length - 1); } - logDebugMessage('Normalised value: ${trimmedInput}'); + logDebugMessage('NormalisedURLPath.normaliseIRLPathOrThrowError: Normalised value: ${trimmedInput}'); return trimmedInput; } catch (e) {} @@ -34,6 +37,7 @@ class NormalisedURLPath { !trimmedInput.startsWith('http://') && !trimmedInput.startsWith('https://')) { trimmedInput = 'https://' + trimmedInput; + logDebugMessage('NormalisedURLPath.normaliseIRLPathOrThrowError: Determined to be a domain name'); return normaliseIRLPathOrThrowError(trimmedInput); } @@ -59,6 +63,7 @@ class NormalisedURLPath { static bool isDomainGiven(String input) { if (input.indexOf('.') == -1 || input.startsWith('/')) { + logDebugMessage('NormalisedURLPath.isDomainGiven: Not a domain'); return false; } try { diff --git a/lib/src/supertokens-http-client.dart b/lib/src/supertokens-http-client.dart index fd54d07..57c1438 100644 --- a/lib/src/supertokens-http-client.dart +++ b/lib/src/supertokens-http-client.dart @@ -49,8 +49,9 @@ class Client extends http.BaseClient { Future _sendWithRetry( CustomRequest customRequest) async { - logDebugMessage('Sending request'); + logDebugMessage('Client._sendWithRetry: Sending request'); if (Client.cookieStore == null) { + logDebugMessage('Client._sendWithRetry: Initiating cookie store'); Client.cookieStore = SuperTokensCookieStore(); } @@ -61,13 +62,13 @@ class Client extends http.BaseClient { if (SuperTokensUtils.getApiDomain(customRequest.request.url.toString()) != SuperTokens.config.apiDomain) { - logDebugMessage('Not matching api domain, using inner client'); + logDebugMessage('Client._sendWithRetry: Not matching api domain, using inner client'); return _innerClient.send(customRequest.request); } if (SuperTokensUtils.getApiDomain(customRequest.request.url.toString()) == SuperTokens.refreshTokenUrl) { - logDebugMessage('Refresh token URL matched'); + logDebugMessage('Client._sendWithRetry: Refresh token URL matched'); return _innerClient.send(customRequest.request); } @@ -75,7 +76,7 @@ class Client extends http.BaseClient { customRequest.request.url.toString(), SuperTokens.config.apiDomain, SuperTokens.config.sessionTokenBackendDomain)) { - logDebugMessage('Skipping interceptions'); + logDebugMessage('Client._sendWithRetry: Skipping interceptions'); return _innerClient.send(customRequest.request); } @@ -86,6 +87,7 @@ class Client extends http.BaseClient { LocalSessionState preRequestLocalSessionState; http.StreamedResponse response; try { + logDebugMessage('Client._sendWithRetry: Copying request to use it'); copiedRequest = SuperTokensUtils.copyRequest(customRequest.request); copiedRequest = await _removeAuthHeaderIfMatchesLocalToken(copiedRequest); @@ -95,15 +97,18 @@ class Client extends http.BaseClient { preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCSRFToken != null) { + logDebugMessage('Client._sendWithRetry: antiCSRFtoken found, setting it'); copiedRequest.headers[antiCSRFHeaderKey] = antiCSRFToken; } SuperTokensTokenTransferMethod tokenTransferMethod = SuperTokens.config.tokenTransferMethod; + logDebugMessage('Client._sendWithRetry: Setting st-auth-mode'); copiedRequest.headers["st-auth-mode"] = tokenTransferMethod.getValue(); // Adding Authorization headers + logDebugMessage('Client._sendWithRetry: Adding authorization headers'); copiedRequest = await Utils.setAuthorizationHeaderIfRequired(copiedRequest); @@ -115,9 +120,11 @@ class Client extends http.BaseClient { // If the request already has a "cookie" header, combine it with persistent cookies if (existingCookieHeader != null && existingCookieHeader != "") { + logDebugMessage('Client._sendWithRetry: Combining cookies with existing ones'); copiedRequest.headers[HttpHeaders.cookieHeader] = _generateCookieHeader(existingCookieHeader, newCookiesToAdd); } else { + logDebugMessage('Client._sendWithRetry: Adding new cookies: ${newCookiesToAdd}'); copiedRequest.headers[HttpHeaders.cookieHeader] = newCookiesToAdd ?? ""; } @@ -134,6 +141,7 @@ class Client extends http.BaseClient { ); // Save cookies from the response + logDebugMessage('Client._sendWithRetry: Saving cookies from the response'); String? setCookieFromResponse = response.headers[HttpHeaders.setCookieHeader]; await Client.cookieStore?.saveFromSetCookieHeader( @@ -150,12 +158,12 @@ class Client extends http.BaseClient { */ if (customRequest.sessionRefreshAttempts >= SuperTokens.config.maxRetryAttemptsForSessionRefresh) { - logDebugMessage('Max attempts of ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} reached for refreshing, cannot continue'); + logDebugMessage('Client._sendWithRetry: Max attempts of ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} reached for refreshing, cannot continue'); throw SuperTokensException( "Received a 401 response from ${customRequest.request.url}. Attempted to refresh the session and retry the request with the updated session tokens ${SuperTokens.config.maxRetryAttemptsForSessionRefresh} times, but each attempt resulted in a 401 error. The maximum session refresh limit has been reached. Please investigate your API. To increase the session refresh attempts, update maxRetryAttemptsForSessionRefresh in the config."); } customRequest.sessionRefreshAttempts++; - logDebugMessage('Refreshing attempt: ${customRequest.sessionRefreshAttempts}'); + logDebugMessage('Client._sendWithRetry: Refreshing attempt: ${customRequest.sessionRefreshAttempts}'); customRequest.request = await _removeAuthHeaderIfMatchesLocalToken(copiedRequest); @@ -164,6 +172,7 @@ class Client extends http.BaseClient { await onUnauthorisedResponse(preRequestLocalSessionState); if (shouldRetry.status == UnauthorisedStatus.RETRY) { // Here we use the original request because it wont contain any of the modifications we make + logDebugMessage('Client._sendWithRetry: Got RETRY status, retrying...'); return await _sendWithRetry(customRequest); } else { if (shouldRetry.exception != null) { @@ -192,6 +201,7 @@ class Client extends http.BaseClient { if (accessToken != null && refreshToken != null && authValue == "Bearer $accessToken") { + logDebugMessage('Client._removeAuthHeaderIfMatchesLocalToken: Removing authorization headers'); mutableRequest.headers.remove("Authorization"); mutableRequest.headers.remove("authorization"); } @@ -208,6 +218,7 @@ class Client extends http.BaseClient { await SuperTokensUtils.getLocalSessionState(); if (postLockLocalSessionState.status == LocalSessionStateStatus.NOT_EXISTS) { + logDebugMessage('Client.onUnauthorisedResponse: local session state does not exist, throwing unauthorised error'); SuperTokens.config.eventHandler(Eventype.UNAUTHORISED); return UnauthorisedResponse(status: UnauthorisedStatus.SESSION_EXPIRED); } @@ -218,6 +229,7 @@ class Client extends http.BaseClient { LocalSessionStateStatus.EXISTS && postLockLocalSessionState.lastAccessTokenUpdate != preRequestLocalSessionState.lastAccessTokenUpdate)) { + logDebugMessage('Client.onUnauthorisedResponse: Retry required, throwing retry error'); return UnauthorisedResponse(status: UnauthorisedStatus.RETRY); } Uri refreshUrl = Uri.parse(SuperTokens.refreshTokenUrl); @@ -228,21 +240,26 @@ class Client extends http.BaseClient { if (preRequestLocalSessionState.status == LocalSessionStateStatus.EXISTS) { + logDebugMessage('Client.onUnauthorisedResponse: preRequestLocalSessionState exists'); String? antiCSRFToken = await AntiCSRF.getToken( preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCSRFToken != null) { + logDebugMessage('Client.onUnauthorisedResponse: Setting antiCSRF token'); refreshReq.headers[antiCSRFHeaderKey] = antiCSRFToken; } } + logDebugMessage('Client.onUnauthorisedResponse: Setting rid and fdi-version headers'); refreshReq.headers['rid'] = SuperTokens.rid; refreshReq.headers['fdi-version'] = Version.supported_fdi.join(','); // Add cookies to request headers + logDebugMessage('Client.onUnauthorisedResponse: Adding cookies to headers'); String? newCookiesToAdd = await Client.cookieStore?.getCookieHeaderStringForRequest(refreshUrl); refreshReq.headers[HttpHeaders.cookieHeader] = newCookiesToAdd ?? ""; SuperTokensTokenTransferMethod tokenTransferMethod = SuperTokens.config.tokenTransferMethod; + logDebugMessage('Client.onUnauthorisedResponse: Setting st-auth-mode'); refreshReq.headers .addAll({'st-auth-mode': tokenTransferMethod.getValue()}); refreshReq = @@ -259,9 +276,11 @@ class Client extends http.BaseClient { bool isUnauthorised = response.statusCode == SuperTokens.config.sessionExpiredStatusCode; + logDebugMessage('Client.onUnauthorisedResponse: isUnauthorised: ${isUnauthorised}'); String? frontTokenInHeaders = response.headers[frontTokenHeaderKey]; if (isUnauthorised && frontTokenInHeaders == null) { + logDebugMessage('Client.onUnauthorisedResponse: Removing frontToken by setting remove'); await FrontToken.setItem("remove"); } @@ -290,6 +309,7 @@ class Client extends http.BaseClient { // this is a result of the refresh API returning a session expiry, which // means that the frontend did not know for sure that the session existed // in the first place. + logDebugMessage('Client.onUnauthorisedResponse: local session state does not exist'); return UnauthorisedResponse(status: UnauthorisedStatus.SESSION_EXPIRED); } @@ -309,6 +329,7 @@ class Client extends http.BaseClient { } static String _generateCookieHeader(String oldCookie, String? newCookie) { + logDebugMessage('Client._generateCookieHeader: Generating cookie header'); if (newCookie == null) { return oldCookie; } @@ -317,6 +338,8 @@ class Client extends http.BaseClient { List newCookies = SuperTokensCookieStore.getCookieListFromHeader(newCookie); Iterable newCookiesNames = newCookies.map((e) => e.name); + logDebugMessage('Client._generateCookieHeader: oldCookies found: ${oldCookies.length}'); + logDebugMessage('Client._generateCookieHeader: newCookies found: ${newCookies.length}'); oldCookies.removeWhere((element) => newCookiesNames.contains(element.name)); newCookies.addAll(oldCookies); return newCookies.map((e) => e.toString()).join(';'); diff --git a/lib/src/supertokens.dart b/lib/src/supertokens.dart index 00a8f12..763a2c4 100644 --- a/lib/src/supertokens.dart +++ b/lib/src/supertokens.dart @@ -60,7 +60,7 @@ class SuperTokens { enableLogging(); } - logDebugMessage("init: Started SuperTokens with debug logging (supertokens.init called)"); + logDebugMessage("SuperTokens.init: Started SuperTokens with debug logging (supertokens.init called)"); SuperTokens.config = NormalisedInputType.normaliseInputType( apiDomain, @@ -74,7 +74,7 @@ class SuperTokens { postAPIHook, ); - logDebugMessage('config: ${jsonEncode(config.toJson())}'); + logDebugMessage('SuperTokens.init: config: ${jsonEncode(config.toJson())}'); SuperTokens.refreshTokenUrl = config.apiDomain + (config.apiBasePath ?? '') + "/session/refresh"; @@ -82,9 +82,9 @@ class SuperTokens { config.apiDomain + (config.apiBasePath ?? '') + "/signout"; SuperTokens.rid = "session"; - logDebugMessage('refreshTokenUrl: ${refreshTokenUrl}'); - logDebugMessage('signOutUrl: ${signOutUrl}'); - logDebugMessage('rid: ${rid}'); + logDebugMessage('SuperTokens.init: refreshTokenUrl: ${refreshTokenUrl}'); + logDebugMessage('SuperTokens.init: signOutUrl: ${signOutUrl}'); + logDebugMessage('SuperTokens.init: rid: ${rid}'); SuperTokens.isInitCalled = true; } @@ -93,8 +93,9 @@ class SuperTokens { static Future doesSessionExist() async { Map? tokenInfo = await FrontToken.getToken(); - logDebugMessage('Got token info: ${jsonEncode(tokenInfo)}'); + logDebugMessage('SuperTokens.doesSessionExist: Got token info: ${jsonEncode(tokenInfo)}'); if (tokenInfo == null) { + logDebugMessage('SuperTokens.doesSessionExist: token info is null'); return false; } @@ -102,6 +103,7 @@ class SuperTokens { int accessTokenExpiry = tokenInfo["ate"]; if (accessTokenExpiry != null && accessTokenExpiry < now) { + logDebugMessage('SuperTokens.doesSessionExist: access token has expired'); LocalSessionState preRequestLocalSessionState = await SuperTokensUtils.getLocalSessionState(); @@ -121,10 +123,12 @@ class SuperTokens { } static Future signOut({Function(Exception?)? completionHandler}) async { - logDebugMessage('Signing out user'); + logDebugMessage('SuperTokens.signOut: Signing out user'); if (!(await doesSessionExist())) { + logDebugMessage('SuperTokens.signOut: Session does not exist'); SuperTokens.config.eventHandler(Eventype.SIGN_OUT); if (completionHandler != null) { + logDebugMessage('SuperTokens.signOut: Calling completionHandler'); completionHandler(null); } return; @@ -141,7 +145,7 @@ class SuperTokens { return; } - logDebugMessage('Using signOutUrl: ${uri}'); + logDebugMessage('SuperTokens.signOut: Using signOutUrl: ${uri}'); http.Request signOut = http.Request('post', uri); signOut = SuperTokens.config.preAPIHook(APIAction.SIGN_OUT, signOut); @@ -177,7 +181,7 @@ class SuperTokens { } static Future attemptRefreshingSession() async { - logDebugMessage('Attempting to refresh session'); + logDebugMessage('SuperTokens.attemptRefreshingSession: Attempting to refresh session'); LocalSessionState preRequestLocalSessionState = await SuperTokensUtils.getLocalSessionState(); bool shouldRetry = false; @@ -186,10 +190,13 @@ class SuperTokens { dynamic resp = await Client.onUnauthorisedResponse(preRequestLocalSessionState); if (resp is UnauthorisedResponse) { + logDebugMessage('SuperTokens.attemptRefreshingSession: Got unauthorised response'); if (resp.status == UnauthorisedStatus.API_ERROR) { + logDebugMessage('SuperTokens.attemptRefreshingSession: Got API error'); exception = resp.error as SuperTokensException; } else { shouldRetry = resp.status == UnauthorisedStatus.RETRY; + logDebugMessage('SuperTokens.attemptRefreshingSession: shouldRetry: ${shouldRetry}'); } } if (exception != null) { @@ -213,10 +220,13 @@ class SuperTokens { Map userPayload = frontToken['up'] as Map; if (accessTokenExpiry < DateTime.now().millisecondsSinceEpoch) { + logDebugMessage('SuperTokens.getAccessTokenPayloadSecurely: access token has expired, trying to refresh'); bool retry = await SuperTokens.attemptRefreshingSession(); - if (retry) + if (retry) { + logDebugMessage('SuperTokens.getAccessTokenPayloadSecurely: Retry was successful, extracting payload'); return getAccessTokenPayloadSecurely(); + } else throw SuperTokensException("Could not refresh session"); } diff --git a/lib/src/supertokens_dio_extension.dart b/lib/src/supertokens_dio_extension.dart index fd3d7fa..ed28b0c 100644 --- a/lib/src/supertokens_dio_extension.dart +++ b/lib/src/supertokens_dio_extension.dart @@ -16,7 +16,7 @@ import 'package:supertokens_flutter/src/logger.dart'; extension SuperTokensDioExtension on Dio { /// Adds the SuperTokens interceptor to the Dio instance. void addSupertokensInterceptor() { - logDebugMessage('Adding supertokens interceptor'); + logDebugMessage('SuperTokensDioExtension.addSupertokensInterceptor: Adding supertokens interceptor'); interceptors.add(SuperTokensInterceptorWrapper(client: this)); } }