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,
|
||||
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<void> {
|
||||
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<void> {
|
||||
await api_request("POST", "users", 201, {
|
||||
email: this.email,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,38 +1,45 @@
|
|||
<template>
|
||||
<form class="box" @submit.prevent="doAction">
|
||||
<b-field label="E-Mail-Adresse">
|
||||
<b-input type="email" v-model="user.email" />
|
||||
<div v-if="errorMessage" class="notification is-danger">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<b-field label="Benutzername">
|
||||
<b-input type="text" v-model="user.username" />
|
||||
</b-field>
|
||||
<b-field label="Kennwort">
|
||||
<b-input type="password" v-model="user.password" />
|
||||
</b-field>
|
||||
|
||||
<div v-if="false" class="notification is-danger">
|
||||
Email or password was wrong.
|
||||
</div>
|
||||
<b-field v-if="mode === 'signup'" label="Kennwort wiederholen">
|
||||
<b-input type="password" v-model="password2" />
|
||||
</b-field>
|
||||
|
||||
<div class="buttons">
|
||||
<b-button
|
||||
native-type="submit"
|
||||
type="is-primary"
|
||||
style="margin-left: auto; margin-right: 0; order: 10"
|
||||
>
|
||||
<b-button native-type="submit" type="is-primary">
|
||||
{{ mode === "login" ? "Anmelden" : "Konto anlegen" }}
|
||||
</b-button>
|
||||
<router-link v-if="mode === 'login'" to="/signup"
|
||||
>Konto anlegen</router-link
|
||||
>
|
||||
<router-link v-else to="/login">Anmelden</router-link>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<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 { Route } from "vue-router";
|
||||
import { NotifyMixin } from "@/mixins";
|
||||
|
||||
@Component
|
||||
export default class LoginForm extends Vue {
|
||||
export default class LoginForm extends mixins(NotifyMixin) {
|
||||
@Prop() private user!: User;
|
||||
|
||||
private mode: "login" | "signup" = "login";
|
||||
private password2 = "";
|
||||
private errorMessage = "";
|
||||
|
||||
public created(): void {
|
||||
if (this.$route.name === "signup") this.mode = "signup";
|
||||
|
@ -48,19 +55,20 @@ export default class LoginForm extends Vue {
|
|||
}
|
||||
|
||||
private async doAction() {
|
||||
if (this.mode === "login") {
|
||||
await this.user.login();
|
||||
if (this.$route.name !== "home") this.$router.push({ name: "home" });
|
||||
} else {
|
||||
await this.user.signup();
|
||||
this.$router.push({ name: "login" });
|
||||
this.$buefy.toast.open({
|
||||
message:
|
||||
"Eine E-Mail zur Bestätigung deiner E-Mail-Adresse wurde versendet.",
|
||||
type: "is-success",
|
||||
});
|
||||
try {
|
||||
if (this.mode === "login") {
|
||||
await this.user.login();
|
||||
if (this.$route.name !== "home") this.$router.push({ name: "home" });
|
||||
} else {
|
||||
await this.user.signup();
|
||||
this.$router.push({ name: "login" });
|
||||
this.showSuccess("Du kannst dich nun anmelden.");
|
||||
}
|
||||
} catch {
|
||||
this.errorMessage = "Fehler";
|
||||
}
|
||||
// TODO: error handling, show confirmation page
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
|
|
|
@ -2,35 +2,12 @@
|
|||
<b-navbar>
|
||||
<template #brand>
|
||||
<b-navbar-item tag="router-link" :to="{ path: '/' }">
|
||||
<img
|
||||
src="/img/logo_text.png"
|
||||
alt="Lightweight UI components for Vue.js based on Bulma"
|
||||
/>
|
||||
<img src="/img/logo_text.png" alt="userausfall" />
|
||||
</b-navbar-item>
|
||||
</template>
|
||||
<template #start>
|
||||
<!--
|
||||
<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 #start> </template>
|
||||
|
||||
<template #end>
|
||||
<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>
|
||||
<template #end> </template>
|
||||
</b-navbar>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export default class Home extends mixins(NotifyMixin) {
|
|||
public async created(): Promise<void> {
|
||||
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" });
|
||||
}
|
||||
}
|
||||
|
|
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):
|
||||
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:
|
||||
|
|
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:
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in a new issue