diff --git a/assets/hanyuu.css/main.css b/assets/hanyuu.css/main.css new file mode 100644 index 0000000..0480458 --- /dev/null +++ b/assets/hanyuu.css/main.css @@ -0,0 +1,49 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + position: relative; +} + +html, body { + width: 100%; + height: 100%; +} + +[hidden], +.hidden { + display: none !important; +} + +:root { + --font-regular: Verdana, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif; + --font-monospace: Consolas, 'Liberation Mono', Menlo, Courier, monospace; +} + +body { + background-color: #111; + color: #fff; + font-size: 16px; + line-height: 25px; + font-family: var(--font-regular); + overflow-y: scroll; + position: static; + display: flex; + flex-direction: column; +} + +pre, code { + font-family: var(--font-monospace); +} + +a { + color: #1e90ff; + text-decoration: none; +} +a:visited { + color: #6B4F80; +} +a:hover, +a:focus { + text-decoration: underline; +} diff --git a/assets/oauth2.css/appinfo.css b/assets/oauth2.css/appinfo.css new file mode 100644 index 0000000..5dd35da --- /dev/null +++ b/assets/oauth2.css/appinfo.css @@ -0,0 +1,43 @@ +.oauth2-appinfo {} + +.oauth2-appinfo-name { + font-size: 2em; + line-height: 1.5em; +} + +.oauth2-appinfo-links { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.oauth2-appinfo-link { + display: flex; + color: inherit; + gap: 5px; + align-items: center; + background: #333; + padding: 2px 6px; + border-radius: 4px; +} +.oauth2-appinfo-link-icon { + flex: 0 0 auto; + background-color: #fff; + width: 12px; + height: 12px; +} +.oauth2-appinfo-link-icon-globe { + mask: url('/images/globe-solid.svg') no-repeat center; +} +.oauth2-appinfo-link-text { + font-size: .8em; + line-height: 1.4em; +} + +.oauth2-appinfo-summary { + font-size: .9em; + line-height: 1.4em; +} +.oauth2-appinfo-summary p { + margin: .5em 0; +} diff --git a/assets/oauth2.css/authorise.css b/assets/oauth2.css/authorise.css new file mode 100644 index 0000000..533b71a --- /dev/null +++ b/assets/oauth2.css/authorise.css @@ -0,0 +1,57 @@ +.oauth2-authorise-requesting { + font-size: .8em; + line-height: 1.4em; + border-bottom: 1px solid #333; +} +.oauth2-authorise-requesting p { + margin: .5em 0; +} + +.oauth2-authorise-buttons { + margin-top: 10px; + display: flex; + justify-content: center; + gap: 10px; +} + +.oauth2-authorise-button { + background-color: #191919; + font-family: var(--font-regular); + font-size: 1.2em; + line-height: 1.4em; + padding: 5px 10px; + min-width: 140px; + text-align: center; + cursor: pointer; + transition: color .2s, background-color .2s, opacity .2s; + border: 1px solid; + border-radius: 2px; + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; +} + +.oauth2-authorise-button[disabled] { + opacity: .5; +} + +.oauth2-authorise-button-accept { + color: #080; + border-color: #0a0; +} +.oauth2-authorise-button-accept:hover, +.oauth2-authorise-button-accept:focus { + color: #191919; + background-color: #0a0; +} + +.oauth2-authorise-button-deny { + color: #c00; + border-color: #a00; +} +.oauth2-authorise-button-deny:hover, +.oauth2-authorise-button-deny:focus { + color: #191919; + background-color: #a00; +} diff --git a/assets/oauth2.css/banner.css b/assets/oauth2.css/banner.css new file mode 100644 index 0000000..cff6f67 --- /dev/null +++ b/assets/oauth2.css/banner.css @@ -0,0 +1,21 @@ +.oauth2-banner { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; +} + +.oauth2-banner-text { + font-size: .9em; + line-height: 1.4em; + flex: 1 1 auto; +} +.oauth2-banner-logo { + flex: 0 0 auto; + background-color: #fff; + mask: url('/images/flashii.svg') no-repeat center; + width: 30px; + height: 30px; + font-size: 0; +} + diff --git a/assets/oauth2.css/error.css b/assets/oauth2.css/error.css new file mode 100644 index 0000000..2668cf1 --- /dev/null +++ b/assets/oauth2.css/error.css @@ -0,0 +1,22 @@ +.oauth2-errorhead { + display: flex; + align-items: center; +} + +.oauth2-errorhead-icon { + flex: 0 0 auto; + background-color: #fff; + mask: url('/images/circle-exclamation-solid.svg') no-repeat center; + width: 40px; + height: 40px; + margin: 10px; +} + +.oauth2-errorhead-text { + font-size: 1.8em; + line-height: 1.4em; +} + +.oauth2-errorbody p { + margin: .5em 1em; +} diff --git a/assets/oauth2.css/login.css b/assets/oauth2.css/login.css new file mode 100644 index 0000000..2dd595e --- /dev/null +++ b/assets/oauth2.css/login.css @@ -0,0 +1,18 @@ +.oauth2-loginhead { + display: flex; + align-items: center; +} + +.oauth2-loginhead-icon { + flex: 0 0 auto; + background-color: #fff; + mask: url('/images/user-lock-solid.svg') no-repeat center; + width: 40px; + height: 40px; + margin: 10px; +} + +.oauth2-loginhead-text { + font-size: 1.8em; + line-height: 1.4em; +} diff --git a/assets/oauth2.css/main.css b/assets/oauth2.css/main.css index a29618a..928a75b 100644 --- a/assets/oauth2.css/main.css +++ b/assets/oauth2.css/main.css @@ -1,29 +1,44 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; - position: relative; +.oauth2-wrapper { + display: flex; + flex-direction: column; + flex: 1 0 auto; + margin-bottom: 10px; } -html, body { +.oauth2-dialog { + display: flex; + flex: 1 0 auto; + padding: 10px; width: 100%; - height: 100%; + align-items: center; + justify-content: center; } -body { - background-color: #111; - color: #fff; - font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif; +.oauth2-dialog-body { + max-width: 500px; + width: 100%; + background: #191919; + box-shadow: 0 1px 2px #0009; + display: flex; + flex-direction: column; } -@media (prefers-color-scheme: light) { - body { - background-color: #ddd; - color: #000; - } +.oauth2-header { + background-image: url('/images/clouds.png'); + background-blend-mode: multiply; + background-color: #8559a5; + width: 100%; + min-height: 4px; } -[hidden], -.hidden { - display: none !important; +.oauth2-body { + margin: 10px; } + +@include banner.css; +@include error.css; +@include login.css; +@include userhead.css; +@include appinfo.css; +@include scope.css; +@include authorise.css; diff --git a/assets/oauth2.css/scope.css b/assets/oauth2.css/scope.css new file mode 100644 index 0000000..b35d6dd --- /dev/null +++ b/assets/oauth2.css/scope.css @@ -0,0 +1,34 @@ +.oauth2-scope { + background: #292929; + border-radius: 4px; + padding: 4px 8px; +} + +.oauth2-scope-header { + border-bottom: 1px solid #494949; +} + +.oauth2-scope-perms { + display: flex; + flex-direction: column; + gap: 4px; + margin: 4px 0; +} + +.oauth2-scope-perm { + display: flex; + align-items: center; + gap: 4px; +} +.oauth2-scope-perm-icon { + width: 16px; + height: 16px; + mask: url('/images/circle-check-regular.svg') no-repeat center; + flex: 0 0 auto; + background-color: #0a0; + margin: 2px; +} +.oauth2-scope-perm-text { + font-size: .8em; + line-height: 1.4em; +} diff --git a/assets/oauth2.css/userhead.css b/assets/oauth2.css/userhead.css new file mode 100644 index 0000000..b8e5efe --- /dev/null +++ b/assets/oauth2.css/userhead.css @@ -0,0 +1,64 @@ +.oauth2-userhead { + display: flex; + flex-direction: column; + background-color: #0005; +} + +.oauth2-userhead-main { + display: flex; + align-items: center; +} +.oauth2-userhead-main-avatar { + flex: 0 0 auto; + margin: 10px; +} +.oauth2-userhead-main-avatar-image { + width: 60px; + height: 60px; + overflow: hidden; + border-radius: 4px; +} +.oauth2-userhead-main-avatar-image img { + width: 100%; + height: 100%; + object-fit: cover; + border: 0; +} +.oauth2-userhead-main-name { + font-size: 1.8em; + line-height: 1.4em; +} +.oauth2-userhead-main-name a { + color: inherit; +} + +.oauth2-userhead-guise { + display: flex; + align-items: center; + background-image: repeating-linear-gradient(-45deg, #8559a57f, #8559a57f 10px, #1111117f 10px, #1111117f 20px); +} +.oauth2-userhead-guise-avatar { + flex: 0 0 auto; + margin: 10px; +} +.oauth2-userhead-guise-avatar-image { + width: 30px; + height: 30px; + overflow: hidden; + border-radius: 4px; +} +.oauth2-userhead-guise-avatar-image img { + width: 100%; + height: 100%; + object-fit: cover; + border: 0; +} +.oauth2-userhead-guise-text { + font-size: .8em; + line-height: 1.5em; + overflow: hidden; + padding: 2px 0; +} +.oauth2-userhead-guise-text p { + margin: 1px 0; +} diff --git a/assets/oauth2.js/main.js b/assets/oauth2.js/main.js index ea03b5c..503dc17 100644 --- a/assets/oauth2.js/main.js +++ b/assets/oauth2.js/main.js @@ -1 +1,32 @@ -/* beans */ +(() => { + const authoriseButtons = document.querySelectorAll('.js-authorise-action'); + + for(const button of authoriseButtons) { + button.disabled = false; + + button.onclick = () => { + for(const other of authoriseButtons) + other.disabled = true; + + const body = []; + for(const name in button.dataset) + body.push(encodeURIComponent(name) + '=' + encodeURIComponent(button.dataset[name])); + + const xhr = new XMLHttpRequest; + xhr.responseType = 'json'; + xhr.open('POST', '/oauth2/authorise'); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.onload = () => { + if(xhr.response.redirect) + location.assign(xhr.response.redirect); + else + location.assign('/oauth2/error?error=invalid_request'); + }; + xhr.onerror = () => { + for(const other of authoriseButtons) + other.disabled = false; + }; + xhr.send(body.join('&')); + }; + } +})(); diff --git a/build.js b/build.js index 3142bac..eb496a2 100644 --- a/build.js +++ b/build.js @@ -21,6 +21,7 @@ const fs = require('fs'); ], css: [ { source: 'errors.css', target: '/', name: 'errors.css', }, + { source: 'hanyuu.css', target: '/assets', name: 'hanyuu.{hash}.css', }, { source: 'oauth2.css', target: '/assets', name: 'oauth2.{hash}.css', }, ], twig: [ diff --git a/hanyuu.cfg.example b/hanyuu.cfg.example index 7a41da7..6730e75 100644 --- a/hanyuu.cfg.example +++ b/hanyuu.cfg.example @@ -9,3 +9,5 @@ misuzu:secret beans oauth2:device:verification_uri https://hau.local/oauth2/device oauth2:device:verification_uri_complete https://hau.local/oauth2/device?code=%s + +csrfp:secret change this please diff --git a/public/images/circle-check-regular.svg b/public/images/circle-check-regular.svg new file mode 100644 index 0000000..0558987 --- /dev/null +++ b/public/images/circle-check-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/circle-exclamation-solid.svg b/public/images/circle-exclamation-solid.svg new file mode 100644 index 0000000..99a68d7 --- /dev/null +++ b/public/images/circle-exclamation-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/clouds.png b/public/images/clouds.png new file mode 100644 index 0000000..4191cd7 Binary files /dev/null and b/public/images/clouds.png differ diff --git a/public/images/flashii.svg b/public/images/flashii.svg new file mode 100644 index 0000000..24f9503 --- /dev/null +++ b/public/images/flashii.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/globe-solid.svg b/public/images/globe-solid.svg new file mode 100644 index 0000000..c2af6c6 --- /dev/null +++ b/public/images/globe-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/user-lock-solid.svg b/public/images/user-lock-solid.svg new file mode 100644 index 0000000..74f93a1 --- /dev/null +++ b/public/images/user-lock-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Apps/AppInfo.php b/src/Apps/AppInfo.php index d2b7197..1d26a8f 100644 --- a/src/Apps/AppInfo.php +++ b/src/Apps/AppInfo.php @@ -49,6 +49,13 @@ class AppInfo { public function getWebsite(): string { return $this->website; } + public function getWebsiteDisplay(): string { + $website = $this->website; + if(str_starts_with($website, 'https://')) + $website = substr($website, 8); + + return rtrim($website, '/'); + } public function isTrusted(): bool { return $this->trusted; diff --git a/src/HanyuuContext.php b/src/HanyuuContext.php index 635fa8f..38f2659 100644 --- a/src/HanyuuContext.php +++ b/src/HanyuuContext.php @@ -68,6 +68,10 @@ class HanyuuContext { return $this->authInfo; } + public function getCSRFPSecret(): string { + return $this->config->getString('csrfp:secret', 'beans'); + } + public function getWebAssetInfo(): ?object { $path = HAU_DIR_ASSETS . '/current.json'; return is_file($path) ? json_decode(file_get_contents($path)) : null; @@ -81,7 +85,12 @@ class HanyuuContext { $routingCtx = new RoutingContext($this->getTemplating()); $routingCtx->getRouter()->use('/', function($response, $request) { - $this->authInfo = $this->misuzuInterop->authCheck('Misuzu', (string)$request->getCookie('msz_auth'), $_SERVER['REMOTE_ADDR']); + $this->authInfo = $this->misuzuInterop->authCheck( + 'Misuzu', + (string)$request->getCookie('msz_auth'), + $_SERVER['REMOTE_ADDR'], + [60] + ); }); $routingCtx->getRouter()->get('/', function($response, $request) { @@ -93,7 +102,8 @@ class HanyuuContext { $this->oauth2Ctx, $this->appsCtx, $this->templating, - $this->getAuthInfo(...) + $this->getAuthInfo(...), + $this->getCSRFPSecret(...) )); return $routingCtx; diff --git a/src/OAuth2/OAuth2AuthoriseData.php b/src/OAuth2/OAuth2AuthoriseData.php index 804000a..b24c726 100644 --- a/src/OAuth2/OAuth2AuthoriseData.php +++ b/src/OAuth2/OAuth2AuthoriseData.php @@ -66,9 +66,13 @@ class OAuth2AuthoriseData { string $state, string $challengeCode, string $challengeMethod, - string $scope + string $scope, + ?string $code = null, + bool $preapprove = false ): OAuth2AuthoriseInfo { - $stmt = $this->cache->get('INSERT INTO hau_oauth2_authorise (app_id, user_id, uri_id, auth_state, auth_challenge_code, auth_challenge_method, auth_scope, auth_code) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'); + $code ??= XString::random(60); + + $stmt = $this->cache->get('INSERT INTO hau_oauth2_authorise (app_id, user_id, uri_id, auth_state, auth_challenge_code, auth_challenge_method, auth_scope, auth_code, auth_approval) VALUES (?, ?, ?, ?, ?, ?, ?, ?, IF(?, "approved", DEFAULT(auth_approval)))'); $stmt->addParameter(1, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo); $stmt->addParameter(2, $userId); $stmt->addParameter(3, $appUriInfo instanceof AppUriInfo ? $appUriInfo->getId() : $appUriInfo); @@ -76,7 +80,8 @@ class OAuth2AuthoriseData { $stmt->addParameter(5, $challengeCode); $stmt->addParameter(6, $challengeMethod); $stmt->addParameter(7, $scope); - $stmt->addParameter(8, XString::random(60)); + $stmt->addParameter(8, $code); + $stmt->addParameter(9, $preapprove ? 1 : 0); $stmt->execute(); return $this->getAuthoriseInfo(authoriseId: (string)$this->dbConn->getLastInsertId()); diff --git a/src/OAuth2/OAuth2Routes.php b/src/OAuth2/OAuth2Routes.php index 13c0cbd..c57c247 100644 --- a/src/OAuth2/OAuth2Routes.php +++ b/src/OAuth2/OAuth2Routes.php @@ -5,6 +5,7 @@ use InvalidArgumentException; use RuntimeException; use Index\XString; use Index\Http\Routing\{HttpGet,HttpOptions,HttpPost,RouteHandler}; +use Index\Security\CSRFP; use Sasae\SasaeEnvironment; use Syokuhou\IConfig; use Hanyuu\Apps\AppsContext; @@ -15,7 +16,8 @@ final class OAuth2Routes extends RouteHandler { private OAuth2Context $oauth2Ctx, private AppsContext $appsCtx, private SasaeEnvironment $templating, - private $getAuthInfo + private $getAuthInfo, + private $getCSRFPSecret ) { if(!is_callable($getAuthInfo)) throw new InvalidArgumentException('$getAuthInfo must be callable'); @@ -45,9 +47,55 @@ final class OAuth2Routes extends RouteHandler { return $scopes; } + private const AUTHORISE_ERRORS = [ + 'invalid_request' => [ + 'description' => 'The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.', + 'allow_description' => true, + ], + 'unauthorized_client' => [ + 'description' => 'The client is not authorised to request an authorisation code using this method.', + ], + 'access_denied' => [ + 'description' => 'The resource owner or authorization server denied the request.', + ], + 'unsupported_response_type' => [ + 'description' => 'The authorisation server does not support obtaining an authorisation code using this method.', + ], + 'invalid_scope' => [ + 'description' => 'The requested scope is invalid, unknown, or malformed.', + ], + 'server_error' => [ + 'status' => 500, + 'description' => 'The authorisation server encountered an unexpected condition that prevented it from fulfilling the request.', + ], + 'temporarily_unavailable' => [ + 'status' => 503, + 'description' => 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.', + ], + ]; + #[HttpGet('/oauth2/error')] public function getError($response, $request) { - // + $error = (string)$request->getParam('error'); + if(!array_key_exists($error, self::AUTHORISE_ERRORS)) + return 404; + + $info = self::AUTHORISE_ERRORS[$error]; + + $description = $info['description']; + if($request->hasParam('error_description') && array_key_exists('allow_description', $info) && $info['allow_description']) + $description = $request->getParam('error_description'); + + $statusCode = 400; + if(array_key_exists('status', $info)) + $statusCode = $info['status']; + + $response->setStatusCode($statusCode); + + return $this->templating->render('oauth2/error', [ + 'error_code' => $error, + 'error_description' => $description, + ]); } #[HttpGet('/oauth2/authorise')] @@ -172,7 +220,8 @@ final class OAuth2Routes extends RouteHandler { $state, $codeChallenge, $codeChallengeMethod, - $scope + $scope, + preapprove: $appInfo->isTrusted() ); } catch(RuntimeException $ex) { return $response->redirect(self::buildCallbackUri($redirectUri, [ @@ -181,28 +230,72 @@ final class OAuth2Routes extends RouteHandler { ])); } + if($authoriseInfo->isApproved()) { + $response->redirect(self::buildCallbackUri($redirectUri, [ + 'code' => $authoriseInfo->getCode(), + 'state' => $authoriseInfo->getState(), + ])); + return; + } + + $csrfp = new CSRFP(($this->getCSRFPSecret)(), $authInfo->session->token); + return $this->templating->render('oauth2/authorise', [ 'app' => $appInfo, 'req' => $authoriseInfo, 'auth' => $authInfo, + 'csrfp_token' => $csrfp->createToken(), + 'redirect_uri' => $redirectUri, ]); } - #[HttpPost('/oauth2/authorise')] public function postAuthorise($response, $request) { if(!$request->isFormContent()) return 400; - $authInfo = ($this->getAuthInfo)(); - if(!isset($authInfo->user)) - return 401; - - // TODO: CSRFP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! $content = $request->getContent(); + + $redirectUri = (string)$content->getParam('redirect'); + if(filter_var($redirectUri, FILTER_VALIDATE_URL) === false) { + $response->setStatusCode(400); + return [ + 'redirect' => self::buildCallbackUri('', [ + 'error' => 'invalid_request', + ]), + ]; + } + + $authInfo = ($this->getAuthInfo)(); + if(!isset($authInfo->user)) { + $response->setStatusCode(403); + return [ + 'redirect' => self::buildCallbackUri($redirectUri, [ + 'error' => 'access_denied', + ]), + ]; + } + + $csrfp = new CSRFP(($this->getCSRFPSecret)(), $authInfo->session->token); + if(!$csrfp->verifyToken((string)$content->getParam('csrfp'))) { + $response->setStatusCode(403); + return [ + 'redirect' => self::buildCallbackUri($redirectUri, [ + 'error' => 'invalid_request', + 'error_description' => 'Request verification failed.', + ]), + ]; + } + $approve = (string)$content->getParam('approve'); - if(!in_array($approve, ['yes', 'no'])) - return 400; + if(!in_array($approve, ['yes', 'no'])) { + $response->setStatusCode(400); + return [ + 'redirect' => self::buildCallbackUri($redirectUri, [ + 'error' => 'invalid_request', + ]), + ]; + } $authoriseData = $this->oauth2Ctx->getAuthoriseData(); try { @@ -211,32 +304,68 @@ final class OAuth2Routes extends RouteHandler { code: (string)$content->getParam('code'), ); } catch(RuntimeException $ex) { - return 404; + $response->setStatusCode(404); + return [ + 'redirect' => self::buildCallbackUri($redirectUri, [ + 'error' => 'invalid_request', + 'error_description' => 'Could not find authorisation request.', + ]), + ]; } - if(!$authoriseInfo->isPending()) - return 410; + if(!$authoriseInfo->isPending()) { + $response->setStatusCode(410); + return [ + 'redirect' => self::buildCallbackUri($redirectUri, [ + 'error' => 'invalid_request', + 'error_description' => 'This authorisation request has already been handled.', + 'state' => $authoriseInfo->getState(), + ]), + ]; + } $appsData = $this->appsCtx->getData(); try { $uriInfo = $appsData->getAppUriInfo($authoriseInfo->getUriId()); } catch(RuntimeException $ex) { - return 400; + $response->setStatusCode(400); + return [ + 'redirect' => self::buildCallbackUri($redirectUri, [ + 'error' => 'invalid_request', + 'error_description' => 'This authorisation request was made with a redirect URI that is no longer registered with this application.', + 'state' => $authoriseInfo->getState(), + ]), + ]; + } + + if($uriInfo->getString() !== $redirectUri) { + $response->setStatusCode(400); + return [ + 'redirect' => self::buildCallbackUri($redirectUri, [ + 'error' => 'invalid_request', + 'error_description' => 'Attempt at request forgery detected.', + 'state' => $authoriseInfo->getState(), + ]), + ]; } $approved = $approve === 'yes'; $authoriseData->setAuthoriseApproval($authoriseInfo, $approved); if($approved) - $response->redirect(self::buildCallbackUri($uriInfo->getString(), [ - 'code' => $authoriseInfo->getCode(), - 'state' => $authoriseInfo->getState(), - ])); - else - $response->redirect(self::buildCallbackUri($uriInfo->getString(), [ + return [ + 'redirect' => self::buildCallbackUri($uriInfo->getString(), [ + 'code' => $authoriseInfo->getCode(), + 'state' => $authoriseInfo->getState(), + ]), + ]; + + return [ + 'redirect' => self::buildCallbackUri($uriInfo->getString(), [ 'error' => 'access_denied', 'state' => $authoriseInfo->getState(), - ])); + ]), + ]; } private static function error(string $code, string $message = '', string $url = ''): array { diff --git a/templates/oauth2/authorise.twig b/templates/oauth2/authorise.twig index 0e5cfe1..9120276 100644 --- a/templates/oauth2/authorise.twig +++ b/templates/oauth2/authorise.twig @@ -1,23 +1,87 @@ {% extends 'oauth2/master.twig' %} {% block body %} -

{{ app.name }}

-

{{ app.summary }}

-

{% if app.isTrusted %}This is an official application. Things should probably just implicitly authorise if we hit this.{% else %}This is a third-party application.{% endif %}

+
+
+
+
+
+ +
+
+ +
+ {% if auth.guise is defined %} +
+
+
+ +
+
+
+

Are you {{ auth.guise.name }} and did you mean to use your own account?

+

Click here and reload this page.

+
+
+ {% endif %} +
+
+
+
+
+ Authorisation Request +
+ +
-

You are logged in as {{ auth.user.name }}.

- {% if auth.guise is defined %} -

Are you {{ auth.guise.name }} and did you mean to use your own account? Click here and reload this page.

- {% endif %} +
+

A third-party application is requesting permission to access your account.

+
-
- - - -
-
- - - -
+
+
+ {{ app.name }} +
{# TODO: author should be listed #} + +
+

{{ app.summary }}

+
+
+ +
+
This application will be able to:
+
+
+
+
Do anything because I have not made up scopes yet.
+
+
+
+
Eat soup.
+
+
+
+
These are placeholders.
+
+
+
+
This one is really long because I want to test wrapping and how the chevron icon thing will handle it so there will be a lot of text here, the app will not be gaining anything from it but yeah sometimes you just need to explode seventy times.
+
+
+
+ +
+ + +
+
{% endblock %} diff --git a/templates/oauth2/error.twig b/templates/oauth2/error.twig new file mode 100644 index 0000000..0f2f278 --- /dev/null +++ b/templates/oauth2/error.twig @@ -0,0 +1,15 @@ +{% extends 'oauth2/master.twig' %} + +{% block body %} +
+
+
+
+ An error occurred! +
+
+
+
+

{{ error_description }}

+
+{% endblock %} diff --git a/templates/oauth2/login.twig b/templates/oauth2/login.twig index 2f8478b..43fd8f9 100644 --- a/templates/oauth2/login.twig +++ b/templates/oauth2/login.twig @@ -1,9 +1,71 @@ {% extends 'oauth2/master.twig' %} {% block body %} -

{{ app.name }}

-

{{ app.summary }}

-

{% if app.isTrusted %}This is an official application. Things should probably just implicitly authorise if we hit this.{% else %}This is a third-party application.{% endif %}

+
+
+
+
+ Not logged in! +
+
+
+
+
+
+ Authorisation Request +
+ +
-

You must be logged in to authorise applications. Log in or create an account and reload this page to try again.

+
+ {% if not app.isTrusted %} +

A third-party application is requesting permission to access your account.

+ {% endif %} + +

You must be logged in to authorise applications. Log in or create an account and reload this page to try again.

+ + {% if app.isTrusted %} +

You will be redirected to the following application after logging in.

+ {% endif %} +
+ +
+
+ {{ app.name }} +
{# TODO: author should be listed #} + +
+

{{ app.summary }}

+
+
+ +
+
This application will be able to:
+
+
+
+
Do anything because I have not made up scopes yet.
+
+
+
+
Eat soup.
+
+
+
+
These are placeholders.
+
+
+
+
This one is really long because I want to test wrapping and how the chevron icon thing will handle it so there will be a lot of text here, the app will not be gaining anything from it but yeah sometimes you just need to explode seventy times.
+
+
+
+
{% endblock %} diff --git a/templates/oauth2/master.twig b/templates/oauth2/master.twig index d412fa4..70632a0 100644 --- a/templates/oauth2/master.twig +++ b/templates/oauth2/master.twig @@ -4,10 +4,17 @@ {% if title is defined %}{{ title }} :: {% endif %}{{ globals.siteInfo.name }} + - {% block body %}{% endblock %} +
+
+
+ {% block body %}{% endblock %} +
+
+