DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,274 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
shrmpy
shrmpy

Posted on • Updated on

Authorization of a Twitch Extension

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

GitHub logo shrmpy / pavlok

twitch extension project

and

Requirements:

  • 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 from preprocess 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 the fauna.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)
}
Enter fullscreen mode Exit fullscreen mode

Calling the Go test runner:

go test $PWD/cmd/auth
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Run the test again:

go test $PWD/cmd/auth
Enter fullscreen mode Exit fullscreen mode

which should be clean and free of FAIL test output.

Then make sure we follow the Go coding standards:

go fmt $PWD/cmd/auth
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 auth func log shock func log
  • The API response will be added in Discord via the webhook discord chan

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.

Top comments (0)

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. πŸ›