From ff0a32af1e66a00e21221c28d1133c06f39a8b0f Mon Sep 17 00:00:00 2001 From: Michael Fogleman Date: Wed, 8 Jan 2014 15:57:42 -0500 Subject: [PATCH] intial commit --- .gitignore | 6 + auth/__init__.py | 13 ++ auth/config.py | 13 ++ auth/email.py | 8 ++ auth/forms.py | 39 ++++++ auth/hooks.py | 40 ++++++ auth/models.py | 84 +++++++++++++ auth/static/ZeroClipboard.min.js | 9 ++ auth/static/ZeroClipboard.swf | Bin 0 -> 1966 bytes auth/static/favicon.ico | Bin 0 -> 5430 bytes auth/static/icon.png | Bin 0 -> 65324 bytes auth/static/style.css | 89 ++++++++++++++ auth/templates/access_tokens.html | 46 +++++++ auth/templates/account_created_email.txt | 5 + auth/templates/base.html | 50 ++++++++ auth/templates/flash_token.html | 16 +++ auth/templates/identity_create.html | 20 +++ auth/templates/identity_tokens.html | 57 +++++++++ auth/templates/index.html | 10 ++ auth/templates/login.html | 30 +++++ auth/templates/util.html | 51 ++++++++ auth/util.py | 7 ++ auth/views.py | 150 +++++++++++++++++++++++ main.py | 5 + 24 files changed, 748 insertions(+) create mode 100644 .gitignore create mode 100644 auth/__init__.py create mode 100644 auth/config.py create mode 100644 auth/email.py create mode 100644 auth/forms.py create mode 100644 auth/hooks.py create mode 100644 auth/models.py create mode 100644 auth/static/ZeroClipboard.min.js create mode 100644 auth/static/ZeroClipboard.swf create mode 100644 auth/static/favicon.ico create mode 100644 auth/static/icon.png create mode 100644 auth/static/style.css create mode 100644 auth/templates/access_tokens.html create mode 100644 auth/templates/account_created_email.txt create mode 100644 auth/templates/base.html create mode 100644 auth/templates/flash_token.html create mode 100644 auth/templates/identity_create.html create mode 100644 auth/templates/identity_tokens.html create mode 100644 auth/templates/index.html create mode 100644 auth/templates/login.html create mode 100644 auth/templates/util.html create mode 100644 auth/util.py create mode 100644 auth/views.py create mode 100644 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce3d77a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.pyc +*.db +*.wsgi +config_local.py +env + diff --git a/auth/__init__.py b/auth/__init__.py new file mode 100644 index 0000000..1ebdad0 --- /dev/null +++ b/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 diff --git a/auth/config.py b/auth/config.py new file mode 100644 index 0000000..827e35f --- /dev/null +++ b/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 diff --git a/auth/email.py b/auth/email.py new file mode 100644 index 0000000..ef39a0e --- /dev/null +++ b/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) diff --git a/auth/forms.py b/auth/forms.py new file mode 100644 index 0000000..6ba39bc --- /dev/null +++ b/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()]) diff --git a/auth/hooks.py b/auth/hooks.py new file mode 100644 index 0000000..a54af7e --- /dev/null +++ b/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 diff --git a/auth/models.py b/auth/models.py new file mode 100644 index 0000000..3642381 --- /dev/null +++ b/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) diff --git a/auth/static/ZeroClipboard.min.js b/auth/static/ZeroClipboard.min.js new file mode 100644 index 0000000..bfea725 --- /dev/null +++ b/auth/static/ZeroClipboard.min.js @@ -0,0 +1,9 @@ +/*! +* ZeroClipboard +* The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. +* Copyright (c) 2013 Jon Rohan, James M. Greene +* Licensed MIT +* http://zeroclipboard.org/ +* v1.2.3 +*/ +!function(){"use strict";var a,b=function(){var a=/\-([a-z])/g,b=function(a,b){return b.toUpperCase()};return function(c){return c.replace(a,b)}}(),c=function(a,c){var d,e,f,g,h,i;if(window.getComputedStyle?d=window.getComputedStyle(a,null).getPropertyValue(c):(e=b(c),d=a.currentStyle?a.currentStyle[e]:a.style[e]),"cursor"===c&&(!d||"auto"===d))for(f=a.tagName.toLowerCase(),g=["a"],h=0,i=g.length;i>h;h++)if(f===g[h])return"pointer";return d},d=function(a){if(p.prototype._singleton){a||(a=window.event);var b;this!==window?b=this:a.target?b=a.target:a.srcElement&&(b=a.srcElement),p.prototype._singleton.setCurrent(b)}},e=function(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c)},f=function(a,b,c){a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent&&a.detachEvent("on"+b,c)},g=function(a,b){if(a.addClass)return a.addClass(b),a;if(b&&"string"==typeof b){var c=(b||"").split(/\s+/);if(1===a.nodeType)if(a.className){for(var d=" "+a.className+" ",e=a.className,f=0,g=c.length;g>f;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}else a.className=b}return a},h=function(a,b){if(a.removeClass)return a.removeClass(b),a;if(b&&"string"==typeof b||void 0===b){var c=(b||"").split(/\s+/);if(1===a.nodeType&&a.className)if(b){for(var d=(" "+a.className+" ").replace(/[\n\t]/g," "),e=0,f=c.length;f>e;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}else a.className=""}return a},i=function(){var a,b,c,d=1;return"function"==typeof document.body.getBoundingClientRect&&(a=document.body.getBoundingClientRect(),b=a.right-a.left,c=document.body.offsetWidth,d=Math.round(100*(b/c))/100),d},j=function(a){var b={left:0,top:0,width:0,height:0,zIndex:999999999},d=c(a,"z-index");if(d&&"auto"!==d&&(b.zIndex=parseInt(d,10)),a.getBoundingClientRect){var e,f,g,h=a.getBoundingClientRect();"pageXOffset"in window&&"pageYOffset"in window?(e=window.pageXOffset,f=window.pageYOffset):(g=i(),e=Math.round(document.documentElement.scrollLeft/g),f=Math.round(document.documentElement.scrollTop/g));var j=document.documentElement.clientLeft||0,k=document.documentElement.clientTop||0;b.left=h.left+e-j,b.top=h.top+f-k,b.width="width"in h?h.width:h.right-h.left,b.height="height"in h?h.height:h.bottom-h.top}return b},k=function(a,b){var c=!(b&&b.useNoCache===!1);return c?(-1===a.indexOf("?")?"?":"&")+"nocache="+(new Date).getTime():""},l=function(a){var b=[],c=[];return a.trustedOrigins&&("string"==typeof a.trustedOrigins?c.push(a.trustedOrigins):"object"==typeof a.trustedOrigins&&"length"in a.trustedOrigins&&(c=c.concat(a.trustedOrigins))),a.trustedDomains&&("string"==typeof a.trustedDomains?c.push(a.trustedDomains):"object"==typeof a.trustedDomains&&"length"in a.trustedDomains&&(c=c.concat(a.trustedDomains))),c.length&&b.push("trustedOrigins="+encodeURIComponent(c.join(","))),"string"==typeof a.amdModuleId&&a.amdModuleId&&b.push("amdModuleId="+encodeURIComponent(a.amdModuleId)),"string"==typeof a.cjsModuleId&&a.cjsModuleId&&b.push("cjsModuleId="+encodeURIComponent(a.cjsModuleId)),b.join("&")},m=function(a,b){if(b.indexOf)return b.indexOf(a);for(var c=0,d=b.length;d>c;c++)if(b[c]===a)return c;return-1},n=function(a){if("string"==typeof a)throw new TypeError("ZeroClipboard doesn't accept query strings.");return a.length?a:[a]},o=function(a,b,c,d,e){e?window.setTimeout(function(){a.call(b,c,d)},0):a.call(b,c,d)},p=function(a,b){if(a&&(p.prototype._singleton||this).glue(a),p.prototype._singleton)return p.prototype._singleton;p.prototype._singleton=this,this.options={};for(var c in s)this.options[c]=s[c];for(var d in b)this.options[d]=b[d];this.handlers={},p.detectFlashSupport()&&v()},q=[];p.prototype.setCurrent=function(b){a=b,this.reposition();var d=b.getAttribute("title");d&&this.setTitle(d);var e=this.options.forceHandCursor===!0||"pointer"===c(b,"cursor");return r.call(this,e),this},p.prototype.setText=function(a){return a&&""!==a&&(this.options.text=a,this.ready()&&this.flashBridge.setText(a)),this},p.prototype.setTitle=function(a){return a&&""!==a&&this.htmlBridge.setAttribute("title",a),this},p.prototype.setSize=function(a,b){return this.ready()&&this.flashBridge.setSize(a,b),this},p.prototype.setHandCursor=function(a){return a="boolean"==typeof a?a:!!a,r.call(this,a),this.options.forceHandCursor=a,this};var r=function(a){this.ready()&&this.flashBridge.setHandCursor(a)};p.version="1.2.3";var s={moviePath:"ZeroClipboard.swf",trustedOrigins:null,text:null,hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",allowScriptAccess:"sameDomain",useNoCache:!0,forceHandCursor:!1};p.setDefaults=function(a){for(var b in a)s[b]=a[b]},p.destroy=function(){p.prototype._singleton.unglue(q);var a=p.prototype._singleton.htmlBridge;a.parentNode.removeChild(a),delete p.prototype._singleton},p.detectFlashSupport=function(){var a=!1;if("function"==typeof ActiveXObject)try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash")&&(a=!0)}catch(b){}return!a&&navigator.mimeTypes["application/x-shockwave-flash"]&&(a=!0),a};var t=null,u=null,v=function(){var a,b,c=p.prototype._singleton,d=document.getElementById("global-zeroclipboard-html-bridge");if(!d){var e={};for(var f in c.options)e[f]=c.options[f];e.amdModuleId=t,e.cjsModuleId=u;var g=l(e),h=' ';d=document.createElement("div"),d.id="global-zeroclipboard-html-bridge",d.setAttribute("class","global-zeroclipboard-container"),d.setAttribute("data-clipboard-ready",!1),d.style.position="absolute",d.style.left="-9999px",d.style.top="-9999px",d.style.width="15px",d.style.height="15px",d.style.zIndex="9999",d.innerHTML=h,document.body.appendChild(d)}c.htmlBridge=d,a=document["global-zeroclipboard-flash-bridge"],a&&(b=a.length)&&(a=a[b-1]),c.flashBridge=a||d.children[0].lastElementChild};p.prototype.resetBridge=function(){return this.htmlBridge.style.left="-9999px",this.htmlBridge.style.top="-9999px",this.htmlBridge.removeAttribute("title"),this.htmlBridge.removeAttribute("data-clipboard-text"),h(a,this.options.activeClass),a=null,this.options.text=null,this},p.prototype.ready=function(){var a=this.htmlBridge.getAttribute("data-clipboard-ready");return"true"===a||a===!0},p.prototype.reposition=function(){if(!a)return!1;var b=j(a);return this.htmlBridge.style.top=b.top+"px",this.htmlBridge.style.left=b.left+"px",this.htmlBridge.style.width=b.width+"px",this.htmlBridge.style.height=b.height+"px",this.htmlBridge.style.zIndex=b.zIndex+1,this.setSize(b.width,b.height),this},p.dispatch=function(a,b){p.prototype._singleton.receiveEvent(a,b)},p.prototype.on=function(a,b){for(var c=a.toString().split(/\s/g),d=0;dhUFz0%Eh51{zZWW59La8(h8ngb96VSt1$GdQBQE(^kv2bQ-xU9M1X; zSATs#_t~szQ@^oP=qpAxffd+ndBt$+OkK0tp?X7I>D$NJ9hFGg=m)xdDoq+$&*^Tn z(DGcPtwT&ExQdNWn?x>J?YeFG4dyMko!5?g(AlDoMmoC3Xy6WNe88f0n>u@Ky2qAR zx8k*)9LGolwp`T%b)l%KrQ-ZTCyWSPDtxEfT?7tr8C5d>N-Uu=_x}C+XG#K2ScIDw zfBOu@m++f^EBD~U|Drr&o?Eu9R^6qZf&MtUqaXwe$nRx+D ztkz#KJxIkzFvs?zb5l0mw$D~0p5J#ZL);EL%h@CMT-RpQ5x3gSI`h&4+od}W49joX z^hn%pc~-#0dOHYQN76xShaCn<+B7!d9Bj63B=uMR2VQAL8-g$WwH&k6edb9Wt{rrs z3e=m#+-{5bEQtK2;rW0%M!D_zt{0)(*3X#~1wjM7k#VAKvMXJYdHI2m=RW9-LRe2d zXijGIbw?q-?}1X21++o~I(6M_J9<19*W;pTD)sh!p#2P|)n6z#RMlfYYg-<>{?)0a z7n<6)9|$TknpWZZ;ZRR+Hho(WRrMQqaNkV-Xh5gR9QoE%b)R~J-qkz3AWv)GR;K>X zFgo4ygn2&f-)$xT$PZXERi-Ukw`?o0m{0aQ8X~oWN9^cwcToF7y0X1cC>4u03-z{T z2bPoUxM~NM?T-#L*kY%_CI`Vv=h=21?X!;Jx&Pth{OP$^ zj7Os14MYz-8xoQHhGj+>2`ipQj|4w}k>_?c1ZrD*4sqOVo!U(HBMQ}J%7#k~=B+!X z%X_XHD0WX)S~MC~h|nJjyf&23sCw3(<@ll>4axDjI(l*oEP>iD_ zwQcvH;x-{`A$rvwDV_O?_DsXz^hZV7gU)VPx-ZumIA61DTZyB7Y8gQz8M6R@OREHJ#f_7?=?pLhx;j)$N5BF8J7i=pAyg*mOjpF z6Y|9D#N0$-qBt?1f65IBm`F0G@X1tqXgE_Pm?Iodut2a#FdR1c`8euMvhoXa8f z?$|s}F95wrF9B8QLqC*SH~W&|O*F4l#MVD_%gpVWEt3cXSih3r4yzn4Twc=rG@ z7ZS4aXAYddx%#(o9n~haoAe`~pV4bCrb6?&IlVT8&6zj&ugB)c`!hKSp=scLt$qIc z+(m?d)$XooSi842i?s@H;HNfc7Sxk!kvlf$dnWU-Nd}=1_`Xded2E(?N~KdurBh0& z@P+hHLJc`I69O?<5hvslkxEq?8p0E;{SrBUXw0vleRLc=}CNy&$ZY(rMfeW=~hK<+G zqUsqAPAowr8wyzQJXmGIVyI;! z?X|PG<4x${xdB{^@W}`t>Ep0N7G@#Tz8=seJs+b3xWoZY06&gHkRwqB(Ugzr4FD7l z`eV2YJOFw^*8xsOZBvfmWDfeQb`PAIW5*{TS0D13YY>2$>aUDPE2(HD^#RHspHv?v zz*UOFSh^Y(8LHu8?NPC|QLJqiYg@&d1ivrQh4^X^P|b}*Q#$x>^DZF&1s$r7C*YyC Ap#T5? literal 0 HcmV?d00001 diff --git a/auth/static/favicon.ico b/auth/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f3823208e808208c2a895a87477e51208ffbc5d3 GIT binary patch literal 5430 zcmdT{OH7-2Vc9gqP4qqIlI3-SWz#g6pwi?JKYmg|s{%8Nb92JCo?JXBG9gq*1<;cNz( zDJv`6%{l0x>aqfLrsh~7RUIv$6D3EKy$ZJbR%yN(SG`#PdqFKeKAt*0`WXsu_{>@5+lkUQk-UCiC~>3RBY;#caOTB4Epb%rm1!MP3U zn_i<{M)$49RO-rnq8rG_@=$x%dFFpe1Jld&{k^RKz8P5};NtaF_Q^+mGd{XJwHkDX zgyb|j-Ssu~-dv8oRqm!}FUoIJZgNQ;&nsC#F(DQX2J5J*458 zWxDqB6FPU&OIPo2DTocM4TU*xPfl0o2XFJ;?<~{koR82Z9&FKd#uc^$8)zf!2jZaL z-_G2yk>of`qkMki$1R#PfiY~r7sO?h&tso{KEd=K>%fhl_{{xvdjIt2l=8Yut;sX# zt><4o;0)Q9n0|-=b;g;a#>h|gYmjrWFa{JR_H2Xr_yBfu|yps(coWwY6$icd{Vjrz^N zpZfaxsNxbA7e~ms1@5e%yemF{A30d^a3$x@2)Q_%k74g@SyZt{&IW{6`pTpEDu+uR z2fsW+53Nv?tMWk1{Ll+o&UD59HfsgZ4bU6V4gfg7E&Mt+^aS`tQ{WlaOl=WO{K<9D zNuKOXx;(k4bVv8iWvZ{O3*v`;0J@{fdz{{@Jw?az-HNN6-;MZ$=Xg)mtZlGniF3#l zdIy?CG>_<>fc7!<4>VB!?Nz0Nzz;3d-uZ>nLzp85_?;+SROt5yK7(1lEKcW{elv+`T@o37>H2l)GMuPIF@ zx~}JzU)%Lg`$F`c)q$$o4#h2+PjsJE`;Fe+2xv(0VVJcQG}Pd%Pw7EuLZF`kZ3%71 zh^VvS3-l)L{%`!@bg1Z2U6Tqwv}tb`eX8Fdc^-NV+@e`Uw~BTZ{R#~$I`-1UBc+3N z{tLs4O4AORx^`f8lVbsW8?29YevF})KRRTx)#L z`Bg-Isb9oywEXJ-l4D@4Z>;maPd7sNot!L0Ab?)~#S zL=46s{Dzw4na(R?#2oS8H@{hrNlk|TRjJERn+?xz1nMZ^)O+v-zt8QGMi)x=3W*Y{zpb6UnK2UzzboB98=>M zJd1{p_w1T={By5QP5xc(w_|dCADi(l`-`!g%ZO(fC+VqSXBf0&7`Xy=B-eseo(Ext W=N8y`9t1vP-!W{oV9XD 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 D)w{pT literal 0 HcmV?d00001 diff --git a/auth/static/style.css b/auth/static/style.css new file mode 100644 index 0000000..1cfd7e2 --- /dev/null +++ b/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); +} diff --git a/auth/templates/access_tokens.html b/auth/templates/access_tokens.html new file mode 100644 index 0000000..0699c99 --- /dev/null +++ b/auth/templates/access_tokens.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} + +{% block content %} + +
+ + + + + + + + + + + {% if access_tokens %} + {% for access_token in access_tokens %} + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
TimestampClientServerIdentity Token
{{ access_token.client_timestamp.strftime('%Y-%m-%d %H:%M:%S') }}{{ access_token.client_addr }}{{ access_token.server_addr }} + {{ access_token.identity_token.name }} + {% if not access_token.identity_token.enabled %} + * + {% endif %} +
+ No logins have yet been recorded. +
+

+ Note: Only the most recent 100 accesses are listed. Deleted identity tokens are marked with an asterisk. +

+
+{% endblock %} diff --git a/auth/templates/account_created_email.txt b/auth/templates/account_created_email.txt new file mode 100644 index 0000000..626c512 --- /dev/null +++ b/auth/templates/account_created_email.txt @@ -0,0 +1,5 @@ +Hello, {{ user.username }}. Thank you for registering. + +Please visit the following link to verify your e-mail address: + +{{ user.get_verification_link() }} diff --git a/auth/templates/base.html b/auth/templates/base.html new file mode 100644 index 0000000..e1449ef --- /dev/null +++ b/auth/templates/base.html @@ -0,0 +1,50 @@ +{% from "util.html" import nav_link with context %} + + + + + + + + Craft Login Server + + + + + + + + + + +
+
+ + +

+ Craft Login Server +

+
+
+ {% for message in get_flashed_messages(category_filter=["message"]) %} +
{{ message }}
+ {% endfor %} + {% block content %} + {% endblock %} +
+ +
+ + diff --git a/auth/templates/flash_token.html b/auth/templates/flash_token.html new file mode 100644 index 0000000..69ca549 --- /dev/null +++ b/auth/templates/flash_token.html @@ -0,0 +1,16 @@ +{% for token in get_flashed_messages(category_filter=["token"]) %} +
+ Important!
+ Copy and paste the following line into the Craft game window. +
/identity {{ g.user.username }} {{ token }}
+ + +
+{% endfor %} diff --git a/auth/templates/identity_create.html b/auth/templates/identity_create.html new file mode 100644 index 0000000..9ce2e04 --- /dev/null +++ b/auth/templates/identity_create.html @@ -0,0 +1,20 @@ +{% from "util.html" import render_field_grid %} + +{% extends "base.html" %} + +{% block content %} +
+

Create Token

+
+

Your token will be used instead of a password to authenticate with the login server during gameplay. Enter a name for the token below so you can keep track of where you used it and revoke the token if necessary.

+

Examples: Home, Work, Michael's iMac

+ {{ form.hidden_tag() }} + {{ render_field_grid(form.name, "col-sm-6", required="", autofocus="") }} +
+
+ +
+
+
+
+{% endblock %} diff --git a/auth/templates/identity_tokens.html b/auth/templates/identity_tokens.html new file mode 100644 index 0000000..3196870 --- /dev/null +++ b/auth/templates/identity_tokens.html @@ -0,0 +1,57 @@ + +
+ {% for identity_token in identity_tokens %} + + {% endfor %} + + + + + {% if identity_tokens %} + {% for identity_token in identity_tokens %} + + + + {% endfor %} + {% else %} + + + + {% endif %} +
+ Identity Tokens + Create Token +
+ {{ identity_token.name }} + Delete + +
+ Click "Create Token" to create a new identity token. +
+
+
+
+

What are identity tokens?

+

Identity tokens are used in place of a password for authenticating in the game. Identity tokens are "installed" once and then you don't have to worry about them. You can create multiple identity tokens if you have the game installed on multiple computers. You can revoke any identity token at any time.

+
diff --git a/auth/templates/index.html b/auth/templates/index.html new file mode 100644 index 0000000..e7949cc --- /dev/null +++ b/auth/templates/index.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} + {% if g.user %} + {% include 'flash_token.html' %} + {% include 'identity_tokens.html' %} + {% else %} + {% include 'login.html' %} + {% endif %} +{% endblock %} diff --git a/auth/templates/login.html b/auth/templates/login.html new file mode 100644 index 0000000..42cbc07 --- /dev/null +++ b/auth/templates/login.html @@ -0,0 +1,30 @@ +{% from "util.html" import render_field %} + +
+
+ + +
+
+ +
+
+
+
+

Why register?

+

You can play on most game servers anonymously. However, without registering you will not be able to make changes in most areas of the world. After you are registered, game server admins can grant you various types of permissions.

+
diff --git a/auth/templates/util.html b/auth/templates/util.html new file mode 100644 index 0000000..3127711 --- /dev/null +++ b/auth/templates/util.html @@ -0,0 +1,51 @@ +{%- macro nav_link(label, endpoint) -%} +
  • + {{ label }} +
  • +{%- endmacro -%} + +{% macro render_field(field) %} +
    + {{ field(placeholder=field.label.text, class="form-control", **kwargs) }} + {% for error in field.errors %} +

    {{ error }}

    + {% endfor %} +
    +{% endmacro %} + +{% macro render_field_grid(field, classes) %} +
    +
    + {{ field(placeholder=field.label.text, class="form-control", **kwargs) }} + {% for error in field.errors %} +

    {{ error }}

    + {% endfor %} +
    +
    +{% endmacro %} + +{% macro render_pagination(pagination, endpoint) %} +
      + {% if pagination.has_prev %} +
    • «
    • + {% else %} +
    • «
    • + {% endif %} + {%- for page in pagination.iter_pages() %} + {% if page %} + {% if page == pagination.page %} +
    • {{ page }}
    • + {% else %} +
    • {{ page }}
    • + {% endif %} + {% else %} +
    • ...
    • + {% endif %} + {%- endfor %} + {% if pagination.has_next %} +
    • »
    • + {% else %} +
    • »
    • + {% endif %} +
    +{% endmacro %} diff --git a/auth/util.py b/auth/util.py new file mode 100644 index 0000000..f11fd55 --- /dev/null +++ b/auth/util.py @@ -0,0 +1,7 @@ +from auth import app +from itsdangerous import URLSafeSerializer + +def get_serializer(secret_key=None): + if secret_key is None: + secret_key = app.secret_key + return URLSafeSerializer(secret_key) diff --git a/auth/views.py b/auth/views.py new file mode 100644 index 0000000..8cdd4ae --- /dev/null +++ b/auth/views.py @@ -0,0 +1,150 @@ +from flask import render_template, url_for, redirect, g, request, flash, session, abort +from werkzeug.security import generate_password_hash +from auth import app, db +from forms import LoginForm, RegistrationForm, IdentityTokenForm +from hooks import login_required +from models import User, IdentityToken, AccessToken +from util import get_serializer +import datetime +import email +import uuid + +# Views +@app.route('/', methods=['GET', 'POST']) +def index(): + if g.user is None: + login_form = LoginForm(prefix="login") + registration_form = RegistrationForm(prefix="register") + button = request.form.get('button') + if button == 'login' and login_form.validate_on_submit(): + user = login_form.user + user.touch() + session['username'] = user.username + return redirect(request.args.get('next', url_for('index'))) + elif button == 'register' and registration_form.validate_on_submit(): + count = User.query.count() + user = User( + registration_form.username.data, + generate_password_hash(registration_form.password.data), + registration_form.email.data, + False, + True, + bool(count == 0), + ) + db.session.add(user) + db.session.flush() + email.send_account_created_email(user) + db.session.commit() + session['username'] = user.username + flash('Registration successful! Please check your e-mail so we can verify your address.') + return redirect(url_for('index')) + else: + return render_template('index.html', + login_form=login_form, + registration_form=registration_form) + else: + identity_tokens = list(g.user.identity_tokens.filter_by(enabled=True)) + return render_template('index.html', identity_tokens=identity_tokens) + +@app.route('/logout') +def logout(): + session.pop('username', None) + return redirect(url_for('index')) + +@app.route('/verify/') +@login_required +def verify_email(payload): + try: + user_id = get_serializer().loads(payload) + except BadSignature: + abort(404) + user = User.query.get_or_404(user_id) + if user != g.user: + abort(403) + user.verified = True + db.session.commit() + flash('E-mail verification successful - thank you!') + return redirect(url_for('index')) + +@app.route('/access') +@login_required +def access(): + access_tokens = list(g.user.access_tokens.order_by( + db.desc(AccessToken.client_timestamp)).limit(100)) + return render_template('access_tokens.html', access_tokens=access_tokens) + +@app.route('/identity/create', methods=['GET', 'POST']) +@login_required +def identity_create(): + form = IdentityTokenForm() + if form.validate_on_submit(): + token = uuid.uuid4().hex + flash(token, 'token') + identity_token = IdentityToken( + g.user, + form.name.data, + generate_password_hash(token), + True) + db.session.add(identity_token) + db.session.commit() + return redirect(url_for('index')) + return render_template('identity_create.html', form=form) + +@app.route('/identity/delete/', methods=['POST']) +@login_required +def identity_delete(identity_token_id): + identity_token = IdentityToken.query.get_or_404(identity_token_id) + if identity_token.user != g.user or not identity_token.enabled: + abort(403) + identity_token.touch() + identity_token.enabled = False + db.session.commit() + flash('Identity token successfully deleted.') + return redirect(url_for('index')) + +@app.route('/api/1/identity', methods=['POST']) +def api_identity(): + form = request.form + user = User.query.filter_by(username=form['username']).first() + if user is None: + abort(403) + for identity_token in user.identity_tokens.filter_by(enabled=True): + if identity_token.check_token(form['identity_token']): + break + else: + abort(403) + identity_token.touch() + token = uuid.uuid4().hex + access_token = AccessToken( + identity_token, + user, + generate_password_hash(token), + True, + request.remote_addr, + datetime.datetime.utcnow(), + None, + None) + db.session.add(access_token) + db.session.commit() + return token + +@app.route('/api/1/access', methods=['POST']) +def api_access(): + form = request.form + user = User.query.filter_by(username=form['username']).first() + if user is None: + abort(403) + access_tokens = list(user.access_tokens.filter_by(enabled=True)) + for access_token in access_tokens: + access_token.enabled = False + db.session.commit() + max_age = datetime.timedelta(minutes=1) + for access_token in access_tokens: + if access_token.check_token(form['access_token'], max_age): + break + else: + abort(403) + access_token.server_addr = request.remote_addr + access_token.server_timestamp = datetime.datetime.utcnow() + db.session.commit() + return str(user.user_id) diff --git a/main.py b/main.py new file mode 100644 index 0000000..8108eed --- /dev/null +++ b/main.py @@ -0,0 +1,5 @@ +from auth import app, db + +if __name__ == '__main__': + db.create_all() + app.run(debug=True)