<?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: arnica-simon</title>
    <description>The latest articles on DEV Community by arnica-simon (@arnicasimon).</description>
    <link>https://dev.to/arnicasimon</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%2F933556%2Fbe8b26ec-9688-49e6-a188-d133c41615d4.jpeg</url>
      <title>DEV Community: arnica-simon</title>
      <link>https://dev.to/arnicasimon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arnicasimon"/>
    <language>en</language>
    <item>
      <title>How to Evaluate a Static Application Security Testing (SAST) Solution</title>
      <dc:creator>arnica-simon</dc:creator>
      <pubDate>Tue, 14 Nov 2023 19:38:53 +0000</pubDate>
      <link>https://dev.to/arnicasimon/how-to-evaluate-a-static-application-security-testing-sast-solution-27i4</link>
      <guid>https://dev.to/arnicasimon/how-to-evaluate-a-static-application-security-testing-sast-solution-27i4</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Static Application Security Testing (SAST) is a foundational building block of an Application Security program. In this blog, we explore what qualities you should look for when selecting your SAST tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to evaluate a Static Application Security Testing (SAST) solution
&lt;/h2&gt;

&lt;p&gt;Static Application Security Testing (SAST) is a type of software testing that occurs relatively early in the software development lifecycle (SDLC). SAST scanners analyze an application's source code to detect syntax errors, bugs, and vulnerabilities, providing vital feedback to developers.&lt;/p&gt;

&lt;p&gt;The static nature of SAST scans means the tool doesn't need to run your source code. It merely inspects the code within your files, ensuring tests are kept performant and portable. For this reason, SAST is often one of the first stages of testing in the development pipeline—discovering a security problem can cause the rest of the pipeline to be terminated early, preventing potentially vulnerable code from being called by later dynamic tests.&lt;/p&gt;

&lt;p&gt;Adding SAST to your SDLC workflows allows you to spot and remove issues before they're included in production software. However, with a wide variety of SAST solutions available, it can be challenging to select the right one. In this article, we'll explain how to evaluate different options to select the best one for your project and team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is SAST important?
&lt;/h2&gt;

&lt;p&gt;SAST scans provide developers with insights into the quality and security of code during the development process. SAST is used to enforce coding standards, prevent bugs that could cause unexpected runtime behavior, and identify potential misconfigurations and &lt;a href="https://www.arnica.io/blog/hardening-software-development-environments-101"&gt;security risks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Utilizing a SAST solution within your SDLC is one way to identify new code risks as they enter your project. Tools can surface problems before they are compiled into a finished binary or distributed to customers. Because code isn't executed by the scanning engine, SAST allows teams to identify threats that could compromise CI/CD or test servers, ensuring mitigation takes place before they can affect future pipeline stages.&lt;/p&gt;

&lt;p&gt;Furthermore, SAST can help you achieve and maintain compliance with applicable regulatory standards such as ISO requirements, SOC2, HIPAA, and PCI DSS. Although SAST scans don’t produce an exhaustive picture of the risks facing your project—other security mechanisms, such as dynamic tests and &lt;a href="https://www.arnica.io/blog/four-takeaways-from-the-nsas-software-supply-chain-security-recommendations"&gt;supply chain hardening&lt;/a&gt;, are also required—you can use them to demonstrate the absence of certain coding issues that could affect a compliance audit's outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to evaluate a SAST solution for your application security program
&lt;/h2&gt;

&lt;p&gt;The SAST arena has expanded considerably over the past few years. It includes developer-oriented tools that work with specific languages, tools that are engineered for security and risk-management scenarios, and generic scanning engines that can be integrated with many different project types.&lt;/p&gt;

&lt;p&gt;To successfully adopt SAST, you need an accurate solution that can be easily added to your development process. SAST should empower developers with new and actionable information, instead of creating friction that leaves engineers waiting for results.&lt;/p&gt;

&lt;p&gt;Here's how you can evaluate different SAST tools and providers so you can make the right choice first time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ease of static scanner deployment and maintenance
&lt;/h3&gt;

&lt;p&gt;SAST scans should be easy to deploy and integrate with your project. Tools that require complex configuration result in additional overheads and will require specialist knowledge to maintain over time.&lt;/p&gt;

&lt;p&gt;Many solutions require you to manually configure new CI/CD pipeline stages before you can run scans and access their results. This increases set up time and can cause missing code coverage if administrators don't enable SAST for their projects.&lt;/p&gt;

&lt;p&gt;Tools that support &lt;a href="https://www.arnica.io/blog/what-is-pipelineless-security"&gt;pipelineless execution&lt;/a&gt; offer a lower effort experience with automatic coverage for new repositories. They can simultaneously minimize the burden on administrators while delivering faster, more accurate results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code coverage support
&lt;/h3&gt;

&lt;p&gt;It goes without saying that whichever SAST solution you choose, it needs to support the languages and frameworks that you're using in your product. Some SAST tools only work with specific languages whereas others &lt;a href="https://semgrep.dev/"&gt;such as Semgrep&lt;/a&gt; use customizable rules to support many different languages.&lt;/p&gt;

&lt;p&gt;If your stack depends on multiple languages or frameworks, you should consider one of these general-purpose tools to support standardization within your teams. This is also helpful when you need to work with legacy code that lacks dedicated language coverage from a purpose-built tool.&lt;/p&gt;

&lt;p&gt;To ensure that all risks are discovered, you need to attain 100% coverage of all the code assets in your repositories. Tools capable of supporting every language that's used can therefore simplify report output and reduce overall pipeline execution time. It's also useful to look for tools that can list the files that were skipped due to exclusion rules or missing language coverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security scanning performance
&lt;/h3&gt;

&lt;p&gt;SAST is most effective when it's fast: scans should occur in real-time, as code is authored. Shifting SAST as far left in the development process as possible allows new errors and risks to be discovered before they have a chance to persist in your project.&lt;/p&gt;

&lt;p&gt;Analyze the available tools to understand your options for accessing and distributing scan results. Results should be immediately made available to the individuals who need them—such as the developer who pushed a commit, or the security team that initiated a scheduled scan—so the SDLC feedback loop is kept tight and efficient. Any roadblocks in the way of developers will reduce productivity and discourage ongoing use of the SAST solution.&lt;/p&gt;

&lt;p&gt;Beware that different scanning tools may impose requirements on the kinds of input they work with. Some solutions scan source files, whereas others analyze the output of compilation processes. This affects overall scan performance; tools that use compiled inputs will extend your pipeline's run time because compilation will have to occur before the scan can start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovery, prioritization, and mitigation of SAST risks
&lt;/h3&gt;

&lt;p&gt;Any SAST scanner you select should be focused on uncovering new findings that can be actioned to effect a quality or security improvement in your project. Scanners which don't help you prioritize and mitigate risks can lead to alert fatigue scenarios, where issues &lt;a href="https://www.arnica.io/blog/security-to-do-lists-slow-you-down-security-tools-need-to-fix-the-problems-they-find"&gt;accumulate in your backlog&lt;/a&gt; because it's unclear which ones are actually relevant.&lt;/p&gt;

&lt;p&gt;Look for cohesive SAST solutions that not only find issues, but which also explain why the problem has occurred and suggest an initial prioritization. This information should be provided directly within the SAST tool so that developers don't have to repeatedly cross multiple applications.&lt;/p&gt;

&lt;p&gt;Users should be able to interact with SAST alerts and reports, such as by following links to view affected code in the repository, or apply a suggested mitigation. Tools that can't provide mitigations require developers to have pre-existing knowledge of the issue, why it presents a risk, and how it can be resolved. In cases where the developer is unfamiliar with the code, perhaps because they weren't the original author, this can impede the resolution process and lead to incorrect severity and priority assessments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presentation of SAST risk context
&lt;/h3&gt;

&lt;p&gt;Similarly, SAST scans need to provide accurate &lt;a href="https://www.arnica.io/blog/the-criticality-of-context-for-addressing-software-supply-chain-risk"&gt;risk context information&lt;/a&gt; so that developers, security leads, and project managers can efficiently determine the extent to which the product or organization is impacted. Alerts should include information such as the project the issue was found in, whether the scan ran against a production or development build, and the business importance of the repository. Other characteristics, such as the individual who introduced the issue and the developers best suited to resolving it, are also useful to expedite the resolution process.&lt;/p&gt;

&lt;p&gt;These details help you efficiently triage issues and prioritize your backlog. Minor code quality issues within projects created for internal use are unlikely to require an immediate fix, for example, whereas vulnerabilities detected in your core product's repositories should be elevated to the top of the queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom SAST rules
&lt;/h3&gt;

&lt;p&gt;SAST isn't a "one-size fits all" category. Although you can get good results out-of-the-box, for long-term use you should seek customizable solutions like Semgrep that allow you to extend the scanning engine with your own rules and policies. This permits use of one SAST tool across your entire product catalog.&lt;/p&gt;

&lt;p&gt;Being able to customize behavior on a per-project, per-repository, or per-team basis ensures different scenarios aren't unnecessarily encumbered by requirements held by the others. You can maintain a central ruleset that all code should adhere to, then layer in the specific customizations that apply to individual situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visibility into security operations
&lt;/h3&gt;

&lt;p&gt;To support security teams and administrators in their work, SAST solutions should ideally provide enough information about their own usage to facilitate full visibility and oversight. Audit logs and usage analytics allow you to understand which teams and developers are effectively using scans, versus those which may need additional support to adopt SAST and start responding to risks.&lt;/p&gt;

&lt;p&gt;These insights can reveal trends in the types of issues that are being discovered too. Recurring vulnerabilities, or repeated incidences of similar code quality problems, can indicate that teams need additional training so they can recognize and prevent those risks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scan frequency and automation
&lt;/h3&gt;

&lt;p&gt;SAST scans should be automated, frequent, and comprehensive to provide maximum protection and ensure consistency throughout your entire SDLC.&lt;/p&gt;

&lt;p&gt;A pipelineless approach provides advantages in this area, allowing you to easily set up scheduled scans that occur independently of code events such as merges and pushes. This provides continual coverage as SAST rules and vulnerability definitions change, even for projects that aren't regularly maintained. Only running scans as part of a pipeline could mean zero-day vulnerabilities go undetected until you next commit to your project.&lt;/p&gt;

&lt;p&gt;As we've discussed above, the results of scans—whether triggered manually, on a schedule, or within a deployment pipeline—should be automatically distributed to relevant team members, ready for actions to be applied. Tools that can send automated alerts to IDEs, chat apps, or pull requests guarantee information is presented where devs will see it, with easy access to available mitigations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;SAST scans analyze code without running it to find bugs, misconfigurations, and vulnerabilities. This provides rapid feedback to developers, allowing issues to be fixed before they cause a problem in later testing stages or after deployment to users.&lt;/p&gt;

&lt;p&gt;When choosing a SAST solution, you should balance factors such as scan performance, code coverage, customization, and the extent to which each tool helps you find, prioritize, and fix issues. SAST scans ideally supply actionable information that allows you to gauge the severity of each risk and understand how it can be resolved. This empowers you to improve application security and quality by ensuring relevant problems are dealt with before code gets deployed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.arnica.io/solution/code-security"&gt;Try Arnica’s SAST scanning&lt;/a&gt; today for free!&lt;/p&gt;

</description>
      <category>sast</category>
      <category>appsec</category>
      <category>security</category>
      <category>development</category>
    </item>
    <item>
      <title>What Developers Can Learn from Taylor Swift's Re-recording Strategy</title>
      <dc:creator>arnica-simon</dc:creator>
      <pubDate>Tue, 13 Jun 2023 16:48:22 +0000</pubDate>
      <link>https://dev.to/arnica/what-developers-can-learn-from-taylor-swifts-re-recording-strategy-24en</link>
      <guid>https://dev.to/arnica/what-developers-can-learn-from-taylor-swifts-re-recording-strategy-24en</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;By Nicholas Rodine &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TLDR:
&lt;/h2&gt;

&lt;p&gt;Drawing inspiration from Swift's strategic decision, developers too can seize control and ownership of their codebases, essentially safeguarding their 'masterpieces' from potential exploitation.&lt;/p&gt;

&lt;p&gt;“In an era where digital assets and intellectual property have become the lifeblood of industries, the need for control and ownership has never been more critical.” With this mantra in mind, let's venture on a journey through code security, bearing an unlikely resemblance to a Grammy-winning artist's fight for ownership over her music.&lt;/p&gt;

&lt;h2&gt;
  
  
  Long Live: A Tale of Music and Code
&lt;/h2&gt;

&lt;p&gt;Taylor Swift, the country girl turned pop phenomenon, known as much for her intricate lyrics as her litany of ex-lovers and the songs that were written in their aftermath, did something groundbreaking in the music industry. Her bold move? Re-recording her early albums. Now, you may be wondering, what does T-Swift's melodious journey have to do with software development? Well, dear reader, like the narratives woven into her songs, there's a compelling story here that every developer should tune into.&lt;/p&gt;

&lt;p&gt;Taylor, the blonde-haired, guitar-strumming darling of Nashville, found herself embroiled in a high-stakes dispute with her former label, which held the rights to her original recordings. Her solution was as innovative as it was defiant – re-record the old tracks, creating new assets she owned entirely. As we pluck the strings of this narrative, parallels emerge, resonating with the everyday struggles faced by software developers navigating the complex world of code ownership and security.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Story of Us: Welcome to the Development Room
&lt;/h2&gt;

&lt;p&gt;Let's move from the glitz of the music industry to the world of software development, where codebases are the equivalent of a musician's discography. They are intricate compositions, echoing the intellectual prowess and the tireless effort of dedicated individuals and teams. And just like Swift's masterpieces, these codebases can be vulnerable to exploitation if not adequately secured.&lt;/p&gt;

&lt;p&gt;Take for instance our fictitious yet relatable protagonist, a software developer named James. James is a dedicated professional, fond of obscure band tees, artisanal coffee, and an occasional binge of Game of Thrones. It's late on a Friday. He's ready to finish his work and dive into the latest expansion of his beloved Zelda franchise, Tears of the Kingdom.&lt;/p&gt;

&lt;p&gt;He reviews his files, commits, and pushes his code. A sense of accomplishment washes over him as all tests pass. He dreams of epic battles and legendary loot waiting for him in the mysterious depths of the underdark.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Knew You Were Trouble: The Perilous Depths of Git History
&lt;/h2&gt;

&lt;p&gt;But come Monday, trouble was waiting. His coworker, Betty, a sharp-eyed QA engineer with an uncanny affinity for finding bugs and a soft spot for Taylor Swift's breakup songs, notices something alarming. James had unwittingly pushed his AWS key to the dev environment. Hidden in the depths of a test, commented out and hardcoded, it was like a secret message hidden in a Swift lyric, but far less romantic.&lt;/p&gt;

&lt;p&gt;James, like any Swiftie hearing "we are never ever getting back together" for the first time, is in denial. "No problem," he thinks. He deletes the comment, pushes the change, and believes the trouble has passed.&lt;/p&gt;

&lt;p&gt;However, as any seasoned Swift fan or developer knows, trouble doesn't fade away so quickly. The secret is still lurking there, in the annals of Git history. Even if he cherry-picks it out and force pushes, he's faced with the dread of convincing everyone to rebase onto his new branch - an ordeal about as popular as wading through a sea of digital glitches on a certain infamous ticket site, all in a desperate race to snag tickets for Swift's latest concert.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything Has Changed: A New Era of Secure Coding with Arnica
&lt;/h2&gt;

&lt;p&gt;But what if there was a way to 'Shake It Off', much like our queen of pop does with her haters? This is where the power of &lt;a href="https://www.arnica.io/"&gt;Arnica&lt;/a&gt; comes into play, turning a Blank Space into an opportunity.&lt;/p&gt;

&lt;p&gt;Arnica, like a knight in shining armor, can rewrite your Git history, in real-time, effectively 're-recording' your codebase to censor sensitive information like passwords. It's as if you had a team of skilled songwriters (and cybersecurity experts) helping you tweak your masterpiece until it's ready for the spotlight.&lt;/p&gt;

&lt;p&gt;Now, let's revisit that fateful Friday, but this time, we're venturing into a parallel universe where Arnica is James' ally:&lt;/p&gt;

&lt;p&gt;It's Friday evening. James, his veins pulsating with the potent rush of Monster Energy and a burning determination to vanquish Ganondorf, is about to commit his code. His fingers fluently string together a git push, an action they have performed countless times before. Even before the push command fully processes, James is already nestled on his couch, gaming controller in hand, ready to dive back into his virtual adventure.&lt;/p&gt;

&lt;p&gt;As James' code begins its journey, Arnica's servers spring into action. They're as alert and keyed-in as Swifties on a mission, deciphering Taylor's cryptic Instagram clues for hints about her next album drop. In this rapid-fire scenario, Arnica swiftly identifies the oversight. With the elegance of a seasoned conductor directing a symphony, it rectifies the issue—all this happening even before any of James' team members can utter, "Look What You Made Me Do."&lt;/p&gt;

&lt;p&gt;With Arnica, the perilous story turns into a 'Love Story'. James' codebase is now secure, and he holds the power and control over his digital assets, much like Taylor with her freshly re-recorded albums.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wildest Dreams: A Better Coding Future
&lt;/h2&gt;

&lt;p&gt;In our increasingly digital world, the parallels between Taylor Swift's strategic re-recording decision and maintaining a secure, reliable codebase are unmistakable. In essence, both are about reclaiming and preserving the integrity, quality, and history of your work.&lt;/p&gt;

&lt;p&gt;So, code wizards, digital developers, and miracle makers: as we gaze into the horizon of a secure coding future, let's look to Swift's resourcefulness and resilience for inspiration. As she had to navigate the tumultuous terrain of the music industry, we too must overcome challenges to ensure our codebases are robust and safe.&lt;/p&gt;

&lt;p&gt;Just remember, with &lt;a href="https://www.arnica.io/"&gt;Arnica&lt;/a&gt; by your side, you're never Out Of The Woods. You're Ready For It, set to embark on an adventure of coding where every commit, push, and pull request is a Fearless step towards better, safer coding practices. Never let any security violation tell you it's going to stay, stay, stay. You can shake it off, and in the process, create your own masterpiece of clean, secure, and reliable code.&lt;/p&gt;

&lt;p&gt;So, to every James, Betty, and developer who has ever faced a daunting bug or a risky commit, remember this: "We're coding in a getaway car, left our old bugs in the dust - no, they won't catch us, developers gonna code, code, code." As Swift's lyricism guides us in our musical journey, may it also inspire us in our coding endeavors. Remember, just like in our favorite pop songs, even in code, the essence is in the narrative, the emotion, and the will to tell our story. Just keep coding.&lt;/p&gt;

</description>
      <category>security</category>
      <category>development</category>
      <category>devops</category>
      <category>taylorswift</category>
    </item>
    <item>
      <title>How We Converted a GitHub Tool Into a General Purpose Webhook Proxy to Supercharge Our Integration Development</title>
      <dc:creator>arnica-simon</dc:creator>
      <pubDate>Fri, 21 Apr 2023 17:11:46 +0000</pubDate>
      <link>https://dev.to/arnica/how-we-converted-a-github-tool-into-a-general-purpose-webhook-proxy-to-supercharge-our-integration-development-3ic</link>
      <guid>https://dev.to/arnica/how-we-converted-a-github-tool-into-a-general-purpose-webhook-proxy-to-supercharge-our-integration-development-3ic</guid>
      <description>&lt;p&gt;&lt;a href="https://www.linkedin.com/in/dorong/"&gt;Doron Guttman&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/roei/"&gt;Roei Ben-Harush&lt;/a&gt; @ &lt;a href="http://www.arnica.io/?utm_source=dev.to&amp;amp;utm_medium=direct&amp;amp;utm_campaign=blog&amp;amp;utm_content=general-purpose-web-proxy"&gt;[arnica]&lt;/a&gt;, April 2023&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Webhooks automate workflows by sending data from one app to another on certain events. They require a public URL, which can be a problem for testing or development. &lt;a href="https://smee.io/"&gt;&lt;em&gt;Smee.io&lt;/em&gt;&lt;/a&gt;  is a payload delivery service which proxies payloads from the webhook source and transmit them to a locally running app. However, it was designed for GitHub, so customizing it for other services is necessary - here's how we did it.&lt;/p&gt;

&lt;h3&gt;
  
  
  WIIFM (what's in it for me)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Learn what is a webhook&lt;/li&gt;
&lt;li&gt;Learn what is &lt;em&gt;smee.io&lt;/em&gt; and how it can be used with webhooks&lt;/li&gt;
&lt;li&gt;Learn how to customize &lt;em&gt;smee.io&lt;/em&gt; for your own needs&lt;/li&gt;
&lt;li&gt;Learn about some alternatives&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Webhook#Function"&gt;Webhooks&lt;/a&gt; are a powerful way to automate workflows and integrate different applications. They allow you to send data from one service to another when certain events happen. For example, you can use webhooks to notify your team on &lt;em&gt;Slack&lt;/em&gt; when someone pushes code to GitHub, or when a new issue is created. &lt;em&gt;Slack&lt;/em&gt; also uses webhooks to let your application know a user has interacted with a card you sent them on &lt;em&gt;Slack&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;However, webhooks have a limitation: they require a public URL that can receive HTTP requests from the webhook source. This means that a webhook cannot be configured to send a message to an endpoint it can't reach, like &lt;a href="http://localhost"&gt;http://localhost&lt;/a&gt;; if you want to test or develop your webhook integration locally, you need some way to expose your local host to the internet.&lt;/p&gt;

&lt;p&gt;This is where &lt;em&gt;smee.io&lt;/em&gt; comes in handy. &lt;em&gt;Smee.io&lt;/em&gt; is a webhook payload delivery service that uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events"&gt;Server-Sent Events (SSE)&lt;/a&gt; to proxy payloads from the webhook source, then transmit them to your locally running application. &lt;em&gt;Smee.io&lt;/em&gt; is "Made with ♥️ by the &lt;a href="https://github.com/probot"&gt;Probot team&lt;/a&gt;." (and we thank them for it 🙏). It's free, easy to use, and works with any service that supports webhooks - or at lease, in theory it should.&lt;/p&gt;

&lt;p&gt;Since &lt;em&gt;smee.io&lt;/em&gt; was designed to work with &lt;a href="https://github.com/probot/probot"&gt;Probot&lt;/a&gt; for enabling development of GitHub applications it works very well with GitHub webhooks and its UI is somewhat tailored for use with GitHub (parsing GitHub specific headers); however, if you want to leverage &lt;em&gt;smee.io&lt;/em&gt; for other services that use webhooks you may hit a few snags.&lt;/p&gt;

&lt;p&gt;Specifically when developing our &lt;em&gt;Slack&lt;/em&gt; app, we ran into some of those snags.&lt;/p&gt;

&lt;p&gt;In this blog post, I will show how I customized &lt;em&gt;smee.io&lt;/em&gt; so we can use it to integrate with &lt;em&gt;Slack&lt;/em&gt; webhooks. This should be applicable for other webhook services.&lt;/p&gt;

&lt;h3&gt;
  
  
  So what are the issues with &lt;em&gt;Smee.io&lt;/em&gt;?
&lt;/h3&gt;

&lt;p&gt;While &lt;em&gt;smee.io&lt;/em&gt; provides a lot of benefits while developing and testing your webhook integrations, especially with GitHub, it does have some downfalls. Some of which proved to be a real blocker for us.&lt;/p&gt;

&lt;h4&gt;
  
  
  Downtime
&lt;/h4&gt;

&lt;p&gt;First of all, since &lt;em&gt;smee.io&lt;/em&gt; is a free service (thank you again!) it is completely understood why it would have some limitations. One of which is that there is no guarantee to it's availability. Unfortunately I could not find a service which monitors &lt;em&gt;smee.io&lt;/em&gt; so I can provide factual information on how often it is down, but I can tell you that as a team working mostly in US Eastern time zone, we encounter it a lot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hoMTgyl2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19g2muhkczck1nbxtc2e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hoMTgyl2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19g2muhkczck1nbxtc2e.png" alt="Image description" width="468" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Reservation
&lt;/h4&gt;

&lt;p&gt;In order to create a channel on &lt;em&gt;smee.io&lt;/em&gt;, you should point your browser to &lt;a href="https://smee.io/new"&gt;https://smee.io/new&lt;/a&gt;, which will then redirect you to a randomly created channel. That random channel ID would be up to 16 alphanumeric characters. From the &lt;em&gt;smee.io&lt;/em&gt; code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-forwarded-proto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-forwarded-host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;/&lt;/span&gt;&lt;span class="sr"&gt;=&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;307&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;Since that channel ID is not saved anywhere, together with the fact the channel endpoints (&lt;code&gt;app.get('/:channel'...)&lt;/code&gt; and &lt;code&gt;app.post('/:channel'...)&lt;/code&gt;) don't care about the channel name itself, means you can actually pick your own (e.g. &lt;a href="https://smee.io/foo"&gt;https://smee.io/foo&lt;/a&gt;). However, if anyone knows your channel ID, they will be able to listen to messages sent to the channel, so you don't want to pick an easily guessable channel ID.&lt;/p&gt;

&lt;h4&gt;
  
  
  Security
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Smee.io&lt;/em&gt; relies on message authentication rather than channel security. This makes a lot of sense as &lt;em&gt;smee.io&lt;/em&gt; has no configuration and all the webhook messages GitHub sends out have a signature header (e.g. &lt;code&gt;X-Hub-Signature-256: sha256=xxxxxxx...&lt;/code&gt;). This is actually a good practice, which &lt;em&gt;Slack&lt;/em&gt; follows as well (different header), however, not all services do. It would have been great if you could protect your channel with a key somehow, wouldn't it? Without it, someone can spam or listen to our channel, intentionally or not. It would also mean that you would give your channels meaningful names, like &lt;code&gt;/slack-integration&lt;/code&gt; or &lt;code&gt;/github-app&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Content Type
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Smee.io&lt;/em&gt; only supports &lt;code&gt;application/json&lt;/code&gt; content as the webhook payload. I do not think that was intentional, but it was not intentionally designed to support other content types as well. This is due to the use of a common Express.js body parsers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Taking &lt;em&gt;Slack&lt;/em&gt; webhooks as an example, &lt;a href="https://api.slack.com/apis/connections/events-api#events-JSON"&gt;most events are sent as &lt;code&gt;application/json&lt;/code&gt;&lt;/a&gt;, however, &lt;a href="https://api.slack.com/interactivity/handling#payloads"&gt;some others like the interaction events, are sent as URL encoded (&lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;)&lt;/a&gt;. I, personally, don't understand why &lt;em&gt;Slack&lt;/em&gt; chose to do so, but it is what it is.&lt;/p&gt;

&lt;p&gt;Because &lt;em&gt;smee.io&lt;/em&gt; uses the &lt;code&gt;express.urlencoded&lt;/code&gt; body parse, when the it receives &lt;code&gt;content-type: application/x-www-form-urlencoded&lt;/code&gt; it will automatically convert the payload (e.g. &lt;code&gt;key1=Some%20Value&amp;amp;key2=Other%20Value&lt;/code&gt;) to JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// -&amp;gt; '{"key1":"Some Value","key2":"Other Value"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is very useful when you want to work with the content, which &lt;em&gt;smee.io&lt;/em&gt; does in order to display the content on the web UI, but it is breaking the forwarding of the message to the clients (as the client expects it to arrive as URL encoded payload)&lt;/p&gt;

&lt;h4&gt;
  
  
  Endpoint Verification
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Smee.io&lt;/em&gt; is a payload delivery service (&lt;a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern"&gt;PubSub&lt;/a&gt;) which receives payloads and forwards them to all subscribers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DN_y11xH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9pswoqq78ne0epqu6ury.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DN_y11xH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9pswoqq78ne0epqu6ury.png" alt="Image description" width="765" height="771"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means it automatically responds to the service with &lt;code&gt;200 OK&lt;/code&gt;, this happens even if there are no subscribers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emitEvent&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&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;Unfortunately, with &lt;em&gt;Slack&lt;/em&gt;, there's an &lt;a href="https://api.slack.com/apis/connections/events-api#handshake"&gt;ownership verification&lt;/a&gt; phase, in which you have to respond with a challenge. With a payload delivery service, like &lt;em&gt;Smee.io&lt;/em&gt;, there is no way to verify the endpoint, which means you can't use it for &lt;em&gt;Slack&lt;/em&gt; webhooks. Note this verification only happens once, when you configure the service.&lt;/p&gt;

&lt;h4&gt;
  
  
  Path Forwarding
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Smee.io&lt;/em&gt; does not support path forwarding. For example, if your webhooks service includes some of the important information in the path (e.g. &lt;code&gt;https://smee.io/foo/:type&lt;/code&gt;, where &lt;code&gt;:type&lt;/code&gt; is the type of payload/event), it would not work with &lt;em&gt;smee.io&lt;/em&gt; as it is not subscribing to the rest of the path which follows the &lt;code&gt;/:channel&lt;/code&gt;. The service will actually receive a &lt;code&gt;404 Cannot POST /foo/:type&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Alternative Solutions
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Public IP / Port Forwarding
&lt;/h5&gt;

&lt;p&gt;Public IPs can be costly and hard to manage. In addition, it require a different configuration (and sometimes entire new application instance) for each developer. Your app has to be deployed out in the public as you'll need a public connection to your dev box. You can combine a public IP solution with &lt;a href="https://en.wikipedia.org/wiki/Port_forwarding"&gt;port forwarding&lt;/a&gt; and trade off some of the complexity, but you will probably have to ask your IT for help with this every time there's a change.&lt;/p&gt;

&lt;h5&gt;
  
  
  Tunneling Service
&lt;/h5&gt;

&lt;p&gt;Tunneling services can be considered as a solution in some cases. Services like &lt;a href="https://ngrok.com/"&gt;ngrok&lt;/a&gt;, &lt;a href="https://github.com/fatedier/frp"&gt;frp&lt;/a&gt;, &lt;a href="https://theboroer.github.io/localtunnel-www/"&gt;localtunnel&lt;/a&gt; and &lt;a href="https://github.com/antoniomika/sish"&gt;sish&lt;/a&gt; create a public endpoint that tunnels communication to your local endpoint via a tunnel client.&lt;/p&gt;

&lt;p&gt;This is great when you need to craft a special response to the webhook service, for example the &lt;em&gt;Slack&lt;/em&gt; ownership verification mentioned above. In addition, tunneling solutions support path forwarding out of the box as it tunnels the full request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jjz0F5KT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wt93f66ssvbo5lc20hvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jjz0F5KT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wt93f66ssvbo5lc20hvx.png" alt="Image description" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tunneling services have downsides too. For example, it is a 1:1 relationship between a tunnel endpoint and a local app (there can be only one local app listening to a public endpoint). When using HTTP webhooks, for every webhook message (request) there must be a response.&lt;/p&gt;

&lt;p&gt;In our case, we have multiple developers working on the integration at the same time; this means that at any given time, while using &lt;em&gt;ngrok&lt;/em&gt; we had to edit the webhook configuration and switch it over from one developer to the other, thus "stealing" the connection.&lt;/p&gt;

&lt;h4&gt;
  
  
  Comparison
&lt;/h4&gt;

&lt;p&gt;Let's compare the different solutions discussed above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alternative&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;One-to-One &lt;sup&gt;1&lt;/sup&gt;
&lt;/th&gt;
&lt;th&gt;One-to-Many&lt;/th&gt;
&lt;th&gt;Security&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Path Forwarding&lt;/th&gt;
&lt;th&gt;Comments&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public IP&lt;/td&gt;
&lt;td&gt;Webhooks will call your public IP&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Up to you&lt;/td&gt;
&lt;td&gt;Static&lt;sup&gt;2&lt;/sup&gt;
&lt;/td&gt;
&lt;td&gt;$$$&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;Usually public IPs are not free and are complex to manage for each developer. App has to be deployed out in the public and you need a public connection to your dev box.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port forwarding&lt;/td&gt;
&lt;td&gt;Webhooks will call your public IP:PORT&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Up to you&lt;/td&gt;
&lt;td&gt;Static&lt;sup&gt;2&lt;/sup&gt;
&lt;/td&gt;
&lt;td&gt;$&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;You will probably have to ask your IT for help with this every time there's a chance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tunneling service&lt;/td&gt;
&lt;td&gt;Webhooks will call your assigned tunneling endpoint&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Everywhere&lt;/td&gt;
&lt;td&gt;$&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;Usually there's a cost associated with keeping a fixed tunneling endpoint.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free Delivery Service&lt;/td&gt;
&lt;td&gt;Like &lt;em&gt;smee.io&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;Guess channel name&lt;/td&gt;
&lt;td&gt;Everywhere&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;No special cases. Downtime.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted Delivery Service&lt;/td&gt;
&lt;td&gt;You can host your own &lt;em&gt;smee.io&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;Customized&lt;/td&gt;
&lt;td&gt;Everywhere&lt;/td&gt;
&lt;td&gt;0-$&lt;/td&gt;
&lt;td&gt;Customized&lt;/td&gt;
&lt;td&gt;You can customize the code for your needs. You can deploy for free in some cases (e.g. &lt;a href="https://vercel.com/pricing"&gt;Vercel&lt;/a&gt; or free-tier cloud providers) or at low cost (e.g. &lt;a href="https://aws.amazon.com/ecs/pricing/"&gt;ECS&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; One-to-one solution meaning you need to configure it for each developer.&lt;br&gt;
&lt;sup&gt;2&lt;/sup&gt; If you want to work from another location it won't work (without additional services, like VPN).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Customize &lt;em&gt;Smee.io&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Given the above, we decided to take &lt;em&gt;Smee.io&lt;/em&gt; and customize it to our needs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Starting Point
&lt;/h4&gt;

&lt;p&gt;My recommended starting point is to fork/clone the &lt;a href="https://github.com/probot/smee.io"&gt;&lt;em&gt;smee.io&lt;/em&gt; repo&lt;/a&gt; and get it working. And by working, I don't mean deploying it, but being able to build and run it locally to a point where you can place a break point in the code and have it pause there. This is necessary in order to customize any code. I actually had some issues doing that with the main branch as it was in &lt;a href="https://github.com/probot/smee.io/commit/3a017596c3371f28a68509549391f076c1f20754"&gt;commit 3a01759&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm using &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/install"&gt;WSL2&lt;/a&gt; and I'm not sure it was related, but I had to replace &lt;a href="https://www.npmjs.com/package/node-sass/v/4.14.0"&gt;&lt;code&gt;node-sass@4.14.0&lt;/code&gt;&lt;/a&gt; with &lt;a href="https://www.npmjs.com/package/sass/v/1.58.3"&gt;&lt;code&gt;sass@1.58.3&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-    "node-sass": "^4.14.0",
&lt;/span&gt;&lt;span class="gi"&gt;+    "sass": "^1.58.3",
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and upgrade a bunch of packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-    "babel-loader": "^8.1.0",
&lt;/span&gt;&lt;span class="gi"&gt;+    "babel-loader": "^9.1.2",
&lt;/span&gt;
-    "mini-css-extract-plugin": "^0.9.0",
&lt;span class="gi"&gt;+    "mini-css-extract-plugin": "^2.7.2",
&lt;/span&gt;
-    "node-sass": "^4.14.0",
&lt;span class="gi"&gt;+    "sass": "^1.58.3",
&lt;/span&gt;
-    "webpack": "^4.43.0",
&lt;span class="gi"&gt;+    "webpack": "^5.75.0",
&lt;/span&gt;&lt;span class="gd"&gt;-    "webpack-cli": "^3.3.11"
&lt;/span&gt;&lt;span class="gi"&gt;+    "webpack-cli": "^5.0.1"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also had to add the &lt;code&gt;--inspect&lt;/code&gt; flag to the &lt;code&gt;start-dev&lt;/code&gt; npm script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-    "start-dev": "concurrently \"nodemon --ignore src/ ./index.js\" \"webpack -w --mode development\"",
&lt;/span&gt;&lt;span class="gi"&gt;+    "start-dev": "concurrently \"nodemon --inspect --ignore src/ ./index.js\" \"webpack -w --mode development\"",
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and declare a newer version of the node engine compatibility in &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;   "engines": {
&lt;span class="gd"&gt;-    "node": "12.x.x"
&lt;/span&gt;&lt;span class="gi"&gt;+    "node": "16.x.x"
&lt;/span&gt;   },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as well as in the &lt;code&gt;Dockerfile&lt;/code&gt; together with specifying the platform architecture for compatibility building on MacBooks as well&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-FROM node:12-alpine as bundles
&lt;/span&gt;&lt;span class="gi"&gt;+FROM --platform=linux/amd64 node:16-alpine as bundles
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the final &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;Dockerfile&lt;/code&gt; &lt;a href="https://github.com/arnica-ext/smee.io/tree/arnica-v1.1"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Do the Work
&lt;/h4&gt;

&lt;p&gt;Now that I had a working local dev environment and was able to successfully build and run the docker image, I was ready to customize the application.&lt;/p&gt;

&lt;h5&gt;
  
  
  Support Configuration
&lt;/h5&gt;

&lt;p&gt;This will allow to set a security operation mode and configure the channels. I chose to use the &lt;a href="https://www.npmjs.com/package/config"&gt;&lt;code&gt;config&lt;/code&gt;&lt;/a&gt; package as I had good experience with it and it supports cascading config options.&lt;/p&gt;

&lt;p&gt;Create the default base configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/default.js&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * Mode enum
 * @enum {string}
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** block all */&lt;/span&gt;
  &lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="cm"&gt;/** no protection */&lt;/span&gt;
  &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="cm"&gt;/** only in list */&lt;/span&gt;
  &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="cm"&gt;/** requires password */&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Mode
     * @param mode {Mode} one of the modes
     */&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slack&lt;/span&gt;&lt;span class="dl"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, during deployment I can mount a &lt;code&gt;config/local.js&lt;/code&gt; file to my docker and it will override some items in the &lt;code&gt;config/default.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;You'll note that the &lt;code&gt;slack&lt;/code&gt; channel configuration defines a &lt;code&gt;handler: 'slack'&lt;/code&gt;, you'll see where that comes to play in a bit.&lt;/p&gt;

&lt;h5&gt;
  
  
  Add Support for URL Encoded Payloads
&lt;/h5&gt;

&lt;p&gt;When a URL encoded payload is received, I want to pass it as is to the subscribers. For that, we need to keep the raw content (it will be signed as well in most cases), so I piggybacked on the body parser verifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;verify&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&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;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saves the original buffer into a new &lt;code&gt;rawBody&lt;/code&gt; property based on this wonderful &lt;a href="https://gist.github.com/stigok/57d075c1cf2a609cb758898c0b202428"&gt;gist&lt;/a&gt; (thanks &lt;a href="https://github.com/stigok"&gt;stigok&lt;/a&gt;! 🙏). Then we need to forward the raw body to the subscribers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emitEvent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payload&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// forward raw body if captured (application/x-www-form-urlencoded)&lt;/span&gt;
    &lt;span class="c1"&gt;// otherwise, forward the parsed body&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&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;h5&gt;
  
  
  Create Custom Middleware
&lt;/h5&gt;

&lt;p&gt;I wanted to touch the original code as little as possible, so it would be easy to merge updates from the upstream repo. This means there should be a minimal footprint for the custom handlers and security and the changes should be encapsulated as much as possible. The best way to do that in the Express.js world is via &lt;a href="https://expressjs.com/en/guide/using-middleware.html"&gt;middleware&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First thing first, load the configuration and create a custom middleware installer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// custom-middleware.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&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;The middleware will first take care of the &lt;code&gt;/new&lt;/code&gt; route and block it if not in &lt;code&gt;open&lt;/code&gt; mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// let the next layer handle the request&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&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;Next we need to handle the &lt;code&gt;/:channel&lt;/code&gt; routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;To simplify the password protection we can embed it in the channel name (e.g. &lt;a href="http://mysmee.io/foo:password"&gt;http://mysmee.io/foo:password&lt;/a&gt;). That way we will be able to support all (I hope) webhook services. Even those who do not support custom headers or query params. To resolve the password&lt;sup&gt;3&lt;/sup&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// Note: password cannot contain the `:` character&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;blockquote&gt;
&lt;p&gt;&lt;sup&gt;3&lt;/sup&gt; Do note that I'm using clear-text password in both sender (URL) and configuration for the sake of simplicity. This solution is not meant to be used  in production. You can extend this code to support a more robust and secure solution, but that is outside of the scope of this post&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Resolve the channel options from configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;name&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;Resolve custom handler from channel options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./handlers/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;What this means is that is a handler is configured, the code will try to load it from the &lt;code&gt;/handlers&lt;/code&gt; folder. e.g. for the configuration shown above the &lt;code&gt;slack&lt;/code&gt; channel had the &lt;code&gt;handler: 'slack'&lt;/code&gt; configured, which means that the code will try to resolve the module from &lt;code&gt;./handlers/slack&lt;/code&gt;; if it finds it, it will bind the connection params to the default export method and later execute it if it passes all the conditions.&lt;/p&gt;

&lt;p&gt;Note that with node, loaded modules are cached, so it actually only loads it once while the application is running.&lt;/p&gt;

&lt;p&gt;Moving on... based on the configured mode, apply conditions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&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="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// block everything&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// allow everything &lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// allow only if in list&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// allow only if in list and password protected&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// misconfiguration&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid mode &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// if there's no handler OR it return 'false'&lt;/span&gt;
  &lt;span class="c1"&gt;// request should be handled by the next layer&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&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;And here's the special &lt;em&gt;Slack&lt;/em&gt; handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// handlers/slack.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;slackHandler&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`slackHandler[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passthrough&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this custom handler, our flow would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O_Ggf8-F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dh3u9bbodo7c04bwq9ju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O_Ggf8-F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dh3u9bbodo7c04bwq9ju.png" alt="Image description" width="746" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Put it all together with error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// custom-middleware.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Forbidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;internalServerError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;// let the next layer handle the request&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customMiddleware&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;internalServerError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./handlers/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// block everything&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// allow everything &lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// allow only if in list&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// allow only if in list and password protected&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// misconfiguration&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid mode &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// if there's no handler OR it return 'false'&lt;/span&gt;
      &lt;span class="c1"&gt;// request should be handled by the next layer&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customMiddleware&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;internalServerError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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;h5&gt;
  
  
  Install the Middleware
&lt;/h5&gt;

&lt;p&gt;In the &lt;code&gt;server.js&lt;/code&gt; file, we'll import the middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./custom-middleware&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and use it as the last middleware before the endpoint registrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pubFolder&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nx"&gt;customMiddlewareInstaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;h5&gt;
  
  
  Path Forwarding?
&lt;/h5&gt;

&lt;p&gt;Well, that will require a change in the &lt;a href="https://github.com/probot/smee-client"&gt;&lt;code&gt;smee-client&lt;/code&gt;&lt;/a&gt; as well. So maybe a follow-up?&lt;/p&gt;

&lt;h4&gt;
  
  
  Deployment
&lt;/h4&gt;

&lt;p&gt;We decided to deploy on our AWS ECS. For simplification we created a &lt;code&gt;deploy.sh&lt;/code&gt; script, you're welcome to use it as well, though it is outside the scope of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/arnica-ext/smee.io/tree/arnica-v1.1"&gt;Arnica's &lt;em&gt;smee.io&lt;/em&gt; repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://smee.io/"&gt;&lt;em&gt;Smee.io&lt;/em&gt;&lt;/a&gt; and &lt;a href="https://github.com/probot/smee.io"&gt;repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/probot/smee-client"&gt;smee-client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The good folks of the &lt;a href="https://github.com/orgs/probot/people"&gt;Probot team&lt;/a&gt; 👏&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/stigok"&gt;stigok&lt;/a&gt;'s &lt;a href="https://gist.github.com/stigok/57d075c1cf2a609cb758898c0b202428"&gt;gist&lt;/a&gt; 🙏&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ngrok.com/"&gt;ngrok&lt;/a&gt;,  &lt;a href="https://github.com/fatedier/frp"&gt;frp&lt;/a&gt;, &lt;a href="https://theboroer.github.io/localtunnel-www/"&gt;localtunnel&lt;/a&gt;, &lt;a href="https://github.com/antoniomika/sish"&gt;sish&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>What to consider before enforcing MFA on GitHub</title>
      <dc:creator>arnica-simon</dc:creator>
      <pubDate>Wed, 19 Oct 2022 21:02:02 +0000</pubDate>
      <link>https://dev.to/arnica/what-to-consider-before-enforcing-mfa-on-github-2j3c</link>
      <guid>https://dev.to/arnica/what-to-consider-before-enforcing-mfa-on-github-2j3c</guid>
      <description>&lt;p&gt;Multi-factor authorization (MFA) is becoming an industry standard for software supply chain security in an era of data leaks, compromised hardcoded secrets, and an endless number of malicious actors operating 24/7. Developers and their ecosystem are increasingly a target, as the meaning of changing source code becomes equivalent to accessing cloud environments, especially where everything is deployed as code.&lt;br&gt;
The fact that these breaches can lead to large-scale damage to an organization makes securing the company’s software supply chain critical. This article explores what an organization should consider when deciding whether to enforce MFA, as well as when and how the different stages of Security Assertion Markup Language (SAML) identity management can factor into the decision. We also discuss the limitations of MFA and how Arnica can help transition an organization smoothly to enforcing GitHub MFA to comply with software supply chain best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background: GitHub’s identity management
&lt;/h2&gt;

&lt;p&gt;GitHub provides several different ways for users to authenticate and gain access to a GitHub organization’s private resources. Developers can create and manage their own accounts, either logging in using GitHub.com directly or via an additional security layer using SAML SSO. Alternatively, organizations using GitHub Enterprise Cloud can choose to leverage Enterprise Managed User accounts, which are completely separate from any personal GitHub account a developer may have; these also allow the organization to have full control over account provisioning, access, and privileges.&lt;/p&gt;

&lt;p&gt;Enterprise Managed User accounts give organizations the most control over the activities of the account holders, with the ability to audit account activity, limit the scope of contributions and social activity to private repositories, and define specifications for user profiles and usernames. However, this option may not be the most developer-friendly, and tradeoffs have to be made between the developer experience and levels of code security. &lt;/p&gt;

&lt;p&gt;By opting to allow developers to use their own accounts to sign in, with or without SAML SSO, developers are able to stay in control of their own GitHub identity. They also don’t have to manage a separate “work” GitHub account and can keep data such as commit statistics all in one place. To see it in action, go to &lt;a href="https://github.com/settings/profile"&gt;https://github.com/settings/profile&lt;/a&gt; and enable the checkbox as seen in the screenshot below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lJxp-d_0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzyq3h7ibgjrwlmc5y5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lJxp-d_0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzyq3h7ibgjrwlmc5y5j.png" alt="Image description" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More information about viewing contributions on your GitHub profile can be found here: &lt;a href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/viewing-contributions-on-your-profile"&gt;https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/viewing-contributions-on-your-profile&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of enforcing MFA
&lt;/h2&gt;

&lt;p&gt;It is widely known that unauthorized access via compromised developer accounts is a source of risk for the software supply chain. To mitigate this risk, MFA has long been recommended as an effective defense to augment traditional password-based authentication and third-party authentication or “Bring Your Own Identity” (BYOI). For example, in the CIS Software Supply Chain Guide, a set of software supply chain best practices that can help organizations reach CIS compliance, the Source Code controls section highlights the importance of restricting codebase access to only authorized individuals. This includes mandating MFA for all code contributors when authenticating. GitHub also announced that it is requiring all users to enable 2FA by the end of 2023. Woot woot!&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoiding disruption when requiring MFA
&lt;/h2&gt;

&lt;p&gt;When enforcing MFA, it’s important to ensure that all members of the organization are aware of the requirement ahead of time. For example, when GitHub MFA is enforced for a GitHub organization, any members who do not comply will be removed from the organization. GitHub provides a way for administrators to see which members are removed as well as steps to restore membership, but this could confuse developers and disrupt existing workflows if it is not properly communicated.&lt;/p&gt;

&lt;p&gt;Many organizations also create users that are being implemented as service accounts, such as bots or processes that use GitHub APIs periodically. GitHub offers a way to enforce MFA for these accounts as well while still enabling programmatic access via personal access tokens (PATs). Organizations enabling SAML SSO will need to instruct the service account owners to authorize PATs in order to access the protected organizations. It is worth keeping in mind that GitHub automatically expires tokens that haven’t been used in over a year, so if there is an annual script, it needs to be taken into consideration. If configured as explained, there should be no issues with continuing to use these accounts after enforcing GitHub MFA.&lt;/p&gt;

&lt;h2&gt;
  
  
  MFA and SAML SSO
&lt;/h2&gt;

&lt;p&gt;If your GitHub organization is not using SAML SSO for authentication and allows personal account access, requiring MFA for personal accounts is a good first step to reducing the risk of unauthorized access: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OSOnWUep--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0t0l7szovnq54hicx4r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OSOnWUep--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0t0l7szovnq54hicx4r.png" alt="Image description" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your organization is transitioning from allowing personal account access to SAML SSO, GitHub allows two levels of SAML integration: enabling SAML without enforcement and enforcing SAML.&lt;/p&gt;

&lt;h3&gt;
  
  
  When SAML is enabled
&lt;/h3&gt;

&lt;p&gt;Enabling SAML without enforcement is a good intermediate step to full enforcement. While developers are being onboarded to SAML SSO, there will be a mix of users accessing private resources who are authenticating through personal accounts and SAML SSO. Effectively, during this period, the same vulnerabilities exist as when there is no SAML integration. As you are rolling out SAML SSO registration, if your identity provider does not already require MFA, it would be good to communicate and roll out enforcement of GitHub MFA at this time to ensure everyone is in full compliance in one step.&lt;/p&gt;

&lt;h3&gt;
  
  
  When SAML is enforced
&lt;/h3&gt;

&lt;p&gt;When SAML is enforced, your personal GitHub identity is linked with your identity provider’s SAML policy, and any unauthorized accounts will have access revoked. Many identity providers require MFA when initializing a SAML session, and with enforced SSO, your GitHub organization can benefit from that security along with all other accounts linked to SAML SSO. If no MFA is configured with the identity provider, then enforcing GitHub MFA can provide security for your GitHub resources. However, you may have to deal with the increased management burden of decentralized MFA enforcement for each service application your developers use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations of MFA
&lt;/h2&gt;

&lt;p&gt;While MFA may increase protection against software supply chain attacks, it doesn’t completely prevent unauthorized access. Token-based or SSH/GPG key-based authentication can still be compromised, just like passwords. While you can further harden SSH and GPG key-based authentication with a local password, the developer experience of this workflow is not ideal since it requires an extra step when pushing code.&lt;br&gt;
Non-bot interactive service accounts, such as an automation service account that simulates user behavior, may encounter problems authenticating when MFA is enabled. There are some workarounds for this, such as requiring human intervention to authorize a service account when needed, but this is a manual step. Another option is to lower the MFA requirement while keeping other security filters such as an IP address allowlist, but there are enough challenges with this route to warrant a completely separate post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arnica can help transition to MFA and SAML SSO
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier in this article, the transition to SAML SSO must be done with care since GitHub will remove any account that is not already registered in your identity provider. One method to make this transition smoother is to ensure the count of GitHub identities is equal to the count of mapped SAML identities:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V3Z7hncP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11t43tmsg382dlpohotw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V3Z7hncP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11t43tmsg382dlpohotw.png" alt="Image description" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will give you a way to verify that everyone bringing their own identity is also enrolled in SAML SSO so that enforcement can be turned on with minimal disruption. At the same time, it helps keep your codebase safe from unauthorized access before SAML SSO is properly enforced; it also follows the security principle of least privilege to source code.&lt;br&gt;
One common use case: In a Bring-Your-Own-Identity paradigm that does not have properly enforced SAML SSO, an employee who departs the company may still have access to your resources if permissions are not managed correctly. Arnica maps Bring-Your-Own-Identities to SAML identities, which is part of the free plan. Here is an example of such mapping:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2c1EOCgd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqqs11vrbb8fobroxmfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2c1EOCgd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqqs11vrbb8fobroxmfv.png" alt="Image description" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Arnica’s access management capabilities, you can find out who has permissions and perform user entitlement reviews easily.&lt;/p&gt;

</description>
      <category>github</category>
      <category>security</category>
      <category>mfa</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
