feat(OAuth): add oauth flow

This commit is contained in:
celestora 2024-03-30 13:56:38 +02:00
parent 0b80c0a6a8
commit 2bdb4f03d0
5 changed files with 251 additions and 0 deletions

View file

@ -318,4 +318,42 @@ final class VKAPIPresenter extends OpenVKPresenter
header("Content-Length: $size"); header("Content-Length: $size");
exit($payload); exit($payload);
} }
function renderOAuthLogin() {
$this->assertUserLoggedIn();
$client = $this->queryParam("client_name");
$postmsg = $this->queryParam("prefers_postMessage") ?? '0';
$stale = $this->queryParam("accepts_stale") ?? '0';
$origin = NULL;
$url = $this->queryParam("redirect_uri");
if(is_null($url) || is_null($client))
exit("<b>Error:</b> redirect_uri and client_name params are required.");
if($url != "about:blank") {
if(!filter_var($url, FILTER_VALIDATE_URL))
exit("<b>Error:</b> Invalid URL passed to redirect_uri.");
$parsedUrl = (object) parse_url($url);
if($parsedUrl->scheme != 'https' && $parsedUrl->scheme != 'http')
exit("<b>Error:</b> redirect_uri should either point to about:blank or to a web resource.");
$origin = "$parsedUrl->scheme://$parsedUrl->host";
if(!is_null($parsedUrl->port ?? NULL))
$origin .= ":$parsedUrl->port";
$url .= strpos($url, '?') === false ? '?' : '&';
} else {
$url .= "#";
if($postmsg == '1') {
exit("<b>Error:</b> prefers_postMessage can only be set if redirect_uri is not about:blank");
}
}
$this->template->clientName = $client;
$this->template->usePostMessage = $postmsg == '1';
$this->template->acceptsStale = $stale == '1';
$this->template->origin = $origin;
$this->template->redirectUri = $url;
}
} }

View file

@ -0,0 +1,193 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Получение доступа | OpenVK</title>
<style>
body {
margin: 0;
font-family: sans-serif;
background-color: #ebedf0;
}
header {
background-color: #fff;
box-shadow: 0 0 10px #d4d6d8;
padding: 13px 0;
margin-bottom: 12px;
}
header > div {
display: flex;
width: 100%;
max-width: 620px;
margin: auto;
align-items: center;
justify-content: space-between;
}
#ovkUser a:not(#pfpLink) {
color: #000;
text-decoration: none;
font-weight: 600;
font-size: 14px;
margin-right: 4px;
}
#ovkUser img {
width: 32px;
height: 32px;
border-radius: 100%;
vertical-align: middle;
object-fit: cover;
}
#ovkLogo a {
display: flex;
text-decoration: none;
color: #fff;
background: #606060;
width: 28px;
height: 28px;
justify-content: center;
align-items: center;
font-size: 21px;
font-weight: 600;
border-radius: 7px;
}
body > div {
width: 100%;
max-width: 620px;
margin: auto;
}
main {
border: 1px solid #e1e3e6;
background-color: #fff;
padding: 16px;
border-radius: 18px;
font-size: 15px;
}
#authHeading, #authExplainer {
color: #818c99;
padding-bottom: 12px;
}
:is(#authHeading, #authExplainer) b {
color: #000;
}
#authButtons {
display: flex;
gap: 10px;
align-items: center;
}
#authButtons a {
color: #818c99;
text-decoration: none;
}
#authButtons a:hover {
text-decoration: underline;
}
#authButtons button {
cursor: pointer;
appearance: none;
border: none;
background-color: #606060;
color: #fff;
font-weight: 600;
padding: 7px;
border-radius: 7px;
font-size: 15px;
}
</style>
</head>
<body>
<header>
<div>
<div style="width: 85px;">
</div>
<div id="ovkLogo">
<a href="/" target="_blank">O</a>
</div>
<div id="ovkUser">
<a href="/logout?hash={rawurlencode($csrfToken)}">{_header_log_out}</a>
<a id="pfpLink" href="/id0" target="_blank">
<img src="{$thisUser->getAvatarUrl('miniscule')}" />
</a>
</div>
</div>
</header>
<div>
<main>
<div id="authHeading">
{_app},
<b>
{if is_null($origin)}
{tr("identifies_itself_as", $clientName)}{else}
{tr("located_at_url", $origin)}{/if}</b>, {_wants_your_token}.
</div>
<div id="authExplainer">
<b>{_app_will_have_access_to}</b><br/>
{_oauth_scope_all|noescape}.
</div>
<div id="authButtons">
<button id="authAllow">{_oauth_grant}</button>
<a id="authDeny" href="javascript:void(0)">{_oauth_deny}</a>
</div>
</main>
</div>
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}
{script "js/al_api.js"}
<script>
//<![CDATA[
let clientName = {$clientName};
let usePostMessage = {$usePostMessage} && window.opener != null;
let acceptsStale = {$acceptsStale};
let origin = {$origin};
let redirectUri = {$redirectUri};
document.querySelector("#authDeny").addEventListener("click", () => {
if (usePostMessage) {
window.opener.postMessage({
error: 'access_denied',
error_reason: 'user_denied',
error_description: 'User denied your request'
}, origin);
return;
}
window.location.href = redirectUri + 'error=access_denied&error_reason=user_denied&error_description=User%20denied%20your%20request';
});
document.querySelector("#authAllow").addEventListener("click", async () => {
let response = await API.Apps.getRegularToken(clientName, acceptsStale);
let ret = {
access_token: response.token,
expires_in: 0,
user_id: {$thisUser->getId()},
is_stale: response.is_stale
};
if (usePostMessage) {
window.opener.postMessage(ret, origin);
return;
}
window.location.href = redirectUri + (new URLSearchParams(ret)).toString();
});
//]]>
</script>
</body>
</html>

View file

@ -373,6 +373,8 @@ routes:
handler: "VKAPI->route" handler: "VKAPI->route"
- url: "/token" - url: "/token"
handler: "VKAPI->tokenLogin" handler: "VKAPI->tokenLogin"
- url: "/authorize"
handler: "VKAPI->OAuthLogin"
- url: "/admin/sandbox" - url: "/admin/sandbox"
handler: "About->sandbox" handler: "About->sandbox"
- url: "/admin/chandler/groups" - url: "/admin/chandler/groups"

View file

@ -775,6 +775,15 @@
"disable_2fa" = "Turn off 2FA"; "disable_2fa" = "Turn off 2FA";
"viewing" = "View"; "viewing" = "View";
/* OAuth */
"identifies_itself_as" = "that identifies itself as $1";
"located_at_url" = "located at $1";
"wants_your_token" = "wants to access your account";
"app_will_have_access_to" = "App will have access to:";
"oauth_scope_all" = "profile information, status, list of friends, photos, wall posts, audios, videos, notifications, fishing rod handle, messages, gifts, <b>your e-mail</b>, polls, communities, discussions, notes, <b>payment method</b>, likes and comments";
"oauth_grant" = "Allow";
"oauth_deny" = "Deny";
/* Sorting */ /* Sorting */
"sort_randomly" = "Sort randomly"; "sort_randomly" = "Sort randomly";

View file

@ -737,6 +737,15 @@
"disable_2fa" = "Отключить 2FA"; "disable_2fa" = "Отключить 2FA";
"viewing" = "Просмотреть"; "viewing" = "Просмотреть";
/* OAuth */
"identifies_itself_as" = "идентифицирующее себя как $1";
"located_at_url" = "располагающееся по адресу $1";
"wants_your_token" = "запрашивает доступ к вашему аккаунту";
"app_will_have_access_to" = "Приложению будут доступны:";
"oauth_scope_all" = "информация страницы, обновление статуса, список друзей, фотографии, публикация записей, аудиозаписи, видео, уведомления, сообщения, подарки, <b>ваш адрес электронной почты</b>, опросы, группы, обсуждения, заметки, <b>голоса</b>, лайки и комментарии";
"oauth_grant" = "Разрешить";
"oauth_deny" = "Отмена";
/* Sorting */ /* Sorting */
"sort_randomly" = "Сортировать случайно"; "sort_randomly" = "Сортировать случайно";