<?php

/**
 * Class file implementing JSON Web Tokens.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2017 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flJWT Class
 *
 * Class to implement JWT tokens.
 *
 * @package fusionLib
 */
class flJWT {

	/**
	 * Encode a block of data.
	 *
	 * @param string $string The string of data to encode.
	 * @return string The encoded data.
	 */
	static private function encodeBlock($string) {
		return str_replace('=', '', strtr(base64_encode($string), '+/', '-_'));
	}

	/**
	 * Decode a block of data.
	 *
	 * @param string $string The string of data to decode.
	 * @return string The decoded data.
	 */
	static private function decodeBlock($string) {
		$remainder = strlen($string) % 4;
		if ($remainder)
			$string .= str_repeat('=', 4 - $remainder);
		return base64_decode(strtr($string, '-_', '+/'));
	}

	/**
	 * Sign a message with a given key.
	 *
	 * @param array $segments The array of message segments.
	 * @param string $key The key to sign the message with.
	 * @param string $alg The algorithm to sign with.
	 * @return string The encrypted signiture.
	 */
	static function sign($segments, $key, $alg = 'HS256') {
		$algs = [
			'HS256' => 'sha256',
			'HS384' => 'sha384',
			'HS512' => 'sha512',
		];
		return hash_hmac($algs[$alg], implode('.', $segments), $key, true);
	}

	/**
	 * Encode and sign a payload.
	 *
	 * @param array $payload The payload.
	 * @param string $key The encryption key.
	 * @param string $alg The algorithm to sign with, HS256, HS384 or HS512.
	 * @return string The encoded and signed payload.
	 */
	static function encode($payload, $key, $alg = 'HS256') {
		$segments = [
			self::encodeBlock(json_encode(['typ' => 'JWT', 'alg' => $alg ])),
			self::encodeBlock(json_encode($payload))
		];
		$segments[] = flJWT::encodeBlock(flJWT::sign($segments, $key, $alg));

		return implode('.', $segments);
	}

	/**
	 * Decode an encoded payload and check the signature.
	 * @param string $jwt The JWT token.
	 * @param string $key The encryption key, null to return the payload unchcked.
	 * @return array The decoded payload or false if not valid.
	 */
	static function decode($jwt, $key) {
		$segments = explode('.', $jwt);
		if(count($segments) != 3)
			return false;

		$header = json_decode(self::decodeBlock($segments[0]), true);
		if(!$header)
			return false;

		$payload = json_decode(self::decodeBlock($segments[1]), true);
		if(!$payload)
			return false;

		if($key !== null) {
			$sig = self::decodeBlock($segments[2]);
			if($sig != self::sign([$segments[0], $segments[1]], $key, $header['alg']))
				return false;
		}

		return $payload;
	}
}
