post('/v1/users') ->desc('Create User') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'create') ->label('sdk.description', '/docs/references/users/create-user.md') ->param('email', '', function () { return new Email(); }, 'User email.') ->param('password', '', function () { return new Password(); }, 'User password.') ->param('name', '', function () { return new Text(100); }, 'User name.', true) ->action( function ($email, $password, $name) use ($response, $projectDB) { $profile = $projectDB->getCollection([ // Get user by email address 'limit' => 1, 'first' => true, 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, 'email='.$email, ], ]); if (!empty($profile)) { throw new Exception('User already registered', 409); } try { $user = $projectDB->createDocument([ '$collection' => Database::SYSTEM_COLLECTION_USERS, '$permissions' => [ 'read' => ['*'], 'write' => ['user:{self}'], ], 'email' => $email, 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash($password), 'password-update' => time(), 'registration' => time(), 'reset' => false, 'name' => $name, ], ['email' => $email]); } catch (Duplicate $th) { throw new Exception('Account already exists', 409); } $oauth2Keys = []; foreach (Config::getParam('providers') as $key => $provider) { if (!$provider['enabled']) { continue; } $oauth2Keys[] = 'oauth2'.ucfirst($key); $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->json(array_merge($user->getArrayCopy(array_merge([ '$id', 'status', 'email', 'registration', 'emailVerification', 'name', ], $oauth2Keys)), ['roles' => []])); } ); $utopia->get('/v1/users') ->desc('List Users') ->label('scope', 'users.read') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'list') ->label('sdk.description', '/docs/references/users/list-users.md') ->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true) ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true) ->action( function ($search, $limit, $offset, $orderType) use ($response, $projectDB) { $results = $projectDB->getCollection([ 'limit' => $limit, 'offset' => $offset, 'orderField' => 'registration', 'orderType' => $orderType, 'orderCast' => 'int', 'search' => $search, 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, ], ]); $oauth2Keys = []; foreach (Config::getParam('providers') as $key => $provider) { if (!$provider['enabled']) { continue; } $oauth2Keys[] = 'oauth2'.ucfirst($key); $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } $results = array_map(function ($value) use ($oauth2Keys) { /* @var $value \Database\Document */ return $value->getArrayCopy(array_merge( [ '$id', 'status', 'email', 'registration', 'emailVerification', 'name', ], $oauth2Keys )); }, $results); $response->json(['sum' => $projectDB->getSum(), 'users' => $results]); } ); $utopia->get('/v1/users/:userId') ->desc('Get User') ->label('scope', 'users.read') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/users/get-user.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action( function ($userId) use ($response, $projectDB) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } $oauth2Keys = []; foreach (Config::getParam('providers') as $key => $provider) { if (!$provider['enabled']) { continue; } $oauth2Keys[] = 'oauth2'.ucfirst($key); $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } $response->json(array_merge($user->getArrayCopy(array_merge( [ '$id', 'status', 'email', 'registration', 'emailVerification', 'name', ], $oauth2Keys )), ['roles' => []])); } ); $utopia->get('/v1/users/:userId/prefs') ->desc('Get User Preferences') ->label('scope', 'users.read') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'getPrefs') ->label('sdk.description', '/docs/references/users/get-user-prefs.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action( function ($userId) use ($response, $projectDB) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } $prefs = $user->getAttribute('prefs', ''); try { $prefs = json_decode($prefs, true); $prefs = ($prefs) ? $prefs : []; } catch (\Exception $error) { throw new Exception('Failed to parse prefs', 500); } $response->json($prefs); } ); $utopia->get('/v1/users/:userId/sessions') ->desc('Get User Sessions') ->label('scope', 'users.read') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'getSessions') ->label('sdk.description', '/docs/references/users/get-user-sessions.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action( function ($userId) use ($response, $projectDB) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } $tokens = $user->getAttribute('tokens', []); $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); $sessions = []; $index = 0; $countries = Locale::getText('countries'); foreach ($tokens as $token) { /* @var $token Document */ if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { continue; } $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN'; $dd = new DeviceDetector($userAgent); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) // $dd->skipBotDetection(); $dd->parse(); $sessions[$index] = [ '$id' => $token->getId(), 'OS' => $dd->getOs(), 'client' => $dd->getClient(), 'device' => $dd->getDevice(), 'brand' => $dd->getBrand(), 'model' => $dd->getModel(), 'ip' => $token->getAttribute('ip', ''), 'geo' => [], ]; try { $record = $reader->country($token->getAttribute('ip', '')); $sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode); $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); } catch (\Exception $e) { $sessions[$index]['geo']['isoCode'] = '--'; $sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown'); } ++$index; } $response->json($sessions); } ); $utopia->get('/v1/users/:userId/logs') ->desc('Get User Logs') ->label('scope', 'users.read') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'getLogs') ->label('sdk.description', '/docs/references/users/get-user-logs.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action( function ($userId) use ($response, $register, $projectDB, $project) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } $adapter = new AuditAdapter($register->get('db')); $adapter->setNamespace('app_'.$project->getId()); $audit = new Audit($adapter); $countries = Locale::getText('countries'); $logs = $audit->getLogsByUserAndActions($user->getId(), [ 'account.create', 'account.delete', 'account.update.name', 'account.update.email', 'account.update.password', 'account.update.prefs', 'account.sessions.create', 'account.sessions.delete', 'account.recovery.create', 'account.recovery.update', 'account.verification.create', 'account.verification.update', 'teams.membership.create', 'teams.membership.update', 'teams.membership.delete', ]); $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); $output = []; foreach ($logs as $i => &$log) { $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN'; $dd = new DeviceDetector($log['userAgent']); $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) $dd->parse(); $output[$i] = [ 'event' => $log['event'], 'ip' => $log['ip'], 'time' => strtotime($log['time']), 'OS' => $dd->getOs(), 'client' => $dd->getClient(), 'device' => $dd->getDevice(), 'brand' => $dd->getBrand(), 'model' => $dd->getModel(), 'geo' => [], ]; try { $record = $reader->country($log['ip']); $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode); $output[$i]['geo']['country'] = $record->country->name; $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); } catch (\Exception $e) { $output[$i]['geo']['isoCode'] = '--'; $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown'); } } $response->json($output); } ); $utopia->patch('/v1/users/:userId/status') ->desc('Update User Status') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'updateStatus') ->label('sdk.description', '/docs/references/users/update-user-status.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('status', '', function () { return new WhiteList([Auth::USER_STATUS_ACTIVATED, Auth::USER_STATUS_BLOCKED, Auth::USER_STATUS_UNACTIVATED]); }, 'User Status code. To activate the user pass '.Auth::USER_STATUS_ACTIVATED.', to block the user pass '.Auth::USER_STATUS_BLOCKED.' and for disabling the user pass '.Auth::USER_STATUS_UNACTIVATED) ->action( function ($userId, $status) use ($response, $projectDB) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [ 'status' => (int)$status, ])); if (false === $user) { throw new Exception('Failed saving user to DB', 500); } $oauth2Keys = []; foreach (Config::getParam('providers') as $key => $provider) { if (!$provider['enabled']) { continue; } $oauth2Keys[] = 'oauth2'.ucfirst($key); $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } $response ->json(array_merge($user->getArrayCopy(array_merge([ '$id', 'status', 'email', 'registration', 'emailVerification', 'name', ], $oauth2Keys)), ['roles' => []])); } ); $utopia->patch('/v1/users/:userId/prefs') ->desc('Update User Preferences') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'updatePrefs') ->label('sdk.description', '/docs/references/users/update-user-prefs.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('prefs', '', function () { return new Assoc();}, 'Prefs key-value JSON object.') ->action( function ($userId, $prefs) use ($response, $projectDB) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } $old = json_decode($user->getAttribute('prefs', '{}'), true); $old = ($old) ? $old : []; $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [ 'prefs' => json_encode(array_merge($old, $prefs)), ])); if (false === $user) { throw new Exception('Failed saving user to DB', 500); } $prefs = $user->getAttribute('prefs', ''); try { $prefs = json_decode($prefs, true); $prefs = ($prefs) ? $prefs : []; } catch (\Exception $error) { throw new Exception('Failed to parse prefs', 500); } $response->json($prefs); } ); $utopia->delete('/v1/users/:userId/sessions/:sessionId') ->desc('Delete User Session') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'deleteSession') ->label('sdk.description', '/docs/references/users/delete-user-session.md') ->label('abuse-limit', 100) ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('sessionId', null, function () { return new UID(); }, 'User unique session ID.') ->action( function ($userId, $sessionId) use ($response, $request, $projectDB) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } $tokens = $user->getAttribute('tokens', []); foreach ($tokens as $token) { /* @var $token Document */ if ($sessionId == $token->getId()) { if (!$projectDB->deleteDocument($token->getId())) { throw new Exception('Failed to remove token from DB', 500); } } } $response->json(array('result' => 'success')); } ); $utopia->delete('/v1/users/:userId/sessions') ->desc('Delete User Sessions') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') ->label('sdk.method', 'deleteSessions') ->label('sdk.description', '/docs/references/users/delete-user-sessions.md') ->label('abuse-limit', 100) ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action( function ($userId) use ($response, $request, $projectDB) { $user = $projectDB->getDocument($userId); if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { throw new Exception('User not found', 404); } $tokens = $user->getAttribute('tokens', []); foreach ($tokens as $token) { /* @var $token Document */ if (!$projectDB->deleteDocument($token->getId())) { throw new Exception('Failed to remove token from DB', 500); } } $response->json(array('result' => 'success')); } );