Added ability to configure email verification for registered user (#1929)

* Added ability to configure email verification for registered user
Removed unused code

* updated changelog

* fixed comments

* fixed swagger

* updated docs

Co-authored-by: Nikita Manovich <nikita.manovich@intel.com>
main
Andrey Zhavoronkov 6 years ago committed by GitHub
parent 5d2c329242
commit 2510d4d659
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [Datumaro] Dataset statistics (<https://github.com/opencv/cvat/pull/1668>)
- Ability to change label color in tasks and predefined labels (<https://github.com/opencv/cvat/pull/2014>)
- [Datumaro] Multi-dataset merge (https://github.com/opencv/cvat/pull/1695)
- Ability to configure email verification for new users (<https://github.com/opencv/cvat/pull/1929>)
- Link to django admin page from UI (<https://github.com/opencv/cvat/pull/2068>)
- Notification message when users use wrong browser (<https://github.com/opencv/cvat/pull/2070>)

@ -81,8 +81,10 @@
cvat.server.register.implementation = async (username, firstName, lastName,
email, password1, password2, userConfirmations) => {
await serverProxy.server.register(username, firstName, lastName, email,
password1, password2, userConfirmations);
const user = await serverProxy.server.register(username, firstName,
lastName, email, password1, password2, userConfirmations);
return new User(user);
};
cvat.server.login.implementation = async (username, password) => {

@ -148,6 +148,7 @@ function build() {
* @param {string} password1 A password for the new account
* @param {string} password2 The confirmation password for the new account
* @param {Object} userConfirmations An user confirmations of terms of use if needed
* @returns {Object} response data
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/

@ -23,6 +23,7 @@
is_staff: null,
is_superuser: null,
is_active: null,
email_verification_required: null,
};
for (const property in data) {
@ -143,6 +144,16 @@
*/
get: () => data.is_active,
},
isVerified: {
/**
* @name isVerified
* @type {boolean}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => !data.email_verification_required,
},
}));
}
}

@ -75,11 +75,10 @@ export const registerAsync = (
dispatch(authActions.register());
try {
await cvat.server.register(username, firstName, lastName, email, password1, password2,
const user = await cvat.server.register(username, firstName, lastName, email, password1, password2,
confirmations);
const users = await cvat.users.get({ self: true });
dispatch(authActions.registerSuccess(users[0]));
dispatch(authActions.registerSuccess(user));
} catch (error) {
dispatch(authActions.registerFailed(error));
}

@ -129,7 +129,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
return;
}
if (user == null) {
if (user == null || !user.isVerified) {
return;
}
@ -249,7 +249,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
keyMap,
} = this.props;
const readyForRender = (userInitialized && user == null)
const readyForRender = (userInitialized && (user == null || !user.isVerified))
|| (userInitialized && formatsInitialized
&& pluginsInitialized && usersInitialized && aboutInitialized);
@ -302,7 +302,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
if (readyForRender) {
if (user) {
if (user && user.isVerified) {
return (
<GlobalErrorBoundary>
<Layout>

@ -252,6 +252,7 @@ export interface NotificationsState {
};
auth: {
changePasswordDone: string;
registerDone: string;
};
};
}

@ -97,6 +97,7 @@ const defaultState: NotificationsState = {
},
auth: {
changePasswordDone: '',
registerDone: '',
},
},
};
@ -163,6 +164,25 @@ export default function (state = defaultState, action: AnyAction): Notifications
},
};
}
case AuthActionTypes.REGISTER_SUCCESS: {
if (!action.payload.user.isVerified) {
return {
...state,
messages: {
...state.messages,
auth: {
...state.messages.auth,
registerDone: `To use your account, you need to confirm the email address. \
We have sent an email with a confirmation link to ${action.payload.user.email}.`,
},
},
};
}
return {
...state,
};
}
case AuthActionTypes.CHANGE_PASSWORD_SUCCESS: {
return {
...state,

@ -1,8 +1,12 @@
# Copyright (C) 2018 Intel Corporation
# Copyright (C) 2018-2020 Intel Corporation
#
# SPDX-License-Identifier: MIT
from . import AUTH_ROLE
from django.conf import settings
from allauth.account import app_settings as allauth_settings
from allauth.account.models import EmailAddress
from . import AUTH_ROLE
def create_user(sender, instance, created, **kwargs):
from django.contrib.auth.models import Group
@ -10,6 +14,11 @@ def create_user(sender, instance, created, **kwargs):
if instance.is_superuser and instance.is_staff:
db_group = Group.objects.get(name=AUTH_ROLE.ADMIN)
instance.groups.add(db_group)
# create and verify EmailAdress for superuser accounts
if allauth_settings.EMAIL_REQUIRED:
EmailAddress.objects.get_or_create(user=instance, email=instance.email, primary=True, verified=True)
for group_name in settings.DJANGO_AUTH_DEFAULT_GROUPS:
db_group = Group.objects.get(name=getattr(AUTH_ROLE, group_name))
instance.groups.add(db_group)

@ -0,0 +1,8 @@
{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}!
<p>You're receiving this e-mail because user <strong>{{ user_display }}</strong> has given yours as an e-mail address to connect their account.</p>
<p>To confirm this is correct, go to <a href="{{ activate_url }}">{{ activate_url }}</a></p>
{% endblocktrans %}
{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}<strong>{{ site_domain }}</strong>{% endblocktrans %}
{% endautoescape %}

@ -2,13 +2,15 @@
#
# SPDX-License-Identifier: MIT
from django.urls import path
from django.urls import path, re_path
from django.conf import settings
from rest_auth.views import (
LoginView, LogoutView, PasswordChangeView,
PasswordResetView, PasswordResetConfirmView)
from rest_auth.registration.views import RegisterView
from .views import SigningView
from allauth.account.views import ConfirmEmailView, EmailVerificationSentView
from allauth.account import app_settings as allauth_settings
from cvat.apps.authentication.views import SigningView, RegisterView
urlpatterns = [
path('login', LoginView.as_view(), name='rest_login'),
@ -26,3 +28,11 @@ if settings.DJANGO_AUTH_TYPE == 'BASIC':
path('password/change', PasswordChangeView.as_view(),
name='rest_password_change'),
]
if allauth_settings.EMAIL_VERIFICATION != \
allauth_settings.EmailVerificationMethod.NONE:
urlpatterns += [
re_path(r'^account-confirm-email/(?P<key>[-:\w]+)/$', ConfirmEmailView.as_view(),
name='account_confirm_email'),
path('register/account-email-verification-sent', EmailVerificationSentView.as_view(),
name='account_email_verification_sent'),
]

@ -5,6 +5,8 @@
from rest_framework import views
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from rest_auth.registration.views import RegisterView as _RegisterView
from allauth.account import app_settings as allauth_settings
from furl import furl
from . import signature
@ -43,3 +45,12 @@ class SigningView(views.APIView):
url = furl(url).add({signature.QUERY_PARAM: sign}).url
return Response(url)
class RegisterView(_RegisterView):
def get_response_data(self, user):
data = self.get_serializer(user).data
data['email_verification_required'] = allauth_settings.EMAIL_VERIFICATION == \
allauth_settings.EmailVerificationMethod.MANDATORY
return data

@ -9,6 +9,7 @@
- [Stop all containers](#stop-all-containers)
- [Advanced settings](#advanced-settings)
- [Share path](#share-path)
- [Email verification](#email-verification)
- [Serving over HTTPS](#serving-over-https)
- [Prerequisites](#prerequisites)
- [Roadmap](#roadmap)
@ -362,6 +363,26 @@ You can change the share device path to your actual share. For user convenience
we have defined the environment variable $CVAT_SHARE_URL. This variable
contains a text (url for example) which is shown in the client-share browser.
### Email verification
You can enable email verification for newly registered users.
Specify these options in the [settings file](../../settings/base.py) to configure Django allauth
to enable email verification (ACCOUNT_EMAIL_VERIFICATION = 'mandatory').
Access is denied until the user's email address is verified.
```python
ACCOUNT_AUTHENTICATION_METHOD = 'username'
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
# Email backend settings for Django
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
```
Also you need to configure the Django email backend to send emails.
This depends on the email server you are using and is not covered in this tutorial, please see
[Django SMTP backend configuration](https://docs.djangoproject.com/en/3.1/topics/email/#django.core.mail.backends.smtp.EmailBackend)
for details.
### Serving over HTTPS
We will add [letsencrypt.org](https://letsencrypt.org/) issued certificate to secure

@ -207,15 +207,17 @@ DJANGO_AUTH_TYPE = 'BASIC'
DJANGO_AUTH_DEFAULT_GROUPS = []
LOGIN_URL = 'rest_login'
LOGIN_REDIRECT_URL = '/'
AUTH_LOGIN_NOTE = '<p>Have not registered yet? <a href="/auth/register">Register here</a>.</p>'
AUTHENTICATION_BACKENDS = [
'rules.permissions.ObjectPermissionBackend',
'django.contrib.auth.backends.ModelBackend'
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
# https://github.com/pennersr/django-allauth
ACCOUNT_EMAIL_VERIFICATION = 'none'
# set UI url to redirect after a successful e-mail confirmation
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = '/auth/login'
OLD_PASSWORD_FIELD_ENABLED = True
# Django-RQ

@ -37,6 +37,8 @@ UI_URL = '{}://{}'.format(UI_SCHEME, UI_HOST)
if UI_PORT and UI_PORT != '80':
UI_URL += ':{}'.format(UI_PORT)
# set UI url to redirect to after successful e-mail confirmation
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = '{}/auth/login'.format(UI_URL)
CORS_ORIGIN_WHITELIST = [UI_URL]
CORS_REPLACE_HTTPS_REFERER = True

@ -27,7 +27,6 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('', include('cvat.apps.engine.urls')),
path('django-rq/', include('django_rq.urls')),
path('auth/', include('cvat.apps.authentication.urls')),
path('documentation/', include('cvat.apps.documentation.urls')),
]

Loading…
Cancel
Save