2019-05-09 06:54:39 +00:00
< ? php
2024-05-27 20:04:50 +00:00
use Ahc\Jwt\JWT ;
2021-05-06 22:31:05 +00:00
use Appwrite\Auth\Auth ;
2024-03-01 02:07:58 +00:00
use Appwrite\Auth\MFA\Type ;
use Appwrite\Auth\MFA\Type\TOTP ;
2021-05-06 22:31:05 +00:00
use Appwrite\Auth\Validator\Password ;
2024-03-06 17:34:21 +00:00
use Appwrite\Auth\Validator\PasswordDictionary ;
use Appwrite\Auth\Validator\PasswordHistory ;
use Appwrite\Auth\Validator\PersonalData ;
2022-06-08 12:50:31 +00:00
use Appwrite\Auth\Validator\Phone ;
2025-04-25 14:25:26 +00:00
use Appwrite\Deletes\Identities as DeleteIdentities ;
use Appwrite\Deletes\Targets as DeleteTargets ;
2022-01-18 11:05:04 +00:00
use Appwrite\Detector\Detector ;
2022-05-25 13:36:25 +00:00
use Appwrite\Event\Delete ;
use Appwrite\Event\Event ;
2024-03-06 17:34:21 +00:00
use Appwrite\Extend\Exception ;
use Appwrite\Hooks\Hooks ;
2022-01-18 11:05:04 +00:00
use Appwrite\Network\Validator\Email ;
2025-01-17 04:31:39 +00:00
use Appwrite\SDK\AuthType ;
use Appwrite\SDK\ContentType ;
use Appwrite\SDK\Method ;
use Appwrite\SDK\Response as SDKResponse ;
2022-01-18 11:05:04 +00:00
use Appwrite\Utopia\Database\Validator\CustomId ;
2023-05-18 01:11:45 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Identities ;
2025-03-13 09:48:39 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Memberships ;
2023-11-02 12:30:17 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Targets ;
2022-08-15 19:35:50 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Users ;
2023-12-11 16:24:24 +00:00
use Appwrite\Utopia\Request ;
2021-05-06 22:31:05 +00:00
use Appwrite\Utopia\Response ;
2024-03-06 17:34:21 +00:00
use MaxMind\Db\Reader ;
2020-06-28 17:31:21 +00:00
use Utopia\App ;
2019-12-29 09:47:55 +00:00
use Utopia\Audit\Audit ;
2022-01-18 11:05:04 +00:00
use Utopia\Config\Config ;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Database ;
use Utopia\Database\DateTime ;
use Utopia\Database\Document ;
use Utopia\Database\Exception\Duplicate ;
2025-04-16 11:46:39 +00:00
use Utopia\Database\Exception\Order as OrderException ;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Exception\Query as QueryException ;
2022-12-14 15:42:25 +00:00
use Utopia\Database\Helpers\ID ;
2022-12-14 16:04:06 +00:00
use Utopia\Database\Helpers\Permission ;
use Utopia\Database\Helpers\Role ;
2021-08-20 09:57:46 +00:00
use Utopia\Database\Query ;
2021-08-28 16:25:48 +00:00
use Utopia\Database\Validator\Authorization ;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Validator\Queries ;
2024-10-17 05:41:24 +00:00
use Utopia\Database\Validator\Query\Cursor ;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Validator\Query\Limit ;
use Utopia\Database\Validator\Query\Offset ;
use Utopia\Database\Validator\UID ;
use Utopia\Locale\Locale ;
2024-05-27 20:04:50 +00:00
use Utopia\System\System ;
2023-05-27 00:23:01 +00:00
use Utopia\Validator\ArrayList ;
2022-01-18 11:05:04 +00:00
use Utopia\Validator\Assoc ;
use Utopia\Validator\Boolean ;
2022-06-14 09:05:46 +00:00
use Utopia\Validator\Integer ;
2024-03-06 17:34:21 +00:00
use Utopia\Validator\Range ;
use Utopia\Validator\Text ;
use Utopia\Validator\WhiteList ;
2019-05-09 06:54:39 +00:00
2022-08-12 16:01:25 +00:00
/** TODO: Remove function when we move to using utopia/platform */
2024-10-29 10:58:57 +00:00
function createUser ( string $hash , mixed $hashOptions , string $userId , ? string $email , ? string $password , ? string $phone , string $name , Document $project , Database $dbForProject , Hooks $hooks ) : Document
2022-06-14 11:08:54 +00:00
{
2024-01-07 12:40:45 +00:00
$plaintextPassword = $password ;
2022-06-13 15:11:31 +00:00
$hashOptionsObject = ( \is_string ( $hashOptions )) ? \json_decode ( $hashOptions , true ) : $hashOptions ; // Cast to JSON array
2022-12-18 04:38:27 +00:00
$passwordHistory = $project -> getAttribute ( 'auths' , [])[ 'passwordHistory' ] ? ? 0 ;
2022-08-16 13:02:28 +00:00
2022-08-16 13:03:38 +00:00
if ( ! empty ( $email )) {
2022-08-16 13:02:28 +00:00
$email = \strtolower ( $email );
2023-05-18 01:11:45 +00:00
// Makes sure this email is not already used in another identity
$identityWithMatchingEmail = $dbForProject -> findOne ( 'identities' , [
Query :: equal ( 'providerEmail' , [ $email ]),
]);
2024-10-31 08:13:23 +00:00
if ( ! $identityWithMatchingEmail -> isEmpty ()) {
2023-05-18 01:11:45 +00:00
throw new Exception ( Exception :: USER_EMAIL_ALREADY_EXISTS );
}
2022-08-16 13:02:28 +00:00
}
2022-06-13 15:11:31 +00:00
try {
2022-08-19 04:49:34 +00:00
$userId = $userId == 'unique()'
? ID :: unique ()
: ID :: custom ( $userId );
2022-08-16 13:03:38 +00:00
2023-07-19 22:24:32 +00:00
if ( $project -> getAttribute ( 'auths' , [])[ 'personalDataCheck' ] ? ? false ) {
2024-02-26 04:50:52 +00:00
$personalDataValidator = new PersonalData (
$userId ,
$email ,
$name ,
$phone ,
strict : false ,
allowEmpty : true
);
2024-01-07 12:40:45 +00:00
if ( ! $personalDataValidator -> isValid ( $plaintextPassword )) {
2023-04-13 20:20:03 +00:00
throw new Exception ( Exception :: USER_PASSWORD_PERSONAL_DATA );
}
}
2022-12-16 10:29:20 +00:00
$password = ( ! empty ( $password )) ? ( $hash === 'plaintext' ? Auth :: passwordHash ( $password , $hash , $hashOptionsObject ) : $password ) : null ;
2024-01-04 15:26:15 +00:00
$user = new Document ([
2022-06-13 15:11:31 +00:00
'$id' => $userId ,
2022-08-19 04:49:34 +00:00
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: user ( $userId )),
Permission :: delete ( Role :: user ( $userId )),
],
2022-06-13 15:11:31 +00:00
'email' => $email ,
'emailVerification' => false ,
2022-08-16 13:02:28 +00:00
'phone' => $phone ,
'phoneVerification' => false ,
2022-06-13 15:11:31 +00:00
'status' => true ,
2023-05-27 00:23:01 +00:00
'labels' => [],
2022-12-16 10:29:20 +00:00
'password' => $password ,
2023-11-08 14:35:16 +00:00
'passwordHistory' => is_null ( $password ) || $passwordHistory === 0 ? [] : [ $password ],
2023-02-20 01:51:56 +00:00
'passwordUpdate' => ( ! empty ( $password )) ? DateTime :: now () : null ,
2022-06-13 15:11:31 +00:00
'hash' => $hash === 'plaintext' ? Auth :: DEFAULT_ALGO : $hash ,
2022-10-10 23:27:03 +00:00
'hashOptions' => $hash === 'plaintext' ? Auth :: DEFAULT_ALGO_OPTIONS : $hashOptionsObject + [ 'type' => $hash ],
2022-08-19 04:20:19 +00:00
'registration' => DateTime :: now (),
2022-06-13 15:11:31 +00:00
'reset' => false ,
'name' => $name ,
'prefs' => new \stdClass (),
'sessions' => null ,
'tokens' => null ,
'memberships' => null ,
2023-08-23 01:34:23 +00:00
'search' => implode ( ' ' , [ $userId , $email , $phone , $name ]),
2024-01-04 15:26:15 +00:00
]);
2024-01-08 17:08:17 +00:00
if ( $hash === 'plaintext' ) {
2024-01-07 12:40:45 +00:00
$hooks -> trigger ( 'passwordValidator' , [ $dbForProject , $project , $plaintextPassword , & $user , true ]);
2024-01-05 11:31:38 +00:00
}
2023-11-28 13:12:34 +00:00
2024-01-24 14:31:58 +00:00
$user = $dbForProject -> createDocument ( 'users' , $user );
2023-11-28 13:12:34 +00:00
if ( $email ) {
try {
$target = $dbForProject -> createDocument ( 'targets' , new Document ([
2024-02-16 04:07:16 +00:00
'$permissions' => [
Permission :: read ( Role :: user ( $user -> getId ())),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: user ( $user -> getId ())),
],
2023-11-28 13:12:34 +00:00
'userId' => $user -> getId (),
2025-05-26 05:42:11 +00:00
'userInternalId' => $user -> getSequence (),
2023-11-28 13:12:34 +00:00
'providerType' => 'email' ,
'identifier' => $email ,
]));
$user -> setAttribute ( 'targets' , [ ... $user -> getAttribute ( 'targets' , []), $target ]);
} catch ( Duplicate ) {
$existingTarget = $dbForProject -> findOne ( 'targets' , [
Query :: equal ( 'identifier' , [ $email ]),
]);
2024-10-07 02:40:01 +00:00
if ( ! $existingTarget -> isEmpty ()) {
2024-06-11 15:47:25 +00:00
$user -> setAttribute ( 'targets' , $existingTarget , Document :: SET_TYPE_APPEND );
2024-06-05 18:04:01 +00:00
}
2023-11-28 13:12:34 +00:00
}
}
if ( $phone ) {
try {
$target = $dbForProject -> createDocument ( 'targets' , new Document ([
2024-02-16 04:07:16 +00:00
'$permissions' => [
Permission :: read ( Role :: user ( $user -> getId ())),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: user ( $user -> getId ())),
],
2023-11-28 13:12:34 +00:00
'userId' => $user -> getId (),
2025-05-26 05:42:11 +00:00
'userInternalId' => $user -> getSequence (),
2023-11-28 13:12:34 +00:00
'providerType' => 'sms' ,
'identifier' => $phone ,
]));
$user -> setAttribute ( 'targets' , [ ... $user -> getAttribute ( 'targets' , []), $target ]);
} catch ( Duplicate ) {
$existingTarget = $dbForProject -> findOne ( 'targets' , [
Query :: equal ( 'identifier' , [ $phone ]),
]);
2024-10-07 02:40:01 +00:00
if ( ! $existingTarget -> isEmpty ()) {
2024-06-11 15:47:25 +00:00
$user -> setAttribute ( 'targets' , $existingTarget , Document :: SET_TYPE_APPEND );
2024-06-05 18:04:01 +00:00
}
2023-11-28 13:12:34 +00:00
}
}
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2022-06-13 15:11:31 +00:00
} catch ( Duplicate $th ) {
2022-08-14 16:23:30 +00:00
throw new Exception ( Exception :: USER_ALREADY_EXISTS );
2022-06-13 15:11:31 +00:00
}
return $user ;
}
2019-05-09 06:54:39 +00:00
2020-06-28 17:31:21 +00:00
App :: post ( '/v1/users' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Create user' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2020-02-05 06:31:34 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.create' )
2022-08-13 14:55:27 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'create' ,
description : '/docs/references/users/create-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_USER ,
)
]
))
2023-01-20 22:22:16 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2022-08-16 13:02:28 +00:00
-> param ( 'email' , null , new Email (), 'User email.' , true )
-> param ( 'phone' , null , new Phone (), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.' , true )
2023-02-20 07:08:27 +00:00
-> param ( 'password' , '' , fn ( $project , $passwordsDictionary ) => new PasswordDictionary ( $passwordsDictionary , $project -> getAttribute ( 'auths' , [])[ 'passwordDictionary' ] ? ? false ), 'Plain text user password. Must be at least 8 chars.' , true , [ 'project' , 'passwordsDictionary' ])
2020-09-10 14:40:14 +00:00
-> param ( 'name' , '' , new Text ( 128 ), 'User name. Max length: 128 chars.' , true )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2022-12-18 04:38:27 +00:00
-> inject ( 'project' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
2024-10-29 10:58:57 +00:00
-> action ( function ( string $userId , ? string $email , ? string $phone , ? string $password , string $name , Response $response , Document $project , Database $dbForProject , Hooks $hooks ) {
$user = createUser ( 'plaintext' , '{}' , $userId , $email , $password , $phone , $name , $project , $dbForProject , $hooks );
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $user , Response :: MODEL_USER );
2020-12-26 16:54:42 +00:00
});
2020-02-05 06:31:34 +00:00
2022-08-12 15:55:58 +00:00
App :: post ( '/v1/users/bcrypt' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Create user with bcrypt password' )
2022-06-14 09:05:46 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.create' )
2022-08-17 10:01:16 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'createBcryptUser' ,
description : '/docs/references/users/create-bcrypt-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_USER ,
)
]
))
2023-01-20 22:22:16 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2020-09-10 14:40:14 +00:00
-> param ( 'email' , '' , new Email (), 'User email.' )
2022-06-17 09:25:28 +00:00
-> param ( 'password' , '' , new Password (), 'User password hashed using Bcrypt.' )
2020-09-10 14:40:14 +00:00
-> param ( 'name' , '' , new Text ( 128 ), 'User name. Max length: 128 chars.' , true )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2022-12-18 04:38:27 +00:00
-> inject ( 'project' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
2024-10-29 10:58:57 +00:00
-> action ( function ( string $userId , string $email , string $password , string $name , Response $response , Document $project , Database $dbForProject , Hooks $hooks ) {
$user = createUser ( 'bcrypt' , '{}' , $userId , $email , $password , null , $name , $project , $dbForProject , $hooks );
2020-02-05 06:31:34 +00:00
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $user , Response :: MODEL_USER );
2022-06-14 09:05:46 +00:00
});
2020-02-05 06:31:34 +00:00
2022-08-14 16:29:07 +00:00
App :: post ( '/v1/users/md5' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Create user with MD5 password' )
2022-06-14 09:05:46 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.create' )
2022-08-17 10:01:16 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'createMD5User' ,
description : '/docs/references/users/create-md5-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_USER ,
)
]
))
2023-01-20 22:22:16 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2022-06-14 09:05:46 +00:00
-> param ( 'email' , '' , new Email (), 'User email.' )
-> param ( 'password' , '' , new Password (), 'User password hashed using MD5.' )
-> param ( 'name' , '' , new Text ( 128 ), 'User name. Max length: 128 chars.' , true )
-> inject ( 'response' )
2022-12-18 04:38:27 +00:00
-> inject ( 'project' )
2022-06-14 09:05:46 +00:00
-> inject ( 'dbForProject' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
2024-10-29 10:58:57 +00:00
-> action ( function ( string $userId , string $email , string $password , string $name , Response $response , Document $project , Database $dbForProject , Hooks $hooks ) {
$user = createUser ( 'md5' , '{}' , $userId , $email , $password , null , $name , $project , $dbForProject , $hooks );
2022-06-14 09:05:46 +00:00
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $user , Response :: MODEL_USER );
2022-06-14 09:05:46 +00:00
});
2022-08-14 16:29:07 +00:00
App :: post ( '/v1/users/argon2' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Create user with Argon2 password' )
2022-06-14 09:05:46 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.create' )
2022-08-17 10:01:16 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'createArgon2User' ,
description : '/docs/references/users/create-argon2-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_USER ,
)
]
))
2023-01-20 22:22:16 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2022-06-14 09:05:46 +00:00
-> param ( 'email' , '' , new Email (), 'User email.' )
-> param ( 'password' , '' , new Password (), 'User password hashed using Argon2.' )
-> param ( 'name' , '' , new Text ( 128 ), 'User name. Max length: 128 chars.' , true )
-> inject ( 'response' )
2022-12-18 04:38:27 +00:00
-> inject ( 'project' )
2022-06-14 09:05:46 +00:00
-> inject ( 'dbForProject' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
2024-10-29 10:58:57 +00:00
-> action ( function ( string $userId , string $email , string $password , string $name , Response $response , Document $project , Database $dbForProject , Hooks $hooks ) {
$user = createUser ( 'argon2' , '{}' , $userId , $email , $password , null , $name , $project , $dbForProject , $hooks );
2022-06-14 09:05:46 +00:00
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $user , Response :: MODEL_USER );
2022-06-14 09:05:46 +00:00
});
2022-08-14 16:29:07 +00:00
App :: post ( '/v1/users/sha' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Create user with SHA password' )
2022-06-14 09:05:46 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.create' )
2022-08-17 10:01:16 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'createSHAUser' ,
description : '/docs/references/users/create-sha-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_USER ,
)
]
))
2023-01-20 22:22:16 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2022-06-14 09:05:46 +00:00
-> param ( 'email' , '' , new Email (), 'User email.' )
-> param ( 'password' , '' , new Password (), 'User password hashed using SHA.' )
2022-06-14 10:40:51 +00:00
-> param ( 'passwordVersion' , '' , new WhiteList ([ 'sha1' , 'sha224' , 'sha256' , 'sha384' , 'sha512/224' , 'sha512/256' , 'sha512' , 'sha3-224' , 'sha3-256' , 'sha3-384' , 'sha3-512' ]), " Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512' " , true )
2022-06-14 09:05:46 +00:00
-> param ( 'name' , '' , new Text ( 128 ), 'User name. Max length: 128 chars.' , true )
-> inject ( 'response' )
2022-12-18 04:38:27 +00:00
-> inject ( 'project' )
2022-06-14 09:05:46 +00:00
-> inject ( 'dbForProject' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
2024-10-29 10:58:57 +00:00
-> action ( function ( string $userId , string $email , string $password , string $passwordVersion , string $name , Response $response , Document $project , Database $dbForProject , Hooks $hooks ) {
2022-06-14 09:05:46 +00:00
$options = '{}' ;
2022-06-14 11:08:54 +00:00
if ( ! empty ( $passwordVersion )) {
2022-06-14 10:40:51 +00:00
$options = '{"version":"' . $passwordVersion . '"}' ;
2020-06-30 11:09:28 +00:00
}
2020-02-05 06:31:34 +00:00
2024-10-29 10:58:57 +00:00
$user = createUser ( 'sha' , $options , $userId , $email , $password , null , $name , $project , $dbForProject , $hooks );
2021-08-16 07:56:18 +00:00
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $user , Response :: MODEL_USER );
2022-06-14 09:05:46 +00:00
});
2022-08-14 16:29:07 +00:00
App :: post ( '/v1/users/phpass' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Create user with PHPass password' )
2022-06-14 09:05:46 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.create' )
2022-08-17 10:01:16 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'createPHPassUser' ,
description : '/docs/references/users/create-phpass-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_USER ,
)
]
))
2023-01-20 22:22:16 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2022-06-14 09:05:46 +00:00
-> param ( 'email' , '' , new Email (), 'User email.' )
-> param ( 'password' , '' , new Password (), 'User password hashed using PHPass.' )
-> param ( 'name' , '' , new Text ( 128 ), 'User name. Max length: 128 chars.' , true )
-> inject ( 'response' )
2022-12-18 04:38:27 +00:00
-> inject ( 'project' )
2022-06-14 09:05:46 +00:00
-> inject ( 'dbForProject' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
2024-10-29 10:58:57 +00:00
-> action ( function ( string $userId , string $email , string $password , string $name , Response $response , Document $project , Database $dbForProject , Hooks $hooks ) {
$user = createUser ( 'phpass' , '{}' , $userId , $email , $password , null , $name , $project , $dbForProject , $hooks );
2022-06-14 09:05:46 +00:00
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $user , Response :: MODEL_USER );
2022-06-14 09:05:46 +00:00
});
2022-08-14 16:29:07 +00:00
App :: post ( '/v1/users/scrypt' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Create user with Scrypt password' )
2022-06-14 09:05:46 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.create' )
2022-08-17 10:01:16 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'createScryptUser' ,
description : '/docs/references/users/create-scrypt-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_USER ,
)
]
))
2023-01-20 22:22:16 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2022-06-14 09:05:46 +00:00
-> param ( 'email' , '' , new Email (), 'User email.' )
2022-06-17 09:25:28 +00:00
-> param ( 'password' , '' , new Password (), 'User password hashed using Scrypt.' )
2022-06-24 12:30:39 +00:00
-> param ( 'passwordSalt' , '' , new Text ( 128 ), 'Optional salt used to hash password.' )
2022-09-29 03:52:57 +00:00
-> param ( 'passwordCpu' , 8 , new Integer (), 'Optional CPU cost used to hash password.' )
-> param ( 'passwordMemory' , 14 , new Integer (), 'Optional memory cost used to hash password.' )
-> param ( 'passwordParallel' , 1 , new Integer (), 'Optional parallelization cost used to hash password.' )
-> param ( 'passwordLength' , 64 , new Integer (), 'Optional hash length used to hash password.' )
2022-06-14 09:05:46 +00:00
-> param ( 'name' , '' , new Text ( 128 ), 'User name. Max length: 128 chars.' , true )
-> inject ( 'response' )
2022-12-18 04:38:27 +00:00
-> inject ( 'project' )
2022-06-14 09:05:46 +00:00
-> inject ( 'dbForProject' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
2024-10-29 10:58:57 +00:00
-> action ( function ( string $userId , string $email , string $password , string $passwordSalt , int $passwordCpu , int $passwordMemory , int $passwordParallel , int $passwordLength , string $name , Response $response , Document $project , Database $dbForProject , Hooks $hooks ) {
2022-06-24 12:30:39 +00:00
$options = [
'salt' => $passwordSalt ,
'costCpu' => $passwordCpu ,
'costMemory' => $passwordMemory ,
'costParallel' => $passwordParallel ,
'length' => $passwordLength
];
2022-06-14 11:08:54 +00:00
2024-10-29 10:58:57 +00:00
$user = createUser ( 'scrypt' , \json_encode ( $options ), $userId , $email , $password , null , $name , $project , $dbForProject , $hooks );
2022-06-14 09:05:46 +00:00
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $user , Response :: MODEL_USER );
2022-06-14 09:05:46 +00:00
});
2022-08-14 16:29:07 +00:00
App :: post ( '/v1/users/scrypt-modified' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Create user with Scrypt modified password' )
2022-06-14 09:05:46 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.create' )
2022-08-17 10:01:16 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'createScryptModifiedUser' ,
description : '/docs/references/users/create-scrypt-modified-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_USER ,
)
]
))
2023-01-20 22:22:16 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2022-06-14 09:05:46 +00:00
-> param ( 'email' , '' , new Email (), 'User email.' )
2022-06-17 09:25:28 +00:00
-> param ( 'password' , '' , new Password (), 'User password hashed using Scrypt Modified.' )
2022-06-14 09:05:46 +00:00
-> param ( 'passwordSalt' , '' , new Text ( 128 ), 'Salt used to hash password.' )
-> param ( 'passwordSaltSeparator' , '' , new Text ( 128 ), 'Salt separator used to hash password.' )
-> param ( 'passwordSignerKey' , '' , new Text ( 128 ), 'Signer key used to hash password.' )
-> param ( 'name' , '' , new Text ( 128 ), 'User name. Max length: 128 chars.' , true )
-> inject ( 'response' )
2022-12-18 04:38:27 +00:00
-> inject ( 'project' )
2024-10-29 11:01:29 +00:00
-> inject ( 'dbForProject' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
2024-10-29 10:58:57 +00:00
-> action ( function ( string $userId , string $email , string $password , string $passwordSalt , string $passwordSaltSeparator , string $passwordSignerKey , string $name , Response $response , Document $project , Database $dbForProject , Hooks $hooks ) {
$user = createUser ( 'scryptMod' , '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}' , $userId , $email , $password , null , $name , $project , $dbForProject , $hooks );
2022-04-04 06:30:07 +00:00
2022-09-07 11:02:36 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $user , Response :: MODEL_USER );
2020-12-26 16:54:42 +00:00
});
2020-02-05 06:31:34 +00:00
2023-08-17 11:54:45 +00:00
App :: post ( '/v1/users/:userId/targets' )
2024-09-03 16:22:30 +00:00
-> desc ( 'Create user target' )
2023-08-17 11:54:45 +00:00
-> groups ([ 'api' , 'users' ])
2023-10-25 17:33:23 +00:00
-> label ( 'audits.event' , 'target.create' )
2023-09-20 18:07:10 +00:00
-> label ( 'audits.resource' , 'target/response.$id' )
2023-10-31 18:23:46 +00:00
-> label ( 'event' , 'users.[userId].targets.[targetId].create' )
2023-08-17 11:54:45 +00:00
-> label ( 'scope' , 'targets.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'targets' ,
2025-01-17 04:31:39 +00:00
name : 'createTarget' ,
description : '/docs/references/users/create-target.md' ,
auth : [ AuthType :: KEY , AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_TARGET ,
)
]
))
2023-11-14 12:44:07 +00:00
-> param ( 'targetId' , '' , new CustomId (), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2023-08-23 20:24:25 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2023-11-29 04:05:37 +00:00
-> param ( 'providerType' , '' , new WhiteList ([ MESSAGE_TYPE_EMAIL , MESSAGE_TYPE_SMS , MESSAGE_TYPE_PUSH ]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.' )
2023-08-23 20:24:25 +00:00
-> param ( 'identifier' , '' , new Text ( Database :: LENGTH_KEY ), 'The target identifier (token, email, phone etc.)' )
2023-11-14 12:44:07 +00:00
-> param ( 'providerId' , '' , new UID (), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.' , true )
2023-11-28 13:12:34 +00:00
-> param ( 'name' , '' , new Text ( 128 ), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.' , true )
2023-10-31 18:23:46 +00:00
-> inject ( 'queueForEvents' )
2023-08-17 11:54:45 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-11-28 13:12:34 +00:00
-> action ( function ( string $targetId , string $userId , string $providerType , string $identifier , string $providerId , string $name , Event $queueForEvents , Response $response , Database $dbForProject ) {
2023-11-14 12:44:07 +00:00
$targetId = $targetId == 'unique()' ? ID :: unique () : $targetId ;
2024-03-08 11:42:15 +00:00
$provider = $dbForProject -> getDocument ( 'providers' , $providerId );
2023-08-17 11:54:45 +00:00
2023-11-21 09:41:09 +00:00
switch ( $providerType ) {
case 'email' :
$validator = new Email ();
if ( ! $validator -> isValid ( $identifier )) {
throw new Exception ( Exception :: GENERAL_INVALID_EMAIL );
}
break ;
2023-11-29 04:05:37 +00:00
case MESSAGE_TYPE_SMS :
2023-11-21 09:41:09 +00:00
$validator = new Phone ();
if ( ! $validator -> isValid ( $identifier )) {
throw new Exception ( Exception :: GENERAL_INVALID_PHONE );
}
break ;
2023-11-29 04:05:37 +00:00
case MESSAGE_TYPE_PUSH :
2023-11-21 09:49:19 +00:00
break ;
2023-11-21 09:41:09 +00:00
default :
throw new Exception ( Exception :: PROVIDER_INCORRECT_TYPE );
2023-11-21 09:30:02 +00:00
}
2023-08-17 11:54:45 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2023-08-17 13:32:07 +00:00
if ( $user -> isEmpty ()) {
2023-08-17 11:54:45 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
}
$target = $dbForProject -> getDocument ( 'targets' , $targetId );
2023-08-23 20:23:52 +00:00
if ( ! $target -> isEmpty ()) {
2023-08-17 11:54:45 +00:00
throw new Exception ( Exception :: USER_TARGET_ALREADY_EXISTS );
}
2023-10-06 13:53:46 +00:00
try {
$target = $dbForProject -> createDocument ( 'targets' , new Document ([
'$id' => $targetId ,
2024-02-16 04:07:16 +00:00
'$permissions' => [
Permission :: read ( Role :: user ( $user -> getId ())),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: user ( $user -> getId ())),
],
2024-03-08 11:42:15 +00:00
'providerId' => empty ( $provider -> getId ()) ? null : $provider -> getId (),
2025-05-26 05:42:11 +00:00
'providerInternalId' => $provider -> isEmpty () ? null : $provider -> getSequence (),
2023-11-14 12:44:07 +00:00
'providerType' => $providerType ,
2023-10-06 13:53:46 +00:00
'userId' => $userId ,
2025-05-26 05:42:11 +00:00
'userInternalId' => $user -> getSequence (),
2023-10-06 13:53:46 +00:00
'identifier' => $identifier ,
2023-11-28 13:12:34 +00:00
'name' => ( $name !== '' ) ? $name : null ,
2023-10-06 13:53:46 +00:00
]));
} catch ( Duplicate ) {
throw new Exception ( Exception :: USER_TARGET_ALREADY_EXISTS );
}
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2023-10-05 11:27:48 +00:00
2023-10-31 18:23:46 +00:00
$queueForEvents
-> setParam ( 'userId' , $user -> getId ())
-> setParam ( 'targetId' , $target -> getId ());
2023-08-17 11:54:45 +00:00
$response
2023-10-05 11:27:48 +00:00
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $target , Response :: MODEL_TARGET );
2023-08-17 11:54:45 +00:00
});
2020-06-28 17:31:21 +00:00
App :: get ( '/v1/users' )
2023-08-01 15:26:48 +00:00
-> desc ( 'List users' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2019-05-09 06:54:39 +00:00
-> label ( 'scope' , 'users.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'list' ,
description : '/docs/references/users/list-users.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER_LIST ,
)
]
))
2023-03-29 19:38:39 +00:00
-> param ( 'queries' , [], new Users (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Users :: ALLOWED_ATTRIBUTES ), true )
2020-09-10 14:40:14 +00:00
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-08-25 10:28:13 +00:00
-> action ( function ( array $queries , string $search , Response $response , Database $dbForProject ) {
2021-05-06 22:31:05 +00:00
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2021-08-06 12:36:48 +00:00
2022-08-11 23:53:52 +00:00
if ( ! empty ( $search )) {
2022-08-15 19:35:50 +00:00
$queries [] = Query :: search ( 'search' , $search );
2021-08-06 12:36:48 +00:00
}
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
2023-08-22 03:25:55 +00:00
$cursor = \array_filter ( $queries , function ( $query ) {
2023-12-06 14:10:40 +00:00
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
2023-08-22 03:25:55 +00:00
});
2022-08-30 23:31:43 +00:00
$cursor = reset ( $cursor );
2022-08-30 11:55:23 +00:00
if ( $cursor ) {
2022-08-15 19:35:50 +00:00
/** @var Query $cursor */
2024-10-17 05:41:24 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2022-08-15 19:35:50 +00:00
$userId = $cursor -> getValue ();
$cursorDocument = $dbForProject -> getDocument ( 'users' , $userId );
2021-08-14 18:56:28 +00:00
2022-08-11 23:53:52 +00:00
if ( $cursorDocument -> isEmpty ()) {
2022-08-15 19:35:50 +00:00
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " User ' { $userId } ' for the 'cursor' value not found. " );
2022-08-11 23:53:52 +00:00
}
2022-08-15 19:35:50 +00:00
$cursor -> setValue ( $cursorDocument );
2021-08-14 18:56:28 +00:00
}
2025-08-11 09:26:10 +00:00
$users = [];
$total = 0 ;
$dbForProject -> skipFilters ( function () use ( $dbForProject , $queries , & $users , & $total ) {
try {
$users = $dbForProject -> find ( 'users' , $queries );
$total = $dbForProject -> count ( 'users' , $queries , APP_LIMIT_COUNT );
} catch ( OrderException $e ) {
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order attribute ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all documents order attribute values are non-null. " );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
}, [ 'subQueryAuthenticators' , 'subQuerySessions' , 'subQueryTokens' , 'subQueryChallenges' , 'subQueryMemberships' ]);
2021-07-25 14:47:18 +00:00
$response -> dynamic ( new Document ([
2025-04-17 04:46:26 +00:00
'users' => $users ,
'total' => $total ,
2020-10-30 19:53:27 +00:00
]), Response :: MODEL_USER_LIST );
2020-12-26 16:54:42 +00:00
});
2019-05-09 06:54:39 +00:00
2020-06-28 17:31:21 +00:00
App :: get ( '/v1/users/:userId' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Get user' )
2020-06-28 12:18:16 +00:00
-> groups ([ 'api' , 'users' ])
2019-05-09 06:54:39 +00:00
-> label ( 'scope' , 'users.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'get' ,
description : '/docs/references/users/get-user.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-08-17 02:35:55 +00:00
-> action ( function ( string $userId , Response $response , Database $dbForProject ) {
2022-08-15 17:33:44 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
2022-10-14 16:17:00 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2022-08-15 17:33:44 +00:00
}
2021-07-25 14:47:18 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
2022-08-15 17:33:44 +00:00
});
2020-06-28 17:31:21 +00:00
App :: get ( '/v1/users/:userId/prefs' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Get user preferences' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2019-05-09 06:54:39 +00:00
-> label ( 'scope' , 'users.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'getPrefs' ,
description : '/docs/references/users/get-user-prefs.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_PREFERENCES ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-08-11 01:43:02 +00:00
-> action ( function ( string $userId , Response $response , Database $dbForProject ) {
2019-05-09 06:54:39 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2019-05-09 06:54:39 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-06-30 11:09:28 +00:00
}
2019-05-09 06:54:39 +00:00
2023-04-12 16:02:43 +00:00
$prefs = $user -> getAttribute ( 'prefs' , []);
2019-05-09 06:54:39 +00:00
2021-07-25 14:47:18 +00:00
$response -> dynamic ( new Document ( $prefs ), Response :: MODEL_PREFERENCES );
2020-12-26 16:54:42 +00:00
});
2019-05-09 06:54:39 +00:00
2023-08-16 14:43:38 +00:00
App :: get ( '/v1/users/:userId/targets/:targetId' )
2024-09-03 16:22:30 +00:00
-> desc ( 'Get user target' )
2023-08-16 14:43:38 +00:00
-> groups ([ 'api' , 'users' ])
2023-08-23 20:24:25 +00:00
-> label ( 'scope' , 'targets.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'targets' ,
2025-01-17 04:31:39 +00:00
name : 'getTarget' ,
description : '/docs/references/users/get-user-target.md' ,
auth : [ AuthType :: KEY , AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_TARGET ,
)
]
))
2023-08-16 14:43:38 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> param ( 'targetId' , '' , new UID (), 'Target ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( string $userId , string $targetId , Response $response , Database $dbForProject ) {
2023-08-23 20:23:52 +00:00
2023-08-16 14:43:38 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
2023-08-16 14:47:36 +00:00
2023-08-16 14:43:38 +00:00
$target = $user -> find ( '$id' , $targetId , 'targets' );
if ( empty ( $target )) {
throw new Exception ( Exception :: USER_TARGET_NOT_FOUND );
}
$response -> dynamic ( $target , Response :: MODEL_TARGET );
2023-08-16 14:47:36 +00:00
});
2023-08-16 14:43:38 +00:00
2020-06-28 17:31:21 +00:00
App :: get ( '/v1/users/:userId/sessions' )
2023-08-01 15:26:48 +00:00
-> desc ( 'List user sessions' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2019-05-09 06:54:39 +00:00
-> label ( 'scope' , 'users.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-04-12 13:38:00 +00:00
group : 'sessions' ,
2025-01-17 04:31:39 +00:00
name : 'listSessions' ,
description : '/docs/references/users/list-user-sessions.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_SESSION_LIST ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2020-12-26 16:54:42 +00:00
-> inject ( 'locale' )
2022-08-11 01:43:02 +00:00
-> action ( function ( string $userId , Response $response , Database $dbForProject , Locale $locale ) {
2019-05-09 06:54:39 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2020-06-30 11:09:28 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-06-30 11:09:28 +00:00
}
2019-05-09 06:54:39 +00:00
2021-02-19 12:12:47 +00:00
$sessions = $user -> getAttribute ( 'sessions' , []);
2020-06-30 11:09:28 +00:00
2022-05-23 14:54:50 +00:00
foreach ( $sessions as $key => $session ) {
2021-02-19 12:12:47 +00:00
/** @var Document $session */
2019-05-09 06:54:39 +00:00
2022-05-23 14:54:50 +00:00
$countryName = $locale -> getText ( 'countries.' . strtolower ( $session -> getAttribute ( 'countryCode' )), $locale -> getText ( 'locale.country.unknown' ));
2021-07-22 20:15:01 +00:00
$session -> setAttribute ( 'countryName' , $countryName );
2021-02-19 12:12:47 +00:00
$session -> setAttribute ( 'current' , false );
2019-05-09 06:54:39 +00:00
2021-02-19 12:12:47 +00:00
$sessions [ $key ] = $session ;
2019-05-09 06:54:39 +00:00
}
2021-07-25 14:47:18 +00:00
$response -> dynamic ( new Document ([
2021-05-27 10:09:14 +00:00
'sessions' => $sessions ,
2022-02-27 09:57:09 +00:00
'total' => count ( $sessions ),
2020-10-30 19:53:27 +00:00
]), Response :: MODEL_SESSION_LIST );
2021-12-27 12:45:23 +00:00
});
2019-05-09 06:54:39 +00:00
2022-05-12 13:20:06 +00:00
App :: get ( '/v1/users/:userId/memberships' )
2023-08-01 15:26:48 +00:00
-> desc ( 'List user memberships' )
2022-05-12 13:20:06 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'memberships' ,
2025-01-17 04:31:39 +00:00
name : 'listMemberships' ,
description : '/docs/references/users/list-user-memberships.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MEMBERSHIP_LIST ,
)
]
))
2022-05-12 13:20:06 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2025-03-13 09:48:39 +00:00
-> param ( 'queries' , [], new Memberships (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Memberships :: ALLOWED_ATTRIBUTES ), true )
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
2022-05-12 13:20:06 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2025-03-13 09:48:39 +00:00
-> action ( function ( string $userId , array $queries , string $search , Response $response , Database $dbForProject ) {
2022-05-12 13:20:06 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2022-05-12 13:20:06 +00:00
}
2025-03-13 09:48:39 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
if ( ! empty ( $search )) {
$queries [] = Query :: search ( 'search' , $search );
}
// Set internal queries
2025-05-27 01:36:23 +00:00
$queries [] = Query :: equal ( 'userInternalId' , [ $user -> getSequence ()]);
2025-03-13 09:48:39 +00:00
2022-05-23 14:54:50 +00:00
$memberships = array_map ( function ( $membership ) use ( $dbForProject , $user ) {
2022-05-12 13:20:06 +00:00
$team = $dbForProject -> getDocument ( 'teams' , $membership -> getAttribute ( 'teamId' ));
$membership
-> setAttribute ( 'teamName' , $team -> getAttribute ( 'name' ))
-> setAttribute ( 'userName' , $user -> getAttribute ( 'name' ))
2022-05-12 14:26:12 +00:00
-> setAttribute ( 'userEmail' , $user -> getAttribute ( 'email' ));
2022-05-12 13:20:06 +00:00
return $membership ;
2025-03-13 09:48:39 +00:00
}, $dbForProject -> find ( 'memberships' , $queries ));
2022-05-12 13:20:06 +00:00
$response -> dynamic ( new Document ([
'memberships' => $memberships ,
'total' => count ( $memberships ),
]), Response :: MODEL_MEMBERSHIP_LIST );
});
2020-06-28 17:31:21 +00:00
App :: get ( '/v1/users/:userId/logs' )
2023-08-01 15:26:48 +00:00
-> desc ( 'List user logs' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2019-05-09 06:54:39 +00:00
-> label ( 'scope' , 'users.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'logs' ,
2025-01-17 04:31:39 +00:00
name : 'listLogs' ,
description : '/docs/references/users/list-user-logs.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_LOG_LIST ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2023-05-16 12:56:20 +00:00
-> param ( 'queries' , [], new Queries ([ new Limit (), new Offset ()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset' , true )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2020-12-26 16:54:42 +00:00
-> inject ( 'locale' )
-> inject ( 'geodb' )
2022-08-25 10:28:13 +00:00
-> action ( function ( string $userId , array $queries , Response $response , Database $dbForProject , Locale $locale , Reader $geodb ) {
2020-06-30 11:09:28 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2020-06-30 11:09:28 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-06-30 11:09:28 +00:00
}
2019-05-09 06:54:39 +00:00
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2025-02-25 08:03:37 +00:00
// Temp fix for logs
$queries [] = Query :: or ([
2025-02-26 01:00:16 +00:00
Query :: greaterThan ( '$createdAt' , DateTime :: format ( new \DateTime ( '2025-02-26T01:30+00:00' ))),
2025-02-25 08:03:37 +00:00
Query :: lessThan ( '$createdAt' , DateTime :: format ( new \DateTime ( '2025-02-13T00:00+00:00' ))),
]);
2025-02-25 05:51:11 +00:00
2021-12-27 12:45:23 +00:00
$audit = new Audit ( $dbForProject );
2021-11-16 14:54:29 +00:00
2025-05-26 05:42:11 +00:00
$logs = $audit -> getLogsByUser ( $user -> getSequence (), $queries );
2020-06-30 11:09:28 +00:00
$output = [];
foreach ( $logs as $i => & $log ) {
$log [ 'userAgent' ] = ( ! empty ( $log [ 'userAgent' ])) ? $log [ 'userAgent' ] : 'UNKNOWN' ;
2021-12-12 17:55:38 +00:00
$detector = new Detector ( $log [ 'userAgent' ]);
$detector -> skipBotDetection (); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
2020-06-30 11:09:28 +00:00
2021-12-12 17:55:38 +00:00
$os = $detector -> getOS ();
$client = $detector -> getClient ();
$device = $detector -> getDevice ();
2020-10-30 19:53:27 +00:00
$output [ $i ] = new Document ([
2020-06-30 11:09:28 +00:00
'event' => $log [ 'event' ],
2024-02-25 08:12:28 +00:00
'userId' => ID :: custom ( $log [ 'data' ][ 'userId' ]),
'userEmail' => $log [ 'data' ][ 'userEmail' ] ? ? null ,
'userName' => $log [ 'data' ][ 'userName' ] ? ? null ,
2020-06-30 11:09:28 +00:00
'ip' => $log [ 'ip' ],
2021-06-12 18:39:59 +00:00
'time' => $log [ 'time' ],
2021-12-12 17:55:38 +00:00
'osCode' => $os [ 'osCode' ],
'osName' => $os [ 'osName' ],
'osVersion' => $os [ 'osVersion' ],
'clientType' => $client [ 'clientType' ],
'clientCode' => $client [ 'clientCode' ],
'clientName' => $client [ 'clientName' ],
'clientVersion' => $client [ 'clientVersion' ],
'clientEngine' => $client [ 'clientEngine' ],
'clientEngineVersion' => $client [ 'clientEngineVersion' ],
'deviceName' => $device [ 'deviceName' ],
'deviceBrand' => $device [ 'deviceBrand' ],
2021-12-12 17:59:12 +00:00
'deviceModel' => $device [ 'deviceModel' ]
2020-10-30 19:53:27 +00:00
]);
$record = $geodb -> get ( $log [ 'ip' ]);
if ( $record ) {
2022-05-23 14:54:50 +00:00
$output [ $i ][ 'countryCode' ] = $locale -> getText ( 'countries.' . strtolower ( $record [ 'country' ][ 'iso_code' ]), false ) ? \strtolower ( $record [ 'country' ][ 'iso_code' ]) : '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'countries.' . strtolower ( $record [ 'country' ][ 'iso_code' ]), $locale -> getText ( 'locale.country.unknown' ));
2020-10-30 19:53:27 +00:00
} else {
$output [ $i ][ 'countryCode' ] = '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'locale.country.unknown' );
2019-05-09 06:54:39 +00:00
}
}
2021-11-16 14:54:29 +00:00
$response -> dynamic ( new Document ([
2025-05-26 05:42:11 +00:00
'total' => $audit -> countLogsByUser ( $user -> getSequence (), $queries ),
2025-02-25 06:21:35 +00:00
'logs' => $output ,
2021-11-16 14:54:29 +00:00
]), Response :: MODEL_LOG_LIST );
2020-12-26 16:54:42 +00:00
});
2019-05-09 06:54:39 +00:00
2023-08-16 14:43:38 +00:00
App :: get ( '/v1/users/:userId/targets' )
2024-09-03 16:22:30 +00:00
-> desc ( 'List user targets' )
2023-08-16 14:43:38 +00:00
-> groups ([ 'api' , 'users' ])
2023-08-23 20:24:25 +00:00
-> label ( 'scope' , 'targets.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'targets' ,
2025-01-17 04:31:39 +00:00
name : 'listTargets' ,
description : '/docs/references/users/list-user-targets.md' ,
auth : [ AuthType :: KEY , AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_TARGET_LIST ,
)
]
))
2023-08-16 14:43:38 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2025-05-27 13:13:29 +00:00
-> param ( 'queries' , [], new Targets (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Targets :: ALLOWED_ATTRIBUTES ), true )
2023-08-16 14:43:38 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-11-02 12:30:17 +00:00
-> action ( function ( string $userId , array $queries , Response $response , Database $dbForProject ) {
2023-08-16 14:43:38 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
2023-08-16 14:47:36 +00:00
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2023-11-02 12:30:17 +00:00
$queries [] = Query :: equal ( 'userId' , [ $userId ]);
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
$cursor = \array_filter ( $queries , function ( $query ) {
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
});
$cursor = reset ( $cursor );
2023-11-02 12:30:17 +00:00
if ( $cursor ) {
2024-10-17 05:41:24 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2023-11-02 12:30:17 +00:00
$targetId = $cursor -> getValue ();
2023-11-28 13:12:34 +00:00
$cursorDocument = $dbForProject -> getDocument ( 'targets' , $targetId );
2023-11-02 12:30:17 +00:00
if ( $cursorDocument -> isEmpty ()) {
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Target ' { $targetId } ' for the 'cursor' value not found. " );
}
$cursor -> setValue ( $cursorDocument );
}
2025-04-16 11:46:39 +00:00
try {
2025-04-17 04:46:26 +00:00
$targets = $dbForProject -> find ( 'targets' , $queries );
$total = $dbForProject -> count ( 'targets' , $queries , APP_LIMIT_COUNT );
2025-04-16 11:46:39 +00:00
} catch ( OrderException $e ) {
2025-04-17 04:46:26 +00:00
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order attribute ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all documents order attribute values are non-null. " );
2025-04-16 11:46:39 +00:00
}
2023-08-16 14:43:38 +00:00
$response -> dynamic ( new Document ([
2025-04-17 04:46:26 +00:00
'targets' => $targets ,
'total' => $total ,
2023-08-16 14:43:38 +00:00
]), Response :: MODEL_TARGET_LIST );
});
2023-05-18 01:11:45 +00:00
App :: get ( '/v1/users/identities' )
2024-09-03 16:22:30 +00:00
-> desc ( 'List identities' )
2023-05-18 01:11:45 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'identities' ,
2025-01-17 04:31:39 +00:00
name : 'listIdentities' ,
description : '/docs/references/users/list-identities.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_IDENTITY_LIST ,
)
]
))
2023-05-18 01:11:45 +00:00
-> param ( 'queries' , [], new Identities (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Identities :: ALLOWED_ATTRIBUTES ), true )
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( array $queries , string $search , Response $response , Database $dbForProject ) {
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2023-05-18 01:11:45 +00:00
if ( ! empty ( $search )) {
$queries [] = Query :: search ( 'search' , $search );
}
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
2023-08-22 03:25:55 +00:00
$cursor = \array_filter ( $queries , function ( $query ) {
2023-12-06 14:10:40 +00:00
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
2023-08-22 03:25:55 +00:00
});
2023-05-18 01:11:45 +00:00
$cursor = reset ( $cursor );
if ( $cursor ) {
/** @var Query $cursor */
2024-10-17 05:41:24 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2023-05-18 01:11:45 +00:00
$identityId = $cursor -> getValue ();
$cursorDocument = $dbForProject -> getDocument ( 'identities' , $identityId );
if ( $cursorDocument -> isEmpty ()) {
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " User ' { $identityId } ' for the 'cursor' value not found. " );
}
$cursor -> setValue ( $cursorDocument );
}
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2025-04-16 11:46:39 +00:00
try {
2025-04-17 04:46:26 +00:00
$identities = $dbForProject -> find ( 'identities' , $queries );
$total = $dbForProject -> count ( 'identities' , $filterQueries , APP_LIMIT_COUNT );
2025-04-16 11:46:39 +00:00
} catch ( OrderException $e ) {
2025-04-17 04:46:26 +00:00
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order attribute ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all documents order attribute values are non-null. " );
2025-04-16 11:46:39 +00:00
}
2023-05-18 01:11:45 +00:00
$response -> dynamic ( new Document ([
2025-04-17 04:46:26 +00:00
'identities' => $identities ,
'total' => $total ,
2023-05-18 01:11:45 +00:00
]), Response :: MODEL_IDENTITY_LIST );
});
2020-06-28 17:31:21 +00:00
App :: patch ( '/v1/users/:userId/status' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Update user status' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2022-04-04 06:30:07 +00:00
-> label ( 'event' , 'users.[userId].update.status' )
2019-05-09 06:54:39 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.update' )
2022-08-13 09:59:34 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2022-08-16 09:00:28 +00:00
-> label ( 'audits.userId' , '{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updateStatus' ,
description : '/docs/references/users/update-user-status.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> param ( 'status' , null , new Boolean ( true ), 'User Status. To activate the user pass `true` and to block the user pass `false`.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , bool $status , Response $response , Database $dbForProject , Event $queueForEvents ) {
2019-05-09 06:54:39 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2019-05-09 06:54:39 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-06-30 11:09:28 +00:00
}
2019-05-09 06:54:39 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user -> setAttribute ( 'status' , ( bool ) $status ));
2019-10-21 06:01:07 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-08-25 08:38:43 +00:00
-> setParam ( 'userId' , $user -> getId ());
2022-04-04 06:30:07 +00:00
2021-07-25 14:47:18 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
2020-12-26 16:54:42 +00:00
});
2019-05-09 06:54:39 +00:00
2023-05-27 00:23:01 +00:00
App :: put ( '/v1/users/:userId/labels' )
2023-09-27 15:11:58 +00:00
-> desc ( 'Update user labels' )
2021-05-30 07:03:51 +00:00
-> groups ([ 'api' , 'users' ])
2023-05-27 00:23:01 +00:00
-> label ( 'event' , 'users.[userId].update.labels' )
2021-05-30 07:03:51 +00:00
-> label ( 'scope' , 'users.write' )
2023-05-27 00:23:01 +00:00
-> label ( 'audits.event' , 'user.update' )
2022-08-13 09:59:34 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updateLabels' ,
description : '/docs/references/users/update-user-labels.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2024-01-28 10:51:05 +00:00
-> param ( 'labels' , [], new ArrayList ( new Text ( 36 , allowList : [ ... Text :: NUMBERS , ... Text :: ALPHABET_UPPER , ... Text :: ALPHABET_LOWER ]), APP_LIMIT_ARRAY_LABELS_SIZE ), 'Array of user labels. Replaces the previous labels. Maximum of ' . APP_LIMIT_ARRAY_LABELS_SIZE . ' labels are allowed, each up to 36 alphanumeric characters long.' )
2021-05-30 07:03:51 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2023-10-10 18:02:24 +00:00
-> action ( function ( string $userId , array $labels , Response $response , Database $dbForProject , Event $queueForEvents ) {
2021-05-30 07:03:51 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2021-05-30 07:03:51 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2021-05-30 07:03:51 +00:00
}
2023-05-27 00:23:01 +00:00
$user -> setAttribute ( 'labels' , ( array ) \array_values ( \array_unique ( $labels )));
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
2019-05-09 06:54:39 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-08-25 08:38:43 +00:00
-> setParam ( 'userId' , $user -> getId ());
2022-04-04 06:30:07 +00:00
2021-07-25 14:47:18 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
2020-12-26 16:54:42 +00:00
});
2019-05-09 06:54:39 +00:00
2022-06-09 19:11:49 +00:00
App :: patch ( '/v1/users/:userId/verification/phone' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Update phone verification' )
2022-06-08 12:50:31 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'event' , 'users.[userId].update.verification' )
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'verification.update' )
2022-08-13 09:59:34 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updatePhoneVerification' ,
description : '/docs/references/users/update-user-phone-verification.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2022-06-08 12:50:31 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> param ( 'phoneVerification' , false , new Boolean (), 'User phone verification status.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , bool $phoneVerification , Response $response , Database $dbForProject , Event $queueForEvents ) {
2022-06-08 12:50:31 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2022-06-08 12:50:31 +00:00
}
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user -> setAttribute ( 'phoneVerification' , $phoneVerification ));
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-08-25 08:38:43 +00:00
-> setParam ( 'userId' , $user -> getId ());
2022-06-08 12:50:31 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
});
2021-08-29 11:13:58 +00:00
App :: patch ( '/v1/users/:userId/name' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Update name' )
2021-08-29 11:13:58 +00:00
-> groups ([ 'api' , 'users' ])
2022-04-04 06:30:07 +00:00
-> label ( 'event' , 'users.[userId].update.name' )
2021-08-29 11:13:58 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.update' )
2022-08-08 14:32:54 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2022-08-16 09:00:28 +00:00
-> label ( 'audits.userId' , '{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updateName' ,
description : '/docs/references/users/update-user-name.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2024-01-20 09:43:31 +00:00
-> param ( 'name' , '' , new Text ( 128 , 0 ), 'User name. Max length: 128 chars.' )
2021-08-29 11:13:58 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , string $name , Response $response , Database $dbForProject , Event $queueForEvents ) {
2021-10-08 08:39:37 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2021-08-29 11:13:58 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2021-08-29 11:13:58 +00:00
}
2023-05-30 20:55:33 +00:00
$user -> setAttribute ( 'name' , $name );
2022-04-26 10:07:33 +00:00
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
2021-08-29 11:13:58 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
2021-08-29 11:13:58 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
});
App :: patch ( '/v1/users/:userId/password' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Update password' )
2021-08-29 11:13:58 +00:00
-> groups ([ 'api' , 'users' ])
2022-04-04 06:30:07 +00:00
-> label ( 'event' , 'users.[userId].update.password' )
2021-08-29 11:13:58 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.update' )
2022-08-08 14:32:54 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2022-08-16 09:00:28 +00:00
-> label ( 'audits.userId' , '{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updatePassword' ,
description : '/docs/references/users/update-user-password.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2024-01-20 09:43:31 +00:00
-> param ( 'password' , '' , fn ( $project , $passwordsDictionary ) => new PasswordDictionary ( $passwordsDictionary , enabled : $project -> getAttribute ( 'auths' , [])[ 'passwordDictionary' ] ? ? false , allowEmpty : true ), 'New user password. Must be at least 8 chars.' , false , [ 'project' , 'passwordsDictionary' ])
2021-08-29 11:13:58 +00:00
-> inject ( 'response' )
2022-12-16 10:47:08 +00:00
-> inject ( 'project' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2024-01-04 15:26:15 +00:00
-> inject ( 'hooks' )
-> action ( function ( string $userId , string $password , Response $response , Document $project , Database $dbForProject , Event $queueForEvents , Hooks $hooks ) {
2021-08-29 11:13:58 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2021-08-29 11:13:58 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2021-08-29 11:13:58 +00:00
}
2023-07-19 22:24:32 +00:00
if ( $project -> getAttribute ( 'auths' , [])[ 'personalDataCheck' ] ? ? false ) {
2023-04-13 20:20:03 +00:00
$personalDataValidator = new PersonalData ( $userId , $user -> getAttribute ( 'email' ), $user -> getAttribute ( 'name' ), $user -> getAttribute ( 'phone' ));
if ( ! $personalDataValidator -> isValid ( $password )) {
throw new Exception ( Exception :: USER_PASSWORD_PERSONAL_DATA );
}
}
2024-01-20 09:43:31 +00:00
if ( \strlen ( $password ) === 0 ) {
$user
-> setAttribute ( 'password' , '' )
-> setAttribute ( 'passwordUpdate' , DateTime :: now ());
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
$response -> dynamic ( $user , Response :: MODEL_USER );
}
2024-02-05 10:40:53 +00:00
2024-01-05 11:31:38 +00:00
$hooks -> trigger ( 'passwordValidator' , [ $dbForProject , $project , $password , & $user , true ]);
2024-01-20 09:43:31 +00:00
2022-12-26 05:52:49 +00:00
$newPassword = Auth :: passwordHash ( $password , Auth :: DEFAULT_ALGO , Auth :: DEFAULT_ALGO_OPTIONS );
2022-12-16 10:47:08 +00:00
$historyLimit = $project -> getAttribute ( 'auths' , [])[ 'passwordHistory' ] ? ? 0 ;
2023-07-19 21:34:27 +00:00
$history = $user -> getAttribute ( 'passwordHistory' , []);
2022-12-18 06:31:14 +00:00
if ( $historyLimit > 0 ) {
2022-12-18 09:08:51 +00:00
$validator = new PasswordHistory ( $history , $user -> getAttribute ( 'hash' ), $user -> getAttribute ( 'hashOptions' ));
if ( ! $validator -> isValid ( $password )) {
2023-04-13 20:20:03 +00:00
throw new Exception ( Exception :: USER_PASSWORD_RECENTLY_USED );
2022-12-16 10:47:08 +00:00
}
2022-12-18 06:28:19 +00:00
2022-12-16 10:47:08 +00:00
$history [] = $newPassword ;
2023-07-19 21:34:27 +00:00
$history = array_slice ( $history , ( count ( $history ) - $historyLimit ), $historyLimit );
2022-12-16 10:22:39 +00:00
}
2021-10-08 08:39:37 +00:00
$user
2022-12-16 10:29:20 +00:00
-> setAttribute ( 'password' , $newPassword )
2023-02-20 01:51:56 +00:00
-> setAttribute ( 'passwordHistory' , $history )
-> setAttribute ( 'passwordUpdate' , DateTime :: now ())
2022-06-13 12:53:28 +00:00
-> setAttribute ( 'hash' , Auth :: DEFAULT_ALGO )
2023-02-20 01:51:56 +00:00
-> setAttribute ( 'hashOptions' , Auth :: DEFAULT_ALGO_OPTIONS );
2021-10-08 08:39:37 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
2021-08-29 11:13:58 +00:00
2025-06-14 12:37:42 +00:00
$sessions = $user -> getAttribute ( 'sessions' , []);
2025-06-16 17:35:52 +00:00
$invalidate = $project -> getAttribute ( 'auths' , default : [])[ 'invalidateSessions' ] ? ? false ;
if ( $invalidate ) {
2025-06-14 12:37:42 +00:00
foreach ( $sessions as $session ) {
/** @var Document $session */
$dbForProject -> deleteDocument ( 'sessions' , $session -> getId ());
}
}
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2022-12-20 16:11:30 +00:00
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
2021-08-29 11:13:58 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
});
App :: patch ( '/v1/users/:userId/email' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Update email' )
2021-08-29 11:13:58 +00:00
-> groups ([ 'api' , 'users' ])
2022-04-04 06:30:07 +00:00
-> label ( 'event' , 'users.[userId].update.email' )
2021-08-29 11:13:58 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.update' )
2022-08-08 14:32:54 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2022-08-16 09:00:28 +00:00
-> label ( 'audits.userId' , '{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updateEmail' ,
description : '/docs/references/users/update-user-email.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2024-01-20 09:43:31 +00:00
-> param ( 'email' , '' , new Email ( allowEmpty : true ), 'User email.' )
2021-08-29 11:13:58 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , string $email , Response $response , Database $dbForProject , Event $queueForEvents ) {
2021-08-29 11:13:58 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2021-08-29 11:13:58 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2021-08-29 11:13:58 +00:00
}
2021-10-07 17:57:23 +00:00
$email = \strtolower ( $email );
2024-01-20 10:06:30 +00:00
if ( \strlen ( $email ) !== 0 ) {
// Makes sure this email is not already used in another identity
$identityWithMatchingEmail = $dbForProject -> findOne ( 'identities' , [
Query :: equal ( 'providerEmail' , [ $email ]),
2025-05-26 05:42:11 +00:00
Query :: notEqual ( 'userInternalId' , $user -> getSequence ()),
2024-01-20 10:06:30 +00:00
]);
2024-10-31 08:13:23 +00:00
if ( ! $identityWithMatchingEmail -> isEmpty ()) {
2024-01-20 10:06:30 +00:00
throw new Exception ( Exception :: USER_EMAIL_ALREADY_EXISTS );
}
2023-05-18 01:11:45 +00:00
2024-01-20 10:06:30 +00:00
$target = $dbForProject -> findOne ( 'targets' , [
Query :: equal ( 'identifier' , [ $email ]),
]);
2023-11-28 13:12:34 +00:00
2024-01-20 10:06:30 +00:00
if ( $target instanceof Document && ! $target -> isEmpty ()) {
throw new Exception ( Exception :: USER_TARGET_ALREADY_EXISTS );
}
2023-11-28 13:12:34 +00:00
}
$oldEmail = $user -> getAttribute ( 'email' );
2022-04-26 10:07:33 +00:00
$user
-> setAttribute ( 'email' , $email )
2022-06-08 12:50:31 +00:00
-> setAttribute ( 'emailVerification' , false )
2023-05-30 20:55:33 +00:00
;
2021-08-29 12:00:25 +00:00
try {
2022-04-26 10:07:33 +00:00
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
2023-11-28 13:12:34 +00:00
/**
* @ var Document $oldTarget
*/
$oldTarget = $user -> find ( 'identifier' , $oldEmail , 'targets' );
if ( $oldTarget instanceof Document && ! $oldTarget -> isEmpty ()) {
2024-01-20 10:06:30 +00:00
if ( \strlen ( $email ) !== 0 ) {
$dbForProject -> updateDocument ( 'targets' , $oldTarget -> getId (), $oldTarget -> setAttribute ( 'identifier' , $email ));
} else {
$dbForProject -> deleteDocument ( 'targets' , $oldTarget -> getId ());
}
2024-01-20 13:07:53 +00:00
} else {
if ( \strlen ( $email ) !== 0 ) {
$target = $dbForProject -> createDocument ( 'targets' , new Document ([
2024-02-16 04:07:16 +00:00
'$permissions' => [
Permission :: read ( Role :: user ( $user -> getId ())),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: user ( $user -> getId ())),
],
2024-01-20 13:07:53 +00:00
'userId' => $user -> getId (),
2025-05-26 05:42:11 +00:00
'userInternalId' => $user -> getSequence (),
2024-01-20 13:07:53 +00:00
'providerType' => 'email' ,
'identifier' => $email ,
]));
$user -> setAttribute ( 'targets' , [ ... $user -> getAttribute ( 'targets' , []), $target ]);
}
2023-11-28 13:12:34 +00:00
}
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2022-05-23 14:54:50 +00:00
} catch ( Duplicate $th ) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_EMAIL_ALREADY_EXISTS );
2021-08-29 11:13:58 +00:00
}
2022-12-20 16:11:30 +00:00
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
2022-06-08 12:50:31 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
});
2022-06-13 12:43:17 +00:00
App :: patch ( '/v1/users/:userId/phone' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Update phone' )
2022-06-08 12:50:31 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'event' , 'users.[userId].update.phone' )
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.update' )
2022-08-08 14:32:54 +00:00
-> label ( 'audits.resource' , 'user/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updatePhone' ,
description : '/docs/references/users/update-user-phone.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2022-06-08 12:50:31 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2024-01-20 09:43:31 +00:00
-> param ( 'number' , '' , new Phone ( allowEmpty : true ), 'User phone number.' )
2022-06-08 12:50:31 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , string $number , Response $response , Database $dbForProject , Event $queueForEvents ) {
2022-06-08 12:50:31 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2022-06-08 12:50:31 +00:00
}
2023-11-28 13:12:34 +00:00
$oldPhone = $user -> getAttribute ( 'phone' );
2022-06-08 12:50:31 +00:00
$user
-> setAttribute ( 'phone' , $number )
-> setAttribute ( 'phoneVerification' , false )
2022-08-25 09:34:36 +00:00
;
2022-06-08 12:50:31 +00:00
2024-01-20 10:06:30 +00:00
if ( \strlen ( $number ) !== 0 ) {
$target = $dbForProject -> findOne ( 'targets' , [
Query :: equal ( 'identifier' , [ $number ]),
]);
2023-11-28 13:12:34 +00:00
2024-01-20 10:06:30 +00:00
if ( $target instanceof Document && ! $target -> isEmpty ()) {
throw new Exception ( Exception :: USER_TARGET_ALREADY_EXISTS );
}
2023-11-28 13:12:34 +00:00
}
2022-06-08 12:50:31 +00:00
try {
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
2023-11-28 13:12:34 +00:00
/**
* @ var Document $oldTarget
*/
$oldTarget = $user -> find ( 'identifier' , $oldPhone , 'targets' );
if ( $oldTarget instanceof Document && ! $oldTarget -> isEmpty ()) {
2024-01-20 10:06:30 +00:00
if ( \strlen ( $number ) !== 0 ) {
$dbForProject -> updateDocument ( 'targets' , $oldTarget -> getId (), $oldTarget -> setAttribute ( 'identifier' , $number ));
} else {
$dbForProject -> deleteDocument ( 'targets' , $oldTarget -> getId ());
}
2024-01-20 13:07:53 +00:00
} else {
if ( \strlen ( $number ) !== 0 ) {
$target = $dbForProject -> createDocument ( 'targets' , new Document ([
2024-02-16 04:07:16 +00:00
'$permissions' => [
Permission :: read ( Role :: user ( $user -> getId ())),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: user ( $user -> getId ())),
],
2024-01-20 13:07:53 +00:00
'userId' => $user -> getId (),
2025-05-26 05:42:11 +00:00
'userInternalId' => $user -> getSequence (),
2024-01-20 13:07:53 +00:00
'providerType' => 'sms' ,
'identifier' => $number ,
]));
$user -> setAttribute ( 'targets' , [ ... $user -> getAttribute ( 'targets' , []), $target ]);
}
2023-11-28 13:12:34 +00:00
}
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2022-06-08 12:50:31 +00:00
} catch ( Duplicate $th ) {
2022-10-04 18:18:15 +00:00
throw new Exception ( Exception :: USER_PHONE_ALREADY_EXISTS );
2022-06-08 12:50:31 +00:00
}
2022-12-20 16:11:30 +00:00
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
2022-06-08 12:50:31 +00:00
2021-08-29 11:13:58 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
});
2022-04-04 06:30:07 +00:00
2022-08-12 14:10:41 +00:00
App :: patch ( '/v1/users/:userId/verification' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Update email verification' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2022-08-12 14:10:41 +00:00
-> label ( 'event' , 'users.[userId].update.verification' )
2019-10-04 21:04:49 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'verification.update' )
2022-08-13 09:59:34 +00:00
-> label ( 'audits.resource' , 'user/{request.userId}' )
2022-08-16 09:00:28 +00:00
-> label ( 'audits.userId' , '{request.userId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updateEmailVerification' ,
description : '/docs/references/users/update-user-email-verification.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2022-08-12 14:10:41 +00:00
-> param ( 'emailVerification' , false , new Boolean (), 'User email verification status.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , bool $emailVerification , Response $response , Database $dbForProject , Event $queueForEvents ) {
2019-10-04 21:04:49 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2019-10-04 21:04:49 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-06-30 11:09:28 +00:00
}
2020-01-19 20:38:00 +00:00
2022-08-12 14:10:41 +00:00
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user -> setAttribute ( 'emailVerification' , $emailVerification ));
2019-10-21 06:01:07 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
2021-08-29 11:13:58 +00:00
$response -> dynamic ( $user , Response :: MODEL_USER );
});
2020-06-28 17:31:21 +00:00
App :: patch ( '/v1/users/:userId/prefs' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Update user preferences' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2022-04-04 06:30:07 +00:00
-> label ( 'event' , 'users.[userId].update.prefs' )
2019-10-04 21:04:49 +00:00
-> label ( 'scope' , 'users.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updatePrefs' ,
description : '/docs/references/users/update-user-prefs.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_PREFERENCES ,
)
]
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2020-09-10 14:40:14 +00:00
-> param ( 'prefs' , '' , new Assoc (), 'Prefs key-value JSON object.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , array $prefs , Response $response , Database $dbForProject , Event $queueForEvents ) {
2019-10-04 21:04:49 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2019-10-04 21:04:49 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-06-30 11:09:28 +00:00
}
2020-01-19 20:38:00 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user -> setAttribute ( 'prefs' , $prefs ));
2019-10-21 06:01:07 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-08-25 08:38:43 +00:00
-> setParam ( 'userId' , $user -> getId ());
2022-04-04 06:30:07 +00:00
2021-07-25 14:47:18 +00:00
$response -> dynamic ( new Document ( $prefs ), Response :: MODEL_PREFERENCES );
2020-12-26 16:54:42 +00:00
});
2019-10-04 21:04:49 +00:00
2023-11-14 12:44:07 +00:00
App :: patch ( '/v1/users/:userId/targets/:targetId' )
2024-09-03 16:22:30 +00:00
-> desc ( 'Update user target' )
2023-08-18 17:58:12 +00:00
-> groups ([ 'api' , 'users' ])
2023-10-25 17:33:23 +00:00
-> label ( 'audits.event' , 'target.update' )
2023-09-20 18:07:10 +00:00
-> label ( 'audits.resource' , 'target/{response.$id}' )
2023-10-31 18:23:46 +00:00
-> label ( 'event' , 'users.[userId].targets.[targetId].update' )
2023-08-18 17:58:12 +00:00
-> label ( 'scope' , 'targets.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'targets' ,
2025-01-17 04:31:39 +00:00
name : 'updateTarget' ,
description : '/docs/references/users/update-target.md' ,
auth : [ AuthType :: KEY , AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_TARGET ,
)
]
))
2023-08-23 20:24:25 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> param ( 'targetId' , '' , new UID (), 'Target ID.' )
2023-11-14 12:44:07 +00:00
-> param ( 'identifier' , '' , new Text ( Database :: LENGTH_KEY ), 'The target identifier (token, email, phone etc.)' , true )
2023-11-28 13:12:34 +00:00
-> param ( 'providerId' , '' , new UID (), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.' , true )
-> param ( 'name' , '' , new Text ( 128 ), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.' , true )
2023-10-31 18:23:46 +00:00
-> inject ( 'queueForEvents' )
2023-08-18 17:58:12 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-11-28 13:12:34 +00:00
-> action ( function ( string $userId , string $targetId , string $identifier , string $providerId , string $name , Event $queueForEvents , Response $response , Database $dbForProject ) {
2023-08-18 17:58:12 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
$target = $dbForProject -> getDocument ( 'targets' , $targetId );
if ( $target -> isEmpty ()) {
throw new Exception ( Exception :: USER_TARGET_NOT_FOUND );
}
2023-10-06 13:53:46 +00:00
if ( $user -> getId () !== $target -> getAttribute ( 'userId' )) {
throw new Exception ( Exception :: USER_TARGET_NOT_FOUND );
}
2023-11-14 12:44:07 +00:00
if ( $identifier ) {
2023-11-21 09:30:02 +00:00
$providerType = $target -> getAttribute ( 'providerType' );
2023-11-21 09:41:09 +00:00
switch ( $providerType ) {
case 'email' :
$validator = new Email ();
if ( ! $validator -> isValid ( $identifier )) {
throw new Exception ( Exception :: GENERAL_INVALID_EMAIL );
}
break ;
2023-11-29 04:05:37 +00:00
case MESSAGE_TYPE_SMS :
2023-11-21 09:41:09 +00:00
$validator = new Phone ();
if ( ! $validator -> isValid ( $identifier )) {
throw new Exception ( Exception :: GENERAL_INVALID_PHONE );
}
break ;
2023-11-29 04:05:37 +00:00
case MESSAGE_TYPE_PUSH :
2023-11-21 09:49:19 +00:00
break ;
2023-11-21 09:41:09 +00:00
default :
throw new Exception ( Exception :: PROVIDER_INCORRECT_TYPE );
2023-11-21 09:30:02 +00:00
}
2024-10-22 01:35:04 +00:00
$target
-> setAttribute ( 'identifier' , $identifier )
-> setAttribute ( 'expired' , false );
2023-11-14 12:44:07 +00:00
}
if ( $providerId ) {
$provider = $dbForProject -> getDocument ( 'providers' , $providerId );
if ( $provider -> isEmpty ()) {
throw new Exception ( Exception :: PROVIDER_NOT_FOUND );
}
2023-11-30 22:09:43 +00:00
if ( $provider -> getAttribute ( 'type' ) !== $target -> getAttribute ( 'providerType' )) {
throw new Exception ( Exception :: PROVIDER_INCORRECT_TYPE );
}
2024-10-22 01:35:04 +00:00
$target
-> setAttribute ( 'providerId' , $provider -> getId ())
2025-05-26 05:42:11 +00:00
-> setAttribute ( 'providerInternalId' , $provider -> getSequence ());
2023-11-14 12:44:07 +00:00
}
2023-08-18 17:58:12 +00:00
2023-11-28 13:12:34 +00:00
if ( $name ) {
$target -> setAttribute ( 'name' , $name );
}
2023-08-18 17:58:12 +00:00
$target = $dbForProject -> updateDocument ( 'targets' , $target -> getId (), $target );
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2023-08-18 17:58:12 +00:00
2023-10-31 18:23:46 +00:00
$queueForEvents
-> setParam ( 'userId' , $user -> getId ())
-> setParam ( 'targetId' , $target -> getId ());
2023-08-18 17:58:12 +00:00
$response
2023-10-05 11:27:48 +00:00
-> dynamic ( $target , Response :: MODEL_TARGET );
2023-08-18 17:58:12 +00:00
});
2024-01-18 13:56:58 +00:00
App :: patch ( '/v1/users/:userId/mfa' )
-> desc ( 'Update MFA' )
-> groups ([ 'api' , 'users' ])
-> label ( 'event' , 'users.[userId].update.mfa' )
-> label ( 'scope' , 'users.write' )
-> label ( 'audits.event' , 'user.update' )
-> label ( 'audits.resource' , 'user/{response.$id}' )
-> label ( 'audits.userId' , '{response.$id}' )
-> label ( 'usage.metric' , 'users.{scope}.requests.update' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'updateMfa' ,
description : '/docs/references/users/update-user-mfa.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USER ,
)
]
))
2024-01-18 13:56:58 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> param ( 'mfa' , null , new Boolean (), 'Enable or disable MFA.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , bool $mfa , Response $response , Database $dbForProject , Event $queueForEvents ) {
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
$user -> setAttribute ( 'mfa' , $mfa );
$user = $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
$response -> dynamic ( $user , Response :: MODEL_USER );
});
2024-02-15 11:26:34 +00:00
App :: get ( '/v1/users/:userId/mfa/factors' )
2024-09-03 16:22:30 +00:00
-> desc ( 'List factors' )
2024-01-18 13:56:58 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.read' )
-> label ( 'usage.metric' , 'users.{scope}.requests.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'mfa' ,
2025-01-17 04:31:39 +00:00
name : 'listMfaFactors' ,
description : '/docs/references/users/list-mfa-factors.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MFA_FACTORS ,
)
]
))
2024-01-18 13:56:58 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( string $userId , Response $response , Database $dbForProject ) {
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
2024-02-29 20:59:49 +00:00
$totp = TOTP :: getAuthenticatorFromUser ( $user );
$factors = new Document ([
2024-03-01 02:07:58 +00:00
Type :: TOTP => $totp !== null && $totp -> getAttribute ( 'verified' , false ),
Type :: EMAIL => $user -> getAttribute ( 'email' , false ) && $user -> getAttribute ( 'emailVerification' , false ),
Type :: PHONE => $user -> getAttribute ( 'phone' , false ) && $user -> getAttribute ( 'phoneVerification' , false )
2024-01-18 13:56:58 +00:00
]);
2024-02-29 20:59:49 +00:00
$response -> dynamic ( $factors , Response :: MODEL_MFA_FACTORS );
2024-01-18 13:56:58 +00:00
});
2024-03-03 15:17:38 +00:00
App :: get ( '/v1/users/:userId/mfa/recovery-codes' )
2024-09-03 16:22:30 +00:00
-> desc ( 'Get MFA recovery codes' )
2024-03-03 15:17:38 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.read' )
-> label ( 'usage.metric' , 'users.{scope}.requests.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'mfa' ,
2025-01-17 04:31:39 +00:00
name : 'getMfaRecoveryCodes' ,
description : '/docs/references/users/get-mfa-recovery-codes.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MFA_RECOVERY_CODES ,
)
]
))
2024-03-03 15:17:38 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( string $userId , Response $response , Database $dbForProject ) {
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
$mfaRecoveryCodes = $user -> getAttribute ( 'mfaRecoveryCodes' , []);
if ( empty ( $mfaRecoveryCodes )) {
throw new Exception ( Exception :: USER_RECOVERY_CODES_NOT_FOUND );
}
$document = new Document ([
'recoveryCodes' => $mfaRecoveryCodes
]);
$response -> dynamic ( $document , Response :: MODEL_MFA_RECOVERY_CODES );
});
2024-03-03 18:11:55 +00:00
App :: patch ( '/v1/users/:userId/mfa/recovery-codes' )
2024-09-03 16:22:30 +00:00
-> desc ( 'Create MFA recovery codes' )
2024-03-03 15:17:38 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'event' , 'users.[userId].create.mfa.recovery-codes' )
-> label ( 'scope' , 'users.write' )
-> label ( 'audits.event' , 'user.update' )
-> label ( 'audits.resource' , 'user/{response.$id}' )
-> label ( 'audits.userId' , '{response.$id}' )
-> label ( 'usage.metric' , 'users.{scope}.requests.update' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'mfa' ,
2025-01-17 04:31:39 +00:00
name : 'createMfaRecoveryCodes' ,
description : '/docs/references/users/create-mfa-recovery-codes.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_MFA_RECOVERY_CODES ,
)
]
))
2024-03-03 15:17:38 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'queueForEvents' )
2024-03-03 18:11:55 +00:00
-> action ( function ( string $userId , Response $response , Database $dbForProject , Event $queueForEvents ) {
2024-03-03 15:17:38 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
$mfaRecoveryCodes = $user -> getAttribute ( 'mfaRecoveryCodes' , []);
if ( ! empty ( $mfaRecoveryCodes )) {
throw new Exception ( Exception :: USER_RECOVERY_CODES_ALREADY_EXISTS );
}
$mfaRecoveryCodes = Type :: generateBackupCodes ();
$user -> setAttribute ( 'mfaRecoveryCodes' , $mfaRecoveryCodes );
$dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
$document = new Document ([
'recoveryCodes' => $mfaRecoveryCodes
]);
$response -> dynamic ( $document , Response :: MODEL_MFA_RECOVERY_CODES );
});
App :: put ( '/v1/users/:userId/mfa/recovery-codes' )
2025-04-14 18:40:48 +00:00
-> desc ( 'Update MFA recovery codes (regenerate)' )
2024-03-03 15:17:38 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'event' , 'users.[userId].update.mfa.recovery-codes' )
-> label ( 'scope' , 'users.write' )
-> label ( 'audits.event' , 'user.update' )
-> label ( 'audits.resource' , 'user/{response.$id}' )
-> label ( 'audits.userId' , '{response.$id}' )
-> label ( 'usage.metric' , 'users.{scope}.requests.update' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'mfa' ,
2025-01-17 04:31:39 +00:00
name : 'updateMfaRecoveryCodes' ,
description : '/docs/references/users/update-mfa-recovery-codes.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MFA_RECOVERY_CODES ,
)
]
))
2024-03-03 15:17:38 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'queueForEvents' )
2024-03-03 18:11:55 +00:00
-> action ( function ( string $userId , Response $response , Database $dbForProject , Event $queueForEvents ) {
2024-03-03 15:17:38 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
$mfaRecoveryCodes = $user -> getAttribute ( 'mfaRecoveryCodes' , []);
if ( empty ( $mfaRecoveryCodes )) {
throw new Exception ( Exception :: USER_RECOVERY_CODES_NOT_FOUND );
}
$mfaRecoveryCodes = Type :: generateBackupCodes ();
$user -> setAttribute ( 'mfaRecoveryCodes' , $mfaRecoveryCodes );
$dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
$document = new Document ([
2024-03-04 08:50:50 +00:00
'recoveryCodes' => $mfaRecoveryCodes
2024-03-03 15:17:38 +00:00
]);
$response -> dynamic ( $document , Response :: MODEL_MFA_RECOVERY_CODES );
});
2024-03-01 16:22:51 +00:00
App :: delete ( '/v1/users/:userId/mfa/authenticators/:type' )
2024-09-03 16:22:30 +00:00
-> desc ( 'Delete authenticator' )
2024-01-18 13:56:58 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'event' , 'users.[userId].delete.mfa' )
-> label ( 'scope' , 'users.write' )
-> label ( 'audits.event' , 'user.update' )
-> label ( 'audits.resource' , 'user/{response.$id}' )
-> label ( 'audits.userId' , '{response.$id}' )
-> label ( 'usage.metric' , 'users.{scope}.requests.update' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'mfa' ,
2025-01-17 04:31:39 +00:00
name : 'deleteMfaAuthenticator' ,
description : '/docs/references/users/delete-mfa-authenticator.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2024-01-18 13:56:58 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2024-03-01 02:07:58 +00:00
-> param ( 'type' , null , new WhiteList ([ Type :: TOTP ]), 'Type of authenticator.' )
2024-01-18 13:56:58 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'queueForEvents' )
2024-02-29 20:59:49 +00:00
-> action ( function ( string $userId , string $type , Response $response , Database $dbForProject , Event $queueForEvents ) {
2024-01-18 13:56:58 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
2024-02-29 20:59:49 +00:00
$authenticator = TOTP :: getAuthenticatorFromUser ( $user );
if ( $authenticator === null ) {
2024-03-01 17:04:09 +00:00
throw new Exception ( Exception :: USER_AUTHENTICATOR_NOT_FOUND );
2024-02-25 10:21:56 +00:00
}
2024-01-18 13:56:58 +00:00
2024-02-29 20:59:49 +00:00
$dbForProject -> deleteDocument ( 'authenticators' , $authenticator -> getId ());
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2024-01-18 13:56:58 +00:00
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
$response -> noContent ();
});
2024-01-09 19:04:47 +00:00
App :: post ( '/v1/users/:userId/sessions' )
2023-12-11 16:24:24 +00:00
-> desc ( 'Create session' )
-> groups ([ 'api' , 'users' ])
2024-01-16 11:13:42 +00:00
-> label ( 'event' , 'users.[userId].sessions.[sessionId].create' )
2023-12-11 16:24:24 +00:00
-> label ( 'scope' , 'users.write' )
-> label ( 'audits.event' , 'session.create' )
-> label ( 'audits.resource' , 'user/{request.userId}' )
-> label ( 'usage.metric' , 'sessions.{scope}.requests.create' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-04-12 13:38:00 +00:00
group : 'sessions' ,
2025-01-17 04:31:39 +00:00
name : 'createSession' ,
description : '/docs/references/users/create-session.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_SESSION ,
)
]
))
2024-01-12 17:26:01 +00:00
-> param ( 'userId' , '' , new CustomId (), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2023-12-11 16:24:24 +00:00
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
-> inject ( 'locale' )
-> inject ( 'geodb' )
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , Request $request , Response $response , Database $dbForProject , Document $project , Locale $locale , Reader $geodb , Event $queueForEvents ) {
$user = $dbForProject -> getDocument ( 'users' , $userId );
2024-02-21 09:48:16 +00:00
if ( $user -> isEmpty ()) {
2024-01-17 11:03:04 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2023-12-11 16:24:24 +00:00
}
2024-06-21 22:29:43 +00:00
$secret = Auth :: tokenGenerator ( Auth :: TOKEN_LENGTH_SESSION );
2023-12-11 16:24:24 +00:00
$detector = new Detector ( $request -> getUserAgent ( 'UNKNOWN' ));
$record = $geodb -> get ( $request -> getIP ());
$duration = $project -> getAttribute ( 'auths' , [])[ 'duration' ] ? ? Auth :: TOKEN_EXPIRATION_LOGIN_LONG ;
$expire = DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime (), $duration ));
$session = new Document ( array_merge (
[
'$id' => ID :: unique (),
'userId' => $user -> getId (),
2025-05-26 05:42:11 +00:00
'userInternalId' => $user -> getSequence (),
2023-12-11 16:24:24 +00:00
'provider' => Auth :: SESSION_PROVIDER_SERVER ,
'secret' => Auth :: hash ( $secret ), // One way hash encryption to protect DB leak
'userAgent' => $request -> getUserAgent ( 'UNKNOWN' ),
2024-11-22 21:52:27 +00:00
'factors' => [ 'server' ],
2023-12-11 16:24:24 +00:00
'ip' => $request -> getIP (),
'countryCode' => ( $record ) ? \strtolower ( $record [ 'country' ][ 'iso_code' ]) : '--' ,
2024-06-21 22:25:27 +00:00
'expire' => $expire ,
2023-12-11 16:24:24 +00:00
],
$detector -> getOS (),
$detector -> getClient (),
$detector -> getDevice ()
));
2025-01-11 17:56:36 +00:00
$session -> setAttribute ( '$permissions' , [
Permission :: read ( Role :: user ( $user -> getId ())),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: user ( $user -> getId ())),
]);
2023-12-11 16:24:24 +00:00
$countryName = $locale -> getText ( 'countries.' . strtolower ( $session -> getAttribute ( 'countryCode' )), $locale -> getText ( 'locale.country.unknown' ));
$session = $dbForProject -> createDocument ( 'sessions' , $session );
2024-11-22 21:52:27 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2023-12-11 16:24:24 +00:00
$session
2024-11-22 21:52:27 +00:00
-> setAttribute ( 'secret' , Auth :: encodeSession ( $user -> getId (), $secret ))
2023-12-11 16:24:24 +00:00
-> setAttribute ( 'countryName' , $countryName );
$queueForEvents
-> setParam ( 'userId' , $user -> getId ())
-> setParam ( 'sessionId' , $session -> getId ())
-> setPayload ( $response -> output ( $session , Response :: MODEL_SESSION ));
return $response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $session , Response :: MODEL_SESSION );
});
2023-11-30 11:35:52 +00:00
App :: post ( '/v1/users/:userId/tokens' )
-> desc ( 'Create token' )
2023-09-28 12:45:52 +00:00
-> groups ([ 'api' , 'users' ])
2024-01-16 10:40:49 +00:00
-> label ( 'event' , 'users.[userId].tokens.[tokenId].create' )
2023-09-28 12:45:52 +00:00
-> label ( 'scope' , 'users.write' )
2023-10-05 10:18:19 +00:00
-> label ( 'audits.event' , 'tokens.create' )
2023-09-28 12:45:52 +00:00
-> label ( 'audits.resource' , 'user/{request.userId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-04-12 13:38:00 +00:00
group : 'sessions' ,
2025-01-17 04:31:39 +00:00
name : 'createToken' ,
description : '/docs/references/users/create-token.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_TOKEN ,
)
]
))
2024-01-17 11:03:04 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2024-01-12 17:35:16 +00:00
-> param ( 'length' , 6 , new Range ( 4 , 128 ), 'Token length in characters. The default length is 6 characters' , true )
-> param ( 'expire' , Auth :: TOKEN_EXPIRATION_GENERIC , new Range ( 60 , Auth :: TOKEN_EXPIRATION_LOGIN_LONG ), 'Token expiration period in seconds. The default expiration is 15 minutes.' , true )
2023-12-11 16:24:24 +00:00
-> inject ( 'request' )
2023-09-28 12:45:52 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-10-23 10:32:09 +00:00
-> inject ( 'queueForEvents' )
2024-01-17 11:03:04 +00:00
-> action ( function ( string $userId , int $length , int $expire , Request $request , Response $response , Database $dbForProject , Event $queueForEvents ) {
2023-10-11 12:20:25 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2023-09-28 12:45:52 +00:00
2024-02-21 09:48:16 +00:00
if ( $user -> isEmpty ()) {
2024-01-17 11:03:04 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2023-09-28 12:45:52 +00:00
}
2023-12-11 16:24:24 +00:00
$secret = Auth :: tokenGenerator ( $length );
2023-11-02 12:52:43 +00:00
$expire = DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime (), $expire ));
2023-09-28 12:45:52 +00:00
$token = new Document ([
'$id' => ID :: unique (),
'userId' => $user -> getId (),
2025-05-26 05:42:11 +00:00
'userInternalId' => $user -> getSequence (),
2023-12-11 16:24:24 +00:00
'type' => Auth :: TOKEN_TYPE_GENERIC ,
2023-12-11 19:41:58 +00:00
'secret' => Auth :: hash ( $secret ),
2023-09-28 12:45:52 +00:00
'expire' => $expire ,
2023-12-11 19:41:58 +00:00
'userAgent' => $request -> getUserAgent ( 'UNKNOWN' ),
2023-12-11 16:24:24 +00:00
'ip' => $request -> getIP ()
2023-09-28 12:45:52 +00:00
]);
$token = $dbForProject -> createDocument ( 'tokens' , $token );
2024-01-25 16:53:51 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2023-10-09 13:49:07 +00:00
$token -> setAttribute ( 'secret' , $secret );
2023-09-28 12:45:52 +00:00
2023-10-23 10:32:09 +00:00
$queueForEvents
2023-10-05 10:18:19 +00:00
-> setParam ( 'userId' , $user -> getId ())
-> setParam ( 'tokenId' , $token -> getId ())
-> setPayload ( $response -> output ( $token , Response :: MODEL_TOKEN ));
2023-09-28 12:45:52 +00:00
return $response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $token , Response :: MODEL_TOKEN );
});
2020-06-28 17:31:21 +00:00
App :: delete ( '/v1/users/:userId/sessions/:sessionId' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Delete user session' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2022-04-04 06:30:07 +00:00
-> label ( 'event' , 'users.[userId].sessions.[sessionId].delete' )
2019-05-09 06:54:39 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'session.delete' )
2022-08-13 09:59:34 +00:00
-> label ( 'audits.resource' , 'user/{request.userId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-04-12 13:38:00 +00:00
group : 'sessions' ,
2025-01-17 04:31:39 +00:00
name : 'deleteSession' ,
description : '/docs/references/users/delete-user-session.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2022-09-19 10:05:42 +00:00
-> param ( 'sessionId' , '' , new UID (), 'Session ID.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , string $sessionId , Response $response , Database $dbForProject , Event $queueForEvents ) {
2019-05-09 06:54:39 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2019-05-09 06:54:39 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-06-30 11:09:28 +00:00
}
2019-05-09 06:54:39 +00:00
2022-04-04 09:59:32 +00:00
$session = $dbForProject -> getDocument ( 'sessions' , $sessionId );
2021-02-19 13:59:36 +00:00
2022-05-23 14:54:50 +00:00
if ( $session -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_SESSION_NOT_FOUND );
2019-05-09 06:54:39 +00:00
}
2022-04-04 09:59:32 +00:00
$dbForProject -> deleteDocument ( 'sessions' , $session -> getId ());
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2022-04-26 10:36:49 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-04-04 06:30:07 +00:00
-> setParam ( 'userId' , $user -> getId ())
2022-10-27 17:32:49 +00:00
-> setParam ( 'sessionId' , $sessionId )
-> setPayload ( $response -> output ( $session , Response :: MODEL_SESSION ));
2022-04-04 06:30:07 +00:00
2020-10-30 19:53:27 +00:00
$response -> noContent ();
2020-12-26 16:54:42 +00:00
});
2019-05-09 06:54:39 +00:00
2020-06-28 17:31:21 +00:00
App :: delete ( '/v1/users/:userId/sessions' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Delete user sessions' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'users' ])
2023-11-06 15:06:56 +00:00
-> label ( 'event' , 'users.[userId].sessions.delete' )
2019-05-09 06:54:39 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'session.delete' )
2022-08-13 09:59:34 +00:00
-> label ( 'audits.resource' , 'user/{user.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-04-12 13:38:00 +00:00
group : 'sessions' ,
2025-01-17 04:31:39 +00:00
name : 'deleteSessions' ,
description : '/docs/references/users/delete-user-sessions.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2021-12-10 12:27:11 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $userId , Response $response , Database $dbForProject , Event $queueForEvents ) {
2019-05-09 06:54:39 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2019-05-09 06:54:39 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-06-30 11:09:28 +00:00
}
2019-05-09 06:54:39 +00:00
2021-07-17 10:04:43 +00:00
$sessions = $user -> getAttribute ( 'sessions' , []);
2022-08-25 08:38:43 +00:00
foreach ( $sessions as $key => $session ) {
/** @var Document $session */
2021-12-27 12:45:23 +00:00
$dbForProject -> deleteDocument ( 'sessions' , $session -> getId ());
2022-05-10 12:13:53 +00:00
//TODO: fix this
2021-07-17 10:04:43 +00:00
}
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2019-05-09 06:54:39 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-04-04 06:30:07 +00:00
-> setParam ( 'userId' , $user -> getId ())
2022-08-25 08:38:43 +00:00
-> setPayload ( $response -> output ( $user , Response :: MODEL_USER ));
2022-04-04 06:30:07 +00:00
2020-10-30 19:53:27 +00:00
$response -> noContent ();
2020-12-26 16:54:42 +00:00
});
2020-08-29 20:40:40 +00:00
App :: delete ( '/v1/users/:userId' )
2023-08-01 15:26:48 +00:00
-> desc ( 'Delete user' )
2020-08-29 20:40:40 +00:00
-> groups ([ 'api' , 'users' ])
2022-04-04 06:30:07 +00:00
-> label ( 'event' , 'users.[userId].delete' )
2020-08-29 20:40:40 +00:00
-> label ( 'scope' , 'users.write' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'user.delete' )
2022-08-13 09:59:34 +00:00
-> label ( 'audits.resource' , 'user/{request.userId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'users' ,
2025-01-17 04:31:39 +00:00
name : 'delete' ,
description : '/docs/references/users/delete.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2022-05-31 14:24:01 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2020-12-26 16:54:42 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForDeletes' )
-> action ( function ( string $userId , Response $response , Database $dbForProject , Event $queueForEvents , Delete $queueForDeletes ) {
2022-04-04 06:30:07 +00:00
2021-12-27 12:45:23 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
2020-08-29 20:40:40 +00:00
2022-05-16 09:58:17 +00:00
if ( $user -> isEmpty ()) {
2022-08-16 06:59:03 +00:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2020-08-29 20:40:40 +00:00
}
2021-10-05 14:29:43 +00:00
// clone user object to send to workers
$clone = clone $user ;
2022-05-16 09:58:17 +00:00
$dbForProject -> deleteDocument ( 'users' , $userId );
2025-05-26 05:42:11 +00:00
DeleteIdentities :: delete ( $dbForProject , Query :: equal ( 'userInternalId' , [ $user -> getSequence ()]));
DeleteTargets :: delete ( $dbForProject , Query :: equal ( 'userInternalId' , [ $user -> getSequence ()]));
2020-08-29 20:40:40 +00:00
2022-12-20 16:11:30 +00:00
$queueForDeletes
2022-04-17 20:34:32 +00:00
-> setType ( DELETE_TYPE_DOCUMENT )
2022-08-25 08:38:43 +00:00
-> setDocument ( $clone );
2020-10-30 19:53:27 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-04-04 06:30:07 +00:00
-> setParam ( 'userId' , $user -> getId ())
2022-08-25 08:38:43 +00:00
-> setPayload ( $response -> output ( $clone , Response :: MODEL_USER ));
2021-10-05 14:29:43 +00:00
2020-08-29 20:40:40 +00:00
$response -> noContent ();
2020-12-26 16:54:42 +00:00
});
2021-08-20 09:57:46 +00:00
2023-08-18 17:58:12 +00:00
App :: delete ( '/v1/users/:userId/targets/:targetId' )
-> desc ( 'Delete user target' )
-> groups ([ 'api' , 'users' ])
2023-10-25 17:33:23 +00:00
-> label ( 'audits.event' , 'target.delete' )
2023-09-20 18:07:10 +00:00
-> label ( 'audits.resource' , 'target/{request.$targetId}' )
2023-10-31 18:23:46 +00:00
-> label ( 'event' , 'users.[userId].targets.[targetId].delete' )
2023-08-18 17:58:12 +00:00
-> label ( 'scope' , 'targets.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'targets' ,
2025-01-17 04:31:39 +00:00
name : 'deleteTarget' ,
description : '/docs/references/users/delete-target.md' ,
auth : [ AuthType :: KEY , AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2023-08-23 20:24:25 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
-> param ( 'targetId' , '' , new UID (), 'Target ID.' )
2023-10-31 18:23:46 +00:00
-> inject ( 'queueForEvents' )
2024-01-25 07:01:15 +00:00
-> inject ( 'queueForDeletes' )
2023-08-18 17:58:12 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2024-01-25 07:01:15 +00:00
-> action ( function ( string $userId , string $targetId , Event $queueForEvents , Delete $queueForDeletes , Response $response , Database $dbForProject ) {
2023-08-18 17:58:12 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
$target = $dbForProject -> getDocument ( 'targets' , $targetId );
if ( $target -> isEmpty ()) {
throw new Exception ( Exception :: USER_TARGET_NOT_FOUND );
2023-10-06 13:53:46 +00:00
}
if ( $user -> getId () !== $target -> getAttribute ( 'userId' )) {
throw new Exception ( Exception :: USER_TARGET_NOT_FOUND );
2023-08-18 17:58:12 +00:00
}
2023-10-31 18:23:46 +00:00
$dbForProject -> deleteDocument ( 'targets' , $target -> getId ());
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2023-08-18 17:58:12 +00:00
2024-01-25 07:01:15 +00:00
$queueForDeletes
-> setType ( DELETE_TYPE_TARGET )
-> setDocument ( $target );
2023-08-18 17:58:12 +00:00
2023-10-31 18:23:46 +00:00
$queueForEvents
-> setParam ( 'userId' , $user -> getId ())
-> setParam ( 'targetId' , $target -> getId ());
2023-08-18 17:58:12 +00:00
$response -> noContent ();
});
2023-05-18 01:11:45 +00:00
App :: delete ( '/v1/users/identities/:identityId' )
2023-12-27 23:35:32 +00:00
-> desc ( 'Delete identity' )
2023-05-18 01:11:45 +00:00
-> groups ([ 'api' , 'users' ])
-> label ( 'event' , 'users.[userId].identities.[identityId].delete' )
-> label ( 'scope' , 'users.write' )
-> label ( 'audits.event' , 'identity.delete' )
-> label ( 'audits.resource' , 'identity/{request.$identityId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-03-31 05:48:17 +00:00
group : 'identities' ,
2025-01-17 04:31:39 +00:00
name : 'deleteIdentity' ,
description : '/docs/references/users/delete-identity.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE ,
))
2023-05-18 01:11:45 +00:00
-> param ( 'identityId' , '' , new UID (), 'Identity ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-12-27 23:35:32 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $identityId , Response $response , Database $dbForProject , Event $queueForEvents ) {
2023-05-18 01:11:45 +00:00
$identity = $dbForProject -> getDocument ( 'identities' , $identityId );
if ( $identity -> isEmpty ()) {
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
}
$dbForProject -> deleteDocument ( 'identities' , $identityId );
2023-12-27 23:35:32 +00:00
$queueForEvents
-> setParam ( 'userId' , $identity -> getAttribute ( 'userId' ))
-> setParam ( 'identityId' , $identity -> getId ())
-> setPayload ( $response -> output ( $identity , Response :: MODEL_IDENTITY ));
2023-05-18 01:11:45 +00:00
return $response -> noContent ();
});
2024-05-27 20:04:50 +00:00
App :: post ( '/v1/users/:userId/jwts' )
-> desc ( 'Create user JWT' )
-> groups ([ 'api' , 'users' ])
-> label ( 'scope' , 'users.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-04-12 13:38:00 +00:00
group : 'sessions' ,
2025-01-17 04:31:39 +00:00
name : 'createJWT' ,
description : '/docs/references/users/create-user-jwt.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_JWT ,
)
]
))
2024-05-27 20:04:50 +00:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2024-07-21 13:00:12 +00:00
-> param ( 'sessionId' , '' , new UID (), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.' , true )
2024-05-28 09:25:54 +00:00
-> param ( 'duration' , 900 , new Range ( 0 , 3600 ), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.' , true )
2024-05-27 20:04:50 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2024-05-28 09:25:54 +00:00
-> action ( function ( string $userId , string $sessionId , int $duration , Response $response , Database $dbForProject ) {
2024-05-27 20:04:50 +00:00
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
throw new Exception ( Exception :: USER_NOT_FOUND );
}
$sessions = $user -> getAttribute ( 'sessions' , []);
$session = new Document ();
2024-09-05 02:25:11 +00:00
if ( $sessionId === 'recent' ) {
2024-05-27 20:04:50 +00:00
// Get most recent
$session = \count ( $sessions ) > 0 ? $sessions [ \count ( $sessions ) - 1 ] : new Document ();
} else {
// Find by ID
foreach ( $sessions as $loopSession ) { /** @var Utopia\Database\Document $loopSession */
if ( $loopSession -> getId () == $sessionId ) {
$session = $loopSession ;
2024-05-29 07:31:08 +00:00
break ;
2024-05-27 20:04:50 +00:00
}
}
}
2024-05-29 07:51:51 +00:00
$jwt = new JWT ( System :: getEnv ( '_APP_OPENSSL_KEY_V1' ), 'HS256' , $duration , 0 );
2024-05-27 20:04:50 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( new Document ([ 'jwt' => $jwt -> encode ([
'userId' => $user -> getId (),
2024-07-21 13:00:12 +00:00
'sessionId' => $session -> isEmpty () ? '' : $session -> getId ()
2024-05-27 20:04:50 +00:00
])]), Response :: MODEL_JWT );
});
2021-08-20 09:57:46 +00:00
App :: get ( '/v1/users/usage' )
2024-02-26 02:44:20 +00:00
-> desc ( 'Get users usage stats' )
2023-10-25 07:39:59 +00:00
-> groups ([ 'api' , 'users' ])
2021-08-20 09:57:46 +00:00
-> label ( 'scope' , 'users.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'users' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'getUsage' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/users/get-usage.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USAGE_USERS ,
)
]
))
2023-11-08 09:09:32 +00:00
-> param ( 'range' , '30d' , new WhiteList ([ '24h' , '30d' , '90d' ], true ), 'Date range.' , true )
2021-08-20 09:57:46 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2021-08-20 09:57:46 +00:00
-> inject ( 'register' )
2023-10-25 07:39:59 +00:00
-> action ( function ( string $range , Response $response , Database $dbForProject ) {
$periods = Config :: getParam ( 'usage' , []);
$stats = $usage = [];
$days = $periods [ $range ];
$metrics = [
METRIC_USERS ,
METRIC_SESSIONS ,
];
2021-08-20 09:57:46 +00:00
2023-11-08 09:09:32 +00:00
Authorization :: skip ( function () use ( $dbForProject , $days , $metrics , & $stats ) {
2023-11-01 18:44:06 +00:00
foreach ( $metrics as $count => $metric ) {
2024-03-05 09:36:23 +00:00
$result = $dbForProject -> findOne ( 'stats' , [
2023-11-01 18:44:06 +00:00
Query :: equal ( 'metric' , [ $metric ]),
Query :: equal ( 'period' , [ 'inf' ])
]);
2021-10-27 19:57:20 +00:00
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'total' ] = $result [ 'value' ] ? ? 0 ;
2023-10-25 07:39:59 +00:00
$limit = $days [ 'limit' ];
$period = $days [ 'period' ];
2024-03-05 09:36:23 +00:00
$results = $dbForProject -> find ( 'stats' , [
2023-10-25 07:39:59 +00:00
Query :: equal ( 'metric' , [ $metric ]),
2023-10-25 12:06:54 +00:00
Query :: equal ( 'period' , [ $period ]),
2023-10-25 07:39:59 +00:00
Query :: limit ( $limit ),
Query :: orderDesc ( 'time' ),
]);
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'data' ] = [];
2023-10-25 07:39:59 +00:00
foreach ( $results as $result ) {
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'data' ][ $result -> getAttribute ( 'time' )] = [
'value' => $result -> getAttribute ( 'value' ),
2023-10-25 07:39:59 +00:00
];
2022-05-23 14:54:50 +00:00
}
2023-10-25 07:39:59 +00:00
}
});
$format = match ( $days [ 'period' ]) {
'1h' => 'Y-m-d\TH:00:00.000P' ,
'1d' => 'Y-m-d\T00:00:00.000P' ,
};
2024-03-06 17:34:21 +00:00
foreach ( $metrics as $metric ) {
$usage [ $metric ][ 'total' ] = $stats [ $metric ][ 'total' ];
$usage [ $metric ][ 'data' ] = [];
$leap = time () - ( $days [ 'limit' ] * $days [ 'factor' ]);
while ( $leap < time ()) {
$leap += $days [ 'factor' ];
$formatDate = date ( $format , $leap );
$usage [ $metric ][ 'data' ][] = [
'value' => $stats [ $metric ][ 'data' ][ $formatDate ][ 'value' ] ? ? 0 ,
'date' => $formatDate ,
];
}
2021-08-20 09:57:46 +00:00
}
2021-08-26 18:53:55 +00:00
2023-10-25 07:39:59 +00:00
$response -> dynamic ( new Document ([
'range' => $range ,
2023-11-08 09:09:32 +00:00
'usersTotal' => $usage [ $metrics [ 0 ]][ 'total' ],
'sessionsTotal' => $usage [ $metrics [ 1 ]][ 'total' ],
'users' => $usage [ $metrics [ 0 ]][ 'data' ],
'sessions' => $usage [ $metrics [ 1 ]][ 'data' ],
2023-10-25 07:39:59 +00:00
]), Response :: MODEL_USAGE_USERS );
2025-01-17 04:39:16 +00:00
});