'; } if(!empty($vars['styles']) && is_array($vars['styles'])) { foreach($vars['styles'] as $style) $vars['head'] .= sprintf('', $style); } $vars['menu_site'] = Template::renderSet('menu-site-item', [ ['text' => 'Home', 'link' => page_url('/')], ['text' => 'Create a Zone', 'link' => page_url('/zones/create')], ['text' => 'Zones', 'link' => page_url('/zones')], ['text' => 'Random', 'link' => page_url('/zones/random')], ]); $userMenu = []; if(UserSession::hasInstance()) { $userName = UserSession::instance()->getUser()->getUsername(); $userMenu = [ ['text' => "@{$userName}", 'link' => page_url("/@{$userName}")], ['text' => 'My Zones', 'link' => page_url('/zones?f=my')], ['text' => 'Settings', 'link' => page_url('/settings')], ]; $userMenu[] = ['text' => 'Log out', 'link' => page_url('/auth/logout', ['s' => UserSession::instance()->getSmallToken()])]; } else { $userMenu = [ ['text' => 'Log in', 'link' => page_url('/auth/login')], ]; } $vars['menu_user'] = Template::renderSet('menu-user-item', $userMenu); $vars['maintenance'] = YTKNS_MAINTENANCE ? Template::renderRaw('maintenance') : ''; return Template::renderRaw('header', $vars); } function html_footer(array $vars = []): string { $vars['footer_took'] = number_format(microtime(true) - YTKNS_STARTUP, 5); $vars['footer_year'] = date('Y'); $scripts = $vars['scripts'] ?? null; $vars['scripts'] = ''; if(!empty($scripts) && is_array($scripts)) { foreach($scripts as $script) $vars['scripts'] .= sprintf('', $script); } return Template::renderRaw('footer', $vars); } function html_information(string $message, string $title = 'Information', ?string $redirect = null, int $redirectTimeout = 2): string { $html = html_header([ 'title' => $title . ' - YTKNS', 'redirect' => $redirect, 'redirect_timeout' => $redirectTimeout, ]); if(!empty($redirect)) $message .= Template::renderRaw('information-redirect', [ 'info_redirect' => $redirect, ]); $html .= Template::renderRaw('information', [ 'info_title' => $title, 'info_content' => $message, ]); $html .= html_footer(); return $html; } function html_pagination(int $pages, int $current, string $urlFormat): string { $html = ''; } if(!YTKNS_MAINTENANCE && !empty($_COOKIE['ytkns_login']) && is_string($_COOKIE['ytkns_login'])) { try { $session = UserSession::byToken($_COOKIE['ytkns_login']); $session->update(); $session->setInstance(); if($session->getBump()) setcookie('ytkns_login', $session->getToken(), $session->getExpires(), '/', '.' . Config::get('domain.main'), false, true); unset($session); } catch(UserSessionNotFoundException $ex) {} } $zoneName = strtolower(substr($_SERVER['HTTP_HOST'], 0, -strlen(Config::get('domain.main')) - 1)); if(!empty($zoneName)) { $redirect = ZoneRedirect::find($zoneName); if($redirect !== null) { $redirect->execute(); return; } try { $zoneInfo = Zone::byName($zoneName); } catch(ZoneNotFoundException $ex) { http_response_code(404); echo html_header(['title' => 'Zone not found!']); Template::render('zones/none', [ 'zone_create_url' => page_url('/zones/create', ['name' => $zoneName]), ]); echo html_footer(); return; } if(!YTKNS_MAINTENANCE) { if(!empty($_GET['_refresh_screenshot'])) { header('Location: /'); $zoneInfo->takeScreenshot(); return; } ZoneView::increment($zoneInfo, $_SERVER['REMOTE_ADDR']); } echo (string)$zoneInfo->getPageBuilder(true); return; } $reqMethod = $_SERVER['REQUEST_METHOD']; $reqPath = '/' . trim(parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/'); if($reqPath === '/') { echo html_header(); Template::render('home'); echo html_footer(); return; } if(substr($reqPath, 0, 4) === '/ss/') { header('Content-Type: image/jpeg'); header('X-Accel-Redirect: /assets/no-screenshot.jpg'); return; } if(preg_match('#^/@([A-Za-z0-9-_]+)$#', $reqPath, $matches)) { try { $profile = User::forProfile($matches[1]); } catch(Exception $ex) { http_response_code(404); echo html_header(['title' => 'User not found - YTKNS']); Template::render('profile/notfound'); echo html_footer(); return; } $zones = Zone::byUser($profile, 'zone_views', false); if(count($zones) < 1) { $profileZones = Template::renderRaw('profile/zone-none'); } else { $profileZones = []; foreach($zones as $zone) $profileZones[] = [ 'zone_id' => $zone->getId(), 'zone_name' => $zone->getName(), 'zone_title' => $zone->getTitle(), 'zone_views' => number_format($zone->getViews()), 'zone_url' => $zone->getUrl(), 'zone_screenshot' => $zone->getScreenshotUrl(), ]; $profileZones = Template::renderRaw('profile/zone-list', [ 'zone_items' => Template::renderSet('profile/zone-item', $profileZones), ]); } echo html_header(['title' => $profile->username . ' @ YTKNS']); Template::render('profile/index', [ 'profile_username' => $profile->username, 'profile_zones' => $profileZones, ]); echo html_footer(); return; } if($reqPath === '/zones') { $zoneFilter = filter_input(INPUT_GET, 'f'); if($zoneFilter === 'my' && !UserSession::hasInstance()) { echo html_information('You must be logged in to do this.'); return; } $zoneTake = 20; $zonePage = max(filter_input(INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT) - 1, 0); $zoneOffset = $zonePage * $zoneTake; $zoneSort = filter_input(INPUT_GET, 'sort'); $zoneSortDesc = filter_input(INPUT_GET, 'asc', FILTER_SANITIZE_NUMBER_INT) < 1; $zoneOrderings = [ 'creation' => 'zone_created', 'updated' => 'zone_updated', 'views' => 'zone_views', 'user' => 'user_id', ]; if(!array_key_exists($zoneSort, $zoneOrderings)) { switch($zoneFilter) { case 'my': $zoneSort = 'creation'; break; default: $zoneSort = 'views'; $zoneSortDesc = false; break; } } switch($zoneFilter) { case 'my': $zones = Zone::byUser(UserSession::instance()->getUser(), $zoneOrderings[$zoneSort], $zoneSortDesc, $zoneTake, $zoneOffset); $zoneListTemplate = 'zones/my-item'; $zoneListTitle = 'My Zones'; break; default: $zones = Zone::all($zoneOrderings[$zoneSort], $zoneSortDesc, $zoneTake, $zoneOffset); $zoneListTemplate = 'zones/item'; $zoneListTitle = 'Zones'; $zoneFilter = ''; break; } $zoneCount = Zone::count(); $zonePages = ceil($zoneCount / $zoneTake); $zoneList = []; foreach($zones as $zone) $zoneList[] = [ 'zone_id' => $zone->getId(), 'zone_name' => $zone->getName(), 'zone_title' => $zone->getTitle(), 'zone_views' => number_format($zone->getViews()), 'zone_url' => $zone->getUrl(), 'zone_edit_url' => page_url(sprintf('/zones/%d', $zone->getId())), 'zone_delete_url' => page_url(sprintf('/zones/%d/delete', $zone->getId())), 'zone_screenshot' => $zone->getScreenshotUrl(), 'zone_user_url' => page_url('/@' . $zone->getUser()->getUsername()), 'zone_user_name' => $zone->getUser()->getUsername(), ]; $zoneSortings = ''; foreach(array_keys($zoneOrderings) as $order) { $orderCurrent = $zoneSort === $order; $zoneSortings .= sprintf( '%3$s%5$s', $order, $orderCurrent ? $zoneSortDesc : !$zoneSortDesc, ucfirst($order), $orderCurrent ? ' zones-sorts-current' : '', $orderCurrent ? sprintf( ' %s', $zoneSortDesc ? 'down' : 'up', $zoneSortDesc ? 'Descending' : 'Ascending' ) : '', $zoneFilter ); } echo html_header(['title' => $zoneListTitle . ' - YTKNS']); Template::render('zones/list', [ 'zone_list_title' => $zoneListTitle, 'zone_list' => Template::renderSet($zoneListTemplate, $zoneList), 'zone_sortings' => $zoneSortings, ]); echo html_pagination($zonePages, $zonePage + 1, sprintf('/zones?f=%s&sort=%s&asc=%d&page=%%s', $zoneFilter, $zoneSort, !$zoneSortDesc)); echo html_footer(); return; } if($reqPath === '/zones/random') { $zoneInfo = Zone::byRandom(); header('Location: ' . $zoneInfo->getUrl()); return; } if($reqPath === '/zones/create') { if(!UserSession::hasInstance()) { http_response_code(403); echo html_information('You must be logged in to do this.'); return; } $createToken = UserSession::instance()->getSmallToken(4); if($reqMethod === 'POST') { $zoneToken = filter_input(INPUT_POST, 'zone_token'); $zoneName = filter_input(INPUT_POST, 'zone_subdomain'); $zoneTitle = filter_input(INPUT_POST, 'zone_title'); if($zoneToken !== $createToken) { $createError = 'Invalid request.'; } else { if(empty($zoneName) || empty($zoneTitle)) { $createError = 'Please fill in all fields.'; } else { if(!Zone::validName($zoneName)) { $createError = 'Name contains invalid characters or is too long.'; } elseif(ZoneRedirect::exists($zoneName) || Zone::exists($zoneName)) { $createError = 'A Zone with this name already exists.'; } elseif(!ctype_alpha($zoneName) || mb_strlen($zoneTitle) > 255) { $createError = 'Invalid data.'; } else { $createZone = Zone::create(UserSession::instance()->getUser(), $zoneName, $zoneTitle); $createZone->addEffect(new \YTKNS\Effects\NewlyCreatedPageEffect); echo html_information('Zone created!', 'Information - YTKNS', "/zones/{$createZone->getId()}"); return; } } } } elseif($reqMethod === 'GET') { $zoneName = filter_input(INPUT_GET, 'name'); } if(isset($createError)) { $createError = Template::renderRaw('error', [ 'error_text' => $createError, ]); } echo html_header(['title' => 'Create a Zone - YTKNS']); Template::render('zones/create', [ 'create_action' => page_url('/zones/create'), 'create_domain' => Config::get('domain.main'), 'create_token' => $createToken, 'create_error' => $createError ?? '', 'create_subdomain' => $zoneName ?? '', 'create_title' => $zoneTitle ?? '', ]); echo html_footer(); return; } if($reqPath === '/zones/_effects') { header('Content-Type: application/json; charset=utf-8'); if(!UserSession::hasInstance()) { http_response_code(403); echo json_encode([ 'err' => 'You must be logged in.', ]); return; } $effects = []; foreach(SITE_EFFECTS as $effectClass) { $instance = new $effectClass; $effects[] = [ 'type' => trim(substr($effectClass, 14, -6), '\\'), 'name' => $instance->getEffectName(), 'props' => $instance->getEffectProperties(), ]; } echo json_encode($effects); return; } if($reqPath === '/zones/_preview') { if(!UserSession::hasInstance()) { http_response_code(403); echo html_information('You must be logged in to do this.'); return; } if($reqMethod !== 'POST') { http_response_code(405); echo html_information('Must be a POST request.'); return; } $zoneToken = filter_input(INPUT_POST, 'zone_token'); if($zoneToken !== UserSession::instance()->getSmallToken(10)) { http_response_code(403); echo html_information('Invalid token.'); return; } $zoneTitle = filter_input(INPUT_POST, 'zone_title'); $zoneEffects = filter_input(INPUT_POST, 'zone_effect', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY); try { $zoneInfo = new Zone; $zoneInfo->setTitle($zoneTitle); $zoneInfo->setPassiveMode(); foreach($zoneEffects as $effectName => $effectValues) { $effectInfo = ZoneEffect::effectClass($effectName); $effectInfo->setEffectParams($effectValues); $zoneInfo->addEffect($effectInfo); } } catch(ZoneInvalidTitleException $ex) { http_response_code(400); echo html_information('Invalid title.'); return; } catch(ZoneEffectClassNotFoundException $ex) { http_response_code(400); echo html_information(sprintf('Invalid effect name: %s.', $ex->getMessage())); return; } catch(PageEffectException $ex) { http_response_code(400); echo html_information(sprintf('%s: %s', $effectName ?? '', $ex->getMessage())); return; } catch(Exception $ex) { http_response_code(500); echo html_information(sprintf('Failed to generate preview.
%s: %s', get_class($ex), $ex->getMessage())); return; } echo (string)$zoneInfo->getPageBuilder(); return; } if(preg_match('#^/zones/([0-9]+)/delete$#', $reqPath, $matches)) { if(!UserSession::hasInstance()) { http_response_code(403); echo html_information('You must be logged in to do this.'); return; } try { $zoneInfo = Zone::byId($matches[1]); } catch(ZoneNotFoundException $ex) { http_response_code(404); echo html_information('This zone doesn\'t exist.'); return; } if($zoneInfo->getUserId() !== UserSession::instance()->getUserId()) { http_response_code(403); echo html_information('You aren\'t allowed to touch this zone.'); return; } $deleteToken = UserSession::instance()->getSmallToken(20); if($reqMethod === 'POST') { if(filter_input(INPUT_POST, 'zone_token') !== $deleteToken) { http_response_code(403); echo html_information('Invalid token.'); return; } header('Location: /zones?f=my'); $zoneInfo->delete(); return; } echo html_header(['title' => sprintf('Deleting zone %s - YTKNS', $zoneInfo->getName())]); Template::render('zones/delete', [ 'delete_zone_id' => $zoneInfo->getId(), 'delete_zone_name' => $zoneInfo->getName(), 'delete_zone_token' => $deleteToken, ]); echo html_footer(); return; } if(preg_match('#^/zones/([0-9]+)/sbs$#', $reqPath, $matches)) { echo ''; echo 'YTKNS Side By Side Editor'; echo ''; echo ''; return; } if(preg_match('#^/zones/([0-9]+)$#', $reqPath, $matches)) { if(!UserSession::hasInstance()) { http_response_code(403); echo html_information('You must be logged in to do this.'); return; } try { $zoneInfo = Zone::byId($matches[1]); } catch(ZoneNotFoundException $ex) { http_response_code(404); echo html_information('This zone doesn\'t exist.'); return; } if(UserSession::instance()->getUserId() !== $zoneInfo->getUserId() && UserSession::instance()->getUserId() !== 1) { http_response_code(403); echo html_information('You aren\'t allowed to touch this zone.'); return; } $isSBS = !empty($_GET['sbs']); $cssHash = hash_file('sha256', YTKNS_PUB . '/assets/editor.css'); $jsHash = hash_file('sha256', YTKNS_PUB . '/assets/editor.js'); if($isSBS) { echo ''; echo ''; echo ''; echo ''; } else { echo html_header([ 'title' => 'Editing Zone - YTKNS', 'styles' => [ page_url('/assets/editor.css', ['v' => $cssHash]), ], ]); } Template::render('zones/edit', [ 'edit_id' => $zoneInfo->getId(), 'edit_token' => UserSession::instance()->getSmallToken(10), 'edit_css_ver' => substr($cssHash, 0, 16), 'edit_js_ver' => substr($jsHash, 0, 16), 'upload_token' => UserSession::instance()->getSmallToken(6), ]); if($isSBS) { echo ''; } else { echo << window.addEventListener('DOMContentLoaded', function() { if(window.location !== window.parent.location) location.assign(location.toString() + '?sbs=1'); }); HTML; echo html_footer([ 'scripts' => [ page_url('/assets/editor.js', ['v' => $jsHash]), ], ]); } return; } if(preg_match('#^/zones/([0-9]+).json$#', $reqPath, $matches)) { header('Content-Type: application/json; charset=utf-8'); if(!UserSession::hasInstance()) { http_response_code(403); echo json_encode([ 'err' => 'You must be logged in to do this.', ]); return; } try { $zoneInfo = Zone::byId($matches[1]); } catch(ZoneNotFoundException $ex) { http_response_code(404); echo json_encode([ 'err' => 'This zone doesn\'t exist', ]); return; } if(UserSession::instance()->getUserId() !== $zoneInfo->getUserId() && UserSession::instance()->getUserId() !== 1) { http_response_code(403); echo json_encode([ 'err' => 'You aren\'t allowed to touch this zone.', ]); return; } if($reqMethod === 'GET') { echo $zoneInfo->toJson(true); return; } if($reqMethod !== 'POST') { http_response_code(405); echo json_encode([ 'err' => 'Invalid request method.', ]); return; } $zoneToken = filter_input(INPUT_POST, 'zone_token'); if($zoneToken !== UserSession::instance()->getSmallToken(10)) { http_response_code(403); echo json_encode([ 'err' => 'Invalid token.', ]); return; } $zoneId = filter_input(INPUT_POST, 'zone_id', FILTER_VALIDATE_INT); if($zoneId !== $zoneInfo->getId()) { http_response_code(400); echo json_encode([ 'err' => 'Zone ID in POST data does not match the target zone ID.', ]); return; } $zoneTitle = filter_input(INPUT_POST, 'zone_title'); $zoneEffects = filter_input(INPUT_POST, 'zone_effect', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY); try { $zoneInfo->setTitle($zoneTitle); $zoneInfo->setPassiveMode(); if(is_array($zoneEffects)) { foreach($zoneEffects as $effectName => $effectValues) { $effectInfo = ZoneEffect::effectClass($effectName); $effectInfo->setEffectParams($effectValues); $zoneInfo->addEffect($effectInfo); } } $zoneInfo->update(['zone_title']); // Schedule a screenshot to be taken $zoneInfo->queueTask('screenshot'); } catch(ZoneInvalidTitleException $ex) { http_response_code(400); echo json_encode([ 'err' => 'Invalid title.', ]); return; } catch(ZoneEffectClassNotFoundException $ex) { http_response_code(400); echo json_encode([ 'err' => sprintf('Invalid effect name: %s.', $ex->getMessage()), ]); return; } catch(PageEffectException $ex) { http_response_code(400); echo json_encode([ 'err' => sprintf('%s: %s', $effectName ?? '', $ex->getMessage()), ]); return; } catch(Exception $ex) { http_response_code(500); echo json_encode([ 'err' => sprintf("An unexpected error occurred.\r\n%s: %s", get_class($ex), $ex->getMessage()), ]); return; } echo json_encode([ 'msg' => 'Saved!', ]); return; } if($reqPath === '/uploads') { header('Content-Type: application/json; charset=utf-8'); if($reqMethod !== 'POST') { http_response_code(405); echo json_encode([ 'err' => 'Unsupported request method.', ]); return; } if(!UserSession::hasInstance()) { http_response_code(403); echo json_encode([ 'err' => 'You must be logged in to upload files.', ]); return; } if(filter_input(INPUT_POST, 'upload_token') !== UserSession::instance()->getSmallToken(6)) { http_response_code(403); echo json_encode([ 'err' => 'Invalid upload token.', ]); return; } if(empty($_FILES['upload_file']['tmp_name'])) { http_response_code(400); echo json_encode([ 'err' => 'Missing file.', ]); return; } $hash = hash_file('sha256', $_FILES['upload_file']['tmp_name']); $existing = Upload::byHash($hash); if($existing !== null) { if($existing->getDMCA() > 0) { http_response_code(451); echo json_encode([ 'err' => 'This file has been removed in response to a DMCA takedown request. It may not be used.', ]); return; } if($existing->getDeleted() > 0) { http_response_code(404); echo json_encode([ 'err' => 'This file has been flagged for deletion, it cannot be reuploaded at this time.', ]); return; } http_response_code(200); echo json_encode([ 'err' => 'File has been uploaded already.', 'file' => $existing->getId(), ]); return; } if($_FILES['upload_file']['size'] > 5242880) { http_response_code(413); echo json_encode([ 'err' => 'Upload is too large.', ]); return; } $contentType = mime_content_type($_FILES['upload_file']['tmp_name']); if(!in_array($contentType, ALLOWED_UPLOADS)) { http_response_code(400); echo json_encode([ 'err' => sprintf('File type not allowed. (Must be %s)', implode(', ', ALLOWED_UPLOADS)), ]); return; } try { $upload = Upload::create(UserSession::instance()->getUser(), $_FILES['upload_file']['name'], $contentType, $hash); } catch(UploadCreationFailedException $ex) { http_response_code(500); echo json_encode([ 'err' => 'Failed to create upload record.', ]); return; } if(!move_uploaded_file($_FILES['upload_file']['tmp_name'], $upload->getPath())) { http_response_code(500); echo json_encode([ 'err' => 'Upload failed.', ]); return; } http_response_code(201); echo json_encode([ 'file' => $upload->getId(), ]); return; } if(preg_match('#^/uploads/([a-zA-Z0-9-_]{16})$#', $reqPath, $matches)) { try { $uploadInfo = Upload::byId($matches[1]); } catch(UploadNotFoundException $ex) { http_response_code(404); return; } if(!empty($_SERVER['HTTP_ORIGIN'])) { $zoneDomain = sprintf(Config::get('domain.zone'), ''); $originPart = substr($_SERVER['HTTP_ORIGIN'], strlen($_SERVER['HTTP_ORIGIN']) - strlen($zoneDomain)); if($originPart === $zoneDomain) { header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); } } if($_SERVER['REQUEST_METHOD'] !== 'HEAD' && $_SERVER['REQUEST_METHOD'] !== 'OPTIONS') header('X-Accel-Redirect: /raw-uploads/' . $uploadInfo->getId()); header('Content-Type: ' . $uploadInfo->getType()); header('Content-Disposition: inline; filename="' . $uploadInfo->getName() . '"'); return; } if(preg_match('#^/uploads/([a-zA-Z0-9-_]{16}).json$#', $reqPath, $matches)) { header('Content-Type: application/json; charset=utf-8'); if(!UserSession::hasInstance()) { http_response_code(403); echo json_encode([ 'err' => 'You must be logged in to do this.', ]); return; } try { $uploadInfo = Upload::byId($matches[1]); } catch(UploadNotFoundException $ex) { http_response_code(404); echo json_encode([ 'err' => 'Upload not found.', ]); return; } echo $uploadInfo->toJson(true); return; } if($reqPath === '/settings') { if(!UserSession::hasInstance()) { http_response_code(403); echo html_information('You must be logged in to access this page.'); return; } echo html_header(['title' => 'Settings - YTKNS']); Template::render('settings/index'); echo html_footer(); return; } if($reqPath === '/auth/login') { if(YTKNS_MAINTENANCE) { http_response_code(503); echo html_information('You cannot log in during maintenance.'); return; } if(UserSession::hasInstance()) { http_response_code(404); echo html_information('You are logged in already.'); return; } if(filter_has_var(INPUT_GET, 'state')) { $state = base64uri_decode((string)filter_input(INPUT_GET, 'state')); if($state === false || strlen($state) !== 60) { http_response_code(400); echo html_information('Provided state is invalid.'); return; } $signature = hash_hmac('sha256', substr($state, 32), YTKNS_OA2_STATE_SECRET, true); if(!hash_equals($signature, substr($state, 0, 32))) { http_response_code(403); echo html_information('Request verification failed.'); return; } $state = unpack('a32hash/Jtime/a20verifier', $state); if($state === false) { http_response_code(500); echo html_information('State unpack failed.'); return; } if($state['time'] < strtotime('-15 minutes') || $state['time'] > strtotime('+15 minutes')) { http_response_code(403); echo html_information('Authorisation request timestamp is no longer valid.'); return; } if(filter_has_var(INPUT_GET, 'error')) { $error = (string)filter_input(INPUT_GET, 'error'); $text = (string)filter_input(INPUT_GET, 'error_description'); if($text === '') $text = match($error) { 'access_denied' => 'You rejected the authorisation request.', 'invalid_request' => 'YTKNS sent an invalid request to the authorisation server, please report this!', 'invalid_scope' => 'YTKNS sent an invalid request to the authorisation server, please report this!', 'server_error' => 'Something went wrong on the authorisation server, please try again later.', default => sprintf('An unexpected error occurred: %s', $error), }; http_response_code(400); echo html_information(htmlspecialchars($text)); return; } if(filter_has_var(INPUT_GET, 'code')) { try { $postData = sprintf( 'grant_type=authorization_code&code=%s&code_verifier=%s', rawurlencode((string)filter_input(INPUT_GET, 'code')), rawurldecode($state['verifier']) ); $authz = sprintf('Basic %s', base64_encode(sprintf('%s:%s', YTKNS_OA2_CLIENT_ID, YTKNS_OA2_CLIENT_SECRET))); $tokenInfo = json_decode(file_get_contents('https://api.flashii.net/oauth2/token', false, stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => implode("\r\n", [ sprintf('Authorization: %s', $authz), 'Content-Type: application/x-www-form-urlencoded', sprintf('Content-Length: %d', strlen($postData)), 'User-Agent: YTKNS', ]), 'content' => $postData, ], ]))); if(isset($tokenInfo->access_token)) { $fUserInfo = json_decode(file_get_contents('https://api.flashii.net/v1/me', false, stream_context_create([ 'http' => [ 'method' => 'GET', 'header' => implode("\r\n", [ sprintf('Authorization: Bearer %s', $tokenInfo->access_token), 'User-Agent: YTKNS', ]), ], ]))); if(empty($fUserInfo->id)) { http_response_code(500); echo html_information('Authentication failed.'); return; } try { $userInfo = User::byRemoteId($fUserInfo->id); $loginMessage = 'You are now logged in!'; } catch(UserNotFoundException) { try { $userInfo = User::create($fUserInfo->id, $fUserInfo->name); } catch(\PDOException) { $userInfo = User::create($fUserInfo->id, sprintf('%s_%04d', $fUserInfo->name, random_int(0, 9999))); } $loginMessage = 'Your account been created!'; } // leaving session bumping off for now, the implementation needs to be better for that $session = UserSession::create($userInfo, false); $session->setInstance(); setcookie('ytkns_login', $session->getToken(), $session->getExpires(), '/', '.' . Config::get('domain.main'), false, true); echo html_information($loginMessage, 'Welcome', '/'); return; } } catch(\Exception $ex) { http_response_code(500); echo html_information('Authorisation request failed, please try again.'); } return; } } $verifier = random_bytes(20); $time = pack('J', time()); $signature = hash_hmac('sha256', $time . $verifier, YTKNS_OA2_STATE_SECRET, true); $state = base64uri_encode($signature . $time . $verifier); header(sprintf( 'Location: https://id.flashii.net/oauth2/authorise?response_type=code&scope=identify&code_challenge_method=S256&client_id=%s&state=%s&code_challenge=%s&redirect_uri=%s', rawurlencode(YTKNS_OA2_CLIENT_ID), rawurlencode($state), rawurlencode(base64uri_encode(hash('sha256', $verifier, true))), rawurlencode('https://ytkns.com/auth/login'), )); return; } if($reqPath === '/auth/logout') { if(!UserSession::hasInstance()) { http_response_code(404); echo html_information('You are not logged in.'); return; } if(filter_input(INPUT_GET, 's') !== UserSession::instance()->getSmallToken()) { http_response_code(403); echo html_information('Log out verification failed, please try again.'); return; } UserSession::instance()->destroy(); UserSession::unsetInstance(); echo html_information('You have been logged out.', 'Log out', '/'); return; } http_response_code(404); echo html_information('The requested page does not exist.', 'Page not found');