Compare commits

..

32 Commits

Author SHA1 Message Date
vilmibm afc5d30ed8 disable help and guestbook; minor fixes 2021-03-08 01:14:36 +00:00
vilmibm 8d04787277 fix validation REs, fix key thing 2021-03-07 21:52:26 +00:00
vilmibm be2975ad62 upgrade django 2020-06-03 02:03:51 +00:00
vilmibm 0f078f86fd upgrade Django 2019-12-18 14:07:40 +00:00
vilmibm 90dca0274a return to generating gifts 2019-12-16 22:25:20 +00:00
vilmibm 158bdcff5f guestbook spam stuff 2019-12-16 22:25:03 +00:00
vilmibm 77d728a153 bootleg anti-spam measure 2019-12-16 22:22:01 +00:00
vilmibm 2385446e74 increase timeout to help with bulk add of users 2019-12-16 22:21:44 +00:00
vilmibm 5220d9bfad atomic user creation/notification 2019-10-11 22:53:39 +00:00
vilmibm 4e3977edd2 enum sigh 2019-08-21 15:39:49 +00:00
vilmibm 2354695401 fix signups toggle 2019-08-20 21:39:38 +00:00
vilmibm de07b42082 gross hack 2019-07-20 01:25:52 +00:00
Nate Smith 3d12a5bfd4
Merge pull request #48 from tildetown/admin-improvements
Admin improvements
2019-07-19 20:09:52 -05:00
Nate Smith 32e640b875 support assigning of help tickets 2019-07-18 17:25:28 -05:00
Nate Smith 02a50e1d11 working support for tickete notes 2019-07-18 17:05:08 -05:00
Nate Smith dfd843064d messy wip for Note 2019-07-15 21:44:13 -05:00
Nate Smith 31adb3cc3a rejected users do not occupy a username 2019-07-15 21:15:33 -05:00
Nate Smith e59bd893fd actually remove reviewed field plus required migrations 2019-07-15 21:10:48 -05:00
Nate Smith a2af7c5c96 restore this field to make migrations easier 2019-07-15 20:48:05 -05:00
Nate Smith 9373589c52 code for adding a state field to users 2019-07-15 20:38:11 -05:00
Nate Smith 2c71afebc2 mark readonly user signup fields 2019-07-10 11:27:29 -05:00
Nate Smith e9d1fea15e add notes field to Townie 2019-07-10 11:27:20 -05:00
vilmibm ffeb999290 Merge branch 'master' of github.com:tildetown/tildetown-admin 2019-07-09 17:28:53 +00:00
vilmibm 0881982614 update some reqs for py3 compat 2019-07-09 17:28:27 +00:00
Nate Smith 4eb72edf42
Update README.md 2019-07-05 13:52:05 -05:00
vilmibm 3cb39206c1 oops 2019-07-03 21:18:08 +00:00
Nate Smith 1757ec85c8
Merge pull request #37 from tildetown/zoho
migrate to zoho
2019-07-03 16:11:22 -05:00
vilmibm 627ccf6e55 no longer need requests 2019-07-03 21:02:50 +00:00
vilmibm b33c244d7b allow referral to be blank 2019-07-03 20:41:50 +00:00
vilmibm f56e12a053 switch away from mailgun and to external SMTP 2019-07-03 20:41:40 +00:00
vilmibm a8917a05c3 support for pausing signups 2019-07-02 04:46:29 +00:00
Nate Smith 57e18fb337
Merge pull request #35 from tildetown/new-signup-questions
add some new signup questions
2019-04-15 15:50:04 -10:00
24 changed files with 363 additions and 80 deletions

View File

@ -10,7 +10,7 @@ _Being an adminstrative tool written in Django for <https://tilde.town>_.
* [x] Helpdesk * [x] Helpdesk
* [x] User account management * [x] User account management
* [ ] Start/stop services * [ ] Start/stop services
* [ ] Cost reporting from AWS * [ ] Cost reporting from DO
* [ ] Status monitoring * [ ] Status monitoring
## Requirements ## Requirements

View File

@ -9,4 +9,4 @@ VENV=/town/venvs/ttadmin
source $VENV/bin/activate source $VENV/bin/activate
export DJANGO_SETTINGS_MODULE=settings_live export DJANGO_SETTINGS_MODULE=settings_live
cd $APP_ROOT cd $APP_ROOT
gunicorn -b0.0.0.0:$PORT ttadmin.wsgi gunicorn -t120 -b0.0.0.0:$PORT ttadmin.wsgi

View File

@ -15,12 +15,11 @@ setup(
'License :: OSI Approved :: Affero GNU General Public License v3 (AGPLv3)', 'License :: OSI Approved :: Affero GNU General Public License v3 (AGPLv3)',
], ],
packages=['ttadmin'], packages=['ttadmin'],
install_requires = ['Django==1.10.2', install_requires = ['Django==1.11.29',
'sshpubkeys==2.2.0', 'sshpubkeys==2.2.0',
'psycopg2==2.7.6.1', 'psycopg2-binary==2.8.5',
'requests==2.12.5',
'gunicorn==19.6.0', 'gunicorn==19.6.0',
'Mastodon.py==1.1.1', 'Mastodon.py==1.4.5',
'tweepy==3.5.0'], 'tweepy==3.7.0'],
include_package_data = True, include_package_data = True,
) )

View File

@ -1,35 +1,26 @@
import logging import logging
from smtplib import SMTP_SSL, SMTPException
import requests from email.message import EmailMessage
from django.conf import settings from django.conf import settings
logger = logging.getLogger() logger = logging.getLogger()
ADMIN_NAME='vilmibm'
EXTERNAL_FROM='root@tilde.town'
REPLY_TO='tildetown@protonmail.ch'
def send_email(to, body, subject='a message from tilde.town'): def send_email(to, body, subject='a message from tilde.town'):
"""Sends an email using mailgun. Logs on failure.""" """Sends an email using external SMTP. Logs on failure."""
response = requests.post( em = EmailMessage()
settings.MAILGUN_URL, em['Subject'] = subject
auth=('api', settings.MAILGUN_KEY), em['From'] = 'root@tilde.town'
data={ em['To'] = to
'from': EXTERNAL_FROM, em.set_content(body)
'h:Reply-To': REPLY_TO, try:
'to': to, with SMTP_SSL(port=settings.SMTP_PORT, host=settings.SMTP_HOST) as smtp:
'subject': subject, smtp.login('root@tilde.town', settings.SMTP_PASSWORD)
'text': body smtp.send_message(em)
} smtp.quit()
) except SMTPException as e:
logger.error(f'failed to send email "{subject}" to {to}: {e}')
return False
success = response.status_code == 200 return True
if not success:
logger.error('{}: failed to send email "{}" to {}'.format(
response.status_code,
subject,
to))
return success

View File

@ -20,15 +20,15 @@
</head> </head>
<body> <body>
<h1>tilde.town guestbook</h1> <h1>tilde.town guestbook</h1>
<p><em>don't try to post urls. it won't work.</em></p>
<marquee>~*~*~*~*say hello*~*~*~*~</marquee> <marquee>~*~*~*~*say hello*~*~*~*~</marquee>
<!-- <form class="tilde" action="{% url 'guestbook:guestbook' %}" method="post"> <form class="tilde" action="{% url 'guestbook:guestbook' %}" method="post">
{% csrf_token %} {% csrf_token %}
<table> <table>
{{form.as_table}} {{form.as_table}}
</table> </table>
<input type="submit" value="sign" > <input type="submit" value="sign" >
</form> </form>
-->
{% for m in messages %} {% for m in messages %}
<p> <p>

View File

@ -1,3 +1,5 @@
import re
from django.shortcuts import redirect from django.shortcuts import redirect
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
@ -5,6 +7,8 @@ from django.views.generic.edit import FormView
from .forms import GuestbookForm from .forms import GuestbookForm
from .models import GuestbookMessage from .models import GuestbookMessage
SUSPICIOUS_RE = re.compile(r'https?://')
class GuestbookView(FormView): class GuestbookView(FormView):
form_class = GuestbookForm form_class = GuestbookForm
@ -17,5 +21,7 @@ class GuestbookView(FormView):
def form_valid(self, form): def form_valid(self, form):
del form.cleaned_data['captcha'] del form.cleaned_data['captcha']
if SUSPICIOUS_RE.search(form.cleaned_data['msg']) != None:
return redirect('guestbook:guestbook')
t = GuestbookMessage.objects.create(**form.cleaned_data) t = GuestbookMessage.objects.create(**form.cleaned_data)
return redirect('guestbook:guestbook') return redirect('guestbook:guestbook')

View File

@ -1,9 +1,44 @@
from django.contrib import admin from django.contrib import admin
from .models import Ticket from django.forms import ModelForm
from .models import Ticket, Note
class ImmutableNoteInline(admin.TabularInline):
model = Note
extra = 1
max_num = 0
fields = ('author', 'created', 'body')
readonly_fields = ('author', 'created', 'body')
can_delete = False
ordering = ('created',)
class NewNoteInline(admin.StackedInline):
model = Note
extra = 0
fields = ('body',)
def get_queryset(self, request):
queryset = super().get_queryset(request)
return queryset.none()
@admin.register(Ticket) @admin.register(Ticket)
class TicketAdmin(admin.ModelAdmin): class TicketAdmin(admin.ModelAdmin):
readonly_fields = ('submitted',) inlines = [ImmutableNoteInline, NewNoteInline]
list_display = ('submitted', 'issue_status', 'issue_type', 'name', 'email') readonly_fields = ('submitted', 'issue_type')
list_filter = ('issue_status', 'issue_type') list_display = ('submitted', 'issue_status', 'assigned', 'issue_type', 'name', 'email',)
fields = ('submitted', 'name', 'email', 'issue_status', 'issue_type', 'issue_text') list_filter = ('issue_status', 'issue_type', 'assigned')
fields = ('submitted', 'name', 'email', 'assigned', 'issue_status', 'issue_type', 'issue_text')
def save_related(self, request, form, formsets, change):
# THIS IS EXTREMELY BOOTLEG AND MAY BREAK IF MORE INLINES ARE ADDED TO THIS ADMIN.
for formset in formsets:
if len(formset.forms) == 1:
# It's probably the add new note form (i hope).
note_form = formset.forms[0]
note_form.instance.author = request.user
note_form.instance.save()
note_form.save(commit=False)
note_form.save_m2m()
return super().save_related(request, form, formsets, change)

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2019-07-16 02:43
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0015_remove_townie_reviewed'),
('help', '0004_ticket_submitted'),
]
operations = [
migrations.CreateModel(
name='Note',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('body', models.TextField()),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Townie')),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='help.Ticket')),
],
),
]

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2019-07-16 02:58
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('help', '0005_note'),
]
operations = [
migrations.AlterField(
model_name='note',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2019-07-18 22:21
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('help', '0006_auto_20190716_0258'),
]
operations = [
migrations.AddField(
model_name='ticket',
name='assigned',
field=models.ForeignKey(help_text='Assign this ticket to an admin or unassign it.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2019-07-18 22:23
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('help', '0007_ticket_assigned'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='assigned',
field=models.ForeignKey(blank=True, help_text='Assign this ticket to an admin or unassign it.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -1,4 +1,5 @@
from django.db.models import Model, TextField, EmailField, CharField, DateTimeField from django.contrib.auth.models import User
from django.db.models import Model, TextField, EmailField, CharField, DateTimeField, ForeignKey
ISSUE_TYPE_CHOICES = ( ISSUE_TYPE_CHOICES = (
('logging_in', 'help logging in'), ('logging_in', 'help logging in'),
@ -31,6 +32,17 @@ class Ticket(Model):
null=False, null=False,
max_length=50, max_length=50,
default=ISSUE_STATUS_CHOICES[0][0]) default=ISSUE_STATUS_CHOICES[0][0])
assigned = ForeignKey(User, blank=True, null=True, help_text="Assign this ticket to an admin or unassign it.")
def __str__(self): def __str__(self):
return '{} from {}'.format(self.issue_type, self.name) return '{} from {}'.format(self.issue_type, self.name)
class Note(Model):
created = DateTimeField(auto_now_add=True)
body = TextField(blank=False, null=False)
author = ForeignKey(User)
ticket = ForeignKey(Ticket)
def __str__(self):
return "admin note"

View File

@ -6,7 +6,7 @@ To run this For Real, you'll want to:
* set a different SECRET_KEY * set a different SECRET_KEY
* change the password for the database or delete the password and use ident * change the password for the database or delete the password and use ident
* change DEBUG to False * change DEBUG to False
* set mailgun api info * set smtp password
""" """
import os import os
@ -101,8 +101,9 @@ STATIC_URL = '/static/'
# Not used during local development, but used in staging+live environments # Not used during local development, but used in staging+live environments
STATIC_ROOT = 'static' STATIC_ROOT = 'static'
MAILGUN_URL = "OVERWRITE THIS" SMTP_PORT=465
MAILGUN_KEY = "OVERWRITE THIS" SMTP_HOST="smtp.zoho.com"
SMTP_PASSWORD="OVERWRITE THIS"
# Mastodon credentials # Mastodon credentials
MASTO_CLIENT_ID = "OVERWRITE THIS" MASTO_CLIENT_ID = "OVERWRITE THIS"

View File

@ -2,8 +2,8 @@ from django.conf.urls import url, include
from django.contrib import admin from django.contrib import admin
urlpatterns = [ urlpatterns = [
url(r'^help/', include('help.urls')), # url(r'^help/', include('help.urls')),
url(r'^users/', include('users.urls')), url(r'^users/', include('users.urls')),
url(r'^guestbook/', include('guestbook.urls')), # url(r'^guestbook/', include('guestbook.urls')),
url(r'^admin/', admin.site.urls), url(r'^admin/', admin.site.urls),
] ]

View File

@ -9,19 +9,27 @@ class PubkeyInline(admin.TabularInline):
model = Pubkey model = Pubkey
extra = 1 extra = 1
def bulk_review(madmin, req, qs): def bulk_accept(madmin, req, qs):
for townie in qs: for townie in qs:
townie.reviewed = True townie.state = Townie.ACCEPTED
townie.save() townie.save()
post_users_to_social(qs) post_users_to_social(qs)
bulk_review.short_description = 'mark selected townies as reviewed' bulk_accept.short_description = 'mark selected townies as accepted'
def bulk_reject(madmin, req, qs):
for townie in qs:
townie.state = Townie.REJECTED
townie.save()
bulk_reject.short_description = 'mark selected townies as rejected'
@admin.register(Townie) @admin.register(Townie)
class TownieAdmin(admin.ModelAdmin): class TownieAdmin(admin.ModelAdmin):
inlines = [PubkeyInline] inlines = [PubkeyInline]
list_display = ('username', 'reviewed', 'email') list_display = ('username', 'state', 'email')
ordering = ('reviewed',) readonly_fields = ('reasons', 'plans', 'socials')
ordering = ('state',)
exclude = ('first_name', 'last_name', 'password', 'groups', 'user_permissions', 'last_login', 'is_staff', 'is_active', 'is_superuser') exclude = ('first_name', 'last_name', 'password', 'groups', 'user_permissions', 'last_login', 'is_staff', 'is_active', 'is_superuser')
actions = (bulk_review,) actions = (bulk_accept, bulk_reject,)
search_fields = ('username', 'email', 'displayname') search_fields = ('username', 'email', 'displayname')

View File

@ -12,9 +12,9 @@ submission_throttle = {}
throttle_submission = throttler(submission_throttle) throttle_submission = throttler(submission_throttle)
USERNAME_RE = re.compile(r'[a-z][a-z0-9_]+') USERNAME_RE = re.compile(r'^[a-z][a-z0-9_]+$')
USERNAME_MIN_LENGTH = 3 USERNAME_MIN_LENGTH = 2
DISPLAY_NAME_RE = re.compile(r"[a-zA-Z0-9_\-']+") DISPLAY_NAME_RE = re.compile(r"^[a-zA-Z0-9_\-']+$")
DISPLAY_MIN_LENGTH = 2 DISPLAY_MIN_LENGTH = 2
@ -23,7 +23,7 @@ def validate_username(username):
raise ValidationError('Username too short.') raise ValidationError('Username too short.')
if not USERNAME_RE.match(username): if not USERNAME_RE.match(username):
raise ValidationError('Username must be all lowercase, start with a letter, and only use the _ special character') raise ValidationError('Username must be all lowercase, start with a letter, and only use the _ special character')
duplicate = Townie.objects.filter(username=username).count() duplicate = Townie.objects.filter(username=username).exclude(state=Townie.REJECTED).count()
if duplicate > 0: if duplicate > 0:
raise ValidationError('Username already in use :(') raise ValidationError('Username already in use :(')

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2019-07-10 16:22
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0011_auto_20190416_0126'),
]
operations = [
migrations.AddField(
model_name='townie',
name='notes',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='townie',
name='referral',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2019-07-16 01:48
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0012_auto_20190710_1622'),
]
operations = [
migrations.AddField(
model_name='townie',
name='state',
field=models.CharField(choices=[('3_rejected', 'Rejected'), ('2_accepted', 'Accepted'), ('0_unreviewed', 'Unreviewed'), ('4_permaban', 'Permanently Banned'), ('1_tempban', 'Temporarily Banned')], default='0_unreviewed', max_length=20),
),
migrations.AlterField(
model_name='townie',
name='notes',
field=models.TextField(blank=True, help_text='Use this field to share information about this user (reviewed or not) for other admins to see', null=True),
),
]

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2019-07-16 01:51
from __future__ import unicode_literals
from django.db import migrations
def set_state(apps, _):
Townie = apps.get_model('users', 'Townie')
for townie in Townie.objects.all():
if townie.reviewed:
townie.state = '2_accepted'
townie.save()
class Migration(migrations.Migration):
dependencies = [
('users', '0013_auto_20190716_0148'),
]
operations = [
migrations.RunPython(set_state)
]

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2019-07-16 02:10
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0014_auto_20190716_0151'),
]
operations = [
migrations.RemoveField(
model_name='townie',
name='reviewed',
),
]

View File

@ -10,7 +10,7 @@ from django.contrib.auth.models import User
from django.db.models import TextField, BooleanField, CharField, ForeignKey from django.db.models import TextField, BooleanField, CharField, ForeignKey
from django.template.loader import get_template from django.template.loader import get_template
from common.mailing import send_email, ADMIN_NAME from common.mailing import send_email
from help.models import Ticket from help.models import Ticket
logger = logging.getLogger() logger = logging.getLogger()
@ -19,6 +19,7 @@ SSH_TYPE_CHOICES = (
('ssh-rsa', 'ssh-rsa',), ('ssh-rsa', 'ssh-rsa',),
('ssh-dss', 'ssh-dss',), ('ssh-dss', 'ssh-dss',),
('ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp256'), ('ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp256'),
('ssh-ed25519', 'ssh-ed25519'),
) )
DEFAULT_INDEX_PATH = '/etc/skel/public_html/index.html' DEFAULT_INDEX_PATH = '/etc/skel/public_html/index.html'
@ -42,23 +43,54 @@ class Townie(User):
class Meta: class Meta:
verbose_name = 'Townie' verbose_name = 'Townie'
verbose_name_plural = 'Townies' verbose_name_plural = 'Townies'
# the actual values here have a leading int for sorting :(
UNREVIEWED = '0_unreviewed'
TEMPBAN = '1_tempban'
ACCEPTED = '2_accepted'
REJECTED = '3_rejected'
PERMABAN = '4_permaban'
STATE_CHOICES = (
(REJECTED, 'Rejected'),
(ACCEPTED, 'Accepted'),
(UNREVIEWED, 'Unreviewed'),
(PERMABAN, 'Permanently Banned'),
(TEMPBAN, 'Temporarily Banned'),
)
shell = CharField(max_length=50, default="/bin/bash") shell = CharField(max_length=50, default="/bin/bash")
reviewed = BooleanField(default=False) state = CharField(max_length=20, choices=STATE_CHOICES, default=UNREVIEWED)
reasons = TextField(blank=True, null=False, default='') reasons = TextField(blank=True, null=False, default='')
plans = TextField(blank=True, null=False, default='') plans = TextField(blank=True, null=False, default='')
socials = TextField(blank=True, null=False, default='') socials = TextField(blank=True, null=False, default='')
referral = CharField(max_length=100, null=True) referral = CharField(max_length=100, null=True, blank=True)
displayname = CharField(max_length=100, blank=False, null=False) displayname = CharField(max_length=100, blank=False, null=False)
notes = TextField(blank=True, null=True,
help_text='Use this field to share information about this user (reviewed or not) for other admins to see')
@property
def accepted(self):
return self.ACCEPTED == self.state
@property
def unreviewed(self):
return self.UNREVIEWED == self.state
@property @property
def home(self): def home(self):
return os.path.join('/home', self.username) return os.path.join('/home', self.username)
def generate_gift(self):
command = '/town/bin/generate_welcome_present.sh'
error = _guarded_run(['sudo', command, self.username])
if error:
logger.error(error)
return
def send_welcome_email(self): def send_welcome_email(self):
welcome_tmpl = get_template('users/welcome_email.txt') welcome_tmpl = get_template('users/welcome_email.txt')
context = { context = {
'username': self.username, 'username': self.username,
'admin_name': ADMIN_NAME, 'admin_name': 'vilmibm',
} }
text = welcome_tmpl.render(context) text = welcome_tmpl.render(context)
success = send_email(self.email, text, subject='tilde.town!') success = send_email(self.email, text, subject='tilde.town!')
@ -85,7 +117,7 @@ class Townie(User):
"""A VERY NOT IDEMPOTENT create function. Originally, I had ambitions """A VERY NOT IDEMPOTENT create function. Originally, I had ambitions
to have this be idempotent and able to incrementally update a user as to have this be idempotent and able to incrementally update a user as
needed, but decided that was overkill for now.""" needed, but decided that was overkill for now."""
assert(self.reviewed) assert(self.accepted)
dot_ssh_path = '/home/{}/.ssh'.format(self.username) dot_ssh_path = '/home/{}/.ssh'.format(self.username)
error = _guarded_run(['sudo', error = _guarded_run(['sudo',
@ -207,7 +239,7 @@ def on_pubkey_post_save(sender, instance, **kwargs):
townie = townie[0] townie = townie[0]
if townie.reviewed: if townie.accepted:
townie.write_authorized_keys() townie.write_authorized_keys()
@ -219,12 +251,22 @@ def on_townie_pre_save(sender, instance, **kwargs):
existing = Townie.objects.get(id=instance.id) existing = Townie.objects.get(id=instance.id)
# See if we need to create this user on disk. # See if we need to create the user on disk.
if not existing.reviewed and instance.reviewed is True: if existing.unreviewed and instance.accepted:
logger.info('Creating user {} on disk.'.format(instance.username)) logger.info('Creating user {} on disk.'.format(instance.username))
try:
instance.create_on_disk() instance.create_on_disk()
instance.send_welcome_email()
instance.write_authorized_keys() instance.write_authorized_keys()
except Exception as e:
logger.error('Failed syncing user {} to disk: {}'.format(instance.username, e))
else:
instance.send_welcome_email()
instance.generate_gift()
return
else:
# This user state transition is currently undefined. In the future, we can check for things
# like bans/unbans and then take the appropriate action.
return return
# See if this user needs a rename on disk # See if this user needs a rename on disk
@ -251,15 +293,3 @@ def _guarded_run(cmd_args, **run_args):
issue_text='error while running {}: {}'.format( issue_text='error while running {}: {}'.format(
cmd_args, e)) cmd_args, e))
return e return e
# things to consider:
# * what happens when a user is marked as not reviewed?
# * does this signal user deletion? Or does literal Townie deletion signal
# "needs to be removed from disk"? I think it makes the most sense for the
# latter to imply full user deletion.
# * I honestly can't even think of a reason to revert a user to "not reviewed"
# and perhaps it's best to just not make that possible. for now, though, I
# think I can ignore it.
# * what happens when a user needs to be banned?
# * the Townie should be deleted via post_delete signal

View File

@ -11,6 +11,7 @@
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% if signups_enabled %}
<form class="tilde" action="{% url 'users:signup' %}" method="post"> <form class="tilde" action="{% url 'users:signup' %}" method="post">
{% csrf_token %} {% csrf_token %}
<table id="signup"> <table id="signup">
@ -40,5 +41,11 @@
<input type="submit" value="sign up <3" /> <input type="submit" value="sign up <3" />
</div> </div>
</form> </form>
{% else %}
<p>
Town signups are currently disabled, but will resume in the future. If you can't wait for an account, you
can check out some <a href="https://tildeverse.org/">similar servers</a>.
</p>
{% endif %}
</body> </body>
</html> </html>

View File

@ -27,7 +27,7 @@
<li><a href="https://tilde.town/~kc/blackout/">black out</a></li> <li><a href="https://tilde.town/~kc/blackout/">black out</a></li>
<li><a href="https://tilde.town/~subtransience/machinecode/index.html">the machine room</a></li> <li><a href="https://tilde.town/~subtransience/machinecode/index.html">the machine room</a></li>
</ul> </ul>
<h2>or a <a href="https://tilde.town/cgi/random">random page</a></h2> <h2>or a <a href="https://cgi.tilde.town/users/random">random page</a></h2>
</td> </td>
<td style="padding:1em"> <td style="padding:1em">
<a href="http://giphy.com/gifs/cyndipop-golden-girls-bea-arthur-ToMjGpK80QLT7KLWPLO"> <a href="http://giphy.com/gifs/cyndipop-golden-girls-bea-arthur-ToMjGpK80QLT7KLWPLO">

View File

@ -12,10 +12,18 @@ from django.views.generic.edit import FormView
from .forms import TownieForm from .forms import TownieForm
from .models import Townie, Pubkey from .models import Townie, Pubkey
SIGNUPS_ENABLED = True
class SignupView(FormView): class SignupView(FormView):
form_class = TownieForm form_class = TownieForm
template_name = 'users/signup.html' template_name = 'users/signup.html'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['signups_enabled'] = SIGNUPS_ENABLED
return ctx
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
del form.cleaned_data['captcha'] del form.cleaned_data['captcha']