Skip to content

Commit

Permalink
Show modal 'logged out' message after Basic Auth logout. Use Clear-Si…
Browse files Browse the repository at this point in the history
…te-Data at /logout in Nginx.
  • Loading branch information
elonen committed Jul 18, 2024
1 parent 6098b99 commit 08ec939
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 20 deletions.
6 changes: 5 additions & 1 deletion client/debian/additional_files/clapshot+htadmin.nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ server {
deny all;
}
location /logout {
# 401 to force re-authentication
# Force re-authentication
add_header Clear-Site-Data "*" always;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires 0;
return 401;
}
}
31 changes: 22 additions & 9 deletions client/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ let uiConnectedState: boolean = false; // true if UI should look like we're conn
let collabDialogAck = false; // true if user has clicked "OK" on the collab dialog
let lastCollabControllingUser: string | null = null; // last user to control the video in a collab session
let forceBadBasicAuth = false;
let wsSocket: WebSocket | undefined;
let sendQueue: any[] = [];
Expand Down Expand Up @@ -434,6 +436,11 @@ function disconnect() {
uiConnectedState = false;
}
function basicAuthLogout() {
forceBadBasicAuth = true;
disconnect();
}
// Send message to server. If not connected, queue it.
function wsEmit(cmd: ClientToServerCmd)
Expand Down Expand Up @@ -470,14 +477,20 @@ let reconnectDelay = 100; // for exponential backoff
function connectWebsocket(wsUrl: string) {
const http_health_url = wsUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:").replace(/\/api\/.*$/, "/api/health");
let req_init: RequestInit = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Clapshot-Cookies': JSON.stringify(LocalStorageCookies.getAllNonExpired()),
},
};
let headers = new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Clapshot-Cookies': JSON.stringify(LocalStorageCookies.getAllNonExpired()),
});
if (forceBadBasicAuth) {
// Health should always return 200, which might trick some browsers to keep the bad basic auth credentials, effectively logging out the user.
const nonce = Math.random().toString(36).substring(2, 15);
headers.set('Authorization', 'Basic ' + btoa('logout_user__'+nonce+':bad_pass__'+nonce));
}
let req_init: RequestInit = { method: 'GET', headers };
function scheduleReconnect() {
reconnectDelay = Math.round(Math.min(reconnectDelay * 1.5, 5000));
Expand Down Expand Up @@ -923,7 +936,7 @@ function onMediaFileListPopupAction(e: { detail: { action: Proto3.ActionDef, ite
<main>
<span id="popup-container"></span>
<div class="flex flex-col bg-[#101016] w-screen h-screen {debugLayout?'border-2 border-yellow-300':''}">
<div class="flex-none w-full"><NavBar on:basic-auth-logout={disconnect} on:add-comments={onAddCommentsBulk}/></div>
<div class="flex-none w-full"><NavBar on:basic-auth-logout={basicAuthLogout} on:add-comments={onAddCommentsBulk}/></div>
<div class="flex-grow w-full overflow-auto {debugLayout?'border-2 border-cyan-300':''}">
<Notifications />

Expand Down
32 changes: 22 additions & 10 deletions client/src/lib/NavBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import {latestProgressReports, clientConfig} from '@/stores';
import type { MediaProgressReport } from '@/types';
import { Dropdown, DropdownItem, DropdownDivider, DropdownHeader } from 'flowbite-svelte';
import EDLImport from './tools/EDLImport.svelte';
import { ChevronRightOutline } from 'flowbite-svelte-icons';
import { ChevronRightOutline } from 'flowbite-svelte-icons';
import { Modal } from 'flowbite-svelte';
const dispatch = createEventDispatcher();
let loggedOut = false;
// Watch for (transcoding) progress reports from server, and update progress bar if one matches this item.
let videoProgressMsg: string | undefined = undefined;
Expand All @@ -22,20 +24,25 @@ onMount(async () => {
function logoutBasicAuth(urlFor401: RequestInfo, redirUrl: string) {
// Try to log out of basic auth by making a request to /logout and expect 401.
function logoutBasicAuth() {
// This is a bit tricky, as HTTP basic auth wasn't really designed for logout.
console.log("Making logout request to " + urlFor401 + " and redirecting to " + redirUrl + "...");
dispatch('basic-auth-logout', {});
fetch(urlFor401)
// Logout URL is expected to return 401 status code and return a Clear-Site-Data header.
// Additionally, send bad credentials in case 401 and Clear-Site-Data wasn't enough to forget the credentials.
// After that, disconnect websocket and show a modal to prompt user to reload the page.
const logoutUrl = $clientConfig?.logout_url || "/logout";
const nonce = Math.random().toString(36).substring(2, 15);
console.log("Making request to " + logoutUrl + " with bad creds...");
fetch(logoutUrl, {method:'GET', headers: {'Authorization': 'Basic ' + btoa('logout_user__'+nonce+':bad_pass__'+nonce)}})
.then(res => {
console.log("Logout response: " + res.status + " - " + res.statusText);
if (res.status === 401) {
console.log("Logout successful.");
dispatch('basic-auth-logout', {});
setTimeout(function () { window.location.href = redirUrl; }, 1000);
dispatch('basic-auth-logout', {});
loggedOut = true; // Show modal
} else {
alert("Basic auth logout failed.\nStatus code from " + urlFor401 + ": " + res.status + " (not 401)");
alert("Basic auth logout failed.\nStatus code from " + logoutUrl + ": " + res.status + " (not 401)");
}
})
.catch(error => {
Expand Down Expand Up @@ -147,7 +154,7 @@ function addEDLComments(event: any) {
{#each $userMenuItems as item}
<DropdownItem>
{#if item.type === "logout-basic-auth" }
<DropdownItem on:click={() => logoutBasicAuth('/logout', '/')}>{item.label}</DropdownItem>
<DropdownItem on:click={() => logoutBasicAuth()}>{item.label}</DropdownItem>
{:else if item.type === "about"}
<DropdownItem on:click={showAbout}>{item.label}</DropdownItem>
{:else if item.type === "divider"}
Expand All @@ -165,6 +172,11 @@ function addEDLComments(event: any) {
</div>
</nav>

<Modal title="Logged out" dismissable={false} bind:open={loggedOut} class="w-96">
<p><i class="fas fa fa-sign-in"></i> Reload page to log in again.</p>
</Modal>


<style>
@import url("https://fonts.googleapis.com/css2?family=Roboto+Condensed&family=Yanone+Kaffeesatz&display=swap");
</style>

0 comments on commit 08ec939

Please sign in to comment.