Skip to content

Commit

Permalink
42 show username on post with more empasis (#56)
Browse files Browse the repository at this point in the history
* remove user mail and requirement for post title

* show logged in user in navbar
  • Loading branch information
ylked authored Apr 9, 2024
1 parent 11bef6e commit e7eae26
Show file tree
Hide file tree
Showing 19 changed files with 207 additions and 119 deletions.
17 changes: 17 additions & 0 deletions api/neuronaApp/migrations/0012_remove_user_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.0.2 on 2024-04-09 15:02

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('neuronaApp', '0011_alter_posts_space_alter_posts_tag'),
]

operations = [
migrations.RemoveField(
model_name='user',
name='email',
),
]
1 change: 0 additions & 1 deletion api/neuronaApp/models/user_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def generate_and_get(self, challenge):

class User(models.Model):
username = models.CharField(max_length=100, unique=True)
email = models.EmailField(max_length=100, unique=True)
display_name = models.CharField(max_length=100, blank=True)
about = models.TextField(max_length=2000, blank=True)
image_url = models.URLField(max_length=200, null=True)
Expand Down
23 changes: 22 additions & 1 deletion api/neuronaApp/serializers/authentication_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

USERNAME_MIN_LENGTH = 3
USERNAME_MAX_LENGTH = 15
DISPLAY_NAME_MIN_LENGTH = 2
DISPLAY_NAME_MAX_LENGTH = 50

USERNAME_REGEX = r"^[a-zA-Z][a-zA-Z0-9_]{" + str(USERNAME_MIN_LENGTH) + r"," + str(USERNAME_MAX_LENGTH) + r"}$"
EMAIL_REGEX = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"


class ChallengeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Challenges
Expand Down Expand Up @@ -40,6 +43,7 @@ def validate_username(self, value):
def get_error_message(self):
return self.errors["username"][0]


class EmailSerializer(serializers.Serializer):
email = serializers.EmailField(max_length=100)

Expand All @@ -56,6 +60,23 @@ def get_error_message(self):
return self.errors["email"][0]


class DisplayNameSerializer(serializers.Serializer):
display_name = serializers.CharField(max_length=50)

def validate_display_name(self, value):
if len(value) > DISPLAY_NAME_MAX_LENGTH:
raise serializers.ValidationError(f"Display name must be at most {DISPLAY_NAME_MAX_LENGTH} characters long")

if len(value) < DISPLAY_NAME_MIN_LENGTH:
raise serializers.ValidationError(
f"Display name must be at least {DISPLAY_NAME_MIN_LENGTH} characters long")

return value

def get_error_message(self):
return self.errors["display_name"][0]


class UsernameOrEmailSerializer(serializers.Serializer):
username_or_email = serializers.CharField(max_length=100)

Expand Down Expand Up @@ -100,4 +121,4 @@ class Meta:
fields = [
"key",
"expires_at"
]
]
2 changes: 0 additions & 2 deletions api/neuronaApp/serializers/posts_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ class Meta:
model = Posts
fields = [
"user",
"title",
"content",
"space",
"tag",
Expand All @@ -24,7 +23,6 @@ class Meta:
"id",
"user",
"votes_and_comments",
"title",
"created_at",
"content",
"space",
Expand Down
14 changes: 12 additions & 2 deletions api/neuronaApp/serializers/users_serializer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import urllib.parse

from rest_framework import serializers

from neuronaApp.models import User

DEFAULT_AVATAR_BASE_URL = "https://avatar.iran.liara.run/username?username="

class UserSerializer(serializers.ModelSerializer):
class Meta:
Expand All @@ -10,6 +13,13 @@ class Meta:
"id",
"username",
"display_name",
"email",
"image_url",
]
]

def to_representation(self, instance):
data = super().to_representation(instance)

if data["image_url"] is None:
display_name_encoded = urllib.parse.quote_plus(data["display_name"])
data["image_url"] = DEFAULT_AVATAR_BASE_URL + display_name_encoded
return data
1 change: 1 addition & 0 deletions api/neuronaApp/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
router.register(r'passkey-options', views.PasskeyChallengeView, basename='passkey-options')
router.register(r'posts', PostsViewSet)
router.register(r'comments', CommentsViewSet)
router.register(r'profile', views.Profile, basename='profile')

urlpatterns = [
path("register/", views.RegisterView.as_view(), name="register"),
Expand Down
26 changes: 14 additions & 12 deletions api/neuronaApp/views/authentication_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from neuronaApp.models import Challenges, User, PublicKeys, ApiKeys
from neuronaApp.serializers.authentication_serializer import ChallengeSerializer, ChallengeIdSerializer, \
UsernameSerializer, \
EmailSerializer, UsernameOrEmailSerializer, ApiKeySerializer
EmailSerializer, UsernameOrEmailSerializer, ApiKeySerializer, DisplayNameSerializer

import webauthn
import logging
Expand All @@ -28,24 +28,24 @@ class PasskeyChallengeView(viewsets.ViewSet):
@action(detail=False, methods=["post"])
def register(self, request, **kwargs):
username_serializer = UsernameSerializer(data=request.data)
email_serializer = EmailSerializer(data=request.data)
display_name_serializer = DisplayNameSerializer(data=request.data)

if not username_serializer.is_valid():
return Response({
"error": username_serializer.errors,
"message": username_serializer.get_error_message()
}, status=400)

if not email_serializer.is_valid():
if not display_name_serializer.is_valid():
return Response({
"error": email_serializer.errors,
"message": email_serializer.get_error_message()
"error": display_name_serializer.errors,
"message": display_name_serializer.get_error_message()
}, status=400)

options = webauthn.generate_registration_options(
rp_name="Neurona",
rp_id=getattr(settings, "PASSKEY_RP_ID", "localhost"),
user_name=email_serializer.validated_data["email"],
user_name=username_serializer.validated_data["username"],
)

challenge = Challenges().generate_and_get(options.challenge)
Expand Down Expand Up @@ -97,8 +97,8 @@ def post(self, request, **kwargs):
registration_data = request.data.get("data", {})

username_serializer = UsernameSerializer(data=registration_data)
email_serializer = EmailSerializer(data=registration_data)
challenge_id_serializer = ChallengeIdSerializer(data=registration_data)
display_name_serializer = DisplayNameSerializer(data=registration_data)

# if the data is not valid, return the errors
if not username_serializer.is_valid():
Expand All @@ -107,10 +107,10 @@ def post(self, request, **kwargs):
"message": username_serializer.get_error_message()
}, status=400)

if not email_serializer.is_valid():
if not display_name_serializer.is_valid():
return Response({
"error": email_serializer.errors,
"message": email_serializer.get_error_message()
"error": display_name_serializer.errors,
"message": display_name_serializer.get_error_message()
}, status=400)

if not challenge_id_serializer.is_valid():
Expand Down Expand Up @@ -141,9 +141,9 @@ def post(self, request, **kwargs):

# if the registration response is valid, save the user and the public key in the database
username = username_serializer.validated_data["username"]
email = email_serializer.validated_data["email"]
display_name = display_name_serializer.validated_data["display_name"]

User(username=username, email=email).save()
User(username=username, display_name=display_name).save()
user = User.objects.get(username=username)

PublicKeys(
Expand Down Expand Up @@ -177,6 +177,7 @@ def post(self, request, **kwargs):
}, status=400)

user = user_serializer.get_user()
logger.info(f"found user = {user.display_name} @ {user.username}")

if not challenge_id_serializer.is_valid():
return Response({
Expand Down Expand Up @@ -212,6 +213,7 @@ def post(self, request, **kwargs):
logger.error(f"authentication_verification: {e}")
return Response({"message": "Login failed. Please try again."}, status=400)


class LogoutView(APIView):
"""
This view is responsible for logging out the user.
Expand Down
1 change: 1 addition & 0 deletions api/neuronaApp/views/posts_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class PostsViewSet(viewsets.ViewSet):
def list(self, request, *args, **kwargs):
queryset = Posts.objects.all()
serializer = PostsComplexSerializer(queryset, context=request.user, many=True)

return Response(serializer.data)

def create(self, request, *args, **kwargs):
Expand Down
10 changes: 9 additions & 1 deletion api/neuronaApp/views/user_profile_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from rest_framework.decorators import action
from rest_framework.response import Response

from neuronaApp.serializers import UsernameSerializer, EmailSerializer
from neuronaApp.serializers import UsernameSerializer, EmailSerializer, UserSerializer
from neuronaApp.token_authentication import TokenAuthentication


class Validity(viewsets.ViewSet):
Expand All @@ -25,3 +26,10 @@ def email(self, request, *args, **kwargs):
return Response({"message": serializer.get_error_message()}, status=400)

return Response(status=200)

class Profile(viewsets.ViewSet):
authentication_classes = (TokenAuthentication,)

def list(self, request, *args, **kwargs):
user = request.user
return Response(UserSerializer(user).data)
17 changes: 17 additions & 0 deletions api/neuronaLogs/migrations/0003_remove_userlogs_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.0.2 on 2024-04-09 15:02

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('neuronaLogs', '0002_alter_commentlogs_comment_id_alter_postlogs_admin_id_and_more'),
]

operations = [
migrations.RemoveField(
model_name='userlogs',
name='email',
),
]
4 changes: 0 additions & 4 deletions api/neuronaLogs/models/logs_managing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
def save_user_log(instance, action):
UserLogs(
user_id=instance.pk,
email=instance.email,
display_name=instance.display_name,
username=instance.username,
about=instance.about,
Expand All @@ -28,9 +27,6 @@ def user_changes(sender, instance, **kwargs):
if instance.pk:
original = sender.objects.get(pk=instance.pk)

if original.email != instance.email:
save_user_log(instance, UserAction.CHANGED_EMAIL)

if original.display_name != instance.display_name:
save_user_log(instance, UserAction.CHANGED_DISPLAY_NAME)

Expand Down
1 change: 0 additions & 1 deletion api/neuronaLogs/models/logs_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class UserAction(Enum):

class UserLogs(models.Model):
user_id = models.IntegerField(null=True)
email = models.EmailField(max_length=100)
display_name = models.CharField(max_length=100)
username = models.CharField(max_length=100)
about = models.TextField(max_length=2000, blank=True)
Expand Down
22 changes: 11 additions & 11 deletions frontend/src/Authentication/Passkey.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import axios from "axios";
import routes from "@/api/routes";

async function fetchRegisterOptions(username, email) {
async function fetchRegisterOptions(username, name) {
console.log("route", routes.authentication.register_options);
return axios.post(routes.authentication.register_options, {
"username": username,
"email": email,
"display_name": name,
});
}

Expand Down Expand Up @@ -34,8 +34,8 @@ function BufToBase64url(buf) {
.replace(/=/g, '');
}

async function getRegisterCredentialOptions(username, email) {
const options_str = await fetchRegisterOptions(username, email);
async function getRegisterCredentialOptions(username, name) {
const options_str = await fetchRegisterOptions(username, name);
const options = JSON.parse(options_str.data.options);
const challenge_id = options_str.data.id;

Expand All @@ -61,8 +61,8 @@ async function getLoginCredentialOptions(username_or_email) {
}
}

async function createPublicKeyCredential(username, email) {
const credentialOptions = await getRegisterCredentialOptions(username, email);
async function createPublicKeyCredential(username, name) {
const credentialOptions = await getRegisterCredentialOptions(username, name);
const credentials_ = await navigator.credentials.create({publicKey: credentialOptions.options});

const credentials = {
Expand Down Expand Up @@ -117,12 +117,12 @@ async function requestLogin(username_or_email, credentials, challenge_id) {
return await axios.post(routes.authentication.login, data);
}

async function requestRegister(username, email, credentials, challenge_id) {
async function requestRegister(username, name, credentials, challenge_id) {
const data = {
credentials: credentials,
data: {
username: username,
email: email,
display_name: name,
challenge_id: challenge_id
}
}
Expand All @@ -136,9 +136,9 @@ async function login(username_or_email) {
return response;
}

async function register(username, email){
const credentials = await createPublicKeyCredential(username, email);
return await requestRegister(username, email, credentials.credentials, credentials.challenge_id);
async function register(username, name){
const credentials = await createPublicKeyCredential(username, name);
return await requestRegister(username, name, credentials.credentials, credentials.challenge_id);
}


Expand Down
3 changes: 3 additions & 0 deletions frontend/src/api/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const routes = {
downvote: (id) => `${BASE_URL}/posts/${id}/downvote/`,
unvote: (id) => `${BASE_URL}/posts/${id}/unvote/`,
},
profile: {
show: `${BASE_URL}/profile/`,
}
}

export default routes;
Loading

0 comments on commit e7eae26

Please sign in to comment.