1
0
Fork 0
mirror of synced 2024-06-27 02:31:04 +12:00

feat: mfa users endpoints

This commit is contained in:
Torsten Dittmann 2024-01-18 14:56:58 +01:00
parent e7c40d1cb1
commit 057496dbaa
10 changed files with 147 additions and 17 deletions

View file

@ -185,7 +185,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '0.6.0-rc.5',
'version' => '0.6.0-rc.6',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -3228,7 +3228,7 @@ App::patch('/v1/account/mfa')
->desc('Update MFA')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
->label('scope', 'accounts.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -3242,7 +3242,7 @@ App::patch('/v1/account/mfa')
->label('sdk.response.model', Response::MODEL_USER)
->label('sdk.offline.model', '/account')
->label('sdk.offline.key', 'current')
->param('mfa', null, new Boolean(), 'User name. Max length: 128 chars.')
->param('mfa', null, new Boolean(), 'Enable or disable MFA.')
->inject('requestTimestamp')
->inject('response')
->inject('user')
@ -3262,7 +3262,7 @@ App::patch('/v1/account/mfa')
App::get('/v1/account/mfa/providers')
->desc('List Providers')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('scope', 'accounts.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@ -3290,7 +3290,7 @@ App::post('/v1/account/mfa/:provider')
->desc('Add Authenticator')
->groups(['api', 'account', 'mfa'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
->label('scope', 'accounts.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -3350,7 +3350,7 @@ App::put('/v1/account/mfa/:provider')
->desc('Verify Authenticator')
->groups(['api', 'account', 'mfa'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
->label('scope', 'accounts.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -3405,9 +3405,9 @@ App::put('/v1/account/mfa/:provider')
App::delete('/v1/account/mfa/:provider')
->desc('Delete Authenticator')
->groups(['api', 'account', 'mfa'])
->groups(['api', 'account'])
->label('event', 'users.[userId].delete.mfa')
->label('scope', 'account')
->label('scope', 'accounts.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -3459,7 +3459,7 @@ App::delete('/v1/account/mfa/:provider')
App::post('/v1/account/mfa/challenge')
->desc('Create MFA Challenge')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('scope', 'accounts.write')
->label('event', 'users.[userId].challenges.[challengeId].create')
->label('auth.type', 'createChallenge')
->label('audits.event', 'challenge.create')
@ -3554,7 +3554,7 @@ App::post('/v1/account/mfa/challenge')
App::put('/v1/account/mfa/challenge')
->desc('Create MFA Challenge (confirmation)')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('scope', 'accounts.write')
->label('event', 'users.[userId].sessions.[tokenId].create')
->label('audits.event', 'challenges.update')
->label('audits.resource', 'user/{response.userId}')

View file

@ -1,6 +1,7 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Auth\MFA\Challenge;
use Appwrite\Auth\Validator\Password;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
@ -1422,6 +1423,131 @@ App::patch('/v1/users/:userId/targets/:targetId')
->dynamic($target, Response::MODEL_TARGET);
});
App::patch('/v1/users/:userId/mfa')
->desc('Update MFA')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'users.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateMfa')
->label('sdk.description', '/docs/references/users/update-user-mfa.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new UID(), 'User ID.')
->param('mfa', null, new Boolean(), 'Enable or disable MFA.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, bool $mfa, Response $response, Database $dbForProject, Event $queueForEvents) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$user->setAttribute('mfa', $mfa);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$queueForEvents->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
});
App::get('/v1/users/:userId/providers')
->desc('List Providers')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'users')
->label('sdk.method', 'listProviders')
->label('sdk.description', '/docs/references/users/list-providers.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MFA_PROVIDERS)
->param('userId', '', new UID(), 'User ID.')
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$providers = new Document([
'totp' => $user->getAttribute('totp', false) && $user->getAttribute('totpVerification', false),
'email' => $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false),
'phone' => $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false)
]);
$response->dynamic($providers, Response::MODEL_MFA_PROVIDERS);
});
App::delete('/v1/users/:userId/mfa/:provider')
->desc('Delete Authenticator')
->groups(['api', 'users'])
->label('event', 'users.[userId].delete.mfa')
->label('scope', 'users.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'users')
->label('sdk.method', 'deleteAuthenticator')
->label('sdk.description', '/docs/references/users/delete-mfa.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new UID(), 'User ID.')
->param('provider', null, new WhiteList(['totp']), 'Provider.')
->param('otp', '', new Text(256), 'Valid verification token.')
->inject('requestTimestamp')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $provider, string $otp, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$success = match ($provider) {
'totp' => Challenge\TOTP::verify($user, $otp),
default => false
};
if (!$success) {
throw new Exception(Exception::USER_INVALID_TOKEN);
}
if (!$user->getAttribute('totp')) {
throw new Exception(Exception::GENERAL_UNKNOWN, 'TOTP not added.');
}
$user
->setAttribute('totp', false)
->setAttribute('totpVerification', false)
->setAttribute('totpSecret', null)
->setAttribute('totpBackup', null);
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
$queueForEvents->setParam('userId', $user->getId());
$response->noContent();
});
App::post('/v1/users/:userId/sessions')
->desc('Create session')
->groups(['api', 'users'])

View file

@ -566,7 +566,11 @@ App::init()
if ($mode !== APP_MODE_ADMIN) {
$minFactors = $project->getAttribute('minFactors') ?? 1;
$mfaEnabled = $user->getAttribute('mfa', false);
if ($mfaEnabled && $minFactors === 1) {
$hasVerifiedAuthenticator = $user->getAttribute('totpVerification', false);
$hasVerifiedEmail = $user->getAttribute('emailVerification', false);
$hasVerifiedPhone = $user->getAttribute('phoneVerification', false);
$hasMoreFactors = $hasVerifiedEmail || $hasVerifiedPhone || $hasVerifiedAuthenticator;
if ($mfaEnabled && $hasMoreFactors && $minFactors === 1) {
$minFactors = 2;
}
if (!in_array('mfa', $route->getGroups())) {