1
0
Fork 0
mirror of synced 2024-06-03 03:14:50 +12:00

Merge branch '0.8.x' of github.com:appwrite/appwrite into feat-database-indexing

This commit is contained in:
Eldad Fux 2021-05-15 04:20:12 +03:00
commit fe617a48a2
31 changed files with 575 additions and 247 deletions

3
.env
View file

@ -1,6 +1,9 @@
_APP_ENV=production _APP_ENV=production
_APP_ENV=development _APP_ENV=development
_APP_LOCALE=en _APP_LOCALE=en
_APP_CONSOLE_WHITELIST_ROOT=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_WHITELIST_IPS=
_APP_SYSTEM_EMAIL_NAME=Appwrite _APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io _APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io

View file

@ -1,33 +1,37 @@
# Version 0.8.0 (Not Released Yet) # Version 0.8.0 (Not Released Yet)
## Features ## Features
- Refactoring SSL generation to work on every request so no domain environment variable is required for SSL generation (#1133)
- Added Anonymous Login ([RFC-010](https://github.com/appwrite/rfc/blob/main/010-anonymous-login.md), #914) - Added Anonymous Login ([RFC-010](https://github.com/appwrite/rfc/blob/main/010-anonymous-login.md), #914)
- Added events for functions and executions (#971) - Added events for functions and executions (#971)
- Added JWT support (#784) - Added JWT support (#784)
- Added ARM support (#726) - Added ARM support (#726)
- Splitted token & session models to become 2 different internal entities (#922) - Split token & session models to become 2 different internal entities (#922)
- Added Dart 2.12 as a new Cloud Functions runtime (#989) - Added Dart 2.12 as a new Cloud Functions runtime (#989)
- Added option to disable email/password (#947) - Added option to disable email/password (#947)
- Added option to disable anonymous login (need to merge and apply changed) (#947) - Added option to disable anonymous login (need to merge and apply changed) (#947)
- Added option to disable JWT auth (#947) - Added option to disable JWT auth (#947)
- Added option to disable team invites (#947) - Added option to disable team invites (#947)
- Option to limit number of users (good for app launches + god account PR) (#947) - Option to limit number of users (good for app launches + root account PR) (#947)
- Added 2 new endpoints to the projects API to allow new settings - Added 2 new endpoints to the projects API to allow new settings
- Enabled 501 errors (Not Implemented) from the error handler - Enabled 501 errors (Not Implemented) from the error handler
- Added Python 3.9 as a new Cloud Functions runtime (#1044) - Added Python 3.9 as a new Cloud Functions runtime (#1044)
- Added Deno 1.8 as a new Cloud Functions runtime (#989) - Added Deno 1.8 as a new Cloud Functions runtime (#989)
- Upgraded to PHP 8.0 (#713) - Upgraded to PHP 8.0 (#713)
- ClamAV is now disabled by default to allow lower min requirments for Appwrite (#1064) - ClamAV is now disabled by default to allow lower min requirements for Appwrite (#1064)
- Added a new env var named `_APP_LOCALE` that allow to change the default `en` locale value (#1056) - Added a new env var named `_APP_LOCALE` that allow to change the default `en` locale value (#1056)
- Updated all the console bottom control to be consistent. Dropped the `+` icon (#1062) - Updated all the console bottom control to be consistent. Dropped the `+` icon (#1062)
- Added Response Models for Documents and Preferences (#1075, #1102) - Added Response Models for Documents and Preferences (#1075, #1102)
- Added new endpoint to update team membership roles (#1142)
## Bugs ## Bugs
- Fixed default value for HTTPS force option - Fixed default value for HTTPS force option
- Fixed form array casting in dashboard (#1070) - Fixed form array casting in dashboard (#1070)
- Fixed collection document rule form in dashboard (#1069) - Fixed collection document rule form in dashboard (#1069)
- Bugs in the Teams API:
- Fixed incorrect audit worker event names (#1143)
- Increased limit of memberships fetched in `createTeamMembership` to 2000 (#1143)
## Breaking Changes (Read before upgrading!) ## Breaking Changes (Read before upgrading!)

View file

@ -97,6 +97,13 @@ ENV _APP_SERVER=swoole \
_APP_DOMAIN_TARGET=localhost \ _APP_DOMAIN_TARGET=localhost \
_APP_HOME=https://appwrite.io \ _APP_HOME=https://appwrite.io \
_APP_EDITION=community \ _APP_EDITION=community \
_APP_CONSOLE_WHITELIST_ROOT=enabled \
_APP_CONSOLE_WHITELIST_EMAILS= \
_APP_CONSOLE_WHITELIST_IPS= \
_APP_SYSTEM_EMAIL_NAME= \
_APP_SYSTEM_EMAIL_ADDRESS= \
_APP_SYSTEM_RESPONSE_FORMAT= \
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS= \
_APP_OPTIONS_ABUSE=enabled \ _APP_OPTIONS_ABUSE=enabled \
_APP_OPTIONS_FORCE_HTTPS=disabled \ _APP_OPTIONS_FORCE_HTTPS=disabled \
_APP_OPENSSL_KEY_V1=your-secret-key \ _APP_OPENSSL_KEY_V1=your-secret-key \

View file

@ -46,7 +46,7 @@ $collections = [
'legalTaxId' => '', 'legalTaxId' => '',
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], 'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], 'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
'authWhitelistDomains' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null)) : [], 'usersAuthLimit' => (App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
], ],
Database::SYSTEM_COLLECTION_COLLECTIONS => [ Database::SYSTEM_COLLECTION_COLLECTIONS => [
'$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS,

View file

@ -197,6 +197,11 @@ return [
'model' => Response::MODEL_MEMBERSHIP, 'model' => Response::MODEL_MEMBERSHIP,
'note' => 'version >= 0.7', 'note' => 'version >= 0.7',
], ],
'teams.memberships.update' => [
'description' => 'This event triggers when a team membership is updated.',
'model' => Response::MODEL_MEMBERSHIP,
'note' => 'version >= 0.8',
],
'teams.memberships.update.status' => [ 'teams.memberships.update.status' => [
'description' => 'This event triggers when a team memberships status is updated.', 'description' => 'This event triggers when a team memberships status is updated.',
'model' => Response::MODEL_MEMBERSHIP, 'model' => Response::MODEL_MEMBERSHIP,

View file

@ -63,9 +63,17 @@ return [
'required' => true, 'required' => true,
'question' => 'Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.', 'question' => 'Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.',
], ],
[
'name' => '_APP_CONSOLE_WHITELIST_ROOT',
'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by invting them to your project. By default this option is enabled.',
'introduction' => '0.8.0',
'default' => 'enabled',
'required' => false,
'question' => '',
],
[ [
'name' => '_APP_CONSOLE_WHITELIST_EMAILS', 'name' => '_APP_CONSOLE_WHITELIST_EMAILS',
'description' => 'This option allows you to limit creation of users to Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma.', 'description' => 'This option allows you to limit creation of new users on the Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma.',
'introduction' => '', 'introduction' => '',
'default' => '', 'default' => '',
'required' => false, 'required' => false,

View file

@ -61,7 +61,6 @@ App::post('/v1/account')
if ('console' === $project->getId()) { if ('console' === $project->getId()) {
$whitlistEmails = $project->getAttribute('authWhitelistEmails'); $whitlistEmails = $project->getAttribute('authWhitelistEmails');
$whitlistIPs = $project->getAttribute('authWhitelistIPs'); $whitlistIPs = $project->getAttribute('authWhitelistIPs');
$whitlistDomains = $project->getAttribute('authWhitelistDomains');
if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) { if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) {
throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401); throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401);
@ -70,10 +69,6 @@ App::post('/v1/account')
if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) { if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) {
throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401); throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401);
} }
if (!empty($whitlistDomains) && !\in_array(\substr(\strrchr($email, '@'), 1), $whitlistDomains)) {
throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401);
}
} }
$limit = $project->getAttribute('usersAuthLimit', 0); $limit = $project->getAttribute('usersAuthLimit', 0);
@ -472,7 +467,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'emailVerification' => true, 'emailVerification' => true,
'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider
'password' => Auth::passwordHash(Auth::passwordGenerator()), 'password' => Auth::passwordHash(Auth::passwordGenerator()),
'passwordUpdate' => \time(), 'passwordUpdate' => 0,
'registration' => \time(), 'registration' => \time(),
'reset' => false, 'reset' => false,
'name' => $name, 'name' => $name,
@ -942,7 +937,7 @@ App::patch('/v1/account/password')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER) ->label('sdk.response.model', Response::MODEL_USER)
->param('password', '', new Password(), 'New user password. Must be between 6 to 32 chars.') ->param('password', '', new Password(), 'New user password. Must be between 6 to 32 chars.')
->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.') ->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.', true)
->inject('response') ->inject('response')
->inject('user') ->inject('user')
->inject('dbForInternal') ->inject('dbForInternal')
@ -953,11 +948,15 @@ App::patch('/v1/account/password')
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $audits */
if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password // Check old password only if its an existing user.
if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password
throw new Exception('Invalid credentials', 401); throw new Exception('Invalid credentials', 401);
} }
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('password', Auth::passwordHash($password))); $user = $dbForInternal->updateDocument('users', $user->getId(), $user
->setAttribute('password', Auth::passwordHash($password))
->setAttribute('passwordUpdate', \time())
);
$audits $audits
->setParam('userId', $user->getId()) ->setParam('userId', $user->getId())

View file

@ -272,7 +272,7 @@ App::get('/v1/health/anti-virus')
App::get('/v1/health/stats') // Currently only used internally App::get('/v1/health/stats') // Currently only used internally
->desc('Get System Stats') ->desc('Get System Stats')
->groups(['api', 'health']) ->groups(['api', 'health'])
->label('scope', 'god') ->label('scope', 'root')
// ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) // ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
// ->label('sdk.namespace', 'health') // ->label('sdk.namespace', 'health')
// ->label('sdk.method', 'getStats') // ->label('sdk.method', 'getStats')

View file

@ -289,7 +289,12 @@ App::post('/v1/teams/:teamId/memberships')
'emailVerification' => false, 'emailVerification' => false,
'status' => Auth::USER_STATUS_UNACTIVATED, 'status' => Auth::USER_STATUS_UNACTIVATED,
'password' => Auth::passwordHash(Auth::passwordGenerator()), 'password' => Auth::passwordHash(Auth::passwordGenerator()),
'passwordUpdate' => \time(), /**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
* old password
*/
'passwordUpdate' => 0,
'registration' => \time(), 'registration' => \time(),
'reset' => false, 'reset' => false,
'name' => $name, 'name' => $name,
@ -376,9 +381,9 @@ App::post('/v1/teams/:teamId/memberships')
->setParam('{{text-cta}}', '#ffffff') ->setParam('{{text-cta}}', '#ffffff')
; ;
if (!$isPrivilegedUser && !$isAppUser) { // No need in comfirmation when in admin or app mode if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode
$mails $mails
->setParam('event', 'teams.membership.create') ->setParam('event', 'teams.memberships.create')
->setParam('from', ($project->getId() === 'console') ? '' : \sprintf($locale->getText('account.emails.team'), $project->getAttribute('name'))) ->setParam('from', ($project->getId() === 'console') ? '' : \sprintf($locale->getText('account.emails.team'), $project->getAttribute('name')))
->setParam('recipient', $email) ->setParam('recipient', $email)
->setParam('name', $name) ->setParam('name', $name)
@ -390,7 +395,7 @@ App::post('/v1/teams/:teamId/memberships')
$audits $audits
->setParam('userId', $invitee->getId()) ->setParam('userId', $invitee->getId())
->setParam('event', 'teams.membership.create') ->setParam('event', 'teams.memberships.create')
->setParam('resource', 'teams/'.$teamId) ->setParam('resource', 'teams/'.$teamId)
; ;
@ -449,6 +454,72 @@ App::get('/v1/teams/:teamId/memberships')
]), Response::MODEL_MEMBERSHIP_LIST); ]), Response::MODEL_MEMBERSHIP_LIST);
}); });
App::patch('/v1/teams/:teamId/memberships/:membershipId')
->desc('Update Membership Roles')
->groups(['api', 'teams'])
->label('event', 'teams.memberships.update')
->label('scope', 'teams.write')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'updateMembershipRoles')
->label('sdk.description', '/docs/references/teams/update-team-membership-roles.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->param('teamId', '', new UID(), 'Team unique ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('roles', [], new ArrayList(new Key()), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.')
->inject('request')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('audits')
->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForInternal, $audits) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
$team = $dbForInternal->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
$membership = $dbForInternal->getDocument('memberships', $membershipId);
if ($membership->isEmpty()) {
throw new Exception('Membership not found', 404);
}
$profile = $dbForInternal->getDocument('users', $membership->getAttribute('userId'));
if ($profile->isEmpty()) {
throw new Exception('User not found', 404);
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$isOwner = Authorization::isRole('team:'.$team->getId().'/owner');;
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception('User is not allowed to modify roles', 401);
}
// Update the roles
$membership->setAttribute('roles', $roles);
$membership = $dbForInternal->updateDocument('memberships', $membership->getId(), $membership);
//TODO sync updated membership in the user $profile object using TYPE_REPLACE
$audits
->setParam('userId', $user->getId())
->setParam('event', 'teams.memberships.update')
->setParam('resource', 'teams/'.$teamId)
;
$response->dynamic2($membership, Response::MODEL_MEMBERSHIP);
});
App::patch('/v1/teams/:teamId/memberships/:membershipId/status') App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->desc('Update Team Membership Status') ->desc('Update Team Membership Status')
->groups(['api', 'teams']) ->groups(['api', 'teams'])
@ -506,7 +577,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
} }
if ($userId != $membership->getAttribute('userId')) { if ($userId != $membership->getAttribute('userId')) {
throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401);
} }
if (empty($user->getId())) { if (empty($user->getId())) {
@ -514,7 +585,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
} }
if ($membership->getAttribute('userId') !== $user->getId()) { if ($membership->getAttribute('userId') !== $user->getId()) {
throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401);
} }
$membership // Attach user to team $membership // Attach user to team
@ -560,7 +631,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$audits $audits
->setParam('userId', $user->getId()) ->setParam('userId', $user->getId())
->setParam('event', 'teams.membership.update') ->setParam('event', 'teams.memberships.update.status')
->setParam('resource', 'teams/'.$teamId) ->setParam('resource', 'teams/'.$teamId)
; ;
@ -653,7 +724,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$audits $audits
->setParam('userId', $membership->getAttribute('userId')) ->setParam('userId', $membership->getAttribute('userId'))
->setParam('event', 'teams.membership.delete') ->setParam('event', 'teams.memberships.delete')
->setParam('resource', 'teams/'.$teamId) ->setParam('resource', 'teams/'.$teamId)
; ;

View file

@ -22,14 +22,60 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $clients) { App::init(function ($utopia, $request, $response, $console, $project, $consoleDB, $user, $locale, $clients) {
/** @var Utopia\Swoole\Request $request */ /** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Database $consoleDB */
/** @var Appwrite\Database\Document $console */ /** @var Appwrite\Database\Document $console */
/** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $project */
/** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Document $user */
/** @var Utopia\Locale\Locale $locale */ /** @var Utopia\Locale\Locale $locale */
/** @var array $clients */ /** @var array $clients */
$domain = $request->getHostname();
$domains = Config::getParam('domains', []);
if (!array_key_exists($domain, $domains)) {
$domain = new Domain(!empty($domain) ? $domain : '');
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$domains[$domain->get()] = false;
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
} else {
Authorization::disable();
$dbDomain = $consoleDB->getCollectionFirst([
'limit' => 1,
'offset' => 0,
'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_CERTIFICATES,
'domain=' . $domain->get(),
],
]);
if (empty($dbDomain)) {
$dbDomain = [
'$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES,
'$permissions' => [
'read' => [],
'write' => [],
],
'domain' => $domain->get(),
];
$dbDomain = $consoleDB->createDocument($dbDomain);
Authorization::enable();
Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in ~30 seconds..'); // TODO move this to installation script
ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [
'document' => $dbDomain,
'domain' => $domain->get(),
'validateTarget' => false,
'validateCNAME' => false,
]);
}
$domains[$domain->get()] = true;
}
Config::setParam('domains', $domains);
}
$localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
@ -211,7 +257,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
} }
}, $user->getAttribute('memberships', [])); }, $user->getAttribute('memberships', []));
// TDOO Check if user is god // TDOO Check if user is root
if (!\in_array($scope, $scopes)) { if (!\in_array($scope, $scopes)) {
if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS !== $project->getCollection()) { // Check if permission is denied because project is missing if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS !== $project->getCollection()) { // Check if permission is denied because project is missing
@ -229,7 +275,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
throw new Exception('Password reset is required', 412); throw new Exception('Password reset is required', 412);
} }
}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'clients']); }, ['utopia', 'request', 'response', 'console', 'project', 'consoleDB', 'user', 'locale', 'clients']);
App::options(function ($request, $response) { App::options(function ($request, $response) {
/** @var Utopia\Swoole\Request $request */ /** @var Utopia\Swoole\Request $request */
@ -427,4 +473,4 @@ include_once __DIR__ . '/shared/web.php';
foreach (Config::getParam('services', []) as $service) { foreach (Config::getParam('services', []) as $service) {
include_once $service['controller']; include_once $service['controller'];
} }

View file

@ -1,5 +1,6 @@
<?php <?php
use Appwrite\Database\Database;
use Appwrite\Specification\Format\OpenAPI3; use Appwrite\Specification\Format\OpenAPI3;
use Appwrite\Specification\Format\Swagger2; use Appwrite\Specification\Format\Swagger2;
use Appwrite\Specification\Specification; use Appwrite\Specification\Specification;
@ -42,10 +43,38 @@ App::get('/')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->inject('response') ->inject('response')
->action(function ($response) { ->inject('consoleDB')
->inject('project')
->action(function ($response, $consoleDB, $project) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Database $consoleDB */
/** @var Appwrite\Database\Document $project */
$response->redirect('/auth/signin'); $response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Expires', 0)
->addHeader('Pragma', 'no-cache')
;
if ('console' === $project->getId() || $project->isEmpty()) {
$whitlistRoot = App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled');
if($whitlistRoot !== 'disabled') {
$consoleDB->getCollection([ // Count users
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_USERS,
],
]);
$sum = $consoleDB->getSum();
if($sum !== 0) {
return $response->redirect('/auth/signin');
}
}
}
$response->redirect('/auth/signup');
}); });
App::get('/auth/signin') App::get('/auth/signin')
@ -58,6 +87,10 @@ App::get('/auth/signin')
$page = new View(__DIR__.'/../../views/home/auth/signin.phtml'); $page = new View(__DIR__.'/../../views/home/auth/signin.phtml');
$page
->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'))
;
$layout $layout
->setParam('title', 'Sign In - '.APP_NAME) ->setParam('title', 'Sign In - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
@ -72,6 +105,10 @@ App::get('/auth/signup')
/** @var Utopia\View $layout */ /** @var Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/signup.phtml'); $page = new View(__DIR__.'/../../views/home/auth/signup.phtml');
$page
->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'))
;
$layout $layout
->setParam('title', 'Sign Up - '.APP_NAME) ->setParam('title', 'Sign Up - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
@ -87,6 +124,10 @@ App::get('/auth/recovery')
$page = new View(__DIR__.'/../../views/home/auth/recovery.phtml'); $page = new View(__DIR__.'/../../views/home/auth/recovery.phtml');
$page
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
;
$layout $layout
->setParam('title', 'Password Recovery - '.APP_NAME) ->setParam('title', 'Password Recovery - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);

View file

@ -109,18 +109,6 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
}); });
}); });
$domain = App::getEnv('_APP_DOMAIN', '');
Console::info('Issuing a TLS certificate for the master domain ('.$domain.') in 30 seconds.
Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script
ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [
'document' => [],
'domain' => $domain,
'validateTarget' => false,
'validateCNAME' => false,
]);
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) { $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
$request = new Request($swooleRequest); $request = new Request($swooleRequest);
$response = new Response($swooleResponse); $response = new Response($swooleResponse);

View file

@ -61,12 +61,12 @@ $cli
Console::log('🟢 Abuse protection is enabled'); Console::log('🟢 Abuse protection is enabled');
} }
$authWhitelistRoot = App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', null);
$authWhitelistEmails = App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null); $authWhitelistEmails = App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null);
$authWhitelistIPs = App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null); $authWhitelistIPs = App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null);
$authWhitelistDomains = App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null);
if(empty($authWhitelistEmails) if(empty($authWhitelistRoot)
&& empty($authWhitelistDomains) && empty($authWhitelistEmails)
&& empty($authWhitelistIPs) && empty($authWhitelistIPs)
) { ) {
Console::log('🔴 Console access limits are disabled'); Console::log('🔴 Console access limits are disabled');

View file

@ -8,11 +8,15 @@ use Utopia\Analytics\GoogleAnalytics;
use Utopia\CLI\Console; use Utopia\CLI\Console;
use Utopia\Config\Config; use Utopia\Config\Config;
use Utopia\View; use Utopia\View;
use Utopia\Validator\Text;
$cli $cli
->task('install') ->task('install')
->desc('Install Appwrite') ->desc('Install Appwrite')
->action(function () { ->param('httpPort', '', new Text(4), 'Server HTTP port', true)
->param('httpsPort', '', new Text(4), 'Server HTTPS port', true)
->param('interactive','Y', new Text(1), 'Run an interactive session', true)
->action(function ($httpPort, $httpsPort, $interactive) {
/** /**
* 1. Start - DONE * 1. Start - DONE
* 2. Check for older setup and get older version - DONE * 2. Check for older setup and get older version - DONE
@ -108,16 +112,20 @@ $cli
} }
} }
$httpPort = Console::confirm('Choose your server HTTP port: (default: '.$defaultHTTPPort.')'); if(empty($httpPort)) {
$httpPort = ($httpPort) ? $httpPort : $defaultHTTPPort; $httpPort = Console::confirm('Choose your server HTTP port: (default: '.$defaultHTTPPort.')');
$httpPort = ($httpPort) ? $httpPort : $defaultHTTPPort;
}
$httpsPort = Console::confirm('Choose your server HTTPS port: (default: '.$defaultHTTPSPort.')'); if(empty($httpsPort)) {
$httpsPort = ($httpsPort) ? $httpsPort : $defaultHTTPSPort; $httpsPort = Console::confirm('Choose your server HTTPS port: (default: '.$defaultHTTPSPort.')');
$httpsPort = ($httpsPort) ? $httpsPort : $defaultHTTPSPort;
}
$input = []; $input = [];
foreach($vars as $key => $var) { foreach($vars as $key => $var) {
if(!$var['required']) { if(!$var['required'] || !Console::isInteractive() || $interactive !== 'Y') {
$input[$var['name']] = $var['default']; $input[$var['name']] = $var['default'];
continue; continue;
} }

View file

@ -1,6 +1,6 @@
<?php <?php
$home = $this->getParam('home', ''); $home = $this->getParam('home', '');
$version = $this->getParam('version', '').'.'.APP_CACHE_BUSTER; $version = $this->getParam('version', '') . '.' . APP_CACHE_BUSTER;
?> ?>
<footer class="clear margin-top-large"> <footer class="clear margin-top-large">
<ul class="copyright pull-start"> <ul class="copyright pull-start">
@ -12,6 +12,14 @@ $version = $this->getParam('version', '').'.'.APP_CACHE_BUSTER;
data-analytics-label="GitHub Link" data-analytics-label="GitHub Link"
href="https://github.com/appwrite/appwrite" target="_blank" rel="noopener"><i class="icon-github-circled"></i> GitHub</a> href="https://github.com/appwrite/appwrite" target="_blank" rel="noopener"><i class="icon-github-circled"></i> GitHub</a>
</li> </li>
<li>
<a class="link-animation-enabled"
data-analytics
data-analytics-event="click"
data-analytics-category="console/footer"
data-analytics-label="Discord Link"
href="https://appwrite.io/discord" target="_blank" rel="noopener"><i class="icon-discord"></i> Discord</a>
</li>
<li> <li>
<a class="link-animation-enabled" <a class="link-animation-enabled"
data-analytics data-analytics

View file

@ -1,3 +1,6 @@
<?php
$smtpEnabled = $this->getParam('smtpEnabled', false);
?>
<div class="zone medium"> <div class="zone medium">
<h1 class="zone xl margin-bottom-large margin-top"> <h1 class="zone xl margin-bottom-large margin-top">
Password Recovery Password Recovery
@ -25,7 +28,13 @@
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}/auth/recovery/reset" /> <input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}/auth/recovery/reset" />
<button type="submit" class="btn btn-primary"><i class="fa fa-sign-in"></i> Recover</button> <?php if(!$smtpEnabled): ?>
<div class="box note padding-tiny warning margin-bottom text-align-center">
<i class="icon-warning"></i> SMTP connection is disabled. <a href="https://appwrite.io/docs/email-delivery" target="_blank" rel="noopener">Learn more <i class="icon-link-ext"></i></a>
</div>
<?php endif; ?>
<button type="submit" class="btn btn-primary"<?php if(!$smtpEnabled): ?> disabled<?php endif; ?>><i class="fa fa-sign-in"></i> Recover</button>
</form> </form>
</div> </div>

View file

@ -1,3 +1,6 @@
<?php
$root = ($this->getParam('root') !== 'disabled');
?>
<div class="zone medium" <div class="zone medium"
data-service="account.get" data-service="account.get"
data-name="account" data-name="account"
@ -43,7 +46,7 @@
<br /> <br />
<div class="text-line-high-large text-align-center"> <div class="text-line-high-large text-align-center">
<a href="/auth/recovery">Forgot password?</a> or don't have an account? <b><a href="/auth/signup">Sign up now</a></b> <a href="/auth/recovery">Forgot password?</a><?php if(!$root): ?> or don't have an account? <b><a href="/auth/signup">Sign up now</a></b><?php endif; ?>
</div> </div>
</div> </div>

View file

@ -1,3 +1,6 @@
<?php
$root = ($this->getParam('root') !== 'disabled');
?>
<div class="zone medium signup"> <div class="zone medium signup">
<h1 class="zone xl margin-bottom-large margin-top"> <h1 class="zone xl margin-bottom-large margin-top">
Sign Up Sign Up
@ -9,6 +12,7 @@
<form name="account.create" <form name="account.create"
data-analytics data-analytics
data-newsletter
data-analytics-activity data-analytics-activity
data-analytics-event="submit" data-analytics-event="submit"
data-analytics-category="home" data-analytics-category="home"
@ -23,6 +27,10 @@
data-failure-param-alert-text="Registration Failed. Please try again later" data-failure-param-alert-text="Registration Failed. Please try again later"
data-failure-param-alert-classname="error"> data-failure-param-alert-classname="error">
<?php if($root): ?>
<p>Please create your root account</p>
<?php endif; ?>
<label>Name</label> <label>Name</label>
<input name="name" type="text" autocomplete="name" placeholder="" required maxlength="128"> <input name="name" type="text" autocomplete="name" placeholder="" required maxlength="128">
@ -39,11 +47,20 @@
By signing up, you agree to the <a data-ls-attrs="href={{env.HOME}}/policy/terms" tabindex="-1" target="_blank" rel="noopener">Terms and Conditions</a> and <a data-ls-attrs="href={{env.HOME}}/policy/privacy" target="_blank" tabindex="-1" rel="noopener">Privacy Policy</a> By signing up, you agree to the <a data-ls-attrs="href={{env.HOME}}/policy/terms" tabindex="-1" target="_blank" rel="noopener">Terms and Conditions</a> and <a data-ls-attrs="href={{env.HOME}}/policy/privacy" target="_blank" tabindex="-1" rel="noopener">Privacy Policy</a>
</div> </div>
<div class="newsletter margin-top margin-bottom-large">
<div class="pull-start margin-end-small margin-bottom">
<input id="newsletter" type="checkbox" />
</div>
Sign up for our <a data-ls-attrs="href={{env.HOME}}/updates" tabindex="-1" target="_blank" rel="noopener">monthly newsletter</a> <span class="text-fade">(optional)</span>.
</div>
<button type="submit">Sign Up</button> <button type="submit">Sign Up</button>
</form> </form>
</div> </div>
<?php if(!$root): ?>
<div class="zone medium text-align-center"> <div class="zone medium text-align-center">
<a href="/auth/signin">Already have an account?</a> <a href="/auth/signin">Already have an account?</a>
</div> </div>
<?PHP endif; ?>

View file

@ -57,6 +57,7 @@ services:
environment: environment:
- _APP_ENV - _APP_ENV
- _APP_LOCALE - _APP_LOCALE
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS - _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS - _APP_CONSOLE_WHITELIST_IPS
- _APP_SYSTEM_EMAIL_NAME - _APP_SYSTEM_EMAIL_NAME

View file

@ -76,6 +76,9 @@ services:
environment: environment:
- _APP_ENV - _APP_ENV
- _APP_LOCALE - _APP_LOCALE
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS
- _APP_SYSTEM_EMAIL_NAME - _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS - _APP_SYSTEM_EMAIL_ADDRESS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS

View file

@ -1 +1 @@
Update currently logged in user password. For validation, user is required to pass in the new password, and the old password. Update currently logged in user password. For validation, user is required to pass in the new password, and the old password. For users created with OAuth and Team Invites, oldPassword is optional.

190
package-lock.json generated
View file

@ -8077,181 +8077,25 @@
} }
} }
}, },
"jest-runtime": { "is-extendable": {
"version": "26.4.2", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.4.2.tgz", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-4Pe7Uk5a80FnbHwSOk7ojNCJvz3Ks2CNQWT5Z7MJo4tX0jb3V/LThKvD9tKPNVNyeMH98J/nzGlcwc00R2dSHQ==", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-finite": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
"integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
"dev": true, "dev": true,
"requires": { "requires": {
"@jest/console": "^26.3.0", "number-is-nan": "^1.0.0"
"@jest/environment": "^26.3.0",
"@jest/fake-timers": "^26.3.0",
"@jest/globals": "^26.4.2",
"@jest/source-map": "^26.3.0",
"@jest/test-result": "^26.3.0",
"@jest/transform": "^26.3.0",
"@jest/types": "^26.3.0",
"@types/yargs": "^15.0.0",
"chalk": "^4.0.0",
"collect-v8-coverage": "^1.0.0",
"exit": "^0.1.2",
"glob": "^7.1.3",
"graceful-fs": "^4.2.4",
"jest-config": "^26.4.2",
"jest-haste-map": "^26.3.0",
"jest-message-util": "^26.3.0",
"jest-mock": "^26.3.0",
"jest-regex-util": "^26.0.0",
"jest-resolve": "^26.4.0",
"jest-snapshot": "^26.4.2",
"jest-util": "^26.3.0",
"jest-validate": "^26.4.2",
"slash": "^3.0.0",
"strip-bom": "^4.0.0",
"yargs": "^15.3.1"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
},
"strip-bom": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
"integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
"dev": true
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
"dev": true
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"dev": true,
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
} }
}, },
"is-fullwidth-code-point": { "is-fullwidth-code-point": {

View file

@ -2100,7 +2100,7 @@ element.dispatchEvent(new Event('looped'));};let template=(element.children.leng
else{if(debug){console.error('Missing template "'+source+'"');}} else{if(debug){console.error('Missing template "'+source+'"');}}
if(!init){view.render(element);} if(!init){view.render(element);}
return;} return;}
http.get(source).then(function(element){return function(data){element.innerHTML=data;view.render(element);element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));}}(element),function(){throw new Error('Failed loading template');});};check(true);for(let i=0;i<paths.length;i++){let path=paths[i].split('.');while(path.length){container.bind(element,path.join('.'),check);path.pop();}}}});window.ls.error=function(){return function(error){window.console.error(error);if(window.location.pathname!=='/console'){window.location='/console';}};};window.addEventListener("error",function(event){console.error("ERROR-EVENT:",event.error.message,event.error.stack);});document.addEventListener("account.deleteSession",function(){window.location="/auth/signin";});document.addEventListener("account.create",function(){let container=window.ls.container;let form=container.get('serviceForm');let sdk=container.get('console');let promise=sdk.account.createSession(form.email,form.password);container.set("serviceForm",{},true,true);promise.then(function(){window.location='/console';},function(error){window.location='/auth/signup?failure=1';});});(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;message.remove=function(){scope.remove(message.id);};scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;} http.get(source).then(function(element){return function(data){element.innerHTML=data;view.render(element);element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));}}(element),function(){throw new Error('Failed loading template');});};check(true);for(let i=0;i<paths.length;i++){let path=paths[i].split('.');while(path.length){container.bind(element,path.join('.'),check);path.pop();}}}});window.ls.error=function(){return function(error){window.console.error(error);if(window.location.pathname!=='/console'){window.location='/console';}};};window.addEventListener("error",function(event){console.error("ERROR-EVENT:",event.error.message,event.error.stack);});document.addEventListener("account.deleteSession",function(){window.location="/auth/signin";});document.addEventListener("account.create",function(){let container=window.ls.container;let form=container.get('serviceForm');let sdk=container.get('console');let promise=sdk.account.createSession(form.email,form.password);container.set("serviceForm",{},true,true);promise.then(function(){var subscribe=document.getElementById('newsletter').checked;if(subscribe){let alerts=container.get('alerts');let loaderId=alerts.add({text:'Loading...',class:""},0);fetch('https://appwrite.io/v1/newsletter/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:form.name,email:form.email,}),}).finally(function(){alerts.remove(loaderId);window.location='/console';});}else{window.location='/console';}},function(error){window.location='/auth/signup?failure=1';});});(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;message.remove=function(){scope.remove(message.id);};scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;}
if(time>0){window.setTimeout(function(message){return function(){scope.remove(message.id)}}(message),time);} if(time>0){window.setTimeout(function(message){return function(){scope.remove(message.id)}}(message),time);}
return message.id;},remove:function(id){let scope=this;for(let index=0;index<scope.list.length;index++){let obj=scope.list[index];if(obj.id===parseInt(id)){scope.counter--;if(typeof obj.callback==="function"){obj.callback();} return message.id;},remove:function(id){let scope=this;for(let index=0;index<scope.list.length;index++){let obj=scope.list[index];if(obj.id===parseInt(id)){scope.counter--;if(typeof obj.callback==="function"){obj.callback();}
scope.list.splice(index,1);};}}};},true,true);})(window);(function(window){"use strict";window.ls.container.set('appwrite',function(window,env){let config={endpoint:'https://appwrite.io/v1',};let http=function(document,env){let globalParams=[],globalHeaders=[];let addParam=function(url,param,value){let a=document.createElement('a'),regex=/(?:\?|&amp;|&)+([^=]+)(?:=([^&]*))*/g;let match,str=[];a.href=url;param=encodeURIComponent(param);while(match=regex.exec(a.search))if(param!==match[1])str.push(match[1]+(match[2]?"="+match[2]:""));str.push(param+(value?"="+encodeURIComponent(value):""));a.search=str.join("&");return a.href;};let buildQuery=function(params){let str=[];for(let p in params){if(params.hasOwnProperty(p)){str.push(encodeURIComponent(p)+"="+encodeURIComponent(params[p]));}} scope.list.splice(index,1);};}}};},true,true);})(window);(function(window){"use strict";window.ls.container.set('appwrite',function(window,env){let config={endpoint:'https://appwrite.io/v1',};let http=function(document,env){let globalParams=[],globalHeaders=[];let addParam=function(url,param,value){let a=document.createElement('a'),regex=/(?:\?|&amp;|&)+([^=]+)(?:=([^&]*))*/g;let match,str=[];a.href=url;param=encodeURIComponent(param);while(match=regex.exec(a.search))if(param!==match[1])str.push(match[1]+(match[2]?"="+match[2]:""));str.push(param+(value?"="+encodeURIComponent(value):""));a.search=str.join("&");return a.href;};let buildQuery=function(params){let str=[];for(let p in params){if(params.hasOwnProperty(p)){str.push(encodeURIComponent(p)+"="+encodeURIComponent(params[p]));}}

View file

@ -137,7 +137,7 @@ element.dispatchEvent(new Event('looped'));};let template=(element.children.leng
else{if(debug){console.error('Missing template "'+source+'"');}} else{if(debug){console.error('Missing template "'+source+'"');}}
if(!init){view.render(element);} if(!init){view.render(element);}
return;} return;}
http.get(source).then(function(element){return function(data){element.innerHTML=data;view.render(element);element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));}}(element),function(){throw new Error('Failed loading template');});};check(true);for(let i=0;i<paths.length;i++){let path=paths[i].split('.');while(path.length){container.bind(element,path.join('.'),check);path.pop();}}}});window.ls.error=function(){return function(error){window.console.error(error);if(window.location.pathname!=='/console'){window.location='/console';}};};window.addEventListener("error",function(event){console.error("ERROR-EVENT:",event.error.message,event.error.stack);});document.addEventListener("account.deleteSession",function(){window.location="/auth/signin";});document.addEventListener("account.create",function(){let container=window.ls.container;let form=container.get('serviceForm');let sdk=container.get('console');let promise=sdk.account.createSession(form.email,form.password);container.set("serviceForm",{},true,true);promise.then(function(){window.location='/console';},function(error){window.location='/auth/signup?failure=1';});});(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;message.remove=function(){scope.remove(message.id);};scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;} http.get(source).then(function(element){return function(data){element.innerHTML=data;view.render(element);element.dispatchEvent(new CustomEvent('template-loaded',{bubbles:true,cancelable:false}));}}(element),function(){throw new Error('Failed loading template');});};check(true);for(let i=0;i<paths.length;i++){let path=paths[i].split('.');while(path.length){container.bind(element,path.join('.'),check);path.pop();}}}});window.ls.error=function(){return function(error){window.console.error(error);if(window.location.pathname!=='/console'){window.location='/console';}};};window.addEventListener("error",function(event){console.error("ERROR-EVENT:",event.error.message,event.error.stack);});document.addEventListener("account.deleteSession",function(){window.location="/auth/signin";});document.addEventListener("account.create",function(){let container=window.ls.container;let form=container.get('serviceForm');let sdk=container.get('console');let promise=sdk.account.createSession(form.email,form.password);container.set("serviceForm",{},true,true);promise.then(function(){var subscribe=document.getElementById('newsletter').checked;if(subscribe){let alerts=container.get('alerts');let loaderId=alerts.add({text:'Loading...',class:""},0);fetch('https://appwrite.io/v1/newsletter/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:form.name,email:form.email,}),}).finally(function(){alerts.remove(loaderId);window.location='/console';});}else{window.location='/console';}},function(error){window.location='/auth/signup?failure=1';});});(function(window){"use strict";window.ls.container.set('alerts',function(window){return{list:[],ids:0,counter:0,max:5,add:function(message,time){var scope=this;message.id=scope.ids++;message.remove=function(){scope.remove(message.id);};scope.counter++;scope.list.unshift(message);if(scope.counter>scope.max){scope.list.pop();scope.counter--;}
if(time>0){window.setTimeout(function(message){return function(){scope.remove(message.id)}}(message),time);} if(time>0){window.setTimeout(function(message){return function(){scope.remove(message.id)}}(message),time);}
return message.id;},remove:function(id){let scope=this;for(let index=0;index<scope.list.length;index++){let obj=scope.list[index];if(obj.id===parseInt(id)){scope.counter--;if(typeof obj.callback==="function"){obj.callback();} return message.id;},remove:function(id){let scope=this;for(let index=0;index<scope.list.length;index++){let obj=scope.list[index];if(obj.id===parseInt(id)){scope.counter--;if(typeof obj.callback==="function"){obj.callback();}
scope.list.splice(index,1);};}}};},true,true);})(window);(function(window){"use strict";window.ls.container.set('appwrite',function(window,env){let config={endpoint:'https://appwrite.io/v1',};let http=function(document,env){let globalParams=[],globalHeaders=[];let addParam=function(url,param,value){let a=document.createElement('a'),regex=/(?:\?|&amp;|&)+([^=]+)(?:=([^&]*))*/g;let match,str=[];a.href=url;param=encodeURIComponent(param);while(match=regex.exec(a.search))if(param!==match[1])str.push(match[1]+(match[2]?"="+match[2]:""));str.push(param+(value?"="+encodeURIComponent(value):""));a.search=str.join("&");return a.href;};let buildQuery=function(params){let str=[];for(let p in params){if(params.hasOwnProperty(p)){str.push(encodeURIComponent(p)+"="+encodeURIComponent(params[p]));}} scope.list.splice(index,1);};}}};},true,true);})(window);(function(window){"use strict";window.ls.container.set('appwrite',function(window,env){let config={endpoint:'https://appwrite.io/v1',};let http=function(document,env){let globalParams=[],globalHeaders=[];let addParam=function(url,param,value){let a=document.createElement('a'),regex=/(?:\?|&amp;|&)+([^=]+)(?:=([^&]*))*/g;let match,str=[];a.href=url;param=encodeURIComponent(param);while(match=regex.exec(a.search))if(param!==match[1])str.push(match[1]+(match[2]?"="+match[2]:""));str.push(param+(value?"="+encodeURIComponent(value):""));a.search=str.join("&");return a.href;};let buildQuery=function(params){let str=[];for(let p in params){if(params.hasOwnProperty(p)){str.push(encodeURIComponent(p)+"="+encodeURIComponent(params[p]));}}

View file

@ -1,24 +1,24 @@
// Init // Init
window.ls.error = function() { window.ls.error = function () {
return function(error) { return function (error) {
window.console.error(error); window.console.error(error);
if(window.location.pathname !== '/console') { if (window.location.pathname !== '/console') {
window.location = '/console'; window.location = '/console';
} }
}; };
}; };
window.addEventListener("error", function(event) { window.addEventListener("error", function (event) {
console.error("ERROR-EVENT:", event.error.message, event.error.stack); console.error("ERROR-EVENT:", event.error.message, event.error.stack);
}); });
document.addEventListener("account.deleteSession", function() { document.addEventListener("account.deleteSession", function () {
window.location = "/auth/signin"; window.location = "/auth/signin";
}); });
document.addEventListener("account.create", function() { document.addEventListener("account.create", function () {
let container = window.ls.container; let container = window.ls.container;
let form = container.get('serviceForm'); let form = container.get('serviceForm');
let sdk = container.get('console'); let sdk = container.get('console');
@ -26,10 +26,29 @@ document.addEventListener("account.create", function() {
let promise = sdk.account.createSession(form.email, form.password); let promise = sdk.account.createSession(form.email, form.password);
container.set("serviceForm", {}, true, true); // Remove sensetive data when not needed container.set("serviceForm", {}, true, true); // Remove sensetive data when not needed
promise.then(function () { promise.then(function () {
var subscribe = document.getElementById('newsletter').checked;
if (subscribe) {
let alerts = container.get('alerts');
let loaderId = alerts.add({ text: 'Loading...', class: "" }, 0);
fetch('https://appwrite.io/v1/newsletter/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: form.name,
email: form.email,
}),
}).finally(function () {
alerts.remove(loaderId);
window.location = '/console';
});
} else {
window.location = '/console'; window.location = '/console';
}, function (error) { }
window.location = '/auth/signup?failure=1'; }, function (error) {
window.location = '/auth/signup?failure=1';
}); });
}); });

View file

@ -34,6 +34,12 @@ class User extends Model
'default' => 0, 'default' => 0,
'example' => 0, 'example' => 0,
]) ])
->addRule('passwordUpdate', [
'type' => self::TYPE_INTEGER,
'description' => 'Unix timestamp of the most recent password update',
'default' => 0,
'example' => 1592981250,
])
->addRule('email', [ ->addRule('email', [
'type' => self::TYPE_STRING, 'type' => self::TYPE_STRING,
'description' => 'User email address.', 'description' => 'User email address.',

View file

@ -154,4 +154,25 @@ trait ProjectCustom
return self::$project; return self::$project;
} }
public function getNewKey(array $scopes) {
$projectId = self::$project['$id'];
$key = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
'x-appwrite-project' => 'console',
], [
'name' => 'Demo Project Key',
'scopes' => $scopes,
]);
$this->assertEquals(201, $key['headers']['status-code']);
$this->assertNotEmpty($key['body']);
$this->assertNotEmpty($key['body']['secret']);
return $key['body']['secret'];
}
} }

View file

@ -509,6 +509,33 @@ trait AccountBase
$this->assertEquals($response['headers']['status-code'], 400); $this->assertEquals($response['headers']['status-code'], 400);
/**
* Existing user tries to update password by passing wrong old password -> SHOULD FAIL
*/
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [
'password' => 'new-password',
'oldPassword' => $password,
]);
$this->assertEquals($response['headers']['status-code'], 401);
/**
* Existing user tries to update password without passing old password -> SHOULD FAIL
*/
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [
'password' => 'new-password'
]);
$this->assertEquals($response['headers']['status-code'], 401);
$data['password'] = 'new-password'; $data['password'] = 'new-password';
return $data; return $data;

View file

@ -272,11 +272,10 @@ class AccountCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [ ]), [
'password' => 'new-password',
'oldPassword' => '', 'oldPassword' => '',
]); ]);
$this->assertEquals($response['headers']['status-code'], 400); $this->assertEquals(400, $response['headers']['status-code']);
return $session; return $session;
} }

View file

@ -43,6 +43,7 @@ trait TeamsBaseClient
$teamUid = $data['teamUid'] ?? ''; $teamUid = $data['teamUid'] ?? '';
$teamName = $data['teamName'] ?? ''; $teamName = $data['teamName'] ?? '';
$email = uniqid().'friend@localhost.test'; $email = uniqid().'friend@localhost.test';
$name = 'Friend User';
/** /**
* Test for SUCCESS * Test for SUCCESS
@ -52,7 +53,7 @@ trait TeamsBaseClient
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [ ], $this->getHeaders()), [
'email' => $email, 'email' => $email,
'name' => 'Friend User', 'name' => $name,
'roles' => ['admin', 'editor'], 'roles' => ['admin', 'editor'],
'url' => 'http://localhost:5000/join-us#title' 'url' => 'http://localhost:5000/join-us#title'
]); ]);
@ -68,7 +69,7 @@ trait TeamsBaseClient
$lastEmail = $this->getLastEmail(); $lastEmail = $this->getLastEmail();
$this->assertEquals($email, $lastEmail['to'][0]['address']); $this->assertEquals($email, $lastEmail['to'][0]['address']);
$this->assertEquals('Friend User', $lastEmail['to'][0]['name']); $this->assertEquals($name, $lastEmail['to'][0]['name']);
$this->assertEquals('Invitation to '.$teamName.' Team at '.$this->getProject()['name'], $lastEmail['subject']); $this->assertEquals('Invitation to '.$teamName.' Team at '.$this->getProject()['name'], $lastEmail['subject']);
$secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); $secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256);
@ -96,7 +97,7 @@ trait TeamsBaseClient
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [ ], $this->getHeaders()), [
'email' => 'dasdkaskdjaskdjasjkd', 'email' => 'dasdkaskdjaskdjasjkd',
'name' => 'Friend User', 'name' => $name,
'roles' => ['admin', 'editor'], 'roles' => ['admin', 'editor'],
'url' => 'http://localhost:5000/join-us#title' 'url' => 'http://localhost:5000/join-us#title'
]); ]);
@ -108,7 +109,7 @@ trait TeamsBaseClient
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [ ], $this->getHeaders()), [
'email' => $email, 'email' => $email,
'name' => 'Friend User', 'name' => $name,
'roles' => 'bad string', 'roles' => 'bad string',
'url' => 'http://localhost:5000/join-us#title' 'url' => 'http://localhost:5000/join-us#title'
]); ]);
@ -120,7 +121,7 @@ trait TeamsBaseClient
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [ ], $this->getHeaders()), [
'email' => $email, 'email' => $email,
'name' => 'Friend User', 'name' => $name,
'roles' => ['admin', 'editor'], 'roles' => ['admin', 'editor'],
'url' => 'http://example.com/join-us#title' // bad url 'url' => 'http://example.com/join-us#title' // bad url
]); ]);
@ -132,6 +133,8 @@ trait TeamsBaseClient
'secret' => $secret, 'secret' => $secret,
'membershipUid' => $membershipUid, 'membershipUid' => $membershipUid,
'userUid' => $userUid, 'userUid' => $userUid,
'email' => $email,
'name' => $name
]; ];
} }
@ -144,6 +147,8 @@ trait TeamsBaseClient
$secret = $data['secret'] ?? ''; $secret = $data['secret'] ?? '';
$membershipUid = $data['membershipUid'] ?? ''; $membershipUid = $data['membershipUid'] ?? '';
$userUid = $data['userUid'] ?? ''; $userUid = $data['userUid'] ?? '';
$email = $data['email'] ?? '';
$name = $data['name'] ?? '';
/** /**
* Test for SUCCESS * Test for SUCCESS
@ -164,6 +169,66 @@ trait TeamsBaseClient
$this->assertCount(2, $response['body']['roles']); $this->assertCount(2, $response['body']['roles']);
$this->assertIsInt($response['body']['joined']); $this->assertIsInt($response['body']['joined']);
$this->assertEquals(true, $response['body']['confirm']); $this->assertEquals(true, $response['body']['confirm']);
$session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']];
$data['session'] = $session;
/** [START] TESTS TO CHECK PASSWORD UPDATE OF NEW USER CREATED USING TEAM INVITE */
/**
* New User tries to update password without old password -> SHOULD PASS
*/
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [
'password' => 'new-password'
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsNumeric($response['body']['registration']);
$this->assertEquals($response['body']['email'], $email);
$this->assertEquals($response['body']['name'], $name);
/**
* New User again tries to update password with ONLY new password -> SHOULD FAIL
*/
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [
'password' => 'new-password',
]);
$this->assertEquals(401, $response['headers']['status-code']);
/**
* New User tries to update password by passing both old and new password -> SHOULD PASS
*/
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [
'password' => 'newer-password',
'oldPassword' => 'new-password'
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsNumeric($response['body']['registration']);
$this->assertEquals($response['body']['email'], $email);
$this->assertEquals($response['body']['name'], $name);
/** [END] TESTS TO CHECK PASSWORD UPDATE OF NEW USER CREATED USING TEAM INVITE */
/** /**
* Test for FAILURE * Test for FAILURE
@ -218,6 +283,81 @@ trait TeamsBaseClient
/** /**
* @depends testUpdateTeamMembership * @depends testUpdateTeamMembership
*/ */
public function testUpdateTeamMembershipRoles($data):array
{
$teamUid = $data['teamUid'] ?? '';
$membershipUid = $data['membershipUid'] ?? '';
$session = $data['session'] ?? '';
/**
* Test for SUCCESS
*/
$roles = ['admin', 'editor', 'uncle'];
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'roles' => $roles
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertCount(count($roles), $response['body']['roles']);
$this->assertEquals($roles[0], $response['body']['roles'][0]);
$this->assertEquals($roles[1], $response['body']['roles'][1]);
$this->assertEquals($roles[2], $response['body']['roles'][2]);
/**
* Test for unknown team
*/
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.'abc'.'/memberships/'.$membershipUid, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'roles' => $roles
]);
$this->assertEquals(404, $response['headers']['status-code']);
/**
* Test for unknown membership ID
*/
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.'abc', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'roles' => $roles
]);
$this->assertEquals(404, $response['headers']['status-code']);
/**
* Test for when a user other than the owner tries to update membership
*/
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid, [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
], [
'roles' => $roles
]);
$this->assertEquals(401, $response['headers']['status-code']);
$this->assertEquals('User is not allowed to modify roles', $response['body']['message']);
return $data;
}
/**
* @depends testUpdateTeamMembershipRoles
*/
public function testDeleteTeamMembership($data):array public function testDeleteTeamMembership($data):array
{ {
$teamUid = $data['teamUid'] ?? ''; $teamUid = $data['teamUid'] ?? '';
@ -249,4 +389,5 @@ trait TeamsBaseClient
return []; return [];
} }
} }

View file

@ -64,6 +64,7 @@ trait TeamsBaseServer
$this->assertEquals(true, $response['body']['confirm']); $this->assertEquals(true, $response['body']['confirm']);
$userUid = $response['body']['userId']; $userUid = $response['body']['userId'];
$membershipUid = $response['body']['$id'];
// $response = $this->client->call(Client::METHOD_GET, '/users/'.$userUid, array_merge([ // $response = $this->client->call(Client::METHOD_GET, '/users/'.$userUid, array_merge([
// 'content-type' => 'application/json', // 'content-type' => 'application/json',
@ -130,6 +131,55 @@ trait TeamsBaseServer
return [ return [
'teamUid' => $teamUid, 'teamUid' => $teamUid,
'userUid' => $userUid, 'userUid' => $userUid,
'membershipUid' => $membershipUid
]; ];
} }
/**
* @depends testCreateTeamMembership
*/
public function testUpdateMembershipRoles($data)
{
$teamUid = $data['teamUid'] ?? '';
$membershipUid = $data['membershipUid'] ?? '';
/**
* Test for SUCCESS
*/
$roles = ['admin', 'editor', 'uncle'];
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'roles' => $roles
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertCount(count($roles), $response['body']['roles']);
$this->assertEquals($roles[0], $response['body']['roles'][0]);
$this->assertEquals($roles[1], $response['body']['roles'][1]);
$this->assertEquals($roles[2], $response['body']['roles'][2]);
/**
* Test for FAILURE
*/
$apiKey = $this->getNewKey(['teams.read']);
$roles = ['admin', 'editor', 'uncle'];
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid, [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $apiKey
], [
'roles' => $roles
]);
$this->assertEquals(401, $response['headers']['status-code']);
}
} }