<commit_before>const { format, URL } = require('url');
const crypto = require('crypto');
const assert = require('assert');

const _ = require('lodash');
const { JWK: { createKeyStore, asKey } } = require('node-jose');
const LRU = require('lru-cache');
const base64url = require('base64url');
const uuid = require('uuid/v4');

const httpRequest = require('../helpers/http');
const epochTime = require('../helpers/epoch_time');
const instance = require('../helpers/weak_cache');
const { InvalidClientMetadata } = require('../helpers/errors');
const getSchema = require('../helpers/client_schema');
const sectorIdentifier = require('../helpers/sector_identifier');
const { LOOPBACKS } = require('../consts/client_attributes');

const KEY_ATTRIBUTES = ['crv', 'e', 'kid', 'kty', 'n', 'use', 'x', 'y', 'x5c'];
const KEY_TYPES = ['RSA', 'EC'];

const nonSecretAuthMethods = ['private_key_jwt', 'none', 'tls_client_auth', 'self_signed_tls_client_auth'];
const clientEncryptions = [
  'id_token_encrypted_response_alg',
  'request_object_encryption_alg',
  'userinfo_encrypted_response_alg',
  'introspection_encrypted_response_alg',
  'authorization_encrypted_response_alg',
];
const signAlgAttributes = [
  'id_token_signed_response_alg',
  'request_object_signing_alg',
  'token_endpoint_auth_signing_alg',
  'userinfo_signed_response_alg',
  'introspection_signed_response_alg',
  'authorization_signed_response_alg',
];

function isDefinedAndMatches(prop) {
  const value = this[prop];
  return /^(A|P)/.test(value);
}

function isDefinedAndStartsWithHS(prop) {
  const value = this[prop];
  return /^HS/.test(value);
}

function handled(kty) { return KEY_TYPES.includes(kty); }

function stripFragment(uri) {
  return format(new URL(uri), { fragment: false });
}

async function validateCertificateChain(jwk) {
  const x5c = jwk.get('x5c');
  if (!Array.isArray(x5c) || !x5c.length) {
    throw new InvalidClientMetadata('when provided, JWK x5c must be non-empty an array');
  }
  const cert = x5c[0];
  const crt = await asKey(Buffer.from(cert, 'base64'), 'x509').catch(() => {
    throw new InvalidClientMetadata('invalid x5c provided');
  });
  switch (jwk.kty) {
    case 'RSA':
      assert(crt.get('n').equals(jwk.get('n')), 'cert and key n mismatch');
      assert(crt.get('e').equals(jwk.get('e')), 'cert and key e mismatch');
      break;
    case 'EC':
      assert.deepEqual(crt.get('crv'), jwk.get('crv'), 'cert and key crv mismatch');
      assert(crt.get('x').equals(jwk.get('x')), 'cert and key x mismatch');
      assert(crt.get('y').equals(jwk.get('y')), 'cert and key y mismatch');
      break;
    default:
  }
}

const JWKStore = createKeyStore().constructor;

Object.defineProperties(JWKStore.prototype, {
  setReferences: {
    value(client, provider) {
      instance(this).client = client;
      instance(this).provider = provider;
    },
  },
  client: {
    get() {
      return instance(this).client;
    },
  },
  provider: {
    get() {
      return instance(this).provider;
    },
  },
  jwksUri: {
    get() {
      return this.client.jwksUri;
    },
  },
  fresh: {
    value() {
      if (!this.jwksUri) return true;
      const now = epochTime();
      return !!this.freshUntil && this.freshUntil > now;
    },
  },
  stale: {
    value() {
      return !this.fresh();
    },
  },
  refresh: {
    async value() {
      try {
        const {
          headers,
          body,
          statusCode,
        } = await httpRequest.get(this.jwksUri, this.provider.httpOptions());

        let freshUntil;

        if (freshUntil = Date.parse(headers.expires)) { // eslint-disable-line no-cond-assign
          freshUntil = epochTime(freshUntil);<commit_msg>fix: JWKStore prototype jwksUri undefined client (#413)<commit_after>const { format, URL } = require('url');
const crypto = require('crypto');
const assert = require('assert');

const _ = require('lodash');
const { JWK: { createKeyStore, asKey } } = require('node-jose');
const LRU = require('lru-cache');
const base64url = require('base64url');
const uuid = require('uuid/v4');

const httpRequest = require('../helpers/http');
const epochTime = require('../helpers/epoch_time');
const instance = require('../helpers/weak_cache');
const { InvalidClientMetadata } = require('../helpers/errors');
const getSchema = require('../helpers/client_schema');
const sectorIdentifier = require('../helpers/sector_identifier');
const { LOOPBACKS } = require('../consts/client_attributes');

const KEY_ATTRIBUTES = ['crv', 'e', 'kid', 'kty', 'n', 'use', 'x', 'y', 'x5c'];
const KEY_TYPES = ['RSA', 'EC'];

const nonSecretAuthMethods = ['private_key_jwt', 'none', 'tls_client_auth', 'self_signed_tls_client_auth'];
const clientEncryptions = [
  'id_token_encrypted_response_alg',
  'request_object_encryption_alg',
  'userinfo_encrypted_response_alg',
  'introspection_encrypted_response_alg',
  'authorization_encrypted_response_alg',
];
const signAlgAttributes = [
  'id_token_signed_response_alg',
  'request_object_signing_alg',
  'token_endpoint_auth_signing_alg',
  'userinfo_signed_response_alg',
  'introspection_signed_response_alg',
  'authorization_signed_response_alg',
];

function isDefinedAndMatches(prop) {
  const value = this[prop];
  return /^(A|P)/.test(value);
}

function isDefinedAndStartsWithHS(prop) {
  const value = this[prop];
  return /^HS/.test(value);
}

function handled(kty) { return KEY_TYPES.includes(kty); }

function stripFragment(uri) {
  return format(new URL(uri), { fragment: false });
}

async function validateCertificateChain(jwk) {
  const x5c = jwk.get('x5c');
  if (!Array.isArray(x5c) || !x5c.length) {
    throw new InvalidClientMetadata('when provided, JWK x5c must be non-empty an array');
  }
  const cert = x5c[0];
  const crt = await asKey(Buffer.from(cert, 'base64'), 'x509').catch(() => {
    throw new InvalidClientMetadata('invalid x5c provided');
  });
  switch (jwk.kty) {
    case 'RSA':
      assert(crt.get('n').equals(jwk.get('n')), 'cert and key n mismatch');
      assert(crt.get('e').equals(jwk.get('e')), 'cert and key e mismatch');
      break;
    case 'EC':
      assert.deepEqual(crt.get('crv'), jwk.get('crv'), 'cert and key crv mismatch');
      assert(crt.get('x').equals(jwk.get('x')), 'cert and key x mismatch');
      assert(crt.get('y').equals(jwk.get('y')), 'cert and key y mismatch');
      break;
    default:
  }
}

const JWKStore = createKeyStore().constructor;

Object.defineProperties(JWKStore.prototype, {
  setReferences: {
    value(client, provider) {
      instance(this).client = client;
      instance(this).provider = provider;
    },
  },
  client: {
    get() {
      return instance(this).client;
    },
  },
  provider: {
    get() {
      return instance(this).provider;
    },
  },
  jwksUri: {
    get() {
      return this.client && this.client.jwksUri;
    },
  },
  fresh: {
    value() {
      if (!this.jwksUri) return true;
      const now = epochTime();
      return !!this.freshUntil && this.freshUntil > now;
    },
  },
  stale: {
    value() {
      return !this.fresh();
    },
  },
  refresh: {
    async value() {
      try {
        const {
          headers,
          body,
          statusCode,
        } = await httpRequest.get(this.jwksUri, this.provider.httpOptions());

        let freshUntil;

        if (freshUntil = Date.parse(headers.expires)) { // eslint-disable-line no-cond-assign
          freshUntil = epochTime(freshUntil);
{'fraction_non_alphanumeric': 0.16354825683133664, 'fraction_numerical': 0.005518912370440167, 'mean_word_length': 3.8562091503267975, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, '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': '4585014', 'n_tokens_mistral': 2543, 'n_tokens_neox': 2487, 'n_words': 490}