From d77dcf62b4005a0f36ef2fa7ba6d3651d2ef38d7 Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Sat, 5 Nov 2022 14:17:27 -0600 Subject: Initial commit (users and statuses) --- users/views/__init__.py | 1 + users/views/auth.py | 15 ++++++ users/views/identity.py | 132 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 users/views/__init__.py create mode 100644 users/views/auth.py create mode 100644 users/views/identity.py (limited to 'users/views') diff --git a/users/views/__init__.py b/users/views/__init__.py new file mode 100644 index 0000000..1e88b4e --- /dev/null +++ b/users/views/__init__.py @@ -0,0 +1 @@ +from .auth import * # noqa diff --git a/users/views/auth.py b/users/views/auth.py new file mode 100644 index 0000000..f9e6ce1 --- /dev/null +++ b/users/views/auth.py @@ -0,0 +1,15 @@ +from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.views import LoginView, LogoutView + +from core.forms import FormHelper + + +class Login(LoginView): + class form_class(AuthenticationForm): + helper = FormHelper(submit_text="Login") + + template_name = "auth/login.html" + + +class Logout(LogoutView): + pass diff --git a/users/views/identity.py b/users/views/identity.py new file mode 100644 index 0000000..63b7fb8 --- /dev/null +++ b/users/views/identity.py @@ -0,0 +1,132 @@ +from django import forms +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.http import Http404, JsonResponse +from django.shortcuts import redirect +from django.utils.decorators import method_decorator +from django.views.generic import FormView, TemplateView, View + +from core.forms import FormHelper +from users.models import Identity +from users.shortcuts import by_handle_or_404 + + +class ViewIdentity(TemplateView): + + template_name = "identity/view.html" + + def get_context_data(self, handle): + identity = by_handle_or_404(self.request, handle, local=False) + statuses = identity.statuses.all()[:100] + return { + "identity": identity, + "statuses": statuses, + } + + +@method_decorator(login_required, name="dispatch") +class SelectIdentity(TemplateView): + + template_name = "identity/select.html" + + def get_context_data(self): + return { + "identities": Identity.objects.filter(users__pk=self.request.user.pk), + } + + +@method_decorator(login_required, name="dispatch") +class CreateIdentity(FormView): + + template_name = "identity/create.html" + + class form_class(forms.Form): + handle = forms.CharField() + name = forms.CharField() + + helper = FormHelper(submit_text="Create") + + def clean_handle(self): + # Remove any leading @ + value = self.cleaned_data["handle"].lstrip("@") + # Don't allow custom domains here quite yet + if "@" in value: + raise forms.ValidationError( + "You are not allowed an @ sign in your handle" + ) + # Ensure there is a domain on the end + if "@" not in value: + value += "@" + settings.DEFAULT_DOMAIN + # Check for existing users + if Identity.objects.filter(handle=value).exists(): + raise forms.ValidationError("This handle is already taken") + return value + + def form_valid(self, form): + new_identity = Identity.objects.create( + handle=form.cleaned_data["handle"], + name=form.cleaned_data["name"], + local=True, + ) + new_identity.users.add(self.request.user) + new_identity.generate_keypair() + return redirect(new_identity.urls.view) + + +class Actor(View): + """ + Returns the AP Actor object + """ + + def get(self, request, handle): + identity = by_handle_or_404(self.request, handle) + return JsonResponse( + { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + ], + "id": f"https://{settings.DEFAULT_DOMAIN}{identity.urls.actor}", + "type": "Person", + "preferredUsername": "alice", + "inbox": f"https://{settings.DEFAULT_DOMAIN}{identity.urls.inbox}", + "publicKey": { + "id": f"https://{settings.DEFAULT_DOMAIN}{identity.urls.actor}#main-key", + "owner": f"https://{settings.DEFAULT_DOMAIN}{identity.urls.actor}", + "publicKeyPem": identity.public_key, + }, + } + ) + + +class Webfinger(View): + """ + Services webfinger requests + """ + + def get(self, request): + resource = request.GET.get("resource") + if not resource.startswith("acct:"): + raise Http404("Not an account resource") + handle = resource[5:] + identity = by_handle_or_404(request, handle) + return JsonResponse( + { + "subject": f"acct:{identity.handle}", + "aliases": [ + f"https://{settings.DEFAULT_DOMAIN}/@{identity.short_handle}", + ], + "links": [ + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": f"https://{settings.DEFAULT_DOMAIN}{identity.urls.view}", + }, + { + "rel": "self", + "type": "application/activity+json", + "href": f"https://{settings.DEFAULT_DOMAIN}{identity.urls.actor}", + }, + ], + } + ) -- cgit v1.2.3