SECURITY: Query @usernames in bulk
Otherwise you could add many requests at once while composing.
This commit is contained in:
parent
9572b28986
commit
c58b495e15
|
@ -0,0 +1,56 @@
|
||||||
|
function replaceSpan($e, username) {
|
||||||
|
$e.replaceWith("<a href='" +
|
||||||
|
Discourse.getURL("/users/") + username.toLowerCase() +
|
||||||
|
"' class='mention'>@" + username + "</a>");
|
||||||
|
}
|
||||||
|
|
||||||
|
const found = [];
|
||||||
|
const checked = [];
|
||||||
|
|
||||||
|
function updateFound($mentions, usernames) {
|
||||||
|
Ember.run.scheduleOnce('afterRender', function() {
|
||||||
|
$mentions.each((i, e) => {
|
||||||
|
const $e = $(e);
|
||||||
|
const username = usernames[i];
|
||||||
|
if (found.indexOf(username) !== -1) {
|
||||||
|
replaceSpan($e, username);
|
||||||
|
} else {
|
||||||
|
$e.removeClass('mention-loading').addClass('mention-tested');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let linking = false;
|
||||||
|
export default function linkMentions($elem) {
|
||||||
|
if (linking) { return Ember.RSVP.Promise.resolve(); }
|
||||||
|
linking = true;
|
||||||
|
|
||||||
|
return new Ember.RSVP.Promise(function(resolve) {
|
||||||
|
const $mentions = $('span.mention:not(.mention-tested):not(.mention-loading)', $elem);
|
||||||
|
if ($mentions.length) {
|
||||||
|
const usernames = $mentions.map((_, e) => $(e).text().substr(1).toLowerCase());
|
||||||
|
|
||||||
|
if (usernames.length) {
|
||||||
|
$mentions.addClass('mention-loading');
|
||||||
|
const uncached = _.uniq(usernames).filter((u) => { return checked.indexOf(u) === -1; });
|
||||||
|
|
||||||
|
if (uncached.length) {
|
||||||
|
return Discourse.ajax("/users/is_local_username", {
|
||||||
|
data: { usernames: uncached}
|
||||||
|
}).then(function(r) {
|
||||||
|
found.push.apply(found, r.valid);
|
||||||
|
checked.push.apply(checked, uncached);
|
||||||
|
updateFound($mentions, usernames);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
updateFound($mentions, usernames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}).finally(() => { linking = false });
|
||||||
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
|
|
||||||
// A local cache lookup
|
|
||||||
var localCache = [];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Lookup a username and return whether it is exists or not.
|
|
||||||
|
|
||||||
@function lookup
|
|
||||||
@param {String} username to look up
|
|
||||||
@return {Promise} promise that results to whether the name was found or not
|
|
||||||
**/
|
|
||||||
function lookup(username) {
|
|
||||||
return new Em.RSVP.Promise(function (resolve) {
|
|
||||||
var cached = localCache[username];
|
|
||||||
|
|
||||||
// If we have a cached answer, return it
|
|
||||||
if (typeof cached !== "undefined") {
|
|
||||||
resolve(cached);
|
|
||||||
} else {
|
|
||||||
Discourse.ajax("/users/is_local_username", { data: { username: username } }).then(function(r) {
|
|
||||||
localCache[username] = r.valid;
|
|
||||||
resolve(r.valid);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Help us link directly to a mentioned user's profile if the username exists.
|
|
||||||
|
|
||||||
@class Mention
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
Discourse.Mention = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
Paints an element in the DOM with the appropriate classes and markup if the username
|
|
||||||
it is mentioning exists.
|
|
||||||
|
|
||||||
@method paint
|
|
||||||
@param {Element} the element in the DOM to decorate
|
|
||||||
**/
|
|
||||||
paint: function(e) {
|
|
||||||
var $elem = $(e);
|
|
||||||
if ($elem.data('mention-tested')) return;
|
|
||||||
var username = $elem.text().substr(1);
|
|
||||||
|
|
||||||
$elem.addClass('mention-loading');
|
|
||||||
lookup(username).then(function(found) {
|
|
||||||
if (found) {
|
|
||||||
$elem.replaceWith("<a href='" + Discourse.getURL("/users/") + username.toLowerCase() + "' class='mention'>@" + username + "</a>");
|
|
||||||
} else {
|
|
||||||
$elem.removeClass('mention-loading').addClass('mention-tested');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -3,6 +3,7 @@ import afterTransition from 'discourse/lib/after-transition';
|
||||||
import loadScript from 'discourse/lib/load-script';
|
import loadScript from 'discourse/lib/load-script';
|
||||||
import avatarTemplate from 'discourse/lib/avatar-template';
|
import avatarTemplate from 'discourse/lib/avatar-template';
|
||||||
import positioningWorkaround from 'discourse/lib/safari-hacks';
|
import positioningWorkaround from 'discourse/lib/safari-hacks';
|
||||||
|
import linkMentions from 'discourse/lib/link-mentions';
|
||||||
|
|
||||||
const ComposerView = Discourse.View.extend(Ember.Evented, {
|
const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
_lastKeyTimeout: null,
|
_lastKeyTimeout: null,
|
||||||
|
@ -175,11 +176,10 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||||
$('a.onebox', $wmdPreview).each(function(i, e) {
|
$('a.onebox', $wmdPreview).each(function(i, e) {
|
||||||
Discourse.Onebox.load(e, refresh);
|
Discourse.Onebox.load(e, refresh);
|
||||||
});
|
});
|
||||||
$('span.mention', $wmdPreview).each(function(i, e) {
|
|
||||||
Discourse.Mention.paint(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.trigger('previewRefreshed', $wmdPreview);
|
linkMentions($wmdPreview).then(() => {
|
||||||
|
this.trigger('previewRefreshed', $wmdPreview);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_applyEmojiAutocomplete() {
|
_applyEmojiAutocomplete() {
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
//= require ./discourse/components/dropdown-button
|
//= require ./discourse/components/dropdown-button
|
||||||
//= require ./discourse/components/notifications-button
|
//= require ./discourse/components/notifications-button
|
||||||
//= require ./discourse/components/topic-notifications-button
|
//= require ./discourse/components/topic-notifications-button
|
||||||
|
//= require ./discourse/lib/link-mentions
|
||||||
//= require ./discourse/views/composer
|
//= require ./discourse/views/composer
|
||||||
//= require ./discourse/lib/show-modal
|
//= require ./discourse/lib/show-modal
|
||||||
//= require ./discourse/routes/discourse
|
//= require ./discourse/routes/discourse
|
||||||
|
|
|
@ -175,10 +175,12 @@ class UsersController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_local_username
|
def is_local_username
|
||||||
params.require(:username)
|
users = params[:usernames]
|
||||||
u = params[:username].downcase
|
users = [params[:username]] if users.blank?
|
||||||
r = User.exec_sql('select 1 from users where username_lower = ?', u).values
|
users.each(&:downcase!)
|
||||||
render json: {valid: r.length == 1}
|
|
||||||
|
result = User.where(username_lower: users).pluck(:username_lower)
|
||||||
|
render json: {valid: result}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_available_true
|
def render_available_true
|
||||||
|
|
Loading…
Reference in New Issue