From 069553f0eb147da349865db9cdfd2225a27e82bd Mon Sep 17 00:00:00 2001 From: Bobban Rydh Date: Fri, 4 Sep 2026 23:54:37 +0200 Subject: [PATCH] updated versions and added privacy page --- ENVIRONMENT.md | 29 +++++ README.md | 10 +- backend/.env.example | 5 + frontend/package-lock.json | 41 +++++-- frontend/package.json | 2 +- frontend/src/App.css | 103 ++++++++++++++++ frontend/src/App.js | 35 +++++- frontend/src/components/LoginPage.js | 6 +- frontend/src/components/PrivacyPage.js | 157 +++++++++++++++++++++++++ 9 files changed, 368 insertions(+), 20 deletions(-) create mode 100644 frontend/src/components/PrivacyPage.js diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md index 93cc924..c26e696 100644 --- a/ENVIRONMENT.md +++ b/ENVIRONMENT.md @@ -62,6 +62,30 @@ The cPanel account must own the domains you want to manage. Uses the cPanel UAPI --- +## Authentik SSO + +Single sign-on via Authentik (optional). When configured, a **Sign in with Authentik** button appears on the login page alongside the regular username/password form. Users who sign in via SSO for the first time are automatically created as local accounts. + +| Variable | Required | Description | +|----------|----------|-------------| +| `AUTHENTIK_URL` | Yes | Issuer URL of your Authentik application, e.g. `https://auth.example.com/application/o/sloth-manager` | +| `AUTHENTIK_CLIENT_ID` | Yes | Client ID from the Authentik OAuth2/OIDC Provider | +| `AUTHENTIK_CLIENT_SECRET` | Yes | Client secret from the Authentik OAuth2/OIDC Provider | + +**Setup steps in Authentik:** + +1. Go to **Applications → Providers → Create** and choose **OAuth2/OpenID Provider**. +2. Set **Client type** to `Confidential`. +3. Under **Redirect URIs**, add your Sloth Manager URL with a trailing slash, e.g. `https://slothmgmt.example.com/`. + The trailing slash is required — Authentik does exact-match on redirect URIs. +4. Copy the **Client ID** and **Client Secret** into your `.env`. +5. Go to **Applications → Applications → Create**, select the provider, and note the **Slug**. +6. Set `AUTHENTIK_URL` to `https://your-authentik-host/application/o/`. + +SSO is disabled (and the button is hidden) when any of the three `AUTHENTIK_*` variables are missing. + +--- + ## Authentication | Variable | Required | Description | @@ -110,6 +134,11 @@ CPANEL_USERNAME=myuser CPANEL_API_TOKEN=your_token CPANEL_INSECURE=false +# Authentik SSO (optional) +AUTHENTIK_URL=https://auth.example.com/application/o/sloth-manager +AUTHENTIK_CLIENT_ID=your_client_id +AUTHENTIK_CLIENT_SECRET=your_client_secret + # Auth JWT_SECRET=your-long-random-secret-here JWT_EXPIRES_IN=24h diff --git a/README.md b/README.md index 14728e0..0dc6f5c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Sloth Manager -A self-hosted web app to manage DNS records across Cloudflare, Loopia, Pi-hole, Azure DNS, and cPanel from a single interface. +A self-hosted web app to manage DNS records across Cloudflare, Loopia, Pi-hole, Azure DNS, cPanel, and Technitium from a single interface. Supports local accounts and Authentik SSO. ## Requirements @@ -92,9 +92,9 @@ All files are created automatically on first use. | File | Contents | |------|---------| -| `ENVIRONMENT.md` | Every `.env` variable explained | +| `ENVIRONMENT.md` | Every `.env` variable explained, including Authentik SSO setup | | `API-ACCESS.md` | API credentials and permissions for each provider | -| `NOTIFICATIONS.md` | Gotify notification setup and troubleshooting | +| `NOTIFICATIONS.md` | Notification setup (Gotify, ntfy, SMTP, webhook) | --- @@ -133,13 +133,15 @@ dns-manager/ │ │ │ ├── records.js │ │ │ ├── settings.js │ │ │ ├── auth.js +│ │ │ ├── oidc.js # Authentik SSO (OIDC) │ │ │ └── users.js │ │ └── adapters/ │ │ ├── cloudflare.js │ │ ├── loopia.js │ │ ├── pihole.js │ │ ├── azure.js -│ │ └── cpanel.js +│ │ ├── cpanel.js +│ │ └── technitium.js │ ├── dns-cache.json # created on first sync │ ├── settings.json # created on first save │ ├── users.json # created on first start diff --git a/backend/.env.example b/backend/.env.example index d78e317..7c793a4 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -25,6 +25,11 @@ CPANEL_INSECURE=false # set to true if cPanel uses a self-signed certificate # Example: DISABLED_PROVIDERS=loopia,cpanel DISABLED_PROVIDERS= +# Authentik SSO (optional — leave blank to disable the SSO button) +# AUTHENTIK_URL=https://auth.example.com/application/o/sloth-manager +# AUTHENTIK_CLIENT_ID=your_client_id +# AUTHENTIK_CLIENT_SECRET=your_client_secret + # Auth — generate a strong random secret, e.g: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" JWT_SECRET=change-this-to-a-long-random-string JWT_EXPIRES_IN=24h diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dc2fc51..fd21303 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,7 +11,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-scripts": "5.0.1", - "recharts": "^3.8.1" + "recharts": "^3.10.1" } }, "node_modules/@alloc/quick-lru": { @@ -14020,9 +14020,9 @@ } }, "node_modules/recharts": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", - "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", "license": "MIT", "workspaces": [ "www" @@ -14033,9 +14033,9 @@ "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", - "immer": "^10.1.1", + "immer": "^11.1.8", "react-redux": "8.x.x || 9.x.x", - "reselect": "5.1.1", + "reselect": "5.2.0", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" @@ -14056,9 +14056,9 @@ "license": "MIT" }, "node_modules/recharts/node_modules/immer": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", - "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.18.tgz", + "integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -14246,9 +14246,9 @@ "license": "MIT" }, "node_modules/reselect": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", "license": "MIT" }, "node_modules/resolve": { @@ -15789,6 +15789,23 @@ } } }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0d4c356..6bd9576 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-scripts": "5.0.1", - "recharts": "^3.8.1" + "recharts": "^3.10.1" }, "scripts": { "start": "react-scripts start", diff --git a/frontend/src/App.css b/frontend/src/App.css index 458560d..d2350a4 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -112,6 +112,109 @@ body { .sidebar-footer a:hover { text-decoration: underline; } +.sidebar-footer-link { + background: none; + border: none; + padding: 0; + font-size: inherit; + color: var(--accent); + cursor: pointer; + text-decoration: none; +} +.sidebar-footer-link:hover { text-decoration: underline; } + +/* ===== Privacy page ===== */ +.privacy-page { + max-width: 760px; + padding: 32px; +} +.privacy-page h2 { + font-size: 22px; + margin-bottom: 4px; +} +.privacy-updated { + font-size: 12px; + color: var(--text-muted); + margin-bottom: 28px; +} +.privacy-page section { + margin-bottom: 28px; +} +.privacy-page h3 { + font-size: 15px; + font-weight: 600; + margin-bottom: 10px; + padding-bottom: 4px; + border-bottom: 1px solid var(--border); +} +.privacy-page h4 { + font-size: 13px; + font-weight: 600; + margin: 14px 0 6px; +} +.privacy-page p, .privacy-page li { + font-size: 13px; + line-height: 1.65; + color: var(--text); +} +.privacy-page ul { + padding-left: 20px; + margin: 6px 0; +} +.privacy-page li { margin-bottom: 4px; } + +.privacy-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; + margin-bottom: 8px; +} +.privacy-table th, .privacy-table td { + text-align: left; + padding: 7px 10px; + border: 1px solid var(--border); + vertical-align: top; +} +.privacy-table th { + background: var(--hover); + font-weight: 600; +} + +/* ===== Privacy modal (pre-login) ===== */ +.privacy-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.55); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} +.privacy-modal { + position: relative; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + width: min(760px, 95vw); + max-height: 85vh; + overflow-y: auto; + padding: 8px 0; +} +.privacy-modal-close { + position: sticky; + top: 12px; + float: right; + margin: 12px 16px 0 0; + background: none; + border: none; + font-size: 16px; + cursor: pointer; + color: var(--text-muted); + line-height: 1; + z-index: 1; +} +.privacy-modal-close:hover { color: var(--text); } + /* ===== Secrets ===== */ .secrets-alert-banner { display: flex; diff --git a/frontend/src/App.js b/frontend/src/App.js index cbf1144..ff04ce2 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -12,6 +12,7 @@ import SecretsPage from './components/SecretsPage'; import IpamPage from './components/IpamPage'; import DiagnosticsPage from './components/DiagnosticsPage'; import DomainsPage from './components/DomainsPage'; +import PrivacyPage from './components/PrivacyPage'; import { useProviderColors, providerBadgeStyle } from './context/ProviderColors'; import { useTheme } from './context/Theme'; import ConfirmDialog from './components/ConfirmDialog'; @@ -246,6 +247,13 @@ function AppShell({ currentUser, onLogout }) {
By bobbantech + {' · '} +
@@ -273,6 +281,7 @@ function AppShell({ currentUser, onLogout }) { {!selectedProvider && view === 'audit' && } {!selectedProvider && view === 'diag' && } {!selectedProvider && view === 'dashboard' && } + {!selectedProvider && view === 'privacy' && } {selectedProvider && !selectedZone && ( { getMe() @@ -362,6 +372,12 @@ export default function App() { return () => window.removeEventListener('auth:logout', handler); }, []); + useEffect(() => { + const handler = () => setShowPrivacyModal(true); + window.addEventListener('show-privacy', handler); + return () => window.removeEventListener('show-privacy', handler); + }, []); + function handleLogin(user) { setCurrentUser(user); } function handleLogout() { @@ -370,6 +386,21 @@ export default function App() { } if (!authChecked) return null; - if (!currentUser) return ; - return ; + + return ( + <> + {!currentUser + ? + : + } + {showPrivacyModal && ( +
setShowPrivacyModal(false)}> +
e.stopPropagation()}> + + +
+
+ )} + + ); } diff --git a/frontend/src/components/LoginPage.js b/frontend/src/components/LoginPage.js index 38ed864..be2f635 100644 --- a/frontend/src/components/LoginPage.js +++ b/frontend/src/components/LoginPage.js @@ -152,7 +152,11 @@ export default function LoginPage({ onLogin }) { {loading ? 'Signing in…' : 'Sign in'} -

By bobbantech

+

+ By bobbantech + {' · '} + { e.preventDefault(); window.dispatchEvent(new CustomEvent('show-privacy')); }}>Privacy +

); diff --git a/frontend/src/components/PrivacyPage.js b/frontend/src/components/PrivacyPage.js new file mode 100644 index 0000000..1349092 --- /dev/null +++ b/frontend/src/components/PrivacyPage.js @@ -0,0 +1,157 @@ +export default function PrivacyPage() { + return ( +
+

Privacy Notice

+

Last updated: September 2026

+ +
+

1. About this notice

+

+ Sloth Manager is a self-hosted application operated by the organisation that deployed it + (the operator). This notice describes what personal data Sloth Manager + stores, why it stores it, and how long it is kept. It applies to all users of this + installation. +

+
+ +
+

2. Data we store

+ +

User accounts

+ + + + + + + + + + + + + + + + +
DataPurposeRetention
UsernameIdentifies you within the application and appears in the audit logUntil the account is deleted by an administrator
Password (bcrypt hash)Authenticates you on login — the original password is never storedUntil the account is deleted or the password is changed
+ +

Audit log

+ + + + + + + + + + + +
DataPurposeRetention
Username, timestamp, action, DNS provider, zone, record details + Maintains an accountable history of all DNS record changes made through + the application + Rolling window of the latest 500 entries; oldest entries are removed automatically
+ +

Notification settings

+ + + + + + + + + + + + + + + + +
DataPurposeRetention
Email address (SMTP "To" field)Sends DNS-change notifications to the configured recipientUntil removed from Settings by an administrator
API tokens / URLs (Gotify, ntfy, webhook)Delivers notifications to the configured channelsUntil removed from Settings by an administrator
+ +

SSO (Authentik)

+

+ If single sign-on is enabled, Sloth Manager receives your username and + email address from Authentik during login. Only the username is stored + locally (as a user account). No SSO tokens or session data are persisted beyond the + duration of your login session. +

+
+ +
+

3. What we do not store

+
    +
  • Browser cookies or tracking identifiers
  • +
  • IP addresses or device information
  • +
  • Analytics or usage statistics
  • +
  • Any data from third-party advertisers
  • +
+

+ Login sessions use a short-lived JWT token stored in your browser's local storage. + It expires automatically and contains only your username and user ID. +

+
+ +
+

4. Legal basis for processing

+

+ Personal data is processed on the basis of legitimate interests — + specifically the secure operation of the DNS management tool and maintaining an + accountable record of infrastructure changes. User accounts are required to access + the application. +

+
+ +
+

5. Data sharing

+

+ Sloth Manager does not share personal data with third parties. Data is stored locally + on the server where the application is hosted. Notification channels (Gotify, ntfy, + SMTP, webhooks) receive message content only — they do not receive account data. +

+

+ DNS operations are performed against the configured DNS providers (Cloudflare, Loopia, + Pi-hole, Azure DNS, cPanel, Technitium). These providers receive only the DNS record + data necessary to fulfil each operation — not user or account information. +

+
+ +
+

6. Your rights

+

+ Under the GDPR you have the right to access, correct, or erase your personal data. + Contact the operator of this installation to exercise these rights. Administrators can: +

+
    +
  • View and update your username in Settings → Users
  • +
  • Delete your account in Settings → Users
  • +
  • Clear the audit log via the Diagnostics page
  • +
+

+ You can change your own password at any time in 👤 My Profile. +

+
+ +
+

7. Data location & security

+

+ All data is stored on the server where this instance of Sloth Manager is hosted. + The operator is responsible for securing that server, applying backups, and ensuring + appropriate access controls. Passwords are stored as bcrypt hashes (cost factor 10) + and are not recoverable. +

+
+ +
+

8. Contact

+

+ For questions about how your data is handled, contact the administrator of this + Sloth Manager installation. +

+
+
+ ); +}