mirror of
https://github.com/owncast/owncast.git
synced 2024-12-28 03:58:32 +03:00
b835de2dc4
* Able to authenticate user against IndieAuth. For #1273 * WIP server indieauth endpoint. For https://github.com/owncast/owncast/issues/1272 * Add migration to remove access tokens from user * Add authenticated bool to user for display purposes * Add indieauth modal and auth flair to display names. For #1273 * Validate URLs and display errors * Renames, cleanups * Handle relative auth endpoint paths. Add error handling for missing redirects. * Disallow using display names in use by registered users. Closes #1810 * Verify code verifier via code challenge on callback * Use relative path to authorization_endpoint * Post-rebase fixes * Use a timestamp instead of a bool for authenticated * Propertly handle and display error in modal * Use auth'ed timestamp to derive authenticated flag to display in chat * don't redirect unless a URL is present avoids redirecting to `undefined` if there was an error * improve error message if owncast server URL isn't set * fix IndieAuth PKCE implementation use SHA256 instead of SHA1, generates a longer code verifier (must be 43-128 chars long), fixes URL-safe SHA256 encoding * return real profile data for IndieAuth response * check the code verifier in the IndieAuth server * Linting * Add new chat settings modal anad split up indieauth ui * Remove logging error * Update the IndieAuth modal UI. For #1273 * Add IndieAuth repsonse error checking * Disable IndieAuth client if server URL is not set. * Add explicit error messages for specific error types * Fix bad logic * Return OAuth-keyed error responses for indieauth server * Display IndieAuth error in plain text with link to return to main page * Remove redundant check * Add additional detail to error * Hide IndieAuth details behind disclosure details * Break out migration into two steps because some people have been runing dev in production * Add auth option to user dropdown Co-authored-by: Aaron Parecki <aaron@parecki.com>
140 lines
3.9 KiB
JavaScript
140 lines
3.9 KiB
JavaScript
import { h, createContext } from '/js/web_modules/preact.js';
|
|
import htm from '/js/web_modules/htm.js';
|
|
import { useState, useEffect, useRef } from '/js/web_modules/preact/hooks.js';
|
|
import UsernameForm from './username.js';
|
|
import { ChatIcon, UserIcon, CaretDownIcon } from '../icons/index.js';
|
|
|
|
const html = htm.bind(h);
|
|
|
|
const moderatorFlag = html`
|
|
<img src="/img/moderator-nobackground.svg" class="moderator-flag" />
|
|
`;
|
|
|
|
const Context = createContext();
|
|
|
|
export const ChatMenu = (props) => {
|
|
const {
|
|
username,
|
|
isModerator,
|
|
chatDisabled,
|
|
noVideoContent,
|
|
handleChatPanelToggle,
|
|
onUsernameChange,
|
|
showAuthModal,
|
|
onFocus,
|
|
onBlur,
|
|
} = props;
|
|
|
|
const [chatMenuOpen, setChatMenuOpen] = useState(false);
|
|
const [view, setView] = useState('main');
|
|
|
|
const chatMenuRef = useRef(undefined);
|
|
closeOnOutsideClick(chatMenuRef, setChatMenuOpen);
|
|
|
|
useEffect(() => {
|
|
if (chatMenuOpen) setView('main');
|
|
}, [chatMenuOpen]);
|
|
|
|
const authMenuItem =
|
|
showAuthModal &&
|
|
html`<li>
|
|
<button type="button" id="chat-auth" onClick=${showAuthModal}>
|
|
Authenticate
|
|
<span><${ChatIcon} /></span>
|
|
</button>
|
|
</li>`;
|
|
|
|
return html`
|
|
<${Context.Provider} value=${props}>
|
|
<div class="chat-menu p-2 relative shadow-lg" ref=${chatMenuRef}>
|
|
<button
|
|
id="chat-menu-button"
|
|
class="flex items-center p-1 bg-transparent rounded-md overflow-hidden text-gray-200 transition duration-150"
|
|
onClick="${() => setChatMenuOpen(!chatMenuOpen)}"
|
|
>
|
|
${
|
|
!isModerator
|
|
? html`<${UserIcon} className="w-6 h-6 mr-2" />`
|
|
: moderatorFlag
|
|
}
|
|
<span
|
|
id="username-display"
|
|
class="text-indigo-100 text-sm font-bold truncate overflow-hidden whitespace-no-wrap ${
|
|
isModerator && 'moderator-flag'
|
|
}"
|
|
>
|
|
${username}
|
|
</span>
|
|
<${CaretDownIcon} className="w-8 h-8"/>
|
|
</button>
|
|
${
|
|
chatMenuOpen &&
|
|
html` <div
|
|
class="chat-menu-popout shadow-2xl text-gray-100 absolute w-max top-full right-0 z-50 rounded-md p-2 bg-gray-900 fadeIn "
|
|
style=${{ minWidth: '20rem' }}
|
|
>
|
|
${view === 'main' &&
|
|
html`<ul class="chat-menu-options w-max">
|
|
<li>
|
|
<${UsernameForm}
|
|
username=${username}
|
|
isModerator=${isModerator}
|
|
onUsernameChange=${onUsernameChange}
|
|
onFocus=${onFocus}
|
|
onBlur=${onBlur}
|
|
/>
|
|
</li>
|
|
${authMenuItem}
|
|
<li>
|
|
<button
|
|
type="button"
|
|
id="chat-toggle"
|
|
onClick=${handleChatPanelToggle}
|
|
style=${{
|
|
display: chatDisabled || noVideoContent ? 'none' : 'flex',
|
|
}}
|
|
>
|
|
<span>Toggle Chat</span>
|
|
<span><${ChatIcon} /></span>
|
|
</button>
|
|
</li>
|
|
</ul>`}
|
|
${view != 'main' &&
|
|
html`<${SubMenuView} view=${view} setView=${setView} />`}
|
|
</div>`
|
|
}
|
|
</div>
|
|
</${Context.Provider}>`;
|
|
};
|
|
|
|
const SubMenuView = ({ view, setView }) => {
|
|
return html`
|
|
<div className=${`chat-view fadeInRight`}>
|
|
<ul>
|
|
<li>
|
|
<button onClick=${() => setView('main')}>
|
|
<span
|
|
><${CaretDownIcon} className="w-6 h-6 transform rotate-90"
|
|
/></span>
|
|
<span>Back</span>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
${view === 'change_username' && html`<${ChangeUsernameView} />`}
|
|
</div>
|
|
`;
|
|
};
|
|
|
|
function closeOnOutsideClick(ref, setter) {
|
|
useEffect(() => {
|
|
function handleClickOutside(event) {
|
|
if (ref.current && !ref.current.contains(event.target)) {
|
|
setter(undefined);
|
|
}
|
|
}
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
};
|
|
}, [ref]);
|
|
}
|