Signed-off-by: Carl Schwan <carl@carlschwan.eu>
This commit is contained in:
Carl Schwan 2022-07-21 12:28:41 +02:00
Родитель 8a4109ff1c
Коммит 91329ac3d5
18 изменённых файлов: 664 добавлений и 365 удалений

Просмотреть файл

@ -38,7 +38,7 @@ This app allows users to register a new account.
<screenshot>https://raw.githubusercontent.com/nextcloud/registration/master/docs/demo.gif</screenshot>
<screenshot>https://raw.githubusercontent.com/nextcloud/registration/master/docs/admin-settings.png</screenshot>
<dependencies>
<nextcloud min-version="22" max-version="25" />
<nextcloud min-version="25" max-version="25" />
</dependencies>
<background-jobs>
<job>OCA\Registration\BackgroundJob\ExpireRegistrations</job>

Просмотреть файл

@ -1,20 +0,0 @@
#alternative-logins.alternative-logins .button.register-button {
color: #ffffff !important;
background-color: #0082c9;
}
#alternative-logins.alternative-logins .button.register-button:focus {
border: 2px solid #000;
background-image: linear-gradient(40deg, #0082c9 0%, #30b6ff 100%);
background-position: initial;
}
#alternative-logins .register-button:only-child {
width: 260px;
margin: 0 auto;
box-sizing: border-box;
}
#alternative-logins .register-button.hidden {
display: none;
}

Просмотреть файл

@ -1,79 +0,0 @@
#body-login #email,
#body-login #token,
#body-login #loginname,
#body-login #fullname,
#body-login #phone,
#body-login #password {
width: calc(100% - 56px);
padding-left: 36px;
}
#body-login #password {
box-shadow: 0 1px 0 rgba(0,0,0,.1) inset !important;
}
.nc-25 #body-login #password {
box-shadow: none !important;
}
#email-icon,
#token-icon,
#loginname-icon,
#fullname-icon,
#phone-icon,
#password-icon {
position: absolute;
left: 16px;
top: 22px;
filter: alpha(opacity=30);
opacity: .3;
}
#email-icon {
top: 27px;
}
.nc-25 #email-icon {
top: 22px;
}
input[type='submit'] {
width: calc(100% - 10px) !important;
white-space: normal;
}
.msg {
color: white;
margin: 5px;
}
.msg a {
color: white;
text-decoration: underline;
}
.nc-25 .msg {
color: var(--color-main-text);
}
.nc-25 .msg a {
color: var(--color-main-text);
}
.error {
margin-bottom: 15px;
}
.groupofone {
position: relative;
}
.nc-25 .groupbutton {
position: relative;
}
.nc-25 .toggle-password {
position: absolute;
right: 0;
top: 5px;
}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,63 @@
/*!
* Determine if an object is a Buffer
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
* @license MIT
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*!
* Vue.js v2.7.4
* (c) 2014-2022 Evan You
* Released under the MIT License.
*/
/*!
* escape-html
* Copyright(c) 2012-2013 TJ Holowaychuk
* Copyright(c) 2015 Andreas Lubbe
* Copyright(c) 2015 Tiancheng "Timothy" Gu
* MIT Licensed
*/
/*! For license information please see Multiselect.js.LICENSE.txt */
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**!
* @fileOverview Kickass library to create and place poppers near their reference elements.
* @version 1.16.1
* @license
* Copyright (c) 2016 Federico Zivolo and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -11,6 +11,7 @@ declare(strict_types=1);
* @author Pellaeon Lin <pellaeon@hs.ntnu.edu.tw>
* @author Julius Härtl <jus@bitgrid.net>
* @author 2020 Joas Schilling <coding@schilljs.com>
* @author 2022 Carl Schwan <carl@carlschwan.eu>
* @copyright Pellaeon Lin 2014
*/
@ -36,6 +37,7 @@ use OCP\AppFramework\Http\RedirectToDefaultAppResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use OCP\IRequest;
@ -43,21 +45,14 @@ use OCP\IURLGenerator;
use OCP\IConfig;
class RegisterController extends Controller {
/** @var IL10N */
private $l10n;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IConfig */
private $config;
/** @var RegistrationService */
private $registrationService;
/** @var MailService */
private $mailService;
/** @var LoginFlowService */
private $loginFlowService;
/** @var IEventDispatcher */
private $eventDispatcher;
private IL10N $l10n;
private IURLGenerator $urlGenerator;
private IConfig $config;
private RegistrationService $registrationService;
private MailService $mailService;
private LoginFlowService $loginFlowService;
private IEventDispatcher $eventDispatcher;
private IInitialState $initialState;
public function __construct(
string $appName,
@ -68,7 +63,8 @@ class RegisterController extends Controller {
RegistrationService $registrationService,
LoginFlowService $loginFlowService,
MailService $mailService,
IEventDispatcher $eventDispatcher
IEventDispatcher $eventDispatcher,
IInitialState $initialState
) {
parent::__construct($appName, $request);
$this->l10n = $l10n;
@ -78,15 +74,12 @@ class RegisterController extends Controller {
$this->loginFlowService = $loginFlowService;
$this->mailService = $mailService;
$this->eventDispatcher = $eventDispatcher;
$this->initialState = $initialState;
}
/**
* @NoCSRFRequired
* @PublicPage
*
* @param string $email
* @param string $message
* @return TemplateResponse
*/
public function showEmailForm(string $email = '', string $message = ''): TemplateResponse {
$emailHint = '';
@ -108,22 +101,18 @@ class RegisterController extends Controller {
$this->eventDispatcher->dispatchTyped(new ShowFormEvent(ShowFormEvent::STEP_EMAIL));
$params = [
'email' => $email,
'message' => $message ?: $emailHint,
'email_is_optional' => $this->config->getAppValue($this->appName, 'email_is_optional', 'no'),
'disable_email_verification' => $this->config->getAppValue($this->appName, 'disable_email_verification', 'no'),
'is_login_flow' => $this->loginFlowService->isUsingLoginFlow(),
];
return new TemplateResponse('registration', 'form/email', $params, 'guest');
$this->initialState->provideInitialState('email', $email);
$this->initialState->provideInitialState('message', $message ?: $emailHint);
$this->initialState->provideInitialState('emailIsOptional', $this->config->getAppValue($this->appName, 'email_is_optional', 'no') === 'yes');
$this->initialState->provideInitialState('disableEmailVerification', $this->config->getAppValue($this->appName, 'disable_email_verification', 'no') === 'yes');
$this->initialState->provideInitialState('isLoginFlow', $this->loginFlowService->isUsingLoginFlow());
$this->initialState->provideInitialState('loginFormLink', $this->urlGenerator->linkToRoute('core.login.showLoginForm'));
return new TemplateResponse('registration', 'form/email', [], 'guest');
}
/**
* @PublicPage
* @AnonRateThrottle(limit=5, period=300)
*
* @param string $email
* @return TemplateResponse
*/
public function submitEmailForm(string $email): Response {
$validateFormEvent = new ValidateFormEvent(ValidateFormEvent::STEP_EMAIL);
@ -184,10 +173,6 @@ class RegisterController extends Controller {
/**
* @NoCSRFRequired
* @PublicPage
*
* @param string $secret
* @param string $message
* @return TemplateResponse
*/
public function showVerificationForm(string $secret, string $message = ''): TemplateResponse {
try {
@ -197,10 +182,10 @@ class RegisterController extends Controller {
}
$this->eventDispatcher->dispatchTyped(new ShowFormEvent(ShowFormEvent::STEP_VERIFICATION, $secret));
$this->initialState->provideInitialState('message', $message);
$this->initialState->provideInitialState('loginFormLink', $this->urlGenerator->linkToRoute('core.login.showLoginForm'));
return new TemplateResponse('registration', 'form/verification', [
'message' => $message,
], 'guest');
return new TemplateResponse('registration', 'form/verification', [], 'guest');
}
/**
@ -248,14 +233,6 @@ class RegisterController extends Controller {
/**
* @NoCSRFRequired
* @PublicPage
*
* @param string $secret
* @param string $token
* @param string $loginname
* @param string $fullname
* @param string $phone
* @param string $message
* @return TemplateResponse
*/
public function showUserForm(string $secret, string $token, string $loginname = '', string $fullname = '', string $phone = '', string $password = '', string $message = ''): TemplateResponse {
try {
@ -268,21 +245,22 @@ class RegisterController extends Controller {
$this->eventDispatcher->dispatchTyped(new ShowFormEvent(ShowFormEvent::STEP_USER, $secret));
$response = new TemplateResponse('registration', 'form/user', [
'email' => $registration->getEmail(),
'email_is_login' => $this->config->getAppValue('registration', 'email_is_login', 'no') === 'yes',
'email_is_optional' => $this->config->getAppValue('registration', 'email_is_optional', 'no') === 'yes',
'loginname' => $loginname,
'fullname' => $fullname,
'show_fullname' => $this->config->getAppValue('registration', 'show_fullname', 'no') === 'yes',
'enforce_fullname' => $this->config->getAppValue('registration', 'enforce_fullname', 'no') === 'yes',
'phone' => $phone,
'show_phone' => $this->config->getAppValue('registration', 'show_phone', 'no') === 'yes',
'enforce_phone' => $this->config->getAppValue('registration', 'enforce_phone', 'no') === 'yes',
'message' => $message,
'password' => $password,
'additional_hint' => $additional_hint,
], 'guest');
$this->initialState->provideInitialState('email', $registration->getEmail());
$this->initialState->provideInitialState('emailIsLogin', $this->config->getAppValue('registration', 'email_is_login', 'no') === 'yes');
$this->initialState->provideInitialState('emailIsOptional', $this->config->getAppValue('registration', 'email_is_optional', 'no') === 'yes');
$this->initialState->provideInitialState('loginname', $loginname);
$this->initialState->provideInitialState('fullname', $fullname);
$this->initialState->provideInitialState('showFullname', $this->config->getAppValue('registration', 'show_fullname', 'no') === 'yes');
$this->initialState->provideInitialState('enforceFullname', $this->config->getAppValue('registration', 'enforce_fullname', 'no') === 'yes');
$this->initialState->provideInitialState('phone', $phone);
$this->initialState->provideInitialState('showPhone', $this->config->getAppValue('registration', 'show_phone', 'no') === 'yes');
$this->initialState->provideInitialState('enforcePhone', $this->config->getAppValue('registration', 'enforce_phone', 'no') === 'yes');
$this->initialState->provideInitialState('message', $message);
$this->initialState->provideInitialState('password', $password);
$this->initialState->provideInitialState('additionalHint', $additional_hint);
$this->initialState->provideInitialState('loginFormLink', $this->urlGenerator->linkToRoute('core.login.showLoginForm'));
$response = new TemplateResponse('registration', 'form/user', [], 'guest');
if ($this->loginFlowService->isUsingLoginFlow(1)) {
$csp = new ContentSecurityPolicy();

Просмотреть файл

@ -59,7 +59,5 @@ class RegistrationLoginOption implements IAlternativeLogin {
}
public function load(): void {
Util::addStyle(Application::APP_ID, 'register-button');
Util::addHeader('style', [], ':root { --color-primary-text: ' . $this->theming->getTextColorPrimary() . '; }');
}
}

1
package-lock.json сгенерированный
Просмотреть файл

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.6.0",
"@nextcloud/dialogs": "^3.1.1",
"@nextcloud/initial-state": "^1.2.0",

Просмотреть файл

@ -13,6 +13,7 @@
"stylelint:fix": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue --fix"
},
"dependencies": {
"@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.6.0",
"@nextcloud/dialogs": "^3.1.1",
"@nextcloud/initial-state": "^1.2.0",

Просмотреть файл

@ -0,0 +1,125 @@
<!--
- @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu>
-
- @author Carl Schwan <carl@carlschwan.eu>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div class="guest-box">
<form action="" method="post">
<fieldset>
<div v-if="message !== ''" class="notecard error">
{{ message }}
</div>
<p class="email">
<input id="email"
type="email"
name="email"
class="email__field"
:placeholder="emailPlaceholder"
required
autofocus>
<label v-if="emailIsOptional" for="email" class="infield">{{ t('registration', 'Email (optional)') }}</label>
<label v-else for="email" class="infield">{{ t('registration', 'Email') }}</label>
<img class="svg email__icon" :src="emailIconPath" alt="">
</p>
<div id="terms_of_service" />
<input type="hidden" name="requesttoken" :value="requesttoken">
<Button id="submit"
native-type="submit"
type="primary"
:wide="true">
{{ submitValue }}
</Button>
<a id="lost-password-back" :href="loginFormLink">
{{ t('registration', 'Back to login') }}
</a>
</fieldset>
</form>
</div>
</template>
<script>
import { getRequestToken } from '@nextcloud/auth'
import Button from '@nextcloud/vue/dist/Components/Button'
import { generateFilePath } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
export default {
name: 'RegistrationEmail',
components: {
Button,
},
data() {
return {
emailIsOptional: loadState('registration', 'emailIsOptional'),
message: loadState('registration', 'message'),
requesttoken: getRequestToken(),
disableEmailVerification: loadState('registration', 'disableEmailVerification'),
isLoginFlow: loadState('registration', 'isLoginFlow'),
loginFormLink: loadState('registration', 'loginFormLink'),
}
},
computed: {
emailPlaceholder() {
return this.emailIsOptional
? t('registration', 'Email (optional)')
: t('registration', 'Email')
},
emailIconPath() {
return generateFilePath('core', 'img', 'actions/mail.svg')
},
submitValue() {
if (this.emailIsOptional || this.disableEmailVerification) {
return t('registration', 'Continue')
} else if (this.isLoginFlow) {
return t('registration', 'Request verification code')
} else {
return t('registration', 'Request verification link')
}
},
},
}
</script>
<style lang="scss" scoped>
.email {
position: relative;
&__field {
margin-bottom: 12px;
width: calc(100% - 56px);
padding-left: 36px;
}
&__icon {
position: absolute;
left: 16px;
top: 22px;
filter: alpha(opacity=30);
opacity: .3;
}
}
</style>

242
src/components/User.vue Normal file
Просмотреть файл

@ -0,0 +1,242 @@
<!--
- @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu>
-
- @author Carl Schwan <carl@carlschwan.eu>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div class="guest-box">
<form action="" method="post">
<input type="hidden" name="requesttoken" :value="requesttoken">
<fieldset>
<div v-if="message !== ''" class="notecard error">
{{ message }}
</div>
<p v-else>
{{ t('registration', 'Welcome, you can create your account below.') }}
</p>
<div v-if="additionalHint" class="notecard success">
{{ additionalHint }}
</div>
<p v-if="!emailIsOptional || email.length > 0" class="input">
<input id="email"
type="email"
class="input__field"
name="email"
:value="email"
disabled>
<label for="email" class="infield">{{ t('registration', 'Email') }}></label>
<img id="email-icon"
class="input__icon"
:src="emailIconPath"
alt="">
</p>
<p v-if="!emailIsLogin" class="input">
<input id="loginname"
type="text"
name="loginname"
class="input__field"
:value="loginname"
:placeholder="t('registration', 'Login name')"
required>
<label for="loginname" class="infield">{{ t('registration', 'Login name') }}</label>
<img id="loginname-icon"
class="input__icon"
:src="authIconPath"
alt="">
</p>
<input v-else
type="hidden"
name="loginname"
:value="email">
<p v-if="showFullname" class="input">
<input id="fullname"
type="text"
name="fullname"
class="input__field"
:value="fullname"
:placeholder="t('registration', 'Full name')"
:required="enforceFullname">
<label for="fullname" class="infield">{{ t('registration', 'Full name') }}</label>
<img id="fullname-icon"
class="input__icon"
:src="userIconPath"
alt="">
</p>
<input v-else
type="hidden"
name="fullname"
value="">
<p v-if="showPhone" class="groupmiddle input">
<input id="phone"
type="text"
name="phone"
class="input__field"
:value="phone"
:placeholder="t('registration', 'Phone number')"
:required="enforcePhone">
<label for="phone" class="infield">{{ t('registration', 'Phone number') }}</label>
<img id="phone-icon"
class="input__icon"
:src="phoneIconPath"
alt="">
</p>
<input v-else
type="hidden"
name="phone"
value="">
<p class="groupbottom input">
<input id="password"
type="password"
class="input__field"
name="password"
:value="password"
:placeholder="t('registration', 'Password')"
required>
<label for="password" class="infield">{{ t('registration', 'Password') }}</label>
<img id="password-icon"
class="svg input__icon"
:src="passwordIconPath"
alt="">
<Button class="toggle-password"
type="tertiary-no-background"
:aria-label="isPasswordHidden ? t('registration', 'Show password') : t('registration', 'Hide password')"
@click.stop.prevent="togglePassword">
<template #icon>
<Eye v-if="isPasswordHidden" :size="20" />
<EyeOff v-else :size="20" />
</template>
</Button>
</p>
<Button id="submit"
native-type="submit"
type="primary"
:wide="true"
:disabled="submitting"
@click="submit">
{{ t('registration', 'Create account') }}
</Button>
</fieldset>
</form>
</div>
</template>
<script>
import { getRequestToken } from '@nextcloud/auth'
import Button from '@nextcloud/vue/dist/Components/Button'
import { generateFilePath } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import Eye from 'vue-material-design-icons/Eye'
import EyeOff from 'vue-material-design-icons/EyeOff'
export default {
name: 'User',
components: {
Button,
Eye,
EyeOff,
},
data() {
return {
email: loadState('registration', 'email'),
emailIsLogin: loadState('registration', 'emailIsLogin'),
emailIsOptional: loadState('registration', 'emailIsOptional'),
loginname: loadState('registration', 'loginname'),
fullname: loadState('registration', 'fullname'),
showFullname: loadState('registration', 'showFullname'),
enforceFullname: loadState('registration', 'enforceFullname'),
phone: loadState('registration', 'phone'),
showPhone: loadState('registration', 'showPhone'),
enforcePhone: loadState('registration', 'enforcePhone'),
message: loadState('registration', 'message'),
password: loadState('registration', 'password'),
additionalHint: loadState('registration', 'additionalHint'),
requesttoken: getRequestToken(),
loginFormLink: loadState('registration', 'loginFormLink'),
isPasswordHidden: true,
passwordInputType: 'password',
submitting: false,
}
},
computed: {
emailIconPath() {
return generateFilePath('core', 'img', 'actions/mail.svg')
},
phoneIconPath() {
return generateFilePath('core', 'img', 'clients/phone.svg')
},
userIconPath() {
return generateFilePath('core', 'img', 'actions/user.svg')
},
authIconPath() {
return generateFilePath('core', 'img', 'categories/auth.svg')
},
passwordIconPath() {
return generateFilePath('core', 'img', 'actions/password.svg')
},
},
methods: {
togglePassword() {
if (this.passwordInputType === 'password') {
this.passwordInputType = 'text'
} else {
this.passwordInputType = 'password'
}
},
submit() {
this.submitting = true
},
},
}
</script>
<style lang="scss" scoped>
.input {
position: relative;
&__field {
margin-bottom: 12px;
width: calc(100% - 56px);
padding-left: 36px;
}
&__icon {
position: absolute;
left: 16px;
top: 22px;
filter: alpha(opacity=30);
opacity: .3;
}
}
.toggle-password {
position: absolute;
top: 6px;
right: 10px;
color: var(--color-text-lighter);
}
</style>

Просмотреть файл

@ -0,0 +1,105 @@
<!--
- @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu>
-
- @author Carl Schwan <carl@carlschwan.eu>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div class="guest-box">
<form action="" method="post">
<fieldset>
<div v-if="message !== ''" class="notecard error">
{{ message }}
</div>
<p class="token">
<input id="token"
type="text"
name="token"
class="token__field"
:placeholder="t('registration', 'Verification code')"
required
autofocus>
<label for="token" class="infield">{{ t('registration', 'Verification code') }}</label>
<img class="svg token__icon" :src="verifyIconPath" alt="">
</p>
<input type="hidden" name="requesttoken" :value="requesttoken">
<Button id="submit"
native-type="submit"
type="primary"
:wide="true">
{{ t('registration', 'Verify') }}
</Button>
<a id="lost-password-back" :href="loginFormLink">
{{ t('registration', 'Back to login') }}
</a>
</fieldset>
</form>
</div>
</template>
<script>
import { getRequestToken } from '@nextcloud/auth'
import Button from '@nextcloud/vue/dist/Components/Button'
import { generateFilePath } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
export default {
name: 'Verification',
components: {
Button,
},
data() {
return {
message: loadState('registration', 'message'),
requesttoken: getRequestToken(),
loginFormLink: loadState('registration', 'loginFormLink'),
}
},
computed: {
verifyIconPath() {
return generateFilePath('registration', 'img', 'verify.svg')
},
},
}
</script>
<style lang="scss" scoped>
.token {
position: relative;
&__field {
margin-bottom: 12px;
width: calc(100% - 56px);
padding-left: 36px;
}
&__icon {
position: absolute;
left: 16px;
top: 22px;
filter: alpha(opacity=30);
opacity: .3;
}
}
</style>

Просмотреть файл

@ -1,19 +1,34 @@
document.addEventListener('DOMContentLoaded', function() {
// Password toggle
$('#showadminpass').click((e) => {
e.preventDefault()
const passwordTextField = $('#password')
if (passwordTextField.attr('type') === 'password') {
passwordTextField.attr('type', 'text')
} else {
passwordTextField.attr('type', 'password')
}
})
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
// Disable submit after first click
$('form').submit(() => {
// prevent duplicate form submissions
$(this).find(':submit').attr('disabled', 'disabled')
$(this).find(':submit')[0].value = t('registration', 'Loading …')
import Vue from 'vue'
import RegistrationEmail from './components/RegistrationEmail.vue'
import Verification from './components/Verification.vue'
import User from './components/User.vue'
Vue.prototype.t = t
Vue.prototype.OC = OC
let view = null
if (document.getElementById('registration_email')) {
view = new Vue({
el: '#registration_email',
render: h => h(RegistrationEmail),
})
})
}
if (document.getElementById('registration_verification')) {
view = new Vue({
el: '#registration_verification',
render: h => h(Verification),
})
}
if (document.getElementById('registration_verification')) {
view = new Vue({
el: '#registration_user',
render: h => h(User),
})
}
export default view

Просмотреть файл

@ -1,47 +1,8 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
script('registration', 'registration-form');
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
\OCP\Util::addScript('registration', 'registration-form')
?>
<div class="login-box">
<form action="" method="post">
<fieldset>
<?php if ($_['message']): ?>
<ul class="error">
<li><?php p($_['message']); ?></li>
</ul>
<?php endif; ?>
<p class="groupofone">
<input type="email" name="email" id="email" placeholder="<?php if ($_['email_is_optional'] === 'yes') {
p($l->t('Email (optional)'));
} else {
p($l->t('Email'));
}?>" value="<?php p($_['email']); ?>" <?php if ($_['email_is_optional'] !== 'yes') { ?>required <?php } ?>autofocus />
<?php if ($_['email_is_optional'] === 'yes') { ?>
<label for="email" class="infield"><?php p($l->t('Email (optional)')); ?></label>
<?php } else { ?>
<label for="email" class="infield"><?php p($l->t('Email')); ?></label>
<?php } ?>
<img id="email-icon" class="svg" src="<?php print_unescaped(image_path('', 'actions/mail.svg')); ?>" alt=""/>
</p>
<div id="terms_of_service"></div>
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>" />
<input type="submit" id="submit" value="<?php
if ($_['email_is_optional'] === 'yes' || $_['disable_email_verification'] === 'yes') {
p($l->t('Continue'));
} elseif ($_['is_login_flow']) {
p($l->t('Request verification code'));
} else {
p($l->t('Request verification link'));
} ?>" />
<a id="lost-password-back" href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('core.login.showLoginForm')) ?>">
<?php p($l->t('Back to login')); ?>
</a>
</fieldset>
</form>
</div>
<div id="registration_email"></div>

Просмотреть файл

@ -1,88 +1,7 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
script('registration', 'registration-form');
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
\OCP\Util::addScript('registration', 'registration-form')
?>
<div class="login-box">
<form action="" method="post">
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
<fieldset>
<?php if (!empty($_['message'])) {?>
<ul class="error">
<li><?php p($_['message']); ?></li>
</ul>
<?php } else { ?>
<ul class="msg">
<li><?php p($l->t('Welcome, you can create your account below.'));?></li>
</ul>
<?php } ?>
<?php if (!empty($_['additional_hint'])): ?>
<ul class="msg">
<li><?php p($_['additional_hint']); ?></li>
</ul>
<?php endif; ?>
<?php if (!$_['email_is_optional'] || !empty($_['email'])) { ?>
<p class="grouptop">
<input type="email" name="email" id="email" value="<?php p($_['email']); ?>" disabled />
<label for="email" class="infield"><?php p($_['email']); ?></label>
<img id="email-icon" class="svg" src="<?php print_unescaped(image_path('', 'actions/mail.svg')); ?>" alt=""/>
</p>
<?php } ?>
<?php if (!$_['email_is_login']) { ?>
<p class="groupmiddle">
<input type="text" name="loginname" id="loginname" value="<?php if (!empty($_['loginname'])) {
p($_['loginname']);
} ?>" placeholder="<?php p($l->t('Login name')); ?>" required />
<label for="loginname" class="infield"><?php p($l->t('Login name')); ?></label>
<img id="loginname-icon" class="svg" src="<?php print_unescaped(image_path('', 'categories/auth.svg')); ?>" alt=""/>
</p>
<?php } else { ?>
<input type="hidden" name="loginname" value="<?php p($_['email']); ?>" />
<?php } ?>
<?php if ($_['show_fullname']) { ?>
<p class="groupmiddle">
<input type="text" name="fullname" id="fullname" value="<?php if (!empty($_['fullname'])) {
p($_['fullname']);
} ?>" placeholder="<?php p($l->t('Full name')); ?>" <?php if ($_['enforce_fullname']) {
p('required');
} ?> />
<label for="fullname" class="infield"><?php p($l->t('Full name')); ?></label>
<img id="fullname-icon" class="svg" src="<?php print_unescaped(image_path('', 'actions/user.svg')); ?>" alt=""/>
</p>
<?php } else { ?>
<input type="hidden" name="fullname" value="" />
<?php } ?>
<?php if ($_['show_phone']) { ?>
<p class="groupmiddle">
<input type="text" name="phone" id="phone" value="<?php if (!empty($_['phone'])) {
p($_['phone']);
} ?>" placeholder="<?php p($l->t('Phone number')); ?>" <?php if ($_['enforce_phone']) {
p('required');
} ?> />
<label for="phone" class="infield"><?php p($l->t('Phone number')); ?></label>
<img id="phone-icon" class="svg" src="<?php print_unescaped(image_path('', 'clients/phone.svg')); ?>" alt=""/>
</p>
<?php } else { ?>
<input type="hidden" name="phone" value="" />
<?php } ?>
<p class="groupbottom">
<input type="password" name="password" id="password" value="<?php if (!empty($_['password'])) {
p($_['password']);
} ?>" placeholder="<?php p($l->t('Password')); ?>" required />
<label for="password" class="infield"><?php p($l->t('Password')); ?></label>
<img id="password-icon" class="svg" src="<?php print_unescaped(image_path('', 'actions/password.svg')); ?>" alt=""/>
<button id="showadminpass" class="toggle-password">
<img src="<?php print_unescaped(image_path('core', 'actions/toggle.svg')); ?>">
</button>
</p>
<input type="submit" class="primary" id="submit" value="<?php p($l->t('Create account')); ?>" />
</fieldset>
</form>
</div>
<div id="registration_user"></div>

Просмотреть файл

@ -1,29 +1,7 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
script('registration', 'registration-form');
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
\OCP\Util::addScript('registration', 'registration-form')
?>
<div class="login-box">
<form action="" method="post">
<fieldset>
<?php if ($_['message']): ?>
<ul class="error">
<li><?php p($_['message']); ?></li>
</ul>
<?php endif; ?>
<p class="groupofone">
<input type="text" name="token" id="token" placeholder="<?php p($l->t('Verification code')); ?>" value="" required autofocus />
<label for="token" class="infield"><?php p($l->t('Verification code')); ?></label>
<img id="token-icon" class="svg" src="<?php print_unescaped(image_path('registration', 'verify.svg')); ?>" alt=""/>
</p>
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>" />
<input type="submit" id="submit" value="<?php p($l->t('Verify')); ?>" />
<a id="lost-password-back" href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('core.login.showLoginForm')) ?>">
<?php p($l->t('Back to login')); ?>
</a>
</fieldset>
</form>
</div>
<div id="registration_verification"></div>

Просмотреть файл

@ -35,6 +35,7 @@ use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\RedirectToDefaultAppResponse;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IL10N;
@ -62,6 +63,8 @@ class RegisterControllerTest extends TestCase {
private $mailService;
/** @var IEventDispatcher|MockObject */
private $eventDispatcher;
/** @var IInitialState|MockObject */
private $initialState;
public function setUp(): void {
parent::setUp();
@ -73,10 +76,11 @@ class RegisterControllerTest extends TestCase {
$this->loginFlowService = $this->createMock(LoginFlowService::class);
$this->mailService = $this->createMock(MailService::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->initialState = $this->createMock(IInitialState::class);
$this->l10n->expects($this->any())
->method('t')
->willReturnCallback(function ($text, $parameters = []) {
->willReturnCallback(function ($text, $parameters = []): string {
return vsprintf($text, $parameters);
});
}
@ -96,7 +100,8 @@ class RegisterControllerTest extends TestCase {
$this->registrationService,
$this->loginFlowService,
$this->mailService,
$this->eventDispatcher
$this->eventDispatcher,
$this->initialState
);
}
@ -112,6 +117,7 @@ class RegisterControllerTest extends TestCase {
$this->loginFlowService,
$this->mailService,
$this->eventDispatcher,
$this->initialState,
])
->getMock();
}
@ -140,13 +146,15 @@ class RegisterControllerTest extends TestCase {
self::assertSame(TemplateResponse::RENDER_AS_GUEST, $response->getRenderAs());
self::assertSame('form/email', $response->getTemplateName());
self::assertSame([
'email' => $email,
'message' => $message,
'email_is_optional' => $email_is_optional,
'disable_email_verification' => $disable_email_verification,
'is_login_flow' => false,
], $response->getParams());
self::assertSame([], $response->getParams());
$this->initialState->method('provideInitialState')
->withConsecutive(
['email', $email],
['message', $message],
['email_is_optional', $email_is_optional],
['disable_email_verification', $disable_email_verification],
['is_login_flow', false],
);
}
public function testSubmitEmailForm(): void {
@ -320,10 +328,10 @@ class RegisterControllerTest extends TestCase {
self::assertSame(TemplateResponse::RENDER_AS_GUEST, $response->getRenderAs());
self::assertSame('form/verification', $response->getTemplateName());
$this->initialState->method('provideInitialState')
->with('message', $message);
self::assertSame([
'message' => $message,
], $response->getParams());
self::assertSame([], $response->getParams());
}
public function testShowVerificationFormInvalidSecret(): void {
@ -473,21 +481,24 @@ class RegisterControllerTest extends TestCase {
self::assertSame(TemplateResponse::RENDER_AS_GUEST, $response->getRenderAs());
self::assertSame('form/user', $response->getTemplateName());
self::assertSame([
'email' => $email,
'email_is_login' => false,
'email_is_optional' => false,
'loginname' => $username,
'fullname' => $fullname,
'show_fullname' => true,
'enforce_fullname' => false,
'phone' => $phone,
'show_phone' => true,
'enforce_phone' => false,
'message' => $message,
'password' => $password,
'additional_hint' => null,
], $response->getParams());
self::assertSame([], $response->getParams());
$this->initialState->method('provideInitialState')
->withConsecutive(
['email', $email],
['email_is_login', false],
['email_is_optional', false],
['loginname', $username],
['fullname', $fullname],
['show_fullname', true],
['enforce_fullname', false],
['phone', $phone],
['show_phone', true],
['enforce_phone', false],
['message', $message],
['password', $password],
['additional_hint', null],
);
}
public function testShowUserFormInvalidSecretAndToken(): void {