/**
 * JSONWebToken Model.
 *
 * Contains logic to create and verify JWT tokens.
 *
 * This file contains the logic to generate public/private key pairs and return
 * them in the format openssl/crypto expects.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
const uuid = require('uuid');
const jwt = require('jsonwebtoken');
const assert = require('assert');

const ec = require('../ec-crypto');
const Database = require('../db');

const ROLE_AUTH_CODE = 'authorization_code';
const ROLE_ACCESS_TOKEN = 'access_token';
const ROLE_REFRESH_TOKEN = 'refresh_token';

class JSONWebToken {

  /**
   * Verify a JWT by it's signature.
   *
   * @return {JSONWebToken|bool} false when invalid JSONWebToken when valid.
   */
  static async verifyJWT(sig) {
    const decoded = jwt.decode(sig, {
      complete: true,
    });

    if (!decoded || !decoded.header || !decoded.header.kid) {
      return false;
    }

    const {kid} = decoded.header;

    const tokenData = await Database.getJSONWebTokenByKeyId(kid);
    if (!tokenData) {
      return false;
    }

    const token = new JSONWebToken(tokenData);
    if (token.verify(sig)) {
      return token;
    }

    return false;
  }

  /**
   * Issue a JWT token and store it in the database.
   *
   * @param {User} user to issue token for.
   * @return {string} the JWT token signature.
   */
  static async issueToken(user) {
    const {sig, token} = this.create(user);
    await Database.createJSONWebToken(token);
    return sig;
  }

  /**
   * Issue a JWT token for an OAuth2 client and store it in the
   * database.
   *
   * @param {ClientRegistry} client to issue token for.
   * @param {string} role for token to fulfill
   * @return {string} the JWT token signature.
   */
  static async issueOAuthToken(client, role) {
    const {sig, token} = this.create(-1, role);
    token.client = client.id;
    await Database.createJSONWebToken(token);
    return sig;
  }


  /**
   * Remove a JWT token from the database by it's key id.
   *
   * @param {string} keyId of the record to remove.
   * @return bool true when a record was deleted.
   */
  static async revokeToken(keyId) {
    assert(typeof keyId === 'string');
    return Database.deleteJSONWebTokenByKeyId(keyId);
  }

  /**
   * @param number user id of the user to create a token for.
   * @return {Object} containing .sig (the jwt signature) and .token
   *  for storage in the database.
   */
  static create(user, role=ROLE_ACCESS_TOKEN) {
    const pair = ec.generateKeyPair();

    // Nothing yet ...
    const payload = {
    };

    const keyId = uuid.v4();
    const sig = jwt.sign(payload, pair.private, {
      algorithm: ec.JWT_ALGORITHM,
      keyid: keyId,
    });

    const token = {
      user,
      issuedAt: new Date(),
      publicKey: pair.public,
      keyId,
      role
    };

    return { sig, token };
  }

  constructor(obj) {
    let {user, issuedAt, publicKey, keyId, role} = obj;
    assert(typeof user === 'number');
    assert(issuedAt);
    assert(typeof publicKey === 'string');
    assert(typeof keyId === 'string');
    if (!role) {
      role = ROLE_ACCESS_TOKEN;
    }
    assert(role === ROLE_AUTH_CODE || role === ROLE_ACCESS_TOKEN ||
           role === ROLE_REFRESH_TOKEN);
    this.user = user;
    this.issuedAt = issuedAt;
    this.publicKey = publicKey;
    this.keyId = keyId;
    this.role = role;
  }

  /**
   * Verify that the given JWT matches this token.
   *
   * @param string sig jwt token.
   * @returns {Object|false} jwt payload if signature matches.
   */
  verify(sig) {
    try {
      return jwt.verify(sig, this.publicKey, {
        algorithms: [ec.JWT_ALGORITHM],
      });
    } catch (err) {
      // If this error is thrown we know the token is invalid.
      if (err.name === 'JsonWebTokenError') {
        return false;
      }
      throw err;
    }
  }

}

module.exports = JSONWebToken;

@@ -602,118 +602,29 @@
 LE_A
-UTH_CODE = 'authorization_code';%0Aconst ROLE_ACCESS_TOKEN = 'access_token';%0Aconst ROLE_REFRESH_TOKEN = 'refresh
+CCESS_TOKEN = 'access
 _tok
@@ -1173,20 +1173,32 @@
 a);%0A    
-if (
+token.payload = 
 token.ve
@@ -1206,16 +1206,39 @@
 ify(sig)
+;%0A    if (token.payload
 ) %7B%0A    
@@ -1926,34 +1926,39 @@
 (-1,
+ %7B%0A     
  role
-);
+,
 %0A    
-token.
+  
 client
- =
+_id:
  cli
@@ -1963,16 +1963,23 @@
 lient.id
+%0A    %7D)
 ;%0A    aw
@@ -2039,17 +2039,16 @@
 g;%0A  %7D%0A%0A
-%0A
   /**%0A  
@@ -2554,13 +2554,23 @@
 er, 
+payload=%7B
 role
-=
+: 
 ROLE
@@ -2582,16 +2582,17 @@
 SS_TOKEN
+%7D
 ) %7B%0A    
@@ -2631,61 +2631,8 @@
 );%0A%0A
-    // Nothing yet ...%0A    const payload = %7B%0A    %7D;%0A%0A
     
@@ -2875,20 +2875,8 @@
 eyId
-,%0A      role
 %0A   
@@ -2937,18 +2937,20 @@
 ) %7B%0A    
-le
+cons
 t %7Buser,
@@ -2976,22 +2976,16 @@
 y, keyId
-, role
 %7D = obj;
@@ -3131,172 +3131,8 @@
 ');%0A
-    if (!role) %7B%0A      role = ROLE_ACCESS_TOKEN;%0A    %7D%0A    assert(role === ROLE_AUTH_CODE %7C%7C role === ROLE_ACCESS_TOKEN %7C%7C%0A           role === ROLE_REFRESH_TOKEN);%0A
     
@@ -3248,19 +3248,22 @@
 his.
-role = role
+payload = null
 ;%0A  

Move JWT role and client_id to payload