diff --git a/CHANGELOG.md b/CHANGELOG.md index af314150..74de2091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## [Unreleased] ### Breaking -* Core: Capacitor 2.x is new minimum peer dependency. closes #80 +* Core: Capacitor 2.x is new minimum peer dependency. closes #80. +* `responseType` is required. Default values were removed. In favor of configuring anything. closes #86. * Configuration: If a flow must not have a `accessTokenEndpoint` but you configured one as base parameter you have to overwrite it in the according platform sections. `accessTokenEndpoint: ""` see Google example in README. * Add `redirectUrl` to base parameter and make it overwritable in the platform sections. closes #84. @@ -23,6 +24,7 @@ This is controlled by Android specific parameters `handleResultOnNewIntent` for ### Changed * Android: Allow no resource url and just return every we got until so far. closes #75. thx [@0x4AMiller](https://github.com/0x4AMiller) * Web, iOS, Android: All base parameters are overwritable in the platform sections. closes #84. +* Restriction to the response type `code` and `token` was removed. Devs can configure anything but are responsible for it as well. closes #86. ### Fixed diff --git a/README.md b/README.md index af7c1c7d..868c201e 100644 --- a/README.md +++ b/README.md @@ -154,17 +154,17 @@ But be aware that only the parameters from the accessToken request are included ### Error Codes +#### authenticate() + * ERR_PARAM_NO_APP_ID ... The appId / clientId is missing. (web, android, ios) * ERR_PARAM_NO_AUTHORIZATION_BASE_URL ... The authorization base url is missing. (web, android, ios) * ERR_PARAM_NO_RESPONSE_TYPE ... The response type is missing. (web, android, ios) -* ERR_PARAM_NO_REDIRECT_URL ... The redirect url / custom scheme url is missing. (web, android, ios) -* ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT ... The access token endpoint url is missing. It is only needed if code flow is used. (web, android, ios) -* ERR_PARAM_INVALID_RESPONSE_TYPE ... You configured a invalid responseType. Only "token" or "code" are allowed. (web, android, ios) -* ERR_PARAM_NO_REFRESH_TOKEN ... The refresh token is missing (only when obtaining an access token based on a refresh token, android/ios) +* ERR_PARAM_NO_REDIRECT_URL ... The redirect url is missing. (web, android, ios) + +* ERR_STATES_NOT_MATCH ... The state included in the authorization code request does not match the one in the redirect. Security risk! (web, android, ios) * ERR_AUTHORIZATION_FAILED ... The authorization failed. * ERR_NO_ACCESS_TOKEN ... No access_token found. (web, android) * ERR_NO_AUTHORIZATION_CODE ... No authorization code was returned in the redirect response. (web, android, ios) -* ERR_STATES_NOT_MATCH ... The state included in the authorization code request does not match the one in the redirect. Security risk! (web, android, ios) * USER_CANCELLED ... The user cancelled the login flow. (web, android, ios) * ERR_CUSTOM_HANDLER_LOGIN ... Login through custom handler class failed. See logs and check your code. (android, ios) * ERR_CUSTOM_HANDLER_LOGOUT ... Logout through custom handler class failed. See logs and check your code. (android, ios) @@ -172,6 +172,14 @@ But be aware that only the parameters from the accessToken request are included * ERR_ANDROID_RESULT_NULL ... The auth result is null. The intent in the ActivityResult is null. This might be a valid state but make sure you configured Android part correctly! See [Platform Android](#platform-android) * ERR_GENERAL ... A unspecific error. Check the logs to see want exactly happened. (web, android, ios) +#### refreshToken() + +* ERR_PARAM_NO_APP_ID ... The appId / clientId is missing. (android, ios) +* ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT ... The access token endpoint url is missing. It is only needed on refresh, on authenticate it is optional. (android, ios) +* ERR_PARAM_NO_REFRESH_TOKEN ... The refresh token is missing. (android, ios) +* ERR_NO_ACCESS_TOKEN ... No access_token found. (web, android) +* ERR_GENERAL ... A unspecific error. Check the logs to see want exactly happened. (android, ios) + ## Platform: Web/PWA This implementation just opens a browser window to let users enter their credentials. diff --git a/android/src/main/java/com/byteowls/capacitor/oauth2/OAuth2ClientPlugin.java b/android/src/main/java/com/byteowls/capacitor/oauth2/OAuth2ClientPlugin.java index 8ec75173..8bdbb5c4 100644 --- a/android/src/main/java/com/byteowls/capacitor/oauth2/OAuth2ClientPlugin.java +++ b/android/src/main/java/com/byteowls/capacitor/oauth2/OAuth2ClientPlugin.java @@ -65,8 +65,6 @@ public class OAuth2ClientPlugin extends Plugin { private static final String ERR_PARAM_NO_REDIRECT_URL = "ERR_PARAM_NO_REDIRECT_URL"; private static final String ERR_PARAM_NO_RESPONSE_TYPE = "ERR_PARAM_NO_RESPONSE_TYPE"; - private static final String ERR_PARAM_INVALID_RESPONSE_TYPE = "ERR_PARAM_INVALID_RESPONSE_TYPE"; - private static final String ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT = "ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT"; private static final String ERR_PARAM_NO_REFRESH_TOKEN = "ERR_PARAM_NO_REFRESH_TOKEN"; @@ -178,6 +176,11 @@ public void onError(Exception error) { call.reject(ERR_GENERAL, e); } } else { + + // ################################### + // ### Validate required parameter ### + // ################################### + if (oauth2Options.getAppId() == null) { call.reject(ERR_PARAM_NO_APP_ID); return; @@ -198,17 +201,7 @@ public void onError(Exception error) { return; } - // TODO remove check #46, #48, #49 - if (!RESPONSE_TYPE_CODE.equals(oauth2Options.getResponseType()) && !RESPONSE_TYPE_TOKEN.equals(oauth2Options.getResponseType())) { - call.reject(ERR_PARAM_INVALID_RESPONSE_TYPE); - return; - } - - // TODO remove check. If there is a accessTokenEndpoint given the plugin will try it regardless of the given responseType - if (RESPONSE_TYPE_CODE.equals(oauth2Options.getResponseType()) && oauth2Options.getAccessTokenEndpoint() == null) { - call.reject(ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT); - return; - } + // ### Configure Uri authorizationUri = Uri.parse(oauth2Options.getAuthorizationBaseUrl()); Uri accessTokenUri; diff --git a/ios/ByteowlsCapacitorOauth2/Source/ByteowlsCapacitorOauth2.swift b/ios/ByteowlsCapacitorOauth2/Source/ByteowlsCapacitorOauth2.swift index ddd7d1cb..baf3214e 100644 --- a/ios/ByteowlsCapacitorOauth2/Source/ByteowlsCapacitorOauth2.swift +++ b/ios/ByteowlsCapacitorOauth2/Source/ByteowlsCapacitorOauth2.swift @@ -25,10 +25,7 @@ public class OAuth2ClientPlugin: CAPPlugin { let PARAM_CUSTOM_HANDLER_CLASS = "ios.customHandlerClass" let PARAM_SCOPE = "scope" let PARAM_STATE = "state" - let PARAM_PKCE_DISABLED = "pkceDisabled" - - let RESPONSE_TYPE_CODE = "code" - let RESPONSE_TYPE_TOKEN = "token" + let PARAM_PKCE_ENABLED = "pkceEnabled" let ERR_GENERAL = "ERR_GENERAL" @@ -44,8 +41,6 @@ public class OAuth2ClientPlugin: CAPPlugin { let ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT = "ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT" let ERR_NO_AUTHORIZATION_CODE = "ERR_NO_AUTHORIZATION_CODE" let ERR_PARAM_NO_REFRESH_TOKEN = "ERR_PARAM_NO_REFRESH_TOKEN" - - let ERR_PARAM_INVALID_RESPONSE_TYPE = "ERR_PARAM_INVALID_RESPONSE_TYPE" struct SharedConstants { static let ERR_USER_CANCELLED = "USER_CANCELLED" @@ -116,7 +111,7 @@ public class OAuth2ClientPlugin: CAPPlugin { consumerSecret: "", // never ever store the app secret on client! authorizeUrl: "", accessTokenUrl: accessTokenEndpoint, - responseType: RESPONSE_TYPE_CODE + responseType: "code" ) self.oauthSwift = oauthSwift @@ -224,10 +219,6 @@ public class OAuth2ClientPlugin: CAPPlugin { return } - if responseType != RESPONSE_TYPE_CODE && responseType != RESPONSE_TYPE_TOKEN { - call.reject(self.ERR_PARAM_INVALID_RESPONSE_TYPE) - return - } var oauthSwift: OAuth2Swift if let accessTokenEndpoint = getOverwritableString(call, PARAM_ACCESS_TOKEN_ENDPOINT), !accessTokenEndpoint.isEmpty { @@ -267,10 +258,9 @@ public class OAuth2ClientPlugin: CAPPlugin { } let requestState = getOverwritableString(call, PARAM_STATE) ?? generateRandom(withLength: 20) - let pkceDisabled: Bool = getOverwritable(call, PARAM_PKCE_DISABLED) as? Bool ?? false + let pkceEnabled: Bool = getOverwritable(call, PARAM_PKCE_ENABLED) as? Bool ?? false // if response type is code and pkce is not disabled - if responseType == RESPONSE_TYPE_CODE && !pkceDisabled { - // oauthSwift.accessTokenBasicAuthentification = true + if pkceEnabled { let pkceCodeVerifier = generateRandom(withLength: 64) let pkceCodeChallenge = pkceCodeVerifier.sha256().base64() @@ -326,14 +316,7 @@ public class OAuth2ClientPlugin: CAPPlugin { private func handleAuthorizationResult(_ result: Result, _ call: CAPPluginCall, _ responseType: String, _ requestState: String, _ resourceUrl: String?) { switch result { case .success(let (credential, response, parameters)): - // oauthSwift internally checks the state if response type is code therefore I only need the token check - if responseType == self.RESPONSE_TYPE_TOKEN { - guard let responseState = parameters["state"] as? String, responseState == requestState else { - call.reject(self.ERR_STATES_NOT_MATCH) - return - } - } - + // state is aready checked by the lib if resourceUrl != nil && !resourceUrl!.isEmpty { self.oauthSwift!.client.get( resourceUrl!, @@ -375,7 +358,6 @@ public class OAuth2ClientPlugin: CAPPlugin { } } - private func getConfigObjectDeepest(_ options: [AnyHashable: Any?]!, key: String) -> [AnyHashable:Any?]? { let parts = key.split(separator: ".") diff --git a/src/definitions.ts b/src/definitions.ts index 8a0f694f..a3ef04c5 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -44,26 +44,31 @@ export interface OAuth2RefreshTokenOptions { scope?: string; } -type ResponseTypeType = "token" | "code"; - export interface OAuth2AuthenticateBaseOptions { /** * The app id (client id) you get from the oauth provider like Google, Facebook,... + * + * required! */ appId?: string; /** * The base url for retrieving tokens depending on the response type from a OAuth 2 provider. e.g. https://accounts.google.com/o/oauth2/auth + * + * required! */ authorizationBaseUrl?: string; /** - * Defaults to 'token' aka implicit flow if empty. + * Tells the authorization server which grant to execute. Be aware that a full code flow is not supported as clientCredentials are not included in requests. * - * Be aware that this plugin does not support authorization code flow with client secrets because of security reason. + * But you can retrieve the authorizationCode if you don't set a accessTokenEndpoint. * + * required! */ - responseType?: ResponseTypeType + responseType?: string; /** * Url to which the oauth provider redirects after authentication. + * + * required! */ redirectUrl?: string; /** diff --git a/src/web.ts b/src/web.ts index 3cef42a0..33ab797d 100644 --- a/src/web.ts +++ b/src/web.ts @@ -31,14 +31,14 @@ export class OAuth2ClientPluginWeb extends WebPlugin implements OAuth2ClientPlug this.webOptions = await WebUtils.buildWebOptions(options); return new Promise((resolve, reject) => { // validate - if (!this.webOptions.appId) { + if (!this.webOptions.appId || this.webOptions.appId.length == 0) { reject(new Error("ERR_PARAM_NO_APP_ID")); - } else if (!this.webOptions.authorizationBaseUrl) { + } else if (!this.webOptions.authorizationBaseUrl || this.webOptions.authorizationBaseUrl.length == 0) { reject(new Error("ERR_PARAM_NO_AUTHORIZATION_BASE_URL")); - } else if (!this.webOptions.redirectUrl) { + } else if (!this.webOptions.redirectUrl || this.webOptions.redirectUrl.length == 0) { reject(new Error("ERR_PARAM_NO_REDIRECT_URL")); - } else if ("code" !== this.webOptions.responseType && "token" !== this.webOptions.responseType) { - reject(new Error("ERR_PARAM_INVALID_RESPONSE_TYPE")); + } else if (!this.webOptions.responseType || this.webOptions.responseType.length == 0) { + reject(new Error("ERR_PARAM_NO_RESPONSE_TYPE")); } else { // init internal control params let loopCount = this.loopCount;