const assert = require('assert');

const base64url = require('base64url');
const nanoid = require('nanoid');

const JWT = require('../../helpers/jwt');
const instance = require('../../helpers/weak_cache');

const opaqueFormat = require('./opaque');

function getClaim(token, claim) {
  return JSON.parse(base64url.decode(token.split('.')[1]))[claim];
}

const use = 'sig';
const FALLBACK_ALG = 'RS256';

module.exports = (provider) => {
  const opaque = opaqueFormat(provider);

  async function getSigningAlgAndKey(clientId) {
    let alg = FALLBACK_ALG;
    let client;
    if (clientId) {
      client = await provider.Client.find(clientId);
      assert(client);
      if (client.idTokenSignedResponseAlg !== 'none') {
        alg = client.idTokenSignedResponseAlg;
      }
    }

    const keystore = alg.startsWith('HS') ? client.keystore : instance(provider).keystore;
    const key = keystore.get({ alg, use });

    return { key, alg };
  }

  return {
    generateTokenId() {
      return nanoid();
    },
    async getValueAndPayload() {
      const [, payload] = await opaque.getValueAndPayload.call(this);
      const {
        jti, sub, iss, iat, exp, scope, aud, clientId: azp,
      } = payload;

      let value;
      if (this.jwt) {
        value = this.jwt;
      } else {
        const { key, alg } = await getSigningAlgAndKey(azp);
        value = await JWT.sign({
          jti, sub, iss, iat, exp, scope, ...(aud ? { aud, azp } : { aud: azp }),
        }, key, alg);
      }
      payload.jwt = value;

      return [value, payload];
    },
    getTokenId(token) {
      return getClaim(token, 'jti');
    },
    async verify(token, stored, { ignoreExpiration }) {
      assert.equal(token, stored.jwt);
      const jti = this.getTokenId(token);
      return opaque.verify.call(this, jti, stored, { ignoreExpiration });
    },
  };
};

const assert = require('assert');

const base64url = require('base64url');
const nanoid = require('nanoid');

const JWT = require('../../helpers/jwt');
const instance = require('../../helpers/weak_cache');

const opaqueFormat = require('./opaque');

function getClaim(token, claim) {
  return JSON.parse(base64url.decode(token.split('.')[1]))[claim];
}

const use = 'sig';
const FALLBACK_ALG = 'RS256';

module.exports = (provider) => {
  const opaque = opaqueFormat(provider);

  async function getSigningAlgAndKey(clientId) {
    let alg = FALLBACK_ALG;
    let client;
    if (clientId) {
      client = await provider.Client.find(clientId);
      assert(client);
      if (client.idTokenSignedResponseAlg !== 'none') {
        alg = client.idTokenSignedResponseAlg;
      }
    }

    const keystore = alg.startsWith('HS') ? client.keystore : instance(provider).keystore;
    const key = keystore.get({ alg, use });

    return { key, alg };
  }

  return {
    generateTokenId() {
      return nanoid();
    },
    async getValueAndPayload() {
      const [, payload] = await opaque.getValueAndPayload.call(this);
      const {
        jti, accountId: sub, iss, iat, exp, scope, aud, clientId: azp,
      } = payload;

      let value;
      if (this.jwt) {
        value = this.jwt;
      } else {
        const { key, alg } = await getSigningAlgAndKey(azp);
        value = await JWT.sign({
          jti, sub, iss, iat, exp, scope, ...(aud ? { aud, azp } : { aud: azp }),
        }, key, alg);
      }
      payload.jwt = value;

      return [value, payload];
    },
    getTokenId(token) {
      return getClaim(token, 'jti');
    },
    async verify(token, stored, { ignoreExpiration }) {
      assert.equal(token, stored.jwt);
      const jti = this.getTokenId(token);
      return opaque.verify.call(this, jti, stored, { ignoreExpiration });
    },
  };
};

add missing sub to jwt access tokens