Added registration email confirmation. Refactored confirm to wsgy runner

deb
androiddrew 7 years ago
parent 442b8acdc5
commit 12bc49100b

@ -1,6 +1,7 @@
from apistar import Route from apistar import Route
from apistar.http import HTMLResponse from apistar.http import HTMLResponse
from apistar_jwt import JWT from apistar_jwt import JWT
from apistar_mail import MailComponent
import logbook import logbook
from sqlalchemy import create_engine from sqlalchemy import create_engine
from pathlib import Path from pathlib import Path
@ -42,6 +43,15 @@ _components = [
'JWT_USER_ID': 'sub', 'JWT_USER_ID': 'sub',
'JWT_SECRET': 'thisisasecret', 'JWT_SECRET': 'thisisasecret',
}), }),
MailComponent(**{
'MAIL_SERVER': 'mail.example.com',
'MAIL_USERNAME': 'me@example.com',
'MAIL_PASSWORD': 'donotsavethistoversioncontrol',
'MAIL_PORT': 587,
'MAIL_USE_TLS': True,
'MAIL_DEFAULT_SENDER': 'me@example.com',
'MAIL_SUPPRESS_SEND': True
})
] ]
@ -51,11 +61,11 @@ def application_factory(routes=_routes, components=_components, hooks=_hooks, se
_settings = {**app_settings, **settings} _settings = {**app_settings, **settings}
global_init(_settings) global_init(_settings)
logger.info("Template directory {}".format(TEMPLATE_DIR)) logger.debug("Template directory {}".format(template_dir))
logger.info("Static directory {}".format(STATIC_DIR)) logger.debug("Static directory {}".format(STATIC_DIR))
return App(components=components, return App(components=components,
event_hooks=hooks, event_hooks=hooks,
routes=routes, routes=routes,
template_dir=template_dir.name, # have to use name because of Jinja2 template_dir=str(template_dir), # have to use name because of Jinja2
static_dir=static_dir) static_dir=static_dir)

@ -1,10 +1,14 @@
import datetime as dt import datetime as dt
import re
from apistar import http, Route, Include from apistar.exceptions import HTTPException
from apistar import http, Route, Include, App
from apistar_jwt.token import JWT, JWTUser from apistar_jwt.token import JWT, JWTUser
# from apistar_mail import Message, Mail from apistar_mail import Message, Mail
# from sqlalchemy.exc import IntegrityError, InvalidRequestError # from sqlalchemy.exc import IntegrityError, InvalidRequestError
from itsdangerous import URLSafeTimedSerializer, BadSignature
import logbook
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
@ -12,6 +16,31 @@ from cookie_api.models import User
from cookie_api.schema import UserExportSchema, UserCreateSchema from cookie_api.schema import UserExportSchema, UserCreateSchema
from cookie_api.util import ExtJSONResponse from cookie_api.util import ExtJSONResponse
# https://realpython.com/handling-email-confirmation-in-flask/
EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
email_regex = re.compile(EMAIL_REGEX)
logger = logbook.Logger(__name__)
def generate_confirmation_token(email):
serializer = URLSafeTimedSerializer('secret-key')
return serializer.dumps(email, salt='password-salt')
def confirm_token(token, expiration=3600):
serializer = URLSafeTimedSerializer('secret-key')
try:
email = serializer.loads(
token,
salt='password-salt',
max_age=expiration
)
except BadSignature:
return False
return email
def login(json_data: http.RequestData, session: Session, jwt: JWT): def login(json_data: http.RequestData, session: Session, jwt: JWT):
user_id = json_data.get('email') user_id = json_data.get('email')
@ -53,33 +82,61 @@ def logout():
pass pass
# TODO Add email confirmation to registration def register(user_data: UserCreateSchema, session: Session, mail: Mail, app: App) -> UserExportSchema:
# def register(user_data: UserCreateSchema, session: Session, mail: Mail): if not email_regex.match(user_data['email']):
def register(user_data: UserCreateSchema, session: Session) -> UserExportSchema: raise HTTPException('Please provide a valid email address', status_code=400)
email_check = session.query(User).filter_by(email=user_data['email']).one_or_none() email_check = session.query(User).filter_by(email=user_data['email']).one_or_none()
if email_check is not None: if email_check is not None:
error = { raise HTTPException('user email address is already in use', status_code=400)
'status': 'error',
'message': 'user email address is already in use'
}
return ExtJSONResponse(error, 400)
user = User(email=user_data['email'], password=user_data['password']) user = User(email=user_data['email'], password=user_data['password'])
try:
tokenized_email = generate_confirmation_token(user.email)
email_body = app.render_template('confirm_email.jinja2',
confirm_url=f'http://localhost:5000/auth/confirm?token={tokenized_email}'
)
msg = Message(subject="Confirm your email",
html=email_body,
recipients=[user_data['email']])
mail.send(msg)
except Exception as e:
logger.error(e, exc_info=True)
raise HTTPException('Error an sending email', status_code=500)
session.add(user) session.add(user)
session.commit() session.commit()
# msg = Message("Thank you for registering please confirm your email", recipients=[user_rep['email']]) headers = {}
# mail.send(msg)
# headers = {}
message = { message = {
'status': 'success',
'message': 'Please check your inbox and confirm your email', 'message': 'Please check your inbox and confirm your email',
'data': UserExportSchema(user) 'data': UserExportSchema(user)
} }
return ExtJSONResponse(message, 201) return ExtJSONResponse(message, 201, headers)
# TODO Return an HTML response since the user will be clicking on a link in a web browser
# TODO Since you are returning a user respresentation add a location and Content-Location header
def confirm(params: http.QueryParams, session: Session):
token = params['token']
email = confirm_token(token)
if not email:
raise HTTPException('Email token is expired or invalid', status_code=400)
user = session.query(User).filter_by(email=email).one()
if user.confirmed:
raise HTTPException('Account already confirmed. Please login.', status_code=400)
user.confirmed = True
session.add(user)
session.commit()
return ExtJSONResponse(UserExportSchema(user))
def user_profile(user: JWTUser, session: Session) -> UserExportSchema: def user_profile(user: JWTUser, session: Session) -> UserExportSchema:
@ -87,15 +144,10 @@ def user_profile(user: JWTUser, session: Session) -> UserExportSchema:
user = session.query(User).filter_by(id=user.id).one() user = session.query(User).filter_by(id=user.id).one()
except NoResultFound as e: except NoResultFound as e:
error = {'message': str(e)} error = {'message': str(e)}
return ExtJSONResponse(error, 400) raise HTTPException(error, status_code=400)
return ExtJSONResponse(UserExportSchema(user)) return ExtJSONResponse(UserExportSchema(user))
# TODO Add email confirmation
def confirm(json_data: http.RequestData, session: Session):
pass
# TODO Add email password reset # TODO Add email password reset
def reset(): def reset():
pass pass
@ -104,7 +156,8 @@ def reset():
routes = [ routes = [
Route('/login', 'POST', login), Route('/login', 'POST', login),
Route('/register', 'POST', register), Route('/register', 'POST', register),
Route('/status', 'GET', user_profile) Route('/status', 'GET', user_profile),
Route('/confirm', 'GET', confirm)
] ]
auth_routes = [Include('/auth', name='auth', routes=routes)] auth_routes = [Include('/auth', name='dirp', routes=routes)]

@ -0,0 +1,4 @@
<p>Welcome! Thanks for signing up. Please follow this link to activate your account:</p>
<p><a href="{{ confirm_url }}">Confirm your email</a></p>
<br>
<p>Cheers!</p>

@ -1,5 +1,6 @@
alembic==0.9.9 alembic==0.9.9
apistar==0.5.18 apistar==0.5.18
apistar-mail=0.3.0
apistar-jwt==0.4.2 apistar-jwt==0.4.2
bcrypt==3.1.4 bcrypt==3.1.4
certifi==2018.4.16 certifi==2018.4.16

@ -6,10 +6,26 @@ except ImportError:
compat.register() compat.register()
from apistar_jwt import JWT
from apistar_mail import MailComponent
from sqlalchemy import create_engine
from cookie_api import application_factory from cookie_api import application_factory
from cookie_api.util import SQLAlchemySession, SQLAlchemyHook
from config import db_config, jwt_config, mail_config
components = [
SQLAlchemySession(create_engine(db_config)),
JWT(jwt_config),
MailComponent(**mail_config)
]
hooks = [
SQLAlchemyHook()
]
app = application_factory() app = application_factory(components=components, hooks=hooks)
if __name__ == '__main__': if __name__ == '__main__':
app = application_factory() app = application_factory()
app.serve('127.0.0.1', 8080, debug=True) app.serve('127.0.0.1', 5000, debug=True)

Loading…
Cancel
Save