feat: add support for robots.txt disabling search indexing (#2929)

* feat: add support for robots.txt

Can toggle disabling search engine indexing. Closes #2684

* fix: unexport ts const
This commit is contained in:
Gabe Kangas 2023-05-30 11:09:51 -07:00 committed by GitHub
parent d5fd76d796
commit 15dc718e61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 122 additions and 2 deletions

View file

@ -751,6 +751,26 @@ func SetHideViewerCount(w http.ResponseWriter, r *http.Request) {
controllers.WriteSimpleResponse(w, true, "hide viewer count setting updated") controllers.WriteSimpleResponse(w, true, "hide viewer count setting updated")
} }
// SetDisableSearchIndexing will set search indexing support.
func SetDisableSearchIndexing(w http.ResponseWriter, r *http.Request) {
if !requirePOST(w, r) {
return
}
configValue, success := getValueFromRequest(w, r)
if !success {
controllers.WriteSimpleResponse(w, false, "unable to update search indexing")
return
}
if err := data.SetDisableSearchIndexing(configValue.Value.(bool)); err != nil {
controllers.WriteSimpleResponse(w, false, err.Error())
return
}
controllers.WriteSimpleResponse(w, true, "search indexing support updated")
}
func requirePOST(w http.ResponseWriter, r *http.Request) bool { func requirePOST(w http.ResponseWriter, r *http.Request) bool {
if r.Method != controllers.POST { if r.Method != controllers.POST {
controllers.WriteSimpleResponse(w, false, r.Method+" not supported") controllers.WriteSimpleResponse(w, false, r.Method+" not supported")

View file

@ -61,6 +61,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
SocketHostOverride: data.GetWebsocketOverrideHost(), SocketHostOverride: data.GetWebsocketOverrideHost(),
ChatEstablishedUserMode: data.GetChatEstbalishedUsersOnlyMode(), ChatEstablishedUserMode: data.GetChatEstbalishedUsersOnlyMode(),
HideViewerCount: data.GetHideViewerCount(), HideViewerCount: data.GetHideViewerCount(),
DisableSearchIndexing: data.GetDisableSearchIndexing(),
VideoSettings: videoSettings{ VideoSettings: videoSettings{
VideoQualityVariants: videoQualityVariants, VideoQualityVariants: videoQualityVariants,
LatencyLevel: data.GetStreamLatencyLevel().Level, LatencyLevel: data.GetStreamLatencyLevel().Level,
@ -121,6 +122,7 @@ type serverConfigAdminResponse struct {
ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"` ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"`
StreamKeyOverridden bool `json:"streamKeyOverridden"` StreamKeyOverridden bool `json:"streamKeyOverridden"`
HideViewerCount bool `json:"hideViewerCount"` HideViewerCount bool `json:"hideViewerCount"`
DisableSearchIndexing bool `json:"disableSearchIndexing"`
} }
type videoSettings struct { type videoSettings struct {

28
controllers/robots.go Normal file
View file

@ -0,0 +1,28 @@
package controllers
import (
"net/http"
"strings"
"github.com/owncast/owncast/core/data"
)
// GetRobotsDotTxt returns the contents of our robots.txt.
func GetRobotsDotTxt(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
contents := []string{
"User-agent: *",
"Disallow: /admin",
"Disallow: /api",
}
if data.GetDisableSearchIndexing() {
contents = append(contents, "Disallow: /")
}
txt := []byte(strings.Join(contents, "\n"))
if _, err := w.Write(txt); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

View file

@ -69,6 +69,7 @@ const (
customOfflineMessageKey = "custom_offline_message" customOfflineMessageKey = "custom_offline_message"
customColorVariableValuesKey = "custom_color_variable_values" customColorVariableValuesKey = "custom_color_variable_values"
streamKeysKey = "stream_keys" streamKeysKey = "stream_keys"
disableSearchIndexingKey = "disable_search_indexing"
) )
// GetExtraPageBodyContent will return the user-supplied body content. // GetExtraPageBodyContent will return the user-supplied body content.
@ -959,3 +960,17 @@ func SetStreamKeys(actions []models.StreamKey) error {
configEntry := ConfigEntry{Key: streamKeysKey, Value: actions} configEntry := ConfigEntry{Key: streamKeysKey, Value: actions}
return _datastore.Save(configEntry) return _datastore.Save(configEntry)
} }
// SetDisableSearchIndexing will set if the web server should be indexable.
func SetDisableSearchIndexing(disableSearchIndexing bool) error {
return _datastore.SetBool(disableSearchIndexingKey, disableSearchIndexing)
}
// GetDisableSearchIndexing will return if the web server should be indexable.
func GetDisableSearchIndexing() bool {
disableSearchIndexing, err := _datastore.GetBool(disableSearchIndexingKey)
if err != nil {
return false
}
return disableSearchIndexing
}

View file

@ -50,6 +50,9 @@ func Start() error {
// return a logo that's compatible with external social networks // return a logo that's compatible with external social networks
http.HandleFunc("/logo/external", controllers.GetCompatibleLogo) http.HandleFunc("/logo/external", controllers.GetCompatibleLogo)
// robots.txt
http.HandleFunc("/robots.txt", controllers.GetRobotsDotTxt)
// status of the system // status of the system
http.HandleFunc("/api/status", controllers.GetStatus) http.HandleFunc("/api/status", controllers.GetStatus)
@ -327,6 +330,9 @@ func Start() error {
// Is the viewer count hidden from viewers // Is the viewer count hidden from viewers
http.HandleFunc("/api/admin/config/hideviewercount", middleware.RequireAdminAuth(admin.SetHideViewerCount)) http.HandleFunc("/api/admin/config/hideviewercount", middleware.RequireAdminAuth(admin.SetHideViewerCount))
// set disabling of search indexing
http.HandleFunc("/api/admin/config/disablesearchindexing", middleware.RequireAdminAuth(admin.SetDisableSearchIndexing))
// Inline chat moderation actions // Inline chat moderation actions
// Update chat message visibility // Update chat message visibility

View file

@ -37,6 +37,8 @@ const defaultFederationConfig = {
blockedDomains: [], blockedDomains: [],
}; };
const defaultHideViewerCount = false; const defaultHideViewerCount = false;
const defaultDisableSearchIndexing = false;
const defaultSocialHandles = [ const defaultSocialHandles = [
{ {
icon: '/img/platformlogos/github.svg', icon: '/img/platformlogos/github.svg',
@ -130,6 +132,7 @@ const newFederationConfig = {
}; };
const newHideViewerCount = !defaultHideViewerCount; const newHideViewerCount = !defaultHideViewerCount;
const newDisableSearchIndexing = !defaultDisableSearchIndexing;
const overriddenWebsocketHost = 'ws://lolcalhost.biz'; const overriddenWebsocketHost = 'ws://lolcalhost.biz';
const customCSS = randomString(); const customCSS = randomString();
@ -340,6 +343,14 @@ test('enable federation', async (done) => {
done(); done();
}); });
test('disable search indexing', async (done) => {
await sendAdminRequest(
'config/disablesearchindexing',
newDisableSearchIndexing
);
done();
});
test('change admin password', async (done) => { test('change admin password', async (done) => {
const res = await sendAdminRequest('config/adminpass', newAdminPassword); const res = await sendAdminRequest('config/adminpass', newAdminPassword);
done(); done();
@ -472,3 +483,18 @@ test('verify frontend status', (done) => {
done(); done();
}); });
}); });
test('verify robots.txt is correct after disabling search indexing', (done) => {
const expected = `User-agent: *
Disallow: /admin
Disallow: /api
Disallow: /`;
request
.get('/robots.txt')
.expect(200)
.then((res) => {
expect(res.text).toBe(expected);
done();
});
});

View file

@ -21,6 +21,7 @@ import {
FIELD_PROPS_NSFW, FIELD_PROPS_NSFW,
FIELD_PROPS_HIDE_VIEWER_COUNT, FIELD_PROPS_HIDE_VIEWER_COUNT,
API_SERVER_OFFLINE_MESSAGE, API_SERVER_OFFLINE_MESSAGE,
FIELD_PROPS_DISABLE_SEARCH_INDEXING,
} from '../../../../utils/config-constants'; } from '../../../../utils/config-constants';
import { UpdateArgs } from '../../../../types/config-section'; import { UpdateArgs } from '../../../../types/config-section';
import { ToggleSwitch } from '../../ToggleSwitch'; import { ToggleSwitch } from '../../ToggleSwitch';
@ -36,7 +37,7 @@ export default function EditInstanceDetails() {
const serverStatusData = useContext(ServerStatusContext); const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {}; const { serverConfig } = serverStatusData || {};
const { instanceDetails, yp, hideViewerCount } = serverConfig; const { instanceDetails, yp, hideViewerCount, disableSearchIndexing } = serverConfig;
const { instanceUrl } = yp; const { instanceUrl } = yp;
const [offlineMessageSaveStatus, setOfflineMessageSaveStatus] = useState(null); const [offlineMessageSaveStatus, setOfflineMessageSaveStatus] = useState(null);
@ -46,6 +47,7 @@ export default function EditInstanceDetails() {
...instanceDetails, ...instanceDetails,
...yp, ...yp,
hideViewerCount, hideViewerCount,
disableSearchIndexing,
}); });
}, [instanceDetails, yp]); }, [instanceDetails, yp]);
@ -87,6 +89,10 @@ export default function EditInstanceDetails() {
handleFieldChange({ fieldName: 'hideViewerCount', value: enabled }); handleFieldChange({ fieldName: 'hideViewerCount', value: enabled });
} }
function handleDisableSearchEngineIndexingChange(enabled: boolean) {
handleFieldChange({ fieldName: 'disableSearchIndexing', value: enabled });
}
const hasInstanceUrl = instanceUrl !== ''; const hasInstanceUrl = instanceUrl !== '';
return ( return (
@ -171,6 +177,14 @@ export default function EditInstanceDetails() {
onChange={handleHideViewerCountChange} onChange={handleHideViewerCountChange}
/> />
<ToggleSwitch
fieldName="disableSearchIndexing"
useSubmit
{...FIELD_PROPS_DISABLE_SEARCH_INDEXING}
checked={formDataValues.disableSearchIndexing}
onChange={handleDisableSearchEngineIndexingChange}
/>
<br /> <br />
<p className="description"> <p className="description">
Increase your audience by appearing in the{' '} Increase your audience by appearing in the{' '}

View file

@ -156,4 +156,5 @@ export interface ConfigDetails {
chatJoinMessagesEnabled: boolean; chatJoinMessagesEnabled: boolean;
chatEstablishedUserMode: boolean; chatEstablishedUserMode: boolean;
hideViewerCount: boolean; hideViewerCount: boolean;
disableSearchIndexing: boolean;
} }

View file

@ -38,7 +38,7 @@ const API_HIDE_VIEWER_COUNT = '/hideviewercount';
const API_CHAT_DISABLE = '/chat/disable'; const API_CHAT_DISABLE = '/chat/disable';
const API_CHAT_JOIN_MESSAGES_ENABLED = '/chat/joinmessagesenabled'; const API_CHAT_JOIN_MESSAGES_ENABLED = '/chat/joinmessagesenabled';
const API_CHAT_ESTABLISHED_MODE = '/chat/establishedusermode'; const API_CHAT_ESTABLISHED_MODE = '/chat/establishedusermode';
const API_DISABLE_SEARCH_INDEXING = '/disablesearchindexing';
const API_SOCKET_HOST_OVERRIDE = '/sockethostoverride'; const API_SOCKET_HOST_OVERRIDE = '/sockethostoverride';
// Federation // Federation
@ -212,6 +212,13 @@ export const FIELD_PROPS_HIDE_VIEWER_COUNT = {
tip: 'Turn this ON to hide the viewer count on the web page.', tip: 'Turn this ON to hide the viewer count on the web page.',
}; };
export const FIELD_PROPS_DISABLE_SEARCH_INDEXING = {
apiPath: API_DISABLE_SEARCH_INDEXING,
configPath: '',
label: 'Disable search engine indexing',
tip: 'Turn this ON to to tell search engines not to index this site.',
};
export const DEFAULT_VARIANT_STATE: VideoVariant = { export const DEFAULT_VARIANT_STATE: VideoVariant = {
framerate: 24, framerate: 24,
videoPassthrough: false, videoPassthrough: false,

View file

@ -71,6 +71,7 @@ const initialServerConfigState: ConfigDetails = {
chatJoinMessagesEnabled: true, chatJoinMessagesEnabled: true,
chatEstablishedUserMode: false, chatEstablishedUserMode: false,
hideViewerCount: false, hideViewerCount: false,
disableSearchIndexing: false,
}; };
const initialServerStatusState = { const initialServerStatusState = {