diff --git a/assets/oauth2.css/device.css b/assets/oauth2.css/device.css
index 2e653fa..0747d7d 100644
--- a/assets/oauth2.css/device.css
+++ b/assets/oauth2.css/device.css
@@ -1,22 +1,3 @@
-.oauth2-devicehead {
- display: flex;
- align-items: center;
-}
-
-.oauth2-devicehead-icon {
- flex: 0 0 auto;
- background-color: #fff;
- mask: url('/images/mobile-screen-solid.svg') no-repeat center;
- width: 40px;
- height: 40px;
- margin: 10px;
-}
-
-.oauth2-devicehead-text {
- font-size: 1.8em;
- line-height: 1.4em;
-}
-
.oauth2-device-form {
display: flex;
justify-content: center;
diff --git a/assets/oauth2.css/error.css b/assets/oauth2.css/error.css
index 2668cf1..41f3b68 100644
--- a/assets/oauth2.css/error.css
+++ b/assets/oauth2.css/error.css
@@ -1,22 +1,3 @@
-.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
deleted file mode 100644
index 2dd595e..0000000
--- a/assets/oauth2.css/login.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.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 690ecbd..8b5ec41 100644
--- a/assets/oauth2.css/main.css
+++ b/assets/oauth2.css/main.css
@@ -38,8 +38,8 @@
@include loading.css;
@include banner.css;
@include error.css;
-@include login.css;
@include device.css;
+@include simplehead.css;
@include userhead.css;
@include appinfo.css;
@include scope.css;
diff --git a/assets/oauth2.css/simplehead.css b/assets/oauth2.css/simplehead.css
new file mode 100644
index 0000000..639c1da
--- /dev/null
+++ b/assets/oauth2.css/simplehead.css
@@ -0,0 +1,30 @@
+.oauth2-simplehead {
+ display: flex;
+ align-items: center;
+}
+
+.oauth2-simplehead-icon {
+ flex: 0 0 auto;
+ background-color: #fff;
+ mask: url('/images/circle-question-solid.svg') no-repeat center;
+ width: 40px;
+ height: 40px;
+ margin: 10px;
+}
+.oauth2-simplehead-icon--code {
+ mask-image: url('/images/mobile-screen-solid.svg');
+}
+.oauth2-simplehead-icon--error {
+ mask-image: url('/images/circle-exclamation-solid.svg');
+}
+.oauth2-simplehead-icon--login {
+ mask-image: url('/images/user-lock-solid.svg');
+}
+.oauth2-simplehead-icon--wait {
+ mask-image: url('/images/ellipsis-solid.svg');
+}
+
+.oauth2-simplehead-text {
+ font-size: 1.8em;
+ line-height: 1.4em;
+}
diff --git a/assets/oauth2.js/authorise.js b/assets/oauth2.js/authorise.js
new file mode 100644
index 0000000..3374981
--- /dev/null
+++ b/assets/oauth2.js/authorise.js
@@ -0,0 +1,236 @@
+#include loading.jsx
+#include app/info.jsx
+#include app/scope.jsx
+#include header/header.js
+#include header/user.jsx
+
+const HanyuuOAuth2AuthoriseErrors = Object.freeze({
+ '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.',
+ },
+ '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': {
+ description: 'The authorisation server encountered an unexpected condition that prevented it from fulfilling the request.',
+ },
+ 'temporarily_unavailable': {
+ description: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.',
+ },
+});
+
+const HanyuuOAuth2Authorise = async () => {
+ const queryParams = new URLSearchParams(window.location.search);
+ const loading = new HanyuuOAuth2Loading('.js-loading');
+ const header = new HanyuuOAuth2Header;
+
+ const fAuths = document.querySelector('.js-authorise-form');
+ const eAuthsInfo = document.querySelector('.js-authorise-form-info');
+ const eAuthsScope = document.querySelector('.js-authorise-form-scope');
+
+ const dError = document.querySelector('.js-authorise-error');
+ const dErrorText = dError?.querySelector('.js-authorise-error-text');
+
+ let scope;
+ let state;
+ let redirectUri;
+ let redirectUriRaw;
+
+ const displayError = (error, description, documentation) => {
+ if(redirectUri === undefined) {
+ if(error in HanyuuOAuth2AuthoriseErrors) {
+ const errInfo = HanyuuOAuth2AuthoriseErrors[error];
+ description ??= errInfo.description;
+ } else
+ description = `An unknown error occurred: ${error}`;
+
+ dErrorText.textContent = description;
+
+ header.setSimpleData('error', 'An error occurred!');
+ header.removeElement();
+
+ loading.visible = false;
+ fAuths.classList.add('hidden');
+ dError.classList.remove('hidden');
+ return;
+ }
+
+ const errorUri = new URL(redirectUri);
+ errorUri.searchParams.set('error', error?.toString() ?? 'invalid_request');
+ if(description)
+ errorUri.searchParams.set('error_description', description.toString());
+ if(documentation)
+ errorUri.searchParams.set('error_uri', documentation.toString());
+ if(state !== undefined)
+ errorUri.searchParams.set('state', state.toString());
+
+ window.location.assign(errorUri);
+ };
+ const translateError = serverError => {
+ if(serverError === 'auth')
+ return displayError('access_denied');
+ if(serverError === 'csrf')
+ return displayError('invalid_request', 'Request verification failed.');
+ if(serverError === 'client')
+ return displayError('invalid_request', 'There is no application associated with the specified Client ID.');
+ if(serverError === 'format')
+ return displayError('invalid_request', 'Redirect URI specified is not registered with this application.');
+ if(serverError === 'method')
+ return displayError('invalid_request', 'Requested code challenge method is not supported.');
+ if(serverError === 'length')
+ return displayError('invalid_request', 'Code challenge length is not acceptable.');
+ if(serverError === 'required')
+ return displayError('invalid_request', 'A registered redirect URI must be specified.');
+ if(serverError === 'scope')
+ return displayError('invalid_scope');
+ if(serverError === 'authorise')
+ return displayError('server_error', 'Server was unable to complete authorisation.');
+
+ return displayError('invalid_request', `An unknown error occurred: ${serverError}.`);
+ };
+
+ if(queryParams.has('redirect_uri'))
+ try {
+ const qRedirectUriRaw = queryParams.get('redirect_uri');
+ const qRedirectUri = new URL(qRedirectUriRaw);
+ if(qRedirectUri.protocol !== 'https:')
+ throw 'protocol must be https';
+
+ redirectUri = qRedirectUri;
+ redirectUriRaw = qRedirectUriRaw;
+ } catch(ex) {
+ return displayError('invalid_request', 'Invalid redirect URI specified.');
+ }
+
+ if(queryParams.has('state')) {
+ const qState = queryParams.get('state');
+
+ if(qState.length > 1000)
+ return displayError('invalid_request', 'State parameter may not be longer than 255 characters.');
+
+ state = qState;
+ }
+
+ if(queryParams.get('response_type') !== 'code')
+ return displayError('unsupported_response_type');
+
+ let codeChallengeMethod = 'plain';
+ if(queryParams.has('code_challenge_method')) {
+ codeChallengeMethod = queryParams.get('code_challenge_method');
+ if(!['plain', 'S256'].includes(codeChallengeMethod))
+ return translateError('method');
+ }
+
+ if(!queryParams.has('code_challenge'))
+ return displayError('invalid_request', 'code_challenge must be specified.');
+
+ const codeChallenge = queryParams.get('code_challenge');
+ if(codeChallengeMethod === 'S256') {
+ if(codeChallenge.length !== 43)
+ return displayError('invalid_request', 'Specified code challenge is not a valid SHA-256 hash.');
+ } else {
+ if(codeChallenge.length < 43)
+ return displayError('invalid_request', 'Code challenge must be at least 43 characters long.');
+ if(codeChallenge.length > 128)
+ return displayError('invalid_request', 'Code challenge may not be longer than 128 characters.');
+ }
+
+ if(!queryParams.has('client_id'))
+ return displayError('invalid_request', 'client_id must be specified.');
+
+ const resolveParams = new URLSearchParams;
+ resolveParams.set('csrfp', HanyuuCSRFP.getToken());
+ resolveParams.set('client', queryParams.get('client_id'));
+ if(redirectUriRaw !== undefined)
+ resolveParams.set('redirect', redirectUriRaw);
+ if(queryParams.has('scope')) {
+ scope = queryParams.get('scope');
+ resolveParams.set('scope', scope);
+ }
+
+ try {
+ const resolved = (await $x.get(`/oauth2/resolve-authorise-app?${resolveParams}`, { type: 'json' })).body();
+ if(!resolved)
+ throw 'authorisation resolve failed';
+ if(typeof resolved.error === 'string')
+ return translateError(resolved.error);
+
+ const userHeader = new HanyuuOAuth2UserHeader(resolved.user);
+ header.setElement(userHeader);
+
+ const verifyAuthsRequest = async () => {
+ const params = {
+ _csrfp: HanyuuCSRFP.getToken(),
+ client: queryParams.get('client_id'),
+ cc: codeChallenge,
+ ccm: codeChallengeMethod,
+ };
+ if(redirectUriRaw !== undefined)
+ params.redirect = redirectUriRaw;
+ if(scope !== undefined)
+ params.scope = scope;
+
+ try {
+ const response = (await $x.post('/oauth2/authorise', { type: 'json' }, params)).body();
+ if(!response)
+ throw 'authorisation failed';
+ if(typeof response.error === 'string')
+ return translateError(response.error);
+
+ const authoriseUri = new URL(response.redirect);
+ authoriseUri.searchParams.set('code', response.code);
+ if(state !== undefined)
+ authoriseUri.searchParams.set('state', state.toString());
+
+ window.location.assign(authoriseUri);
+ } catch(ex) {
+ console.error(ex);
+ translateError('authorise');
+ }
+ };
+
+ if(resolved.app.trusted && resolved.user.guise === undefined) {
+ if(userHeader)
+ userHeader.guiseVisible = false;
+
+ verifyAuthsRequest();
+ return;
+ }
+
+ const appElem = new HanyuuOAuth2AppInfo(resolved.app);
+ eAuthsInfo.replaceWith(appElem.element);
+
+ const scopeElem = new HanyuuOAuth2AppScopeList();
+ eAuthsScope.replaceWith(scopeElem.element);
+
+ fAuths.onsubmit = ev => {
+ ev.preventDefault();
+
+ loading.visible = true;
+ fAuths.classList.add('hidden');
+
+ if(userHeader)
+ userHeader.guiseVisible = false;
+
+ if(ev.submitter?.value === 'yes')
+ verifyAuthsRequest();
+ else
+ displayError('access_denied');
+ };
+
+ loading.visible = false;
+ fAuths.classList.remove('hidden');
+ } catch(ex) {
+ console.error(ex);
+ displayError('server_error', 'Server was unable to respond to the client info request.');
+ }
+};
diff --git a/assets/oauth2.js/header/header.js b/assets/oauth2.js/header/header.js
index 4161293..7f925d9 100644
--- a/assets/oauth2.js/header/header.js
+++ b/assets/oauth2.js/header/header.js
@@ -15,11 +15,13 @@ const HanyuuOAuth2Header = function(element = '.js-oauth2-header', simpleElement
if(hasSimpleElement)
simpleElement.classList.toggle('hidden', !state);
};
- const setSimpleData = (name, text) => {
+ const setSimpleData = (icon, text) => {
if(hasSimpleElement) {
- simpleElement.className = `oauth2-${name}`;
- simpleElementIcon.className = `oauth2-${name}-icon`;
- simpleElementText.className = `oauth2-${name}-text`;
+ for(const className of simpleElementIcon.classList)
+ if(className.startsWith('oauth2-simplehead-icon--'))
+ simpleElementIcon.classList.remove(className);
+
+ simpleElementIcon.classList.add(`oauth2-simplehead-icon--${icon}`);
simpleElementText.textContent = text;
}
};
diff --git a/assets/oauth2.js/main.js b/assets/oauth2.js/main.js
index 182ba17..29b8204 100644
--- a/assets/oauth2.js/main.js
+++ b/assets/oauth2.js/main.js
@@ -1,37 +1,9 @@
+#include authorise.js
#include verify.js
(() => {
- 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('&'));
- };
- }
-
+ if(location.pathname === '/oauth2/authorise')
+ HanyuuOAuth2Authorise();
if(location.pathname === '/oauth2/verify')
HanyuuOAuth2Verify();
})();
diff --git a/assets/oauth2.js/verify.js b/assets/oauth2.js/verify.js
index 32bd210..38363fb 100644
--- a/assets/oauth2.js/verify.js
+++ b/assets/oauth2.js/verify.js
@@ -18,49 +18,48 @@ const HanyuuOAuth2Verify = () => {
let userCode = '';
let userHeader;
+
const verifyAuthsRequest = async approve => {
- return await $x.post('/oauth2/verify', { type: 'json' }, {
- _csrfp: HanyuuCSRFP.getToken(),
- code: userCode,
- approve: approve === true ? 'yes' : 'no',
- });
- };
+ try {
+ const response = (await $x.post('/oauth2/verify', { type: 'json' }, {
+ _csrfp: HanyuuCSRFP.getToken(),
+ code: userCode,
+ approve: approve === true ? 'yes' : 'no',
+ })).body();
- const handleVerifyAuthsResponse = result => {
- const response = result.body();
+ if(!response)
+ throw 'response is empty';
- if(!response) {
+ if(typeof response.error === 'string') {
+ // TODO: nicer errors
+ if(response.error === 'auth')
+ alert('You are not logged in.');
+ else if(response.error === 'csrf')
+ alert('Request verification failed, please refresh and try again.');
+ else if(response.error === 'code')
+ alert('This code is not associated with any authorisation request.');
+ else if(response.error === 'approval')
+ alert('The authorisation request associated with this code is not pending approval.');
+ else if(response.error === 'invalid')
+ alert('Invalid approval state specified.');
+ else
+ alert(`An unknown error occurred: ${response.error}`);
+
+ loading.visible = false;
+ fAuths.classList.remove('hidden');
+ return;
+ }
+
+ loading.visible = false;
+ if(response.approval === 'approved')
+ rApproved.classList.remove('hidden');
+ else
+ rDenied.classList.remove('hidden');
+ } catch(ex) {
alert('Request to verify endpoint failed. Please try again.');
loading.visible = false;
fAuths.classList.remove('hidden');
- return;
}
-
- if(typeof response.error === 'string') {
- // TODO: nicer errors
- if(response.error === 'auth')
- alert('You are not logged in.');
- else if(response.error === 'csrf')
- alert('Request verification failed, please refresh and try again.');
- else if(response.error === 'code')
- alert('This code is not associated with any authorisation request.');
- else if(response.error === 'approval')
- alert('The authorisation request associated with this code is not pending approval.');
- else if(response.error === 'invalid')
- alert('Invalid approval state specified.');
- else
- alert(`An unknown error occurred: ${response.error}`);
-
- loading.visible = false;
- fAuths.classList.remove('hidden');
- return;
- }
-
- loading.visible = false;
- if(response.approval === 'approved')
- rApproved.classList.remove('hidden');
- else
- rDenied.classList.remove('hidden');
};
fAuths.onsubmit = ev => {
@@ -72,8 +71,7 @@ const HanyuuOAuth2Verify = () => {
if(userHeader)
userHeader.guiseVisible = false;
- verifyAuthsRequest(ev.submitter.value === 'yes')
- .then(handleVerifyAuthsResponse);
+ verifyAuthsRequest(ev.submitter.value === 'yes');
};
const fCode = document.querySelector('.js-verify-code');
@@ -85,7 +83,7 @@ const HanyuuOAuth2Verify = () => {
loading.visible = true;
fCode.classList.add('hidden');
- $x.get(`/oauth2/resolve-request?csrfp=${encodeURIComponent(HanyuuCSRFP.getToken())}&code=${encodeURIComponent(eUserCode.value)}`, { type: 'json' })
+ $x.get(`/oauth2/resolve-verify?csrfp=${encodeURIComponent(HanyuuCSRFP.getToken())}&code=${encodeURIComponent(eUserCode.value)}`, { type: 'json' })
.then(result => {
const response = result.body();
@@ -123,7 +121,7 @@ const HanyuuOAuth2Verify = () => {
if(userHeader)
userHeader.guiseVisible = false;
- verifyAuthsRequest(true).then(handleVerifyAuthsResponse);
+ verifyAuthsRequest(true);
return;
}
@@ -135,6 +133,10 @@ const HanyuuOAuth2Verify = () => {
loading.visible = false;
fAuths.classList.remove('hidden');
+ }).catch(() => {
+ alert('Request to resolve endpoint failed. Please try again.');
+ loading.visible = false;
+ fCode.classList.remove('hidden');
});
};
diff --git a/database/2024_07_30_211859_remove_state_fields_from_authorisations_db.php b/database/2024_07_30_211859_remove_state_fields_from_authorisations_db.php
new file mode 100644
index 0000000..9aa02da
--- /dev/null
+++ b/database/2024_07_30_211859_remove_state_fields_from_authorisations_db.php
@@ -0,0 +1,9 @@
+execute('ALTER TABLE hau_oauth2_authorise DROP COLUMN auth_state, DROP COLUMN auth_approval;');
+ }
+}
diff --git a/public/images/circle-question-solid.svg b/public/images/circle-question-solid.svg
new file mode 100644
index 0000000..3cc83fd
--- /dev/null
+++ b/public/images/circle-question-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/ellipsis-solid.svg b/public/images/ellipsis-solid.svg
new file mode 100644
index 0000000..7283ce3
--- /dev/null
+++ b/public/images/ellipsis-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/OAuth2/OAuth2AuthoriseData.php b/src/OAuth2/OAuth2AuthorisationData.php
similarity index 52%
rename from src/OAuth2/OAuth2AuthoriseData.php
rename to src/OAuth2/OAuth2AuthorisationData.php
index b24c726..df30d03 100644
--- a/src/OAuth2/OAuth2AuthoriseData.php
+++ b/src/OAuth2/OAuth2AuthorisationData.php
@@ -9,7 +9,7 @@ use Index\Data\IDbConnection;
use Hanyuu\Apps\AppInfo;
use Hanyuu\Apps\AppUriInfo;
-class OAuth2AuthoriseData {
+class OAuth2AuthorisationData {
private IDbConnection $dbConn;
private DbStatementCache $cache;
@@ -18,26 +18,21 @@ class OAuth2AuthoriseData {
$this->cache = new DbStatementCache($dbConn);
}
- public function getAuthoriseInfo(
- ?string $authoriseId = null,
+ public function getAuthorisationInfo(
+ ?string $authsId = null,
AppInfo|string|null $appInfo = null,
- ?string $userId = null,
?string $code = null
- ): OAuth2AuthoriseInfo {
+ ): OAuth2AuthorisationInfo {
$selectors = [];
$values = [];
- if($authoriseId !== null) {
+ if($authsId !== null) {
$selectors[] = 'auth_id = ?';
- $values[] = $authoriseId;
+ $values[] = $authsId;
}
if($appInfo !== null) {
$selectors[] = 'app_id = ?';
$values[] = $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo;
}
- if($userId !== null) {
- $selectors[] = 'user_id = ?';
- $values[] = $userId;
- }
if($code !== null) {
$selectors[] = 'auth_code = ?';
$values[] = $code;
@@ -47,7 +42,7 @@ class OAuth2AuthoriseData {
throw new RuntimeException('Insufficient data to do authorisation request lookup.');
$args = 0;
- $stmt = $this->cache->get('SELECT auth_id, app_id, user_id, uri_id, auth_state, auth_challenge_code, auth_challenge_method, auth_scope, auth_code, auth_approval, UNIX_TIMESTAMP(auth_created), UNIX_TIMESTAMP(auth_expires) FROM hau_oauth2_authorise WHERE ' . implode(' AND ', $selectors));
+ $stmt = $this->cache->get('SELECT auth_id, app_id, user_id, uri_id, auth_challenge_code, auth_challenge_method, auth_scope, auth_code, UNIX_TIMESTAMP(auth_created), UNIX_TIMESTAMP(auth_expires) FROM hau_oauth2_authorise WHERE ' . implode(' AND ', $selectors));
foreach($values as $value)
$stmt->addParameter(++$args, $value);
$stmt->execute();
@@ -56,45 +51,41 @@ class OAuth2AuthoriseData {
if(!$result->next())
throw new RuntimeException('Authorise request not found.');
- return OAuth2AuthoriseInfo::fromResult($result);
+ return OAuth2AuthorisationInfo::fromResult($result);
}
- public function createAuthorise(
+ public function createAuthorisation(
AppInfo|string $appInfo,
string $userId,
AppUriInfo|string $appUriInfo,
- string $state,
string $challengeCode,
string $challengeMethod,
string $scope,
- ?string $code = null,
- bool $preapprove = false
- ): OAuth2AuthoriseInfo {
+ ?string $code = null
+ ): OAuth2AuthorisationInfo {
$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 = $this->cache->get('INSERT INTO hau_oauth2_authorise (app_id, user_id, uri_id, auth_challenge_code, auth_challenge_method, auth_scope, auth_code) VALUES (?, ?, ?, ?, ?, ?, ?)');
$stmt->addParameter(1, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
$stmt->addParameter(2, $userId);
$stmt->addParameter(3, $appUriInfo instanceof AppUriInfo ? $appUriInfo->getId() : $appUriInfo);
- $stmt->addParameter(4, $state);
- $stmt->addParameter(5, $challengeCode);
- $stmt->addParameter(6, $challengeMethod);
- $stmt->addParameter(7, $scope);
- $stmt->addParameter(8, $code);
- $stmt->addParameter(9, $preapprove ? 1 : 0);
+ $stmt->addParameter(4, $challengeCode);
+ $stmt->addParameter(5, $challengeMethod);
+ $stmt->addParameter(6, $scope);
+ $stmt->addParameter(7, $code);
$stmt->execute();
- return $this->getAuthoriseInfo(authoriseId: (string)$this->dbConn->getLastInsertId());
+ return $this->getAuthorisationInfo(authsId: (string)$this->dbConn->getLastInsertId());
}
- public function deleteAuthorise(
- OAuth2AuthoriseInfo|string|null $authoriseInfo = null
+ public function deleteAuthorisation(
+ OAuth2AuthorisationInfo|string|null $authsInfo = null
): void {
$selectors = [];
$values = [];
- if($authoriseInfo !== null) {
+ if($authsInfo !== null) {
$selectors[] = 'auth_id = ?';
- $values[] = $authoriseInfo instanceof OAuth2AuthoriseInfo ? $authoriseInfo->getId() : $authoriseInfo;
+ $values[] = $authsInfo instanceof OAuth2AuthorisationInfo ? $authsInfo->getId() : $authsInfo;
}
$query = 'DELETE FROM hau_oauth2_authorise';
@@ -107,18 +98,4 @@ class OAuth2AuthoriseData {
$stmt->addParameter(++$args, $value);
$stmt->execute();
}
-
- public function setAuthoriseApproval(OAuth2AuthoriseInfo|string $authoriseInfo, bool $approval): void {
- if($authoriseInfo instanceof OAuth2AuthoriseInfo) {
- if(!$authoriseInfo->isPending())
- return;
-
- $authoriseInfo = $authoriseInfo->getId();
- }
-
- $stmt = $this->cache->get('UPDATE hau_oauth2_authorise SET auth_approval = ? WHERE auth_id = ? AND auth_approval = "pending"');
- $stmt->addParameter(1, $approval ? 'approved' : 'denied');
- $stmt->addParameter(2, $authoriseInfo);
- $stmt->execute();
- }
}
diff --git a/src/OAuth2/OAuth2AuthoriseInfo.php b/src/OAuth2/OAuth2AuthorisationInfo.php
similarity index 69%
rename from src/OAuth2/OAuth2AuthoriseInfo.php
rename to src/OAuth2/OAuth2AuthorisationInfo.php
index 77c7644..5906e70 100644
--- a/src/OAuth2/OAuth2AuthoriseInfo.php
+++ b/src/OAuth2/OAuth2AuthorisationInfo.php
@@ -4,36 +4,32 @@ namespace Hanyuu\OAuth2;
use Index\Data\IDbResult;
use Index\Serialisation\UriBase64;
-class OAuth2AuthoriseInfo {
+class OAuth2AuthorisationInfo {
public function __construct(
private string $id,
private string $appId,
private string $userId,
private string $uriId,
- private string $state,
private string $challengeCode,
private string $challengeMethod,
private string $scope,
private string $code,
- private string $approval,
private int $created,
private int $expires
) {}
- public static function fromResult(IDbResult $result): OAuth2AuthoriseInfo {
- return new OAuth2AuthoriseInfo(
+ public static function fromResult(IDbResult $result): OAuth2AuthorisationInfo {
+ return new OAuth2AuthorisationInfo(
id: $result->getString(0),
appId: $result->getString(1),
userId: $result->getString(2),
uriId: $result->getString(3),
- state: $result->getString(4),
- challengeCode: $result->getString(5),
- challengeMethod: $result->getString(6),
- scope: $result->getString(7),
- code: $result->getString(8),
- approval: $result->getString(9),
- created: $result->getInteger(10),
- expires: $result->getInteger(11),
+ challengeCode: $result->getString(4),
+ challengeMethod: $result->getString(5),
+ scope: $result->getString(6),
+ code: $result->getString(7),
+ created: $result->getInteger(8),
+ expires: $result->getInteger(9),
);
}
@@ -53,10 +49,6 @@ class OAuth2AuthoriseInfo {
return $this->userId;
}
- public function getState(): string {
- return $this->state;
- }
-
public function getChallengeCode(): string {
return $this->challengeCode;
}
@@ -89,19 +81,6 @@ class OAuth2AuthoriseInfo {
return $this->code;
}
- public function getApproval(): string {
- return $this->approval;
- }
- public function isPending(): bool {
- return strcasecmp($this->approval, 'pending') === 0;
- }
- public function isApproved(): bool {
- return strcasecmp($this->approval, 'approved') === 0;
- }
- public function isDenied(): bool {
- return strcasecmp($this->approval, 'denied') === 0;
- }
-
public function getCreatedTime(): int {
return $this->created;
}
diff --git a/src/OAuth2/OAuth2Context.php b/src/OAuth2/OAuth2Context.php
index b4d7fb5..965719c 100644
--- a/src/OAuth2/OAuth2Context.php
+++ b/src/OAuth2/OAuth2Context.php
@@ -5,18 +5,18 @@ use Index\Data\IDbConnection;
use Hanyuu\Apps\AppInfo;
class OAuth2Context {
- private OAuth2AuthoriseData $authorise;
+ private OAuth2AuthorisationData $authorisations;
private OAuth2TokensData $tokens;
private OAuth2DevicesData $devices;
public function __construct(IDbConnection $dbConn) {
- $this->authorise = new OAuth2AuthoriseData($dbConn);
+ $this->authorisations = new OAuth2AuthorisationData($dbConn);
$this->tokens = new OAuth2TokensData($dbConn);
$this->devices = new OAuth2DevicesData($dbConn);
}
- public function getAuthoriseData(): OAuth2AuthoriseData {
- return $this->authorise;
+ public function getAuthorisationData(): OAuth2AuthorisationData {
+ return $this->authorisations;
}
public function getTokensData(): OAuth2TokensData {
diff --git a/src/OAuth2/OAuth2Routes.php b/src/OAuth2/OAuth2Routes.php
index 2c723f1..cd4ca06 100644
--- a/src/OAuth2/OAuth2Routes.php
+++ b/src/OAuth2/OAuth2Routes.php
@@ -33,15 +33,6 @@ final class OAuth2Routes extends RouteHandler {
return $info;
}
- private static function buildCallbackUri(string $target, array $params): string {
- if($target === '')
- $target = '/oauth2/error';
-
- $target .= strpos($target, '?') === false ? '?' : '&';
- $target .= http_build_query($params, '', '&', PHP_QUERY_RFC3986);
- return $target;
- }
-
private static function filterScopes(string $source): array {
$scopes = [];
@@ -57,205 +48,16 @@ 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')]
public function getAuthorise($response, $request) {
- $redirectUri = (string)$request->getParam('redirect_uri');
- if($redirectUri !== '' && filter_var($redirectUri, FILTER_VALIDATE_URL) === false)
- return $response->redirect(self::buildCallbackUri('', [
- 'error' => 'invalid_request',
- 'error_description' => 'Request URI must be a correctly formatted absolute URI.',
- ]));
-
- $clientId = (string)$request->getParam('client_id');
- $appsData = $this->appsCtx->getData();
- try {
- $appInfo = $appsData->getAppInfo(clientId: $clientId, deleted: false);
- } catch(RuntimeException $ex) {
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'Client ID is missing or is not associated with an existing application.',
- ]));
- }
-
- $state = (string)$request->getParam('state');
- if(strlen($state) > 255)
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'State parameter may not be longer than 255 characters.',
- ]));
-
- if($redirectUri === '') {
- $uriInfos = $appsData->countAppUris($appInfo);
- if($uriInfos !== 1)
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'A registered redirect URI must be specified.',
- 'state' => $state,
- ]));
-
- $uriInfos = $appsData->getAppUriInfos($appInfo);
- if(count($uriInfos) !== 1)
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'A registered redirect URI must be specified (congrats, you hit an edge case!).',
- 'state' => $state,
- ]));
-
- $uriInfo = array_pop($uriInfos);
- $redirectUriId = $uriInfo->getId();
- $redirectUri = $uriInfo->getString();
- } else {
- $redirectUriId = $appsData->getAppUriId($appInfo, $redirectUri);
- if($redirectUriId === null)
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'Specified redirect URI is not registered with the application.',
- 'state' => $state,
- ]));
- }
-
- $type = (string)$request->getParam('response_type');
- if($type !== 'code')
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'unsupported_response_type',
- 'state' => $state,
- ]));
-
- $codeChallengeMethod = (string)$request->getParam('code_challenge_method');
- if($codeChallengeMethod === '') $codeChallengeMethod = 'plain';
-
- $codeChallenge = (string)$request->getParam('code_challenge');
- $codeChallengeLength = strlen($codeChallenge);
-
- if($codeChallengeMethod === 'plain') {
- if($codeChallengeLength < 43)
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'Code challenge must be at least 43 characters long.',
- 'state' => $state,
- ]));
- elseif($codeChallengeLength > 128)
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'Code challenge may not be longer than 128 characters.',
- 'state' => $state,
- ]));
- } elseif($codeChallengeMethod === 'S256') {
- if($codeChallengeLength !== 43)
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'Specified code challenge is not a valid SHA-256 hash.',
- 'state' => $state,
- ]));
- } else {
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'Request code challenge method is not supported.',
- 'state' => $state,
- ]));
- }
-
- $scopes = self::filterScopes((string)$request->getParam('scope'));
- if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes))
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_scope',
- 'state' => $state,
- ]));
- $scope = implode(' ', $scopes);
-
$authInfo = ($this->getAuthInfo)();
if(!isset($authInfo->user))
- return $this->templating->render('oauth2/login', [
- 'app' => $appInfo,
- 'auth' => $authInfo,
- ]);
-
- $authoriseData = $this->oauth2Ctx->getAuthoriseData();
- try {
- $authoriseInfo = $authoriseData->createAuthorise(
- $appInfo,
- $authInfo->user->id,
- $redirectUriId,
- $state,
- $codeChallenge,
- $codeChallengeMethod,
- $scope,
- preapprove: $appInfo->isTrusted()
- );
- } catch(RuntimeException $ex) {
- return $response->redirect(self::buildCallbackUri($redirectUri, [
- 'error' => 'server_error',
- 'state' => $state,
- ]));
- }
-
- if($authoriseInfo->isApproved()) {
- $response->redirect(self::buildCallbackUri($redirectUri, [
- 'code' => $authoriseInfo->getCode(),
- 'state' => $authoriseInfo->getState(),
- ]));
- return;
- }
+ return $this->templating->render('oauth2/login', ['auth' => $authInfo]);
$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,
]);
}
@@ -264,127 +66,155 @@ final class OAuth2Routes extends RouteHandler {
if(!$request->isFormContent())
return 400;
- $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',
- ]),
- ];
- }
+ // TODO: RATE LIMITING
$authInfo = ($this->getAuthInfo)();
- if(!isset($authInfo->user)) {
- $response->setStatusCode(403);
- return [
- 'redirect' => self::buildCallbackUri($redirectUri, [
- 'error' => 'access_denied',
- ]),
- ];
- }
+ if(!isset($authInfo->user))
+ return ['error' => 'auth'];
+
+ $content = $request->getContent();
$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.',
- ]),
- ];
+ if(!$csrfp->verifyToken((string)$content->getParam('_csrfp')))
+ return ['error' => 'csrf'];
+
+ $codeChallengeMethod = 'plain';
+ if($content->hasParam('ccm')) {
+ $codeChallengeMethod = $content->getParam('ccm');
+ if(!in_array($codeChallengeMethod, ['plain', 'S256']))
+ return ['error' => 'method'];
}
- $approve = (string)$content->getParam('approve');
- if(!in_array($approve, ['yes', 'no'])) {
- $response->setStatusCode(400);
- return [
- 'redirect' => self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- ]),
- ];
- }
-
- $authoriseData = $this->oauth2Ctx->getAuthoriseData();
- try {
- $authoriseInfo = $authoriseData->getAuthoriseInfo(
- userId: $authInfo->user->id,
- code: (string)$content->getParam('code'),
- );
- } catch(RuntimeException $ex) {
- $response->setStatusCode(404);
- return [
- 'redirect' => self::buildCallbackUri($redirectUri, [
- 'error' => 'invalid_request',
- 'error_description' => 'Could not find authorisation request.',
- ]),
- ];
- }
-
- 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(),
- ]),
- ];
+ $codeChallenge = $content->getParam('cc');
+ $codeChallengeLength = strlen($codeChallenge);
+ if($codeChallengeMethod === 'S256') {
+ if($codeChallengeLength !== 43)
+ return ['error' => 'length'];
+ } else {
+ if($codeChallengeLength < 43 || $codeChallengeLength > 128)
+ return ['error' => 'length'];
}
$appsData = $this->appsCtx->getData();
try {
- $uriInfo = $appsData->getAppUriInfo($authoriseInfo->getUriId());
+ $appInfo = $appsData->getAppInfo(clientId: (string)$content->getParam('client'), deleted: false);
} catch(RuntimeException $ex) {
- $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(),
- ]),
- ];
+ return ['error' => 'client'];
}
- 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(),
- ]),
- ];
+ $scope = '';
+ if($content->hasParam('scope')) {
+ $scopes = self::filterScopes((string)$content->getParam('scope'));
+ if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes))
+ return ['error' => 'scope'];
+
+ $scope = implode(' ', $scopes);
}
- $approved = $approve === 'yes';
- $authoriseData->setAuthoriseApproval($authoriseInfo, $approved);
+ if($content->hasParam('redirect')) {
+ $redirectUri = (string)$content->getParam('redirect');
+ $redirectUriId = $appsData->getAppUriId($appInfo, $redirectUri);
+ if($redirectUriId === null)
+ return ['error' => 'format'];
+ } else {
+ $uriInfos = $appsData->getAppUriInfos($appInfo);
+ if(count($uriInfos) !== 1)
+ return ['error' => 'required'];
- if($approved)
- return [
- 'redirect' => self::buildCallbackUri($uriInfo->getString(), [
- 'code' => $authoriseInfo->getCode(),
- 'state' => $authoriseInfo->getState(),
- ]),
- ];
+ $uriInfo = array_pop($uriInfos);
+ $redirectUri = $uriInfo->getString();
+ $redirectUriId = $uriInfo->getId();
+ }
+
+ $authsData = $this->oauth2Ctx->getAuthorisationData();
+ try {
+ $authsInfo = $authsData->createAuthorisation(
+ $appInfo,
+ $authInfo->user->id,
+ $redirectUriId,
+ $codeChallenge,
+ $codeChallengeMethod,
+ $scope
+ );
+ } catch(RuntimeException $ex) {
+ return ['error' => 'authorise', 'detail' => $ex->getMessage()];
+ }
return [
- 'redirect' => self::buildCallbackUri($uriInfo->getString(), [
- 'error' => 'access_denied',
- 'state' => $authoriseInfo->getState(),
- ]),
+ 'code' => $authsInfo->getCode(),
+ 'redirect' => $redirectUri,
];
}
+ #[HttpGet('/oauth2/resolve-authorise-app')]
+ public function getResolveAuthorise($response, $request) {
+ // TODO: RATE LIMITING
+
+ $authInfo = ($this->getAuthInfo)();
+ if(!isset($authInfo->user))
+ return ['error' => 'auth'];
+
+ $csrfp = new CSRFP(($this->getCSRFPSecret)(), $authInfo->session->token);
+ if(!$csrfp->verifyToken((string)$request->getParam('csrfp')))
+ return ['error' => 'csrf'];
+
+ $appsData = $this->appsCtx->getData();
+ try {
+ $appInfo = $appsData->getAppInfo(clientId: (string)$request->getParam('client'), deleted: false);
+ } catch(RuntimeException $ex) {
+ return ['error' => 'client'];
+ }
+
+ if($request->hasParam('redirect')) {
+ $redirectUri = (string)$request->getParam('redirect');
+ if($appsData->getAppUriId($appInfo, $redirectUri) === null)
+ return ['error' => 'format'];
+ } else {
+ $uriInfos = $appsData->getAppUriInfos($appInfo);
+ if(count($uriInfos) !== 1)
+ return ['error' => 'required'];
+ }
+
+ if($request->hasParam('scope')) {
+ $scopes = self::filterScopes((string)$request->getParam('scope'));
+ if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes))
+ return ['error' => 'scope'];
+ }
+
+ $result = [
+ 'app' => [
+ 'name' => $appInfo->getName(),
+ 'summary' => $appInfo->getSummary(),
+ 'trusted' => $appInfo->isTrusted(),
+ 'links' => [
+ ['title' => 'Website', 'display' => $appInfo->getWebsiteDisplay(), 'uri' => $appInfo->getWebsite()],
+ ],
+ ],
+ 'user' => [
+ 'name' => $authInfo->user->name,
+ 'colour' => $authInfo->user->colour,
+ 'profile_uri' => $authInfo->user->profile_url,
+ 'avatar_uri' => $authInfo->user->avatars->x120,
+ ],
+ ];
+
+ if(isset($authInfo->guise))
+ $result['user']['guise'] = [
+ 'name' => $authInfo->guise->name,
+ 'colour' => $authInfo->guise->colour,
+ 'profile_uri' => $authInfo->guise->profile_url,
+ 'revert_uri' => $authInfo->guise->revert_url,
+ 'avatar_uri' => $authInfo->guise->avatars->x60,
+ ];
+
+ return $result;
+ }
+
#[HttpGet('/oauth2/verify')]
public function getVerify($response, $request) {
$authInfo = ($this->getAuthInfo)();
if(!isset($authInfo->user))
- return $this->templating->render('oauth2/login', [
- 'auth' => $authInfo,
- ]);
+ return $this->templating->render('oauth2/login', ['auth' => $authInfo]);
$csrfp = new CSRFP(($this->getCSRFPSecret)(), $authInfo->session->token);
@@ -432,8 +262,8 @@ final class OAuth2Routes extends RouteHandler {
];
}
- #[HttpGet('/oauth2/resolve-request')]
- public function getResolveRequest($response, $request) {
+ #[HttpGet('/oauth2/resolve-verify')]
+ public function getResolveVerify($response, $request) {
// TODO: RATE LIMITING
$authInfo = ($this->getAuthInfo)();
@@ -652,9 +482,9 @@ final class OAuth2Routes extends RouteHandler {
$type = (string)$content->getParam('grant_type');
if($type === 'authorization_code') {
- $authoriseData = $this->oauth2Ctx->getAuthoriseData();
+ $authsData = $this->oauth2Ctx->getAuthorisationData();
try {
- $authoriseInfo = $authoriseData->getAuthoriseInfo(
+ $authsInfo = $authsData->getAuthorisationInfo(
appInfo: $appInfo,
code: (string)$content->getParam('code'),
);
@@ -663,24 +493,19 @@ final class OAuth2Routes extends RouteHandler {
return self::error('invalid_grant', 'No authorisation request with this code exists.');
}
- if($authoriseInfo->hasExpired()) {
+ if($authsInfo->hasExpired()) {
$response->setStatusCode(400);
return self::error('invalid_grant', 'Authorisation request has expired.');
}
- if(!$authoriseInfo->verifyCodeChallenge((string)$content->getParam('code_verifier'))) {
+ if(!$authsInfo->verifyCodeChallenge((string)$content->getParam('code_verifier'))) {
$response->setStatusCode(400);
return self::error('invalid_request', 'Code challenge verification failed.');
}
- if(!$authoriseInfo->isApproved()) {
- $response->setStatusCode(400);
- return self::error('invalid_grant', 'Authorisation request has not been approved.');
- }
+ $authsData->deleteAuthorisation($authsInfo);
- $authoriseData->deleteAuthorise($authoriseInfo);
-
- $scopes = $authoriseInfo->getScopes();
+ $scopes = $authsInfo->getScopes();
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes)) {
$response->setStatusCode(400);
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
@@ -690,12 +515,12 @@ final class OAuth2Routes extends RouteHandler {
$tokensData = $this->oauth2Ctx->getTokensData();
$accessInfo = $tokensData->createAccess(
$appInfo,
- $authoriseInfo->getUserId(),
+ $authsInfo->getUserId(),
scope: $scope,
);
// 'scope' only has to be in the response if it differs from what was requested
- if($scope === $authoriseInfo->getScope())
+ if($scope === $authsInfo->getScope())
unset($scope);
if($appInfo->shouldIssueRefreshToken())
diff --git a/templates/oauth2/authorise.twig b/templates/oauth2/authorise.twig
index abadc5d..a1391f1 100644
--- a/templates/oauth2/authorise.twig
+++ b/templates/oauth2/authorise.twig
@@ -1,81 +1,29 @@
{% extends 'oauth2/master.twig' %}
+{% set body_header_icon = 'wait' %}
+{% set body_header_text = 'Loading...' %}
{% set body_title = 'Authorisation Request' %}
-{% block body_header %}
- Are you {{ auth.guise.name }} and did you mean to use your own account? Click here and reload this page.
{{ app.summary }}
+ -{{ error_description }}
-