diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 60f483c3ae..6404b41188 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -48,7 +48,6 @@ $utopia->get('/v1/account') '$uid', 'email', 'registration', - 'confirm', 'name', ], $oauthKeys @@ -216,10 +215,9 @@ $utopia->post('/v1/account') ->label('abuse-limit', 10) ->param('email', '', function () { return new Email(); }, 'Account email') ->param('password', '', function () { return new Password(); }, 'User password') - ->param('confirm', '', function () use ($clients) { return new Host($clients); }, 'Confirmation URL to redirect user after confirm token has been sent to user email') // TODO add our own built-in confirm page ->param('name', '', function () { return new Text(100); }, 'User name', true) ->action( - function ($email, $password, $confirm, $name) use ($request, $response, $register, $audit, $projectDB, $project, $webhook) { + function ($email, $password, $name) use ($request, $response, $audit, $projectDB, $project, $webhook) { if ('console' === $project->getUid()) { $whitlistEmails = $project->getAttribute('authWhitelistEmails'); $whitlistIPs = $project->getAttribute('authWhitelistIPs'); @@ -252,7 +250,6 @@ $utopia->post('/v1/account') } $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; - $confirmSecret = Auth::tokenGenerator(); $loginSecret = Auth::tokenGenerator(); Authorization::disable(); @@ -282,15 +279,6 @@ $utopia->post('/v1/account') Authorization::setRole('user:'.$user->getUid()); $user - ->setAttribute('tokens', new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]], - 'type' => Auth::TOKEN_TYPE_CONFIRM, - 'secret' => Auth::hash($confirmSecret), // On way hash encryption to protect DB leak - 'expire' => time() + Auth::TOKEN_EXPIRATION_CONFIRM, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND) ->setAttribute('tokens', new Document([ '$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]], @@ -308,39 +296,6 @@ $utopia->post('/v1/account') throw new Exception('Failed saving tokens to DB', 500); } - // Send email address confirmation email - - $confirm = Template::parseURL($confirm); - $confirm['query'] = Template::mergeQuery(((isset($confirm['query'])) ? $confirm['query'] : ''), ['userId' => $user->getUid(), 'token' => $confirmSecret]); - $confirm = Template::unParseURL($confirm); - - $body = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('auth.emails.confirm.body')); - $body - ->setParam('{{direction}}', Locale::getText('settings.direction')) - ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('{{name}}', $name) - ->setParam('{{redirect}}', $confirm) - ; - - $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */ - - $mail->addAddress($email, $name); - - $mail->Subject = Locale::getText('auth.emails.confirm.title'); - $mail->Body = $body->render(); - $mail->AltBody = strip_tags($body->render()); - - try { - $mail->send(); - } catch (\Exception $error) { - // if($failure) { - // $response->redirect($failure); - // return; - // } - - // throw new Exception('Problem sending mail: ' . $error->getMessage(), 500); - } - $webhook ->setParam('payload', [ 'name' => $name, @@ -360,9 +315,81 @@ $utopia->post('/v1/account') } ); +$utopia->post('/v1/account/sessions') + ->desc('Login') + ->label('webhook', 'account.sessions.create') + ->label('scope', 'account') + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'createAccountSession') + ->label('sdk.description', '/docs/references/account/create-account-session.md') + ->label('abuse-limit', 10) + ->label('abuse-key', 'url:{url},email:{param-email}') + ->param('email', '', function () { return new Email(); }, 'User account email address') + ->param('password', '', function () { return new Password(); }, 'User account password') + ->action( + function ($email, $password) use ($response, $request, $projectDB, $audit, $webhook) { + $profile = $projectDB->getCollection([ // Get user by email address + 'limit' => 1, + 'first' => true, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'email='.$email, + ], + ]); + + if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { + $audit + //->setParam('userId', $profile->getUid()) + ->setParam('event', 'auth.failure') + ; + + throw new Exception('Invalid credentials', 401); // Wrong password or username + } + + $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $secret = Auth::tokenGenerator(); + + $profile->setAttribute('tokens', new Document([ + '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$permissions' => ['read' => ['user:'.$profile->getUid()], 'write' => ['user:'.$profile->getUid()]], + 'type' => Auth::TOKEN_TYPE_LOGIN, + 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak + 'expire' => $expiry, + 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), + 'ip' => $request->getIP(), + ]), Document::SET_TYPE_APPEND); + + Authorization::setRole('user:'.$profile->getUid()); + + $profile = $projectDB->updateDocument($profile->getArrayCopy()); + + if (false === $profile) { + throw new Exception('Failed saving user to DB', 500); + } + + $webhook + ->setParam('payload', [ + 'name' => $profile->getAttribute('name', ''), + 'email' => $profile->getAttribute('email', ''), + ]) + ; + + $audit + ->setParam('userId', $profile->getUid()) + ->setParam('event', 'auth.login') + ; + + $response + ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getUid(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, COOKIE_SAMESITE); + + $response + ->json(array('result' => 'success')); + } + ); + $utopia->patch('/v1/account/name') ->desc('Update Account Name') - ->label('webhook', 'account.update-account-name') + ->label('webhook', 'account.update.name') ->label('scope', 'account') ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateAccountName') @@ -386,7 +413,7 @@ $utopia->patch('/v1/account/name') $utopia->patch('/v1/account/password') ->desc('Update Account Password') - ->label('webhook', 'account.update-account-password') + ->label('webhook', 'account.update.password') ->label('scope', 'account') ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateAccountPassword') @@ -415,7 +442,7 @@ $utopia->patch('/v1/account/password') $utopia->patch('/v1/account/email') ->desc('Update Account Email') - ->label('webhook', 'account.update-email') + ->label('webhook', 'account.update.email') ->label('scope', 'account') ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateEmail') @@ -459,7 +486,7 @@ $utopia->patch('/v1/account/email') $utopia->patch('/v1/account/prefs') ->desc('Update Account Prefs') - ->label('webhook', 'account') + ->label('webhook', 'account.update.prefs') ->label('scope', 'account') ->label('sdk.namespace', 'account') ->label('sdk.method', 'updatePrefs') diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 62f7ef5fff..db42b6d473 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -160,6 +160,7 @@ $utopia->post('/v1/database') $utopia->put('/v1/database/:collectionId') ->desc('Update Collection') ->label('scope', 'collections.write') + ->label('webhook', 'database.collections.update') ->label('sdk.namespace', 'database') ->label('sdk.method', 'updateCollection') ->label('sdk.description', '/docs/references/database/update-collection.md') @@ -169,7 +170,7 @@ $utopia->put('/v1/database/:collectionId') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->param('rules', [], function () use ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation', true) ->action( - function ($collectionId, $name, $read, $write, $rules) use ($response, $projectDB) { + function ($collectionId, $name, $read, $write, $rules) use ($response, $projectDB, $webhook, $audit) { $collection = $projectDB->getDocument($collectionId, false); if (empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { @@ -203,6 +204,18 @@ $utopia->put('/v1/database/:collectionId') throw new Exception('Failed saving collection to DB', 500); } + $data = $collection->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; + + $audit + ->setParam('event', 'database.collections.update') + ->setParam('resource', 'database/collections/'.$data['$uid']) + ->setParam('data', $data) + ; + $response->json($collection->getArrayCopy()); } ); @@ -210,12 +223,13 @@ $utopia->put('/v1/database/:collectionId') $utopia->delete('/v1/database/:collectionId') ->desc('Delete Collection') ->label('scope', 'collections.write') + ->label('webhook', 'database.collections.delete') ->label('sdk.namespace', 'database') ->label('sdk.method', 'deleteCollection') ->label('sdk.description', '/docs/references/database/delete-collection.md') ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.') ->action( - function ($collectionId) use ($response, $projectDB, $audit) { + function ($collectionId) use ($response, $projectDB, $webhook, $audit) { $collection = $projectDB->getDocument($collectionId, false); if (empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { @@ -225,11 +239,17 @@ $utopia->delete('/v1/database/:collectionId') if (!$projectDB->deleteDocument($collectionId)) { throw new Exception('Failed to remove collection from DB', 500); } + + $data = $collection->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; $audit - ->setParam('event', 'database.collections.create') - ->setParam('resource', 'database/collection/'.$collection->getUid()) - ->setParam('data', $collection->getArrayCopy()) // Audit document in case of malicious or disastrous action + ->setParam('event', 'database.collections.delete') + ->setParam('resource', 'database/collections/'.$data['$uid']) + ->setParam('data', $data) ; $response->noContent(); @@ -454,7 +474,7 @@ $utopia->post('/v1/database/:collectionId/documents') $utopia->patch('/v1/database/:collectionId/documents/:documentId') ->desc('Update Document') - ->label('webhook', 'database.documents.patch') + ->label('webhook', 'database.documents.update') ->label('scope', 'documents.write') ->label('sdk.namespace', 'database') ->label('sdk.method', 'updateDocument') @@ -518,7 +538,7 @@ $utopia->patch('/v1/database/:collectionId/documents/:documentId') ; $audit - ->setParam('event', 'database.documents.patch') + ->setParam('event', 'database.documents.update') ->setParam('resource', 'database/document/'.$data['$uid']) ->setParam('data', $data) ; @@ -533,13 +553,14 @@ $utopia->patch('/v1/database/:collectionId/documents/:documentId') $utopia->delete('/v1/database/:collectionId/documents/:documentId') ->desc('Delete Document') ->label('scope', 'documents.write') + ->label('webhook', 'database.documents.delete') ->label('sdk.namespace', 'database') ->label('sdk.method', 'deleteDocument') ->label('sdk.description', '/docs/references/database/delete-document.md') ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID') ->param('documentId', null, function () { return new UID(); }, 'Document unique ID') ->action( - function ($collectionId, $documentId) use ($response, $projectDB, $audit, $isDev) { + function ($collectionId, $documentId) use ($response, $projectDB, $audit, $webhook, $isDev) { $collection = $projectDB->getDocument($collectionId, $isDev); $document = $projectDB->getDocument($documentId, $isDev); @@ -561,10 +582,16 @@ $utopia->delete('/v1/database/:collectionId/documents/:documentId') throw new Exception('Failed to remove document from DB', 500); } + $data = $document->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; + $audit ->setParam('event', 'database.documents.delete') - ->setParam('resource', 'database/document/'.$documentId) - ->setParam('data', $document->getArrayCopy()) // Audit document in case of malicious or disastrous action + ->setParam('resource', 'database/document/'.$data['$uid']) + ->setParam('data', $data) // Audit document in case of malicious or disastrous action ; $response->noContent(); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 793767f3f9..911e58b649 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -398,6 +398,7 @@ $utopia->get('/v1/storage/files/:fileId/view') $utopia->post('/v1/storage/files') ->desc('Create File') ->label('scope', 'files.write') + ->label('webhook', 'storage.files.create') ->label('sdk.namespace', 'storage') ->label('sdk.method', 'createFile') ->label('sdk.description', '/docs/references/storage/create-file.md') @@ -407,7 +408,7 @@ $utopia->post('/v1/storage/files') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') // ->param('folderId', '', function () { return new UID(); }, 'Folder to associate files with.', true) ->action( - function ($files, $read, $write, $folderId = '') use ($request, $response, $user, $projectDB, $audit, $usage) { + function ($files, $read, $write, $folderId = '') use ($request, $response, $user, $projectDB, $webhook, $audit, $usage) { $files = $request->getFiles('files'); $read = (empty($read)) ? ['user:'.$user->getUid()] : $read; $write = (empty($write)) ? ['user:'.$user->getUid()] : $write; @@ -513,9 +514,13 @@ $utopia->post('/v1/storage/files') throw new Exception('Failed saving file to DB', 500); } + $webhook + ->setParam('payload', $file->getArrayCopy()) + ; + $audit - ->setParam('event', 'storage.upload') - ->setParam('resource', 'storage/file/'.$file->getUid()) + ->setParam('event', 'storage.files.create') + ->setParam('resource', 'storage/files/'.$file->getUid()) ; $usage @@ -535,6 +540,7 @@ $utopia->post('/v1/storage/files') $utopia->put('/v1/storage/files/:fileId') ->desc('Update File') ->label('scope', 'files.write') + ->label('webhook', 'storage.files.update') ->label('sdk.namespace', 'storage') ->label('sdk.method', 'updateFile') ->label('sdk.description', '/docs/references/storage/update-file.md') @@ -543,7 +549,7 @@ $utopia->put('/v1/storage/files/:fileId') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') //->param('folderId', '', function () { return new UID(); }, 'Folder to associate files with.', true) ->action( - function ($fileId, $read, $write, $folderId = '') use ($response, $projectDB) { + function ($fileId, $read, $write, $folderId = '') use ($response, $projectDB, $audit, $webhook) { $file = $projectDB->getDocument($fileId); if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { @@ -562,6 +568,15 @@ $utopia->put('/v1/storage/files/:fileId') throw new Exception('Failed saving file to DB', 500); } + $webhook + ->setParam('payload', $file->getArrayCopy()) + ; + + $audit + ->setParam('event', 'storage.files.update') + ->setParam('resource', 'storage/files/'.$file->getUid()) + ; + $response->json($file->getArrayCopy()); } ); @@ -569,12 +584,13 @@ $utopia->put('/v1/storage/files/:fileId') $utopia->delete('/v1/storage/files/:fileId') ->desc('Delete File') ->label('scope', 'files.write') + ->label('webhook', 'storage.files.delete') ->label('sdk.namespace', 'storage') ->label('sdk.method', 'deleteFile') ->label('sdk.description', '/docs/references/storage/delete-file.md') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') ->action( - function ($fileId) use ($response, $projectDB, $audit, $usage) { + function ($fileId) use ($response, $projectDB, $webhook, $audit, $usage) { $file = $projectDB->getDocument($fileId); if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { @@ -589,9 +605,13 @@ $utopia->delete('/v1/storage/files/:fileId') } } + $webhook + ->setParam('payload', $file->getArrayCopy()) + ; + $audit - ->setParam('event', 'storage.delete') - ->setParam('resource', 'storage/file/'.$file->getUid()) + ->setParam('event', 'storage.files.delete') + ->setParam('resource', 'storage/files/'.$file->getUid()) ; $usage