Necessary Pigeonholing Mechanism

    atlassian-connect-auth
    TypeScript icon, indicating that this package has built-in type declarations

    3.0.2 • Public • Published

    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 atlassian-jwt@2.x.
      • Replace encode() with encodeSymmetric() or encodeAsymmetric().
      • Replace decode() with decodeSymmetric() or decodeAsymmetric(). Passing the algorithm is required.

    Install

    npm i atlassian-connect-auth

    DownloadsWeekly Downloads

    21

    Version

    3.0.2

    License

    Apache-2.0

    Unpacked Size

    124 kB

    Total Files

    63

    Last publish

    Collaborators

    • daniel.hreben
    • modestfake