diff --git a/spec/System/TestPoEAPIAuth_spec.lua b/spec/System/TestPoEAPIAuth_spec.lua new file mode 100644 index 000000000..c1f63ed25 --- /dev/null +++ b/spec/System/TestPoEAPIAuth_spec.lua @@ -0,0 +1,76 @@ +describe("PoEAPI auth", function() + local originalLaunchSubScript + local originalDownloadPage + local originalSubScripts + + before_each(function() + originalLaunchSubScript = _G.LaunchSubScript + originalDownloadPage = launch.DownloadPage + originalSubScripts = launch.subScripts + launch.subScripts = { } + end) + + after_each(function() + _G.LaunchSubScript = originalLaunchSubScript + launch.DownloadPage = originalDownloadPage + launch.subScripts = originalSubScripts + end) + + it("passes token exchange errors to the auth callback #auth", function() + local authState + _G.LaunchSubScript = function(_, _, _, authUrl) + authState = authUrl:match("state=([^&]+)") + return 123 + end + launch.DownloadPage = function(_, url, callback) + assert.are.equals("https://www.pathofexile.com/oauth/token", url) + callback(nil, "SSL connect error") + end + + local api = new("PoEAPI") + local callbackArgs + api:FetchAuthToken(function(response, errMsg, updateSettings) + callbackArgs = { + response = response, + errMsg = errMsg, + updateSettings = updateSettings, + } + end) + + assert.is_not_nil(authState) + assert.is_not_nil(launch.subScripts[123]) + launch.subScripts[123].callback("auth-code", nil, authState, 12345) + + assert.is_nil(callbackArgs.response) + assert.are.equals("SSL connect error", callbackArgs.errMsg) + assert.True(callbackArgs.updateSettings) + assert.is_nil(api.authToken) + end) + + it("reports OAuth state mismatches without exchanging a token", function() + _G.LaunchSubScript = function() + return 123 + end + launch.DownloadPage = function() + error("token exchange should not run for mismatched OAuth state") + end + + local api = new("PoEAPI") + local callbackArgs + api:FetchAuthToken(function(response, errMsg, updateSettings) + callbackArgs = { + response = response, + errMsg = errMsg, + updateSettings = updateSettings, + } + end) + + assert.is_not_nil(launch.subScripts[123]) + launch.subScripts[123].callback("auth-code", nil, "wrong-state", 12345) + + assert.is_nil(callbackArgs.response) + assert.are.equals("OAuth state mismatch", callbackArgs.errMsg) + assert.True(callbackArgs.updateSettings) + assert.is_nil(api.authToken) + end) +end) diff --git a/src/Classes/ImportTab.lua b/src/Classes/ImportTab.lua index c54d92bf7..3229f5878 100644 --- a/src/Classes/ImportTab.lua +++ b/src/Classes/ImportTab.lua @@ -49,7 +49,7 @@ local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function( -- Stage: Authenticate self.controls.authenticateButton = new("ButtonControl", {"TOPLEFT",self.controls.characterImportAnchor,"TOPLEFT"}, {0, 0, 200, 16}, "^7Authorize with Path of Exile", function() - self.api:FetchAuthToken(function() + self.api:FetchAuthToken(function(_, errMsg) if self.api.authToken then self.charImportMode = "GETACCOUNTNAME" self.charImportStatus = "Authenticated" @@ -59,6 +59,8 @@ local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function( main.tokenExpiry = self.api.tokenExpiry main:SaveSettings() self:DownloadCharacterList() + elseif errMsg and errMsg ~= self.api.ERROR_NO_AUTH then + self.charImportStatus = colorCodes.NEGATIVE.."Authentication failed: "..errMsg else self.charImportStatus = colorCodes.WARNING.."Not authenticated" end diff --git a/src/Classes/PoEAPI.lua b/src/Classes/PoEAPI.lua index 8fc90874d..c1685d5bb 100644 --- a/src/Classes/PoEAPI.lua +++ b/src/Classes/PoEAPI.lua @@ -57,6 +57,7 @@ local function base64_encode(secret) return base64.encode(secret):gsub("+","-"):gsub("/","_"):gsub("=$", "") end +-- func callback(response, errorMsg, updateSettings) function PoEAPIClass:FetchAuthToken(callback) math.randomseed(os.time()) local secret = math.random(2^32-1) @@ -86,11 +87,16 @@ function PoEAPIClass:FetchAuthToken(callback) self.authToken = nil self.refreshToken = nil self.tokenExpiry = nil - callback(nil, self.ERROR_NO_AUTH, true) + callback(nil, errMsg or self.ERROR_NO_AUTH, true) return end if initialState ~= state then + ConPrintf("OAuth state mismatch during authentication") + self.authToken = nil + self.refreshToken = nil + self.tokenExpiry = nil + callback(nil, "OAuth state mismatch", true) return end local formText = "client_id=pob&grant_type=authorization_code&code=" .. code .. "&redirect_uri=http://localhost:" .. port .. "&scope=" .. table.concat(scopesOAuth, " ") .. "&code_verifier=" .. code_verifier @@ -100,7 +106,7 @@ function PoEAPIClass:FetchAuthToken(callback) self.authToken = nil self.refreshToken = nil self.tokenExpiry = nil - callback() + callback(nil, errMsg, true) return end local responseLua = dkjson.decode(response.body)