This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Use cross domain https for slightly safer login.

Login UI code has been simplified and moved into the client side. CORS
is used for the cross-domain POST if available, otherwise an iframe and
cookie polling technique is used. Start fleshing out the new JS tree. :)
  • Loading branch information...
chromakode committed Oct 6, 2011
1 parent edbefbc commit b8477570692084da4f110d1405488bb12f70adb1
View
@@ -22,7 +22,7 @@
# Javascript files to be compressified
js_libs = $(addprefix lib/,json2.js jquery.cookie.js jquery.url.js ui.core.js ui.datepicker.js jquery.flot.js jquery.lazyload.js)
-js_sources = $(js_libs) jquery.reddit.js reddit.js base.js sponsored.js compact.js blogbutton.js flair.js analytics.js
+js_sources = $(js_libs) jquery.reddit.js reddit.js login.js ui.js base.js sponsored.js compact.js blogbutton.js flair.js analytics.js
js_targets = button.js jquery.flot.js sponsored.js
localized_js_targets = reddit.js mobile.js
localized_js_outputs = $(localized_js_targets:.js=.*.js)
View
@@ -85,6 +85,8 @@ display_timezone = MST
shutdown_secret = 12345
# list of servers that the service monitor will care about
monitored_servers = reddit, localhost
+# https api endpoint (must be g.domain or a subdomain of g.domain)
+https_endpoint =
# name of the cookie to drop with login information
login_cookie = reddit_session
View
@@ -20,7 +20,7 @@
# CondeNet, Inc. All Rights Reserved.
################################################################################
from reddit_base import RedditController, MinimalController, set_user_cookie
-from reddit_base import paginated_listing
+from reddit_base import cross_domain, paginated_listing
from pylons.i18n import _
from pylons import c, request, response
@@ -110,7 +110,7 @@ def GET_info(self, link1, link2, count):
@json_validate()
- def GET_me(self):
+ def GET_me(self, responder):
if c.user_is_loggedin:
return Wrapped(c.user).render()
else:
@@ -353,60 +353,53 @@ def POST_fetch_title(self, form, jquery, url):
else:
form.set_html(".title-status", _("no title found"))
- def _login(self, form, user, dest='', rem = None):
+ def _login(self, responder, user, rem = None):
"""
AJAX login handler, used by both login and register to set the
user cookie and send back a redirect.
"""
self.login(user, rem = rem)
- form._send_data(modhash = user.modhash())
- form._send_data(cookie = user.make_cookie())
- dest = dest or request.referer or '/'
- form.redirect(dest)
+ if request.params.get("hoist") != "cookie":
+ responder._send_data(modhash = user.modhash())
+ responder._send_data(cookie = user.make_cookie())
+ @cross_domain([g.origin, g.https_endpoint], allow_credentials=True)
@validatedForm(VDelay("login"),
user = VLogin(['user', 'passwd']),
username = VLength('user', max_length = 100),
- dest = VDestination(),
- rem = VBoolean('rem'),
- reason = VReason('reason'))
- def POST_login(self, form, jquery, user, username, dest, rem, reason):
- if form.has_errors('vdelay', errors.RATELIMIT):
- jquery(".recover-password").addClass("attention")
+ rem = VBoolean('rem'))
+ def POST_login(self, form, responder, user, username, rem):
+ if responder.has_errors('vdelay', errors.RATELIMIT):
return
- if reason and reason[0] == 'redirect':
- dest = reason[1]
-
- if login_throttle(username, wrong_password = form.has_errors("passwd",
+ if login_throttle(username, wrong_password = responder.has_errors("passwd",
errors.WRONG_PASSWORD)):
VDelay.record_violation("login", seconds=1, growfast=True)
- jquery(".recover-password").addClass("attention")
c.errors.add(errors.WRONG_PASSWORD, field = "passwd")
- if not form.has_errors("passwd", errors.WRONG_PASSWORD):
- self._login(form, user, dest, rem)
+ if not responder.has_errors("passwd", errors.WRONG_PASSWORD):
+ self._login(responder, user, rem)
+ @cross_domain([g.origin, g.https_endpoint], allow_credentials=True)
@validatedForm(VCaptcha(),
VRatelimit(rate_ip = True, prefix = "rate_register_"),
name = VUname(['user']),
email = ValidEmails("email", num = 1),
password = VPassword(['passwd', 'passwd2']),
- dest = VDestination(),
- rem = VBoolean('rem'),
- reason = VReason('reason'))
- def POST_register(self, form, jquery, name, email,
- password, dest, rem, reason):
- if not (form.has_errors("user", errors.BAD_USERNAME,
+ rem = VBoolean('rem'))
+ def POST_register(self, form, responder, name, email,
+ password, rem):
+ bad_captcha = responder.has_errors('captcha', errors.BAD_CAPTCHA)
+ if not (responder.has_errors("user", errors.BAD_USERNAME,
errors.USERNAME_TAKEN_DEL,
errors.USERNAME_TAKEN) or
- form.has_errors("email", errors.BAD_EMAILS) or
- form.has_errors("passwd", errors.BAD_PASSWORD) or
- form.has_errors("passwd2", errors.BAD_PASSWORD_MATCH) or
- form.has_errors('ratelimit', errors.RATELIMIT) or
- (not g.disable_captcha and form.has_errors('captcha', errors.BAD_CAPTCHA))):
-
+ responder.has_errors("email", errors.BAD_EMAILS) or
+ responder.has_errors("passwd", errors.BAD_PASSWORD) or
+ responder.has_errors("passwd2", errors.BAD_PASSWORD_MATCH) or
+ responder.has_errors('ratelimit', errors.RATELIMIT) or
+ (not g.disable_captcha and bad_captcha)):
+
user = register(name, password)
VRatelimit.ratelimit(rate_ip = True, prefix = "rate_register_")
@@ -427,14 +420,7 @@ def POST_register(self, form, jquery, name, email,
user._commit()
c.user = user
- if reason:
- if reason[0] == 'redirect':
- dest = reason[1]
- elif reason[0] == 'subscribe':
- for sr, sub in reason[1].iteritems():
- self._subscribe(sr, sub)
-
- self._login(form, user, dest, rem)
+ self._login(responder, user, rem)
@noresponse(VUser(),
VModhash(),
@@ -19,7 +19,7 @@
# All portions of the code written by CondeNet are Copyright (c) 2006-2010
# CondeNet, Inc. All Rights Reserved.
################################################################################
-from pylons import c, request, g
+from pylons import c, g, request, response
from pylons.i18n import _
from pylons.controllers.util import abort
from r2.lib import utils, captcha, promote
@@ -154,14 +154,15 @@ def wrap(response_function):
def _api_validate(*simple_vals, **param_vals):
def val(fn):
def newfn(self, *a, **env):
- c.render_style = api_type(request.params.get("renderstyle",
- response_type))
- c.response_content_type = 'application/json; charset=UTF-8'
+ c.render_style = api_type(request.params.get("renderstyle", response_type))
# generate a response object
- if response_type is None or request.params.get('api_type') == "json":
- responder = JsonResponse()
- else:
+ if response_type == "html" and not request.params.get('api_type') == "json":
responder = JQueryResponse()
+ else:
+ responder = JsonResponse()
+
+ c.response_content_type = responder.content_type
+
try:
kw = _make_validated_kw(fn, simple_vals, param_vals, env)
return response_function(self, fn, responder,
@@ -191,8 +192,11 @@ def textresponse(self, self_method, responder, simple_vals, param_vals, *a, **kw
def json_validate(self, self_method, responder, simple_vals, param_vals, *a, **kw):
if c.extension != 'json':
abort(404)
- r = self_method(self, *a, **kw)
- return self.api_wrapper(r)
+
+ val = self_method(self, responder, *a, **kw)
+ if not val:
+ val = responder.make_response()
+ return self.api_wrapper(val)
@api_validate("html")
def validatedForm(self, self_method, responder, simple_vals, param_vals,
@@ -1234,38 +1238,6 @@ def run(self, val):
else:
return val
-class VReason(Validator):
- def run(self, reason):
- if not reason:
- return
-
- if reason.startswith('redirect_'):
- dest = reason[9:]
- if (not dest.startswith(c.site.path) and
- not dest.startswith("http:")):
- dest = (c.site.path + dest).replace('//', '/')
- return ('redirect', dest)
- if reason.startswith('vote_'):
- fullname = reason[5:]
- t = Thing._by_fullname(fullname, data=True)
- return ('redirect', t.make_permalink_slow())
- elif reason.startswith('share_'):
- fullname = reason[6:]
- t = Thing._by_fullname(fullname, data=True)
- return ('redirect', t.make_permalink_slow())
- elif reason.startswith('reply_'):
- fullname = reason[6:]
- t = Thing._by_fullname(fullname, data=True)
- return ('redirect', t.make_permalink_slow())
- elif reason.startswith('sr_change_'):
- sr_list = reason[10:].split(',')
- fullnames = dict(i.split(':') for i in sr_list)
- srs = Subreddit._by_fullname(fullnames.keys(), data = True,
- return_dict = False)
- sr_onoff = dict((sr, fullnames[sr._fullname] == 1) for sr in srs)
- return ('subscribe', sr_onoff)
-
-
class ValidEmails(Validator):
"""Validates a list of email addresses passed in as a string and
delineated by whitespace, ',' or ';'. Also validates quantity of
View
@@ -286,6 +286,8 @@ def reset_caches():
self.origin = "http://" + self.domain
self.secure_domains = set([urlparse(self.payment_domain).netloc])
+ if self.https_endpoint:
+ self.secure_domains.add(urlparse(self.https_endpoint).netloc)
# load the unique hashed names of files under static
static_files = os.path.join(self.paths.get('static_files'), 'static')
View
@@ -187,6 +187,8 @@ def use(self):
"lib/jquery.url.js",
"jquery.reddit.js",
"base.js",
+ "ui.js",
+ "login.js",
"analytics.js",
"flair.js",
"reddit.js",
@@ -44,6 +44,9 @@ class JsonResponse(object):
in the api func's validators, as well as blobs of data set by the
api func.
"""
+
+ content_type = 'application/json; charset=UTF-8'
+
def __init__(self):
self._clear()
@@ -67,7 +70,7 @@ def make_response(self):
res = {}
if self._data:
res['data'] = self._data
- res['errors'] = [(e[0], c.errors[e].message) for e in self._errors]
+ res['errors'] = [(e[0], c.errors[e].message, e[1]) for e in self._errors]
return {"json": res}
def set_error(self, error_name, field_name):
View
@@ -44,6 +44,8 @@
banned_by = "removed by %s",
banned = "removed",
reports = "reports: %d",
+
+ submitting = _("submitting..."),
# this accomodates asian languages which don't use spaces
number_label = _("%(num)d %(thing)s"),
@@ -377,6 +377,10 @@ body[orient="landscape"] > #topbar > h1 { margin-left: -125px; width: 250px; }
.loading img { -webkit-animation-name: rotateThis; -webkit-animation-duration: .5s; -webkit-animation-iteration-count: infinite; -webkit-animation-timing-function: linear; }
+.throbber { display: none; margin: 0 2px; background: url("/static/compact/throbber.gif") no-repeat; width: 18px; height: 18px; }
+
+.working .throbber { display: inline-block; }
+
/* Login and Register */
#login_login, #login_reg { background: white; border: 1px solid #d9d9d9; margin: 10px; -webkit-border-bottom-left-radius: 8px; -webkit-border-bottom-right-radius: 8px; -moz-border-radius-bottomleft: 8px; -moz-border-radius-bottomright: 8px; max-width: 350px; margin-left: auto; margin-right: auto; }
@@ -390,6 +394,8 @@ body[orient="landscape"] > #topbar > h1 { margin-left: -125px; width: 250px; }
#login_login > div > ul li input[type="checkbox"] + label, #login_reg > div > ul li input[type="checkbox"] + label { display: inline; }
+.user-form .submit * { vertical-align: middle; }
+
/* toolbar specific stuf here */
body.toolbar { margin: 0px; padding: 0px; overflow: hidden; }
@@ -1097,6 +1097,16 @@ padding: 5px;
-webkit-animation-iteration-count:infinite;
-webkit-animation-timing-function:linear;
}
+
+.throbber {
+ display: none;
+ margin: 0 2px;
+ background: url($static + 'throbber.gif') no-repeat;
+ width: 18px;
+ height: 18px;
+}
+.working .throbber { display: inline-block; }
+
/* Login and Register */
#login_login, #login_reg {
background: white;
@@ -1133,6 +1143,11 @@ padding: 5px;
#login_login > div > ul li input[type="checkbox"] + label, #login_reg > div > ul li input[type="checkbox"] + label {
display: inline;
}
+
+.user-form .submit * {
+ vertical-align: middle;
+}
+
/* toolbar specific stuf here */
body.toolbar {
margin: 0px;
Oops, something went wrong.

0 comments on commit b847757

Please sign in to comment.