<commit_before>'use strict'

import fs from 'fs'
import jwt from 'jsonwebtoken'
import logger from 'winston'
import path from 'path'

import * as client from '../model/clients'
import * as configIndex from '../config'

const TOKEN_PATTERN = /^ *(?:[Bb][Ee][Aa][Rr][Ee][Rr]) +([A-Za-z0-9\-._~+/]+=*) *$/

async function authenticateClient(clientID) {
  return client.ClientModel.findOne({ clientID }).then((client) => {
    if (!client) {
      throw new Error('Client does not exist')
    }
    return client
  })
}

function resolveJwtSecretOrPublicKey() {
  let secretOrPublicKey = configIndex.config.get(
    'authentication:jwt:secretOrPublicKey'
  )

  try {
    const publicKeyFilePath = path.resolve(
      __dirname,
      '..',
      '..',
      'resources',
      'certs',
      'jwt',
      secretOrPublicKey
    )

    // Check file exists
    if (fs.existsSync(publicKeyFilePath)) {
      secretOrPublicKey = fs.readFileSync(publicKeyFilePath).toString()
    }
    return secretOrPublicKey
  } catch (error) {
    throw new Error(
      `Could not read public key file to verify asymmetric JWT: ${error}`
    )
  }
}

function getJwtOptions() {
  const jwtConfig = configIndex.config.get('authentication:jwt')
  const jwtOptions = {}

  if (jwtConfig.algorithms) {
    // Algorithms can be input as an environment variable therefore the string needs to be split
    jwtOptions.algorithms = jwtConfig.algorithms.split(' ')
  } else {
    // The jsonwebtoken package does not require this field, but allowing any algorithm to be used opens a security risk
    throw new Error('JWT Algorithm not specified')
  }

  if (jwtConfig.audience) {
    // Audience can be input as an environment variable therefore the string needs to be split
    jwtOptions.audience = jwtConfig.audience.split(' ')
  }

  jwtOptions.issuer = jwtConfig.issuer

  return jwtOptions
}

async function authenticateToken(ctx) {
  if (ctx.authenticated != null) {
    return
  }

  const authHeader = ctx.request.header.authorization || ''
  const token = TOKEN_PATTERN.exec(authHeader)

  if (!token) {
    logger.warn(`Missing or invalid 'Authorization' header`)
    return
  }

  try {
    const decodedToken = jwt.verify(
      token[1],
      resolveJwtSecretOrPublicKey(),
      getJwtOptions()
    )

    if (!decodedToken.sub) {
      logger.error('Invalid JWT Payload')
      return
    }

    const client = await authenticateClient(decodedToken.sub)<commit_msg>Add JWT to Auth log message<commit_after>'use strict'

import fs from 'fs'
import jwt from 'jsonwebtoken'
import logger from 'winston'
import path from 'path'

import * as client from '../model/clients'
import * as configIndex from '../config'

const TOKEN_PATTERN = /^ *(?:[Bb][Ee][Aa][Rr][Ee][Rr]) +([A-Za-z0-9\-._~+/]+=*) *$/

async function authenticateClient(clientID) {
  return client.ClientModel.findOne({ clientID }).then((client) => {
    if (!client) {
      throw new Error('Client does not exist')
    }
    return client
  })
}

function resolveJwtSecretOrPublicKey() {
  let secretOrPublicKey = configIndex.config.get(
    'authentication:jwt:secretOrPublicKey'
  )

  try {
    const publicKeyFilePath = path.resolve(
      __dirname,
      '..',
      '..',
      'resources',
      'certs',
      'jwt',
      secretOrPublicKey
    )

    // Check file exists
    if (fs.existsSync(publicKeyFilePath)) {
      secretOrPublicKey = fs.readFileSync(publicKeyFilePath).toString()
    }
    return secretOrPublicKey
  } catch (error) {
    throw new Error(
      `Could not read public key file to verify asymmetric JWT: ${error}`
    )
  }
}

function getJwtOptions() {
  const jwtConfig = configIndex.config.get('authentication:jwt')
  const jwtOptions = {}

  if (jwtConfig.algorithms) {
    // Algorithms can be input as an environment variable therefore the string needs to be split
    jwtOptions.algorithms = jwtConfig.algorithms.split(' ')
  } else {
    // The jsonwebtoken package does not require this field, but allowing any algorithm to be used opens a security risk
    throw new Error('JWT Algorithm not specified')
  }

  if (jwtConfig.audience) {
    // Audience can be input as an environment variable therefore the string needs to be split
    jwtOptions.audience = jwtConfig.audience.split(' ')
  }

  jwtOptions.issuer = jwtConfig.issuer

  return jwtOptions
}

async function authenticateToken(ctx) {
  if (ctx.authenticated != null) {
    return
  }

  const authHeader = ctx.request.header.authorization || ''
  const token = TOKEN_PATTERN.exec(authHeader)

  if (!token) {
    logger.warn(`Missing or invalid JWT 'Authorization' header`)
    return
  }

  try {
    const decodedToken = jwt.verify(
      token[1],
      resolveJwtSecretOrPublicKey(),
      getJwtOptions()
    )

    if (!decodedToken.sub) {
      logger.error('Invalid JWT Payload')
      return
    }

    const client = await authenticateClient(decodedToken.sub)
{'fraction_non_alphanumeric': 0.11372867587327376, 'fraction_numerical': 0.0012185215272136475, 'mean_word_length': 3.205807002561913, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3380854', 'n_tokens_mistral': 1567, 'n_tokens_neox': 1517, 'n_words': 409}