Skip to content

Commit

Permalink
intial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
fogleman committed Jan 8, 2014
0 parents commit ff0a32a
Show file tree
Hide file tree
Showing 24 changed files with 748 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
@@ -0,0 +1,6 @@
*.pyc
*.db
*.wsgi
config_local.py
env

13 changes: 13 additions & 0 deletions auth/__init__.py
@@ -0,0 +1,13 @@
from flask import Flask
from flask_mail import Mail
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object('auth.config')

mail = Mail(app)
db = SQLAlchemy(app)

import hooks
import models
import views
13 changes: 13 additions & 0 deletions auth/config.py
@@ -0,0 +1,13 @@
SECRET_KEY = 'CHANGE_ME'
SQLALCHEMY_DATABASE_URI = 'sqlite:///auth.db'
STATIC_ROOT = None

MAIL_SERVER = ''
MAIL_USERNAME = ''
MAIL_PASSWORD = ''
MAIL_DEFAULT_SENDER = ''

try:
from config_local import *
except ImportError:
pass
8 changes: 8 additions & 0 deletions auth/email.py
@@ -0,0 +1,8 @@
from flask import render_template
from flask_mail import Message
from auth import mail

def send_account_created_email(user):
message = Message('Craft E-mail Verification', [user.email])
message.body = render_template('account_created_email.txt', user=user)
mail.send(message)
39 changes: 39 additions & 0 deletions auth/forms.py
@@ -0,0 +1,39 @@
from flask.ext.wtf import Form
from wtforms import TextField, PasswordField, validators
from models import User

class LoginForm(Form):
username = TextField('Username', [validators.Required()])
password = PasswordField('Password', [validators.Required()])
def validate(self):
if not Form.validate(self):
return False
user = User.query.filter_by(username=self.username.data).first()
if not user:
self.password.errors.append('Incorrect username or password.')
return False
if not user.check_password(self.password.data):
self.password.errors.append('Incorrect username or password.')
return False
if not user.enabled:
self.username.errors.append('User account is disabled.')
return False
self.user = user
return True

class RegistrationForm(Form):
username = TextField('Username', [validators.Length(3, 15)])
email = TextField('E-mail Address', [validators.Email()])
password = PasswordField('Password', [validators.Length(6)])
confirm_password = PasswordField('Confirm Password', [validators.EqualTo('password')])
def validate(self):
if not Form.validate(self):
return False
user = User.query.filter(User.username.ilike(self.username.data)).first()
if user:
self.username.errors.append('Username already in use.')
return False
return True

class IdentityTokenForm(Form):
name = TextField('Name', [validators.Required()])
40 changes: 40 additions & 0 deletions auth/hooks.py
@@ -0,0 +1,40 @@
from flask import url_for, g, session, redirect, request
from auth import app
from models import User
import functools
import urlparse

def static(path):
root = app.config.get('STATIC_ROOT')
if root is None:
return url_for('static', filename=path)
else:
return urlparse.urljoin(root, path)

@app.context_processor
def context_processor():
return dict(static=static)

@app.before_request
def before_request():
try:
g.user = User.query.filter_by(username=session['username']).first()
except Exception:
g.user = None

def login_required(f):
@functools.wraps(f)
def decorated_function(*args, **kwargs):
if g.user is None:
return redirect(url_for('index', next=request.url))
return f(*args, **kwargs)
return decorated_function

def admin_required(f):
@functools.wraps(f)
def decorated_function(*args, **kwargs):
if g.user and g.user.admin:
return f(*args, **kwargs)
else:
abort(403)
return decorated_function
84 changes: 84 additions & 0 deletions auth/models.py
@@ -0,0 +1,84 @@
from flask import url_for
from auth import db
from util import get_serializer
from werkzeug.security import check_password_hash
import datetime

class User(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(256), nullable=False, unique=True)
password = db.Column(db.String(256), nullable=False)
email = db.Column(db.String(256), nullable=False)
verified = db.Column(db.Boolean, nullable=False)
enabled = db.Column(db.Boolean, nullable=False)
admin = db.Column(db.Boolean, nullable=False)
created = db.Column(db.DateTime, nullable=False)
touched = db.Column(db.DateTime, nullable=False)
def __init__(self, username, password, email, verified, enabled, admin):
self.username = username
self.password = password
self.email = email
self.verified = verified
self.enabled = enabled
self.admin = admin
self.created = datetime.datetime.utcnow()
self.touched = self.created
def check_password(self, password):
return check_password_hash(self.password, password)
def touch(self):
self.touched = datetime.datetime.utcnow()
db.session.commit()
def get_verification_link(self):
payload = get_serializer().dumps(self.user_id)
return url_for('verify_email', payload=payload, _external=True)

class IdentityToken(db.Model):
identity_token_id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.user_id), nullable=False, index=True)
name = db.Column(db.String(256), nullable=False)
token = db.Column(db.String(256), nullable=False)
enabled = db.Column(db.Boolean, nullable=False)
created = db.Column(db.DateTime, nullable=False)
touched = db.Column(db.DateTime, nullable=False)
user = db.relationship(User, backref=db.backref('identity_tokens', lazy='dynamic'))
def __init__(self, user, name, token, enabled):
self.user = user
self.name = name
self.token = token
self.enabled = enabled
self.created = datetime.datetime.utcnow()
self.touched = self.created
def check_token(self, token):
return check_password_hash(self.token, token)
def touch(self):
self.touched = datetime.datetime.utcnow()
db.session.commit()

class AccessToken(db.Model):
access_token_id = db.Column(db.Integer, primary_key=True)
identity_token_id = db.Column(db.Integer, db.ForeignKey(IdentityToken.identity_token_id), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey(User.user_id), nullable=False, index=True)
token = db.Column(db.String(256), nullable=False, unique=True)
enabled = db.Column(db.Boolean, nullable=False)
client_addr = db.Column(db.String(256), nullable=False)
client_timestamp = db.Column(db.DateTime, nullable=False)
server_addr = db.Column(db.String(256), nullable=True)
server_timestamp = db.Column(db.DateTime, nullable=True)
identity_token = db.relationship(IdentityToken, backref=db.backref('access_tokens', lazy='dynamic'))
user = db.relationship(User, backref=db.backref('access_tokens', lazy='dynamic'))
def __init__(self, identity_token, user, token, enabled, client_addr, client_timestamp, server_addr, server_timestamp):
self.identity_token = identity_token
self.user = user
self.token = token
self.enabled = enabled
self.client_addr = client_addr
self.client_timestamp = client_timestamp
self.server_addr = server_addr
self.server_timestamp = server_timestamp
@property
def age(self):
return datetime.datetime.utcnow() - self.client_timestamp
def check_token(self, token, max_age):
if self.age > max_age:
return False
return check_password_hash(self.token, token)
9 changes: 9 additions & 0 deletions auth/static/ZeroClipboard.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added auth/static/ZeroClipboard.swf
Binary file not shown.
Binary file added auth/static/favicon.ico
Binary file not shown.
Binary file added auth/static/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 89 additions & 0 deletions auth/static/style.css
@@ -0,0 +1,89 @@
body {
padding: 20px 0;
}

.container {
max-width: 730px;
}

.header {
border-bottom: 1px solid #e5e5e5;
margin-bottom: 20px;
}

.header img {
float: left;
margin-right: 10px;
margin-top: -5px;
}

.header h2 {
margin: 0;
line-height: 40px;
padding-bottom: 20px;
}

.content {
margin-bottom: 20px;
}

.footer {
padding: 10px;
color: #777;
border-top: 1px solid #e5e5e5;
}

.form-login h3 {
margin-top: 0;
}

.form-login .form-control {
position: relative;
font-size: 16px;
height: auto;
padding: 10px;
margin-bottom: 10px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}

.forgot {
margin: 10px 0;
text-align: center;
}

.button-row th {
line-height: 34px !important;
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0, #eee 100%);
background-image: linear-gradient(to bottom, #fff 0, #eee 100%);
background-repeat: repeat-x;
}

.button-row td {
line-height: 30px !important;
}

.button-row .btn {
float: right;
margin-left: 8px;
}

.clipboard {
width: 180px;
}

.zeroclipboard-is-hover, .zeroclipboard-is-active {
color: #333333;
text-decoration: none;
background-color: #ebebeb;
border-color: #adadad;
}

.zeroclipboard-is-active {
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}

0 comments on commit ff0a32a

Please sign in to comment.