FEATURE: detect when client thinks user is logged on but is not
This cleans up an error condition where UI thinks a user is logged on but the user is not. If this happens user will be prompted to refresh.
This commit is contained in:
parent
f0d5f83424
commit
0134e41286
|
@ -11,6 +11,7 @@
|
||||||
// Stuff we need to load first
|
// Stuff we need to load first
|
||||||
//= require ./discourse/lib/utilities
|
//= require ./discourse/lib/utilities
|
||||||
//= require ./discourse/lib/page-visible
|
//= require ./discourse/lib/page-visible
|
||||||
|
//= require ./discourse/lib/logout
|
||||||
//= require ./discourse/lib/ajax
|
//= require ./discourse/lib/ajax
|
||||||
//= require ./discourse/lib/text
|
//= require ./discourse/lib/text
|
||||||
//= require ./discourse/lib/hash
|
//= require ./discourse/lib/hash
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import logout from 'discourse/lib/logout';
|
import logout from 'discourse/lib/logout';
|
||||||
|
|
||||||
|
let _showingLogout = false;
|
||||||
|
|
||||||
// Subscribe to "logout" change events via the Message Bus
|
// Subscribe to "logout" change events via the Message Bus
|
||||||
export default {
|
export default {
|
||||||
name: "logout",
|
name: "logout",
|
||||||
|
@ -7,14 +9,22 @@ export default {
|
||||||
|
|
||||||
initialize: function (container) {
|
initialize: function (container) {
|
||||||
const messageBus = container.lookup('message-bus:main');
|
const messageBus = container.lookup('message-bus:main');
|
||||||
const siteSettings = container.lookup('site-settings:main');
|
|
||||||
const keyValueStore = container.lookup('key-value-store:main');
|
|
||||||
|
|
||||||
if (!messageBus) { return; }
|
if (!messageBus) { return; }
|
||||||
const callback = () => logout(siteSettings, keyValueStore);
|
|
||||||
|
|
||||||
messageBus.subscribe("/logout", function () {
|
messageBus.subscribe("/logout", function () {
|
||||||
bootbox.dialog(I18n.t("logout"), {label: I18n.t("refresh"), callback}, {onEscape: callback, backdrop: 'static'});
|
if (!_showingLogout) {
|
||||||
|
|
||||||
|
_showingLogout = true;
|
||||||
|
|
||||||
|
bootbox.dialog(I18n.t("logout"), {
|
||||||
|
label: I18n.t("refresh"),
|
||||||
|
callback: logout
|
||||||
|
}, {
|
||||||
|
onEscape: logout,
|
||||||
|
backdrop: 'static'
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import pageVisible from 'discourse/lib/page-visible';
|
import pageVisible from 'discourse/lib/page-visible';
|
||||||
|
import logout from 'discourse/lib/logout';
|
||||||
|
|
||||||
let _trackView = false;
|
let _trackView = false;
|
||||||
let _transientHeader = null;
|
let _transientHeader = null;
|
||||||
|
let _showingLogout = false;
|
||||||
|
|
||||||
export function setTransientHeader(key, value) {
|
export function setTransientHeader(key, value) {
|
||||||
_transientHeader = {key, value};
|
_transientHeader = {key, value};
|
||||||
|
@ -39,6 +41,10 @@ export function ajax() {
|
||||||
|
|
||||||
args.headers = args.headers || {};
|
args.headers = args.headers || {};
|
||||||
|
|
||||||
|
if (Discourse.__container__.lookup('current-user:main')) {
|
||||||
|
args.headers['Discourse-Logged-In'] = "true";
|
||||||
|
}
|
||||||
|
|
||||||
if (_transientHeader) {
|
if (_transientHeader) {
|
||||||
args.headers[_transientHeader.key] = _transientHeader.value;
|
args.headers[_transientHeader.key] = _transientHeader.value;
|
||||||
_transientHeader = null;
|
_transientHeader = null;
|
||||||
|
@ -54,7 +60,22 @@ export function ajax() {
|
||||||
args.headers['Discourse-Visible'] = "true";
|
args.headers['Discourse-Visible'] = "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let handleLogoff = function(xhr) {
|
||||||
|
if (xhr.getResponseHeader('Discourse-Logged-Out') && !_showingLogout) {
|
||||||
|
_showingLogout = true;
|
||||||
|
bootbox.dialog(
|
||||||
|
I18n.t("logout"), {label: I18n.t("refresh"), callback: logout},
|
||||||
|
{
|
||||||
|
onEscape: () => logout(),
|
||||||
|
backdrop: 'static'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
args.success = (data, textStatus, xhr) => {
|
args.success = (data, textStatus, xhr) => {
|
||||||
|
handleLogoff(xhr);
|
||||||
|
|
||||||
if (xhr.getResponseHeader('Discourse-Readonly')) {
|
if (xhr.getResponseHeader('Discourse-Readonly')) {
|
||||||
Ember.run(() => Discourse.Site.currentProp('isReadOnly', true));
|
Ember.run(() => Discourse.Site.currentProp('isReadOnly', true));
|
||||||
}
|
}
|
||||||
|
@ -67,6 +88,8 @@ export function ajax() {
|
||||||
};
|
};
|
||||||
|
|
||||||
args.error = (xhr, textStatus, errorThrown) => {
|
args.error = (xhr, textStatus, errorThrown) => {
|
||||||
|
handleLogoff(xhr);
|
||||||
|
|
||||||
// note: for bad CSRF we don't loop an extra request right away.
|
// note: for bad CSRF we don't loop an extra request right away.
|
||||||
// this allows us to eliminate the possibility of having a loop.
|
// this allows us to eliminate the possibility of having a loop.
|
||||||
if (xhr.status === 403 && xhr.responseText === "[\"BAD CSRF\"]") {
|
if (xhr.status === 403 && xhr.responseText === "[\"BAD CSRF\"]") {
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
export default function logout(siteSettings, keyValueStore) {
|
export default function logout(siteSettings, keyValueStore) {
|
||||||
|
if (!siteSettings || !keyValueStore) {
|
||||||
|
const container = Discourse.__container__;
|
||||||
|
siteSettings = siteSettings || container.lookup('site-settings:main');
|
||||||
|
keyValueStore = keyValueStore || container.lookup('key-value-store:main');
|
||||||
|
}
|
||||||
|
|
||||||
keyValueStore.abandonLocal();
|
keyValueStore.abandonLocal();
|
||||||
|
|
||||||
const redirect = siteSettings.logout_redirect;
|
const redirect = siteSettings.logout_redirect;
|
||||||
|
|
|
@ -22,13 +22,18 @@ def setup_message_bus_env(env)
|
||||||
user.groups.pluck('groups.id')
|
user.groups.pluck('groups.id')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
extra_headers = {
|
||||||
|
"Access-Control-Allow-Origin" => Discourse.base_url_no_prefix,
|
||||||
|
"Access-Control-Allow-Methods" => "GET, POST",
|
||||||
|
"Access-Control-Allow-Headers" => "X-SILENCE-LOGGER, X-Shared-Session-Key, Dont-Chunk, Discourse-Visible"
|
||||||
|
}
|
||||||
|
|
||||||
|
if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN]
|
||||||
|
extra_headers['Discourse-Logged-Out'] = '1'
|
||||||
|
end
|
||||||
|
|
||||||
hash = {
|
hash = {
|
||||||
extra_headers:
|
extra_headers: extra_headers,
|
||||||
{
|
|
||||||
"Access-Control-Allow-Origin" => Discourse.base_url_no_prefix,
|
|
||||||
"Access-Control-Allow-Methods" => "GET, POST",
|
|
||||||
"Access-Control-Allow-Headers" => "X-SILENCE-LOGGER, X-Shared-Session-Key, Dont-Chunk, Discourse-Visible"
|
|
||||||
},
|
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
group_ids: group_ids,
|
group_ids: group_ids,
|
||||||
is_admin: is_admin,
|
is_admin: is_admin,
|
||||||
|
|
|
@ -14,6 +14,7 @@ class Auth::DefaultCurrentUserProvider
|
||||||
TOKEN_COOKIE ||= "_t"
|
TOKEN_COOKIE ||= "_t"
|
||||||
PATH_INFO ||= "PATH_INFO"
|
PATH_INFO ||= "PATH_INFO"
|
||||||
COOKIE_ATTEMPTS_PER_MIN ||= 10
|
COOKIE_ATTEMPTS_PER_MIN ||= 10
|
||||||
|
BAD_TOKEN ||= "_DISCOURSE_BAD_TOKEN"
|
||||||
|
|
||||||
# do all current user initialization here
|
# do all current user initialization here
|
||||||
def initialize(env)
|
def initialize(env)
|
||||||
|
@ -58,7 +59,8 @@ class Auth::DefaultCurrentUserProvider
|
||||||
current_user = @user_token.try(:user)
|
current_user = @user_token.try(:user)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless current_user
|
if !current_user
|
||||||
|
@env[BAD_TOKEN] = true
|
||||||
begin
|
begin
|
||||||
limiter.performed!
|
limiter.performed!
|
||||||
rescue RateLimiter::LimitExceeded
|
rescue RateLimiter::LimitExceeded
|
||||||
|
@ -69,6 +71,8 @@ class Auth::DefaultCurrentUserProvider
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elsif @env['HTTP_DISCOURSE_LOGGED_IN']
|
||||||
|
@env[BAD_TOKEN] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if current_user && should_update_last_seen?
|
if current_user && should_update_last_seen?
|
||||||
|
|
|
@ -78,6 +78,10 @@ module Hijack
|
||||||
headers['Content-Length'] = body.bytesize
|
headers['Content-Length'] = body.bytesize
|
||||||
headers['Connection'] = "close"
|
headers['Connection'] = "close"
|
||||||
|
|
||||||
|
if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN]
|
||||||
|
headers['Discourse-Logged-Out'] = '1'
|
||||||
|
end
|
||||||
|
|
||||||
status_string = Rack::Utils::HTTP_STATUS_CODES[response.status.to_i] || "Unknown"
|
status_string = Rack::Utils::HTTP_STATUS_CODES[response.status.to_i] || "Unknown"
|
||||||
io.write "#{response.status} #{status_string}\r\n"
|
io.write "#{response.status} #{status_string}\r\n"
|
||||||
|
|
||||||
|
|
|
@ -169,6 +169,11 @@ class Middleware::RequestTracker
|
||||||
if info && (headers = result[1])
|
if info && (headers = result[1])
|
||||||
headers["X-Runtime"] = "%0.6f" % info[:total_duration]
|
headers["X-Runtime"] = "%0.6f" % info[:total_duration]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN] && (headers = result[1])
|
||||||
|
headers['Discourse-Logged-Out'] = '1'
|
||||||
|
end
|
||||||
|
|
||||||
result
|
result
|
||||||
ensure
|
ensure
|
||||||
if (limiters = env['DISCOURSE_RATE_LIMITERS']) && env['DISCOURSE_IS_ASSET_PATH']
|
if (limiters = env['DISCOURSE_RATE_LIMITERS']) && env['DISCOURSE_IS_ASSET_PATH']
|
||||||
|
|
|
@ -182,4 +182,17 @@ RSpec.describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'logoff support' do
|
||||||
|
it 'can log off users cleanly' do
|
||||||
|
user = Fabricate(:user)
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
UserAuthToken.destroy_all
|
||||||
|
|
||||||
|
# we need a route that will call current user
|
||||||
|
post '/draft.json', params: {}
|
||||||
|
expect(response.headers['Discourse-Logged-Out']).to eq("1")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue