diff --git a/src/api.ts b/src/api.ts index ef72ad6..24edc12 100644 --- a/src/api.ts +++ b/src/api.ts @@ -8,7 +8,7 @@ export class APIError extends Error { } } -async function request( +async function api_request( method: HTTPMethod, endpoint: string, successStatus: number, @@ -29,7 +29,7 @@ async function request( } init.headers.set("Accept", "application/json"); init.headers.set("Content-Type", "application/json"); - const response = await fetch(`/${endpoint}/`, init); + const response = await fetch(`/api/${endpoint}/`, init); if (response.status !== 204) { if (response.status === successStatus) { return await response.json(); @@ -39,16 +39,6 @@ async function request( } } -async function api_request( - method: HTTPMethod, - endpoint: string, - successStatus: number, - data: any, - authToken?: string -) { - return request(method, `api/${endpoint}`, successStatus, data, authToken); -} - export class User { email: string | undefined; password: string | undefined; @@ -62,7 +52,7 @@ export class User { } async login(): Promise { - if (!this.email || !this.password) throw new APIError("", ""); + if (!this.username || !this.password) throw new APIError("", ""); // logout any existing sessions //await logout() @@ -73,7 +63,7 @@ export class User { // authenticate us const body = new window.FormData(); - body.append("username", this.email); + body.append("username", this.username); body.append("password", this.password); const csrf_token = Cookies.get("csrftoken"); if (csrf_token) body.append("csrfmiddlewaretoken", csrf_token); @@ -102,7 +92,7 @@ export class User { async signup(): Promise { await api_request("POST", "users", 201, { - email: this.email, + username: this.username, password: this.password, }); } diff --git a/src/components/LoginForm.vue b/src/components/LoginForm.vue index ada39b1..9dd5186 100644 --- a/src/components/LoginForm.vue +++ b/src/components/LoginForm.vue @@ -1,38 +1,45 @@ + + diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 75c4a8f..b1f5089 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -2,35 +2,12 @@ - + - + diff --git a/src/mixins.ts b/src/mixins.ts index 56575a2..1a53b21 100644 --- a/src/mixins.ts +++ b/src/mixins.ts @@ -3,17 +3,22 @@ import Vue from "vue"; @Component export class NotifyMixin extends Vue { - showError(): void { - this.$buefy.toast.open({ - message: "Es ist leider ein Fehler aufgetreten.", - type: "is-danger", - }); + public showError(): void { + this.showMessage("Es ist leider ein Fehler aufgetreten.", "is-danger"); } showSuccess(message: string): void { - this.$buefy.toast.open({ - message, - type: "is-success", - }); + this.showMessage(message, "is-success"); + } + + showWarning(message: string): void { + this.showMessage(message, "is-warning"); + } + + private showMessage( + message: string, + type: "is-success" | "is-danger" | "is-warning" + ): void { + this.$buefy.toast.open({ message, type }); } } diff --git a/src/views/MainPage.vue b/src/views/MainPage.vue index 7bd39ea..274da2e 100644 --- a/src/views/MainPage.vue +++ b/src/views/MainPage.vue @@ -29,7 +29,7 @@ export default class Home extends mixins(NotifyMixin) { public async created(): Promise { if (this.$route.name === "confirm") { await this.doConfirm(); - } else if (!this.user.isAuthenticated) { + } else if (!this.user.isAuthenticated && this.$route.name !== "login") { this.$router.push({ name: "login" }); } } diff --git a/userausfall/migrations/0007_user_email_unconfirmed.py b/userausfall/migrations/0007_user_email_unconfirmed.py new file mode 100644 index 0000000..6c8a017 --- /dev/null +++ b/userausfall/migrations/0007_user_email_unconfirmed.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.20 on 2021-07-29 10:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('userausfall', '0006_auto_20210521_0805'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='email_unconfirmed', + field=models.EmailField(blank=True, max_length=254, unique=True, verbose_name='email address'), + ), + ] diff --git a/userausfall/migrations/0008_auto_20210802_0744.py b/userausfall/migrations/0008_auto_20210802_0744.py new file mode 100644 index 0000000..33d1932 --- /dev/null +++ b/userausfall/migrations/0008_auto_20210802_0744.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.20 on 2021-08-02 07:44 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('userausfall', '0007_user_email_unconfirmed'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='confidant', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='confidants', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='user', + name='confidant_unconfirmed', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='unconfirmed_confidants', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/userausfall/migrations/0009_auto_20210802_0745.py b/userausfall/migrations/0009_auto_20210802_0745.py new file mode 100644 index 0000000..f318957 --- /dev/null +++ b/userausfall/migrations/0009_auto_20210802_0745.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.20 on 2021-08-02 07:45 + +import django.contrib.auth.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('userausfall', '0008_auto_20210802_0744'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(blank=True, max_length=254, verbose_name='email address'), + ), + migrations.AlterField( + model_name='user', + name='email_unconfirmed', + field=models.EmailField(blank=True, max_length=254, verbose_name='email address'), + ), + migrations.AlterField( + model_name='user', + name='username', + field=models.CharField(blank=True, error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'), + ), + ] diff --git a/userausfall/models.py b/userausfall/models.py index d20c385..eb51702 100644 --- a/userausfall/models.py +++ b/userausfall/models.py @@ -24,21 +24,21 @@ class PasswordMismatch(Exception): class UserManager(BaseUserManager): use_in_migrations = True - def _create_user(self, email, password, **extra_fields): + def _create_user(self, username, password, **extra_fields): """ Create and save a user with the given username, email, and password. """ - email = self.normalize_email(email) - # TODO: username = self.model.normalize_username(username) - user = self.model(email=email, **extra_fields) + # email = self.normalize_email(email) + username = self.model.normalize_username(username) + user = self.model(username=username, **extra_fields) user.set_password(password) user.save(using=self._db) return user - def create_user(self, email, password=None, **extra_fields): + def create_user(self, username, password=None, **extra_fields): extra_fields.setdefault("is_staff", False) extra_fields.setdefault("is_superuser", False) - return self._create_user(email, password, **extra_fields) + return self._create_user(username, password, **extra_fields) def create_superuser(self, email, password, **extra_fields): extra_fields.setdefault("is_staff", True) @@ -63,9 +63,11 @@ class User(AbstractBaseUser, PermissionsMixin): ), validators=[username_validator], error_messages={"unique": _("A user with that username already exists.")}, + unique=True, blank=True, ) - email = models.EmailField(_("email address"), unique=True, blank=True) + email = models.EmailField(_("email address"), blank=True) + email_unconfirmed = models.EmailField(_("email address"), blank=True) is_staff = models.BooleanField( _("staff status"), default=False, @@ -73,19 +75,20 @@ class User(AbstractBaseUser, PermissionsMixin): ) date_joined = models.DateTimeField(_("date joined"), default=timezone.now) confidant = models.ForeignKey( - "User", on_delete=models.SET_NULL, null=True, related_name="confidants" + "User", on_delete=models.SET_NULL, null=True, blank=True, related_name="confidants" ) confidant_unconfirmed = models.ForeignKey( "User", on_delete=models.SET_NULL, null=True, + blank=True, related_name="unconfirmed_confidants", ) objects = UserManager() EMAIL_FIELD = "email" - USERNAME_FIELD = "email" + USERNAME_FIELD = "username" REQUIRED_FIELDS = [] class Meta: diff --git a/userausfall/rest_api/permissions.py b/userausfall/rest_api/permissions.py new file mode 100644 index 0000000..72aa7dd --- /dev/null +++ b/userausfall/rest_api/permissions.py @@ -0,0 +1,11 @@ +from rest_framework import permissions + + +class UserPermission(permissions.BasePermission): + def has_permission(self, request, view): + if request.method == "POST": + return True + return False + + def has_object_permission(self, request, view, obj): + return False diff --git a/userausfall/rest_api/serializers.py b/userausfall/rest_api/serializers.py index c06ecf3..b3d619d 100644 --- a/userausfall/rest_api/serializers.py +++ b/userausfall/rest_api/serializers.py @@ -13,9 +13,19 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ("pk", "email", "username", "confidant_email") + read_only_fields = ("email",) def update(self, instance: User, validated_data): confidant_email = validated_data.pop("confidant_email") confidant, _ = User.objects.get_or_create(email=confidant_email) instance.confidant_unconfirmed = confidant return super().update(instance, validated_data) + + +class CreateUserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ("username", "password") + + def create(self, validated_data): + return User.objects.create_user(**validated_data) diff --git a/userausfall/rest_api/views.py b/userausfall/rest_api/views.py index 119f837..2e42eff 100644 --- a/userausfall/rest_api/views.py +++ b/userausfall/rest_api/views.py @@ -5,7 +5,8 @@ from rest_framework.response import Response from djeveric import ConfirmationView from userausfall.models import User, MissingUserAttribute, PasswordMismatch from userausfall.confirmations import ConfidantConfirmation -from userausfall.rest_api.serializers import UserSerializer, UserActivationSerializer +from userausfall.rest_api.permissions import UserPermission +from userausfall.rest_api.serializers import UserSerializer, UserActivationSerializer, CreateUserSerializer class ConfidantConfirmationView(ConfirmationView): @@ -13,8 +14,8 @@ class ConfidantConfirmationView(ConfirmationView): class UserViewSet(viewsets.ModelViewSet): + permission_classes = [UserPermission] queryset = User.objects.all() - serializer_class = UserSerializer @action(detail=True, methods=["post"]) def activate(self, request, pk=None): @@ -29,3 +30,7 @@ class UserViewSet(viewsets.ModelViewSet): return Response(status=status.HTTP_204_NO_CONTENT) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def get_serializer_class(self): + if self.action == "create": + return CreateUserSerializer