From fa5aed489f61565510153f3754ba238989958265 Mon Sep 17 00:00:00 2001 From: SamTV12345 <40429738+samtv12345@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:32:04 +0100 Subject: [PATCH] Fixed docker build. --- .dockerignore | 1 + .github/workflows/backend-tests.yml | 24 ++ .github/workflows/frontend-admin-tests.yml | 5 +- .gitignore | 3 +- Dockerfile | 11 +- admin/index.html | 4 +- admin/pnpm-workspace.yaml | 0 admin/src/App.tsx | 23 +- admin/src/index.css | 24 ++ admin/src/localization/i18n.ts | 6 +- admin/src/main.tsx | 4 +- admin/src/pages/HelpPage.tsx | 5 +- admin/src/pages/HomePage.tsx | 41 +++- admin/src/pages/LoginScreen.tsx | 17 +- admin/src/pages/PadPage.tsx | 52 +++- admin/src/pages/Plugin.ts | 2 +- admin/src/store/store.ts | 2 +- admin/vite.config.ts | 7 +- bin/buildForWindows.sh | 7 + pnpm-workspace.yaml | 1 - src/.eslintrc.cjs | 2 - src/ep.json | 2 - src/node/hooks/express/admin.ts | 22 +- src/node/hooks/express/adminplugins.ts | 28 --- src/node/hooks/express/adminsettings.ts | 10 - src/node/hooks/express/webaccess.ts | 20 +- src/static/js/admin/jquery.autosize.js | 180 -------------- src/static/js/admin/minify.json.js | 61 ----- src/static/js/admin/plugins.js | 273 --------------------- src/static/js/admin/settings.js | 69 ------ src/templates/admin/index.html | 28 --- src/templates/admin/plugins-info.html | 47 ---- src/templates/admin/plugins.html | 121 --------- src/templates/admin/settings.html | 58 ----- src/templates/javascript.html | 8 - src/tests/backend/specs/webaccess.ts | 42 ++-- 36 files changed, 243 insertions(+), 967 deletions(-) create mode 100644 admin/pnpm-workspace.yaml delete mode 100644 src/static/js/admin/jquery.autosize.js delete mode 100644 src/static/js/admin/minify.json.js delete mode 100644 src/static/js/admin/plugins.js delete mode 100644 src/static/js/admin/settings.js delete mode 100644 src/templates/admin/index.html delete mode 100644 src/templates/admin/plugins-info.html delete mode 100644 src/templates/admin/plugins.html delete mode 100644 src/templates/admin/settings.html diff --git a/.dockerignore b/.dockerignore index d8d3a3ebe..f7accabfd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -24,3 +24,4 @@ Dockerfile settings.json src/node_modules +admin/node_modules diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index f41f5ac5e..0dd1000d8 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -54,6 +54,12 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: bin/installDeps.sh + - name: Install admin ui + working-directory: admin + run: pnpm install + - name: Build admin ui + working-directory: admin + run: pnpm build - name: Run the backend tests run: pnpm test @@ -105,6 +111,12 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: bin/installDeps.sh + - name: Install admin ui + working-directory: admin + run: pnpm install + - name: Build admin ui + working-directory: admin + run: pnpm build - name: Install Etherpad plugins run: > @@ -163,6 +175,12 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: bin/installOnWindows.bat + - name: Install admin ui + working-directory: admin + run: pnpm install + - name: Build admin ui + working-directory: admin + run: pnpm build - name: Fix up the settings.json run: | @@ -207,6 +225,12 @@ jobs: ${{ runner.os }}-pnpm-store- - name: Only install direct dependencies run: pnpm config set auto-install-peers false + - name: Install admin ui + working-directory: admin + run: pnpm install + - name: Build admin ui + working-directory: admin + run: pnpm build - name: Install Etherpad plugins # The --legacy-peer-deps flag is required to work around a bug in npm diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml index aa1c4e70a..37c8ede08 100644 --- a/.github/workflows/frontend-admin-tests.yml +++ b/.github/workflows/frontend-admin-tests.yml @@ -12,7 +12,6 @@ jobs: name: with plugins runs-on: ubuntu-latest -# node: [16, 19, 20] >> Disabled node 16 and 18 because they do not work strategy: fail-fast: false matrix: @@ -83,11 +82,11 @@ jobs: run: "sed -i 's/\"enableAdminUITests\": false/\"enableAdminUITests\": true,\\n\"users\":{\"admin\":{\"password\":\"changeme\",\"is_admin\":true}}/' settings.json" - name: increase maxHttpBufferSize - run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 100000/' settings.json" + run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 10000000/' settings.json" - name: Disable import/export rate limiting run: | - sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 1000000/' -i settings.json + sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 100000000/' -i settings.json - name: Remove standard frontend test files, so only admin tests are run run: mv src/tests/frontend/specs/* /tmp && mv /tmp/admin*.js src/tests/frontend/specs diff --git a/.gitignore b/.gitignore index 38e2889d9..2a8335497 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ out/ /src/bin/etherpad-1.deb /src/bin/node.exe plugin_packages -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml +/src/templates/admin diff --git a/Dockerfile b/Dockerfile index 7cd8105d9..35e4665b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,13 @@ # # Author: muxator +FROM node:alpine as adminBuild + +WORKDIR /opt/etherpad-lite +COPY ./admin ./admin +RUN cd ./admin && npm install -g pnpm && pnpm install && pnpm run build --outDir ./dist + + FROM node:alpine as build LABEL maintainer="Etherpad team, https://github.com/ether/etherpad-lite" @@ -99,16 +106,18 @@ COPY --chown=etherpad:etherpad ./pnpm-workspace.yaml ./package.json ./ FROM build as development COPY --chown=etherpad:etherpad ./src/package.json .npmrc ./src/pnpm-lock.yaml ./src/ +COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/admin/dist ./src/templates/admin RUN bin/installDeps.sh && { [ -z "${ETHERPAD_PLUGINS}" ] || \ pnpm install --workspace-root ${ETHERPAD_PLUGINS}; } - + FROM build as production ENV NODE_ENV=production ENV ETHERPAD_PRODUCTION=true COPY --chown=etherpad:etherpad ./src ./src +COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/admin/dist ./src/templates/admin RUN bin/installDeps.sh && { [ -z "${ETHERPAD_PLUGINS}" ] || \ pnpm install --workspace-root ${ETHERPAD_PLUGINS}; } && \ diff --git a/admin/index.html b/admin/index.html index daff73e73..8863894ed 100644 --- a/admin/index.html +++ b/admin/index.html @@ -2,9 +2,9 @@ - - Vite + React + TS + Etherpad Admin Dashboard +
diff --git a/admin/pnpm-workspace.yaml b/admin/pnpm-workspace.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/App.tsx b/admin/src/App.tsx index de5bab2e8..3db45f0e8 100644 --- a/admin/src/App.tsx +++ b/admin/src/App.tsx @@ -2,26 +2,38 @@ import {useEffect} from 'react' import './App.css' import {connect} from 'socket.io-client' import {isJSONClean} from './utils/utils.ts' -import {NavLink, Outlet} from "react-router-dom"; +import {NavLink, Outlet, useNavigate} from "react-router-dom"; import {useStore} from "./store/store.ts"; import {LoadingScreen} from "./utils/LoadingScreen.tsx"; -import {ToastDialog} from "./utils/Toast.tsx"; import {Trans, useTranslation} from "react-i18next"; - +const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : '' export const App = ()=> { const setSettings = useStore(state => state.setSettings); const {t} = useTranslation() + const navigate = useNavigate() + + useEffect(() => { + fetch('/admin-auth/', { + method: 'POST' + }).then((value)=>{ + if(!value.ok){ + navigate('/login') + } + }).catch(()=>{ + navigate('/login') + }) + }, []); useEffect(() => { document.title = t('admin.page-title') useStore.getState().setShowLoading(true); - const settingSocket = connect('http://localhost:9001/settings', { + const settingSocket = connect(`${WS_URL}/settings`, { transports: ['websocket'], }); - const pluginsSocket = connect('http://localhost:9001/pluginfw/installer', { + const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, { transports: ['websocket'], }) @@ -74,7 +86,6 @@ export const App = ()=> { return
-

Etherpad

    diff --git a/admin/src/index.css b/admin/src/index.css index 7bfd40c23..e9683befa 100644 --- a/admin/src/index.css +++ b/admin/src/index.css @@ -339,6 +339,25 @@ pre { } +.dialog-confirm-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 100; +} + + +.dialog-confirm-content { + position: fixed; + top: 50%; + left: 50%; + background-color: white; + transform: translate(-50%, -50%); + padding: 20px; + z-index: 101; +} + + .dialog-content { position: fixed; top: 50%; @@ -463,3 +482,8 @@ pre { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px } + +.search-field { + width: 50%; + padding: 5px; +} diff --git a/admin/src/localization/i18n.ts b/admin/src/localization/i18n.ts index 032dafaa9..67ae140e7 100644 --- a/admin/src/localization/i18n.ts +++ b/admin/src/localization/i18n.ts @@ -28,17 +28,17 @@ const LazyImportPlugin: BackendModule = { try { json = JSON.parse(await localeJSON.text()) } catch(e) { - callback(true, null); + callback(new Error("Error loading"), null); } callback(null, json); }, - save: function (language, namespace, data) { + save: function () { }, - create: function (languages, namespace, key, fallbackValue) { + create: function () { /* save the missing translation */ }, }; diff --git a/admin/src/main.tsx b/admin/src/main.tsx index 2b3ae329c..03ec73104 100644 --- a/admin/src/main.tsx +++ b/admin/src/main.tsx @@ -11,6 +11,7 @@ import * as Toast from '@radix-ui/react-toast' import {I18nextProvider} from "react-i18next"; import i18n from "./localization/i18n.ts"; import {PadPage} from "./pages/PadPage.tsx"; +import {ToastDialog} from "./utils/Toast.tsx"; const router = createBrowserRouter(createRoutesFromElements( <>}> @@ -31,7 +32,8 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - + + , diff --git a/admin/src/pages/HelpPage.tsx b/admin/src/pages/HelpPage.tsx index 3ffd8d16e..6f06907e1 100644 --- a/admin/src/pages/HelpPage.tsx +++ b/admin/src/pages/HelpPage.tsx @@ -10,7 +10,6 @@ export const HelpPage = () => { useEffect(() => { if(!settingsSocket) return; settingsSocket?.on('reply:help', (data) => { - console.log(data) setHelpData(data) }); @@ -19,11 +18,11 @@ export const HelpPage = () => { const renderHooks = (hooks:Record>) => { return Object.keys(hooks).map((hookName, i) => { - return
    + return

    {hookName}

      {Object.keys(hooks[hookName]).map((hook, i) =>
    • {hook} -
        +
          {Object.keys(hooks[hookName][hook]).map((subHook, i) =>
        • {subHook}
        • )}
        )} diff --git a/admin/src/pages/HomePage.tsx b/admin/src/pages/HomePage.tsx index 7b5259b09..423434f4a 100644 --- a/admin/src/pages/HomePage.tsx +++ b/admin/src/pages/HomePage.tsx @@ -2,7 +2,7 @@ import {useStore} from "../store/store.ts"; import {useEffect, useState} from "react"; import {InstalledPlugin, PluginDef, SearchParams} from "./Plugin.ts"; import {useDebounce} from "../utils/useDebounce.ts"; -import {Trans} from "react-i18next"; +import {Trans, useTranslation} from "react-i18next"; export const HomePage = () => { @@ -17,6 +17,7 @@ export const HomePage = () => { searchTerm: '' }) const [searchTerm, setSearchTerm] = useState('') + const {t} = useTranslation() useEffect(() => { @@ -30,8 +31,18 @@ export const HomePage = () => { setInstalledPlugins(data.installed) }) - pluginsSocket.on('results:updatable', () => { - console.log("Finished install") + pluginsSocket.on('results:updatable', (data) => { + data.updatable.forEach((pluginName: string) => { + setInstalledPlugins(installedPlugins.map(plugin => { + if (plugin.name === pluginName) { + return { + ...plugin, + updatable: true + } + } + return plugin + })) + }) }) pluginsSocket.on('finished:install', () => { @@ -118,19 +129,25 @@ export const HomePage = () => { return {plugin.name} {plugin.version} - { - }}> - + + { + plugin.updatable ? + + : + + } - - })} - - + + })} + + -

        +

        - { + { setSearchTerm(v.target.value) }}/> diff --git a/admin/src/pages/LoginScreen.tsx b/admin/src/pages/LoginScreen.tsx index 2c7dc62b3..7860368f8 100644 --- a/admin/src/pages/LoginScreen.tsx +++ b/admin/src/pages/LoginScreen.tsx @@ -1,17 +1,28 @@ import {useState} from "react"; +import {useStore} from "../store/store.ts"; +import {useNavigate} from "react-router-dom"; export const LoginScreen = ()=>{ + const navigate = useNavigate() const [username, setUsername] = useState('') const [password, setPassword] = useState('') const login = ()=>{ - fetch('/api/auth', { - method: 'GET', + fetch('/admin-auth/', { + method: 'POST', headers:{ Authorization: `Basic ${btoa(`${username}:${password}`)}` } }).then(r=>{ - console.log(r.status) + if(!r.ok) { + useStore.getState().setToastState({ + open: true, + title: "Login failed", + success: false + }) + } else { + navigate('/') + } }).catch(e=>{ console.error(e) }) diff --git a/admin/src/pages/PadPage.tsx b/admin/src/pages/PadPage.tsx index f5c0384c8..5c11755d6 100644 --- a/admin/src/pages/PadPage.tsx +++ b/admin/src/pages/PadPage.tsx @@ -1,9 +1,10 @@ -import {Trans} from "react-i18next"; +import {Trans, useTranslation} from "react-i18next"; import {useEffect, useMemo, useState} from "react"; import {useStore} from "../store/store.ts"; import {PadSearchQuery, PadSearchResult} from "../utils/PadSearch.ts"; import {useDebounce} from "../utils/useDebounce.ts"; import {determineSorting} from "../utils/sorting.ts"; +import * as Dialog from "@radix-ui/react-dialog"; export const PadPage = ()=>{ const settingsSocket = useStore(state=>state.settingsSocket) @@ -14,7 +15,7 @@ export const PadPage = ()=>{ sortBy: 'padName', ascending: true }) - + const {t} = useTranslation() const [searchTerm, setSearchTerm] = useState('') const pads = useStore(state=>state.pads) const pages = useMemo(()=>{ @@ -24,7 +25,9 @@ export const PadPage = ()=>{ const totalPages = Math.ceil(pads!.total / searchParams.limit) return Array.from({length: totalPages}, (_, i) => i+1) - },[pads]) + },[pads, searchParams.limit]) + const [deleteDialog, setDeleteDialog] = useState(false) + const [padToDelete, setPadToDelete] = useState('') useDebounce(()=>{ setSearchParams({ @@ -64,11 +67,39 @@ export const PadPage = ()=>{ }) }, [settingsSocket, pads]); + const deletePad = (padID: string)=>{ + settingsSocket?.emit('deletePad', padID) + } + return
        + + + +
        +
        +
        + {t("ep_admin_pads:ep_adminpads2_confirm", { + padID: padToDelete, + })} +
        +
        + + +
        +
        +
        +
        +

        - setSearchTerm(v.target.value)} placeholder="Pads suchen"/> + setSearchTerm(v.target.value)} + placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/> @@ -78,21 +109,21 @@ export const PadPage = ()=>{ sortBy: 'padName', ascending: !searchParams.ascending }) - }}>PadId + }}> + }}> + }}> - + @@ -114,8 +145,9 @@ export const PadPage = ()=>{ - - - - - - - -
        { setSearchParams({ ...searchParams, sortBy: 'lastEdited', ascending: !searchParams.ascending }) - }}>Users{ setSearchParams({ ...searchParams, sortBy: 'userCount', ascending: !searchParams.ascending }) - }}>Last Edited{ setSearchParams({ ...searchParams, @@ -100,7 +131,7 @@ export const PadPage = ()=>{ ascending: !searchParams.ascending }) }}>Revision numberActions
        + setPadToDelete(pad.padName) + setDeleteDialog(true) + }}> diff --git a/admin/src/pages/Plugin.ts b/admin/src/pages/Plugin.ts index 398494397..3188c247f 100644 --- a/admin/src/pages/Plugin.ts +++ b/admin/src/pages/Plugin.ts @@ -12,7 +12,7 @@ export type InstalledPlugin = { path: string, realPath: string, version:string, - updatable: boolean + updatable?: boolean } diff --git a/admin/src/store/store.ts b/admin/src/store/store.ts index 6b2a9cbc3..d662dfdf3 100644 --- a/admin/src/store/store.ts +++ b/admin/src/store/store.ts @@ -1,6 +1,6 @@ import {create} from "zustand"; import {Socket} from "socket.io-client"; -import {PadSearchResult, PadType} from "../utils/PadSearch.ts"; +import {PadSearchResult} from "../utils/PadSearch.ts"; type ToastState = { description?:string, diff --git a/admin/vite.config.ts b/admin/vite.config.ts index 38e9814c9..ff329032f 100644 --- a/admin/vite.config.ts +++ b/admin/vite.config.ts @@ -14,14 +14,17 @@ export default defineConfig({ ] })], base: '/admin', + build:{ + outDir: '../src/templates/admin' + }, server:{ proxy: { - '/socket.io/': { + '/socket.io/*': { target: 'http://localhost:9001', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') }, - '/api/auth': { + '/admin-auth/': { target: 'http://localhost:9001', changeOrigin: true, rewrite: (path) => path.replace(/^\/admin-prox/, '/admin/') diff --git a/bin/buildForWindows.sh b/bin/buildForWindows.sh index e49a3da69..570cd53e8 100755 --- a/bin/buildForWindows.sh +++ b/bin/buildForWindows.sh @@ -49,6 +49,13 @@ rm -rf src/node_modules || true #log "do a normal unix install first..." #$(try cd ./bin/installDeps.sh) +# Install admin frontend +cd admin +try pnpm install +try pnpm run build +cd .. + + log "copy the windows settings template..." try cp settings.json.template settings.json diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3d3e285e0..4d8f70bb3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,2 @@ packages: - src - - admin diff --git a/src/.eslintrc.cjs b/src/.eslintrc.cjs index 95c9efa07..03d432ede 100644 --- a/src/.eslintrc.cjs +++ b/src/.eslintrc.cjs @@ -5,8 +5,6 @@ require('eslint-config-etherpad/patch/modern-module-resolution'); module.exports = { ignorePatterns: [ - '/static/js/admin/jquery.autosize.js', - '/static/js/admin/minify.json.js', '/static/js/vendors/browser.js', '/static/js/vendors/farbtastic.js', '/static/js/vendors/gritter.js', diff --git a/src/ep.json b/src/ep.json index ec09696c5..f6d41e203 100644 --- a/src/ep.json +++ b/src/ep.json @@ -92,14 +92,12 @@ { "name": "adminplugins", "hooks": { - "expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins", "socketio": "ep_etherpad-lite/node/hooks/express/adminplugins" } }, { "name": "adminsettings", "hooks": { - "expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminsettings", "socketio": "ep_etherpad-lite/node/hooks/express/adminsettings" } }, diff --git a/src/node/hooks/express/admin.ts b/src/node/hooks/express/admin.ts index 90e491396..3c85d072a 100644 --- a/src/node/hooks/express/admin.ts +++ b/src/node/hooks/express/admin.ts @@ -1,7 +1,9 @@ 'use strict'; import {ArgsExpressType} from "../../types/ArgsExpressType"; +import path from "path"; +const settings = require('ep_etherpad-lite/node/utils/Settings'); -const eejs = require('../../eejs'); +const ADMIN_PATH = path.join(settings.root, 'src', 'templates', 'admin'); /** * Add the admin navigation link @@ -11,9 +13,19 @@ const eejs = require('../../eejs'); * @return {*} */ exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function): any => { - args.app.get('/admin', (req:any, res:any) => { - if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/'); - res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req})); - }); + args.app.get('/admin/*', (req:any, res:any, next:Function) => { + if (req.path.includes('.')) { + const relativPath = req.path.split('/admin/')[1]; + res.sendFile(path.join(ADMIN_PATH, relativPath)); + } else { + res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); + res.header('Expires', '-1'); + res.header('Pragma', 'no-cache'); + res.sendFile(path.join(ADMIN_PATH, 'index.html')); + } + }); + args.app.get('/admin', (req:any, res:any, next:Function) => { + if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/'); + }) return cb(); }; diff --git a/src/node/hooks/express/adminplugins.ts b/src/node/hooks/express/adminplugins.ts index ad1795e17..dc34cd437 100644 --- a/src/node/hooks/express/adminplugins.ts +++ b/src/node/hooks/express/adminplugins.ts @@ -12,35 +12,7 @@ const installer = require('../../../static/js/pluginfw/installer'); const pluginDefs = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugins'); const semver = require('semver'); -const UpdateCheck = require('../../utils/UpdateCheck'); -exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function) => { - args.app.get('/admin/plugins', (req:any, res:any) => { - res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', { - plugins: pluginDefs.plugins, - req, - errors: [], - })); - }); - - args.app.get('/admin/plugins/info', (req:any, res:any) => { - const gitCommit = settings.getGitCommit(); - const epVersion = settings.getEpVersion(); - - res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', { - gitCommit, - epVersion, - installedPlugins: `
        ${plugins.formatPlugins().replace(/, /g, '\n')}
        `, - installedParts: `
        ${plugins.formatParts()}
        `, - installedServerHooks: `
        ${plugins.formatHooks('hooks', true)}
        `, - installedClientHooks: `
        ${plugins.formatHooks('client_hooks', true)}
        `, - latestVersion: UpdateCheck.getLatestVersion(), - req, - })); - }); - - return cb(); -}; exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => { const io = args.io.of('/pluginfw/installer'); diff --git a/src/node/hooks/express/adminsettings.ts b/src/node/hooks/express/adminsettings.ts index a7d3b8585..03258c584 100644 --- a/src/node/hooks/express/adminsettings.ts +++ b/src/node/hooks/express/adminsettings.ts @@ -13,16 +13,6 @@ const UpdateCheck = require('../../utils/UpdateCheck'); const padManager = require('../../db/PadManager'); const api = require('../../db/API'); -exports.expressCreateServer = (hookName:string, {app}:any) => { - app.get('/admin/settings', (req:any, res:any) => { - res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', { - req, - settings: '', - errors: [], - })); - }); -}; - const queryPadLimit = 12; diff --git a/src/node/hooks/express/webaccess.ts b/src/node/hooks/express/webaccess.ts index 43bdea5fc..0034f87c8 100644 --- a/src/node/hooks/express/webaccess.ts +++ b/src/node/hooks/express/webaccess.ts @@ -50,7 +50,7 @@ exports.userCanModify = (padId: string, req: SocketClientRequest) => { exports.authnFailureDelayMs = 1000; const checkAccess = async (req:any, res:any, next: Function) => { - const requireAdmin = req.path.toLowerCase().startsWith('/admin'); + const requireAdmin = req.path.toLowerCase().startsWith('/admin-auth'); // /////////////////////////////////////////////////////////////////////////////////////////////// // Step 1: Check the preAuthorize hook for early permit/deny (permit is only allowed for non-admin @@ -126,7 +126,13 @@ const checkAccess = async (req:any, res:any, next: Function) => { // completed, or maybe different credentials are required), go to the next step. // /////////////////////////////////////////////////////////////////////////////////////////////// - if (await authorize()) return next(); + if (await authorize()) { + if(requireAdmin) { + res.status(200).send('Authorized') + return + } + return next(); + } // /////////////////////////////////////////////////////////////////////////////////////////////// // Step 3: Authenticate the user. (Or, if already logged in, reauthenticate with different @@ -163,7 +169,7 @@ const checkAccess = async (req:any, res:any, next: Function) => { if (await aCallFirst0('authnFailure', {req, res})) return; if (await aCallFirst0('authFailure', {req, res, next})) return; // No plugin handled the authentication failure. Fall back to basic authentication. - res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); + //res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); // Delay the error response for 1s to slow down brute force attacks. await new Promise((resolve) => setTimeout(resolve, exports.authnFailureDelayMs)); res.status(401).send('Authentication Required'); @@ -188,7 +194,13 @@ const checkAccess = async (req:any, res:any, next: Function) => { // a login page). // /////////////////////////////////////////////////////////////////////////////////////////////// - if (await authorize()) return next(); + const auth = await authorize() + if (auth && !requireAdmin) return next(); + if(auth && requireAdmin) { + res.status(200).send('Authorized') + return + } + if (await aCallFirst0('authzFailure', {req, res})) return; if (await aCallFirst0('authFailure', {req, res, next})) return; // No plugin handled the authorization failure. diff --git a/src/static/js/admin/jquery.autosize.js b/src/static/js/admin/jquery.autosize.js deleted file mode 100644 index a94ef3cde..000000000 --- a/src/static/js/admin/jquery.autosize.js +++ /dev/null @@ -1,180 +0,0 @@ -// Autosize 1.13 - jQuery plugin for textareas -// (c) 2012 Jack Moore - jacklmoore.com -// license: www.opensource.org/licenses/mit-license.php - -(function ($) { - var - defaults = { - className: 'autosizejs', - append: "", - callback: false - }, - hidden = 'hidden', - borderBox = 'border-box', - lineHeight = 'lineHeight', - copy = ' - - -
        -
        - Example production settings template - Example development settings template -
        - -
        -

        -
        - - - - - diff --git a/src/templates/javascript.html b/src/templates/javascript.html index 42482f69d..c501af65c 100644 --- a/src/templates/javascript.html +++ b/src/templates/javascript.html @@ -34,24 +34,16 @@
        require-kernel.js
        plugins.js Apache-2.0-onlyplugins.js
        minify.json.js Expatminify.json.js
        settings.js Apache-2.0-onlysettings.js
        jquery.autosize.js Expatjquery.autosize.js
        diff --git a/src/tests/backend/specs/webaccess.ts b/src/tests/backend/specs/webaccess.ts index 009737c46..96c2265fc 100644 --- a/src/tests/backend/specs/webaccess.ts +++ b/src/tests/backend/specs/webaccess.ts @@ -54,10 +54,10 @@ describe(__filename, function () { await agent.get('/').expect(200); }); - it('!authn !authz anonymous /admin/ -> 401', async function () { + it('!authn !authz anonymous /admin-auth// -> 401', async function () { settings.requireAuthentication = false; settings.requireAuthorization = false; - await agent.get('/admin/').expect(401); + await agent.get('/admin-auth/').expect(401); }); it('authn !authz anonymous / -> 401', async function () { @@ -72,10 +72,10 @@ describe(__filename, function () { await agent.get('/').auth('user', 'user-password').expect(200); }); - it('authn !authz user /admin/ -> 403', async function () { + it('authn !authz user //admin-auth// -> 403', async function () { settings.requireAuthentication = true; settings.requireAuthorization = false; - await agent.get('/admin/').auth('user', 'user-password').expect(403); + await agent.get('/admin-auth//').auth('user', 'user-password').expect(403); }); it('authn !authz admin / -> 200', async function () { @@ -84,10 +84,10 @@ describe(__filename, function () { await agent.get('/').auth('admin', 'admin-password').expect(200); }); - it('authn !authz admin /admin/ -> 200', async function () { + it('authn !authz admin /admin-auth/ -> 200', async function () { settings.requireAuthentication = true; settings.requireAuthorization = false; - await agent.get('/admin/').auth('admin', 'admin-password').expect(200); + await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200); }); it('authn authz anonymous /robots.txt -> 200', async function () { @@ -102,10 +102,10 @@ describe(__filename, function () { await agent.get('/').auth('user', 'user-password').expect(403); }); - it('authn authz user /admin/ -> 403', async function () { + it('authn authz user //admin-auth// -> 403', async function () { settings.requireAuthentication = true; settings.requireAuthorization = true; - await agent.get('/admin/').auth('user', 'user-password').expect(403); + await agent.get('/admin-auth//').auth('user', 'user-password').expect(403); }); it('authn authz admin / -> 200', async function () { @@ -114,10 +114,10 @@ describe(__filename, function () { await agent.get('/').auth('admin', 'admin-password').expect(200); }); - it('authn authz admin /admin/ -> 200', async function () { + it('authn authz admin /admin-auth/ -> 200', async function () { settings.requireAuthentication = true; settings.requireAuthorization = true; - await agent.get('/admin/').auth('admin', 'admin-password').expect(200); + await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200); }); describe('login fails if password is nullish', function () { @@ -130,7 +130,7 @@ describe(__filename, function () { it(`admin password: ${adminPassword} credentials: ${creds}`, async function () { settings.users.admin.password = adminPassword; const encCreds = Buffer.from(creds).toString('base64'); - await agent.get('/admin/').set('Authorization', `Basic ${encCreds}`).expect(401); + await agent.get('/admin-auth/').set('Authorization', `Basic ${encCreds}`).expect(401); }); } } @@ -228,11 +228,11 @@ describe(__filename, function () { it('cannot grant access to /admin', async function () { handlers.preAuthorize[0].innerHandle = () => [true]; - await agent.get('/admin/').expect(401); + await agent.get('/admin-auth/').expect(401); // Notes: // * preAuthorize[1] is called despite preAuthorize[0] returning a non-empty list because - // 'true' entries are ignored for /admin/* requests. - // * The authenticate hook always runs for /admin/* requests even if + // 'true' entries are ignored for /admin-auth//* requests. + // * The authenticate hook always runs for /admin-auth//* requests even if // settings.requireAuthentication is false. assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', @@ -240,9 +240,9 @@ describe(__filename, function () { 'authenticate_1']); }); - it('can deny access to /admin', async function () { + it('can deny access to /admin-auth/', async function () { handlers.preAuthorize[0].innerHandle = () => [false]; - await agent.get('/admin/').auth('admin', 'admin-password').expect(403); + await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(403); assert.deepEqual(callOrder, ['preAuthorize_0']); }); @@ -258,7 +258,7 @@ describe(__filename, function () { res.status(200).send('injected'); return cb([true]); })]; - await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected'); + await agent.get('/admin-auth//').auth('admin', 'admin-password').expect(200, 'injected'); assert(called); }); @@ -274,15 +274,15 @@ describe(__filename, function () { settings.requireAuthorization = false; }); - it('is not called if !requireAuthentication and not /admin/*', async function () { + it('is not called if !requireAuthentication and not /admin-auth/*', async function () { settings.requireAuthentication = false; await agent.get('/').expect(200); assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']); }); - it('is called if !requireAuthentication and /admin/*', async function () { + it('is called if !requireAuthentication and /admin-auth//*', async function () { settings.requireAuthentication = false; - await agent.get('/admin/').expect(401); + await agent.get('/admin-auth/').expect(401); assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0', @@ -393,7 +393,7 @@ describe(__filename, function () { it('is not called if !requireAuthorization (/admin)', async function () { settings.requireAuthorization = false; - await agent.get('/admin/').auth('admin', 'admin-password').expect(200); + await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200); assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0',