Merge pull request #7713 from appwrite/feat-more-mfa-endpoints
Feat: More Recovery code endpoints
This commit is contained in:
commit
f48b03e3b9
|
@ -17,7 +17,6 @@ tasks:
|
|||
|
||||
ports:
|
||||
- port: 8080
|
||||
onOpen: open-preview
|
||||
visibility: public
|
||||
|
||||
vscode:
|
||||
|
|
|
@ -287,7 +287,7 @@ $commonCollections = [
|
|||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
'filters' => ['encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authenticators'),
|
||||
|
@ -979,6 +979,17 @@ $commonCollections = [
|
|||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('mfaUpdatedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
|
|
@ -242,6 +242,11 @@ return [
|
|||
'description' => 'Authenticator could not be found on the current user.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::USER_RECOVERY_CODES_NOT_FOUND => [
|
||||
'name' => Exception::USER_RECOVERY_CODES_NOT_FOUND,
|
||||
'description' => 'Recovery codes could not be found on the current user.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::USER_AUTHENTICATOR_ALREADY_VERIFIED => [
|
||||
'name' => Exception::USER_AUTHENTICATOR_ALREADY_VERIFIED,
|
||||
'description' => 'This authenticator is already verified on the current user.',
|
||||
|
@ -262,6 +267,11 @@ return [
|
|||
'description' => 'More factors are required to complete the sign in process.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_CHALLENGE_REQUIRED => [
|
||||
'name' => Exception::USER_CHALLENGE_REQUIRED,
|
||||
'description' => 'A recently succeessful challenge is required to complete this action. A challenge is considered recent for 5 minutes.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_OAUTH2_BAD_REQUEST => [
|
||||
'name' => Exception::USER_OAUTH2_BAD_REQUEST,
|
||||
'description' => 'OAuth2 provider rejected the bad request.',
|
||||
|
|
|
@ -2154,6 +2154,10 @@ App::get('/v1/account/sessions')
|
|||
->inject('project')
|
||||
->action(function (Response $response, Document $user, Locale $locale, Document $project) {
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$current = Auth::sessionVerify($sessions, Auth::$secret);
|
||||
|
||||
|
@ -2162,6 +2166,8 @@ App::get('/v1/account/sessions')
|
|||
|
||||
$session->setAttribute('countryName', $countryName);
|
||||
$session->setAttribute('current', ($current == $session->getId()) ? true : false);
|
||||
$session->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $session->getAttribute('secret', '') : '');
|
||||
|
||||
$sessions[$key] = $session;
|
||||
}
|
||||
|
||||
|
@ -2256,6 +2262,10 @@ App::get('/v1/account/sessions/:sessionId')
|
|||
->inject('project')
|
||||
->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Document $project) {
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$sessionId = ($sessionId === 'current')
|
||||
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
|
||||
|
@ -2268,6 +2278,7 @@ App::get('/v1/account/sessions/:sessionId')
|
|||
$session
|
||||
->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret)))
|
||||
->setAttribute('countryName', $countryName)
|
||||
->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $session->getAttribute('secret', '') : '')
|
||||
;
|
||||
|
||||
return $response->dynamic($session, Response::MODEL_SESSION);
|
||||
|
@ -3712,17 +3723,16 @@ App::post('/v1/account/mfa/recovery-codes')
|
|||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'createMfaRecoveryCodes')
|
||||
->label('sdk.description', '/docs/references/account/create-mfa-recovery-codes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.response.model', Response::MODEL_MFA_RECOVERY_CODES)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []);
|
||||
|
||||
|
@ -3743,6 +3753,77 @@ App::post('/v1/account/mfa/recovery-codes')
|
|||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::patch('/v1/account/mfa/recovery-codes')
|
||||
->desc('Regenerate MFA Recovery Codes')
|
||||
->groups(['api', 'account', 'mfaProtected'])
|
||||
->label('event', 'users.[userId].update.mfa')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.update')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'updateMfaRecoveryCodes')
|
||||
->label('sdk.description', '/docs/references/account/update-mfa-recovery-codes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MFA_RECOVERY_CODES)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->action(function (Database $dbForProject, Response $response, Document $user, Event $queueForEvents) {
|
||||
|
||||
$mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []);
|
||||
if (empty($mfaRecoveryCodes)) {
|
||||
throw new Exception(Exception::USER_RECOVERY_CODES_NOT_FOUND);
|
||||
}
|
||||
|
||||
$mfaRecoveryCodes = Type::generateBackupCodes();
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
$document = new Document([
|
||||
'recoveryCodes' => $mfaRecoveryCodes
|
||||
]);
|
||||
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::get('/v1/account/mfa/recovery-codes')
|
||||
->desc('Get MFA Recovery Codes')
|
||||
->groups(['api', 'account', 'mfaProtected'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'getMfaRecoveryCodes')
|
||||
->label('sdk.description', '/docs/references/account/get-mfa-recovery-codes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MFA_RECOVERY_CODES)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->action(function (Response $response, Document $user) {
|
||||
|
||||
$mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []);
|
||||
|
||||
if (empty($mfaRecoveryCodes)) {
|
||||
throw new Exception(Exception::USER_RECOVERY_CODES_NOT_FOUND);
|
||||
}
|
||||
|
||||
$document = new Document([
|
||||
'recoveryCodes' => $mfaRecoveryCodes
|
||||
]);
|
||||
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::delete('/v1/account/mfa/authenticators/:type')
|
||||
->desc('Delete Authenticator')
|
||||
->groups(['api', 'account'])
|
||||
|
@ -4063,7 +4144,11 @@ App::put('/v1/account/mfa/challenge')
|
|||
$sessionId = Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration);
|
||||
$session = $dbForProject->getDocument('sessions', $sessionId);
|
||||
|
||||
$dbForProject->updateDocument('sessions', $sessionId, $session->setAttribute('factors', $type, Document::SET_TYPE_APPEND));
|
||||
$session = $session
|
||||
->setAttribute('factors', $type, Document::SET_TYPE_APPEND)
|
||||
->setAttribute('mfaUpdatedAt', DateTime::now());
|
||||
|
||||
$dbForProject->updateDocument('sessions', $sessionId, $session);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
|
|
|
@ -1591,6 +1591,132 @@ App::get('/v1/users/:userId/mfa/factors')
|
|||
$response->dynamic($factors, Response::MODEL_MFA_FACTORS);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Get MFA Recovery Codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('usage.metric', 'users.{scope}.requests.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'getMfaRecoveryCodes')
|
||||
->label('sdk.description', '/docs/references/users/get-mfa-recovery-codes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MFA_RECOVERY_CODES)
|
||||
->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);
|
||||
}
|
||||
|
||||
$mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []);
|
||||
|
||||
if (empty($mfaRecoveryCodes)) {
|
||||
throw new Exception(Exception::USER_RECOVERY_CODES_NOT_FOUND);
|
||||
}
|
||||
|
||||
$document = new Document([
|
||||
'recoveryCodes' => $mfaRecoveryCodes
|
||||
]);
|
||||
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Create MFA Recovery Codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create.mfa.recovery-codes')
|
||||
->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', 'createMfaRecoveryCodes')
|
||||
->label('sdk.description', '/docs/references/users/create-mfa-recovery-codes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MFA_RECOVERY_CODES)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []);
|
||||
|
||||
if (!empty($mfaRecoveryCodes)) {
|
||||
throw new Exception(Exception::USER_RECOVERY_CODES_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$mfaRecoveryCodes = Type::generateBackupCodes();
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
$document = new Document([
|
||||
'recoveryCodes' => $mfaRecoveryCodes
|
||||
]);
|
||||
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Regenerate MFA Recovery Codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.mfa.recovery-codes')
|
||||
->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', 'updateMfaRecoveryCodes')
|
||||
->label('sdk.description', '/docs/references/users/update-mfa-recovery-codes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MFA_RECOVERY_CODES)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []);
|
||||
if (empty($mfaRecoveryCodes)) {
|
||||
throw new Exception(Exception::USER_RECOVERY_CODES_NOT_FOUND);
|
||||
}
|
||||
|
||||
$mfaRecoveryCodes = Type::generateBackupCodes();
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
$document = new Document([
|
||||
'recoveryCodes' => $mfaRecoveryCodes
|
||||
]);
|
||||
|
||||
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
->desc('Delete Authenticator')
|
||||
->groups(['api', 'users'])
|
||||
|
|
|
@ -7,6 +7,26 @@ use Appwrite\Extend\Exception;
|
|||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Database\DateTime;
|
||||
|
||||
App::init()
|
||||
->groups(['mfaProtected'])
|
||||
->inject('session')
|
||||
->action(function (Document $session) {
|
||||
$isSessionFresh = false;
|
||||
|
||||
$lastUpdate = $session->getAttribute('mfaUpdatedAt');
|
||||
if (!empty($lastUpdate)) {
|
||||
$now = DateTime::now();
|
||||
$maxAllowedDate = DateTime::addSeconds($lastUpdate, Auth::MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
|
||||
|
||||
$isSessionFresh = DateTime::formatTz($maxAllowedDate) >= DateTime::formatTz($now);
|
||||
}
|
||||
|
||||
if (!$isSessionFresh) {
|
||||
throw new Exception(Exception::USER_CHALLENGE_REQUIRED);
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
->groups(['auth'])
|
||||
|
|
1
docs/references/account/get-mfa-recovery-codes.md
Normal file
1
docs/references/account/get-mfa-recovery-codes.md
Normal file
|
@ -0,0 +1 @@
|
|||
Get recovery codes that can be used as backup for MFA flow. Before getting codes, they must be generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method. An OTP challenge is required to read recovery codes.
|
1
docs/references/account/update-mfa-recovery-codes.md
Normal file
1
docs/references/account/update-mfa-recovery-codes.md
Normal file
|
@ -0,0 +1 @@
|
|||
Regenerate recovery codes that can be used as backup for MFA flow. Before regenerating codes, they must be first generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method. An OTP challenge is required to regenreate recovery codes.
|
1
docs/references/users/create-mfa-recovery-codes.md
Normal file
1
docs/references/users/create-mfa-recovery-codes.md
Normal file
|
@ -0,0 +1 @@
|
|||
Generate recovery codes used as backup for MFA flow for User ID. Recovery codes can be used as a MFA verification type in [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) method by client SDK.
|
1
docs/references/users/get-mfa-recovery-codes.md
Normal file
1
docs/references/users/get-mfa-recovery-codes.md
Normal file
|
@ -0,0 +1 @@
|
|||
Get recovery codes that can be used as backup for MFA flow by User ID. Before getting codes, they must be generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method.
|
1
docs/references/users/update-mfa-recovery-codes.md
Normal file
1
docs/references/users/update-mfa-recovery-codes.md
Normal file
|
@ -0,0 +1 @@
|
|||
Regenerate recovery codes that can be used as backup for MFA flow by User ID. Before regenerating codes, they must be first generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method.
|
|
@ -86,6 +86,11 @@ class Auth
|
|||
public const TOKEN_LENGTH_OAUTH2 = 64;
|
||||
public const TOKEN_LENGTH_SESSION = 256;
|
||||
|
||||
/**
|
||||
* MFA
|
||||
*/
|
||||
public const MFA_RECENT_DURATION = 1800; // 30 mins
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
|
|
@ -92,6 +92,8 @@ class Exception extends \Exception
|
|||
public const USER_AUTHENTICATOR_NOT_FOUND = 'user_authenticator_not_found';
|
||||
public const USER_AUTHENTICATOR_ALREADY_VERIFIED = 'user_authenticator_already_verified';
|
||||
public const USER_RECOVERY_CODES_ALREADY_EXISTS = 'user_recovery_codes_already_exists';
|
||||
public const USER_RECOVERY_CODES_NOT_FOUND = 'user_recovery_codes_not_found';
|
||||
public const USER_CHALLENGE_REQUIRED = 'user_challenge_required';
|
||||
public const USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request';
|
||||
public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized';
|
||||
public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error';
|
||||
|
|
|
@ -173,6 +173,12 @@ class Session extends Model
|
|||
'default' => '',
|
||||
'example' => '5e5bb8c16897e',
|
||||
])
|
||||
->addRule('mfaUpdatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent date in ISO 8601 format when the session successfully passed MFA challenge.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
|
|
@ -234,6 +234,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(2, $response['body']['total']);
|
||||
$this->assertEquals($sessionId, $response['body']['sessions'][0]['$id']);
|
||||
$this->assertEmpty($response['body']['sessions'][0]['secret']);
|
||||
|
||||
$this->assertEquals('Windows', $response['body']['sessions'][0]['osName']);
|
||||
$this->assertEquals('WIN', $response['body']['sessions'][0]['osCode']);
|
||||
|
@ -1770,6 +1771,7 @@ class AccountCustomClientTest extends Scope
|
|||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals('123456', $response['body']['providerAccessToken']);
|
||||
$this->assertEquals('tuvwxyz', $response['body']['providerRefreshToken']);
|
||||
$this->assertGreaterThan(DateTime::addSeconds(new \DateTime(), 14400 - 5), $response['body']['providerAccessTokenExpiry']); // 5 seconds allowed networking delay
|
||||
|
@ -1805,6 +1807,7 @@ class AccountCustomClientTest extends Scope
|
|||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals($response['body']['provider'], 'anonymous');
|
||||
|
||||
$sessionID = $response['body']['$id'];
|
||||
|
@ -1817,6 +1820,7 @@ class AccountCustomClientTest extends Scope
|
|||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals($response['body']['provider'], 'anonymous');
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/97823askjdkasd80921371980', array_merge([
|
||||
|
|
Loading…
Reference in a new issue