Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mail verification and password reset #208

Merged
merged 32 commits into from Dec 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6c6a8d5
Install anymail with sparkpost
tiltec Oct 25, 2016
81cccab
Add prototype for mail verification
tiltec Oct 25, 2016
f0b8665
Merge branch 'master' into mailing
tiltec Dec 24, 2016
7c53e95
Fix requirements
tiltec Dec 24, 2016
98de983
Fix model, update migrations
tiltec Dec 24, 2016
455d4bc
fix formatting
tiltec Dec 24, 2016
892d6b5
Update local config
tiltec Dec 24, 2016
9085d6e
Set blank=True instead of null=True
tiltec Dec 24, 2016
d239ff5
Extend key lifetime
tiltec Dec 24, 2016
3d9efd9
Use embedded Django crypto, return status fields in API
tiltec Dec 24, 2016
63977c9
Move verification to own serializer
tiltec Dec 24, 2016
ae8ee3b
Enable verification even when not logged in
tiltec Dec 24, 2016
365ee57
Revert "Enable verification even when not logged in"
tiltec Dec 24, 2016
ef03502
Add password reset mail
tiltec Dec 25, 2016
86f5802
Newline fix in mail template
tiltec Dec 25, 2016
4c48c88
Move production secrets to another file
tiltec Dec 25, 2016
e77727d
Add tests
tiltec Dec 25, 2016
48dc93c
Fix permission_classes
tiltec Dec 25, 2016
180c35a
Move verification methods to model
tiltec Dec 25, 2016
9009c08
Move verification methods to model
tiltec Dec 25, 2016
546b2ec
More tests for reset_password
tiltec Dec 25, 2016
c85652b
More testing
tiltec Dec 25, 2016
65b6efc
Enable changing the password via put and patch
tiltec Dec 25, 2016
98f7a85
Add test, change and extend UserFactory
tiltec Dec 25, 2016
314f10a
Move reset_password logic from viewset to model
tiltec Dec 25, 2016
a4fa597
Update example local config
tiltec Dec 25, 2016
17d25ad
Return frontend URL for mail verification
tiltec Dec 25, 2016
abd24eb
Return success even if email was not found
tiltec Dec 25, 2016
bbad4b6
Add newline for swagger
tiltec Dec 25, 2016
d66e71b
Make mails translateable and add German translation
tiltec Dec 25, 2016
14e1bc1
Fix CI hostname setting
tiltec Dec 25, 2016
c2ca33f
Merge branch 'master' into mailing
tiltec Dec 26, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -4,9 +4,11 @@ env

# Project
/config/local_settings.py
/config/secrets.py
/dump.rdb
/db.sqlite3
*.pid
*.mo

# IDE
/.idea
Expand Down
11 changes: 9 additions & 2 deletions config/local_settings.py.ci
@@ -1,14 +1,18 @@
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'yunity_database',
'NAME': 'fstool-db',
'TEST': {
'NAME': 'yunity_test'
'NAME': 'fstool_test'
},
'USER': 'ubuntu',
}
}

DEFAULT_FROM_EMAIL = "testing@example.com"
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
HOSTNAME = ''

INFLUXDB_DISABLED = True
INFLUXDB_HOST = ''
INFLUXDB_PORT = ''
Expand All @@ -25,3 +29,6 @@ SHELL_PLUS_PRE_IMPORTS = (
('yunity.utils.tests.mock', ('*')),
('yunity.permissions.lookup', ('*')),
)

SECRET_KEY = 'c*#4=n$s4!*gdgq3nora#a$*xznctg-6=4_edeg9^dsxk&=p=$'
DEBUG = True
21 changes: 17 additions & 4 deletions config/local_settings.py.example
Expand Up @@ -4,10 +4,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'yunity-database',
'USER': 'yunity-user',
'PASSWORD': 'yunity',
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'fstool-db',
'USER': 'fstool-user',
'PASSWORD': 'fstool-pw',
'HOST': 'localhost',
'PORT': '',
}
Expand All @@ -25,3 +25,16 @@ INFLUXDB_USE_THREADING = True

SHELL_PLUS_PRE_IMPORTS = (
)

# consider using the console backend instead of SparkPost
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

# ANYMAIL = {
# "SPARKPOST_API_KEY": "your sparkpost key"
# }

DEFAULT_FROM_EMAIL = "your mail domain"

SECRET_KEY = 'c*#4=n$s4!*gdgq3nora#a$*xznctg-6=4_edeg9^dsxk&=p=$'
DEBUG = True
HOSTNAME = 'https://localhost:8000'
23 changes: 8 additions & 15 deletions config/settings.py
Expand Up @@ -4,10 +4,10 @@
Generated by 'django-admin startproject' using Django 1.8.4.

For more information on this file, see
https://docs.djangoproject.com/en/1.9/topics/settings/
https://docs.djangoproject.com/en/dev/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
https://docs.djangoproject.com/en/dev/ref/settings/
"""

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
Expand All @@ -16,17 +16,6 @@
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'c*#4=n$s4!*gdgq3nora#a$*xznctg-6=4_edeg9^dsxk&=p=$'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

Expand All @@ -46,6 +35,7 @@
'rest_framework',
'rest_framework_nested',
'rest_framework_swagger',
'anymail',
'influxdb_metrics',

# Application
Expand Down Expand Up @@ -77,6 +67,7 @@
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.middleware.common.CommonMiddleware',
Expand Down Expand Up @@ -120,7 +111,7 @@
WSGI_APPLICATION = 'config.wsgi.application'

# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
Expand All @@ -137,8 +128,10 @@
},
]

EMAIL_BACKEND = "anymail.backends.sparkpost.SparkPostBackend"

# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
# https://docs.djangoproject.com/en/dev/topics/i18n/

LANGUAGE_CODE = 'en-us'

Expand Down
11 changes: 10 additions & 1 deletion deploy/remote.sh
Expand Up @@ -33,7 +33,13 @@ DATABASES = {
}
DEBUG = False
ALLOWED_HOSTS = ['fstool.yunity.org', 'mvp-proposal.yunity.org', 'mvp-design.yunity.org']
HOSTNAME = 'https://fstool.yunity.org'
STATIC_ROOT = '${deploy_dir}/${backend_dir}/static/'
ANYMAIL = {
"SPARKPOST_API_KEY": add your server key
}

DEFAULT_FROM_EMAIL = "fstool@yunity.org"
INFLUXDB_HOST = '127.0.0.1'
INFLUXDB_PORT = '8086'
INFLUXDB_USER = ''
Expand All @@ -43,6 +49,8 @@ INFLUXDB_TAGS_HOST = 'yuca'
INFLUXDB_TIMEOUT = 2
INFLUXDB_USE_CELERY = False
INFLUXDB_USE_THREADING = True

from .secrets import *
CONFIG

createdb fstool || true
Expand All @@ -55,7 +63,8 @@ createdb fstool || true
env/bin/pip install -r requirements.txt && \
env/bin/python manage.py migrate --fake-initial && \
env/bin/python manage.py check --deploy && \
env/bin/python manage.py collectstatic --clear --no-input
env/bin/python manage.py collectstatic --clear --no-input && \
env/bin/python manage.py compilemessages
)

touch /tmp/fstool.reload
2 changes: 2 additions & 0 deletions requirements.in
Expand Up @@ -16,4 +16,6 @@ flake8
pytz
django-filter
django-crispy-forms
django-anymail
sparkpost
django-influxdb-metrics
6 changes: 4 additions & 2 deletions requirements.txt
Expand Up @@ -9,6 +9,7 @@ billiard==3.5.0.2 # via celery
celery==4.0.1 # via django-influxdb-metrics
coreapi==2.1.1 # via django-rest-swagger, openapi-codec
coverage==4.2
django-anymail==0.5
django-cors-headers==1.3.1
django-crispy-forms==1.6.1
django-enumfield==1.3b2
Expand Down Expand Up @@ -38,9 +39,10 @@ python-dateutil==2.6.0 # via fake-factory, influxdb
python-server-metrics==0.2.1 # via django-influxdb-metrics
pytz==2016.7
redis==2.10.5
requests==2.12.3 # via coreapi, influxdb
requests==2.12.3 # via coreapi, django-anymail, influxdb, sparkpost
simplejson==3.10.0 # via django-rest-swagger
six==1.10.0 # via django-extensions, fake-factory, influxdb, python-dateutil, tld
six==1.10.0 # via django-anymail, django-extensions, fake-factory, influxdb, python-dateutil, tld
sparkpost==1.3.0
tld==0.7.6 # via django-influxdb-metrics
uritemplate==3.0.0 # via coreapi
vine==1.1.3 # via amqp
4 changes: 2 additions & 2 deletions yunity/conversations/factories.py
@@ -1,7 +1,7 @@
from factory import post_generation, DjangoModelFactory, SubFactory, LazyAttribute
from yunity.conversations.models import Conversation as ConversationModel
from yunity.conversations.models import ConversationMessage as MessageModel
from yunity.users.factories import User
from yunity.users.factories import UserFactory
from yunity.utils.tests.fake import faker


Expand All @@ -22,6 +22,6 @@ class Message(DjangoModelFactory):
class Meta:
model = MessageModel

author = SubFactory(User)
author = SubFactory(UserFactory)
in_conversation = SubFactory(Conversation)
content = LazyAttribute(lambda x: faker.text())
8 changes: 4 additions & 4 deletions yunity/conversations/tests/test_conversations_api.py
Expand Up @@ -2,7 +2,7 @@
from rest_framework.test import APITestCase

from yunity.conversations.factories import Conversation
from yunity.users.factories import User
from yunity.users.factories import UserFactory
from yunity.utils.tests.fake import faker


Expand All @@ -13,13 +13,13 @@ def setUpClass(cls):
cls.url = '/api/conversations/'

# A chat with 2 participants
cls.participant = User()
cls.participant2 = User()
cls.participant = UserFactory()
cls.participant2 = UserFactory()
cls.conversation = Conversation(participants=[cls.participant, cls.participant2])
cls.conversation_url = cls.url + str(cls.conversation.id) + '/'

# not a participant
cls.user = User()
cls.user = UserFactory()

# another chat
cls.conversation_data = {'topic': faker.name(),
Expand Down
4 changes: 2 additions & 2 deletions yunity/conversations/tests/test_serializers.py
Expand Up @@ -2,7 +2,7 @@
from django.utils.dateparse import parse_datetime
from yunity.conversations.factories import Message, Conversation
from yunity.conversations.serializers import MessageSerializer, ConversationSerializer
from yunity.users.factories import User
from yunity.users.factories import UserFactory


class TestMessageSerializer(TestCase):
Expand All @@ -22,7 +22,7 @@ class TestConversationSerializer(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.conversation = Conversation(participants=[User() for _ in range(3)])
cls.conversation = Conversation(participants=[UserFactory() for _ in range(3)])
[Message(in_conversation=cls.conversation) for _ in range(10)]

def test_instantiation(self):
Expand Down
6 changes: 3 additions & 3 deletions yunity/groups/tests/test_api.py
Expand Up @@ -2,16 +2,16 @@
from rest_framework.test import APITestCase
from yunity.groups.factories import Group as GroupFactory
from yunity.groups.models import Group as GroupModel
from yunity.users.factories import User
from yunity.users.factories import UserFactory
from yunity.utils.tests.fake import faker


class TestGroupsAPI(APITestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user = User()
cls.member = User()
cls.user = UserFactory()
cls.member = UserFactory()
cls.group = GroupFactory(members=[cls.member, ])
cls.group_with_password = GroupFactory(password='abc')
cls.join_password_url = '/api/groups/{}/join/'.format(cls.group_with_password.id)
Expand Down
8 changes: 4 additions & 4 deletions yunity/groups/tests/test_groups_api_filter.py
@@ -1,7 +1,7 @@
from rest_framework import status
from rest_framework.test import APITestCase
from yunity.groups.factories import Group
from yunity.users.factories import User
from yunity.users.factories import UserFactory


class TestStoresAPIFilter(APITestCase):
Expand All @@ -11,12 +11,12 @@ def setUpClass(cls):
cls.url = '/api/groups/'

# some user
cls.user = User()
cls.user = UserFactory()

# two groups with different members
cls.member = User()
cls.member = UserFactory()
cls.group = Group(members=[cls.member, ])
cls.member2 = User()
cls.member2 = UserFactory()
cls.group2 = Group(members=[cls.member2, ])
cls.empty_group = Group()

Expand Down
4 changes: 2 additions & 2 deletions yunity/groups/tests/test_serializers.py
@@ -1,14 +1,14 @@
from django.test import TestCase
from yunity.groups.factories import Group
from yunity.users.factories import UserFactory
from yunity.groups.serializers import GroupDetailSerializer, GroupPreviewSerializer
from yunity.users.factories import User


class TestGroupSerializer(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.group = Group(members=[User() for _ in range(3)])
cls.group = Group(members=[UserFactory() for _ in range(3)])

def test_detail(self):
serializer = GroupDetailSerializer(self.group)
Expand Down
36 changes: 36 additions & 0 deletions yunity/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,36 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-25 21:56+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: yunity/users/models.py:104
msgid "Verify your mail address"
msgstr "Bestätige deine E-Mail-Adresse"

#: yunity/users/models.py:105
msgid "Here is your activation key: {}. It will be valid for 7 days."
msgstr "Hier ist dein Aktivierungsschlüssel: {}. Er wird für 7 Tage gültig sein."

#: yunity/users/models.py:114
msgid "New password"
msgstr "Neues Passwort"

#: yunity/users/models.py:115
msgid ""
"Here is your new temporary password: {}. You can use it to login. Please "
"change it soon."
msgstr "Hier ist dein neues Zufalls-Passwort: {}. Melde dich damit an und ändere es."
6 changes: 3 additions & 3 deletions yunity/stores/tests/test_pickupdates_api.py
Expand Up @@ -5,7 +5,7 @@
from rest_framework.test import APITestCase
from yunity.groups.factories import Group
from yunity.stores.factories import Store, PickupDate
from yunity.users.factories import User
from yunity.users.factories import UserFactory


class TestPickupDatesAPI(APITestCase):
Expand All @@ -15,7 +15,7 @@ def setUpClass(cls):
cls.url = '/api/pickup-dates/'

# pickup date for group with one member and one store
cls.member = User()
cls.member = UserFactory()
cls.group = Group(members=[cls.member, ])
cls.store = Store(group=cls.group)
cls.pickup = PickupDate(store=cls.store)
Expand All @@ -24,7 +24,7 @@ def setUpClass(cls):
cls.leave_url = cls.pickup_url + 'remove/'

# not a member of the group
cls.user = User()
cls.user = UserFactory()

# another pickup date for above store
cls.pickup_data = {'date': timezone.now() + timedelta(days=2),
Expand Down