';
}
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(
' ',
$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');