feat: Re-enable signup and login
This commit is contained in:
parent
3ad75c2f0d
commit
74afc805dc
12 changed files with 168 additions and 87 deletions
20
src/api.ts
20
src/api.ts
|
@ -8,7 +8,7 @@ export class APIError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function request(
|
async function api_request(
|
||||||
method: HTTPMethod,
|
method: HTTPMethod,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
successStatus: number,
|
successStatus: number,
|
||||||
|
@ -29,7 +29,7 @@ async function request(
|
||||||
}
|
}
|
||||||
init.headers.set("Accept", "application/json");
|
init.headers.set("Accept", "application/json");
|
||||||
init.headers.set("Content-Type", "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 !== 204) {
|
||||||
if (response.status === successStatus) {
|
if (response.status === successStatus) {
|
||||||
return await response.json();
|
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 {
|
export class User {
|
||||||
email: string | undefined;
|
email: string | undefined;
|
||||||
password: string | undefined;
|
password: string | undefined;
|
||||||
|
@ -62,7 +52,7 @@ export class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(): Promise<void> {
|
async login(): Promise<void> {
|
||||||
if (!this.email || !this.password) throw new APIError("", "");
|
if (!this.username || !this.password) throw new APIError("", "");
|
||||||
|
|
||||||
// logout any existing sessions
|
// logout any existing sessions
|
||||||
//await logout()
|
//await logout()
|
||||||
|
@ -73,7 +63,7 @@ export class User {
|
||||||
|
|
||||||
// authenticate us
|
// authenticate us
|
||||||
const body = new window.FormData();
|
const body = new window.FormData();
|
||||||
body.append("username", this.email);
|
body.append("username", this.username);
|
||||||
body.append("password", this.password);
|
body.append("password", this.password);
|
||||||
const csrf_token = Cookies.get("csrftoken");
|
const csrf_token = Cookies.get("csrftoken");
|
||||||
if (csrf_token) body.append("csrfmiddlewaretoken", csrf_token);
|
if (csrf_token) body.append("csrfmiddlewaretoken", csrf_token);
|
||||||
|
@ -102,7 +92,7 @@ export class User {
|
||||||
|
|
||||||
async signup(): Promise<void> {
|
async signup(): Promise<void> {
|
||||||
await api_request("POST", "users", 201, {
|
await api_request("POST", "users", 201, {
|
||||||
email: this.email,
|
username: this.username,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,45 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="box" @submit.prevent="doAction">
|
<form class="box" @submit.prevent="doAction">
|
||||||
<b-field label="E-Mail-Adresse">
|
<div v-if="errorMessage" class="notification is-danger">
|
||||||
<b-input type="email" v-model="user.email" />
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<b-field label="Benutzername">
|
||||||
|
<b-input type="text" v-model="user.username" />
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field label="Kennwort">
|
<b-field label="Kennwort">
|
||||||
<b-input type="password" v-model="user.password" />
|
<b-input type="password" v-model="user.password" />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
<b-field v-if="mode === 'signup'" label="Kennwort wiederholen">
|
||||||
<div v-if="false" class="notification is-danger">
|
<b-input type="password" v-model="password2" />
|
||||||
Email or password was wrong.
|
</b-field>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<b-button
|
<b-button native-type="submit" type="is-primary">
|
||||||
native-type="submit"
|
|
||||||
type="is-primary"
|
|
||||||
style="margin-left: auto; margin-right: 0; order: 10"
|
|
||||||
>
|
|
||||||
{{ mode === "login" ? "Anmelden" : "Konto anlegen" }}
|
{{ mode === "login" ? "Anmelden" : "Konto anlegen" }}
|
||||||
</b-button>
|
</b-button>
|
||||||
|
<router-link v-if="mode === 'login'" to="/signup"
|
||||||
|
>Konto anlegen</router-link
|
||||||
|
>
|
||||||
|
<router-link v-else to="/login">Anmelden</router-link>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
import { Component, Prop, Watch } from "vue-property-decorator";
|
||||||
|
import { mixins } from "vue-class-component";
|
||||||
import { User } from "@/api";
|
import { User } from "@/api";
|
||||||
import { Route } from "vue-router";
|
import { Route } from "vue-router";
|
||||||
|
import { NotifyMixin } from "@/mixins";
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class LoginForm extends Vue {
|
export default class LoginForm extends mixins(NotifyMixin) {
|
||||||
@Prop() private user!: User;
|
@Prop() private user!: User;
|
||||||
|
|
||||||
private mode: "login" | "signup" = "login";
|
private mode: "login" | "signup" = "login";
|
||||||
|
private password2 = "";
|
||||||
|
private errorMessage = "";
|
||||||
|
|
||||||
public created(): void {
|
public created(): void {
|
||||||
if (this.$route.name === "signup") this.mode = "signup";
|
if (this.$route.name === "signup") this.mode = "signup";
|
||||||
|
@ -48,19 +55,20 @@ export default class LoginForm extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doAction() {
|
private async doAction() {
|
||||||
|
try {
|
||||||
if (this.mode === "login") {
|
if (this.mode === "login") {
|
||||||
await this.user.login();
|
await this.user.login();
|
||||||
if (this.$route.name !== "home") this.$router.push({ name: "home" });
|
if (this.$route.name !== "home") this.$router.push({ name: "home" });
|
||||||
} else {
|
} else {
|
||||||
await this.user.signup();
|
await this.user.signup();
|
||||||
this.$router.push({ name: "login" });
|
this.$router.push({ name: "login" });
|
||||||
this.$buefy.toast.open({
|
this.showSuccess("Du kannst dich nun anmelden.");
|
||||||
message:
|
}
|
||||||
"Eine E-Mail zur Bestätigung deiner E-Mail-Adresse wurde versendet.",
|
} catch {
|
||||||
type: "is-success",
|
this.errorMessage = "Fehler";
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// TODO: error handling, show confirmation page
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss"></style>
|
||||||
|
|
|
@ -2,35 +2,12 @@
|
||||||
<b-navbar>
|
<b-navbar>
|
||||||
<template #brand>
|
<template #brand>
|
||||||
<b-navbar-item tag="router-link" :to="{ path: '/' }">
|
<b-navbar-item tag="router-link" :to="{ path: '/' }">
|
||||||
<img
|
<img src="/img/logo_text.png" alt="userausfall" />
|
||||||
src="/img/logo_text.png"
|
|
||||||
alt="Lightweight UI components for Vue.js based on Bulma"
|
|
||||||
/>
|
|
||||||
</b-navbar-item>
|
</b-navbar-item>
|
||||||
</template>
|
</template>
|
||||||
<template #start>
|
<template #start> </template>
|
||||||
<!--
|
|
||||||
<b-navbar-item href="#"> Home </b-navbar-item>
|
|
||||||
<b-navbar-item href="#"> Documentation </b-navbar-item>
|
|
||||||
<b-navbar-dropdown label="Info">
|
|
||||||
<b-navbar-item href="#"> About </b-navbar-item>
|
|
||||||
<b-navbar-item href="#"> Contact </b-navbar-item>
|
|
||||||
</b-navbar-dropdown>
|
|
||||||
-->
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #end>
|
<template #end> </template>
|
||||||
<b-navbar-item tag="div">
|
|
||||||
<div class="buttons">
|
|
||||||
<router-link :to="{ name: 'signup' }" class="button is-primary">
|
|
||||||
<strong>Konto anlegen</strong>
|
|
||||||
</router-link>
|
|
||||||
<router-link :to="{ name: 'login' }" class="button is-light"
|
|
||||||
>Anmelden</router-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</b-navbar-item>
|
|
||||||
</template>
|
|
||||||
</b-navbar>
|
</b-navbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,22 @@ import Vue from "vue";
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export class NotifyMixin extends Vue {
|
export class NotifyMixin extends Vue {
|
||||||
showError(): void {
|
public showError(): void {
|
||||||
this.$buefy.toast.open({
|
this.showMessage("Es ist leider ein Fehler aufgetreten.", "is-danger");
|
||||||
message: "Es ist leider ein Fehler aufgetreten.",
|
|
||||||
type: "is-danger",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccess(message: string): void {
|
showSuccess(message: string): void {
|
||||||
this.$buefy.toast.open({
|
this.showMessage(message, "is-success");
|
||||||
message,
|
}
|
||||||
type: "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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default class Home extends mixins(NotifyMixin) {
|
||||||
public async created(): Promise<void> {
|
public async created(): Promise<void> {
|
||||||
if (this.$route.name === "confirm") {
|
if (this.$route.name === "confirm") {
|
||||||
await this.doConfirm();
|
await this.doConfirm();
|
||||||
} else if (!this.user.isAuthenticated) {
|
} else if (!this.user.isAuthenticated && this.$route.name !== "login") {
|
||||||
this.$router.push({ name: "login" });
|
this.$router.push({ name: "login" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
userausfall/migrations/0007_user_email_unconfirmed.py
Normal file
18
userausfall/migrations/0007_user_email_unconfirmed.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
25
userausfall/migrations/0008_auto_20210802_0744.py
Normal file
25
userausfall/migrations/0008_auto_20210802_0744.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
29
userausfall/migrations/0009_auto_20210802_0745.py
Normal file
29
userausfall/migrations/0009_auto_20210802_0745.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -24,21 +24,21 @@ class PasswordMismatch(Exception):
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
use_in_migrations = True
|
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.
|
Create and save a user with the given username, email, and password.
|
||||||
"""
|
"""
|
||||||
email = self.normalize_email(email)
|
# email = self.normalize_email(email)
|
||||||
# TODO: username = self.model.normalize_username(username)
|
username = self.model.normalize_username(username)
|
||||||
user = self.model(email=email, **extra_fields)
|
user = self.model(username=username, **extra_fields)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
return user
|
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_staff", False)
|
||||||
extra_fields.setdefault("is_superuser", 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):
|
def create_superuser(self, email, password, **extra_fields):
|
||||||
extra_fields.setdefault("is_staff", True)
|
extra_fields.setdefault("is_staff", True)
|
||||||
|
@ -63,9 +63,11 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||||
),
|
),
|
||||||
validators=[username_validator],
|
validators=[username_validator],
|
||||||
error_messages={"unique": _("A user with that username already exists.")},
|
error_messages={"unique": _("A user with that username already exists.")},
|
||||||
|
unique=True,
|
||||||
blank=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(
|
is_staff = models.BooleanField(
|
||||||
_("staff status"),
|
_("staff status"),
|
||||||
default=False,
|
default=False,
|
||||||
|
@ -73,19 +75,20 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||||
)
|
)
|
||||||
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
|
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
|
||||||
confidant = models.ForeignKey(
|
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(
|
confidant_unconfirmed = models.ForeignKey(
|
||||||
"User",
|
"User",
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
|
blank=True,
|
||||||
related_name="unconfirmed_confidants",
|
related_name="unconfirmed_confidants",
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
EMAIL_FIELD = "email"
|
EMAIL_FIELD = "email"
|
||||||
USERNAME_FIELD = "email"
|
USERNAME_FIELD = "username"
|
||||||
REQUIRED_FIELDS = []
|
REQUIRED_FIELDS = []
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
11
userausfall/rest_api/permissions.py
Normal file
11
userausfall/rest_api/permissions.py
Normal file
|
@ -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
|
|
@ -13,9 +13,19 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ("pk", "email", "username", "confidant_email")
|
fields = ("pk", "email", "username", "confidant_email")
|
||||||
|
read_only_fields = ("email",)
|
||||||
|
|
||||||
def update(self, instance: User, validated_data):
|
def update(self, instance: User, validated_data):
|
||||||
confidant_email = validated_data.pop("confidant_email")
|
confidant_email = validated_data.pop("confidant_email")
|
||||||
confidant, _ = User.objects.get_or_create(email=confidant_email)
|
confidant, _ = User.objects.get_or_create(email=confidant_email)
|
||||||
instance.confidant_unconfirmed = confidant
|
instance.confidant_unconfirmed = confidant
|
||||||
return super().update(instance, validated_data)
|
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)
|
||||||
|
|
|
@ -5,7 +5,8 @@ from rest_framework.response import Response
|
||||||
from djeveric import ConfirmationView
|
from djeveric import ConfirmationView
|
||||||
from userausfall.models import User, MissingUserAttribute, PasswordMismatch
|
from userausfall.models import User, MissingUserAttribute, PasswordMismatch
|
||||||
from userausfall.confirmations import ConfidantConfirmation
|
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):
|
class ConfidantConfirmationView(ConfirmationView):
|
||||||
|
@ -13,8 +14,8 @@ class ConfidantConfirmationView(ConfirmationView):
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(viewsets.ModelViewSet):
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
|
permission_classes = [UserPermission]
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
|
||||||
|
|
||||||
@action(detail=True, methods=["post"])
|
@action(detail=True, methods=["post"])
|
||||||
def activate(self, request, pk=None):
|
def activate(self, request, pk=None):
|
||||||
|
@ -29,3 +30,7 @@ class UserViewSet(viewsets.ModelViewSet):
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
else:
|
else:
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action == "create":
|
||||||
|
return CreateUserSerializer
|
||||||
|
|
Reference in a new issue