mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
teams update
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
@@ -18,6 +17,7 @@ use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Teams;
|
||||
@@ -28,6 +28,9 @@ use MaxMind\Db\Reader;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -88,8 +91,8 @@ App::post('/v1/teams')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = User::isAppUser($authorization->getRoles());
|
||||
|
||||
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
|
||||
|
||||
@@ -177,6 +180,7 @@ App::get('/v1/teams')
|
||||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject) {
|
||||
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -475,10 +479,9 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('roles', [], function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
;
|
||||
$roles = array_keys(Config::getParam('roles', []));
|
||||
array_filter($roles, function ($role) {
|
||||
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
|
||||
$roles = array_filter($roles, function ($role) {
|
||||
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
@@ -498,9 +501,11 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('timelimit')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
|
||||
$url = htmlentities($url);
|
||||
if (empty($url)) {
|
||||
@@ -570,6 +575,8 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$hash = $proofForPassword->hash($proofForPassword->generate());
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
@@ -590,9 +597,9 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
// TODO: Set password empty?
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'password' => $hash,
|
||||
'hash' => $proofForPassword->getHash()->getName(),
|
||||
'hashOptions' => $proofForPassword->getHash()->getOptions(),
|
||||
/**
|
||||
* Set the password update time to 0 for users created using
|
||||
* team invite and OAuth to allow password updates without an
|
||||
@@ -632,7 +639,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
Query::equal('teamInternalId', [$team->getSequence()]),
|
||||
]);
|
||||
|
||||
$secret = Auth::tokenGenerator();
|
||||
$secret = $proofForToken->generate();
|
||||
if ($membership->isEmpty()) {
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
@@ -652,7 +659,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
'invited' => DateTime::now(),
|
||||
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
|
||||
'confirm' => ($isPrivilegedUser || $isAppUser),
|
||||
'secret' => Auth::hash($secret),
|
||||
'secret' => $proofForToken->hash($secret),
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()])
|
||||
]);
|
||||
|
||||
@@ -663,9 +670,8 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
}
|
||||
|
||||
} elseif ($membership->getAttribute('confirm') === false) {
|
||||
$membership->setAttribute('secret', Auth::hash($secret));
|
||||
$membership->setAttribute('secret', $proofForToken->hash($secret));
|
||||
$membership->setAttribute('invited', DateTime::now());
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
@@ -768,7 +774,6 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->setName($invitee->getAttribute('name', ''))
|
||||
->setVariables($emailVariables)
|
||||
->trigger();
|
||||
|
||||
} elseif (!empty($phone)) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
@@ -934,7 +939,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
$isAppUser = User::isAppUser($roles);
|
||||
|
||||
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
|
||||
return $privacy || $isPrivilegedUser || $isAppUser;
|
||||
@@ -1026,7 +1031,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
$isAppUser = User::isAppUser($roles);
|
||||
|
||||
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
|
||||
return $privacy || $isPrivilegedUser || $isAppUser;
|
||||
@@ -1091,8 +1096,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->param('roles', [], function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
$roles = array_keys(Config::getParam('roles', []));
|
||||
array_filter($roles, function ($role) {
|
||||
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
|
||||
$roles = array_filter($roles, function ($role) {
|
||||
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
|
||||
});
|
||||
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}
|
||||
@@ -1122,8 +1127,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = User::isAppUser($authorization->getRoles());
|
||||
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
@@ -1209,7 +1214,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Document $project, Reader $geodb, Event $queueForEvents) {
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
@@ -1228,7 +1235,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
|
||||
}
|
||||
|
||||
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
|
||||
if (!$proofForToken->verify($secret, $membership->getAttribute('secret'))) {
|
||||
throw new Exception(Exception::TEAM_INVALID_SECRET);
|
||||
}
|
||||
|
||||
@@ -1262,9 +1269,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
|
||||
$secret = Auth::tokenGenerator();
|
||||
$secret = $proofForToken->generate();
|
||||
$session = new Document(array_merge([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
@@ -1274,9 +1281,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
],
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getSequence(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'provider' => SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $user->getAttribute('email'),
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['email'],
|
||||
@@ -1288,14 +1295,19 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$authorization->addRole(Role::user($userId)->toString());
|
||||
|
||||
$encoded = $store
|
||||
->setProperty('id', $user->getId())
|
||||
->setProperty('secret', $secret)
|
||||
->encode();
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie(
|
||||
name: Auth::$cookieName . '_legacy',
|
||||
value: Auth::encodeSession($user->getId(), $secret),
|
||||
name: $store->getKey() . '_legacy',
|
||||
value: $encoded,
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
@@ -1303,8 +1315,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
httponly: true
|
||||
)
|
||||
->addCookie(
|
||||
name: Auth::$cookieName,
|
||||
value: Auth::encodeSession($user->getId(), $secret),
|
||||
name: $store->getKey(),
|
||||
value: $encoded,
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
|
||||
Generated
+64
-72
@@ -7874,47 +7874,39 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.3.6",
|
||||
"version": "v8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a"
|
||||
"reference": "307d3cf852f5ead3618ac60ecbedbdd512c348b1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a",
|
||||
"reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/307d3cf852f5ead3618ac60ecbedbdd512c348b1",
|
||||
"reference": "307d3cf852f5ead3618ac60ecbedbdd512c348b1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
"php": ">=8.4",
|
||||
"symfony/polyfill-mbstring": "^1.0",
|
||||
"symfony/service-contracts": "^2.5|^3",
|
||||
"symfony/string": "^7.2"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/dependency-injection": "<6.4",
|
||||
"symfony/dotenv": "<6.4",
|
||||
"symfony/event-dispatcher": "<6.4",
|
||||
"symfony/lock": "<6.4",
|
||||
"symfony/process": "<6.4"
|
||||
"symfony/string": "^7.4|^8.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/log-implementation": "1.0|2.0|3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/config": "^6.4|^7.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0",
|
||||
"symfony/event-dispatcher": "^6.4|^7.0",
|
||||
"symfony/http-foundation": "^6.4|^7.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0",
|
||||
"symfony/lock": "^6.4|^7.0",
|
||||
"symfony/messenger": "^6.4|^7.0",
|
||||
"symfony/process": "^6.4|^7.0",
|
||||
"symfony/stopwatch": "^6.4|^7.0",
|
||||
"symfony/var-dumper": "^6.4|^7.0"
|
||||
"symfony/config": "^7.4|^8.0",
|
||||
"symfony/dependency-injection": "^7.4|^8.0",
|
||||
"symfony/event-dispatcher": "^7.4|^8.0",
|
||||
"symfony/http-foundation": "^7.4|^8.0",
|
||||
"symfony/http-kernel": "^7.4|^8.0",
|
||||
"symfony/lock": "^7.4|^8.0",
|
||||
"symfony/messenger": "^7.4|^8.0",
|
||||
"symfony/process": "^7.4|^8.0",
|
||||
"symfony/stopwatch": "^7.4|^8.0",
|
||||
"symfony/var-dumper": "^7.4|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -7948,7 +7940,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.6"
|
||||
"source": "https://github.com/symfony/console/tree/v8.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7968,29 +7960,29 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-04T01:21:42+00:00"
|
||||
"time": "2025-11-21T13:19:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v7.3.6",
|
||||
"version": "v8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "e9bcfd7837928ab656276fe00464092cc9e1826a"
|
||||
"reference": "7fc96ae83372620eaba3826874f46e26295768ca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a",
|
||||
"reference": "e9bcfd7837928ab656276fe00464092cc9e1826a",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/7fc96ae83372620eaba3826874f46e26295768ca",
|
||||
"reference": "7fc96ae83372620eaba3826874f46e26295768ca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"php": ">=8.4",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-mbstring": "~1.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/process": "^6.4|^7.0"
|
||||
"symfony/process": "^7.4|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -8018,7 +8010,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.3.6"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v8.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8038,27 +8030,27 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-05T09:52:27+00:00"
|
||||
"time": "2025-11-05T14:36:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v7.3.5",
|
||||
"version": "v8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "9f696d2f1e340484b4683f7853b273abff94421f"
|
||||
"reference": "7598dd5770580fa3517ec83e8da0c9b9e01f4291"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f",
|
||||
"reference": "9f696d2f1e340484b4683f7853b273abff94421f",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/7598dd5770580fa3517ec83e8da0c9b9e01f4291",
|
||||
"reference": "7598dd5770580fa3517ec83e8da0c9b9e01f4291",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2"
|
||||
"php": ">=8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/filesystem": "^6.4|^7.0"
|
||||
"symfony/filesystem": "^7.4|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -8086,7 +8078,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v7.3.5"
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8106,24 +8098,24 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-15T18:45:57+00:00"
|
||||
"time": "2025-11-05T14:36:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
"version": "v7.3.3",
|
||||
"version": "v8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/options-resolver.git",
|
||||
"reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d"
|
||||
"reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d",
|
||||
"reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7",
|
||||
"reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"php": ">=8.4",
|
||||
"symfony/deprecation-contracts": "^2.5|^3"
|
||||
},
|
||||
"type": "library",
|
||||
@@ -8157,7 +8149,7 @@
|
||||
"options"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v8.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8177,7 +8169,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-05T10:16:07+00:00"
|
||||
"time": "2025-11-12T15:55:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
@@ -8511,20 +8503,20 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.3.4",
|
||||
"version": "v8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
|
||||
"reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
|
||||
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/a0a750500c4ce900d69ba4e9faf16f82c10ee149",
|
||||
"reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2"
|
||||
"php": ">=8.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -8552,7 +8544,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.3.4"
|
||||
"source": "https://github.com/symfony/process/tree/v8.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8572,38 +8564,38 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
"time": "2025-10-16T16:25:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.3.4",
|
||||
"version": "v8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "f96476035142921000338bad71e5247fbc138872"
|
||||
"reference": "f929eccf09531078c243df72398560e32fa4cf4f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
|
||||
"reference": "f96476035142921000338bad71e5247fbc138872",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/f929eccf09531078c243df72398560e32fa4cf4f",
|
||||
"reference": "f929eccf09531078c243df72398560e32fa4cf4f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-intl-grapheme": "~1.0",
|
||||
"symfony/polyfill-intl-normalizer": "~1.0",
|
||||
"symfony/polyfill-mbstring": "~1.0"
|
||||
"php": ">=8.4",
|
||||
"symfony/polyfill-ctype": "^1.8",
|
||||
"symfony/polyfill-intl-grapheme": "^1.33",
|
||||
"symfony/polyfill-intl-normalizer": "^1.0",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/translation-contracts": "<2.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/emoji": "^7.1",
|
||||
"symfony/http-client": "^6.4|^7.0",
|
||||
"symfony/intl": "^6.4|^7.0",
|
||||
"symfony/emoji": "^7.4|^8.0",
|
||||
"symfony/http-client": "^7.4|^8.0",
|
||||
"symfony/intl": "^7.4|^8.0",
|
||||
"symfony/translation-contracts": "^2.5|^3.0",
|
||||
"symfony/var-exporter": "^6.4|^7.0"
|
||||
"symfony/var-exporter": "^7.4|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -8642,7 +8634,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.3.4"
|
||||
"source": "https://github.com/symfony/string/tree/v8.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8662,7 +8654,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-11T14:36:48+00:00"
|
||||
"time": "2025-09-11T14:37:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "textalk/websocket",
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Documents;
|
||||
|
||||
use Utopia\Auth\Proof;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Roles;
|
||||
|
||||
class User extends Document
|
||||
{
|
||||
public const ROLE_ANY = 'any';
|
||||
public const ROLE_GUESTS = 'guests';
|
||||
public const ROLE_USERS = 'users';
|
||||
public const ROLE_ADMIN = 'admin';
|
||||
public const ROLE_DEVELOPER = 'developer';
|
||||
public const ROLE_OWNER = 'owner';
|
||||
public const ROLE_APPS = 'apps';
|
||||
public const ROLE_SYSTEM = 'system';
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->getAttribute('email');
|
||||
}
|
||||
|
||||
public function getPhone(): ?string
|
||||
{
|
||||
return $this->getAttribute('phone');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all roles for a user.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getRoles(): array
|
||||
{
|
||||
$roles = [];
|
||||
|
||||
if (!$this->isPrivileged(Authorization::getRoles()) && !$this->isApp(Authorization::getRoles())) {
|
||||
if ($this->getId()) {
|
||||
$roles[] = Role::user($this->getId())->toString();
|
||||
$roles[] = Role::users()->toString();
|
||||
|
||||
$emailVerified = $this->getAttribute('emailVerification', false);
|
||||
$phoneVerified = $this->getAttribute('phoneVerification', false);
|
||||
|
||||
if ($emailVerified || $phoneVerified) {
|
||||
$roles[] = Role::user($this->getId(), Roles::DIMENSION_VERIFIED)->toString();
|
||||
$roles[] = Role::users(Roles::DIMENSION_VERIFIED)->toString();
|
||||
} else {
|
||||
$roles[] = Role::user($this->getId(), Roles::DIMENSION_UNVERIFIED)->toString();
|
||||
$roles[] = Role::users(Roles::DIMENSION_UNVERIFIED)->toString();
|
||||
}
|
||||
} else {
|
||||
return [Role::guests()->toString()];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getAttribute('memberships', []) as $node) {
|
||||
if (!isset($node['confirm']) || !$node['confirm']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($node['$id']) && isset($node['teamId'])) {
|
||||
$roles[] = Role::team($node['teamId'])->toString();
|
||||
$roles[] = Role::member($node['$id'])->toString();
|
||||
|
||||
if (isset($node['roles'])) {
|
||||
foreach ($node['roles'] as $nodeRole) { // Set all team roles
|
||||
$roles[] = Role::team($node['teamId'], $nodeRole)->toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getAttribute('labels', []) as $label) {
|
||||
$roles[] = 'label:' . $label;
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is anonymous.
|
||||
*
|
||||
* @param Document $this
|
||||
* @return bool
|
||||
*/
|
||||
public function isAnonymous(): bool
|
||||
{
|
||||
return is_null($this->getEmail())
|
||||
&& is_null($this->getPhone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Privileged User?
|
||||
*
|
||||
* @param array<string> $roles
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPrivileged(array $roles): bool
|
||||
{
|
||||
if (
|
||||
in_array(self::ROLE_OWNER, $roles) ||
|
||||
in_array(self::ROLE_DEVELOPER, $roles) ||
|
||||
in_array(self::ROLE_ADMIN, $roles)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is App User?
|
||||
*
|
||||
* @param array<string> $roles
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isApp(array $roles): bool
|
||||
{
|
||||
if (in_array(self::ROLE_APPS, $roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function tokenVerify(int $type = null, string $secret, Proof $proofForToken): false|Document
|
||||
{
|
||||
$tokens = $this->getAttribute('tokens', []);
|
||||
foreach ($tokens as $token) {
|
||||
if (
|
||||
$token->isSet('secret') &&
|
||||
$token->isSet('expire') &&
|
||||
$token->isSet('type') &&
|
||||
($type === null || $token->getAttribute('type') === $type) &&
|
||||
$proofForToken->verify($secret, $token->getAttribute('secret')) &&
|
||||
DateTime::formatTz($token->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify session and check that its not expired.
|
||||
*
|
||||
* @param array<Document> $sessions
|
||||
* @param string $secret
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function sessionVerify(string $secret, Token $proofForToken)
|
||||
{
|
||||
$sessions = $this->getAttribute('sessions', []);
|
||||
|
||||
foreach ($sessions as $session) {
|
||||
if (
|
||||
$session->isSet('secret') &&
|
||||
$session->isSet('provider') &&
|
||||
$session->isSet('expire') &&
|
||||
$proofForToken->verify($secret, $session->getAttribute('secret')) &&
|
||||
DateTime::formatTz(DateTime::format(new \DateTime($session->getAttribute('expire')))) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return $session->getId();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user