<?php

namespace Vn\Web;

use Exception;

/**
 * Basic class to encode, decode and verify JWT tokens. It implements the HS256
 * algorithm from the RFC 7519 standard.
 **/
class Jwt
{
	/**
	 * Creates a new JWT token with the passed $payload and $key.
	 *
	 * @param {Array} $payload The data to encode
	 * @param {string} $key The key used to sign the token
	 * @return {string} The new JWT token
	 **/
	static function encode ($payload, $key)
	{
		$header = [
			'alg' => 'HS256',
			'typ' => 'JWT'
		];
		
		$b64Header = self::jsonB64Encode ($header);
		$b64Payload = self::jsonB64Encode ($payload);
		$b64Signature = self::getSignature ($b64Header, $b64Payload, $key);

		return "$b64Header.$b64Payload.$b64Signature";
	}

	/**
	 * Validates and extracts the data from a JWT token.
	 *
	 * @param {Array} $token The JWT token
	 * @param {string} $key The key used to validate the token
	 * @return {string} The JWT validated and decoded data
	 **/
	static function decode ($token, $key)
	{
		$parts = explode ('.', $token);
		
		if (count($parts) !== 3)
			throw new Exception ('Bad JWT token');
		
		$b64Header = $parts[0];
		$b64Payload = $parts[1];
		$b64Signature = $parts[2];

		$header = self::jsonB64Decode ($b64Header);
		$payload = self::jsonB64Decode ($b64Payload);

		if ($b64Signature != self::getSignature ($b64Header, $b64Payload, $key))
			throw new Exception ('Bad token signature');
			
		return $payload;
	}
	
	static function getSignature ($b64Header, $b64Payload, $key)
	{
		$signature = hash_hmac ('sha256', "$b64Header.$b64Payload", $key, TRUE);
		return self::base64UrlEncode ($signature);
	}
	
	static function jsonB64Encode ($data)
	{
		return self::base64UrlEncode (json_encode ($data));
	}
	
	static function jsonB64Decode ($data)
	{
		return json_decode (self::base64UrlDecode ($data), TRUE);
	}
	
	static function base64UrlEncode ($data)
	{
		return rtrim (strtr (base64_encode ($data), '+/', '-_'), '=');
	}

	static function base64UrlDecode ($data)
	{
		$remainder = strlen ($data) % 4;
		$data = strtr ($data, '-_', '+/');
		return base64_decode (str_pad ($data, $remainder, '=', STR_PAD_RIGHT));
	}
}
