IndieAuth support (#1811)

* 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>
This commit is contained in:
Gabe Kangas
2022-04-21 14:55:26 -07:00
committed by GitHub
parent b86537fa91
commit b835de2dc4
47 changed files with 1844 additions and 274 deletions

View File

@@ -27,6 +27,8 @@ import FediverseFollowModal, {
import { NotifyButton, NotifyModal } from './components/notification.js';
import { isPushNotificationSupported } from './notification/registerWeb.js';
import ChatSettingsModal from './components/chat-settings-modal.js';
import {
addNewlines,
checkUrlPathForDisplay,
@@ -110,6 +112,9 @@ export default class App extends Component {
externalActionModalData: null,
fediverseModalData: null,
// authentication options
indieAuthEnabled: false,
// routing & tabbing
section: '',
sectionId: '',
@@ -144,6 +149,8 @@ export default class App extends Component {
this.closeFediverseFollowModal = this.closeFediverseFollowModal.bind(this);
this.displayNotificationModal = this.displayNotificationModal.bind(this);
this.closeNotificationModal = this.closeNotificationModal.bind(this);
this.showAuthModal = this.showAuthModal.bind(this);
this.closeAuthModal = this.closeAuthModal.bind(this);
// player events
this.handlePlayerReady = this.handlePlayerReady.bind(this);
@@ -268,8 +275,14 @@ export default class App extends Component {
}
setConfigData(data = {}) {
const { name, summary, chatDisabled, socketHostOverride, notifications } =
data;
const {
name,
summary,
chatDisabled,
socketHostOverride,
notifications,
authentication,
} = data;
window.document.title = name;
this.socketHostOverride = socketHostOverride;
@@ -281,10 +294,12 @@ export default class App extends Component {
}
this.hasConfiguredChat = true;
const { indieAuthEnabled } = authentication;
this.setState({
canChat: !chatDisabled,
notifications,
indieAuthEnabled,
configData: {
...data,
summary: summary && addNewlines(summary),
@@ -618,6 +633,17 @@ export default class App extends Component {
}
}
showAuthModal() {
const data = {
title: 'Chat',
};
this.setState({ authModalData: data });
}
closeAuthModal() {
this.setState({ authModalData: null });
}
handleWebsocketMessage(e) {
if (e.type === SOCKET_MESSAGE_TYPES.ERROR_USER_DISABLED) {
// User has been actively disabled on the backend. Turn off chat for them.
@@ -637,10 +663,10 @@ export default class App extends Component {
// When connected the user will return an event letting us know what our
// user details are so we can display them properly.
const { user } = e;
const { displayName } = user;
const { displayName, authenticated } = user;
this.setState({
username: displayName,
authenticated,
isModerator: checkIsModerator(e),
});
}
@@ -724,17 +750,20 @@ export default class App extends Component {
streamTitle,
touchKeyboardActive,
username,
authenticated,
viewerCount,
websocket,
windowHeight,
windowWidth,
fediverseModalData,
authModalData,
externalActionModalData,
notificationModalData,
notifications,
lastDisconnectTime,
section,
sectionId,
indieAuthEnabled,
} = state;
const {
@@ -864,11 +893,32 @@ export default class App extends Component {
/>`}
/>`;
const authModal =
authModalData &&
html`
<${ExternalActionModal}
onClose=${this.closeAuthModal}
action=${authModalData}
useIframe=${false}
customContent=${html`<${ChatSettingsModal}
name=${name}
logo=${logo}
onUsernameChange=${this.handleUsernameChange}
username=${username}
accessToken=${this.state.accessToken}
authenticated=${authenticated}
onClose=${this.closeAuthModal}
indieAuthEnabled=${indieAuthEnabled}
/>`}
/>
`;
const chat = this.state.websocket
? html`
<${Chat}
websocket=${websocket}
username=${username}
authenticated=${authenticated}
chatInputEnabled=${chatInputEnabled && !chatDisabled}
instanceTitle=${name}
accessToken=${accessToken}
@@ -911,6 +961,8 @@ export default class App extends Component {
});
}
const authIcon = '/img/user-settings.svg';
return html`
<div
id="app-container"
@@ -942,9 +994,11 @@ export default class App extends Component {
>
</h1>
<${ChatMenu} username=${username} isModerator=${isModerator} onUsernameChange=${
this.handleUsernameChange
} onFocus=${this.handleFormFocus} onBlur=${
<${ChatMenu} username=${username} isModerator=${isModerator} showAuthModal=${
indieAuthEnabled && this.showAuthModal
} onUsernameChange=${this.handleUsernameChange} onFocus=${
this.handleFormFocus
} onBlur=${
this.handleFormBlur
} chatDisabled=${chatDisabled} noVideoContent=${noVideoContent} handleChatPanelToggle=${
this.handleChatPanelToggle
@@ -1027,7 +1081,7 @@ export default class App extends Component {
</footer>
${chat} ${externalActionModal} ${fediverseFollowModal}
${notificationModal}
${notificationModal} ${authModal}
</div>
`;
}