diff --git a/config.sample.php b/config.sample.php index 0073713..31b0e6e 100644 --- a/config.sample.php +++ b/config.sample.php @@ -29,6 +29,17 @@ GoController::$templateVersion = UNL\Templates\Templates::VERSION_5_3; } +// Defines the Hosts that can use the API and keys associated with them +// Referer Host Name => API Key +// "*" is used when no host name is provided +// +// Sample js code for using api +// `const go_response = await fetch("https://go.unl.edu/api/links?token=${API_KEY}", { credentials: "include" });` +$api_access_tokens = array( + "*" => "", + "local-events.unl.edu" => "test", +); + // Define Auth // Path to system trusted certificates define('CAS_CA_FILE', '/etc/pki/tls/cert.pem'); diff --git a/src/goController.php b/src/goController.php index 63f3d47..618c731 100644 --- a/src/goController.php +++ b/src/goController.php @@ -24,6 +24,7 @@ class GoController extends GoRouter { private $qrIconPNG; private $qrIconSVG; private $qrIconSize; + private $apiAccessTokens; private $flashBag; // Public State @@ -34,13 +35,14 @@ class GoController extends GoRouter { public static $template; public static $templateVersion; - public function __construct($lilurl, $auth, $flashBag, $qrIconPNG, $qrIconSVG, $qrIconSize) + public function __construct($lilurl, $auth, $flashBag, $qrIconPNG, $qrIconSVG, $qrIconSize, $apiAccessTokens) { $this->lilurl = $lilurl; $this->auth = $auth; $this->qrIconPNG = $qrIconPNG; $this->qrIconSVG = $qrIconSVG; $this->qrIconSize = $qrIconSize; + $this->apiAccessTokens = $apiAccessTokens; $this->flashBag = $flashBag; // See if already logged in via PHP CAS @@ -52,6 +54,13 @@ public function __construct($lilurl, $auth, $flashBag, $qrIconPNG, $qrIconSVG, $ private function loginCheck() { if ($this->routeRequiresLogin() && !$this->auth->isAuthenticated()) { + if ($this->routeNoRedirect()) { + $this->sendCORSHeaders(); + header('Content-Type: application/json; charset=utf-8'); + header('HTTP/1.1 403 Forbidden'); + echo '{ "message": "Forbidden" }'; + exit; + } $this->redirect($this->lilurl->getBaseUrl(self::ROUTE_PATH_LOGIN)); } } @@ -113,6 +122,10 @@ public function dispatch() { } switch($this->route) { + case self::ROUTE_NAME_LINKS_API: + $this->handleRouteLinksAPI(); + break; + case self::ROUTE_NAME_API: case self::ROUTE_NAME_HOME: $this->handleRouteHomePage(); @@ -503,6 +516,38 @@ private function handleRouteHomePage() { } } + private function handleRouteLinksAPI(): void + { + $this->sendCORSHeaders(); + header('Content-Type: application/json; charset=utf-8'); + + if (!$this->auth->isAuthenticated()) { + header('HTTP/1.1 403 Forbidden'); + echo '{"message": "Not Logged In"}'; + exit; + } + + $referer = (string) (parse_url($_SERVER['HTTP_REFERER'] ?? "", PHP_URL_HOST) ?? "*"); + $token = (string) ($_GET['token'] ?? ""); + $uid = (string) ($this->auth->getUserId() ?? ""); // This could be a url param maybe + + if (!isset($this->apiAccessTokens[$referer]) || $this->apiAccessTokens[$referer] != $token) { + header('HTTP/1.1 400 Bad Request'); + echo '{"message": "Invalid Token"}'; + exit; + } + + if (empty($uid)) { + header('HTTP/1.1 400 Bad Request'); + echo '{"message": "You need a UID!", "referer": "' . $referer . '"}'; + exit; + } + + $urls = $this->lilurl->getUserURLs($uid); + echo json_encode($urls); + exit; + } + private function sanitizeURLPost(&$mode, &$userId, &$alias) { $modeFiltered = htmlspecialchars($_POST['mode'] ?? ''); $mode = $modeFiltered === static::MODE_EDIT ? static::MODE_EDIT : static::MODE_CREATE; diff --git a/src/goRouter.php b/src/goRouter.php index 6896427..5e22e3f 100644 --- a/src/goRouter.php +++ b/src/goRouter.php @@ -6,6 +6,7 @@ class GoRouter { // route names const ROUTE_NAME_API = 'api'; + const ROUTE_NAME_LINKS_API = 'link_api'; const ROUTE_NAME_EDIT = 'edit'; const ROUTE_NAME_GROUP = 'group'; const ROUTE_NAME_GROUP_USER_ADD = 'group-user-add'; @@ -25,6 +26,7 @@ class GoRouter { // route paths const ROUTE_PATH_A = 'a/'; const ROUTE_PATH_API = 'api/'; + const ROUTE_PATH_LINKS_API = 'api/links'; const ROUTE_PATH_GROUP = 'a/group'; const ROUTE_PATH_GROUP_USER_ADD = 'a/group-user-add'; const ROUTE_PATH_GROUP_USER_REMOVE = 'a/group-user-remove'; @@ -49,6 +51,7 @@ class GoRouter { protected function routeRequiresLogin(): bool { return in_array($this->route, array( + self::ROUTE_NAME_LINKS_API, self::ROUTE_NAME_EDIT, self::ROUTE_NAME_GROUP, self::ROUTE_NAME_GROUP_USER_ADD, @@ -61,6 +64,21 @@ protected function routeRequiresLogin(): bool )); } + protected function routeNoRedirect(): bool + { + return in_array($this->route, array( + self::ROUTE_NAME_LINKS_API, + )); + } + + protected function routeRequiresCredentials(): bool + { + return in_array($this->route, array( + self::ROUTE_NAME_LINKS_API, + self::ROUTE_NAME_API, + )); + } + public function getViewTemplate() { return $this->viewTemplate; } @@ -76,7 +94,9 @@ public function route() { $this->route = self::ROUTE_NAME_HOME; } elseif ($this->pathInfo === self::ROUTE_PATH_API) { $this->route = self::ROUTE_NAME_API; - } elseif (preg_match('#^([^/]+)\.qr$#', $this->pathInfo, $matches)) { + } elseif ($this->pathInfo === self::ROUTE_PATH_LINKS_API) { + $this->route = self::ROUTE_NAME_LINKS_API; + }elseif (preg_match('#^([^/]+)\.qr$#', $this->pathInfo, $matches)) { $this->route = self::ROUTE_NAME_QR_PNG; $this->goId = $matches[1]; } elseif (preg_match('#^([^/]+)\.png$#', $this->pathInfo, $matches)) { @@ -151,6 +171,10 @@ protected function sendCORSHeaders() { header('Access-Control-Allow-Origin: ' . $allowedCORSDomain); header('Access-Control-Allow-Methods: GET, POST'); header('Access-Control-Allow-Headers: X-Requested-With'); + + if ($this->routeRequiresCredentials()) { + header('Access-Control-Allow-Credentials: true'); + } } } diff --git a/www/index.php b/www/index.php index 5c8f39b..5a01dfd 100755 --- a/www/index.php +++ b/www/index.php @@ -26,7 +26,7 @@ } $flashBag = new GoFlashBag(); -$controller = new GoController($lilurl, $auth, $flashBag, $qrIconPng, $qrIconSvg, $qrIconSize); +$controller = new GoController($lilurl, $auth, $flashBag, $qrIconPng, $qrIconSvg, $qrIconSize, $api_access_tokens); // do predispatch actions $controller->preDispatch();