8000 feat-anon-login by TorstenDittmann · Pull Request #914 · appwrite/appwrite · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat-anon-login #914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Mar 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGES.md 10000
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Version 0.8.0 (Not Released Yet)

## Features
- Anonymous login (#914)

- Added Anonymous Login ([RFC-010](https://github.com/appwrite/rfc/blob/main/010-anonymous-login.md), #914)
- Added new Environment Variable to enable or disable Anonymous Login
- Added events for functions and executions (#971)

## Breaking Changes
Expand Down
4 changes: 2 additions & 2 deletions app/config/collections.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@
'key' => 'email',
'type' => Database::SYSTEM_VAR_TYPE_EMAIL,
'default' => '',
'required' => true,
'required' => false,
'array' => false,
],
[
Expand All @@ -222,7 +222,7 @@
'key' => 'password',
'type' => Database::SYSTEM_VAR_TYPE_TEXT,
'default' => '',
'required' => true,
'required' => false,
'array' => false,
],
[
Expand Down
2 changes: 1 addition & 1 deletion app/config/variables.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
'default' => 'enabled',
'required' => false,
'question' => '',
],
]
],
],
[
Expand Down
156 changes: 149 additions & 7 deletions app/controllers/api/account.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
throw new Exception('Account already exists', 409);
}

Authorization::enable();
Authorization::reset();

Authorization::unsetRole('role:'.Auth::USER_ROLE_GUEST);
Authorization::setRole('user:'.$user->getId());
Expand Down Expand Up @@ -487,7 +487,7 @@
throw new Exception('Account already exists', 409);
}

Authorization::enable();
Authorization::reset();

if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
Expand Down Expand Up @@ -517,6 +517,15 @@
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));

$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password'));

if ($isAnonymousUser) {
$user
->setAttribute('name', $oauth2->getUserName($accessToken))
->setAttribute('email', $oauth2->getUserEmail($accessToken))
;
}

$user
->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID)
->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken)
Expand Down Expand Up @@ -566,6 +575,130 @@
;
});

App::post('/v1/account/sessions/anonymous')
->desc('Create Anonymous Session')
->groups(['api', 'account'])
->label('event', 'account.sessions.create')
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createAnonymousSession')
->label('sdk.description', '/docs/references/account/create-session-anonymous.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SESSION)
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->inject('request')
->inject('response')
->inject('locale')
->inject('user')
->inject('project')
->inject('projectDB')
->inject('geodb')
->inject('audits')
->action(function ($request, $response, $locale, $user, $project, $projectDB, $geodb, $audits) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Database\Document $project */
/** @var Appwrite\Database\Database $projectDB */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */

$protocol = $request->getProtocol();

if ($user->getId() || 'console' === $project->getId()) {
throw new Exception('Failed to create anonymous user.', 401);
}

Authorization::disable();
try {
$user = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_USERS,
'$permissions' => [
'read' => ['*'],
'write' => ['user:{self}']
],
'email' => null,
'emailVerification' => false,
'status' => Auth::USER_STATUS_UNACTIVATED,
'password' => null,
'passwordUpdate' => \time(),
'registration' => \time(),
'reset' => false,
'name' => null
]);
} catch (Exception $th) {
throw new Exception('Failed saving user to DB', 500);
}

Authorization::reset();

if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
}

// Create session token

$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$secret = Auth::tokenGenerator();
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session = new Document(array_merge(
[
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:' . $user['$id']], 'write' => ['user:' . $user['$id']]],
'userId' => $user->getId(),
'type' => Auth::TOKEN_TYPE_LOGIN,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expiry,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
],
$detector->getOS(),
$detector->getClient(),
$detector->getDevice()
));

$user->setAttribute('tokens', $session, Document::SET_TYPE_APPEND);

Authorization::setRole('user:'.$user->getId());

$user = $projectDB->updateDocument($user->getArrayCopy());

if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
}

$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.sessions.create')
->setParam('resource', 'users/'.$user->getId())
;

if (!Config::getParam('domainVerification')) {
$response
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}

$response
->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
->setStatusCode(Response::STATUS_CODE_CREATED)
;

$session
->setAttribute('current', true)
->setAttribute('countryName', (isset($countries[$session->getAttribute('countryCode')])) ? $countries[$session->getAttribute('countryCode')] : $locale->getText('locale.country.unknown'))
;

$response->dynamic($session, Response::MODEL_SESSION);
});

App::post('/v1/account/jwt')
->desc('Create Account JWT')
->groups(['api', 'account'])
Expand Down Expand Up @@ -880,7 +1013,12 @@
/** @var Appwrite\Database\Database $projectDB */
/** @var Appwrite\Event\Event $audits */

if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting

if (
!$isAnonymousUser &&
!Auth::passwordVerify($password, $user->getAttribute('password'))
) { // Double check user password
throw new Exception('Invalid credentials', 401);
}

Expand All @@ -898,10 +1036,14 @@

// TODO after this user needs to confirm mail again

$user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
'email' => $email,
'emailVerification' => false,
]));
$user = $projectDB->updateDocument(\array_merge(
$user->getArrayCopy(),
($isAnonymousUser ? [ 'password' => Auth::passwordHash($password) ] : []),
[
'email' => $email,
'emailVerification' => false,
]
));

if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
Expand Down
1 change: 1 addition & 0 deletions docs/references/account/create-session-anonymous.md
F438
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use this endpoint to allow a new user to register an anonymous account in your project. This route will also create a new session for the user. To allow the new user to convert an anonymous account to a normal account account, you need to update its [email and password](/docs/client/account#accountUpdateEmail).
3 changes: 2 additions & 1 deletion docs/references/account/update-email.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Update currently logged in user account email address. After changing user address, user confirmation status is being reset and a new confirmation mail is sent. For security measures, user password is required to complete this request.
Update currently logged in user account email address. After changing user address, user confirmation status is being reset and a new confirmation mail is sent. For security measures, user password is required to complete this request.
This endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.
37 changes: 33 additions & 4 deletions tests/e2e/Services/Account/AccountBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,39 @@ public function testCreateAccount():array

$this->assertEquals($response['headers']['status-code'], 409);

$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => '',
'password' => '',
]);

$this->assertEquals($response['headers']['status-code'], 400);

$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => $email,
'password' => '',
]);

$this->assertEquals($response['headers']['status-code'], 400);

$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => '',
'password' => $password,
]);

$this->assertEquals($response['headers']['status-code'], 400);

return [
'id' => $id,
'email' => $email,
Expand Down Expand Up @@ -372,7 +405,6 @@ public function testUpdateAccountName($data):array
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsNumeric($response['body']['registration']);
$this->assertEquals($response['body']['email'], $email);
Expand Down Expand Up @@ -440,7 +472,6 @@ public function testUpdateAccountPassword($data):array
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsNumeric($response['body']['registration']);
$this->assertEquals($response['body']['email'], $email);
Expand Down Expand Up @@ -507,7 +538,6 @@ public function testUpdateAccountEmail($data):array
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsNumeric($response['body']['registration']);
$this->assertEquals($response['body']['email'], $newEmail);
Expand Down Expand Up @@ -565,7 +595,6 @@ public function testUpdateAccountPrefs($data):array
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertEquals('prefValue1', $response['body']['prefs']['prefKey1']);
$this->assertEquals('prefValue2', $response['body']['prefs']['prefKey2']);

Expand Down
Loading
0