<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: shrmpy</title>
    <description>The latest articles on DEV Community by shrmpy (@shrmpy).</description>
    <link>https://dev.to/shrmpy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F722150%2F328e5911-46c8-499e-9d8d-1aac4d11ab47.png</url>
      <title>DEV Community: shrmpy</title>
      <link>https://dev.to/shrmpy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shrmpy"/>
    <language>en</language>
    <item>
      <title>CORS for a Twitch Extension</title>
      <dc:creator>shrmpy</dc:creator>
      <pubDate>Sat, 20 Nov 2021 05:33:36 +0000</pubDate>
      <link>https://dev.to/shrmpy/cors-for-a-twitch-extension-3170</link>
      <guid>https://dev.to/shrmpy/cors-for-a-twitch-extension-3170</guid>
      <description>&lt;p&gt;This article is the fourth in a multi-part series to walk through the creation of a Twitch extension. For the fourth part, the goal is to refactor the CORS headers.&lt;/p&gt;

&lt;p&gt;To go directly to the project, the source code repository is available at&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shrmpy"&gt;
        shrmpy
      &lt;/a&gt; / &lt;a href="https://github.com/shrmpy/pavlok"&gt;
        pavlok
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      twitch extension project
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
and&lt;br&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shrmpy"&gt;
        shrmpy
      &lt;/a&gt; / &lt;a href="https://github.com/shrmpy/pavlok-panel"&gt;
        pavlok-panel
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Twitch extension client ID&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  § Overview
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/mdMobOZ?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  § Headers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;preprocess&lt;/code&gt; flow
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/qBXjmJP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;enableCors&lt;/code&gt; flow
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/PoKjOBq?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The wildcard (&lt;code&gt;*&lt;/code&gt;) in the &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header is the primary change in this refactor work. It is time to restrict the origin to the hosting server (&lt;code&gt;ID.ext-twitch.tv&lt;/code&gt;) of the Twitch extension.&lt;/li&gt;
&lt;li&gt;Another change that should not add extra scope, is to remove &lt;code&gt;DELETE&lt;/code&gt; from the &lt;code&gt;Access-Control-Allow-Methods&lt;/code&gt; header.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  § Test
&lt;/h2&gt;

&lt;p&gt;Start the refactor by adding the &lt;a href="https://gist.github.com/shrmpy/1961ee953b5f9b84d762919dc346998f"&gt;new test&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestAccessControlAllowOrigin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// prepare data&lt;/span&gt;
        &lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ebs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExtensionId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HOSTNAME-TEST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expectMethods&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"POST, GET, OPTIONS, PUT"&lt;/span&gt;
        &lt;span class="n"&gt;expectHeaders&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization"&lt;/span&gt;
        &lt;span class="n"&gt;expectOrigin&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"https://HOSTNAME-TEST.ext-twitch.tv"&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;newTestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// run handler logic&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ebs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MiddlewareCORS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// check for expected CORS&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expectMethods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Methods"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expectHeaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Headers"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expectOrigin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Origin"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling the test runner will lead to compile errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/cmd/auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  § Refactor
&lt;/h2&gt;

&lt;p&gt;Add the new package files to define the &lt;a href="https://gist.github.com/shrmpy/68d1d82b21016843267e7acfdb748f5e"&gt;configuration&lt;/a&gt; and &lt;a href="https://gist.github.com/shrmpy/536a78d1b3e79b7bac50a1af2deb8672"&gt;middleware&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;ebs&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// configuration&lt;/span&gt;
&lt;span class="c"&gt;// to make environment variables available to testing&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EXTENSION_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"EXTENSION_ID"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;extensionId&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ExtensionId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Hostname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// format the hostname for the CORS allow-origin&lt;/span&gt;
    &lt;span class="c"&gt;// 1. For Netlify, EXTENSION_ID environment variable should be defined&lt;/span&gt;
    &lt;span class="c"&gt;// 2. Locally for testing, rely on configuration field&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensionId&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://%s.ext-twitch.tv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXTENSION_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cid&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://%s.ext-twitch.tv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;ebs&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/aws/aws-lambda-go/events"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayProxyRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayProxyResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;/*
func MiddlewareTemplate(next HandlerFunc) HandlerFunc {
    return func(ev events.APIGatewayProxyRequest)
            (events.APIGatewayProxyResponse, error) {
        return next(ev)
    }
}
*/&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;MiddlewareCORS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayProxyRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayProxyResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// preflight check is short-circuited&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPMethod&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"OPTIONS"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;blankResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c"&gt;// without next, just act same as preflight&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;blankResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// run next handler along chain&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// post-process&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enableCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;enableCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"Access-Control-Allow-Origin"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hostname&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s"&gt;"Access-Control-Allow-Methods"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"POST, GET, OPTIONS, PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Access-Control-Allow-Headers"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// TODO merge, if CORS headers exist&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;blankResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;descr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayProxyResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;enableCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayProxyResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There will be references to &lt;code&gt;enableCors&lt;/code&gt; in &lt;code&gt;preprocess.go&lt;/code&gt; and &lt;code&gt;main.go&lt;/code&gt; files that need to be cleaned-up. &lt;/p&gt;

&lt;p&gt;The changes also break one of the existing tests. So fix the old test for the &lt;a href="https://gist.github.com/shrmpy/33635208047e79daf1adab393aff3689"&gt;preflight request&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestPreflight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// prep test data&lt;/span&gt;
        &lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ebs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;expectMethods&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"POST, GET, OPTIONS, PUT"&lt;/span&gt;
        &lt;span class="n"&gt;expectHeaders&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization"&lt;/span&gt;
        &lt;span class="n"&gt;expectOrigin&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;newTestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OPTIONS"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// run the handler logic&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ebs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MiddlewareCORS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;// check for expected CORS&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expectMethods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Methods"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expectHeaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Headers"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expectOrigin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Origin"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterwards, the compile should be successful. Plus calling the test runner this time should have zero fails. All done? not yet. &lt;em&gt;Even though the tests pass,&lt;/em&gt; the middleware has not been applied to the original handler. Go to the &lt;a href="https://gist.github.com/shrmpy/ab950a1ee78235c2bf28146434f208e9"&gt;main.go file&lt;/a&gt; and adjust the &lt;code&gt;init()&lt;/code&gt; and &lt;code&gt;lambda.Start()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ebs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ebs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"EXTENSION_SECRET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;helper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decodeSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;ebs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MiddlewareCORS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the new configuration also expects a new environment variable &lt;code&gt;EXTENSION_ID&lt;/code&gt;. Go to the Netlify &lt;strong&gt;Site settings | Build &amp;amp; deploy | Environment&lt;/strong&gt; page. Click the &lt;strong&gt;Add&lt;/strong&gt; variable button. Name it &lt;code&gt;EXTENSION_ID&lt;/code&gt; and paste the Twitch extension client ID.&lt;/p&gt;

&lt;p&gt;Remember coding standards before saving the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;
go &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/cmd/auth
git add config.go middleware.go &lt;span class="nv"&gt;$PWD&lt;/span&gt;/cmd/auth
git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s1"&gt;'refactor CORS allow-origin'&lt;/span&gt;
git push origin gh-issue-NNN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  * Notes, Lessons, Monologue
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Why change the &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;?&lt;/em&gt; We used the wildcard (&lt;code&gt;*&lt;/code&gt;) in the early iterations, in order to make the requests work. At the time, there were CORS errors to overcome and without knowing the correct values required, we chose to &lt;em&gt;allow all&lt;/em&gt;. Now it's time to restrict access for security. So we learned that the Twitch extension is hosted from the &lt;code&gt;ID.ext-twitch.tv&lt;/code&gt; server and this would be the correct value for the Allow-Origin header.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why the middleware?&lt;/em&gt; It was in our backlog. So it was a matter of when. For this refactor, the idea was to intercept the response to shape the headers (that affect CORS). To intercept the response, do processing, and then continue the response flow, fits the description of middleware. The other benefit of middleware is consolidation and uncluttering the business logic. Before, we checked for preflight in &lt;code&gt;preprocess&lt;/code&gt;, repeated basic responses, and made a direct call to &lt;code&gt;enableCors&lt;/code&gt; from the main handler. Now CORS header logic is in one place, &lt;code&gt;ebs.MiddlewareCORS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why did the test pass before the &lt;code&gt;init&lt;/code&gt; and &lt;code&gt;lambda.Start&lt;/code&gt; was patched?&lt;/em&gt; is the test pointless? The test only covers the middleware for the inputs supplied. The test doesn't execute the &lt;code&gt;main()&lt;/code&gt; or &lt;code&gt;init()&lt;/code&gt; functions. It may seem pointless, which is important to pause and reflect. Writing the test forced the design for a way to control the value of &lt;code&gt;EXTENSION_ID&lt;/code&gt;. Before now, an environment variable was the first choice. So thinking test first, we knew we needed another approach because assigning the environment variable in test scaffolding is not self-contained; the test would need to push any existing environment onto some stack before test run, then pop the environment after tests finish. The environment requires this kind of management because we don't want the test to clobber the variable of the host's environment. Even this precaution isn't self-contained because what if you run tests in parallel? Each test will step on each others' environment variable assignment. A very wordy way to say that's why we created the configuration in this refactor. It might appear as if the configuration struct is an one-off just for the test, but the real value is that it forced us to undertake the decoupling.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why not use dot env files?&lt;/em&gt; Honestly, I didn't think of it. At the time, I considered TOML/YAML for the configuration, and decided it was overkill. Remember that we want to do the &lt;em&gt;minimum&lt;/em&gt; to make a test green. The &lt;a href="https://gist.github.com/shrmpy/68d1d82b21016843267e7acfdb748f5e"&gt;config.go&lt;/a&gt; that we defined is lean in the current incarnation. Down the road, it may be the case that &lt;a href="https://github.com/joho/godotenv"&gt;dot env&lt;/a&gt; files will be the solution that scales.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What does the call &lt;code&gt;ebs.MiddlewareCORS(conf, handler)(req)&lt;/code&gt; do?&lt;/em&gt; why is the &lt;code&gt;lambda.Start&lt;/code&gt; different? In the test, this line invokes the function wrapped by the middleware. The invoke uses the &lt;code&gt;req&lt;/code&gt; variable as the parameter to that function. With the &lt;code&gt;lambda.Start&lt;/code&gt;, the &lt;em&gt;function pointer&lt;/em&gt; is being supplied. That reference can be resolved at a later time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why pass the configuration as a parameter to the middleware call?&lt;/em&gt; This is the "trick". We needed a way to specify a setting in the handler. Before writing the test, this wasn't an issue since using an environment variable has global scope; the handler would have access to the variable. Inside the test, we need to specify the setting and supply it to the handler without using globals. So the configuration becomes the parameter. &lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>security</category>
    </item>
    <item>
      <title>Authorization of a Twitch Extension</title>
      <dc:creator>shrmpy</dc:creator>
      <pubDate>Thu, 04 Nov 2021 05:43:27 +0000</pubDate>
      <link>https://dev.to/shrmpy/authorization-of-a-twitch-extension-1hn9</link>
      <guid>https://dev.to/shrmpy/authorization-of-a-twitch-extension-1hn9</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;To go directly to the project, the source code repository is available at &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shrmpy" rel="noopener noreferrer"&gt;
        shrmpy
      &lt;/a&gt; / &lt;a href="https://github.com/shrmpy/pavlok" rel="noopener noreferrer"&gt;
        pavlok
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      twitch extension project
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
 and &lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shrmpy" rel="noopener noreferrer"&gt;
        shrmpy
      &lt;/a&gt; / &lt;a href="https://github.com/shrmpy/pavlok-panel" rel="noopener noreferrer"&gt;
        pavlok-panel
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CORS&lt;/li&gt;
&lt;li&gt;OAuth2&lt;/li&gt;
&lt;li&gt;encryption&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  § Overview
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/ExvvOmX?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  § Routes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;/auth&lt;/code&gt; flow
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/JjyJJRv?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There was claims-extraction processing in earlier incarnations as part of the &lt;code&gt;state&lt;/code&gt; field construction. In examining the flow, the diagram helps show there is no longer a claims-extraction step. It's been isolated from &lt;code&gt;preprocess&lt;/code&gt; 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.&lt;/li&gt;
&lt;li&gt;Also confirmed after diagramming, the Fauna data abstraction layer is not used. After double-checking via &lt;code&gt;grep&lt;/code&gt;, it's safe to remove the &lt;code&gt;fauna.go&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/qBXjmJP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/dyzRRma?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/xxLrLMG?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/XWagzMe?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/PoKjOBq?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/xxLrpEe?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;/callback&lt;/code&gt; flow
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/NWvvGMx?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/QWMMyyg?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/OJjjNaB?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/porrybG?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/OJjjXRY?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/shrmpy/embed/oNeezgO?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  § Test
&lt;/h2&gt;

&lt;p&gt;Start the refactor by adding &lt;a href="https://gist.github.com/shrmpy/14638c7b525cb09e6cf7cae712c4892c" rel="noopener noreferrer"&gt;another test&lt;/a&gt; to the &lt;code&gt;main_test.go&lt;/code&gt; &lt;a href="https://github.com/shrmpy/pavlok/blob/71d9db338da142d436707a590b8fc738b44be542/cmd/auth/main_test.go" rel="noopener noreferrer"&gt;file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestWrongExtensionSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// prepare data&lt;/span&gt;
        &lt;span class="n"&gt;wrongHeader&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;wrongHeader&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bearer WRONG-KEY"&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayProxyRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;HTTPMethod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;wrongHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// run request handlerlogic&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// case specific expectation&lt;/span&gt;
        &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;newResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Wrong authorization header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ignoreTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ignoreTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;// inspect content&lt;/span&gt;
        &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling the Go test runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/cmd/auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;should give a &lt;code&gt;FAIL&lt;/code&gt; with output resembling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;---&lt;/span&gt; FAIL: TestWrongExtensionSecret &lt;span class="o"&gt;(&lt;/span&gt;0.00s&lt;span class="o"&gt;)&lt;/span&gt;
    main_test.go:89:
                Error Trace:    main_test.go:89
                Error:          Not equal:
                                expected: &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;errors&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;DEMO-401&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;detail&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
"&lt;/span&gt;Wrong authorization header&lt;span class="se"&gt;\"&lt;/span&gt;,,&lt;span class="se"&gt;\"&lt;/span&gt;status&lt;span class="se"&gt;\"&lt;/span&gt;:&lt;span class="se"&gt;\"&lt;/span&gt;401&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="o"&gt;}]}&lt;/span&gt;&lt;span class="s2"&gt;"
                                actual  : "&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;Location&lt;span class="se"&gt;\"&lt;/span&gt;:&lt;span class="se"&gt;\"&lt;/span&gt;https://app.pavlok.com/oauth/aut
horize?client_id&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="nv"&gt;u0026redirect_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="nv"&gt;u0026response_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;code&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="nv"&gt;u0026state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2e54fb84beedc5b2
53be28d3dac67edf6bae84141b1b2ba56f3668a0e646100e1c156721e0&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"

                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -{"&lt;/span&gt;errors&lt;span class="s2"&gt;":[{"&lt;/span&gt;code&lt;span class="s2"&gt;":"&lt;/span&gt;DEMO-401&lt;span class="s2"&gt;","&lt;/span&gt;detail&lt;span class="s2"&gt;":"&lt;/span&gt;Wrong authorizat
ion header&lt;span class="s2"&gt;",,"&lt;/span&gt;status&lt;span class="s2"&gt;":"&lt;/span&gt;401&lt;span class="s2"&gt;"}]}
                                +{"&lt;/span&gt;Location&lt;span class="s2"&gt;":"&lt;/span&gt;https://app.pavlok.com/oauth/authorize?client
&lt;span class="nv"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="nv"&gt;0026redirect_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="nv"&gt;0026response_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;code&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="nv"&gt;0026state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2e54fb84beedc5b253be28d3dac67edf
6bae84141b1b2ba56f3668a0e646100e1c156721e0&lt;span class="s2"&gt;"}
                Test:           TestWrongExtensionSecret
FAIL
FAIL    github.com/shrmpy/pavlok/cmd/auth       0.015s
FAIL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output tells us that the expected result was a JSON struct containing the &lt;code&gt;401&lt;/code&gt; status code. Except the actual result was JSON containing the Pavlok OAuth hyperlink; the handler ran &lt;em&gt;normally&lt;/em&gt;. In the test, we purposely wire-up incorrect values for the &lt;code&gt;Authorization&lt;/code&gt; header to force the error scenario. So this indicates that the current logic ignores the header contents.&lt;/p&gt;

&lt;h2&gt;
  
  
  § Refactor
&lt;/h2&gt;

&lt;p&gt;To trigger the verification of the extension secret, we add the &lt;a href="https://gist.github.com/shrmpy/4017db7f99d1cb9d35f01f68bfed39a9" rel="noopener noreferrer"&gt;claims-extraction call&lt;/a&gt;  to the bottom of the &lt;code&gt;preprocess&lt;/code&gt; &lt;a href="https://github.com/shrmpy/pavlok/blob/71d9db338da142d436707a590b8fc738b44be542/cmd/auth/preprocess.go" rel="noopener noreferrer"&gt;function&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;        &lt;span class="c"&gt;// extension secret is enforced by claims extraction&lt;/span&gt;
        &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Malformed claims meta data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Wrong authorization header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"claims"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Claims (ch/role): %s / %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChannelID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayProxyResponse&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the test again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/cmd/auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which should be clean and free of &lt;code&gt;FAIL&lt;/code&gt; test output.&lt;/p&gt;

&lt;p&gt;Then make sure we follow the Go coding standards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/cmd/auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can commit the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nv"&gt;$PWD&lt;/span&gt;/cmd/auth
git commit &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="s1"&gt;'add claims extraction in preprocess'&lt;/span&gt;
git push origin gh-issue-num
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then navigate to the git repo, and create the merge request. This should launch the deploy preview on Netlify.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;After the build is done, visit the Twitch extensions console to check that the panel is still okay.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;shock&lt;/code&gt; button will create entries in the Netlify function logs
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0av2xag059u6bt5uv0jw.png" alt="auth func log"&gt; &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lvkt840lgx480dzm0f2.png" alt="shock func log"&gt;
&lt;/li&gt;
&lt;li&gt;The API response will be added in Discord via the webhook
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwosh9y2unbt7alaae0x5.png" alt="discord chan"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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 &lt;em&gt;minimize&lt;/em&gt; taking two steps backwards. &lt;/p&gt;




&lt;h3&gt;
  
  
  * Notes, Lessons, Monologue
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Why is a refactor needed?&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;WTF, you made a tutorial that deployed unfinished code?!&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why write tests?&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why are there no mocks?&lt;/em&gt; Unfortunately, it's laziness. More refactoring is necessary to make the package testable. Probably lump it with the middleware tech debt.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why no tests for positive cases?&lt;/em&gt; 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).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why verify the header, things seem to work fine?&lt;/em&gt; The &lt;code&gt;Authorization&lt;/code&gt; 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.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>security</category>
    </item>
    <item>
      <title>Activate a Twitch Extension Panel</title>
      <dc:creator>shrmpy</dc:creator>
      <pubDate>Tue, 19 Oct 2021 07:19:01 +0000</pubDate>
      <link>https://dev.to/shrmpy/activate-a-twitch-extension-panel-318p</link>
      <guid>https://dev.to/shrmpy/activate-a-twitch-extension-panel-318p</guid>
      <description>&lt;p&gt;This article is the second in a multi-part series to walk through the creation of a Twitch extension. For the second part, the goal is to install the front-end panel.&lt;/p&gt;

&lt;p&gt;To go directly to the project, the source code repository is available at &lt;a href="https://github.com/shrmpy/pavlok-panel"&gt;github.com/shrmpy/pavlok-panel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2FA&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shrmpy/netlify-quick-deploy-for-a-twitch-extension-ebs-30cp"&gt;Deploy EBS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  § Steps
&lt;/h2&gt;

&lt;p&gt;There will be steps from &lt;a href=""&gt;part 1 of the series&lt;/a&gt; that overlap. So some steps may already be done. That said, the intention is for part 2 to be as self-contained as reasonable.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Enable two-factor. Follow the &lt;a href="https://help.twitch.tv/s/article/two-factor-authentication"&gt;instructions from Twitch support docs&lt;/a&gt;. 2FA is a requirement for creating extensions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Follow the &lt;a href="https://dev.twitch.tv/docs/tutorials/extension-101-tutorial-series/create-extension"&gt;tutorial&lt;/a&gt; to register a new extension. Specifically (repeated here):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Creating the Extension in the Console&lt;/p&gt;

&lt;p&gt;1) Go to the &lt;a href="https://dev.twitch.tv/console/extensions"&gt;Extensions Developer Console&lt;/a&gt;. From this console, you can start the process of creating a new Extension, view and clone your Extensions, and manage specific versions of your Extension(s). Login with your Twitch ID. You should see a page like this:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hy1c64b_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hy1c64b_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-2.png" alt="Extensions developer console"&gt;&lt;/a&gt;&lt;br&gt;
2) Click the &lt;strong&gt;Create an Extension&lt;/strong&gt; button on the right side of the page.&lt;/p&gt;

&lt;p&gt;3) Fill in the &lt;strong&gt;Name Your Extension&lt;/strong&gt; section with any name you want, and then click Continue.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FOag5Se2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FOag5Se2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-3.png" alt="Start an extension"&gt;&lt;/a&gt;&lt;br&gt;
4) On this versions page, be sure to fill out respective fields&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type of Extension&lt;/strong&gt; — (Required) Select Panel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Number&lt;/strong&gt; — (Required) Leave this as 0.0.1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summary&lt;/strong&gt; — (Optional) This will be viewable by broadcasters and viewers. It should be 1-2 brief sentences describing what your Extension does. To provide more detail, use the Description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt; — (Optional) More detail than the Summary about the functions of your Extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author Name&lt;/strong&gt; — (Optional) The full name of the Extension author or organization that will receive credit on the Extensions manager.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author Email&lt;/strong&gt; — (Optional) Contact information for the Extension creator. This is used to contact the developer with information about the Extension’s life cycle (e.g., reject/accept notifications). Twitch will never reveal this email to anyone on the site. If you provide this information, you’ll get a verification email soon after creating the Extension version. Be sure to check your email, as you need to verify ownership of the author email address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support Email&lt;/strong&gt; — (Optional) Public contact information for support-related queries from broadcasters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5) Click &lt;strong&gt;Create Extension Version&lt;/strong&gt;. You will then see the Status page of the Extension Manager. This page essentially shows the current status (Local Test) of the Extension in the Extension Life Cycle, and also provides you with next steps to move the Extension through the life cycle. You should see the following page:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kzd_jF7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kzd_jF7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-4.png" alt="extension life cycle"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the filenames of the broadcaster config, live config, and viewer panel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the &lt;strong&gt;Asset Hosting&lt;/strong&gt; tab. &lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Panel Viewer Path&lt;/strong&gt; should already be pre-filled with the name &lt;code&gt;panel.html&lt;/code&gt;. If not, then input the name &lt;code&gt;panel.html&lt;/code&gt;.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OEMdPyD6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-14.png" alt="asset hosting"&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Config Path&lt;/strong&gt; should already be pre-filled with the name &lt;code&gt;config.html&lt;/code&gt;. If not, then input the name &lt;code&gt;config.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Live Config Path&lt;/strong&gt; should be empty. Input the name &lt;code&gt;live_config.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Save Changes&lt;/strong&gt; button to save the filename settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose Configuration Service from the &lt;strong&gt;Capabilities&lt;/strong&gt; tab. Taken from the &lt;a href="https://dev.twitch.tv/docs/tutorials/extension-101-tutorial-series/local-test#step-2-enabling-the-configuration-service"&gt;tutorial&lt;/a&gt; (repeated here):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before testing the Extension, go to the Console Manager and enable the Configuration Service. Go to the &lt;strong&gt;Capabilities&lt;/strong&gt; tab of the Extension Manager, and scroll down to the second frame, where you should see a section titled “Select how you will configure your Extension.” Select &lt;strong&gt;Extension Configuration Service​&lt;/strong&gt;, and then save the changes.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G-Ar1EMS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G-Ar1EMS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-15.png" alt="configuration service"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the new extension secret as explained in &lt;a href="https://dev.twitch.tv/docs/tutorials/extension-101-tutorial-series/jwt"&gt;Step 2: JWT Secret&lt;/a&gt;. Specifically (repeated here):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; Each Extension maintains a shared secret that is used to sign tokens that validate the identity of users. Extension Secret is &lt;code&gt;base64&lt;/code&gt; encoded and is provided to you by the Extension Manager.&lt;/p&gt;

&lt;p&gt;To retrieve the secret, lets go to the Extension Manager. To get to the Manager, go to the &lt;a href="https://dev.twitch.tv/console/extensions"&gt;console&lt;/a&gt;, select a version of the Extension and click &lt;strong&gt;Manage&lt;/strong&gt;. Once on this page, you will see the default status page. On the top right of the manager, click the &lt;strong&gt;Extension Settings&lt;/strong&gt; button. You will see the following page:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Xx3rTTCW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xx3rTTCW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-29.png" alt="JWT secret"&gt;&lt;/a&gt;&lt;br&gt;
Under the &lt;strong&gt;Extension Client Configuration&lt;/strong&gt; section, copy the key provided. We need to use this key to verify that the token provided is signed with the same secret.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Obtain the ZIP archive of the front-end files. Navigate to the &lt;a href="https://github.com/shrmpy/pavlok-panel"&gt;source code repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;Releases&lt;/strong&gt; link on the right hand column to reach the release files. Download the archive named &lt;code&gt;panel.zip&lt;/code&gt;:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EPIOhao_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jt0nl0clz0lf6knyp6kl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EPIOhao_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jt0nl0clz0lf6knyp6kl.png" alt="release zip"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upload ZIP archive. With the &lt;code&gt;panel.zip&lt;/code&gt; downloaded from the source code repository, return to the &lt;a href="https://dev.twitch.tv/console/extensions"&gt;console&lt;/a&gt; where the &lt;strong&gt;Files&lt;/strong&gt; tab can be found (same as &lt;strong&gt;Go to Files&lt;/strong&gt; link in &lt;strong&gt;Status&lt;/strong&gt; tab).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Package your Extension&lt;br&gt;
When you send Twitch an Extension, you need to bundle the Extension files into a zip file. Make sure you are bundling the files, not the folder containing your Extension files.&lt;/p&gt;

&lt;p&gt;To upload your zip file to Twitch, choose &lt;strong&gt;Files &amp;gt; Upload Version in Assets &amp;gt; Choose File&lt;/strong&gt;. Then click the purple &lt;strong&gt;Upload&lt;/strong&gt; button at the bottom.&lt;/p&gt;

&lt;p&gt;Now we’re ready to move to Hosted Test. Click &lt;strong&gt;Next Step&lt;/strong&gt; in the top right, click &lt;strong&gt;Move To Hosted Test&lt;/strong&gt;, and confirm your choice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switch to Hosted Test. Navigate to the &lt;strong&gt;Status&lt;/strong&gt; tab. The heading will show &lt;strong&gt;Current Status: Local Test&lt;/strong&gt;. Now the &lt;strong&gt;Move to Hosted Test&lt;/strong&gt; button will be available. Click the button to change to Hosted Test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/shrmpy/netlify-quick-deploy-for-a-twitch-extension-ebs-30cp"&gt;Quick Deploy the EBS&lt;/a&gt;. The URL that Netlify creates for the deployment will be our EBS base URL. If you completed part 1 previously, then step #9 is already done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Activate. Follow &lt;a href="https://dev.twitch.tv/docs/tutorials/extension-101-tutorial-series/local-test#step-3-install-configure-and-activate-your-extension"&gt;tutorial&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We are ready to see the Extension live on Twitch! To do so, go to the &lt;strong&gt;Status&lt;/strong&gt; page of the Extension Manager, and scroll down to the section titled “Next Steps.” Click the ​&lt;strong&gt;View on Twitch and Install​&lt;/strong&gt; button.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w3LSlNES--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w3LSlNES--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-16.png" alt="upload files"&gt;&lt;/a&gt;&lt;br&gt;
Clicking this button will open a new tab with the Extension Details page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;:​ Next, click the purple &lt;strong&gt;I​nstall&lt;/strong&gt; ​button. When the Extension is installed, you will see a confirmation pop-up informing you that Extension installation is complete, and that if you want to activate the Extension right now, you need to configure it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gCAqhpGO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnlyzj8po0byg38kvcd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gCAqhpGO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnlyzj8po0byg38kvcd9.png" alt="panel install"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;:​ Click &lt;strong&gt;​Configure&lt;/strong&gt;​. Upon doing so, you will be directed to the Broadcaster Configuration Page. Here we see the page we created where the Broadcaster can select the options they want viewers to be able to choose from.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--49_mgHDQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/03r7e0z1cldnse2p1m47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--49_mgHDQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/03r7e0z1cldnse2p1m47.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
The URL shown here (&lt;a href="https://randomized-phrase.netlify.app"&gt;https://randomized-phrase.netlify.app&lt;/a&gt;) is an example, but the format of EBS base URL will be similar from the result of step #9.&lt;/p&gt;

&lt;p&gt;Clicking the button &lt;strong&gt;connect pavlok (opens in new tab)&lt;/strong&gt; will be the first test that the configuration fields are matched correctly. If everything works, the Pavlok OAuth Login prompt will display. Unless pop-up windows are blocked, in which case the browser configuration needs to allow &lt;code&gt;dashboard.twitch.tv&lt;/code&gt; for this step.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--naRoLzdO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ulwg0hkrkwknmgx4b5dm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--naRoLzdO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ulwg0hkrkwknmgx4b5dm.png" alt="pavlok login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the Pavlok Login is successful, the pop-up will be closed and return you to the extension config view. Close the config view, and the extension will have the &lt;strong&gt;Activate&lt;/strong&gt; dropdown available:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8E7A15sv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4okpm98kxbk6yf1s4ahv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8E7A15sv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4okpm98kxbk6yf1s4ahv.png" alt="panel activate"&gt;&lt;/a&gt;&lt;br&gt;
Choose &lt;strong&gt;Set as Panel 1&lt;/strong&gt; if that is available. Now the extension is activated.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  § Test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to the &lt;strong&gt;Stream Manager&lt;/strong&gt; page on Twitch, and under the &lt;strong&gt;Quick Actions&lt;/strong&gt; list click on the new extension. It should launch the Live Config view in a new window.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ErI5QzuP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1oqoc18z6e0317cf1ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ErI5QzuP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1oqoc18z6e0317cf1ec.png" alt="live config"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the &lt;strong&gt;shock&lt;/strong&gt; button and if everything is connected, the Pavlok API call will be invoked. If the Discord webhook is configured, the API result code will be logged in the respective channel, too.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  * Notes, Lessons, Monologue
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s05zOymw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rr0m56ruf1ubiojrfx31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s05zOymw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rr0m56ruf1ubiojrfx31.png" alt="panel flowchart"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Legacy&lt;/em&gt; panel view flow; need to revise for iterations so far.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ADxp_rhB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzest7yqscvymhth2bth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ADxp_rhB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzest7yqscvymhth2bth.png" alt="config flowchart"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Legacy&lt;/em&gt; broadcaster config; need to revise.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>tutorial</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Netlify Quick Deploy for a Twitch Extension EBS</title>
      <dc:creator>shrmpy</dc:creator>
      <pubDate>Mon, 11 Oct 2021 05:59:38 +0000</pubDate>
      <link>https://dev.to/shrmpy/netlify-quick-deploy-for-a-twitch-extension-ebs-30cp</link>
      <guid>https://dev.to/shrmpy/netlify-quick-deploy-for-a-twitch-extension-ebs-30cp</guid>
      <description>&lt;p&gt;This article is the first in a multi-part series to walk through the creation of a Twitch extension. For the first part, the goal is to host an EBS on Netlify.&lt;/p&gt;

&lt;p&gt;To go directly to the project, the source code repository is available at &lt;a href="https://github.com/shrmpy/pavlok"&gt;github.com/shrmpy/pavlok&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Netlify account&lt;/li&gt;
&lt;li&gt;Fauna DB account&lt;/li&gt;
&lt;li&gt;Twitch extension registration&lt;/li&gt;
&lt;li&gt;Pavlok app registration&lt;/li&gt;
&lt;li&gt;Discord webhook (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  § Steps
&lt;/h2&gt;

&lt;p&gt;Technically, there is a &lt;em&gt;step zero&lt;/em&gt; because you'll need a &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt;/&lt;a href="https://gitlab.com"&gt;GitLab&lt;/a&gt; account. So if you don't have one already, sign-up first AND you'll be able to use that account as your OAuth login to do &lt;code&gt;step #1&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Sign-up for a free account at &lt;a href="https://app.netlify.com/signup"&gt;Netlify&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sign-up for a free account at &lt;a href="https://dashboard.fauna.com/accounts/register"&gt;Fauna DB&lt;/a&gt; with Netlify account credentials using OAuth: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3sWX4tQr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.netlify.com/e7e29da444d6ad55250ffc14b3fcab6ff0734f35/cc486/img/blog/screenshot-2019-09-10-06.04.14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3sWX4tQr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.netlify.com/e7e29da444d6ad55250ffc14b3fcab6ff0734f35/cc486/img/blog/screenshot-2019-09-10-06.04.14.png" alt="fauna login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After your new account is opened, make the database keys as described in the &lt;a href="https://github.com/netlify/netlify-faunadb-example"&gt;example&lt;/a&gt;. Specifically (repeated here for convenience):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a. Create a database&lt;/p&gt;

&lt;p&gt;In the Fauna Cloud Console:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click “New Database”&lt;/li&gt;
&lt;li&gt;Enter “Netlify” as the “Database Name”&lt;/li&gt;
&lt;li&gt;Click “Save”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;b. Create a database access key&lt;/p&gt;

&lt;p&gt;In the Fauna Cloud Console:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click “Security” in the left navigation&lt;/li&gt;
&lt;li&gt;Click “New Key”&lt;/li&gt;
&lt;li&gt;Make sure that the “Database” field is set to “Netlify”&lt;/li&gt;
&lt;li&gt;Make sure that the “Role” field is set to “Admin”&lt;/li&gt;
&lt;li&gt;Enter “Netlify” as the “Key Name”&lt;/li&gt;
&lt;li&gt;Click “Save”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;c. Copy the database access key’s secret&lt;/p&gt;

&lt;p&gt;Save the secret somewhere safe; you won’t get a second chance to see it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Register your Twitch extension according to the &lt;a href="https://dev.twitch.tv/docs/extensions/"&gt;dev guide&lt;/a&gt;. Specifically (repeated here for convenience): &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Configure the Extension&lt;br&gt;
Configuring an Extension requires a few steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://dev.twitch.tv/console/extensions"&gt;https://dev.twitch.tv/console/extensions&lt;/a&gt;, and click &lt;strong&gt;Create Extension&lt;/strong&gt;.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FBi7PHxY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extensions-console-ui.png" alt="extensions console"&gt;
&lt;/li&gt;
&lt;li&gt;Type a unique name for your new Extension.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6P3trtLD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extensions-create-hello-world.png" alt="extension name"&gt;
&lt;/li&gt;
&lt;li&gt;Pick a type for your Extension. For this example, we’ll use a Panel Extension.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D0hJuh0a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-create-version-ui.png" alt="extension type"&gt;
&lt;/li&gt;
&lt;li&gt;Fill in any optional fields you choose.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fxEMPkzi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-optional-config-ui.png" alt="extension options"&gt;
&lt;/li&gt;
&lt;li&gt;After filling in the optional fields, click &lt;strong&gt;Create Extension Version&lt;/strong&gt;. You now have an extension!&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Find the new extension secret as explained in &lt;a href="https://dev.twitch.tv/docs/tutorials/extension-101-tutorial-series/jwt"&gt;Step 2: JWT Secret&lt;/a&gt;. Specifically (repeated here):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Concept:&lt;/strong&gt; Each Extension maintains a shared secret that is used to sign tokens that validate the identity of users. Extension Secret is &lt;code&gt;base64&lt;/code&gt; encoded and is provided to you by the Extension Manager.&lt;/p&gt;

&lt;p&gt;To retrieve the secret, lets go to the Extension Manager. To get to the Manager, go to the &lt;a href="https://dev.twitch.tv/console/extensions"&gt;console&lt;/a&gt;, select a version of the Extension and click &lt;strong&gt;Manage&lt;/strong&gt;. Once on this page, you will see the default status page. On the top right of the manager, click the &lt;strong&gt;Extension Settings&lt;/strong&gt; button. You will see the following page:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Xx3rTTCW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xx3rTTCW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.twitch.tv/docs/assets/uploads/extension-tutorial-29.png" alt="JWT secret"&gt;&lt;/a&gt;&lt;br&gt;
Under the &lt;strong&gt;Extension Client Configuration&lt;/strong&gt; section, copy the key provided. We need to use this key to verify that the token provided is signed with the same secret.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;a)&lt;/strong&gt; Sign-up for a free account at &lt;a href="https://app.pavlok.com/users/sign_up"&gt;Pavlok&lt;/a&gt;. With the account opened, &lt;a href="https://app.pavlok.com/oauth/applications"&gt;register your Pavlok app&lt;/a&gt;. Similar to the previous steps, it's necessary to keep the &lt;strong&gt;client ID&lt;/strong&gt; and &lt;strong&gt;client secret&lt;/strong&gt; for use later during the deployment. Their &lt;a href="https://app.pavlok.com/docs/oauth.html"&gt;docs&lt;/a&gt; explain in some detail about the OAuth process. For our purposes, the "redirect URI" entry can start as a placeholder value of &lt;code&gt;http://localhost:8080&lt;/code&gt;. the real value will be available after Netlify deploys successfully. Seen here, the field "Callback URL" example has the format &lt;code&gt;https://randomized-phrase.netlify.app/api/pavlok/callback&lt;/code&gt;. The &lt;em&gt;randomized-phrase&lt;/em&gt; is the hostname Netlify will create when done. So we will need to return to this Pavlok app page and enter the final URL. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ID-RalGb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06b03yfy6dl0s9m53745.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ID-RalGb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06b03yfy6dl0s9m53745.png" alt="pavlok registration"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b)&lt;/strong&gt; (OPTIONAL) Create your Discord webhook as outlined in &lt;a href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks"&gt;support doc&lt;/a&gt;. Specifically (repeated here for convenience):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yxgwg5sL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://support.discord.com/hc/article_attachments/1500000463501/Screen_Shot_2020-12-15_at_4.41.53_PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yxgwg5sL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://support.discord.com/hc/article_attachments/1500000463501/Screen_Shot_2020-12-15_at_4.41.53_PM.png" alt="discord integrations"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1) Open your &lt;strong&gt;Server Settings&lt;/strong&gt; and head into the &lt;strong&gt;Integrations&lt;/strong&gt; tab:&lt;br&gt;
2) Click the &lt;strong&gt;"Create Webhook"&lt;/strong&gt; button to create a new webhook!&lt;/p&gt;

&lt;p&gt;You'll have a few options here. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edit the avatar: By clicking the avatar next to the Name in the top left&lt;/li&gt;
&lt;li&gt;Choose what channel the Webhook posts to: By  selecting the desired text channel in the  dropdown menu.&lt;/li&gt;
&lt;li&gt;Name your Webhook: Good for distinguishing multiple webhooks for multiple different services.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;a href="https://app.netlify.com/start/deploy?repository=https://github.com/shrmpy/pavlok"&gt;Quick Deploy&lt;/a&gt; to host on Netlify&lt;br&gt;
&lt;a href="https://app.netlify.com/start/deploy?repository=https://github.com/shrmpy/pavlok"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gq_bfuxi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.netlify.com/img/deploy/button.svg" alt="quick deploy"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you choose either GitHub or GitLab for your repository, the prompt will list the environment variable settings. Match the fields to the corresponding keys from previous steps. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--drSeaTSJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iw0n3y37fl08bka5cpxm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--drSeaTSJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iw0n3y37fl08bka5cpxm.png" alt="deploy settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secret phrase used to encrypt OAuth state field: 
Can be any random text that you choose. Used internally by the EBS and will never be displayed in UI.&lt;/li&gt;
&lt;li&gt;Discord webhook:
(OPTIONAL) Discord webhook URL &lt;code&gt;step #4b&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Secret from the Twitch Extension Manager:
The secret from &lt;code&gt;step #3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Secret from Fauna DB account:
The secret from &lt;code&gt;step #2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Github avatar:
(OPTIONAL) The profile icon of your GitHub account.&lt;/li&gt;
&lt;li&gt;Pavlok app client ID:
The client ID from &lt;code&gt;step #4a&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pavlok app secret:
The secret from &lt;code&gt;step #4a&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pavlok redirect:
Use &lt;code&gt;http://localhost:8080&lt;/code&gt; for now. After deployment is completed, change it inside the Netlify site Build environment variables page. It will then become the Netlify deploy URL concatenated with &lt;code&gt;/api/pavlok/callback&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  § Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; netlify-deploy-url/api/pavlok/auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;netlify-deploy-url&lt;/code&gt; is the resulting Netlify URL from &lt;code&gt;step #5&lt;/code&gt;. The test only validates the service responds because the &lt;code&gt;Authorization&lt;/code&gt; header is missing. So to actually show more, we can try the frontend of the Twitch extension, next.&lt;/p&gt;

&lt;h2&gt;
  
  
  § End-to-end
&lt;/h2&gt;

&lt;p&gt;At this point, the EBS is live, but we don't have the UI. The front-end will demonstrate the full trip end-to-end. To continue work on the front-end, try part 2 in the &lt;a href="https://dev.to/shrmpy/series/14971"&gt;series&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  * Notes, Lessons, Monologue
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1Uv7KB37--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vlskiht2e27j8vd3j19i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1Uv7KB37--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vlskiht2e27j8vd3j19i.png" alt="sequence diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why a separate EBS?&lt;/em&gt; To help visualize where the EBS sits, the sequence diagram shows one flow. If we chop off the left side that contains Panel / Twitch, it leaves the subsystem that is not coupled to the Twitch &lt;code&gt;iframe&lt;/code&gt; and related restrictions. This makes some testing easier especially in early experiments. This was also because I was not using the Twitch Developer Rig. Since Twitch introduces extensions as simply web pages with the &lt;code&gt;helper&lt;/code&gt; imported, I wanted to pursue that and postpone learning &lt;em&gt;"Twitch"&lt;/em&gt; until later.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why three (auth/callback/shock) routes?&lt;/em&gt; To troubleshoot and learn and test. Concepts were more obvious when I could say, "first auth goes to &lt;code&gt;/auth&lt;/code&gt;, then callback goes to &lt;code&gt;/callback&lt;/code&gt;, etc". It became more obvious when understanding the &lt;code&gt;state&lt;/code&gt; field, and asking how to verify continuity (when Pavlok sends the browser to the callback). Refactoring will clean up duplicated code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why doesn't the Panel link directly to Pavlok's Login page?&lt;/em&gt; This is primarily a convenience so that the Panel doesn't keep any of the Pavlok fields required to construct the hyperlink. The EBS does the &lt;em&gt;heavy lifting&lt;/em&gt; to format the &lt;code&gt;URL&lt;/code&gt; and returns it to the Panel for it to launch a pop-up window. The side effect is that the Panel can stay thin (and remains truer to a presentation layer module).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why is a thin Panel good?&lt;/em&gt; This is another happy accident from the Twitch JWT token which contains claims meta data. It made it possible to avoid adding data to the request. The &lt;code&gt;Authorization&lt;/code&gt; header is required for the EBS to make sure requests are from trusted users. Then the token from the header is leveraged for the &lt;code&gt;channel ID&lt;/code&gt;. Maybe this overloads the token, but for now it keeps the Panel minimal and less complicated (until we learn more about Twitch).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why Netlify?&lt;/em&gt; Serverless (our last &lt;em&gt;workstation&lt;/em&gt; suffered battery failure). Golang support. Build times are quick with helpful log output for troubleshooting. Abstracts AWS Lambda (protects us from some of the complexities).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why Fauna DB?&lt;/em&gt; when the Twitch Configuration Service is already available as a key/value store. Good support between Netlify and Fauna makes other alternatives lower preference, but the primary benefit is testing without Twitch dependency. We still use the Twitch Configuration Service to store the EBS base URL; even though this makes the extension installation a little unfriendly, it helps us avoid having to hard-code the URL into the frontend.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why Pavlok?&lt;/em&gt; The Pavlok API is the functional piece of this Twitch extension, but it can be seen as a real world example of an integration. A different third party API with OAuth2 should have very similar interfaces.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why Discord?&lt;/em&gt; The Discord webhook is optional, but it is very helpful to observe the API result. Since testing without a Pavlok device, means the API calls cannot deliver the electric shocks. So using a Discord webhook serves the dual purpose of logging the API call, as well as verifying that connections are established to external systems.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>tutorial</category>
      <category>devjournal</category>
    </item>
  </channel>
</rss>
