diff --git a/ttadmin/users/forms.py b/ttadmin/users/forms.py new file mode 100644 index 0000000..be11fe3 --- /dev/null +++ b/ttadmin/users/forms.py @@ -0,0 +1,98 @@ +from random import shuffle +import re + +from django.core.exceptions import ValidationError +from django.forms import Form, CharField, EmailField, Textarea, ChoiceField, BooleanField +import sshpubkeys as ssh + +from .models import SSH_TYPE_CHOICES + +USERNAME_RE = re.compile(r'[a-z][a-z0-9_]+') +USERNAME_MIN_LENGTH = 4 +DISPLAY_NAME_RE = re.compile(r"[a-zA-Z0-9_\-']+") +DISPLAY_MIN_LENGTH = 2 + +# >_> +CAPTCHA_CHOICES = [('two', 'zorp borp'), + ('three', 'quop bop'), + ('four', 'NO, I AM NOT A ROBOT'), + ('five', 'crackle zop'), + ('six', '*rusty screech*'), + ('seven', 'mother, give me legs')] +shuffle(CAPTCHA_CHOICES) +CAPTCHA_CHOICES.insert(0, ('one', 'beep boop'),) +NOT_A_ROBOT = 'four' +# <_< + +def validate_username(username): + if len(username) < USERNAME_MIN_LENGTH: + raise ValidationError('Username too short.') + if not USERNAME_RE.match(username): + raise ValidationError('Username must be all lowercase, start with a letter, and only use the _ special charcter') + +def validate_displayname(display_name): + if len(display_name) < DISPLAY_MIN_LENGTH: + raise ValidationError('Display name too short.') + if not DISPLAY_NAME_RE.match(display_name): + raise ValidationError("Valid characters: a-z, A-Z, 0-9, -, _, and '.") + +def validate_pubkey(pubkey): + key = ssh.SSHKey(pubkey, strict_mode=False, skip_option_parsing=True) + try: + key.parse() + except ssh.InvalidKeyException as e: + raise ValidationError('Could not validate key: {}'.format(e)) + except NotImplementedError as e: + raise ValidationError('Invalid key type') + except Exception as e: + raise ValidationError('unknown error: {}'.format(e)) + +def validate_captcha(captcha): + if captcha != NOT_A_ROBOT: + raise ValidationError('Are you sure you are not a robot?') + + +class TownieForm(Form): + username = CharField( + validators=(validate_username,), + help_text='lowercase and no spaces. underscore ok', + label='username') + email = EmailField( + help_text='only used to message you about your account and nothing else.', + label='e-mail') + displayname = CharField( + validators=(validate_displayname,), + help_text='100% optional. pseudonyms welcome.', + label='display name', + required=False) + reasons = CharField( + widget=Textarea, + required=False, + label='what interests you about tilde.town?', + help_text='This is a totally optional place for you to tell us what excites you about getting a ~ account. This is mainly just so we can all feel warm fuzzies.') + captcha = ChoiceField( + choices=CAPTCHA_CHOICES, + label='are you a robot?', + help_text='pick the response that indicates whether or not you are a robot.', + validators=(validate_captcha,) + ) + pubkey = CharField( + widget=Textarea, + validators=(validate_pubkey,), + label='SSH public key', + help_text='if this is not a thing you are familiar with, that\'s okay! check out our guide to learn how to get one of these.') + pubkey_type = ChoiceField( + choices=SSH_TYPE_CHOICES, + label='SSH public key type', + help_text="unless you know what you're doing you can leave this be.") + aup = BooleanField( + label='i super agree to our acceptable use policy', + help_text='please read our code of conduct and click this box if you agree.') + + def clean(self): + result = super().clean() + if self.errors: + raise ValidationError('oops, looks like there were some problems below.') + return result + + diff --git a/ttadmin/users/models.py b/ttadmin/users/models.py index 9d45224..20d234e 100644 --- a/ttadmin/users/models.py +++ b/ttadmin/users/models.py @@ -1,19 +1,24 @@ +import re + from django.db.models.signals import post_save from django.dispatch import receiver from django.contrib.auth.models import User from django.db.models import TextField, BooleanField, CharField + SSH_TYPE_CHOICES = ( ('ssh-rsa', 'ssh-rsa',), ('ssh-dss', 'ssh-dss',), ) + class Townie(User): """Both an almost normal Django User as well as an abstraction over a system user.""" pubkey = TextField(blank=False, null=False) shell = CharField(max_length=50, default="/bin/bash") reviewed = BooleanField(default=False) + reasons = TextField(blank=True, null=False, default='') displayname = CharField(max_length=100, blank=False, null=False) pubkey_type = CharField(max_length=15, blank=False, diff --git a/ttadmin/users/urls.py b/ttadmin/users/urls.py index 9e4f7ba..b11c6d3 100644 --- a/ttadmin/users/urls.py +++ b/ttadmin/users/urls.py @@ -1,9 +1,9 @@ from django.conf.urls import url -from .views import UserSignupView +from .views import SignupView, ThanksView app_name = 'users' urlpatterns = [ - url(r'^signup/?$', UserSignupView.as_view(), name='signup'), - + url(r'^signup/?$', SignupView.as_view(), name='signup'), + url(r'^thanks/?$', ThanksView.as_view(), name='thanks'), ] diff --git a/ttadmin/users/views.py b/ttadmin/users/views.py index 7869981..52f657a 100644 --- a/ttadmin/users/views.py +++ b/ttadmin/users/views.py @@ -1,48 +1,37 @@ +import re + +from django.core.exceptions import ValidationError +from django.forms import Form, CharField, EmailField, Textarea, ChoiceField, BooleanField from django.http import HttpResponse +from django.shortcuts import redirect from django.views.generic import TemplateView +from django.views.generic.edit import FormView +from django.urls import reverse -from .models import Townie, SSH_TYPE_CHOICES +from .forms import TownieForm +from .models import Townie -# TODO validation functions for final request validation and live js validation -# I refuse to duplicate the logic for validation on the front-end and am going -# to accept round-trip validation costs with long-term caching. +class SignupView(FormView): + form_class = TownieForm + template_name = 'users/signup.html' + # TODO reverse + success_url = '/thanks' -class UserSignupView(TemplateView): - template_name = 'ttadmin/signup.html' + def form_valid(self, form): - def get_context_data(self): - ctx = super().get_context_data() - ctx['ssh_type_choices'] = SSH_TYPE_CHOICES - return ctx + #t = Townie( + # username=username, + # displayname=displayname, + # pubkey=pubkey, + # email=email, + #) - def post(self, request): - print(request.POST) - # TODO validate - username = request.POST.get('username') + #t.set_unusable_password() + #t.save() - displayname = request.POST.get('displayname') - - if displayname is None: - displayname = username - else: - # TODO validate - pass - - # TODO validate - pubkey = request.POST.get('pubkey') - - # TODO validate - email = request.POST.get('email') - - t = Townie( - username=username, - displayname=displayname, - pubkey=pubkey, - email=email, - ) - - t.set_unusable_password() - t.save() + return super().form_valid(form) - return HttpResponse('LOLOLOLOL') +# TODO add template for this once i've fixed template directories +class ThanksView(TemplateView): + template_name = 'users/thanks.html'