server/api: add password reminders

This commit is contained in:
rr- 2016-04-03 17:01:48 +02:00
parent 1ed17a2046
commit 9ce67b64ed
9 changed files with 92 additions and 6 deletions

View file

@ -79,8 +79,8 @@ user@host:szuru/server$ source python_modules/bin/activate # enters the sandbox
user@host:szuru$ vim config.ini
```
Pay extra attention to the `[database]` and `[smtp]` sections, and API URL in
`[basic]`.
Pay extra attention to the `[database]` section, `[smtp]` section, API URL
and base URL in `[basic]`.
2. Compile the frontend:
@ -162,6 +162,7 @@ server {
```ini
[basic]
api_url = http://big.dude/api/
base_url = http://big.dude/
```
Then the backend is started with `./server/host-waitress` from within

View file

@ -5,7 +5,8 @@
name = szurubooru
debug = 0
secret = change
api_url = http://api.example.com/ # see INSTALL.md
api_url = "http://api.example.com/" # where frontend connects to
base_url = "http://example.com/" # used in absolute links (e.g. password reminder)
[database]
schema = postgres

View file

@ -1,3 +1,4 @@
''' Falcon-compatible API facades. '''
from szurubooru.api.password_reminder_api import PasswordReminderApi
from szurubooru.api.user_api import UserListApi, UserDetailApi

View file

@ -0,0 +1,53 @@
''' Exports PasswordReminderApi. '''
import hashlib
from szurubooru.api.base_api import BaseApi
from szurubooru.errors import ValidationError, NotFoundError
class PasswordReminderApi(BaseApi):
''' API for password reminders. '''
def __init__(self, config, mailer, user_service):
super().__init__()
self._config = config
self._mailer = mailer
self._user_service = user_service
def get(self, request, context, user_name):
user = self._user_service.get_by_name(context.session, user_name)
if not user:
raise NotFoundError('User %r not found.' % user_name)
if not user.email:
raise ValidationError(
'User %r hasn\'t supplied email. Cannot reset password.' % user_name)
token = self._generate_authentication_token(user)
self._mailer.send(
'noreply@%s' % self._config['basic']['name'],
user.email,
'Password reset for %s' % self._config['basic']['name'],
'You (or someone else) requested to reset your password on %s.\n'
'If you wish to proceed, click this link: %s/password-reset/%s\n'
'Otherwise, please ignore this email.' %
(self._config['basic']['name'],
self._config['basic']['base_url'].rstrip('/'),
token))
return {}
def post(self, request, context, user_name):
user = self._user_service.get_by_name(context.session, user_name)
if not user:
raise NotFoundError('User %r not found.' % user_name)
good_token = self._generate_authentication_token(user)
if not 'token' in context.request:
raise ValidationError('Missing password reset token.')
token = context.request['token']
if token != good_token:
raise ValidationError('Invalid password reset token.')
new_password = self._user_service.reset_password(user)
context.session.commit()
return {'password': new_password}
def _generate_authentication_token(self, user):
digest = hashlib.sha256()
digest.update(self._config['basic']['secret'].encode('utf8'))
digest.update(user.password_salt.encode('utf8'))
return digest.hexdigest()

View file

@ -63,6 +63,7 @@ def create_app():
scoped_session = sqlalchemy.orm.scoped_session(session_maker)
# TODO: is there a better way?
mailer = szurubooru.services.Mailer(config)
password_service = szurubooru.services.PasswordService(config)
auth_service = szurubooru.services.AuthService(config, password_service)
user_service = szurubooru.services.UserService(config, password_service)
@ -70,6 +71,8 @@ def create_app():
user_list_api = szurubooru.api.UserListApi(auth_service, user_service)
user_detail_api = szurubooru.api.UserDetailApi(
config, auth_service, password_service, user_service)
password_reminder_api = szurubooru.api.PasswordReminderApi(
config, mailer, user_service)
app = falcon.API(
request_type=_CustomRequest,
@ -88,5 +91,6 @@ def create_app():
app.add_route('/users/', user_list_api)
app.add_route('/user/{user_name}', user_detail_api)
app.add_route('/password_reminder/{user_name}', password_reminder_api)
return app

View file

@ -12,7 +12,7 @@ class Config(object):
def __init__(self):
self.config = configobj.ConfigObj('../config.ini.dist')
if os.path.exists('../config.ini'):
self.config.merge(configobj.ConfigObj('config.ini'))
self.config.merge(configobj.ConfigObj('../config.ini'))
self._validate()
def __getitem__(self, key):

View file

@ -3,6 +3,7 @@ Middle layer between REST API and database.
All the business logic goes here.
'''
from szurubooru.services.mailer import Mailer
from szurubooru.services.auth_service import AuthService
from szurubooru.services.user_service import UserService
from szurubooru.services.password_service import PasswordService

View file

@ -0,0 +1,19 @@
import smtplib
from email.mime.text import MIMEText
class Mailer(object):
def __init__(self, config):
self._config = config
def send(self, sender, recipient, subject, body):
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = recipient
smtp = smtplib.SMTP(
self._config['smtp']['host'],
int(self._config['smtp']['port']))
smtp.login(self._config['smtp']['user'], self._config['smtp']['pass'])
smtp.send_message(msg)
smtp.quit()

View file

@ -35,10 +35,9 @@ class UserService(object):
user = User()
user.name = name
user.password = password
user.password_salt = self._password_service.create_password()
user.password_hash = self._password_service.get_password_hash(
user.password_salt, user.password)
user.password_salt, password)
user.email = email
user.access_rank = self._config['service']['default_user_rank']
user.creation_time = datetime.now()
@ -50,6 +49,13 @@ class UserService(object):
def bump_login_time(self, user):
user.last_login_time = datetime.now()
def reset_password(self, user):
password = self._password_service.create_password()
user.password_salt = self._password_service.create_password()
user.password_hash = self._password_service.get_password_hash(
user.password_salt, password)
return password
def get_by_name(self, session, name):
''' Retrieves an user by its name. '''
return session.query(User).filter_by(name=name).first()