fix: token verification
This commit is contained in:
parent
dbf1874439
commit
e94631f730
|
@ -768,6 +768,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
}
|
||||
|
||||
// Add token for server platforms
|
||||
$loginSecret = Auth::tokenGenerator();
|
||||
|
||||
$token = new Document([
|
||||
|
@ -777,7 +778,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
'type' => Auth::TOKEN_TYPE_OAUTH2,
|
||||
'secret' => Auth::hash($loginSecret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'userAgent' => 'UNKNOWN',
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
|
@ -1139,14 +1140,12 @@ App::put('/v1/account/sessions/token')
|
|||
->action(function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($userFromRequest->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$token = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret);
|
||||
|
||||
if (!$token) {
|
||||
$verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), null, $secret);
|
||||
if (!$verifiedToken) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
|
@ -1157,6 +1156,8 @@ App::put('/v1/account/sessions/token')
|
|||
$record = $geodb->get($request->getIP());
|
||||
$secret = Auth::tokenGenerator();
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
|
||||
$provider = Auth::getSessionProviderByTokenType($verifiedToken->getAttribute('type'));
|
||||
|
||||
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
|
@ -1258,9 +1259,9 @@ App::put('/v1/account/sessions/magic-url')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$token = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), Auth::TOKEN_TYPE_MAGIC_URL, $secret);
|
||||
$verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), Auth::TOKEN_TYPE_MAGIC_URL, $secret);
|
||||
|
||||
if (!$token) {
|
||||
if (!$verifiedToken) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
|
@ -1305,7 +1306,7 @@ App::put('/v1/account/sessions/magic-url')
|
|||
* We act like we're updating and validating
|
||||
* the recovery token but actually we don't need it anymore.
|
||||
*/
|
||||
$dbForProject->deleteDocument('tokens', $token);
|
||||
$dbForProject->deleteDocument('tokens', $verifiedToken->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$user->setAttribute('emailVerification', true);
|
||||
|
@ -1509,9 +1510,9 @@ App::put('/v1/account/sessions/phone')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$token = Auth::phoneTokenVerify($userFromRequest->getAttribute('tokens', []), $secret);
|
||||
$verifiedToken = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), Auth::TOKEN_TYPE_PHONE, $secret);
|
||||
|
||||
if (!$token) {
|
||||
if (!$verifiedToken) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
|
@ -1554,7 +1555,7 @@ App::put('/v1/account/sessions/phone')
|
|||
* We act like we're updating and validating
|
||||
* the recovery token but actually we don't need it anymore.
|
||||
*/
|
||||
$dbForProject->deleteDocument('tokens', $token);
|
||||
$dbForProject->deleteDocument('tokens', $verifiedToken->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$user->setAttribute('phoneVerification', true);
|
||||
|
@ -2721,9 +2722,9 @@ App::put('/v1/account/recovery')
|
|||
}
|
||||
|
||||
$tokens = $profile->getAttribute('tokens', []);
|
||||
$recovery = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_RECOVERY, $secret);
|
||||
$verifiedToken = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_RECOVERY, $secret);
|
||||
|
||||
if (!$recovery) {
|
||||
if (!$verifiedToken) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
|
@ -2753,13 +2754,13 @@ App::put('/v1/account/recovery')
|
|||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
$recoveryDocument = $dbForProject->getDocument('tokens', $recovery);
|
||||
$recoveryDocument = $dbForProject->getDocument('tokens', $verifiedToken->getId());
|
||||
|
||||
/**
|
||||
* We act like we're updating and validating
|
||||
* the recovery token but actually we don't need it anymore.
|
||||
*/
|
||||
$dbForProject->deleteDocument('tokens', $recovery);
|
||||
$dbForProject->deleteDocument('tokens', $verifiedToken->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
|
||||
$events
|
||||
|
@ -2963,9 +2964,9 @@ App::put('/v1/account/verification')
|
|||
}
|
||||
|
||||
$tokens = $profile->getAttribute('tokens', []);
|
||||
$verification = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_VERIFICATION, $secret);
|
||||
$verifiedToken = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_VERIFICATION, $secret);
|
||||
|
||||
if (!$verification) {
|
||||
if (!$verifiedToken) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
|
@ -2975,13 +2976,13 @@ App::put('/v1/account/verification')
|
|||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
$verificationDocument = $dbForProject->getDocument('tokens', $verification);
|
||||
$verificationDocument = $dbForProject->getDocument('tokens', $verifiedToken->getId());
|
||||
|
||||
/**
|
||||
* We act like we're updating and validating
|
||||
* the verification token but actually we don't need it anymore.
|
||||
*/
|
||||
$dbForProject->deleteDocument('tokens', $verification);
|
||||
$dbForProject->deleteDocument('tokens', $verifiedToken->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
|
||||
$events
|
||||
|
@ -3119,9 +3120,9 @@ App::put('/v1/account/verification/phone')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$verification = Auth::phoneTokenVerify($user->getAttribute('tokens', []), $secret);
|
||||
$verifiedToken = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_PHONE, $secret);
|
||||
|
||||
if (!$verification) {
|
||||
if (!$verifiedToken) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
|
@ -3131,12 +3132,12 @@ App::put('/v1/account/verification/phone')
|
|||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
$verificationDocument = $dbForProject->getDocument('tokens', $verification);
|
||||
$verificationDocument = $dbForProject->getDocument('tokens', $verifiedToken->getId());
|
||||
|
||||
/**
|
||||
* We act like we're updating and validating the verification token but actually we don't need it anymore.
|
||||
*/
|
||||
$dbForProject->deleteDocument('tokens', $verification);
|
||||
$dbForProject->deleteDocument('tokens', $verifiedToken->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
|
||||
$events
|
||||
|
|
|
@ -1081,25 +1081,23 @@ App::patch('/v1/users/:userId/prefs')
|
|||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/token')
|
||||
App::post('/v1/users/:userId/tokens')
|
||||
->desc('Create universal token')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].sessions.create')
|
||||
->label('event', 'users.[userId].tokens.create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'session.create')
|
||||
->label('audits.event', 'tokens.create')
|
||||
->label('audits.resource', 'user/{request.userId}')
|
||||
->label('usage.metric', 'sessions.{scope}.requests.create')
|
||||
->label('usage.metric', 'tokens.requests.create')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'createSession')
|
||||
->label('sdk.method', 'createToken')
|
||||
->label('sdk.description', '/docs/references/users/create-user-token.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $events) {
|
||||
|
@ -1125,6 +1123,11 @@ App::post('/v1/users/:userId/token')
|
|||
|
||||
$token = $dbForProject->createDocument('tokens', $token);
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('tokenId', $token->getId())
|
||||
->setPayload($response->output($token, Response::MODEL_TOKEN));
|
||||
|
||||
return $response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($token, Response::MODEL_TOKEN);
|
||||
|
|
|
@ -121,6 +121,27 @@ class Auth
|
|||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Token type to session provider mapping.
|
||||
*/
|
||||
public static function getSessionProviderByTokenType(int $type): string
|
||||
{
|
||||
switch ($type) {
|
||||
case Auth::TOKEN_TYPE_VERIFICATION:
|
||||
case Auth::TOKEN_TYPE_RECOVERY:
|
||||
case Auth::TOKEN_TYPE_INVITE:
|
||||
return Auth::SESSION_PROVIDER_EMAIL;
|
||||
case Auth::TOKEN_TYPE_MAGIC_URL:
|
||||
return Auth::SESSION_PROVIDER_MAGIC_URL;
|
||||
case Auth::TOKEN_TYPE_PHONE:
|
||||
return Auth::SESSION_PROVIDER_PHONE;
|
||||
case Auth::TOKEN_TYPE_OAUTH2:
|
||||
return Auth::SESSION_PROVIDER_OAUTH2;
|
||||
default:
|
||||
return Auth::SESSION_PROVIDER_UNIVERSAL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Session.
|
||||
*
|
||||
|
@ -307,10 +328,10 @@ class Auth
|
|||
* Verify token and check that its not expired.
|
||||
*
|
||||
* @param array $tokens
|
||||
* @param int $type
|
||||
* @param int $type Type of token to verify, if null will verify any type
|
||||
* @param string $secret
|
||||
*
|
||||
* @return bool|string
|
||||
* @return bool|Document
|
||||
*/
|
||||
public static function tokenVerify(array $tokens, int $type = null, string $secret)
|
||||
{
|
||||
|
@ -319,30 +340,11 @@ class Auth
|
|||
if (
|
||||
$token->isSet('secret') &&
|
||||
$token->isSet('expire') &&
|
||||
$type === null || $token->getAttribute('type') === $type &&
|
||||
($type === null || $token->isSet('type') && $token->getAttribute('type') === $type) &&
|
||||
$token->getAttribute('secret') === self::hash($secret) &&
|
||||
DateTime::formatTz($token->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return (string)$token->getId();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function phoneTokenVerify(array $tokens, string $secret)
|
||||
{
|
||||
foreach ($tokens as $token) {
|
||||
/** @var Document $token */
|
||||
if (
|
||||
$token->isSet('type') &&
|
||||
$token->isSet('secret') &&
|
||||
$token->isSet('expire') &&
|
||||
$token->getAttribute('type') == Auth::TOKEN_TYPE_PHONE &&
|
||||
$token->getAttribute('secret') === self::hash($secret) &&
|
||||
DateTime::formatTz($token->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return (string) $token->getId();
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue