Require authentication to participate in chat (#4762)
* feat(chat): require authentication to participate in chat * fix: it's pretty much impossible to bypass the auth requirement, addressing review feedback anyway * feat(chat): render chat text input as disabled if chat auth is required * Commit updated API documentation --------- Co-authored-by: Owncast <owncast@owncast.online>
This commit is contained in:
@@ -397,6 +397,14 @@ func (s *Server) eventReceived(event chatClientEvent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if authentication is required for chat
|
||||||
|
if configRepository.GetChatRequireAuthentication() {
|
||||||
|
if u == nil || (!u.Authenticated && !u.IsModerator()) {
|
||||||
|
s.sendActionToClient(c, "Authentication is required to participate in chat.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var typecheck map[string]interface{}
|
var typecheck map[string]interface{}
|
||||||
if err := json.Unmarshal(event.data, &typecheck); err != nil {
|
if err := json.Unmarshal(event.data, &typecheck); err != nil {
|
||||||
log.Debugln(err)
|
log.Debugln(err)
|
||||||
|
|||||||
+4214
-4581
File diff suppressed because one or more lines are too long
@@ -1514,6 +1514,35 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
$ref: '#/components/responses/204'
|
$ref: '#/components/responses/204'
|
||||||
|
/admin/config/chat/requireauthentication:
|
||||||
|
post:
|
||||||
|
summary: Set require authentication for chat
|
||||||
|
operationId: SetChatRequireAuthentication
|
||||||
|
tags: ['Internal', 'Admin', 'Chat']
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
requestBody:
|
||||||
|
$ref: '#/components/requestBodies/AdminConfigValue'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Require authentication setting updated
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/BaseAPIResponse'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401BasicAuth'
|
||||||
|
default:
|
||||||
|
$ref: '#/components/responses/Default'
|
||||||
|
options:
|
||||||
|
operationId: SetChatRequireAuthenticationOptions
|
||||||
|
x-internal: true
|
||||||
|
tags: ['Objects', 'Internal', 'Admin', 'Chat']
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
$ref: '#/components/responses/204'
|
||||||
/admin/config/video/codec:
|
/admin/config/video/codec:
|
||||||
post:
|
post:
|
||||||
summary: Set video codec
|
summary: Set video codec
|
||||||
@@ -3660,6 +3689,9 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
chatDisabled:
|
chatDisabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
chatRequireAuthentication:
|
||||||
|
type: boolean
|
||||||
|
description: Whether users must authenticate before sending chat messages
|
||||||
nsfw:
|
nsfw:
|
||||||
type: boolean
|
type: boolean
|
||||||
authentication:
|
authentication:
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const (
|
|||||||
chatEstablishedUsersOnlyModeKey = "chat_established_users_only_mode"
|
chatEstablishedUsersOnlyModeKey = "chat_established_users_only_mode"
|
||||||
chatSpamProtectionEnabledKey = "chat_spam_protection_enabled"
|
chatSpamProtectionEnabledKey = "chat_spam_protection_enabled"
|
||||||
chatSlurFilterEnabledKey = "chat_slur_filter_enabled"
|
chatSlurFilterEnabledKey = "chat_slur_filter_enabled"
|
||||||
|
chatRequireAuthenticationKey = "chat_require_authentication"
|
||||||
notificationsEnabledKey = "notifications_enabled"
|
notificationsEnabledKey = "notifications_enabled"
|
||||||
discordConfigurationKey = "discord_configuration"
|
discordConfigurationKey = "discord_configuration"
|
||||||
browserPushConfigurationKey = "browser_push_configuration"
|
browserPushConfigurationKey = "browser_push_configuration"
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ type ConfigRepository interface {
|
|||||||
GetChatSpamProtectionEnabled() bool
|
GetChatSpamProtectionEnabled() bool
|
||||||
SetChatSlurFilterEnabled(enabled bool) error
|
SetChatSlurFilterEnabled(enabled bool) error
|
||||||
GetChatSlurFilterEnabled() bool
|
GetChatSlurFilterEnabled() bool
|
||||||
|
SetChatRequireAuthentication(enabled bool) error
|
||||||
|
GetChatRequireAuthentication() bool
|
||||||
GetExternalActions() []models.ExternalAction
|
GetExternalActions() []models.ExternalAction
|
||||||
SetExternalActions(actions []models.ExternalAction) error
|
SetExternalActions(actions []models.ExternalAction) error
|
||||||
SetCustomStyles(styles string) error
|
SetCustomStyles(styles string) error
|
||||||
|
|||||||
@@ -541,6 +541,21 @@ func (r *SqlConfigRepository) GetChatSlurFilterEnabled() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetChatRequireAuthentication will set if authentication is required for chat.
|
||||||
|
func (r *SqlConfigRepository) SetChatRequireAuthentication(enabled bool) error {
|
||||||
|
return r.datastore.SetBool(chatRequireAuthenticationKey, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChatRequireAuthentication will return if authentication is required for chat.
|
||||||
|
func (r *SqlConfigRepository) GetChatRequireAuthentication() bool {
|
||||||
|
enabled, err := r.datastore.GetBool(chatRequireAuthenticationKey)
|
||||||
|
if err == nil {
|
||||||
|
return enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// GetExternalActions will return the registered external actions.
|
// GetExternalActions will return the registered external actions.
|
||||||
func (r *SqlConfigRepository) GetExternalActions() []models.ExternalAction {
|
func (r *SqlConfigRepository) GetExternalActions() []models.ExternalAction {
|
||||||
configEntry, err := r.datastore.Get(externalActionsKey)
|
configEntry, err := r.datastore.Get(externalActionsKey)
|
||||||
|
|||||||
@@ -119,3 +119,68 @@ test('verify message is in the chat feed', async () => {
|
|||||||
|
|
||||||
expect(message.length).toBe(0);
|
expect(message.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Chat authentication requirement tests
|
||||||
|
const unauthenticatedUserFailedChatMessage = {
|
||||||
|
body:
|
||||||
|
'this unauthenticated message should fail ' +
|
||||||
|
Math.floor(Math.random() * 100),
|
||||||
|
type: 'CHAT',
|
||||||
|
};
|
||||||
|
|
||||||
|
const unauthenticatedUserSucceedChatMessage = {
|
||||||
|
body:
|
||||||
|
'this unauthenticated message should succeed ' +
|
||||||
|
Math.floor(Math.random() * 100),
|
||||||
|
type: 'CHAT',
|
||||||
|
};
|
||||||
|
|
||||||
|
test('enable chat require authentication mode', async () => {
|
||||||
|
await sendAdminRequest('config/chat/requireauthentication', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('send a message after require authentication is enabled', async () => {
|
||||||
|
const registration = await registerChat();
|
||||||
|
const accessToken = registration.accessToken;
|
||||||
|
|
||||||
|
await sendChatMessage(unauthenticatedUserFailedChatMessage, accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verify unauthenticated message is not in the chat feed', async () => {
|
||||||
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
const res = await getAdminResponse('chat/messages');
|
||||||
|
const expectedBody =
|
||||||
|
`<p>` + unauthenticatedUserFailedChatMessage.body + `</p>`;
|
||||||
|
|
||||||
|
const message = res.body.filter((obj) => {
|
||||||
|
return obj.body === expectedBody;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(message.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disable chat require authentication mode', async () => {
|
||||||
|
await sendAdminRequest('config/chat/requireauthentication', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('send a message after require authentication is disabled', async () => {
|
||||||
|
const registration = await registerChat();
|
||||||
|
const accessToken = registration.accessToken;
|
||||||
|
|
||||||
|
await sendChatMessage(unauthenticatedUserSucceedChatMessage, accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verify message from unauthenticated user is now in the chat feed', async () => {
|
||||||
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
const res = await getAdminResponse('chat/messages');
|
||||||
|
const expectedBody =
|
||||||
|
`<p>` + unauthenticatedUserSucceedChatMessage.body + `</p>`;
|
||||||
|
|
||||||
|
const message = res.body.filter((obj) => {
|
||||||
|
return obj.body === expectedBody;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(message.length).toBe(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var request = require('supertest');
|
var request = require('supertest');
|
||||||
var bcrypt = require('bcrypt');
|
var bcrypt = require('bcrypt');
|
||||||
var shajs = require("sha.js");
|
var shajs = require('sha.js');
|
||||||
|
|
||||||
const sendAdminRequest = require('./lib/admin').sendAdminRequest;
|
const sendAdminRequest = require('./lib/admin').sendAdminRequest;
|
||||||
const failAdminRequest = require('./lib/admin').failAdminRequest;
|
const failAdminRequest = require('./lib/admin').failAdminRequest;
|
||||||
@@ -330,6 +330,24 @@ test('disable search indexing', async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('set chat require authentication enabled', async () => {
|
||||||
|
await sendAdminRequest('config/chat/requireauthentication', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verify chat require authentication is enabled in config', async () => {
|
||||||
|
const res = await getAdminResponse('serverconfig');
|
||||||
|
expect(res.body.chatRequireAuthentication).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('set chat require authentication disabled', async () => {
|
||||||
|
await sendAdminRequest('config/chat/requireauthentication', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verify chat require authentication is disabled in config', async () => {
|
||||||
|
const res = await getAdminResponse('serverconfig');
|
||||||
|
expect(res.body.chatRequireAuthentication).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test('change admin password', async () => {
|
test('change admin password', async () => {
|
||||||
await sendAdminRequest('config/adminpass', newAdminPassword);
|
await sendAdminRequest('config/adminpass', newAdminPassword);
|
||||||
});
|
});
|
||||||
@@ -366,7 +384,7 @@ test('change admin password >72 bytes', async () => {
|
|||||||
test('verify admin password change (>72 bytes)', async () => {
|
test('verify admin password change (>72 bytes)', async () => {
|
||||||
const res = await getAdminResponse(
|
const res = await getAdminResponse(
|
||||||
'serverconfig',
|
'serverconfig',
|
||||||
(adminPassword = newAdminPasswordLong)
|
(adminPassword = newAdminPasswordLong),
|
||||||
);
|
);
|
||||||
|
|
||||||
bcrypt.compare(
|
bcrypt.compare(
|
||||||
@@ -374,7 +392,7 @@ test('verify admin password change (>72 bytes)', async () => {
|
|||||||
res.body.adminPassword,
|
res.body.adminPassword,
|
||||||
function (err, result) {
|
function (err, result) {
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -382,7 +400,7 @@ test('reset admin password (>72)', async () => {
|
|||||||
await sendAdminRequest(
|
await sendAdminRequest(
|
||||||
'config/adminpass',
|
'config/adminpass',
|
||||||
defaultAdminPassword,
|
defaultAdminPassword,
|
||||||
(adminPassword = newAdminPasswordLong)
|
(adminPassword = newAdminPasswordLong),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -618,6 +618,22 @@ export const ChatDisabled = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AuthenticationRequired = {
|
||||||
|
render: Template,
|
||||||
|
|
||||||
|
args: {
|
||||||
|
loading: false,
|
||||||
|
messages,
|
||||||
|
usernameToHighlight: 'testuser',
|
||||||
|
chatUserId: 'testuser',
|
||||||
|
isModerator: false,
|
||||||
|
showInput: true,
|
||||||
|
chatAvailable: true,
|
||||||
|
inputEnabled: false,
|
||||||
|
inputDisabledPlaceholder: 'Authenticate to chat',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const SingleMessage = {
|
export const SingleMessage = {
|
||||||
render: Template,
|
render: Template,
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export type ChatContainerProps = {
|
|||||||
focusInput?: boolean;
|
focusInput?: boolean;
|
||||||
desktop?: boolean;
|
desktop?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
inputEnabled?: boolean;
|
||||||
|
inputDisabledPlaceholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let resizeWindowCallback: () => void;
|
let resizeWindowCallback: () => void;
|
||||||
@@ -82,11 +84,15 @@ export const ChatContainer: FC<ChatContainerProps> = ({
|
|||||||
isModerator,
|
isModerator,
|
||||||
showInput = true,
|
showInput = true,
|
||||||
height = 'auto',
|
height = 'auto',
|
||||||
chatAvailable: chatEnabled,
|
chatAvailable,
|
||||||
desktop,
|
desktop,
|
||||||
focusInput = true,
|
focusInput = true,
|
||||||
readonly = false,
|
readonly = false,
|
||||||
|
inputEnabled,
|
||||||
|
inputDisabledPlaceholder,
|
||||||
}) => {
|
}) => {
|
||||||
|
// If inputEnabled is explicitly set, use that; otherwise fall back to chatAvailable
|
||||||
|
const chatInputEnabled = inputEnabled !== undefined ? inputEnabled : chatAvailable;
|
||||||
const [showScrollToBottomButton, setShowScrollToBottomButton] = useState(false);
|
const [showScrollToBottomButton, setShowScrollToBottomButton] = useState(false);
|
||||||
const [isAtBottom, setIsAtBottom] = useState(false);
|
const [isAtBottom, setIsAtBottom] = useState(false);
|
||||||
|
|
||||||
@@ -366,7 +372,11 @@ export const ChatContainer: FC<ChatContainerProps> = ({
|
|||||||
{MessagesTable}
|
{MessagesTable}
|
||||||
{showInput && (
|
{showInput && (
|
||||||
<div className={styles.chatTextField}>
|
<div className={styles.chatTextField}>
|
||||||
<ChatTextField enabled={chatEnabled} focusInput={focusInput} />
|
<ChatTextField
|
||||||
|
enabled={chatInputEnabled}
|
||||||
|
focusInput={focusInput}
|
||||||
|
disabledPlaceholder={inputDisabledPlaceholder}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{desktop && (
|
{desktop && (
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export type ChatTextFieldProps = {
|
|||||||
defaultText?: string;
|
defaultText?: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
focusInput: boolean;
|
focusInput: boolean;
|
||||||
|
disabledPlaceholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const characterLimit = 300;
|
const characterLimit = 300;
|
||||||
@@ -119,7 +120,12 @@ const getTextContent = node => {
|
|||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, focusInput }) => {
|
export const ChatTextField: FC<ChatTextFieldProps> = ({
|
||||||
|
defaultText,
|
||||||
|
enabled,
|
||||||
|
focusInput,
|
||||||
|
disabledPlaceholder,
|
||||||
|
}) => {
|
||||||
const [inputDraft, setInputDraft] = useRecoilState(chatInputDraftAtom);
|
const [inputDraft, setInputDraft] = useRecoilState(chatInputDraftAtom);
|
||||||
const [characterCount, setCharacterCount] = useState(defaultText?.length);
|
const [characterCount, setCharacterCount] = useState(defaultText?.length);
|
||||||
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
|
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
|
||||||
@@ -276,7 +282,9 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
|
|||||||
<ContentEditable
|
<ContentEditable
|
||||||
id="chat-input-content-editable"
|
id="chat-input-content-editable"
|
||||||
html={defaultText || ''}
|
html={defaultText || ''}
|
||||||
placeholder={enabled ? 'Send a message to chat' : 'Chat is disabled'}
|
placeholder={
|
||||||
|
enabled ? 'Send a message to chat' : disabledPlaceholder || 'Chat is disabled'
|
||||||
|
}
|
||||||
disabled={!enabled}
|
disabled={!enabled}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
onContentChange={handleChange}
|
onContentChange={handleChange}
|
||||||
|
|||||||
@@ -14,9 +14,17 @@ export type ChatModalProps = {
|
|||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
currentUser: CurrentUser;
|
currentUser: CurrentUser;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
|
inputEnabled?: boolean;
|
||||||
|
inputDisabledPlaceholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChatModal: FC<ChatModalProps> = ({ messages, currentUser, handleClose }) => {
|
export const ChatModal: FC<ChatModalProps> = ({
|
||||||
|
messages,
|
||||||
|
currentUser,
|
||||||
|
handleClose,
|
||||||
|
inputEnabled = true,
|
||||||
|
inputDisabledPlaceholder,
|
||||||
|
}) => {
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -58,6 +66,8 @@ export const ChatModal: FC<ChatModalProps> = ({ messages, currentUser, handleClo
|
|||||||
chatUserId={id}
|
chatUserId={id}
|
||||||
isModerator={isModerator}
|
isModerator={isModerator}
|
||||||
chatAvailable
|
chatAvailable
|
||||||
|
inputEnabled={inputEnabled}
|
||||||
|
inputDisabledPlaceholder={inputDisabledPlaceholder}
|
||||||
focusInput={false}
|
focusInput={false}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import MessageFilled from '@ant-design/icons/MessageFilled';
|
|||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import ActionButtons from './ActionButtons';
|
import ActionButtons from './ActionButtons';
|
||||||
import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../utils/localStorage';
|
import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../utils/localStorage';
|
||||||
import { canPushNotificationsBeSupported } from '../../../utils/browserPushNotifications';
|
import { canPushNotificationsBeSupported } from '../../../utils/browserPushNotifications';
|
||||||
|
import { Localization } from '../../../types/localization';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clientConfigStateAtom,
|
clientConfigStateAtom,
|
||||||
@@ -19,6 +21,7 @@ import {
|
|||||||
serverStatusState,
|
serverStatusState,
|
||||||
isChatAvailableSelector,
|
isChatAvailableSelector,
|
||||||
visibleChatMessagesSelector,
|
visibleChatMessagesSelector,
|
||||||
|
chatAuthenticatedAtom,
|
||||||
} from '../../stores/ClientConfigStore';
|
} from '../../stores/ClientConfigStore';
|
||||||
import { ClientConfig } from '../../../interfaces/client-config.model';
|
import { ClientConfig } from '../../../interfaces/client-config.model';
|
||||||
|
|
||||||
@@ -97,6 +100,7 @@ const ExternalModal = ({ externalActionToDisplay, setExternalActionToDisplay })
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Content: FC = () => {
|
export const Content: FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
||||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
||||||
const chatState = useRecoilValue<ChatState>(chatStateAtom);
|
const chatState = useRecoilValue<ChatState>(chatStateAtom);
|
||||||
@@ -106,6 +110,7 @@ export const Content: FC = () => {
|
|||||||
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
||||||
const online = useRecoilValue<boolean>(isOnlineSelector);
|
const online = useRecoilValue<boolean>(isOnlineSelector);
|
||||||
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
|
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
|
||||||
|
const isUserAuthenticated = useRecoilValue<boolean>(chatAuthenticatedAtom);
|
||||||
|
|
||||||
const { viewerCount, lastConnectTime, lastDisconnectTime, streamTitle } =
|
const { viewerCount, lastConnectTime, lastDisconnectTime, streamTitle } =
|
||||||
useRecoilValue<ServerStatus>(serverStatusState);
|
useRecoilValue<ServerStatus>(serverStatusState);
|
||||||
@@ -118,6 +123,7 @@ export const Content: FC = () => {
|
|||||||
externalActions,
|
externalActions,
|
||||||
offlineMessage,
|
offlineMessage,
|
||||||
chatDisabled,
|
chatDisabled,
|
||||||
|
chatRequireAuthentication,
|
||||||
federation,
|
federation,
|
||||||
notifications,
|
notifications,
|
||||||
} = clientConfig;
|
} = clientConfig;
|
||||||
@@ -219,6 +225,16 @@ export const Content: FC = () => {
|
|||||||
|
|
||||||
const showChat = isChatAvailable && !chatDisabled && chatState === ChatState.VISIBLE;
|
const showChat = isChatAvailable && !chatDisabled && chatState === ChatState.VISIBLE;
|
||||||
|
|
||||||
|
// Determine if chat input should be enabled based on authentication requirements.
|
||||||
|
// Moderators bypass the authentication requirement.
|
||||||
|
const chatInputEnabled = !!(
|
||||||
|
isChatAvailable &&
|
||||||
|
(!chatRequireAuthentication || isUserAuthenticated || currentUser?.isModerator)
|
||||||
|
);
|
||||||
|
const chatInputDisabledMessage = chatRequireAuthentication
|
||||||
|
? t(Localization.Frontend.Chat.authenticateToChat)
|
||||||
|
: t(Localization.Frontend.chatDisabled);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.main}>
|
<div className={styles.main}>
|
||||||
<div className={styles.mainColumn}>
|
<div className={styles.mainColumn}>
|
||||||
@@ -325,6 +341,8 @@ export const Content: FC = () => {
|
|||||||
isModerator={currentUser.isModerator}
|
isModerator={currentUser.isModerator}
|
||||||
chatAvailable={isChatAvailable}
|
chatAvailable={isChatAvailable}
|
||||||
showInput={!!currentUser}
|
showInput={!!currentUser}
|
||||||
|
inputEnabled={chatInputEnabled}
|
||||||
|
inputDisabledPlaceholder={chatInputDisabledMessage}
|
||||||
desktop
|
desktop
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -351,6 +369,8 @@ export const Content: FC = () => {
|
|||||||
messages={messages}
|
messages={messages}
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
handleClose={() => setShowChatModal(false)}
|
handleClose={() => setShowChatModal(false)}
|
||||||
|
inputEnabled={chatInputEnabled}
|
||||||
|
inputDisabledPlaceholder={chatInputDisabledMessage}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isMobile && isChatAvailable && !chatDisabled && (
|
{isMobile && isChatAvailable && !chatDisabled && (
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"unsupportedLocal": "Browser notifications are not supported for local servers."
|
"unsupportedLocal": "Browser notifications are not supported for local servers."
|
||||||
},
|
},
|
||||||
"Chat": {
|
"Chat": {
|
||||||
|
"authenticateToChat": "Authenticate to chat",
|
||||||
"moderatorNotification": "You are now a moderator.",
|
"moderatorNotification": "You are now a moderator.",
|
||||||
"nameChangeText": "is now known as",
|
"nameChangeText": "is now known as",
|
||||||
"userJoined": "joined the chat.",
|
"userJoined": "joined the chat.",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface ClientConfig {
|
|||||||
extraPageContent: string;
|
extraPageContent: string;
|
||||||
socialHandles: SocialHandle[];
|
socialHandles: SocialHandle[];
|
||||||
chatDisabled: boolean;
|
chatDisabled: boolean;
|
||||||
|
chatRequireAuthentication: boolean;
|
||||||
externalActions: any[];
|
externalActions: any[];
|
||||||
customStyles: string;
|
customStyles: string;
|
||||||
appearanceVariables: Map<string, string>;
|
appearanceVariables: Map<string, string>;
|
||||||
@@ -55,6 +56,7 @@ export function makeEmptyClientConfig(): ClientConfig {
|
|||||||
extraPageContent: '',
|
extraPageContent: '',
|
||||||
socialHandles: [],
|
socialHandles: [],
|
||||||
chatDisabled: false,
|
chatDisabled: false,
|
||||||
|
chatRequireAuthentication: false,
|
||||||
externalActions: [],
|
externalActions: [],
|
||||||
customStyles: '',
|
customStyles: '',
|
||||||
appearanceVariables: new Map(),
|
appearanceVariables: new Map(),
|
||||||
|
|||||||
Generated
+212
-75
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@ import {
|
|||||||
FIELD_PROPS_ENABLE_CHAT_SLUR_FILTER,
|
FIELD_PROPS_ENABLE_CHAT_SLUR_FILTER,
|
||||||
CHAT_ESTABLISHED_USER_MODE,
|
CHAT_ESTABLISHED_USER_MODE,
|
||||||
FIELD_PROPS_DISABLE_CHAT,
|
FIELD_PROPS_DISABLE_CHAT,
|
||||||
|
FIELD_PROPS_CHAT_REQUIRE_AUTHENTICATION,
|
||||||
postConfigUpdateToAPI,
|
postConfigUpdateToAPI,
|
||||||
RESET_TIMEOUT,
|
RESET_TIMEOUT,
|
||||||
TEXTFIELD_PROPS_CHAT_FORBIDDEN_USERNAMES,
|
TEXTFIELD_PROPS_CHAT_FORBIDDEN_USERNAMES,
|
||||||
@@ -47,6 +48,7 @@ export default function ConfigChat() {
|
|||||||
chatEstablishedUserMode,
|
chatEstablishedUserMode,
|
||||||
chatSpamProtectionEnabled,
|
chatSpamProtectionEnabled,
|
||||||
chatSlurFilterEnabled,
|
chatSlurFilterEnabled,
|
||||||
|
chatRequireAuthentication,
|
||||||
} = serverConfig;
|
} = serverConfig;
|
||||||
const { welcomeMessage } = instanceDetails;
|
const { welcomeMessage } = instanceDetails;
|
||||||
|
|
||||||
@@ -77,6 +79,10 @@ export default function ConfigChat() {
|
|||||||
handleFieldChange({ fieldName: 'chatSlurFilterEnabled', value: enabled });
|
handleFieldChange({ fieldName: 'chatSlurFilterEnabled', value: enabled });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleChatRequireAuthenticationChange(enabled: boolean) {
|
||||||
|
handleFieldChange({ fieldName: 'chatRequireAuthentication', value: enabled });
|
||||||
|
}
|
||||||
|
|
||||||
function resetForbiddenUsernameState() {
|
function resetForbiddenUsernameState() {
|
||||||
setForbiddenUsernameSaveState(null);
|
setForbiddenUsernameSaveState(null);
|
||||||
}
|
}
|
||||||
@@ -169,6 +175,7 @@ export default function ConfigChat() {
|
|||||||
chatEstablishedUserMode,
|
chatEstablishedUserMode,
|
||||||
chatSpamProtectionEnabled,
|
chatSpamProtectionEnabled,
|
||||||
chatSlurFilterEnabled,
|
chatSlurFilterEnabled,
|
||||||
|
chatRequireAuthentication,
|
||||||
});
|
});
|
||||||
}, [serverConfig]);
|
}, [serverConfig]);
|
||||||
|
|
||||||
@@ -250,6 +257,12 @@ export default function ConfigChat() {
|
|||||||
checked={formDataValues.chatSlurFilterEnabled}
|
checked={formDataValues.chatSlurFilterEnabled}
|
||||||
onChange={handleChatSlurFilterChange}
|
onChange={handleChatSlurFilterChange}
|
||||||
/>
|
/>
|
||||||
|
<ToggleSwitch
|
||||||
|
fieldName="chatRequireAuthentication"
|
||||||
|
{...FIELD_PROPS_CHAT_REQUIRE_AUTHENTICATION}
|
||||||
|
checked={formDataValues.chatRequireAuthentication}
|
||||||
|
onChange={handleChatRequireAuthenticationChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { ChatMessage } from '../../../../interfaces/chat-message.model';
|
import { ChatMessage } from '../../../../interfaces/chat-message.model';
|
||||||
import { ChatContainer } from '../../../../components/chat/ChatContainer/ChatContainer';
|
import { ChatContainer } from '../../../../components/chat/ChatContainer/ChatContainer';
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
appStateAtom,
|
appStateAtom,
|
||||||
serverStatusState,
|
serverStatusState,
|
||||||
isChatAvailableSelector,
|
isChatAvailableSelector,
|
||||||
|
chatAuthenticatedAtom,
|
||||||
} from '../../../../components/stores/ClientConfigStore';
|
} from '../../../../components/stores/ClientConfigStore';
|
||||||
import Header from '../../../../components/ui/Header/Header';
|
import Header from '../../../../components/ui/Header/Header';
|
||||||
import { ClientConfig } from '../../../../interfaces/client-config.model';
|
import { ClientConfig } from '../../../../interfaces/client-config.model';
|
||||||
@@ -19,8 +21,10 @@ import { AppStateOptions } from '../../../../components/stores/application-state
|
|||||||
import { ServerStatus } from '../../../../interfaces/server-status.model';
|
import { ServerStatus } from '../../../../interfaces/server-status.model';
|
||||||
import { Theme } from '../../../../components/theme/Theme';
|
import { Theme } from '../../../../components/theme/Theme';
|
||||||
import { ComponentError } from '../../../../components/ui/ComponentError/ComponentError';
|
import { ComponentError } from '../../../../components/ui/ComponentError/ComponentError';
|
||||||
|
import { Localization } from '../../../../types/localization';
|
||||||
|
|
||||||
export default function ReadWriteChatEmbed() {
|
export default function ReadWriteChatEmbed() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const currentUser = useRecoilValue(currentUserAtom);
|
const currentUser = useRecoilValue(currentUserAtom);
|
||||||
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
||||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
||||||
@@ -28,8 +32,19 @@ export default function ReadWriteChatEmbed() {
|
|||||||
|
|
||||||
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
||||||
const isChatAvailable = useRecoilValue(isChatAvailableSelector);
|
const isChatAvailable = useRecoilValue(isChatAvailableSelector);
|
||||||
|
const isUserAuthenticated = useRecoilValue<boolean>(chatAuthenticatedAtom);
|
||||||
|
|
||||||
const { name, chatDisabled } = clientConfig;
|
const { name, chatDisabled, chatRequireAuthentication } = clientConfig;
|
||||||
|
|
||||||
|
// Determine if chat input should be enabled based on authentication requirements.
|
||||||
|
// Moderators bypass the authentication requirement.
|
||||||
|
const chatInputEnabled = !!(
|
||||||
|
isChatAvailable &&
|
||||||
|
(!chatRequireAuthentication || isUserAuthenticated || currentUser?.isModerator)
|
||||||
|
);
|
||||||
|
const chatInputDisabledMessage = chatRequireAuthentication
|
||||||
|
? t(Localization.Frontend.Chat.authenticateToChat)
|
||||||
|
: t(Localization.Frontend.chatDisabled);
|
||||||
const { videoAvailable } = appState;
|
const { videoAvailable } = appState;
|
||||||
const { streamTitle, online } = clientStatus;
|
const { streamTitle, online } = clientStatus;
|
||||||
|
|
||||||
@@ -73,6 +88,8 @@ export default function ReadWriteChatEmbed() {
|
|||||||
showInput
|
showInput
|
||||||
height="92vh"
|
height="92vh"
|
||||||
chatAvailable={isChatAvailable}
|
chatAvailable={isChatAvailable}
|
||||||
|
inputEnabled={chatInputEnabled}
|
||||||
|
inputDisabledPlaceholder={chatInputDisabledMessage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const Page = () => {
|
|||||||
|
|
||||||
const fakeConfig: ClientConfig = {
|
const fakeConfig: ClientConfig = {
|
||||||
chatDisabled: false,
|
chatDisabled: false,
|
||||||
|
chatRequireAuthentication: false,
|
||||||
name: 'Fake Owncast Server',
|
name: 'Fake Owncast Server',
|
||||||
summary: '',
|
summary: '',
|
||||||
logo: '',
|
logo: '',
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ export interface ConfigDetails {
|
|||||||
chatDisabled: boolean;
|
chatDisabled: boolean;
|
||||||
chatSpamProtectionEnabled: boolean;
|
chatSpamProtectionEnabled: boolean;
|
||||||
chatSlurFilterEnabled: boolean;
|
chatSlurFilterEnabled: boolean;
|
||||||
|
chatRequireAuthentication: boolean;
|
||||||
federation: Federation;
|
federation: Federation;
|
||||||
notifications: NotificationsConfig;
|
notifications: NotificationsConfig;
|
||||||
chatJoinMessagesEnabled: boolean;
|
chatJoinMessagesEnabled: boolean;
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ export const Localization = {
|
|||||||
deniedTitle: 'Frontend.BrowserNotifyModal.deniedTitle',
|
deniedTitle: 'Frontend.BrowserNotifyModal.deniedTitle',
|
||||||
deniedDescription: 'Frontend.BrowserNotifyModal.deniedDescription',
|
deniedDescription: 'Frontend.BrowserNotifyModal.deniedDescription',
|
||||||
mainDescription: 'Frontend.BrowserNotifyModal.mainDescription',
|
mainDescription: 'Frontend.BrowserNotifyModal.mainDescription',
|
||||||
learnMoreAboutNotifications:
|
learnMoreAboutNotifications: 'Frontend.BrowserNotifyModal.learnMoreAboutNotifications',
|
||||||
'Frontend.BrowserNotifyModal.learnMoreAboutNotifications',
|
|
||||||
errorTitle: 'Frontend.BrowserNotifyModal.errorTitle',
|
errorTitle: 'Frontend.BrowserNotifyModal.errorTitle',
|
||||||
errorMessage: 'Frontend.BrowserNotifyModal.errorMessage',
|
errorMessage: 'Frontend.BrowserNotifyModal.errorMessage',
|
||||||
},
|
},
|
||||||
@@ -107,6 +106,7 @@ export const Localization = {
|
|||||||
userLeft: 'Frontend.Chat.userLeft',
|
userLeft: 'Frontend.Chat.userLeft',
|
||||||
nameChangeText: 'Frontend.Chat.nameChangeText',
|
nameChangeText: 'Frontend.Chat.nameChangeText',
|
||||||
moderatorNotification: 'Frontend.Chat.moderatorNotification',
|
moderatorNotification: 'Frontend.Chat.moderatorNotification',
|
||||||
|
authenticateToChat: 'Frontend.Chat.authenticateToChat',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Follow modal component
|
// Follow modal component
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const API_CHAT_JOIN_MESSAGES_ENABLED = '/chat/joinmessagesenabled';
|
|||||||
const API_CHAT_ESTABLISHED_MODE = '/chat/establishedusermode';
|
const API_CHAT_ESTABLISHED_MODE = '/chat/establishedusermode';
|
||||||
const API_CHAT_SPAM_PROTECTION_ENABLED = '/chat/spamprotectionenabled';
|
const API_CHAT_SPAM_PROTECTION_ENABLED = '/chat/spamprotectionenabled';
|
||||||
const API_CHAT_SLUR_FILTER_ENABLED = '/chat/slurfilterenabled';
|
const API_CHAT_SLUR_FILTER_ENABLED = '/chat/slurfilterenabled';
|
||||||
|
const API_CHAT_REQUIRE_AUTHENTICATION = '/chat/requireauthentication';
|
||||||
const API_DISABLE_SEARCH_INDEXING = '/disablesearchindexing';
|
const API_DISABLE_SEARCH_INDEXING = '/disablesearchindexing';
|
||||||
const API_SOCKET_HOST_OVERRIDE = '/sockethostoverride';
|
const API_SOCKET_HOST_OVERRIDE = '/sockethostoverride';
|
||||||
const API_VIDEO_SERVING_ENDPOINT = '/videoservingendpoint';
|
const API_VIDEO_SERVING_ENDPOINT = '/videoservingendpoint';
|
||||||
@@ -292,6 +293,14 @@ export const CHAT_ESTABLISHED_USER_MODE = {
|
|||||||
useSubmit: true,
|
useSubmit: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const FIELD_PROPS_CHAT_REQUIRE_AUTHENTICATION = {
|
||||||
|
apiPath: API_CHAT_REQUIRE_AUTHENTICATION,
|
||||||
|
configPath: '',
|
||||||
|
label: 'Require Authentication',
|
||||||
|
tip: 'Only users who have authenticated may chat.',
|
||||||
|
useSubmit: true,
|
||||||
|
};
|
||||||
|
|
||||||
export const TEXTFIELD_PROPS_CHAT_FORBIDDEN_USERNAMES = {
|
export const TEXTFIELD_PROPS_CHAT_FORBIDDEN_USERNAMES = {
|
||||||
apiPath: API_CHAT_FORBIDDEN_USERNAMES,
|
apiPath: API_CHAT_FORBIDDEN_USERNAMES,
|
||||||
placeholder: 'username',
|
placeholder: 'username',
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ const initialServerConfigState: ConfigDetails = {
|
|||||||
chatDisabled: false,
|
chatDisabled: false,
|
||||||
chatSpamProtectionEnabled: true,
|
chatSpamProtectionEnabled: true,
|
||||||
chatSlurFilterEnabled: false,
|
chatSlurFilterEnabled: false,
|
||||||
|
chatRequireAuthentication: false,
|
||||||
chatJoinMessagesEnabled: true,
|
chatJoinMessagesEnabled: true,
|
||||||
chatEstablishedUserMode: false,
|
chatEstablishedUserMode: false,
|
||||||
hideViewerCount: false,
|
hideViewerCount: false,
|
||||||
@@ -127,6 +128,7 @@ const ServerStatusProvider: FC<ServerStatusProviderProps> = ({ children }) => {
|
|||||||
|
|
||||||
setStatus({ ...result, error: { type: null, msg: null } });
|
setStatus({ ...result, error: { type: null, msg: null } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch server status:', error);
|
||||||
setStatus(initialStatus => ({
|
setStatus(initialStatus => ({
|
||||||
...initialStatus,
|
...initialStatus,
|
||||||
error: {
|
error: {
|
||||||
@@ -134,7 +136,6 @@ const ServerStatusProvider: FC<ServerStatusProviderProps> = ({ children }) => {
|
|||||||
msg: 'Cannot connect to the Owncast service. Please check you are connected to the internet and the Owncast server is running.',
|
msg: 'Cannot connect to the Owncast service. Please check you are connected to the internet and the Owncast server is running.',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
// todo
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getConfig = async () => {
|
const getConfig = async () => {
|
||||||
|
|||||||
@@ -877,6 +877,25 @@ func SetChatSlurFilterEnabled(w http.ResponseWriter, r *http.Request) {
|
|||||||
webutils.WriteSimpleResponse(w, true, "chat message slur filter changed")
|
webutils.WriteSimpleResponse(w, true, "chat message slur filter changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetChatRequireAuthentication will enable or disable requiring authentication for chat.
|
||||||
|
func SetChatRequireAuthentication(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !requirePOST(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configValue, success := getValueFromRequest(w, r)
|
||||||
|
if !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configRepository := configrepository.Get()
|
||||||
|
if err := configRepository.SetChatRequireAuthentication(configValue.Value.(bool)); err != nil {
|
||||||
|
webutils.WriteSimpleResponse(w, false, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
webutils.WriteSimpleResponse(w, true, "chat authentication requirement changed")
|
||||||
|
}
|
||||||
|
|
||||||
func requirePOST(w http.ResponseWriter, r *http.Request) bool {
|
func requirePOST(w http.ResponseWriter, r *http.Request) bool {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
webutils.WriteSimpleResponse(w, false, r.Method+" not supported")
|
webutils.WriteSimpleResponse(w, false, r.Method+" not supported")
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
ChatEstablishedUserMode: configRepository.GetChatEstbalishedUsersOnlyMode(),
|
ChatEstablishedUserMode: configRepository.GetChatEstbalishedUsersOnlyMode(),
|
||||||
ChatSpamProtectionEnabled: configRepository.GetChatSpamProtectionEnabled(),
|
ChatSpamProtectionEnabled: configRepository.GetChatSpamProtectionEnabled(),
|
||||||
ChatSlurFilterEnabled: configRepository.GetChatSlurFilterEnabled(),
|
ChatSlurFilterEnabled: configRepository.GetChatSlurFilterEnabled(),
|
||||||
|
ChatRequireAuthentication: configRepository.GetChatRequireAuthentication(),
|
||||||
HideViewerCount: configRepository.GetHideViewerCount(),
|
HideViewerCount: configRepository.GetHideViewerCount(),
|
||||||
DisableSearchIndexing: configRepository.GetDisableSearchIndexing(),
|
DisableSearchIndexing: configRepository.GetDisableSearchIndexing(),
|
||||||
VideoSettings: videoSettings{
|
VideoSettings: videoSettings{
|
||||||
@@ -129,6 +130,7 @@ type serverConfigAdminResponse struct {
|
|||||||
ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"`
|
ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"`
|
||||||
ChatSpamProtectionEnabled bool `json:"chatSpamProtectionEnabled"`
|
ChatSpamProtectionEnabled bool `json:"chatSpamProtectionEnabled"`
|
||||||
ChatSlurFilterEnabled bool `json:"chatSlurFilterEnabled"`
|
ChatSlurFilterEnabled bool `json:"chatSlurFilterEnabled"`
|
||||||
|
ChatRequireAuthentication bool `json:"chatRequireAuthentication"`
|
||||||
DisableSearchIndexing bool `json:"disableSearchIndexing"`
|
DisableSearchIndexing bool `json:"disableSearchIndexing"`
|
||||||
StreamKeyOverridden bool `json:"streamKeyOverridden"`
|
StreamKeyOverridden bool `json:"streamKeyOverridden"`
|
||||||
HideViewerCount bool `json:"hideViewerCount"`
|
HideViewerCount bool `json:"hideViewerCount"`
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type webConfigResponse struct {
|
|||||||
HideViewerCount bool `json:"hideViewerCount"`
|
HideViewerCount bool `json:"hideViewerCount"`
|
||||||
ChatDisabled bool `json:"chatDisabled"`
|
ChatDisabled bool `json:"chatDisabled"`
|
||||||
ChatSpamProtectionDisabled bool `json:"chatSpamProtectionDisabled"`
|
ChatSpamProtectionDisabled bool `json:"chatSpamProtectionDisabled"`
|
||||||
|
ChatRequireAuthentication bool `json:"chatRequireAuthentication"`
|
||||||
NSFW bool `json:"nsfw"`
|
NSFW bool `json:"nsfw"`
|
||||||
Authentication authenticationConfigResponse `json:"authentication"`
|
Authentication authenticationConfigResponse `json:"authentication"`
|
||||||
}
|
}
|
||||||
@@ -134,6 +135,7 @@ func getConfigResponse() webConfigResponse {
|
|||||||
SocialHandles: socialHandles,
|
SocialHandles: socialHandles,
|
||||||
ChatDisabled: configRepository.GetChatDisabled(),
|
ChatDisabled: configRepository.GetChatDisabled(),
|
||||||
ChatSpamProtectionDisabled: configRepository.GetChatSpamProtectionEnabled(),
|
ChatSpamProtectionDisabled: configRepository.GetChatSpamProtectionEnabled(),
|
||||||
|
ChatRequireAuthentication: configRepository.GetChatRequireAuthentication(),
|
||||||
ExternalActions: configRepository.GetExternalActions(),
|
ExternalActions: configRepository.GetExternalActions(),
|
||||||
CustomStyles: configRepository.GetCustomStyles(),
|
CustomStyles: configRepository.GetCustomStyles(),
|
||||||
MaxSocketPayloadSize: config.MaxSocketPayloadSize,
|
MaxSocketPayloadSize: config.MaxSocketPayloadSize,
|
||||||
|
|||||||
@@ -127,6 +127,14 @@ func (*ServerInterfaceImpl) SetChatSlurFilterEnabledOptions(w http.ResponseWrite
|
|||||||
middleware.RequireAdminAuth(admin.SetChatSlurFilterEnabled)(w, r)
|
middleware.RequireAdminAuth(admin.SetChatSlurFilterEnabled)(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*ServerInterfaceImpl) SetChatRequireAuthentication(w http.ResponseWriter, r *http.Request) {
|
||||||
|
middleware.RequireAdminAuth(admin.SetChatRequireAuthentication)(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ServerInterfaceImpl) SetChatRequireAuthenticationOptions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
middleware.RequireAdminAuth(admin.SetChatRequireAuthentication)(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
func (*ServerInterfaceImpl) SetVideoCodec(w http.ResponseWriter, r *http.Request) {
|
func (*ServerInterfaceImpl) SetVideoCodec(w http.ResponseWriter, r *http.Request) {
|
||||||
middleware.RequireAdminAuth(admin.SetVideoCodec)(w, r)
|
middleware.RequireAdminAuth(admin.SetVideoCodec)(w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package generated provides primitives to interact with the openapi HTTP API.
|
// Package generated provides primitives to interact with the openapi HTTP API.
|
||||||
//
|
//
|
||||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT.
|
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT.
|
||||||
package generated
|
package generated
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -918,6 +918,9 @@ type SetForbiddenUsernameListJSONRequestBody SetForbiddenUsernameListJSONBody
|
|||||||
// SetChatJoinMessagesEnabledJSONRequestBody defines body for SetChatJoinMessagesEnabled for application/json ContentType.
|
// SetChatJoinMessagesEnabledJSONRequestBody defines body for SetChatJoinMessagesEnabled for application/json ContentType.
|
||||||
type SetChatJoinMessagesEnabledJSONRequestBody = AdminConfigValue
|
type SetChatJoinMessagesEnabledJSONRequestBody = AdminConfigValue
|
||||||
|
|
||||||
|
// SetChatRequireAuthenticationJSONRequestBody defines body for SetChatRequireAuthentication for application/json ContentType.
|
||||||
|
type SetChatRequireAuthenticationJSONRequestBody = AdminConfigValue
|
||||||
|
|
||||||
// SetChatSlurFilterEnabledJSONRequestBody defines body for SetChatSlurFilterEnabled for application/json ContentType.
|
// SetChatSlurFilterEnabledJSONRequestBody defines body for SetChatSlurFilterEnabled for application/json ContentType.
|
||||||
type SetChatSlurFilterEnabledJSONRequestBody = AdminConfigValue
|
type SetChatSlurFilterEnabledJSONRequestBody = AdminConfigValue
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user