npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

atlassian-connect-auth

v4.1.0

Published

Helper for handling webhooks from Atlassian products

Downloads

288

Readme

atlassian-connect-auth

Known Vulnerabilities

This library implements authentication for installation requests, webhooks, and page loading from Atlassian products built with Connect.

For a deeper understanding of the concepts built into this library, please read through the Atlassian Connect documentation for the corresponding product:

Usage

import {
  AuthError,
  AuthErrorCode,
  CredentialsWithEntity,
  ExpressReqAuthDataProvider,
  InstallationType,
  verifyInstallation,
  verifyRequest,
} from 'atlassian-connect-auth'

// Consumers of this library have to provide a KeyProvider implementation that will fetch the public key from a CDN.
// Examples can be found under the `test` directory in this library.
import { GotKeyProvider } from './GotKeyProvider';

const baseUrl = 'https://your-app-base-url.com'
const asymmetricKeyProvider = new GotKeyProvider()

async function loadInstallationEntity(clientKey: string): Promise<CredentialsWithEntity<InstallationEntity>> {
  const storedEntity = await model.InstallationEntity.findOne({ where: { clientKey } })
  if (storedEntity) {
    return {
      sharedSecret: decrypt(storedEntity.encryptedSharedSecret),
      storedEntity,
    }
  }
}

const handleInstallation = async (req, res) => {
  try {
    const result = await verifyInstallation({
      baseUrl,
      asymmetricKeyProvider,
      authDataProvider: new ExpressReqAuthDataProvider(req),
      credentialsLoader: loadInstallationEntity,
    })

    const newInstallationEntity = req.body

    if (result.type === InstallationType.update) {
      const existingInstallationEntity = result.storedEntity
      await existingInstallationEntity.update(newInstallationEntity)
    } else {
      await model.InstallationEntity.create(newInstallationEntity)
    }

    res.sendStatus(201)
  } catch (error) {
    if (error instanceof AuthError) {
      console.warn(error)
      res.sendStatus(401)
    } else {
      console.error(error)
      res.sendStatus(500)
    }
  }
}

const handleAuth = async (req, res, next) => {
  try {
    const { connectJwt, storedEntity } = await verifyRequest({
      baseUrl,
      asymmetricKeyProvider,
      authDataProvider: new ExpressReqAuthDataProvider(req),
      credentialsLoader: loadInstallationEntity,
      queryStringHashType: 'context',
    })

    req.context = {
      accountId: connectJwt.context?.user?.accountId ?? connectJwt.sub,
      installationData: storedEntity
    }

    next()
  } catch (error) {
    if (error instanceof AuthError) {
      console.warn(error)
      res.sendStatus(401)
    } else {
      console.error(error)
      res.sendStatus(500)
    }
  }
}

const app = express()
  .post('/api/hooks/jira/installed', handleInstall)
  .post('/api/hooks/jira/uninstalled', handleAuth, handleUninstall)
  .post('/api/hooks/jira/project/created', handleAuth, handleProjectCreated)

Upgrading to 3.x with signed installs

Addon class was replaced with stateless function calls

Remove class instantiation and replace method calls with function calls as follows:

  • addon.auth()verifyRequest()
  • addon.install()verifyInstallation()

Also:

  • Move the baseUrl argument from the class instantiation to the function calls.
  • Remove the product argument altogether.

Callback changes

  • Replace loadCredentials with credentialsLoader.
    • The return value used to be any object with a required sharedSecret property.
    • Now you should return an object with a sharedSecret property and optionally your stored database value instoredEntity as follows:
        return {
          sharedSecret: '...',
          storedEntity: databaseInstallationData,
        }
  • Remove saveCredentials from the installation verification. Use the request body payload to persist the installation data. It's safe after verifying the installation request.
  • storedEntity will be returned by the verification function if a value is provided.
    • For installation updates (when the loader callback returns a stored entity), verifyInstallation() will return the loaded entity with an attribute also named storedEntity.
    • For new installations (when the loader callback does not return a stored entity), verifyInstallation() will not return the property storedEntity.

Query String Hash

Replace the argument skipQsh with queryStringHashType, which is an enum with the following values:

  • 'skip': skip QSH verification altogether. Use this in routes you had skipQsg: true.
  • 'computed': force verification using regular QSH algorithm.
  • 'context': force verification using static value context-qsh.
  • 'any': accepts both 'computed' and 'context'.

Note: Bitbucket Cloud does not currently support context-qsh as it does not have a JavaScript API that allows generating a context token.

Extracting the token from a request

Version 2.x took a request object as the first argument of the verifications functions. It expected an Express.js-like request object in order to extract the token from headers or query arguments.

Version 3.x decouples that from the web framework with the authDataProvider parameter.

  • Remove the first argument with the request object.
  • Provide an implementation of authDataProvider.
    • For Express.js, use provided ExpressReqAuthDataProvider. Example:
        verifyRequest({
          authDataProvider: new ExpressReqAuthDataProvider(req),
          ...
        })
    • Replace custom token extraction with customExtractToken with your implementation of AuthDataProvider.
      • Implement interface AuthDataProvider with your own token extraction.
      • Extend ExpressReqAuthDataProvider and add new ways of extracting the token from the req object. For instance:
        export class MyAuthDataProvider extends ExpressReqAuthDataProvider {
          extractConnectJwt(): string {
            // Custom query argument
            const jwt = this.req.query.customJwt as string
            if (jwt) {
              return jwt
            }
      
            // fallback to regular Connect token extraction
            return super.extractConnectJwt()
          }
        }

Signed installs and legacy verification

Add the authorizationMethod argument to the verification methods to define how you want installations to be verified.

  • sharedSecret: force legacy method that won't check new installations and will use the sharedSecret to verify installation updates and uninstallations.
  • publicKey: force new signed installs that use a public key to verify new installations, installation updates, and uninstallations.
  • any: accept both verification methods, meant to be used during the transition period. This is the default value.

Note: Bitbucket Cloud does not support signed installs as of 2021. You can still upgrade the library and keep it in compatibility mode (accepting legacy installs) as a preparation for a future upgrade.

Signed installations need to download a public key from the Atlassian Connect CDN. You need to provide an asymmetricKeyProvider to the verification functions.

  • Implement the KeyProvider interface with your HTTP client implementation.
  • The enum ConnectInstallKeysCdnUrl provides the base URLs for the Atlassian Connect CDN.
  • Look into ./test/keyProviderExamples for examples of implementations using Axios, Got, and Node Fetch.

Error codes

  • When checking error codes, replace string literals with values from the AuthErrorCode enum.
  • Code changes:
    • 'MISSED_TOKEN' is now AuthErrorCode.MISSING_JWT
    • 'MISSED_QSH' is now AuthErrorCode.MISSING_QSH

Upgrades in your app

  • Enabled signed installs in your app descriptor. For instance:
    apiMigrations: {
      gdpr: true,
      'context-qsh': true,
      'signed-install': true,
    },
  • Upgrade atlassian-jwt to 2.x, if you have a direct dependency.
    • This library depends on [email protected].
    • Replace encode() with encodeSymmetric() or encodeAsymmetric().
    • Replace decode() with decodeSymmetric() or decodeAsymmetric(). Passing the algorithm is required.