import { sign, verify, isVerifyError } from './jwt';
import base64url from 'base64url';

test('success sign and verify', () => {
  const token = sign({ value: 'testvalue' }, 5 * 60, privateKey);
  const payload = verify<{ value: 'testvalue' }>(token, publicKey);
  if (isVerifyError(payload)) {
    throw new Error(`${payload.reason} | ${payload.message}`);
  }

  expect(payload.value).toBe('testvalue');
});

test('failed sign and verify (jwt expired)', () => {
  // 有効期限を0秒にすることで、検証を行った時点で有効期限切れにする
  const token = sign({ value: 'testvalue' }, 0, privateKey);
  const payload = verify<{ value: 'testvalue' }>(token, publicKey);
  if (!isVerifyError(payload)) {
    throw new Error(JSON.stringify(payload));
  }
  expect(payload.reason).toBe('ExpiredError');
});

test('failed sign and verify (invalid key pair)', () => {
  const token = sign({ value: 'testvalue' }, 5 * 60, privateKey);
  // 秘密鍵と対ではない公開鍵を使用して検証する
  const payload = verify<{ value: 'testvalue' }>(token, anotherPublicKey);
  if (!isVerifyError(payload)) {
    throw new Error(JSON.stringify(payload));
  }
  expect(payload.reason).toBe('InvalidToken');
  expect(payload.message).toBe('invalid signature');
});

test('failed sign and verify (invalid public key)', () => {
  const token = sign({ value: 'testvalue' }, 5 * 60, privateKey);
  // 公開鍵の形式になっていない文字列を使用して検証する
  const payload = verify<{ value: 'testvalue' }>(token, fakePublicKey);
  if (!isVerifyError(payload)) {
    throw new Error(JSON.stringify(payload));
  }
  expect(payload.reason).toBe('InvalidToken');
  expect(payload.message).toBe(
    'secretOrPublicKey must be an asymmetric key when using RS256',
  );
});

test('failed sign (invalid private key)', () => {
  expect(() => {
    // 不正な秘密鍵で署名しようとする場合はエラーがthrowされる
    sign({ value: 'testvalue' }, 5 * 60, fakePrivateKey);
  }).toThrowError();
});

test('success rewrite-token verify (as is)', () => {
  const token = sign({ value: 'testvalue' }, 5 * 60, privateKey);
  const { header, payload, verifySignature } = splitToken(token);

  {
    // 何も操作せずに構築しなおした場合、成功する
    const validToken = rebuildToken(header, payload, verifySignature);

    const value = verify<{ value: string }>(validToken, publicKey);
    if (isVerifyError(value)) {
      throw new Error(`${value.reason} | ${value.message}`);
    }

    expect(value.value).toBe('testvalue');
  }
});

test('failed rewrite-token verify (override algorithm)', () => {
  const token = sign({ value: 'testvalue' }, 5 * 60, privateKey);
  const { payload, verifySignature } = splitToken(token);

  {
    // 検証アルゴリズムを「検証なし」に書き換える
    const headerObject = { alg: 'none' };
    const payloadObject = JSON.parse(payload) as {
      value: string;
      iat: number;
      exp: number;
    };

    // 内容を操作して構築しなおした場合、失敗する
    const customToken = rebuildToken(
      JSON.stringify(headerObject),
      JSON.stringify(payloadObject),
      verifySignature,
    );

    const value = verify<{ value: string }>(customToken, publicKey);
    if (!isVerifyError(value)) {
      throw new Error(JSON.stringify(payload));
    }
    expect(value.reason).toBe('InvalidToken');
    expect(value.message).toBe('invalid algorithm');
  }
});

test('failed rewrite-token verify (override expire)', () => {
  const token = sign({ value: 'testvalue' }, 5 * 60, privateKey);
  const { header, payload, verifySignature } = splitToken(token);

  {
    // expの値を操作する
    const payloadObject = JSON.parse(payload) as {
      value: string;
      iat: number;
      exp: number;
    };
    payloadObject.exp = payloadObject.exp + 100000;

    // 内容を操作して構築しなおした場合、失敗する
    const customToken = rebuildToken(
      header,
      JSON.stringify(payloadObject),
      verifySignature,
    );

    const value = verify<{ value: string }>(customToken, publicKey);
    if (!isVerifyError(value)) {
      throw new Error(JSON.stringify(payload));
    }
    expect(value.reason).toBe('InvalidToken');
    expect(value.message).toBe('invalid signature');
  }
});

// JWT改竄テスト用ユーティリティ
const splitToken = (
  token: string,
): { header: string; payload: string; verifySignature: string } => {
  const splited = token.split('.');

  const header = base64url.decode(splited[0]);
  const payload = base64url.decode(splited[1]);
  const verifySignature = splited[2];
  return { header, payload, verifySignature };
};

// JWT改竄テスト用ユーティリティ
const rebuildToken = (
  header: string,
  payload: string,
  verifySignature: string,
): string => {
  const rebuild_header = base64url.encode(header);
  const rebuild_payload = base64url.encode(payload);
  return `${rebuild_header}.${rebuild_payload}.${verifySignature}`;
};

// テスト用に生成した秘密鍵
const privateKey = [
  '-----BEGIN RSA PRIVATE KEY-----',
  'MIIEpAIBAAKCAQEAsTVLNpW0/FzVCU7qo1DDjOkYWx6s/jE56YOOc3UzaaG/zb1F',
  'GyfRoUUgS4DnQxPNz9oM63RpQlhvG6UCwx23tL7p3PS0ZCsLeggcyLctbJAzLy/a',
  'fF9ABoreorqp/AaEs+Vdwbykb+M+nB2Sxsc57Tli2x8NiOZr5dafs3vMuIIKNsBa',
  'FAugFrd2ApxXR04jBRAorZRRFPtECE7D+hxDalw5DCd0mmdY0vrbRsgkbej0Zzzq',
  'zukJVXTMjy1YScqi3I9gLx2hLVmpK76Gtxn21AIcn8P3rKZmDyPH+9KNfWC8+ubF',
  '+VuY6nItlCgiSyTKErAp6M9pyRHKbPpdUM3aIQIDAQABAoIBAQCk7fkmwIdGKhCN',
  'LUns3opiZ8AnbpGLs702vR6kDvze35BoqDPdZl4RPwkjvMGBCLmRLly/+ATPnwcq',
  'L5Y2iz4jl1yKLaaHZBi2Zz6DARnh5QP+cwdiojQw4qb7xcfXrSltVZjBbBWPnWz0',
  'WAH3yAz94V9Emc47EFpz/CF/J0YOokxY8GlR4cwfK6NE0goAjzmatwV3IVFeR/eE',
  'x6JZAmd/0HMfOn3k/NumAMCJXKnZMQBAMQ3AduTO2lbZm+29yBqymtzTGFjrj0gm',
  '+E/ibD8vVzh0toPvUfPIqetdRT8vkUJ5UHhAkz9Vzvqhr6BhYhc2ft0x/z7HpaiX',
  'cDqnaRLBAoGBAODdPEktK1VOVXhOuikZBUHXU25iQdQRbM4kCtWiE8lBZ/f+6OPc',
  'BN+OedYMDhpFe/oFqGU4t610SPO1CdVRPnWHhMSabjh9G3gqOZjSW5tEAgT2wi+H',
  'IOVXnsos1qCMFdXWgVZw6F8wNcui9VabGic/EOqMRihEeSOjcradTSQFAoGBAMm+',
  'y2wZ8usanIDzADgTJnA4kBZzhIxK6qcPf3tPVXKuFUOFWwzGiDXeXTwM0sWN7kGb',
  'iymqhTWlYETQ3C6jPXTJiyOSco1rw45wO+xSHeQvUzXpk+9whbVAlhTcoVGiKz+9',
  'BS7+3+lKtBzXDNADxQfSGjiGb+ceilBGLV+WurRtAoGAPxn2a/aP/X1hAMTe+t95',
  'mTNqx0Qtguxs4yA8Jh04fjarjW1sP10jxPR/fjCd2IN9OflSey1CZhuGyVUZcFI/',
  'O84O1PkdSx7YkY0P4rHNYTHhezEf5yR9d75x4fxZMm59RifO3coLe4LU5dNSE76s',
  'xSyue5NnsK8ea4DXlSVpW10CgYAfHz3GWWJt/lbyVYpNHDcrzK39qKhj9BKq3ust',
  'nJlz7YL+PY5ENERC+yCq6NeC/lgo6tPXA6U1F2P4ebfdwfTzFTxPqoHdayhpysqT',
  'tD9EOkC96mCV6WfXBDWi1j5Ul43QcVphW5QzKwEKCerCFDLK+BBvc93Da6SuqYTK',
  'YDhBKQKBgQDKtNe8CjHRvkWoyKErMMpv5D0ce/yWq+oAaoqW1QKwngPyaiDeDwqM',
  'iOJzQxtvK4YqMYQdkgj5VLfWzeazd28RLODZua6phe776zuUv93LHTvYq/8RZfhk',
  'JIQJ7GETBnHmoTemwmJiSdVDsjJdtsyR4XRjIDNR5bGe7NNbZJpCUw==',
  '-----END RSA PRIVATE KEY-----',
].join('\n');

// テスト用に生成した公開鍵
const publicKey = [
  '-----BEGIN PUBLIC KEY-----',
  'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsTVLNpW0/FzVCU7qo1DD',
  'jOkYWx6s/jE56YOOc3UzaaG/zb1FGyfRoUUgS4DnQxPNz9oM63RpQlhvG6UCwx23',
  'tL7p3PS0ZCsLeggcyLctbJAzLy/afF9ABoreorqp/AaEs+Vdwbykb+M+nB2Sxsc5',
  '7Tli2x8NiOZr5dafs3vMuIIKNsBaFAugFrd2ApxXR04jBRAorZRRFPtECE7D+hxD',
  'alw5DCd0mmdY0vrbRsgkbej0ZzzqzukJVXTMjy1YScqi3I9gLx2hLVmpK76Gtxn2',
  '1AIcn8P3rKZmDyPH+9KNfWC8+ubF+VuY6nItlCgiSyTKErAp6M9pyRHKbPpdUM3a',
  'IQIDAQAB',
  '-----END PUBLIC KEY-----',
].join('\n');

// テスト用に作成した、違う秘密鍵から生成した公開鍵
const anotherPublicKey = [
  '-----BEGIN PUBLIC KEY-----',
  'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt1WsgrjpjsEfRa7vqlR3',
  '2mGxErXpvC+uRQnFtSXdP4tEYicPb1cNFUcu5xW6attTyzKHKMzwJrvmKEKVYGig',
  'n43rM+UyW79DNOQWQQblCHAc3hMolLWC+Tkw7xL4JhzZLH0rm5DF52YNYSicV1S9',
  'RpxYEeyHUa+ExV82lT47ySWAwg+yPwtDeDPMbOxHXqyw1wdqR2WVuxsQBaIRQgMk',
  'EL/qObQjA4e5jOOwERRvVLxzjhnldUZcG0cYGDfjPTewRYfCeXzMx2YM4Uo0vx0x',
  '2ZIY+im061GvfugX4/31xB5YEi+62qIwuSL5UpKjMv5yx1cvIqO76Ro3XNwsR+81',
  'KQIDAQAB',
  '-----END PUBLIC KEY-----',
].join('\n');

// 秘密鍵のように見えるが想定する形式と違う
const fakePrivateKey = [
  '-----BAGIN RSA PRIVATE KEY-----',
  'MIIEpAIBAAKCAQEAsTVLNpW0/FzVCU7qo1DDjOkYWx6s/jE56YOOc3UzaaG/zb1F',
  'GyfRoUUgS4DnQxPNz9oM63RpQlhvG6UCwx23tL7p3PS0ZCsLeggcyLctbJAzLy/a',
  'fF9ABoreorqp/AaEs+Vdwbykb+M+nB2Sxsc57Tli2x8NiOZr5dafs3vMuIIKNsBa',
  'FAugFrd2ApxXR04jBRAorZRRFPtECE7D+hxDalw5DCd0mmdY0vrbRsgkbej0Zzzq',
  'zukJVXTMjy1YScqi3I9gLx2hLVmpK76Gtxn21AIcn8P3rKZmDyPH+9KNfWC8+ubF',
  '+VuY6nItlCgiSyTKErAp6M9pyRHKbPpdUM3aIQIDAQABAoIBAQCk7fkmwIdGKhCN',
  'LUns3opiZ8AnbpGLs702vR6kDvze35BoqDPdZl4RPwkjvMGBCLmRLly/+ATPnwcq',
  'L5Y2iz4jl1yKLaaHZBi2Zz6DARnh5QP+cwdiojQw4qb7xcfXrSltVZjBbBWPnWz0',
  'WAH3yAz94V9Emc47EFpz/CF/J0YOokxY8GlR4cwfK6NE0goAjzmatwV3IVFeR/eE',
  'x6JZAmd/0HMfOn3k/NumAMCJXKnZMQBAMQ3AduTO2lbZm+29yBqymtzTGFjrj0gm',
  '+E/ibD8vVzh0toPvUfPIqetdRT8vkUJ5UHhAkz9Vzvqhr6BhYhc2ft0x/z7HpaiX',
  'cDqnaRLBAoGBAODdPEktK1VOVXhOuikZBUHXU25iQdQRbM4kCtWiE8lBZ/f+6OPc',
  'BN+OedYMDhpFe/oFqGU4t610SPO1CdVRPnWHhMSabjh9G3gqOZjSW5tEAgT2wi+H',
  'IOVXnsos1qCMFdXWgVZw6F8wNcui9VabGic/EOqMRihEeSOjcradTSQFAoGBAMm+',
  'y2wZ8usanIDzADgTJnA4kBZzhIxK6qcPf3tPVXKuFUOFWwzGiDXeXTwM0sWN7kGb',
  'iymqhTWlYETQ3C6jPXTJiyOSco1rw45wO+xSHeQvUzXpk+9whbVAlhTcoVGiKz+9',
  'BS7+3+lKtBzXDNADxQfSGjiGb+ceilBGLV+WurRtAoGAPxn2a/aP/X1hAMTe+t95',
  'mTNqx0Qtguxs4yA8Jh04fjarjW1sP10jxPR/fjCd2IN9OflSey1CZhuGyVUZcFI/',
  'O84O1PkdSx7YkY0P4rHNYTHhezEf5yR9d75x4fxZMm59RifO3coLe4LU5dNSE76s',
  'xSyue5NnsK8ea4DXlSVpW10CgYAfHz3GWWJt/lbyVYpNHDcrzK39qKhj9BKq3ust',
  'nJlz7YL+PY5ENERC+yCq6NeC/lgo6tPXA6U1F2P4ebfdwfTzFTxPqoHdayhpysqT',
  'tD9EOkC96mCV6WfXBDWi1j5Ul43QcVphW5QzKwEKCerCFDLK+BBvc93Da6SuqYTK',
  'YDhBKQKBgQDKtNe8CjHRvkWoyKErMMpv5D0ce/yWq+oAaoqW1QKwngPyaiDeDwqM',
  'iOJzQxtvK4YqMYQdkgj5VLfWzeazd28RLODZua6phe776zuUv93LHTvYq/8RZfhk',
  'JIQJ7GETBnHmoTemwmJiSdVDsjJdtsyR4XRjIDNR5bGe7NNbZJpCUw==',
  '-----END RSA PRIVATE KEY-----',
].join('\n');

// 公開鍵のように見えるが想定する形式と違う
const fakePublicKey = [
  'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt1WsgrjpjsEfRa7vqlR3',
  '2mGxErXpvC+uRQnFtSXdP4tEYicPb1cNFUcu5xW6attTyzKHKMzwJrvmKEKVYGig',
  'n43rM+UyW79DNOQWQQblCHAc3hMolLWC+Tkw7xL4JhzZLH0rm5DF52YNYSicV1S9',
  'RpxYEeyHUa+ExV82lT47ySWAwg+yPwtDeDPMbOxHXqyw1wdqR2WVuxsQBaIRQgMk',
  'EL/qObQjA4e5jOOwERRvVLxzjhnldUZcG0cYGDfjPTewRYfCeXzMx2YM4Uo0vx0x',
  '2ZIY+im061GvfugX4/31xB5YEi+62qIwuSL5UpKjMv5yx1cvIqO76Ro3XNwsR+81',
  'KQIDAQAB',
].join('\n');
