diff --git a/api/config/settings/common.py b/api/config/settings/common.py index d29480648..1372f59e3 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -144,7 +144,6 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS MIDDLEWARE = ( # Make sure djangosecure.middleware.SecurityMiddleware is listed first 'django.contrib.sessions.middleware.SessionMiddleware', - 'funkwhale_api.users.middleware.AnonymousSessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -173,7 +172,10 @@ FIXTURE_DIRS = ( # EMAIL CONFIGURATION # ------------------------------------------------------------------------------ -EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend') +EMAIL_CONFIG = env.email_url( + 'EMAIL_CONFIG', default='consolemail://') + +vars().update(EMAIL_CONFIG) # DATABASE CONFIGURATION # ------------------------------------------------------------------------------ @@ -293,7 +295,7 @@ AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', 'allauth.account.auth_backends.AuthenticationBackend', ) - +SESSION_COOKIE_HTTPONLY = False # Some really nice defaults ACCOUNT_AUTHENTICATION_METHOD = 'username_email' ACCOUNT_EMAIL_REQUIRED = True @@ -368,6 +370,7 @@ CORS_ORIGIN_ALLOW_ALL = True # 'funkwhale.localhost', # ) CORS_ALLOW_CREDENTIALS = True + REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', @@ -392,6 +395,11 @@ REST_FRAMEWORK = { 'django_filters.rest_framework.DjangoFilterBackend', ) } +REST_AUTH_SERIALIZERS = { + 'PASSWORD_RESET_SERIALIZER': 'funkwhale_api.users.serializers.PasswordResetSerializer' # noqa +} +REST_SESSION_LOGIN = False +REST_USE_JWT = True ATOMIC_REQUESTS = False USE_X_FORWARDED_HOST = True diff --git a/api/config/settings/local.py b/api/config/settings/local.py index dcbea66d2..592600629 100644 --- a/api/config/settings/local.py +++ b/api/config/settings/local.py @@ -25,9 +25,6 @@ SECRET_KEY = env("DJANGO_SECRET_KEY", default='mc$&b=5j#6^bv7tld1gyjp2&+^-qrdy=0 # ------------------------------------------------------------------------------ EMAIL_HOST = 'localhost' EMAIL_PORT = 1025 -EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', - default='django.core.mail.backends.console.EmailBackend') - # django-debug-toolbar # ------------------------------------------------------------------------------ diff --git a/api/funkwhale_api/history/models.py b/api/funkwhale_api/history/models.py index 762d5bf7b..480461d35 100644 --- a/api/funkwhale_api/history/models.py +++ b/api/funkwhale_api/history/models.py @@ -21,13 +21,6 @@ class Listening(models.Model): class Meta: ordering = ('-creation_date',) - def save(self, **kwargs): - if not self.user and not self.session_key: - raise ValidationError('Cannot have both session_key and user empty for listening') - - super().save(**kwargs) - - def get_activity_url(self): return '{}/listenings/tracks/{}'.format( self.user.get_activity_url(), self.pk) diff --git a/api/funkwhale_api/history/serializers.py b/api/funkwhale_api/history/serializers.py index 8fe6fa6e0..f7333f243 100644 --- a/api/funkwhale_api/history/serializers.py +++ b/api/funkwhale_api/history/serializers.py @@ -36,13 +36,9 @@ class ListeningSerializer(serializers.ModelSerializer): class Meta: model = models.Listening - fields = ('id', 'user', 'session_key', 'track', 'creation_date') - + fields = ('id', 'user', 'track', 'creation_date') def create(self, validated_data): - if self.context.get('user'): - validated_data['user'] = self.context.get('user') - else: - validated_data['session_key'] = self.context['session_key'] + validated_data['user'] = self.context['user'] return super().create(validated_data) diff --git a/api/funkwhale_api/history/views.py b/api/funkwhale_api/history/views.py index d5cbe316b..bea96a418 100644 --- a/api/funkwhale_api/history/views.py +++ b/api/funkwhale_api/history/views.py @@ -1,4 +1,5 @@ from rest_framework import generics, mixins, viewsets +from rest_framework import permissions from rest_framework import status from rest_framework.response import Response from rest_framework.decorators import detail_route @@ -10,31 +11,26 @@ from funkwhale_api.music.serializers import TrackSerializerNested from . import models from . import serializers -class ListeningViewSet(mixins.CreateModelMixin, - mixins.RetrieveModelMixin, - viewsets.GenericViewSet): + +class ListeningViewSet( + mixins.CreateModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet): serializer_class = serializers.ListeningSerializer queryset = models.Listening.objects.all() - permission_classes = [ConditionalAuthentication] + permission_classes = [permissions.IsAuthenticated] def perform_create(self, serializer): r = super().perform_create(serializer) - if self.request.user.is_authenticated: - record.send(serializer.instance) + record.send(serializer.instance) return r def get_queryset(self): queryset = super().get_queryset() - if self.request.user.is_authenticated: - return queryset.filter(user=self.request.user) - else: - return queryset.filter(session_key=self.request.session.session_key) + return queryset.filter(user=self.request.user) def get_serializer_context(self): context = super().get_serializer_context() - if self.request.user.is_authenticated: - context['user'] = self.request.user - else: - context['session_key'] = self.request.session.session_key + context['user'] = self.request.user return context diff --git a/api/funkwhale_api/radios/models.py b/api/funkwhale_api/radios/models.py index 0273b5387..8758abc61 100644 --- a/api/funkwhale_api/radios/models.py +++ b/api/funkwhale_api/radios/models.py @@ -55,8 +55,6 @@ class RadioSession(models.Model): related_object = GenericForeignKey('related_object_content_type', 'related_object_id') def save(self, **kwargs): - if not self.user and not self.session_key: - raise ValidationError('Cannot have both session_key and user empty for radio session') self.radio.clean(self) super().save(**kwargs) diff --git a/api/funkwhale_api/radios/serializers.py b/api/funkwhale_api/radios/serializers.py index 2e7e6a409..195b382c9 100644 --- a/api/funkwhale_api/radios/serializers.py +++ b/api/funkwhale_api/radios/serializers.py @@ -38,6 +38,7 @@ class RadioSerializer(serializers.ModelSerializer): return super().save(**kwargs) + class RadioSessionTrackSerializerCreate(serializers.ModelSerializer): class Meta: model = models.RadioSessionTrack @@ -62,17 +63,14 @@ class RadioSessionSerializer(serializers.ModelSerializer): 'user', 'creation_date', 'custom_radio', - 'session_key') + ) def validate(self, data): registry[data['radio_type']]().validate_session(data, **self.context) return data def create(self, validated_data): - if self.context.get('user'): - validated_data['user'] = self.context.get('user') - else: - validated_data['session_key'] = self.context['session_key'] + validated_data['user'] = self.context['user'] if validated_data.get('related_object_id'): radio = registry[validated_data['radio_type']]() validated_data['related_object'] = radio.get_related_object(validated_data['related_object_id']) diff --git a/api/funkwhale_api/radios/views.py b/api/funkwhale_api/radios/views.py index ffd1d1659..37c07c5e4 100644 --- a/api/funkwhale_api/radios/views.py +++ b/api/funkwhale_api/radios/views.py @@ -2,6 +2,7 @@ from django.db.models import Q from django.http import Http404 from rest_framework import generics, mixins, viewsets +from rest_framework import permissions from rest_framework import status from rest_framework.response import Response from rest_framework.decorators import detail_route, list_route @@ -24,7 +25,7 @@ class RadioViewSet( viewsets.GenericViewSet): serializer_class = serializers.RadioSerializer - permission_classes = [ConditionalAuthentication] + permission_classes = [permissions.IsAuthenticated] filter_class = filtersets.RadioFilter def get_queryset(self): @@ -84,21 +85,15 @@ class RadioSessionViewSet(mixins.CreateModelMixin, serializer_class = serializers.RadioSessionSerializer queryset = models.RadioSession.objects.all() - permission_classes = [ConditionalAuthentication] + permission_classes = [permissions.IsAuthenticated] def get_queryset(self): queryset = super().get_queryset() - if self.request.user.is_authenticated: - return queryset.filter(user=self.request.user) - else: - return queryset.filter(session_key=self.request.session.session_key) + return queryset.filter(user=self.request.user) def get_serializer_context(self): context = super().get_serializer_context() - if self.request.user.is_authenticated: - context['user'] = self.request.user - else: - context['session_key'] = self.request.session.session_key + context['user'] = self.request.user return context @@ -106,17 +101,14 @@ class RadioSessionTrackViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet): serializer_class = serializers.RadioSessionTrackSerializer queryset = models.RadioSessionTrack.objects.all() - permission_classes = [ConditionalAuthentication] + permission_classes = [permissions.IsAuthenticated] def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) session = serializer.validated_data['session'] try: - if request.user.is_authenticated: - assert request.user == session.user - else: - assert request.session.session_key == session.session_key + assert request.user == session.user except AssertionError: return Response(status=status.HTTP_403_FORBIDDEN) track = session.radio.pick() diff --git a/api/funkwhale_api/templates/account/email/email_confirmation_message.txt b/api/funkwhale_api/templates/account/email/email_confirmation_message.txt new file mode 100644 index 000000000..8aec540fe --- /dev/null +++ b/api/funkwhale_api/templates/account/email/email_confirmation_message.txt @@ -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 }}! + +You're receiving this e-mail because user {{ user_display }} at {{ site_domain }} has given yours as an e-mail address to connect their account. + +To confirm this is correct, go to {{ funkwhale_url }}/auth/email/confirm?key={{ key }} +{% endblocktrans %}{% endautoescape %} +{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you from {{ site_name }}! +{{ site_domain }}{% endblocktrans %} diff --git a/api/funkwhale_api/templates/registration/password_reset_email.html b/api/funkwhale_api/templates/registration/password_reset_email.html new file mode 100644 index 000000000..7a587d720 --- /dev/null +++ b/api/funkwhale_api/templates/registration/password_reset_email.html @@ -0,0 +1,12 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %} + +{% trans "Please go to the following page and choose a new password:" %} +{{ funkwhale_url }}/auth/password/reset/confirm?uid={{ uid }}&token={{ token }} +{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} diff --git a/api/funkwhale_api/users/adapters.py b/api/funkwhale_api/users/adapters.py index 96d1b8b1d..7bd341d14 100644 --- a/api/funkwhale_api/users/adapters.py +++ b/api/funkwhale_api/users/adapters.py @@ -1,5 +1,6 @@ -from allauth.account.adapter import DefaultAccountAdapter +from django.conf import settings +from allauth.account.adapter import DefaultAccountAdapter from dynamic_preferences.registries import global_preferences_registry @@ -8,3 +9,7 @@ class FunkwhaleAccountAdapter(DefaultAccountAdapter): def is_open_for_signup(self, request): manager = global_preferences_registry.manager() return manager['users__registration_enabled'] + + def send_mail(self, template_prefix, email, context): + context['funkwhale_url'] = settings.FUNKWHALE_URL + return super().send_mail(template_prefix, email, context) diff --git a/api/funkwhale_api/users/middleware.py b/api/funkwhale_api/users/middleware.py deleted file mode 100644 index e3eba95f3..000000000 --- a/api/funkwhale_api/users/middleware.py +++ /dev/null @@ -1,11 +0,0 @@ - - -class AnonymousSessionMiddleware: - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - if not request.session.session_key: - request.session.save() - response = self.get_response(request) - return response diff --git a/api/funkwhale_api/users/rest_auth_urls.py b/api/funkwhale_api/users/rest_auth_urls.py index 31f5384aa..fa6c425cc 100644 --- a/api/funkwhale_api/users/rest_auth_urls.py +++ b/api/funkwhale_api/users/rest_auth_urls.py @@ -1,16 +1,20 @@ from django.views.generic import TemplateView from django.conf.urls import url -from rest_auth.registration.views import VerifyEmailView -from rest_auth.views import PasswordChangeView +from rest_auth.registration import views as registration_views +from rest_auth import views as rest_auth_views -from .views import RegisterView +from . import views urlpatterns = [ - url(r'^$', RegisterView.as_view(), name='rest_register'), - url(r'^verify-email/$', VerifyEmailView.as_view(), name='rest_verify_email'), - url(r'^change-password/$', PasswordChangeView.as_view(), name='change_password'), + url(r'^$', views.RegisterView.as_view(), name='rest_register'), + url(r'^verify-email/$', + registration_views.VerifyEmailView.as_view(), + name='rest_verify_email'), + url(r'^change-password/$', + rest_auth_views.PasswordChangeView.as_view(), + name='change_password'), # This url is used by django-allauth and empty TemplateView is # defined just to allow reverse() call inside app, for example when email diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py index b21aa6935..eadce6154 100644 --- a/api/funkwhale_api/users/serializers.py +++ b/api/funkwhale_api/users/serializers.py @@ -1,5 +1,7 @@ -from rest_framework import serializers +from django.conf import settings +from rest_framework import serializers +from rest_auth.serializers import PasswordResetSerializer as PRS from funkwhale_api.activity import serializers as activity_serializers from . import models @@ -63,3 +65,12 @@ class UserReadSerializer(serializers.ModelSerializer): 'status': o.has_perm(internal_codename) } return perms + + +class PasswordResetSerializer(PRS): + def get_email_options(self): + return { + 'extra_email_context': { + 'funkwhale_url': settings.FUNKWHALE_URL + } + } diff --git a/api/setup.cfg b/api/setup.cfg index a2b8b92c6..b1267c904 100644 --- a/api/setup.cfg +++ b/api/setup.cfg @@ -11,7 +11,7 @@ python_files = tests.py test_*.py *_tests.py testpaths = tests env = SECRET_KEY=test - DJANGO_EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend + EMAIL_CONFIG=consolemail:// CELERY_BROKER_URL=memory:// CELERY_TASK_ALWAYS_EAGER=True CACHEOPS_ENABLED=False diff --git a/api/tests/history/test_history.py b/api/tests/history/test_history.py index 563cf2f08..202725596 100644 --- a/api/tests/history/test_history.py +++ b/api/tests/history/test_history.py @@ -14,21 +14,6 @@ def test_can_create_listening(factories): l = models.Listening.objects.create(user=user, track=track) -def test_anonymous_user_can_create_listening_via_api( - client, factories, preferences): - preferences['common__api_authentication_required'] = False - track = factories['music.Track']() - url = reverse('api:v1:history:listenings-list') - response = client.post(url, { - 'track': track.pk, - }) - - listening = models.Listening.objects.latest('id') - - assert listening.track == track - assert listening.session_key == client.session.session_key - - def test_logged_in_user_can_create_listening_via_api( logged_in_client, factories, activity_muted): track = factories['music.Track']() diff --git a/api/tests/radios/test_radios.py b/api/tests/radios/test_radios.py index e78bd0e2f..b166b648c 100644 --- a/api/tests/radios/test_radios.py +++ b/api/tests/radios/test_radios.py @@ -151,20 +151,6 @@ def test_can_start_radio_for_logged_in_user(logged_in_client): assert session.user == logged_in_client.user -def test_can_start_radio_for_anonymous_user(api_client, db, preferences): - preferences['common__api_authentication_required'] = False - url = reverse('api:v1:radios:sessions-list') - response = api_client.post(url, {'radio_type': 'random'}) - - assert response.status_code == 201 - - session = models.RadioSession.objects.latest('id') - - assert session.radio_type == 'random' - assert session.user is None - assert session.session_key == api_client.session.session_key - - def test_can_get_track_for_session_from_api(factories, logged_in_client): files = factories['music.TrackFile'].create_batch(1) tracks = [f.track for f in files] @@ -227,25 +213,25 @@ def test_can_start_tag_radio(factories): radio = radios.TagRadio() session = radio.start_session(user, related_object=tag) - assert session.radio_type =='tag' + assert session.radio_type == 'tag' for i in range(5): assert radio.pick() in good_tracks -def test_can_start_artist_radio_from_api(api_client, preferences, factories): - preferences['common__api_authentication_required'] = False +def test_can_start_artist_radio_from_api( + logged_in_api_client, preferences, factories): artist = factories['music.Artist']() url = reverse('api:v1:radios:sessions-list') - response = api_client.post( + response = logged_in_api_client.post( url, {'radio_type': 'artist', 'related_object_id': artist.id}) assert response.status_code == 201 session = models.RadioSession.objects.latest('id') - assert session.radio_type, 'artist' - assert session.related_object, artist + assert session.radio_type == 'artist' + assert session.related_object == artist def test_can_start_less_listened_radio(factories): @@ -257,6 +243,6 @@ def test_can_start_less_listened_radio(factories): good_tracks = [f.track for f in good_files] radio = radios.LessListenedRadio() session = radio.start_session(user) - assert session.related_object == user + for i in range(5): assert radio.pick() in good_tracks diff --git a/api/tests/users/test_views.py b/api/tests/users/test_views.py index 4be586965..985a78c8a 100644 --- a/api/tests/users/test_views.py +++ b/api/tests/users/test_views.py @@ -136,6 +136,20 @@ def test_changing_password_updates_secret_key(logged_in_client): assert user.password != password +def test_can_request_password_reset( + factories, api_client, mailoutbox): + user = factories['users.User']() + payload = { + 'email': user.email, + } + emails = len(mailoutbox) + url = reverse('rest_password_reset') + + response = api_client.post(url, payload) + assert response.status_code == 200 + assert len(mailoutbox) > emails + + def test_user_can_patch_his_own_settings(logged_in_api_client): user = logged_in_api_client.user payload = { diff --git a/changes/changelog.d/187.feature b/changes/changelog.d/187.feature new file mode 100644 index 000000000..501331a19 --- /dev/null +++ b/changes/changelog.d/187.feature @@ -0,0 +1,24 @@ +Users can now request password reset by email, assuming +a SMTP server was correctly configured (#187) + +Update +^^^^^^ + +Starting from this release, Funkwhale will send two types +of emails: + +- Email confirmation emails, to ensure a user's email is valid +- Password reset emails, enabling user to reset their password without an admin's intervention + +Email sending is disabled by default, as it requires additional configuration. +In this mode, emails are simply outputed on stdout. + +If you want to actually send those emails to your users, you should edit your +.env file and tweak the EMAIL_CONFIG variable. See :ref:`setting-EMAIL_CONFIG` +for more details. + +.. note:: + + As a result of these changes, the DJANGO_EMAIL_BACKEND variable, + which was not documented, has no effect anymore. You can safely remove it from + your .env file if it is set. diff --git a/deploy/env.prod.sample b/deploy/env.prod.sample index f1718ff7e..dfd17ff4d 100644 --- a/deploy/env.prod.sample +++ b/deploy/env.prod.sample @@ -6,6 +6,7 @@ # - DJANGO_SECRET_KEY # - DJANGO_ALLOWED_HOSTS # - FUNKWHALE_URL +# - EMAIL_CONFIG (if you plan to send emails) # On non-docker setup **only**, you'll also have to tweak/uncomment those variables: # - DATABASE_URL # - CACHE_URL @@ -41,6 +42,16 @@ FUNKWHALE_API_PORT=5000 # your instance FUNKWHALE_URL=https://yourdomain.funwhale +# Configure email sending using this variale +# By default, funkwhale will output emails sent to stdout +# here are a few examples for this setting +# EMAIL_CONFIG=consolemail:// # output emails to console (the default) +# EMAIL_CONFIG=dummymail:// # disable email sending completely +# On a production instance, you'll usually want to use an external SMTP server: +# EMAIL_CONFIG=smtp://user@:password@youremail.host:25' +# EMAIL_CONFIG=smtp+ssl://user@:password@youremail.host:465' +# EMAIL_CONFIG=smtp+tls://user@:password@youremail.host:587' + # Depending on the reverse proxy used in front of your funkwhale instance, # the API will use different kind of headers to serve audio files # Allowed values: nginx, apache2 diff --git a/docs/configuration.rst b/docs/configuration.rst index 1c89feeb8..f498b9c87 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -39,6 +39,24 @@ settings in this interface. Configuration reference ----------------------- +.. _setting-EMAIL_CONFIG: + +``EMAIL_CONFIG`` +^^^^^^^^^^^^^^^^ + +Determine how emails are sent. + +Default: ``consolemail://`` + +Possible values: + +- ``consolemail://``: Output sent emails to stdout +- ``dummymail://``: Completely discard sent emails +- ``smtp://user:password@youremail.host:25``: Send emails via SMTP via youremail.host on port 25, without encryption, authenticating as user "user" with password "password" +- ``smtp+ssl://user:password@youremail.host:465``: Send emails via SMTP via youremail.host on port 465, using SSL encryption, authenticating as user "user" with password "password" +- ``smtp+tls://user:password@youremail.host:587``: Send emails via SMTP via youremail.host on port 587, using TLS encryption, authenticating as user "user" with password "password" + + .. _setting-MUSIC_DIRECTORY_PATH: ``MUSIC_DIRECTORY_PATH`` diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index 8e0df8016..97c743bbe 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -35,24 +35,24 @@
{{ $t('My account') }}
{{ $t('Music') }}
@@ -62,7 +62,7 @@ class="item" v-if="$store.state.auth.availablePermissions['import.launch']" :to="{name: 'library.requests', query: {status: 'pending' }}"> - {{ $t('Import requests') }} + {{ $t('Import requests') }}
@@ -72,7 +72,7 @@ class="item" v-if="$store.state.auth.availablePermissions['federation.manage']" :to="{path: '/manage/federation/libraries'}"> - {{ $t('Federation') }} + {{ $t('Federation') }}
diff --git a/front/src/components/auth/Login.vue b/front/src/components/auth/Login.vue index b06ce89f0..f3add57b1 100644 --- a/front/src/components/auth/Login.vue +++ b/front/src/components/auth/Login.vue @@ -12,9 +12,15 @@
- +
- - + + +
- - - - +
@@ -42,12 +46,15 @@ diff --git a/front/src/components/forms/PasswordInput.vue b/front/src/components/forms/PasswordInput.vue new file mode 100644 index 000000000..624a92d87 --- /dev/null +++ b/front/src/components/forms/PasswordInput.vue @@ -0,0 +1,31 @@ + + diff --git a/front/src/router/index.js b/front/src/router/index.js index 2e06bda99..b1e208023 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -9,6 +9,9 @@ import Signup from '@/components/auth/Signup' import Profile from '@/components/auth/Profile' import Settings from '@/components/auth/Settings' import Logout from '@/components/auth/Logout' +import PasswordReset from '@/views/auth/PasswordReset' +import PasswordResetConfirm from '@/views/auth/PasswordResetConfirm' +import EmailConfirm from '@/views/auth/EmailConfirm' import Library from '@/components/library/Library' import LibraryHome from '@/components/library/Home' import LibraryArtist from '@/components/library/Artist' @@ -59,6 +62,31 @@ export default new Router({ component: Login, props: (route) => ({ next: route.query.next || '/library' }) }, + { + path: '/auth/password/reset', + name: 'auth.password-reset', + component: PasswordReset, + props: (route) => ({ + defaultEmail: route.query.email + }) + }, + { + path: '/auth/email/confirm', + name: 'auth.email-confirm', + component: EmailConfirm, + props: (route) => ({ + defaultKey: route.query.key + }) + }, + { + path: '/auth/password/reset/confirm', + name: 'auth.password-reset-confirm', + component: PasswordResetConfirm, + props: (route) => ({ + defaultUid: route.query.uid, + defaultToken: route.query.token + }) + }, { path: '/signup', name: 'signup', diff --git a/front/src/store/auth.js b/front/src/store/auth.js index b1753404f..68a15090b 100644 --- a/front/src/store/auth.js +++ b/front/src/store/auth.js @@ -97,6 +97,11 @@ export default { } }, fetchProfile ({commit, dispatch, state}) { + if (document) { + // this is to ensure we do not have any leaking cookie set by django + document.cookie = 'sessionid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;' + } + return axios.get('users/users/me/').then((response) => { logger.default.info('Successfully fetched user profile') let data = response.data diff --git a/front/src/store/player.js b/front/src/store/player.js index ed437c3f0..2149b51ff 100644 --- a/front/src/store/player.js +++ b/front/src/store/player.js @@ -85,7 +85,10 @@ export default { togglePlay ({commit, state}) { commit('playing', !state.playing) }, - trackListened ({commit}, track) { + trackListened ({commit, rootState}, track) { + if (!rootState.auth.authenticated) { + return + } return axios.post('history/listenings/', {'track': track.id}).then((response) => {}, (response) => { logger.default.error('Could not record track in history') }) diff --git a/front/src/views/auth/EmailConfirm.vue b/front/src/views/auth/EmailConfirm.vue new file mode 100644 index 000000000..7ffa3c8d1 --- /dev/null +++ b/front/src/views/auth/EmailConfirm.vue @@ -0,0 +1,71 @@ + + + + + + diff --git a/front/src/views/auth/PasswordReset.vue b/front/src/views/auth/PasswordReset.vue new file mode 100644 index 000000000..f6b445e00 --- /dev/null +++ b/front/src/views/auth/PasswordReset.vue @@ -0,0 +1,75 @@ + + + + + + diff --git a/front/src/views/auth/PasswordResetConfirm.vue b/front/src/views/auth/PasswordResetConfirm.vue new file mode 100644 index 000000000..102ed6126 --- /dev/null +++ b/front/src/views/auth/PasswordResetConfirm.vue @@ -0,0 +1,85 @@ + + + + + +