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)); } }