diff --git a/embark/dashboard/urls.py b/embark/dashboard/urls.py index 4eb798af..17b87a88 100644 --- a/embark/dashboard/urls.py +++ b/embark/dashboard/urls.py @@ -9,6 +9,7 @@ # view routing urlpatterns = [ + path('', views.main_dashboard, name='embark-MainDashboard'), path('dashboard/main/', views.main_dashboard, name='embark-MainDashboard'), path('dashboard/service/', views.service_dashboard, name='embark-dashboard-service'), path('dashboard/report/', views.report_dashboard, name='embark-ReportDashboard'), diff --git a/embark/embark/settings/dev.py b/embark/embark/settings/dev.py index beb66775..9de31948 100644 --- a/embark/embark/settings/dev.py +++ b/embark/embark/settings/dev.py @@ -82,6 +82,7 @@ SESSION_COOKIE_SAMESITE = 'Strict' SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SECURE = False WSGI_APPLICATION = 'embark.wsgi.application' @@ -187,16 +188,19 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": { + "min_length": 8, + }, }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -226,10 +230,10 @@ # STATICFILES_FINDERS # URL of Login-Page -LOGIN_URL = '' +LOGIN_URL = 'user/login/' # URL of Logout-Page -LOGOUT_REDIRECT_URL = '' +LOGOUT_REDIRECT_URL = 'user/logout' # Added for FIle storage to get the path to save Firmware images. MEDIA_ROOT = os.path.join(BASE_DIR.parent, 'media') @@ -272,6 +276,7 @@ }, }, } +# TODO check this https://docs.djangoproject.com/en/5.1/topics/cache/ TEMP_DIR = Path("/tmp/") try: diff --git a/embark/templates/user/accountDelete.html b/embark/templates/user/accountDelete.html index 6e673b50..b601123f 100644 --- a/embark/templates/user/accountDelete.html +++ b/embark/templates/user/accountDelete.html @@ -6,21 +6,21 @@ {% block maincontent %}
-
-
-
- -
-
-

Are you sure you want to permanently delete your account?

-
-
-

- -

-
-
-
+
+
+
+ +
+
+

Are you sure you want to permanently delete your account?

+
+
+

+ +

+
+
+
{% endblock maincontent %} \ No newline at end of file diff --git a/embark/templates/user/email_template.html b/embark/templates/user/email_template_activation.html similarity index 68% rename from embark/templates/user/email_template.html rename to embark/templates/user/email_template_activation.html index e3e99f33..bad556a5 100644 --- a/embark/templates/user/email_template.html +++ b/embark/templates/user/email_template_activation.html @@ -2,7 +2,7 @@ Dear {{ username }}, Please follow the link to confirm your registration, -http://{{ domain }}{% url 'activate' user_id=uid activation_token=token %} +http://{{ domain }}{% url 'embark-activate-user' user_id=uid token=token %} OR manually input the following token: {{ token }} {% endautoescape %} \ No newline at end of file diff --git a/embark/templates/user/email_template_deactivation.html b/embark/templates/user/email_template_deactivation.html new file mode 100644 index 00000000..0c005dec --- /dev/null +++ b/embark/templates/user/email_template_deactivation.html @@ -0,0 +1,8 @@ +{% autoescape off %} +Dear {{ username }}, + +Please follow the link to confirm the account deletion, +http://{{ domain }}{% url 'embark-deactivate-user' user_id=uid activation_token=token %} + +OR manually input the following token: {{ token }} +{% endautoescape %} \ No newline at end of file diff --git a/embark/templates/user/index.html b/embark/templates/user/index.html index 3bd3389c..1fdbe3f0 100644 --- a/embark/templates/user/index.html +++ b/embark/templates/user/index.html @@ -5,6 +5,14 @@ {% block navigation %}{% include "navigation.html" %}{% endblock navigation %} {% block maincontent %} +{% if user.is_staff %} +
+

+ +

+ +
+{% endif %}
{% block timezone %}{% include "user/timezone.html" %}{% endblock timezone %}
diff --git a/embark/templates/user/login.html b/embark/templates/user/login.html index cdb37bc9..e2489889 100644 --- a/embark/templates/user/login.html +++ b/embark/templates/user/login.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load static %} +{% load django_bootstrap5 %} {% block style %}{% endblock style %} {% block title %}EMBArk login{% endblock title %} {% block maincontent %} @@ -15,24 +16,23 @@

New Version 0.1

New New Buttons everywhere

-
- {% csrf_token %} +

Sign in

-
- - -
-
- - -
- + {% csrf_token %} + {% for field in form %} + {% bootstrap_field field %} + {% endfor %} +
+ {% endblock maincontent %} \ No newline at end of file diff --git a/embark/templates/user/register.html b/embark/templates/user/register.html index cd4d4500..ec70bf3c 100644 --- a/embark/templates/user/register.html +++ b/embark/templates/user/register.html @@ -6,9 +6,9 @@ {% block maincontent %}
-
+ {% csrf_token %} - {% for field in signup_form %} + {% for field in form %} {% bootstrap_field field %} {% endfor %} diff --git a/embark/users/forms.py b/embark/users/forms.py index 0f3691da..eb2368aa 100644 --- a/embark/users/forms.py +++ b/embark/users/forms.py @@ -1,14 +1,42 @@ from django import forms -from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth import password_validation +from django.contrib.auth.validators import UnicodeUsernameValidator +from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from users.models import User -class SignupForm(UserCreationForm): +username_validator = UnicodeUsernameValidator() + + +class SignUpForm(UserCreationForm): + first_name = forms.CharField(max_length=12, min_length=4, required=False, help_text='Optional: First Name', + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'First Name'})) + last_name = forms.CharField(max_length=12, min_length=4, required=False, help_text='Optional: Last Name', + widget=(forms.TextInput(attrs={'class': 'form-control'}))) + email = forms.EmailField(max_length=50, help_text='Required. Inform a valid email address.', + widget=(forms.TextInput(attrs={'class': 'form-control'}))) + password1 = forms.CharField(label='Password', + widget=(forms.PasswordInput(attrs={'class': 'form-control'})), + help_text=password_validation.password_validators_help_text_html()) + password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput(attrs={'class': 'form-control'}), + help_text='Just Enter the same password, for confirmation') + username = forms.CharField( + label='Username', + max_length=150, + help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', + validators=[username_validator], + error_messages={'unique': "A user with that username already exists."}, + widget=forms.TextInput(attrs={'class': 'form-control'}) + ) + usable_password = None class Meta: model = User - fields = ('username', 'email', 'password1', 'password2') - + fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2',) -class ActivationForm(forms.Form): - token = forms.CharField(max_length=256, min_length=256) +class LoginForm(AuthenticationForm): + error_messages = { + "invalid_login": "Please enter a correct %(username)s and password. Note that both fields may be case-sensitive.", + "inactive": "This account is not yet activated", + "deactivated": "Account was deactivated", + } diff --git a/embark/users/models.py b/embark/users/models.py index 9e3febc8..fd4a7c21 100644 --- a/embark/users/models.py +++ b/embark/users/models.py @@ -28,6 +28,7 @@ class Team(models.Model): class User(AbstractUser): timezone = models.CharField(max_length=32, choices=settings.TIMEZONES, default='UTC') + email = models.EmailField(verbose_name="email address", blank=True, unique=True) class TeamMember(models.Model): diff --git a/embark/users/urls.py b/embark/users/urls.py index a1011cb6..5d2811a8 100644 --- a/embark/users/urls.py +++ b/embark/users/urls.py @@ -11,12 +11,13 @@ urlpatterns = [ path(settings.LOGIN_URL, views.embark_login, name='embark-login'), path('user/', views.user_main, name='embark-user-main'), - path('register/', views.register, name='embark-register'), - path('register/activate//', views.register, name='embark-activate-user'), - path('logout/', views.embark_logout, name='embark-logout'), + path('user/register/', views.register, name='embark-register'), + path('user//activate/', views.activate, name='embark-activate-user'), + path('user/reset_password/', views.reset_password, name='embark-password-reset'), + path(settings.LOGOUT_REDIRECT_URL, views.embark_logout, name='embark-logout'), path('user/password_change/', views.password_change, name='embark-password-change'), - path('user/acc_delete/', views.acc_delete, name='embark-acc-delete'), + path('user/delete/', views.acc_delete, name='embark-acc-delete'), + path('user//deactivate', views.deactivate, name='embark-deactivate-user'), path('user/set_timezone/', views.set_timezone, name='embark-acc-timezone'), - # TODO account menu path('my-account/', views., name='embark-), for admin options etc - path('log///', views.get_log, name='log'), + path('log///', views.get_log, name='log'), # TODO move to admin ] diff --git a/embark/users/views.py b/embark/users/views.py index 21b85ce6..23634c13 100644 --- a/embark/users/views.py +++ b/embark/users/views.py @@ -11,6 +11,7 @@ from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.shortcuts import get_current_site from django.contrib import messages +from django.forms import ValidationError from django.shortcuts import render, redirect from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods @@ -20,7 +21,7 @@ from django.urls import reverse from django.core.mail import send_mail -from users.forms import ActivationForm, SignupForm +from users.forms import LoginForm, SignUpForm from users.models import User logger = logging.getLogger(__name__) @@ -29,6 +30,8 @@ @require_http_methods(["GET"]) def user_main(request): logger.debug("Account settings for %s", request.user) + user = get_user(request) + # return render(request, 'user/index.html', {"timezones": settings.TIMEZONES, "server_tz": settings.TIME_ZONE, 'user': user}) return render(request, 'user/index.html', {"timezones": settings.TIMEZONES, "server_tz": settings.TIME_ZONE}) @@ -39,7 +42,7 @@ def register(request): logger.debug(request.POST) try: user = get_user(request) - signup_form = SignupForm(request.POST) + signup_form = SignUpForm(data=request.POST) if signup_form.is_valid(): username = signup_form.cleaned_data.get('username') password = signup_form.cleaned_data.get('password') @@ -48,13 +51,13 @@ def register(request): user = User.objects.create(username=username, email=email) user.set_password(password) user.is_active = False + # user.is_active = True user.save() logger.debug('User created') token = default_token_generator.make_token(user) current_site = get_current_site(request) mail_subject = 'Activate your EMBArk account.' - message = render_to_string('email_template.html', context={ - 'user': user, + message = render_to_string('user/email_template_activation.html', context={ 'username': user.username, 'domain': current_site.domain, 'uid': user.id, @@ -64,10 +67,10 @@ def register(request): if settings.EMAIL_ACTIVE == True: send_mail(mail_subject, message, 'system@' + settings.DOMAIN, [email]) messages.success(request, 'Registration successful. Please check your email to activate') - return redirect(reverse('embark-login')) + return redirect(reverse('embark-activate-user', kwargs={'uuid':user.id})) else: logger.debug("Registered, redirecting to login") - if activate_user(user.id, token): + if activate_user(user, token): messages.success(request, 'Registration successful.') return redirect(reverse('embark-login')) else: @@ -75,58 +78,51 @@ def register(request): except builtins.Exception as error: logger.exception('Wide exception in Signup: %s', error) messages.error(request, 'Something went wrong when signing up the user.') - register_form = SignupForm() - return render(request, 'user/register.html', {'signup_form': register_form}) + else: + signup_form = SignUpForm() + return render(request, 'user/register.html', {'form': signup_form}) @require_http_methods(["GET", "POST"]) def embark_login(request): if request.method == "POST": - logger.debug(request.POST) - data = {k: v[0] for k, v in dict(request.POST).items()} - logger.debug(data) try: - body = {k: v[0] for k, v in dict(request.POST).items()} - try: - username = body['username'] - password = body['password'] - except KeyError: - logger.exception('Missing keys from data- Username and password') - messages.error(request, 'Username or password are wrong.') - return render(request, 'user/login.html') - - logger.debug('Found user name and password') - user = authenticate(request, username=username, password=password) - if user is not None: - logger.debug('User authenticated') - login(request, user) - logger.debug('User logged in') - request.session["django_timezone"] = user.timezone - # messages.success(request, str(user.username) + ' timezone set to : ' + str(user.timezone)) - return redirect('../../dashboard/main/') - # else: - logger.debug('User could not be authenticated') - messages.error(request, "Invalid user data") - return render(request, 'user/login.html') + login_form = LoginForm(request, request.POST) + logger.debug(login_form) + if login_form.is_valid(): + logger.debug('form valid') + user = login_form.get_user() + if user: + logger.debug('User authenticated') + login(request, user) + logger.debug('User logged in') + request.session["django_timezone"] = user.timezone + # messages.success(request, str(user.username) + ' timezone set to : ' + str(user.timezone)) + return redirect('embark-uploader-home') + logger.debug('User could not be authenticated') + logger.debug('Form errors: %s', str(login_form.errors)) + except ValidationError as valid_error: + logger.error("Exception in value: %s ", valid_error) + messages.error(request, 'User is deactivated') except builtins.Exception as error: logger.exception('Wide exception in Signup: %s', error) messages.error(request, 'Something went wrong when logging in the user.') - return render(request, 'user/login.html') - return render(request, 'user/login.html') + else: + login_form = LoginForm() + return render(request, 'user/login.html', {'form': login_form}) -# @cache_control(no_cache=True, must_revalidate=True, no_store=True) @login_required(login_url='/' + settings.LOGIN_URL) -def embark_logout(request): # FIXME this just flushes session_id??! +def embark_logout(request): logout(request=request) logger.debug("Logout user %s", request) messages.success(request, 'Logout successful.') - return render(request, 'user/login.html') + return redirect('embark-login') @login_required(login_url='/' + settings.LOGIN_URL) @require_http_methods(["GET", "POST"]) -def password_change(request): +def password_change(request): # FIXME if request.method == "POST": logger.debug(request.POST) user = get_user(request) @@ -179,19 +175,41 @@ def acc_delete(request): if request.method == "POST": logger.debug('disabling account') user = get_user(request) - logger.debug(' %s Account: %s disabled', timezone.now().strftime("%H:%M:%S"), user) - user.username = user.get_username() + '_disactivated_' + timezone.now().strftime( - "%H:%M:%S") # workaround for not duplicating entry users_user.username - user.is_active = False - user.save() - messages.success(request, 'Account successfully deleted.') - return render(request, 'user/register.html') # TODO should be redirect + email = user.email + token = default_token_generator.make_token(user) + current_site = get_current_site(request) + mail_subject = 'Delete your EMBArk account.' + message = render_to_string('user/email_template_deactivation.html', context={ + 'user': user, + 'username': user.username, + 'domain': current_site.domain, + 'uid': user.id, + 'token': token, + }) + if settings.EMAIL_ACTIVE == True: + send_mail(mail_subject, message, 'system@' + settings.DOMAIN, [email]) + messages.success(request, 'Please check your email to confirm deletion') + return redirect(reverse('embark-deactivate-user', kwargs={'uuid':user.id})) + else: + logger.debug(' %s Account: %s disabled', timezone.now().strftime("%H:%M:%S"), user) + user.username = user.get_username() + '_disactivated_' + timezone.now().strftime( + "%H:%M:%S") # workaround for not duplicating entry users_user.username + user.is_active = False + user.save() + messages.success(request, 'Account successfully deleted.') + return redirect('embark-login') return render(request, 'user/accountDelete.html') +@require_http_methods(["POST"]) +@login_required(login_url='/' + settings.LOGIN_URL) +def deactivate(request, uid): + pass + + @require_http_methods(["GET"]) @login_required(login_url='/' + settings.LOGIN_URL) -def get_log(request, log_type, lines): # FIXME update or remove +def get_log(request, log_type, lines): # FIXME move to admin """ View takes a get request with following params: 1. log_type: selector of log file (daphne, migration, mysql_db, redis_db, uwsgi, web) @@ -260,27 +278,25 @@ def activate_user(user, token): return False -@require_http_methods(["GET", "POST"]) +@require_http_methods(["GET"]) @login_required(login_url='/' + settings.LOGIN_URL) -def activate(request): +def activate(request, user_id, token): """ activation page + form request activates user through the usage of token """ - if request.method == "POST": - logger.debug(request.POST) - try: - user = get_user(request) - activation_form = ActivationForm(request.POST) - if activation_form.is_valid(): - token = activation_form.cleand_data["token"] - if activate_user(user, token): - messages.success(request, str(user.username) + 'activated') - else: - messages.error(request, "Token invalid - maybe it expired?") - except ValueError as val_error: - logger.error(f"{val_error} in token {token}") - return redirect(reverse('embark-login')) - else: - activation_form = ActivationForm() - render(request, 'activate.html', {'activation-form': activation_form}) + try: + user = User.objects.get(id=user_id) + if activate_user(user, token): + login(request, user) + messages.success(request, str(user.username) + 'activated') + else: + messages.error(request, "Token invalid - maybe it expired?") + except ValueError as val_error: + logger.error(f"{val_error} in token {token}") + return redirect(reverse('embark-MainDashboard')) + + +@require_http_methods(["GET", "POST"]) +def reset_password(request): + pass