From 3fb41896e13a287cfdb5d9e6524608a0a7add9ac Mon Sep 17 00:00:00 2001 From: Rello Date: Fri, 8 May 2026 10:43:02 +0200 Subject: [PATCH 1/8] Add files via upload Signed-off-by: Rello --- appinfo/routes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/appinfo/routes.php b/appinfo/routes.php index 5a65fc8..14d8c94 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -33,6 +33,7 @@ 'routes' => [ ['name' => 'mail#confirm_mail_address', 'url' => '/login/confirm/{email}/{token}', 'verb' => 'GET'], ['name' => 'password#set_password', 'url' => '/password/set/{email}/{token}', 'verb' => 'GET'], + ['name' => 'password#set_password_flow', 'url' => '/password/set/{email}/{token}/flow/{flow}', 'verb' => 'GET'], ['name' => 'password#set_password_ocs', 'url' => '/password/set/{email}/{token}/{ocs}', 'verb' => 'GET'], ['name' => 'password#submit_password', 'url' => '/password/submit/{token}', 'verb' => 'POST'] ] From 34c00de1fedccbbbcf6be0382196dc39fed5695b Mon Sep 17 00:00:00 2001 From: Rello Date: Fri, 8 May 2026 10:43:22 +0200 Subject: [PATCH 2/8] Add files via upload Signed-off-by: Rello --- lib/Controller/AccountController.php | 12 ++- lib/Controller/PasswordController.php | 111 +++++++++++++++++++++----- 2 files changed, 102 insertions(+), 21 deletions(-) diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index affc44a..87fecb8 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -139,12 +139,13 @@ public function __construct(string $appName, * * @param string $token The security token required * @param string $email The email to create an account for + * @param string $flow The registration flow variant * * @return DataResponse the app password for the user * * @throws OCSForbiddenException */ - public function requestAccount(string $token = '', string $email = ''): DataResponse { + public function requestAccount(string $token = '', string $email = '', string $flow = ''): DataResponse { // checking if valid token $provider_token = $this->config->getAppValue($this->appName, 'provider_token'); if ($provider_token === '' || $provider_token !== $token) { @@ -213,7 +214,7 @@ public function requestAccount(string $token = '', string $email = ''): DataResp // generate set password token try { - $setPasswordUrl = $this->processSetPasswordToken($email); + $setPasswordUrl = $this->processSetPasswordToken($email, $flow); } catch (\Exception $e) { $this->logger->error("An error occured during the password token generation for $email", ['exception' => $e]); @@ -229,14 +230,19 @@ public function requestAccount(string $token = '', string $email = ''): DataResp * Generate token and process it * * @param string $email mail address + * @param string $flow The registration flow variant * @return string reset password url */ - private function processSetPasswordToken(string $email): string { + private function processSetPasswordToken(string $email, string $flow = ''): string { $token = $this->generateRandomToken(); $encryptedValue = $this->crypto->encrypt($token, $email . $this->config->getSystemValue('secret')); $this->config->setUserValue($email, $this->appName, 'set_password', $encryptedValue); $this->config->setUserValue($email, $this->appName, 'remind_password', strval(time())); + if ($flow === 'V3') { + return $this->urlGenerator->linkToRouteAbsolute($this->appName . '.password.set_password_flow', ['email' => $email, 'token' => $token, 'flow' => $flow]); + } + return $this->urlGenerator->linkToRouteAbsolute($this->appName . '.password.set_password', ['email' => $email, 'token' => $token]); } diff --git a/lib/Controller/PasswordController.php b/lib/Controller/PasswordController.php index 4217940..1391e02 100644 --- a/lib/Controller/PasswordController.php +++ b/lib/Controller/PasswordController.php @@ -123,7 +123,7 @@ public function __construct(string $appName, * * @param string $token The security token * @param string $email The user email - * @param string $ocsis this a ocs api request + * @param string $ocs is this a ocs api request * @return TemplateResponse */ public function setPassword(string $token, string $email, $ocs = false) { @@ -135,7 +135,26 @@ public function setPassword(string $token, string $email, $ocs = false) { ], 'guest'); } - return $this->generateTemplate($token, $email, '', $ocs !== false); + return $this->generateTemplate($token, $email, '', $ocs !== false ? (string)$ocs : ''); + } + + /** + * @NoCSRFRequired + * + * @PublicPage + * + * shortcut for secondary route with flow parameter + */ + public function setPasswordFlow(string $token, string $email, string $flow = ''): TemplateResponse { + try { + $this->checkPasswordToken($token, $email); + } catch (\Exception $e) { + return new TemplateResponse('core', 'error', [ + 'errors' => [['error' => $e->getMessage()]] + ], 'guest'); + } + + return $this->generateTemplate($token, $email, '', '', $flow); } /** @@ -160,9 +179,10 @@ public function setPasswordOcs(string $token, string $email, $ocs = false): Temp * @param string $email The user email * @param string $password The user password * @param string $ocsapirequest OCS-APIREQUEST header check + * @param string $flow registration flow variant * @return TemplateResponse|RedirectResponse */ - public function submitPassword(string $token, string $email, string $password, string $ocsapirequest = '') { + public function submitPassword(string $token, string $email, string $password, string $ocsapirequest = '', string $flow = '') { // process token validation try { $this->checkPasswordToken($token, $email); @@ -182,14 +202,19 @@ public function submitPassword(string $token, string $email, string $password, s try { $user = $this->userManager->get($email); if (!$user->setPassword($password)) { - return $this->generateTemplate($token, $email, $this->l10n->t('Unable to set the password. Contact your provider.'), $ocsapirequest === '1'); + return $this->generateTemplate($token, $email, $this->l10n->t('Unable to set the password. Contact your provider.'), $ocsapirequest, $flow); } $this->config->deleteUserValue($email, $this->appName, 'set_password'); $this->config->deleteUserValue($email, $this->appName, 'remind_password'); // logout and ignore failure @\OC::$server->getUserSession()->unsetMagicInCookie(); } catch (\Exception $e) { - return $this->generateTemplate($token, $email, $e->getMessage(), $ocsapirequest === '1'); + return $this->generateTemplate($token, $email, $e->getMessage(), $ocsapirequest, $flow); + } + + if ($flow === 'V3') { + $this->loginUser($email, $password); + return $this->generateFlowLoginResponse(); } // redirect to ClientFlowLogin if the request comes from android/ios/desktop @@ -200,13 +225,7 @@ public function submitPassword(string $token, string $email, string $password, s } // login - try { - $loginResult = $this->userManager->checkPasswordNoLogging($email, $password); - $this->userSession->completeLogin($loginResult, ['loginName' => $email, 'password' => $password]); - $this->userSession->createSessionToken($this->request, $loginResult->getUID(), $email, $password); - } catch (\Exception $e) { - $this->logger->debug('Unable to perform auto login for ' . $email, ['app' => $this->appName]); - } + $this->loginUser($email, $password); return new RedirectResponse($this->urlGenerator->getAbsoluteURL('/')); } @@ -219,20 +238,22 @@ public function submitPassword(string $token, string $email, string $password, s * @param string $error optional * @return TemplateResponse */ - protected function generateTemplate(string $token, string $email, string $error = '', bool $ocs = false) { + protected function generateTemplate(string $token, string $email, string $error = '', string $ocs = '', string $flow = '') { + $ocsapirequest = $flow === '' && $this->request->getHeader('OCS-APIREQUEST') ? '1' : $ocs; $response = new TemplateResponse( $this->appName, 'password-public', [ 'link' => $this->urlGenerator->linkToRoute($this->appName . '.password.submit_password', ['token' => $token]), 'email' => $email, - 'ocsapirequest' => $this->request->getHeader('OCS-APIREQUEST') || $ocs, + 'ocsapirequest' => $ocsapirequest, + 'flow' => $flow, 'error' => $error ], 'guest' ); - if ($ocs) { + if ($ocsapirequest !== '') { // We need to set the CSP header to allow the redirect to the Nextcloud client // some browsers (e.g. Safari) seems to block the redirect if the CSP header is not set. $csp = new ContentSecurityPolicy(); @@ -243,6 +264,27 @@ protected function generateTemplate(string $token, string $email, string $error return $response; } + /** + * @return TemplateResponse + */ + protected function generateFlowLoginResponse() { + $response = new TemplateResponse( + $this->appName, + 'flow-login', + [ + 'ncLoginUrl' => $this->generateServerLoginUrl(), + 'redirectUrl' => $this->urlGenerator->getAbsoluteURL('/'), + ], + 'guest' + ); + + $csp = new ContentSecurityPolicy(); + $csp->addAllowedFormActionDomain('nc://*'); + $response->setContentSecurityPolicy($csp); + + return $response; + } + /** * Check token authenticity * @@ -280,6 +322,22 @@ private function getClientName() { return $userAgent !== '' ? $userAgent : 'unknown'; } + /** + * @param string $email the user email/userId + * @param string $password the user password + * + * @return void + */ + private function loginUser(string $email, string $password): void { + try { + $loginResult = $this->userManager->checkPasswordNoLogging($email, $password); + $this->userSession->completeLogin($loginResult, ['loginName' => $email, 'password' => $password]); + $this->userSession->createSessionToken($this->request, $loginResult->getUID(), $email, $password); + } catch (\Exception $e) { + $this->logger->debug('Unable to perform auto login for ' . $email, ['app' => $this->appName]); + } + } + /** * generate application password and return nc protocol formatted url * @@ -293,6 +351,25 @@ protected function generateAppPassword(string $email, string $clientName) { $token = $this->secureRandom->generate(72, ISecureRandom::CHAR_HUMAN_READABLE); $this->tokenProvider->generateToken($token, $email, $email, null, $clientName); + $serverPath = $this->getServerPath(); + $redirectUri = 'nc://login/server:' . $serverPath . '&user:' . urlencode($email) . '&password:' . urlencode($token); + + return $redirectUri; + } + + /** + * generate server-only nc protocol formatted url + * + * @return string + */ + protected function generateServerLoginUrl() { + return 'nc://login/server:' . $this->getServerPath(); + } + + /** + * @return string + */ + private function getServerPath() { $serverPostfix = ''; if (strpos($this->request->getRequestUri(), '/index.php') !== false) { @@ -312,8 +389,6 @@ protected function generateAppPassword(string $email, string $clientName) { } $serverPath = $protocol . '://' . $this->request->getServerHost() . $serverPostfix; - $redirectUri = 'nc://login/server:' . $serverPath . '&user:' . urlencode($email) . '&password:' . urlencode($token); - - return $redirectUri; + return $serverPath; } } From a3b5ba0b1deefb7b1651fc6e58c11475557ba958 Mon Sep 17 00:00:00 2001 From: Rello Date: Fri, 8 May 2026 10:43:57 +0200 Subject: [PATCH 3/8] Add files via upload Signed-off-by: Rello --- templates/flow-login.php | 18 ++++++++++++++++++ templates/password-public.php | 1 + 2 files changed, 19 insertions(+) create mode 100644 templates/flow-login.php diff --git a/templates/flow-login.php b/templates/flow-login.php new file mode 100644 index 0000000..fe2a43f --- /dev/null +++ b/templates/flow-login.php @@ -0,0 +1,18 @@ + + diff --git a/templates/password-public.php b/templates/password-public.php index 4f429fa..9a67c90 100644 --- a/templates/password-public.php +++ b/templates/password-public.php @@ -49,6 +49,7 @@
+
From 1e23db53d52df01b8a740fb3b749a662b5368d40 Mon Sep 17 00:00:00 2001 From: Rello Date: Fri, 8 May 2026 10:44:12 +0200 Subject: [PATCH 4/8] Add files via upload Signed-off-by: Rello --- js/flow-login.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 js/flow-login.js diff --git a/js/flow-login.js b/js/flow-login.js new file mode 100644 index 0000000..109ec29 --- /dev/null +++ b/js/flow-login.js @@ -0,0 +1,19 @@ +(function() { + var flowLogin = document.getElementById('flow-login') + if (!flowLogin) { + return + } + + var ncLoginUrl = flowLogin.getAttribute('data-nc-login-url') + var redirectUrl = flowLogin.getAttribute('data-redirect-url') + + if (ncLoginUrl) { + window.location.assign(ncLoginUrl) + } + + if (redirectUrl) { + window.setTimeout(function() { + window.location.assign(redirectUrl) + }, 1000) + } +})() From 2ea0be94e0247da10579a209e1c63e5afd436de4 Mon Sep 17 00:00:00 2001 From: Rello Date: Mon, 1 Jun 2026 09:56:25 +0200 Subject: [PATCH 5/8] fix: revert to old ocs variable handling Signed-off-by: Rello --- lib/Controller/PasswordController.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Controller/PasswordController.php b/lib/Controller/PasswordController.php index 1391e02..6d2c019 100644 --- a/lib/Controller/PasswordController.php +++ b/lib/Controller/PasswordController.php @@ -135,7 +135,7 @@ public function setPassword(string $token, string $email, $ocs = false) { ], 'guest'); } - return $this->generateTemplate($token, $email, '', $ocs !== false ? (string)$ocs : ''); + return $this->generateTemplate($token, $email, '', $ocs !== false); } /** @@ -154,7 +154,7 @@ public function setPasswordFlow(string $token, string $email, string $flow = '') ], 'guest'); } - return $this->generateTemplate($token, $email, '', '', $flow); + return $this->generateTemplate($token, $email, '', false, $flow); } /** @@ -202,14 +202,14 @@ public function submitPassword(string $token, string $email, string $password, s try { $user = $this->userManager->get($email); if (!$user->setPassword($password)) { - return $this->generateTemplate($token, $email, $this->l10n->t('Unable to set the password. Contact your provider.'), $ocsapirequest, $flow); + return $this->generateTemplate($token, $email, $this->l10n->t('Unable to set the password. Contact your provider.'), $ocsapirequest === '1', $flow); } $this->config->deleteUserValue($email, $this->appName, 'set_password'); $this->config->deleteUserValue($email, $this->appName, 'remind_password'); // logout and ignore failure @\OC::$server->getUserSession()->unsetMagicInCookie(); } catch (\Exception $e) { - return $this->generateTemplate($token, $email, $e->getMessage(), $ocsapirequest, $flow); + return $this->generateTemplate($token, $email, $e->getMessage(), $ocsapirequest === '1', $flow); } if ($flow === 'V3') { @@ -238,8 +238,8 @@ public function submitPassword(string $token, string $email, string $password, s * @param string $error optional * @return TemplateResponse */ - protected function generateTemplate(string $token, string $email, string $error = '', string $ocs = '', string $flow = '') { - $ocsapirequest = $flow === '' && $this->request->getHeader('OCS-APIREQUEST') ? '1' : $ocs; + protected function generateTemplate(string $token, string $email, string $error = '', bool $ocs = false, string $flow = '') { + $ocsapirequest = $flow === '' && ($this->request->getHeader('OCS-APIREQUEST') || $ocs) ? '1' : ''; $response = new TemplateResponse( $this->appName, 'password-public', From ffc53b50d1ef8a9c2c74ad45bf3df789b42a8163 Mon Sep 17 00:00:00 2001 From: Rello Date: Mon, 1 Jun 2026 10:01:16 +0200 Subject: [PATCH 6/8] fix: escaping to avoid injections Signed-off-by: Rello --- templates/password-public.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/password-public.php b/templates/password-public.php index 9a67c90..554fbf8 100644 --- a/templates/password-public.php +++ b/templates/password-public.php @@ -49,7 +49,7 @@
- +
From e4505477b62e0e8c0b88d73baa65e9bca2768f7e Mon Sep 17 00:00:00 2001 From: Rello Date: Mon, 1 Jun 2026 10:18:22 +0200 Subject: [PATCH 7/8] fix: return correct url to V3 Signed-off-by: Rello --- lib/Controller/PasswordController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller/PasswordController.php b/lib/Controller/PasswordController.php index 6d2c019..f6ece0b 100644 --- a/lib/Controller/PasswordController.php +++ b/lib/Controller/PasswordController.php @@ -363,7 +363,7 @@ protected function generateAppPassword(string $email, string $clientName) { * @return string */ protected function generateServerLoginUrl() { - return 'nc://login/server:' . $this->getServerPath(); + return 'nc://login/server:' . rtrim($this->urlGenerator->getBaseUrl(), '/'); } /** From 7ce63cae3267bc51327b96131bfee6de607ced3f Mon Sep 17 00:00:00 2001 From: Rello Date: Mon, 1 Jun 2026 10:47:14 +0200 Subject: [PATCH 8/8] Fix: added test instructions into Readme Signed-off-by: Rello --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7199703..b7a01f3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ This application allows external request of new accounts. ![screen](https://user-images.githubusercontent.com/14975046/45147329-f829de80-b1c4-11e8-8024-8e53dec98f6c.png) - +# Test +## Web and Mobile Clients 1. Install and enable the application. 2. Go to the preferred providers settings and keep your token in reach. 3. Make a POST request to `/ocs/v2.php/account/request/YOURTOKEN` with the `{email: 'myawesomemail@nextcloud.com'}` data. @@ -17,5 +18,19 @@ This application allows external request of new accounts. 5. Meanwhile a mail confirmation is sent to the user. He have 6h to confirm or his account will be disabled 6. After 4, if you set up the `OCS-APIREQUEST` header, you will be redirected to a `nc://` url with valid app-password token for your application. If not, you will be logged and redirected to the home page. +## Desktop Client +1. Install and enable the application. +2. Go to the preferred providers settings and keep your token in reach. +3. Make a POST request to `/ocs/v2.php/account/request/YOURTOKEN` with the `{email: 'myawesomemail@nextcloud.com', flow: 'V3'}` data. + ``` js + $.post('/ocs/v2.php/account/request/56300a2bf7e06894a5b59c1eb47f7460', {email:'myawesomemail@nextcloud.com', flow: 'V3'}).complete((response) => { + console.log(JSON.parse(response.responseText).data.setPassword) + }) + ``` +4. The server will accept or not the request and provide a link for the user login and password definition https://cloud.yourdomain.com/apps/preferred_providers/password/set/yourawesomemail@nextcloud.com/aipTgstNeenUXe20BJTH8/flow/V3 +5. Meanwhile a mail confirmation is sent to the user. He have 6h to confirm or his account will be disabled +6. You set the passord for the user and will be locked in automatically +7. A `nc://` url with the server url will be triggered and the Desktop Client will open with the "Grant Access" Page + ## Website part The repo for the register modue on the website is https://github.com/nextcloud/nextcloud-register/