diff --git a/lib/Controller/HostedSignalingServerController.php b/lib/Controller/HostedSignalingServerController.php index 57b67669b3..9578ba7cb2 100644 --- a/lib/Controller/HostedSignalingServerController.php +++ b/lib/Controller/HostedSignalingServerController.php @@ -83,11 +83,11 @@ class HostedSignalingServerController extends OCSController { public function requestTrial(string $url, string $name, string $email, string $language, string $country): DataResponse { $client = $this->clientService->newClient(); + $apiServer = $this->config->getSystemValue('talk_hardcoded_hpb_service', 'https://api.spreed.cloud'); try { $nonce = $this->secureRandom->generate(32); $this->config->setAppValue('spreed', 'hosted-signaling-server-nonce', $nonce); - $apiServer = $this->config->getSystemValue('talk_hardcoded_hpb_service', 'https://api.spreed.cloud'); $response = $client->post($apiServer . '/v1/account', [ 'json' => [ 'url' => $url, @@ -239,9 +239,6 @@ class HostedSignalingServerController extends OCSController { ], Http::STATUS_INTERNAL_SERVER_ERROR); } - // will contain the URL that can be used to query information on the account - $statusUrl = $response->getHeader('Location'); - $body = $response->getBody(); $data = json_decode($body, true); @@ -260,6 +257,167 @@ class HostedSignalingServerController extends OCSController { } $this->config->setAppValue('spreed', 'hosted-signaling-server-account-id', $data['account_id']); - return new DataResponse([]); + + $data = [ + 'account_id' => $this->config->getAppValue('spreed', 'hosted-signaling-server-account-id') + ]; + + // account is now properly requested + + // fetch account details + + try { + $nonce = $this->secureRandom->generate(32); + $this->config->setAppValue('spreed', 'hosted-signaling-server-nonce', $nonce); + $response = $client->get($apiServer . '/v1/account/' . $data['account_id'], [ + 'headers' => [ + 'X-Account-Service-Nonce' => $nonce, + ], + 'timeout' => 10, + ]); + } catch(ClientException $e) { + $response = $e->getResponse(); + + if ($response === null) { + $this->logger->logException($e, [ + 'app' => 'spreed', + 'message' => 'Trial requested but failed to get account information', + ]); + return new DataResponse([ + 'message' => $this->l10n->t('Trial requested but failed to get account information. Please check back later.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + + $status = $response->getStatusCode(); + + switch ($status) { + case Http::STATUS_UNAUTHORIZED: + // TODO log it + return new DataResponse([ + 'message' => $this->l10n->t('There is a problem with the authentication of this request. Maybe the account was deleted.') // TODO deleted? + ], Http::STATUS_INTERNAL_SERVER_ERROR); + case Http::STATUS_BAD_REQUEST: + $body = $response->getBody()->getContents(); + if ($body) { + $parsedBody = json_decode($body, true); + if (json_last_error() !== JSON_ERROR_NONE) { + $this->logger->error('Getting the account information failed: cannot parse JSON response - JSON error: '. json_last_error() . ' ' . json_last_error_msg() . ' HTTP status: ' . $status . ' Response body: ' . $body, ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('Something unexpected happened.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + if ($parsedBody['reason']) { + switch($parsedBody['reason']) { + case 'missing_account_id': + $log = 'The account ID is missing.'; + break; + default: + $body = $response->getBody()->getContents(); + $this->logger->error('Getting the account information failed: something else happened - HTTP status: ' . $status . ' Response body: ' . $body, ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('Failed to fetch account information because the trial server behaved wrongly. Please check back later.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + $this->logger->error('Getting the account information failed: bad request - reason: ' . $parsedBody['reason'] . ' ' . $log); + return new DataResponse([ + 'message' => $this->l10n->t('There is a problem with fetching the account information. Please check your logs for further information.') + ], Http::STATUS_BAD_REQUEST); + } + } + + return new DataResponse([ + 'message' => $this->l10n->t('Something unexpected happened.') + ], Http::STATUS_BAD_REQUEST); + case Http::STATUS_TOO_MANY_REQUESTS: + $body = $response->getBody()->getContents(); + $this->logger->error('Getting the account information failed: too many requests - HTTP status: ' . $status . ' Response body: ' . $body, ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('Too many requests are send from your servers address. Please try again later.') + ], Http::STATUS_TOO_MANY_REQUESTS); + case Http::STATUS_NOT_FOUND: + $body = $response->getBody()->getContents(); + $this->logger->error('Getting the account information failed: account not found - HTTP status: ' . $status . ' Response body: ' . $body, ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('There is no such account registered.') + ], Http::STATUS_CONFLICT); + case Http::STATUS_INTERNAL_SERVER_ERROR: + $body = $response->getBody()->getContents(); + $this->logger->error('Getting the account information failed: internal server error - HTTP status: ' . $status . ' Response body: ' . $body, ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('Something unexpected happened. Please try again later.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + default: + $body = $response->getBody()->getContents(); + $this->logger->error('Getting the account information failed: something else happened - HTTP status: ' . $status . ' Response body: ' . $body, ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('Failed to fetch account information because the trial server behaved wrongly. Please check back later.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } catch (\Exception $e) { + $this->logger->logException($e, [ + 'app' => 'spreed', + 'message' => 'Failed to request hosted signaling server trial', + ]); + + return new DataResponse([ + 'message' => $this->l10n->t('Failed to fetch account information because the trial server is unreachable. Please check back later.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + + $status = $response->getStatusCode(); + + if ($status !== Http::STATUS_OK) { + $body = $response->getBody(); + $this->logger->error('Getting the account information failed: something else happened - HTTP status: ' . $status . ' Response body: ' . $body, ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('Something unexpected happened.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + + $body = $response->getBody(); + $data = json_decode($body, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + $this->logger->error('Getting the account information failed: cannot parse JSON response - JSON error: '. json_last_error() . ' ' . json_last_error_msg() . ' HTTP status: ' . $status . ' Response body: ' . $body, ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('Something unexpected happened.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + + return $this->sanitizeAndCacheAccountData($data); + } + + protected function sanitizeAndCacheAccountData(array $data): DataResponse { + if (!isset($data['status']) + || !isset($data['created']) + || ($data['status'] === 'active' && ( + !isset($data['signaling']) + || !isset($data['signaling']['url']) + || !isset($data['signaling']['secret']) + ) + ) + || !isset($data['owner']) + || !isset($data['owner']['url']) + || !isset($data['owner']['name']) + || !isset($data['owner']['email']) + || !isset($data['owner']['language']) + || !isset($data['owner']['country']) + || ($data['status'] === 'active' && ( + !isset($data['limits']) + || !isset($data['limits']['users']) + ) + ) + || (in_array($data['status'], ['error', 'blocked']) && !isset($data['reason'])) + || !in_array($data['status'], ['error', 'blocked', 'pending', 'active', 'expired']) + ) { + $this->logger->error('Getting the account information failed: response is missing mandatory field - data: ' . json_encode($data), ['app' => 'spreed']); + return new DataResponse([ + 'message' => $this->l10n->t('Something unexpected happened.') + ], Http::STATUS_INTERNAL_SERVER_ERROR); + } + + $this->config->setAppValue('spreed', 'hosted-signaling-server-account', json_encode($data)); + + return new DataResponse($data); } } diff --git a/lib/Settings/Admin/AdminSettings.php b/lib/Settings/Admin/AdminSettings.php index c2cee2e9f6..df77387d59 100644 --- a/lib/Settings/Admin/AdminSettings.php +++ b/lib/Settings/Admin/AdminSettings.php @@ -129,6 +129,9 @@ class AdminSettings implements ISettings { 'language' => $this->serverConfig->getUserValue($this->currentUser->getUID(), 'core', 'lang', 'en_US'), 'country' => $this->serverConfig->getUserValue($this->currentUser->getUID(), 'core', 'locale', 'en_US'), ]); + $this->initialStateService->provideInitialState('talk', 'hosted_signaling_server_trial_data', + json_decode($this->serverConfig->getAppValue('spreed', 'hosted-signaling-server-account', "[]"), true) ?? [] + ); } /** diff --git a/src/components/AdminSettings/HostedSignalingServer.vue b/src/components/AdminSettings/HostedSignalingServer.vue index 4992ac29af..da2abcf3d5 100644 --- a/src/components/AdminSettings/HostedSignalingServer.vue +++ b/src/components/AdminSettings/HostedSignalingServer.vue @@ -30,7 +30,7 @@ {{ t('spreed', 'Our partner Struktur AG provides a service where a hosted signaling server can be requested. For this you only need to fill out the form below and your Nextcloud will request it. Once the server is set up for you the credentials will be filled automatically.') }}

-
+

{{ t('spreed', 'URL of this Nextcloud instance') }}

{{ requestError }}

+ +

+

+
+

+ {{ t('spreed', 'You can see the current status of your hosted signaling server in the following table.') }} +

+ + + + + + + + + + + + + + + + + +
{{ t('spreed', 'Status') }}{{ translatedStatus }}
{{ t('spreed', 'Created at') }}{{ createdDate }}
{{ t('spreed', 'Expires at') }}{{ expiryDate }}
{{ t('spreed', 'Limits') }}{{ n('spreed', '%n user', '%n users', trailAccount.limits.users) }}
-

@@ -90,6 +114,7 @@ import { loadState } from '@nextcloud/initial-state' import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' +import moment from '@nextcloud/moment' export default { name: 'HostedSignalingServer', @@ -103,6 +128,8 @@ export default { hostedHPBCountry: '', requestError: '', loading: false, + + trailAccount: [], } }, @@ -119,6 +146,28 @@ export default { .replace('{linkstart}', '') .replace('{linkend}', ' ↗') }, + translatedStatus() { + switch (this.trailAccount.status) { + case 'pending': + return t('spreed', 'Pending') + case 'error': + return t('spreed', 'Error') + case 'blocked': + return t('spreed', 'Blocked') + case 'active': + return t('spreed', 'Active') + case 'expired': + return t('spreed', 'Expired') + } + + return '' + }, + expiryDate() { + return moment(this.trailAccount.expires).format('L') + }, + createdDate() { + return moment(this.trailAccount.created).format('L') + }, }, beforeMount() { @@ -128,6 +177,8 @@ export default { this.hostedHPBEmail = state.email this.hostedHPBLanguage = state.language this.hostedHPBCountry = state.country + + this.trailAccount = loadState('talk', 'hosted_signaling_server_trial_data') }, methods: { @@ -143,10 +194,7 @@ export default { country: this.hostedHPBCountry, }) - // TODO all fine - - - + this.trailAccount = res.data.ocs.data } catch (err) { this.requestError = err?.response?.data?.ocs?.data?.message || t('spreed', 'The trial could not be requested. Please try again later.') } finally { @@ -162,4 +210,17 @@ export default { margin-top: 10px; } +td { + padding: 5px; + border-bottom: 1px solid var(--color-border); +} + +tr:last-child td { + border-bottom: none; +} + +tr :first-child { + opacity: .5; +} +