Authorization of a Twitch Extension
shrmpy
Posted on November 4, 2021
This article is the third in a multi-part series to walk through the creation of a Twitch extension. For the third part, the goal is to refactor the authorization.
To go directly to the project, the source code repository is available at
andRequirements:
- CORS
- OAuth2
- encryption
§ Overview
§ Routes
/auth
flow
- There was claims-extraction processing in earlier incarnations as part of the
state
field construction. In examining the flow, the diagram helps show there is no longer a claims-extraction step. It's been isolated frompreprocess
which means now the logic that enforces the extension secret is missing. We have to decide how to re-enable the verification of the extension secret. This will be the focus of our first refactor effort. - Also confirmed after diagramming, the Fauna data abstraction layer is not used. After double-checking via
grep
, it's safe to remove thefauna.go
file.
/callback
flow
§ Test
Start the refactor by adding another test to the main_test.go
file:
func TestWrongExtensionSecret(t *testing.T) {
// prepare data
wrongHeader := make(map[string]string)
wrongHeader["Authorization"] = "Bearer WRONG-KEY"
req := events.APIGatewayProxyRequest{
HTTPMethod: "GET",
Body: "",
Headers: wrongHeader,
}
// run request handlerlogic
result, err := handler(req)
assert.IsType(t, nil, err)
assert.Equal(t, http.StatusOK, result.StatusCode)
// case specific expectation
missing := newResponse("Wrong authorization header", http.StatusUnauthorized)
expected := ignoreTimestamp(missing.Body)
actual := ignoreTimestamp(result.Body)
// inspect content
assert.Equal(t, expected, actual)
}
Calling the Go test runner:
go test $PWD/cmd/auth
should give a FAIL
with output resembling:
--- FAIL: TestWrongExtensionSecret (0.00s)
main_test.go:89:
Error Trace: main_test.go:89
Error: Not equal:
expected: "{\"errors\":[{\"code\":\"DEMO-401\",\"detail\":\
"Wrong authorization header\",,\"status\":\"401\"}]}"
actual : "{\"Location\":\"https://app.pavlok.com/oauth/aut
horize?client_id=\\u0026redirect_uri=\\u0026response_type=code\\u0026state=2e54fb84beedc5b2
53be28d3dac67edf6bae84141b1b2ba56f3668a0e646100e1c156721e0\"}"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-{"errors":[{"code":"DEMO-401","detail":"Wrong authorizat
ion header",,"status":"401"}]}
+{"Location":"https://app.pavlok.com/oauth/authorize?client
_id=\u0026redirect_uri=\u0026response_type=code\u0026state=2e54fb84beedc5b253be28d3dac67edf
6bae84141b1b2ba56f3668a0e646100e1c156721e0"}
Test: TestWrongExtensionSecret
FAIL
FAIL github.com/shrmpy/pavlok/cmd/auth 0.015s
FAIL
The output tells us that the expected result was a JSON struct containing the 401
status code. Except the actual result was JSON containing the Pavlok OAuth hyperlink; the handler ran normally. In the test, we purposely wire-up incorrect values for the Authorization
header to force the error scenario. So this indicates that the current logic ignores the header contents.
§ Refactor
To trigger the verification of the extension secret, we add the claims-extraction call to the bottom of the preprocess
function:
// extension secret is enforced by claims extraction
cl, err := helper.claims(token)
if err != nil {
log.Print("Malformed claims meta data")
return newResponse("Wrong authorization header", http.StatusUnauthorized),
errors.New("claims")
}
log.Printf("Claims (ch/role): %s / %s", cl.ChannelID, cl.Role)
return events.APIGatewayProxyResponse{}, nil
}
Run the test again:
go test $PWD/cmd/auth
which should be clean and free of FAIL
test output.
Then make sure we follow the Go coding standards:
go fmt $PWD/cmd/auth
Now we can commit the change:
git add $PWD/cmd/auth
git commit -m'add claims extraction in preprocess'
git push origin gh-issue-num
Then navigate to the git repo, and create the merge request. This should launch the deploy preview on Netlify.
Once the preview logs look normal, it's safe to complete the merge request back at the git repo, and remove the patch branch. Netlify should launch a new build for production based on the main
branch.
After the build is done, visit the Twitch extensions console to check that the panel is still okay.
- The
shock
button will create entries in the Netlify function logs - The API response will be added in Discord via the webhook
This was enough to get the test to work, and the desired behavior. Refactoring can easily snowball. So we try to chip away, and be careful to minimize taking two steps backwards.
* Notes, Lessons, Monologue
Why is a refactor needed? The code is developed in iterations. In the early rounds, it's a race to get behavior to work. To achieve this, it is normal to follow the "happy path" and postpone negative cases for later. Due to the API requirements, significant work was already interleaved into the iterations to be able to exercise the API calls. So the refactor should be more manageable and not as daunting.
WTF, you made a tutorial that deployed unfinished code?! True. The series is my journey into Twitch extension development. Whenever there is a trade-off, I try to "do no harm". So now, it's valuable to analyze the authorization in public view. To get more eyes, and reveal any gaps.
Why write tests? In refactoring code, it is best to put tests in place before any changes. The tests should document expected behavior. Then in the process of changes, run the tests frequently to catch breaking code as early as possible.
Why are there no mocks? Unfortunately, it's laziness. More refactoring is necessary to make the package testable. Probably lump it with the middleware tech debt.
Why no tests for positive cases? For now, relying on old fashioned manual interaction to verify. More refactoring is required before automation is friction-less (e.g., configure environment variables, mocks).
Why verify the header, things seem to work fine? The Authorization
header tells the EBS that a request originates from a trusted application. If we allow all requests, the EBS will be flooded and resource limits for the free tier will be exhausted.
Posted on November 4, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.