diff --git a/api/ApiOATHValidate.php b/api/ApiOATHValidate.php
new file mode 100644
index 00000000..2ce162ee
--- /dev/null
+++ b/api/ApiOATHValidate.php
@@ -0,0 +1,111 @@
+requirePostedParameters( [ 'totp', 'token' ] );
+
+ $params = $this->extractRequestParams();
+ if ( $params['user'] === null ) {
+ $params['user'] = $this->getUser()->getName();
+ }
+
+ if ( !$this->getUser()->isAllowed( 'oathauth-api-all' ) ) {
+ $this->dieUsage(
+ 'You do not have permission to validate an OATH token',
+ 'permissiondenied'
+ );
+ }
+
+ $user = User::newFromName( $params['user'] );
+ if ( $user === false ) {
+ $this->dieUsageMsg( [ 'noname', $params['user'] ] );
+ }
+
+ // Don't increase pingLimiter, just check for limit exceeded
+ if ( $user->pingLimiter( 'badoath', 0 ) ) {
+ $this->dieUsageMsg( 'actionthrottledtext' );
+ }
+
+ $result = [
+ ApiResult::META_BC_BOOLS => [ 'enabled', 'valid' ],
+ 'enabled' => false,
+ 'valid' => false,
+ ];
+
+ if ( !$user->isAnon() ) {
+ $oathUser = OATHAuthHooks::getOATHUserRepository()
+ ->findByUser( $user );
+ if ( $oathUser ) {
+ $key = $oathUser->getKey();
+ if ( $key !== null ) {
+ $result['enabled'] = true;
+ $result['valid'] = $key->verifyToken(
+ $params['totp'], $oathUser ) !== false;
+ }
+ }
+ }
+
+ if ( !$result['valid'] ) {
+ // Increase rate limit counter for failed request
+ $user->pingLimiter( 'badoath' );
+ }
+
+ $this->getResult()->addValue( null, $this->getModuleName(), $result );
+ }
+
+ public function getCacheMode( $params ) {
+ return 'private';
+ }
+
+ public function isInternal() {
+ return true;
+ }
+
+ public function needsToken() {
+ return 'csrf';
+ }
+
+ public function getAllowedParams() {
+ return [
+ 'user' => [
+ ApiBase::PARAM_TYPE => 'user',
+ ],
+ 'totp' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ],
+ ];
+ }
+
+ protected function getExamplesMessages() {
+ return [
+ 'action=oathvalidate&totp=123456&token=123ABC'
+ => 'apihelp-oath-example-1',
+ 'action=oathvalidate&user=Example&totp=123456&token=123ABC'
+ => 'apihelp-oath-example-2',
+ ];
+ }
+}
diff --git a/api/ApiQueryOATH.php b/api/ApiQueryOATH.php
index 2a20d2c3..6c1629a2 100644
--- a/api/ApiQueryOATH.php
+++ b/api/ApiQueryOATH.php
@@ -51,6 +51,7 @@ class ApiQueryOATH extends ApiQueryBase {
$result = $this->getResult();
$data = [
+ ApiResult::META_BC_BOOLS => [ 'enabled' ],
'enabled' => false,
];
diff --git a/extension.json b/extension.json
index ed21abf9..d1bc56f6 100644
--- a/extension.json
+++ b/extension.json
@@ -7,6 +7,7 @@
"type": "other",
"license-name": "GPL-2.0+",
"AutoloadClasses": {
+ "ApiOATHValidate": "api/ApiOATHValidate.php",
"ApiQueryOATH": "api/ApiQueryOATH.php",
"OATHAuthHooks": "OATHAuth.hooks.php",
"OATHAuthLegacyHooks": "OATHAuth.hooks.legacy.php",
@@ -90,8 +91,19 @@
"GrantPermissionGroups": {
"oath": "authentication"
},
+ "APIModules": {
+ "oathvalidate": "ApiOATHValidate"
+ },
"APIMetaModules": {
"oath": "ApiQueryOATH"
},
+ "RateLimits": {
+ "badoath": {
+ "user": [
+ 10,
+ 60
+ ]
+ }
+ },
"manifest_version": 1
}
diff --git a/i18n/en.json b/i18n/en.json
index 1970e7a5..a49b8ec0 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -58,5 +58,10 @@
"apihelp-query+oath-description": "Check to see if two-factor authentication (OATH) is enabled for a user.",
"apihelp-query+oath-param-user": "User to get information about. Defaults to the current user.",
"apihelp-query+oath-example-1": "Get information about the current user",
- "apihelp-query+oath-example-2": "Get information about user Example"
+ "apihelp-query+oath-example-2": "Get information about user Example",
+ "apihelp-oathvalidate-description": "Validate a two-factor authentication (OATH) token.",
+ "apihelp-oathvalidate-param-user": "User to validate token for. Defaults to the current user.",
+ "apihelp-oathvalidate-param-totp": "Two-factor authentication (OATH) token.",
+ "apihelp-oath-example-1": "Validate a token for the current user",
+ "apihelp-oath-example-2": "Validate a token for the user Example"
}
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 96c00acd..ddf548db 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -63,5 +63,10 @@
"apihelp-query+oath-description": "{{doc-apihelp-description|query+oath}}",
"apihelp-query+oath-param-user": "{{doc-apihelp-param|query+oath|user}}",
"apihelp-query+oath-example-1": "{{doc-apihelp-example|query+oath}}",
- "apihelp-query+oath-example-2": "{{doc-apihelp-example|query+oath}}"
+ "apihelp-query+oath-example-2": "{{doc-apihelp-example|query+oath}}",
+ "apihelp-oathvalidate-description": "{{doc-apihelp-description|oathvalidate}}",
+ "apihelp-oathvalidate-param-user": "{{doc-apihelp-param|oathvalidate|user}}",
+ "apihelp-oathvalidate-param-totp": "{{doc-apihelp-param|oathvalidate|totp}}",
+ "apihelp-oath-example-1": "{{doc-apihelp-example|oathvalidate}}",
+ "apihelp-oath-example-2": "{{doc-apihelp-example|oathvalidate}}"
}