mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-26 07:19:25 -05:00
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. :)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 @@ class ApiController(RedditController):
|
||||
|
||||
|
||||
@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 @@ class ApiController(RedditController):
|
||||
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 @@ class ApiController(RedditController):
|
||||
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 api_validate(response_type=None):
|
||||
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 @@ class VOneOf(Validator):
|
||||
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
|
||||
|
||||
@@ -286,6 +286,8 @@ class Globals(object):
|
||||
|
||||
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')
|
||||
|
||||
@@ -187,6 +187,8 @@ module["reddit"] = LocalizedModule("reddit.js",
|
||||
"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 @@ class JsonResponse(object):
|
||||
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):
|
||||
|
||||
@@ -44,6 +44,8 @@ string_dict = dict(
|
||||
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"),
|
||||
|
||||
1
r2/r2/public/static/compact/throbber.gif
Symbolic link
1
r2/r2/public/static/compact/throbber.gif
Symbolic link
@@ -0,0 +1 @@
|
||||
../throbber.gif
|
||||
@@ -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;
|
||||
|
||||
@@ -1412,21 +1412,36 @@ textarea.gray { color: gray; }
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.login-form-side input {
|
||||
border: 1px solid gray;
|
||||
width: 138px;
|
||||
.login-form-side input[type=text],
|
||||
.login-form-side input[type=password] {
|
||||
border: 1px solid #999;
|
||||
width: 137px;
|
||||
height: 17px;
|
||||
margin: 5px 0px 0px 5px;
|
||||
top: 5px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.login-form-side .error {
|
||||
margin: 5px;
|
||||
.login-form-side input[type=password] {
|
||||
width: 138px;
|
||||
}
|
||||
|
||||
#remember-me {
|
||||
margin: 5px;
|
||||
.login-form-side #remember-me,
|
||||
.login-form-side .submit {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.login-form-side .submit input[type=button] {
|
||||
margin:1px;
|
||||
}
|
||||
|
||||
.login-form-side #remember-me {
|
||||
float: left;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.login-form-side #remember-me * {
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
/*the checkbox*/
|
||||
@@ -1436,6 +1451,7 @@ textarea.gray { color: gray; }
|
||||
width: auto;
|
||||
border: none;
|
||||
margin-right: 5px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.login-form-side label {
|
||||
@@ -1444,13 +1460,31 @@ textarea.gray { color: gray; }
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.login-form-side button {
|
||||
.login-form-side .recover-password {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.login-form-side .status { display:none; }
|
||||
|
||||
.login-form-side .submit {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.login-form-side .submit *, .user-form .submit * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.status { margin-left: 5px; color: red; font-size: small;}
|
||||
.error { color: red; font-size: small; margin: 5px; }
|
||||
.throbber {
|
||||
display: none;
|
||||
margin: 0 2px;
|
||||
background: url(/static/throbber.gif) no-repeat;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.working .throbber { display: inline-block; }
|
||||
|
||||
.status { margin: 5px 0 0 5px; font-size: small;}
|
||||
.error { color: red; font-size: small; }
|
||||
.red { color:red }
|
||||
.buygold { color: #9A7D2E; font-weight: bold; }
|
||||
.line-through { text-decoration: line-through }
|
||||
@@ -1572,16 +1606,16 @@ label + #moresearchinfo {
|
||||
.legal {color: #808080; font-family: serif; font-size: small; margin-top: 20px; }
|
||||
.legal a {text-decoration: underline}
|
||||
|
||||
.divide { border-right: 2px solid #D3D3D3; }
|
||||
.divide { border-right: 2px solid #D3D3D3; margin-right: -2px; }
|
||||
|
||||
.loginform {
|
||||
.login-form-section {
|
||||
float: left;
|
||||
width: 45%;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
width: 46%;
|
||||
padding-left: 2%;
|
||||
padding-right: 2%;
|
||||
}
|
||||
|
||||
.loginform h3 {
|
||||
.login-form-section h3 {
|
||||
margin-bottom: 0;
|
||||
margin-top: 10px;
|
||||
font-size: large;
|
||||
@@ -1589,26 +1623,31 @@ label + #moresearchinfo {
|
||||
font-variant: small-caps;
|
||||
color: #404040;
|
||||
}
|
||||
.loginform p {
|
||||
.login-form-section p {
|
||||
text-align: left;
|
||||
margin-bottom: 10px;
|
||||
color: #606060;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.loginform label {
|
||||
|
||||
.user-form label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
.loginform .remember { display:inline; margin-left: 5px; }
|
||||
.loginform ul { margin: 5px; }
|
||||
.loginform li { margin-top: 5px; }
|
||||
.loginform p .btn { margin-top: 5px }
|
||||
.loginform input.logtxt { width: 125px; }
|
||||
.user-form .error {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.loginform input[type=text],
|
||||
.loginform input[type=password] {
|
||||
.user-form .remember { display:inline; margin-left: 5px; }
|
||||
.user-form ul { margin: 5px; }
|
||||
.user-form li { margin-top: 5px; }
|
||||
.user-form p .btn { margin-top: 5px }
|
||||
.user-form input.logtxt { width: 125px; }
|
||||
|
||||
.user-form input[type=text],
|
||||
.user-form input[type=password] {
|
||||
width: 125px;
|
||||
border: 1px solid #A0A0A0;
|
||||
margin-top: 2px;
|
||||
@@ -1616,10 +1655,14 @@ label + #moresearchinfo {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.loginform #captcha {
|
||||
.user-form #captcha {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.user-form .submit {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#passform h1 {margin-bottom: 0px}
|
||||
#passform p {margin-bottom: 5px; font-size: small}
|
||||
|
||||
@@ -1664,6 +1707,11 @@ label + #moresearchinfo {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.popup .close-popup {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.usertable { margin-left: 10px;}
|
||||
.usertable { font-size: larger }
|
||||
.usertable td, .usertable th { padding: 0 .7em }
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
r = {}
|
||||
r = r || {}
|
||||
|
||||
$(function() {
|
||||
r.login.ui.init()
|
||||
})
|
||||
|
||||
@@ -140,7 +140,6 @@ function showcover() {
|
||||
$(".login-popup:first").fadeIn()
|
||||
.find(".popup").css("top", $(window).scrollTop() + 75).end()
|
||||
.find(".cover").css("height", $(document).height()).end()
|
||||
.find("form input[name=reason]").attr("value", (reason || ""));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -259,10 +259,7 @@ rate_limit = function() {
|
||||
|
||||
$.fn.vote = function(vh, callback, event, ui_only) {
|
||||
/* for vote to work, $(this) should be the clicked arrow */
|
||||
if (!reddit.logged) {
|
||||
showcover(true, 'vote_' + $(this).thing_id());
|
||||
}
|
||||
else if($(this).hasClass("arrow")) {
|
||||
if (reddit.logged && $(this).hasClass("arrow")) {
|
||||
var dir = ( $(this).hasClass(up_cls) ? 1 :
|
||||
( $(this).hasClass(down_cls) ? -1 : 0) );
|
||||
var things = $(this).all_things_by_id();
|
||||
@@ -303,9 +300,6 @@ $.fn.vote = function(vh, callback, event, ui_only) {
|
||||
if(callback)
|
||||
callback(things, dir);
|
||||
}
|
||||
if(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.show_unvotable_message = function() {
|
||||
@@ -584,8 +578,7 @@ $.fn.captcha = function(iden) {
|
||||
/* */
|
||||
var c = this.find(".capimage");
|
||||
if(iden) {
|
||||
c.attr("src", "http://" + reddit.ajax_domain
|
||||
+ "/captcha/" + iden + ".png")
|
||||
c.attr("src", "/captcha/" + iden + ".png")
|
||||
.parents("form").find('input[name="iden"]').val(iden);
|
||||
}
|
||||
return c;
|
||||
|
||||
211
r2/r2/public/static/js/login.js
Normal file
211
r2/r2/public/static/js/login.js
Normal file
@@ -0,0 +1,211 @@
|
||||
r.login = {
|
||||
post: function(form, action, callback) {
|
||||
var username = $('input[name="user"]', form.$el).val(),
|
||||
endpoint = r.config.https_endpoint || ('http://'+r.config.ajax_domain),
|
||||
sameOrigin = location.protocol+'//'+location.host == endpoint,
|
||||
apiTarget = endpoint+'/api/'+action+'/'+username
|
||||
|
||||
if (sameOrigin || $.support.cors) {
|
||||
var params = form.serialize()
|
||||
params.push({name:'api_type', value:'json'})
|
||||
$.ajax({
|
||||
url: apiTarget,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: params,
|
||||
success: callback,
|
||||
error: function(xhr, err) {
|
||||
callback(false, err, xhr)
|
||||
},
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
var iframe = $('<iframe>'),
|
||||
postForm = form.$el.clone(true)
|
||||
|
||||
iframe
|
||||
.css('display', 'none')
|
||||
.appendTo('body')
|
||||
|
||||
var frameName = iframe[0].contentWindow.name = ('resp'+Math.random()).replace('.', '')
|
||||
|
||||
postForm
|
||||
.unbind()
|
||||
.css('display', 'none')
|
||||
.attr('action', apiTarget)
|
||||
.attr('target', frameName)
|
||||
.appendTo('body')
|
||||
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'hidden',
|
||||
name: 'api_type',
|
||||
value: 'json'
|
||||
})
|
||||
.appendTo(postForm)
|
||||
|
||||
$('<input>')
|
||||
.attr({
|
||||
type: 'hidden',
|
||||
name: 'hoist',
|
||||
value: r.login.hoist.type
|
||||
})
|
||||
.appendTo(postForm)
|
||||
|
||||
r.login.hoist.watch(action, function(result) {
|
||||
if (!r.config.debug) {
|
||||
iframe.remove()
|
||||
postForm.remove()
|
||||
}
|
||||
callback(result)
|
||||
})
|
||||
|
||||
postForm.submit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.login.hoist = {
|
||||
type: 'cookie',
|
||||
watch: function(name, callback) {
|
||||
var cookieName = 'hoist_'+name
|
||||
|
||||
var interval = setInterval(function() {
|
||||
data = $.cookie(cookieName)
|
||||
if (data) {
|
||||
try {
|
||||
data = JSON.parse(data)
|
||||
} catch(e) {
|
||||
data = null
|
||||
}
|
||||
$.cookie(cookieName, null, {domain:r.config.cur_domain})
|
||||
clearInterval(interval)
|
||||
callback(data)
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
r.login.ui = {
|
||||
init: function() {
|
||||
if (!r.config.logged) {
|
||||
$('.content form.login-form, .side form.login-form').each(function(i, el) {
|
||||
new r.ui.LoginForm(el)
|
||||
})
|
||||
|
||||
$('.content form.register-form').each(function(i, el) {
|
||||
new r.ui.RegisterForm(el)
|
||||
})
|
||||
|
||||
this.popup = new r.ui.LoginPopup($('.login-popup')[0])
|
||||
|
||||
$(document).delegate('.login-required', 'click', $.proxy(this, 'loginRequiredAction'))
|
||||
}
|
||||
},
|
||||
|
||||
loginRequiredAction: function(e) {
|
||||
if (r.config.logged) {
|
||||
return true
|
||||
} else {
|
||||
var el = $(e.target),
|
||||
href = el.attr('href'),
|
||||
dest
|
||||
if (href && href != '#') {
|
||||
// User clicked on a link that requires login to continue
|
||||
dest = href
|
||||
} else {
|
||||
// User clicked on a thing button that requires login
|
||||
var thing = el.thing()
|
||||
if (thing.length) {
|
||||
dest = thing.find('.comments').attr('href')
|
||||
}
|
||||
}
|
||||
|
||||
this.popup.show(true, dest && function() {
|
||||
window.location = dest
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.ui.LoginForm = function() {
|
||||
r.ui.Form.apply(this, arguments)
|
||||
}
|
||||
r.ui.LoginForm.prototype = $.extend(new r.ui.Form(), {
|
||||
showErrors: function(errors) {
|
||||
r.ui.Form.prototype.showErrors.call(this, errors)
|
||||
if (errors.length) {
|
||||
this.$el.find('.recover-password')
|
||||
.addClass('attention')
|
||||
}
|
||||
},
|
||||
|
||||
showStatus: function() {
|
||||
this.$el.find('.error').css('opacity', 1)
|
||||
r.ui.Form.prototype.showStatus.apply(this, arguments)
|
||||
},
|
||||
|
||||
resetErrors: function() {
|
||||
if (this.$el.hasClass('login-form-side')) {
|
||||
// Dim the error in place so the form doesn't change size.
|
||||
var errorEl = this.$el.find('.error')
|
||||
if (errorEl.is(':visible')) {
|
||||
errorEl.fadeTo(100, .35)
|
||||
}
|
||||
} else {
|
||||
r.ui.Form.prototype.resetErrors.apply(this, arguments)
|
||||
}
|
||||
},
|
||||
|
||||
_submit: function() {
|
||||
r.login.post(this, 'login', $.proxy(this, 'handleResult'))
|
||||
},
|
||||
|
||||
_handleResult: function(result) {
|
||||
if (!result.json.errors.length) {
|
||||
// Success. Load the destination page with the new session cookie.
|
||||
if (this.successCallback) {
|
||||
this.successCallback(result)
|
||||
} else {
|
||||
var base = r.config.extension ? '/.'+r.config.extension : '/',
|
||||
defaultDest = /\/login\/?$/.test($.url().attr('path')) ? base : window.location,
|
||||
destParam = this.$el.find('input[name="dest"]').val()
|
||||
window.location = destParam || defaultDest
|
||||
}
|
||||
} else {
|
||||
r.ui.Form.prototype._handleResult.call(this, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
r.ui.RegisterForm = function() {
|
||||
r.ui.Form.apply(this, arguments)
|
||||
}
|
||||
r.ui.RegisterForm.prototype = $.extend(new r.ui.Form(), {
|
||||
_submit: function() {
|
||||
r.login.post(this, 'register', $.proxy(this, 'handleResult'))
|
||||
},
|
||||
|
||||
_handleResult: r.ui.LoginForm.prototype._handleResult
|
||||
})
|
||||
|
||||
r.ui.LoginPopup = function(el) {
|
||||
r.ui.Base.call(this, el)
|
||||
this.loginForm = new r.ui.LoginForm(this.$el.find('form.login-form:first'))
|
||||
this.registerForm = new r.ui.RegisterForm(this.$el.find('form.register-form:first'))
|
||||
}
|
||||
r.ui.LoginPopup.prototype = $.extend(new r.ui.Base(), {
|
||||
show: function(notice, callback) {
|
||||
this.loginForm.successCallback = callback
|
||||
this.registerForm.successCallback = callback
|
||||
$.request("new_captcha", {id: this.$el.attr('id')})
|
||||
this.$el
|
||||
.find(".cover-msg").toggle(!!notice).end()
|
||||
.show()
|
||||
}
|
||||
})
|
||||
@@ -123,17 +123,6 @@ function showlang() {
|
||||
return false;
|
||||
};
|
||||
|
||||
function showcover(warning, reason) {
|
||||
$.request("new_captcha");
|
||||
if (warning)
|
||||
$("#cover_disclaim, #cover_msg").show();
|
||||
else
|
||||
$("#cover_disclaim, #cover_msg").hide();
|
||||
$(".login-popup:first").show()
|
||||
.find('form input[name="reason"]').val(reason || "");
|
||||
return false;
|
||||
};
|
||||
|
||||
function hidecover(where) {
|
||||
$(where).parents(".cover-overlay").hide();
|
||||
return false;
|
||||
@@ -322,9 +311,7 @@ function linkstatus(form) {
|
||||
|
||||
function subscribe(reddit_name) {
|
||||
return function() {
|
||||
if (!reddit.logged) {
|
||||
showcover();
|
||||
} else {
|
||||
if (reddit.logged) {
|
||||
$.things(reddit_name).find(".entry").addClass("likes");
|
||||
$.request("subscribe", {sr: reddit_name, action: "sub"});
|
||||
}
|
||||
@@ -333,9 +320,7 @@ function subscribe(reddit_name) {
|
||||
|
||||
function unsubscribe(reddit_name) {
|
||||
return function() {
|
||||
if (!reddit.logged) {
|
||||
showcover();
|
||||
} else {
|
||||
if (reddit.logged) {
|
||||
$.things(reddit_name).find(".entry").removeClass("likes");
|
||||
$.request("subscribe", {sr: reddit_name, action: "unsub"});
|
||||
}
|
||||
@@ -344,10 +329,7 @@ function unsubscribe(reddit_name) {
|
||||
|
||||
function friend(user_name, container_name, type) {
|
||||
return function() {
|
||||
if (!reddit.logged) {
|
||||
showcover();
|
||||
}
|
||||
else {
|
||||
if (reddit.logged) {
|
||||
encoded = encodeURIComponent(reddit.referer);
|
||||
$.request("friend?note=" + encoded,
|
||||
{name: user_name, container: container_name, type: type});
|
||||
|
||||
104
r2/r2/public/static/js/ui.js
Normal file
104
r2/r2/public/static/js/ui.js
Normal file
@@ -0,0 +1,104 @@
|
||||
r.ui = {}
|
||||
|
||||
r.ui.Base = function(el) {
|
||||
this.$el = $(el)
|
||||
}
|
||||
|
||||
r.ui.Form = function(el) {
|
||||
r.ui.Base.call(this, el)
|
||||
this.$el.submit($.proxy(function(e) {
|
||||
e.preventDefault()
|
||||
this.submit(e)
|
||||
}, this))
|
||||
}
|
||||
r.ui.Form.prototype = $.extend(new r.ui.Base(), {
|
||||
workingDelay: 200,
|
||||
|
||||
setWorking: function(isWorking) {
|
||||
// Delay the initial throbber display to prevent flashes for fast
|
||||
// operations
|
||||
if (isWorking) {
|
||||
if (!this.$el.hasClass('working') && !this._workingTimer) {
|
||||
this._workingTimer = setTimeout($.proxy(function() {
|
||||
this.$el.addClass('working')
|
||||
}, this), this.workingDelay)
|
||||
}
|
||||
} else {
|
||||
if (this._workingTimer) {
|
||||
clearTimeout(this._workingTimer)
|
||||
delete this._workingTimer
|
||||
}
|
||||
this.$el.removeClass('working')
|
||||
}
|
||||
},
|
||||
|
||||
showStatus: function(msg, isError) {
|
||||
this.$el.find('.status')
|
||||
.show()
|
||||
.toggleClass('error', !!isError)
|
||||
.text(msg)
|
||||
},
|
||||
|
||||
showErrors: function(errors) {
|
||||
statusMsgs = []
|
||||
$.each(errors, $.proxy(function(i, err) {
|
||||
var errName = err[0],
|
||||
errMsg = err[1],
|
||||
errField = err[2],
|
||||
errCls = '.error.'+errName + (errField ? '.field-'+errField : ''),
|
||||
errEl = this.$el.find(errCls)
|
||||
|
||||
if (errEl.length) {
|
||||
errEl.show().text(errMsg)
|
||||
} else {
|
||||
statusMsgs.push(errMsg)
|
||||
}
|
||||
}, this))
|
||||
|
||||
if (statusMsgs.length) {
|
||||
this.showStatus(statusMsgs.join(', '), true)
|
||||
}
|
||||
},
|
||||
|
||||
resetErrors: function() {
|
||||
this.$el.find('.error').hide()
|
||||
},
|
||||
|
||||
checkCaptcha: function(errors) {
|
||||
if (this.$el.has('input[name="captcha"]').length) {
|
||||
var badCaptcha = $.grep(errors, function(el) {
|
||||
return el[0] == 'badCaptcha'
|
||||
})
|
||||
if (badCaptcha) {
|
||||
$.request("new_captcha", {id: this.$el.attr('id')})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
serialize: function() {
|
||||
return this.$el.serializeArray()
|
||||
},
|
||||
|
||||
submit: function() {
|
||||
this.resetErrors()
|
||||
this.setWorking(true)
|
||||
this._submit()
|
||||
},
|
||||
|
||||
_submit: function() {},
|
||||
|
||||
handleResult: function(result, err, xhr) {
|
||||
if (result) {
|
||||
this.checkCaptcha(result.json.errors)
|
||||
this._handleResult(result)
|
||||
} else {
|
||||
this.setWorking(false)
|
||||
this.showStatus('an error occurred (' + xhr.status + ')', true)
|
||||
}
|
||||
},
|
||||
|
||||
_handleResult: function(result) {
|
||||
this.showErrors(result.json.errors)
|
||||
this.setWorking(false)
|
||||
}
|
||||
})
|
||||
@@ -79,10 +79,8 @@ ${rounded_captcha()}
|
||||
<td>
|
||||
%endif
|
||||
<input class="captcha cap-text" id="captcha_"
|
||||
name="captcha" type="text" size="${size}" />
|
||||
<script type="text/javascript">
|
||||
emptyInput($('input.captcha'), "${_('type the letters from the image above')}");
|
||||
</script>
|
||||
name="captcha" type="text" size="${size}"
|
||||
placeholder="type the letters from the image above" />
|
||||
%if tabular:
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -46,18 +46,12 @@
|
||||
<%def name="login_form(register=False, user='', dest='', include_tos=True)">
|
||||
<% op = "reg" if register else "login" %>
|
||||
<form id="login_${op}" method="post"
|
||||
action="${add_sr('/post/' + op, nocname = True)}"
|
||||
%if not c.frameless_cname or c.authorized_cname:
|
||||
onsubmit="return post_user(this, '${"register" if register else "login"}');"
|
||||
%else:
|
||||
onsubmit="return update_user(this);"
|
||||
%endif
|
||||
target="_top">
|
||||
action="${add_sr(g.https_endpoint + '/post/' + op, nocname = True)}"
|
||||
class="user-form ${'register-form' if register else 'login-form'}">
|
||||
%if c.cname:
|
||||
<input type="hidden" name="${UrlParser.cname_get}"
|
||||
value="${random.random()}" />
|
||||
%endif
|
||||
<input type="hidden" name="reason" value="" />
|
||||
<input type="hidden" name="op" value="${op}" />
|
||||
%if dest:
|
||||
<input type="hidden" name="dest" value="${dest}" />
|
||||
@@ -126,10 +120,11 @@
|
||||
</li>
|
||||
%endif
|
||||
</ul>
|
||||
<p>
|
||||
<p class="submit">
|
||||
<button type="submit" class="button">
|
||||
${register and _("create account") or _("login")}
|
||||
</button>
|
||||
<span class="throbber"></span>
|
||||
<span class="status"></span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -138,7 +133,7 @@
|
||||
|
||||
|
||||
<%def name="login_panel(lf, user_reg = '', user_login = '', dest='')">
|
||||
<div class="loginform divide">
|
||||
<div class="login-form-section divide">
|
||||
<h3>${_("create a new account")}</h3>
|
||||
<p class="tagline">
|
||||
${_("all it takes is a username and password")}
|
||||
@@ -150,7 +145,7 @@
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="loginform">
|
||||
<div class="login-form-section">
|
||||
<h3>${_("login")}</h3>
|
||||
<p class="tagline">
|
||||
${_("already have an account and just want to login?")}
|
||||
|
||||
@@ -28,33 +28,31 @@
|
||||
<%namespace file="utils.html" import="error_field"/>
|
||||
|
||||
<% op = "login-main" %>
|
||||
<form method="post"
|
||||
id="login_${op}" action="${add_sr('/post/login', nocname = True)}"
|
||||
%if thing.auth_cname:
|
||||
onsubmit="return post_user(this, 'login');"
|
||||
%else:
|
||||
onsubmit="return update_user(this);"
|
||||
%endif
|
||||
class="login-form-side">
|
||||
<form method="post"
|
||||
action="${add_sr(g.https_endpoint + '/post/' + op, nocname = True)}"
|
||||
id="login_${op}"
|
||||
class="login-form login-form-side">
|
||||
%if thing.cname:
|
||||
<input type="hidden" name="${UrlParser.cname_get}"
|
||||
value="${random.random()}" />
|
||||
%endif
|
||||
<input type="hidden" name="op" value="${op}" />
|
||||
<input name="user" type="text" maxlength="20" tabindex="1"/>
|
||||
<input name="passwd" type="password" maxlength="20" tabindex="2"/>
|
||||
<input name="user" placeholder="username" type="text" maxlength="20" tabindex="1"/>
|
||||
<input name="passwd" placeholder="password" type="password" maxlength="20" tabindex="2"/>
|
||||
|
||||
${error_field("WRONG_PASSWORD", "passwd", "div")}
|
||||
${error_field("RATELIMIT", "ratelimit")}
|
||||
${error_field("RATELIMIT", "vdelay")}
|
||||
<div class="status error"></div>
|
||||
<div class="status"></div>
|
||||
|
||||
<div id="remember-me">
|
||||
<button class="btn" type="submit" tabindex="4">${_("login")}</button>
|
||||
<input type="checkbox" name="rem" tabindex="3" id="rem-${op}" />
|
||||
<label for="rem-${op}">${_("remember me")}</label>
|
||||
<a class="recover-password" href="/password">${_("recover password")}</a>
|
||||
<div class="clear"></div>
|
||||
<a class="recover-password" href="/password">${_("reset password")}</a>
|
||||
</div>
|
||||
|
||||
<div class="submit">
|
||||
<span class="throbber"></span>
|
||||
<button class="btn" type="submit" tabindex="4">${_("login")}</button>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ ${self.RenderPrintable()}
|
||||
_class = _type + ("mod" if mod else "")
|
||||
fullname = this._fullname
|
||||
%>
|
||||
<div class="arrow ${_class}"
|
||||
<div class="arrow ${_class} login-required"
|
||||
%if getattr(thing, "votable", True):
|
||||
onclick="$(this).vote('${thing.votehash}', null, event)"
|
||||
%else:
|
||||
|
||||
@@ -424,12 +424,11 @@
|
||||
callback, cancelback = cancelback, callback
|
||||
title, alt_title = alt_title, title
|
||||
css_class, alt_css_class = alt_css_class, css_class
|
||||
extra_class = "login-required" if login_required else ""
|
||||
%>
|
||||
<span class="${class_name} toggle" style="${style}">
|
||||
<a class="option active ${css_class}" href="#" tabindex="100"
|
||||
%if login_required and not c.user_is_loggedin:
|
||||
onclick="return showcover('', '${callback}_' + $(this).thing_id());"
|
||||
%else:
|
||||
<a class="option active ${css_class} ${extra_class}" href="#" tabindex="100"
|
||||
%if not login_required or c.user_is_loggedin:
|
||||
onclick="return toggle(this, ${callback}, ${cancelback})"
|
||||
%endif
|
||||
>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"friend('%s', '%s', 'friend')" % (thing.user.name, thing.my_fullname),
|
||||
"unfriend('%s', '%s', 'friend')" % (thing.user.name, thing.my_fullname),
|
||||
css_class = "add", alt_css_class = "remove",
|
||||
reverse = thing.is_friend)}
|
||||
reverse = thing.is_friend, login_required=True)}
|
||||
</div>
|
||||
%endif
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<div class="popup">
|
||||
<h1 class="cover-msg">${strings.cover_msg}</h1>
|
||||
${login_panel(login_form)}
|
||||
<div style="text-align:center; clear: both">
|
||||
<div class="close-popup">
|
||||
<a href="#" onclick="return hidecover(this)">
|
||||
${_("close this window")}
|
||||
</a>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
%if thing.enable_login_cover and not g.read_only_mode:
|
||||
<span class="user">
|
||||
${text_with_js(_("want to join? %(register)s in seconds"),
|
||||
register=(_("register"), "return showcover(false);"))}
|
||||
register=(_("register"), "r.login.ui.popup.show(); return false"))}
|
||||
</span>
|
||||
${separator("|")}
|
||||
%endif
|
||||
|
||||
@@ -23,25 +23,16 @@
|
||||
<%namespace file="utils.html" import="plain_link"/>
|
||||
|
||||
<div class="sidebox ${thing.css_class}">
|
||||
<%
|
||||
if not c.user_is_loggedin and thing.show_cover:
|
||||
onclick="return(showcover(true, 'redirect_%s'));" % thing.link
|
||||
else:
|
||||
onclick = None
|
||||
|
||||
nocname = thing.nocname
|
||||
%>
|
||||
|
||||
<div class="morelink">
|
||||
${plain_link(thing.title,thing.link, _sr_path = thing.sr_path,
|
||||
onclick=onclick, nocname=nocname)}
|
||||
${plain_link(thing.title, thing.link, _sr_path=thing.sr_path,
|
||||
_class='login-required', nocname=thing.nocname)}
|
||||
<div class="nub"> </div>
|
||||
</div>
|
||||
|
||||
%if thing.subtitles:
|
||||
<div class="spacer">
|
||||
${plain_link('', thing.link, _sr_path=thing.sr_path,
|
||||
onclick=onclick, nocname=nocname)}
|
||||
${plain_link('', thing.link, _sr_path=thing.sr_path,
|
||||
_class='login-required', nocname=thing.nocname)}
|
||||
%for subtitle in thing.subtitles:
|
||||
<div class="subtitle">${subtitle}</div>
|
||||
%endfor
|
||||
|
||||
@@ -65,7 +65,8 @@
|
||||
"subscribe('%s')" % sr._fullname,
|
||||
"unsubscribe('%s')" % sr._fullname,
|
||||
css_class = "add", alt_css_class = "remove",
|
||||
reverse = sr.subscriber)}
|
||||
reverse = sr.subscriber,
|
||||
login_required = True)}
|
||||
</%def>
|
||||
|
||||
##this function is used by subscriptionbox.html
|
||||
|
||||
@@ -365,7 +365,8 @@ ${unsafe(txt)}
|
||||
<%
|
||||
from r2.lib.template_helpers import get_domain
|
||||
%>
|
||||
var reddit = {
|
||||
r = {};
|
||||
r.config = reddit = {
|
||||
/* is the user logged in */
|
||||
logged: ${c.user_is_loggedin and ("'%s'" % c.user.name) or "false"},
|
||||
/* the subreddit's name (for posts) */
|
||||
@@ -383,6 +384,8 @@ ${unsafe(txt)}
|
||||
no_www = True)}",
|
||||
/* where do ajax request go? */
|
||||
ajax_domain: "${get_domain(cname=c.authorized_cname, subreddit = False)}",
|
||||
extension: '${c.extension}',
|
||||
https_endpoint: "${g.https_endpoint}",
|
||||
/* debugging? */
|
||||
debug: ${"true" if g.debug else "false"},
|
||||
vl: {},
|
||||
|
||||
Reference in New Issue
Block a user