From 4e5a2aa4f9db3be15d7ec11d5e73d416d2fc5ca4 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Fri, 17 Jul 2026 21:49:27 -0400 Subject: [PATCH] Add plain-language concept docs; fix docs viewer rendering; link API tokens - New docs/concepts-{accounts,oauth-apps,api-tokens}.md -- plain-language guides aimed at less technical readers, each linking onward to the existing schema/protocol-level doc for anyone who wants that detail. Card help links (Users, Groups, OAuth cards, My groups, Members of 's group) now point here instead of straight at the technical docs; the LDAP-protocol-wiring cards (raw connection details for connecting a 3rd-party app) stay pointed at the technical ldap.md, since that's genuinely the right depth for that task. - The "New API Token" card had no help link at all -- added, pointing to the new API Tokens doc. - Fixed the in-app docs viewer rendering every docs/*.md page with a garbled heading + stray
at the top: Jekyll front matter (meant only for the GitHub Pages build) was never stripped before being handed to the markdown renderer. Also fixed: cross-doc links (ldap.html, index.html, etc.) never resolved in-app, since this viewer serves docs at /docs/ with no .html suffix -- rewritten to the correct in-app URL, same idea as the existing image-path fix. Bumps to v1.1.12. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C --- API.md | 5 ++ CHANGELOG.md | 13 ++++- docs/concepts-accounts.md | 102 ++++++++++++++++++++++++++++++++++ docs/concepts-api-tokens.md | 59 ++++++++++++++++++++ docs/concepts-oauth-apps.md | 79 ++++++++++++++++++++++++++ docs/ldap.md | 4 ++ docs/oauth.md | 4 ++ nodejs/package-lock.json | 4 +- nodejs/package.json | 2 +- nodejs/routes/docs.js | 38 ++++++++++++- nodejs/views/groups.ejs | 4 +- nodejs/views/integrations.ejs | 4 +- nodejs/views/profile.ejs | 6 +- nodejs/views/users.ejs | 8 +-- 14 files changed, 313 insertions(+), 19 deletions(-) create mode 100644 docs/concepts-accounts.md create mode 100644 docs/concepts-api-tokens.md create mode 100644 docs/concepts-oauth-apps.md diff --git a/API.md b/API.md index f6cc00f..e40dfca 100644 --- a/API.md +++ b/API.md @@ -1,5 +1,10 @@ # SSO Manager API Documentation +> Looking for a plainer explanation of what API tokens are and when you'd +> want one, instead of a full endpoint reference? See +> [API Tokens](/docs/api-tokens) (in-app) or +> [concepts-api-tokens.md](docs/concepts-api-tokens.md) (repo). + ## Overview API documentation for the SSO Manager Node application. Provides endpoints for authentication, user management, group management, token management, notifications, and OAuth 2.0 / OpenID Connect. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2981ff2..3c22e4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,15 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`. ## [Unreleased] -## [1.1.11] - 2026-07-17 +## [1.1.12] - 2026-07-17 + +### Added +- Three new plain-language docs aimed at less technical readers, replacing the schema-level LDAP/OAuth/API docs as the target of most card help links: **Accounts, Groups & Managers**, **Connecting Apps (SSO)**, and **API Tokens**. Each links onward to the deeper technical reference for readers who want it; the technical docs link back the other way too. The personal-access-token card (previously missed) now links to its own doc. + +### Fixed +- The in-app docs viewer rendered every `docs/*.md` page with a garbled heading and a stray horizontal rule at the top — Jekyll front matter (meant only for the GitHub Pages build) was never stripped before being handed to the markdown renderer. Also fixed: cross-doc links (`ldap.html`, `index.html`, etc.) never resolved in-app, since this viewer serves docs at `/docs/` with no `.html` suffix — they're now rewritten to the correct in-app URL, the same way image paths already were. + +Bumps to v1.1.12. ### Changed - Moved the help (❓) link out of the global header and onto each relevant card individually (Invite User, Add new user, User List, Service Accounts, group cards, OAuth/LDAP integration cards, My groups, Members of ``'s group, New API Token) — each now deep-links straight to the doc that actually covers it, instead of one generic header icon. @@ -88,7 +96,8 @@ First tagged release. Establishes the `vX.Y.Z` tag convention that the in-app up - Unix/POSIX and LDAP bind-only service account support, distinct from real-person accounts. - Merged OAuth Apps + LDAP Info into a single Integrations page. -[Unreleased]: https://github.com/theta42/sso-manager-node/compare/v1.1.11...HEAD +[Unreleased]: https://github.com/theta42/sso-manager-node/compare/v1.1.12...HEAD +[1.1.12]: https://github.com/theta42/sso-manager-node/compare/v1.1.11...v1.1.12 [1.1.11]: https://github.com/theta42/sso-manager-node/compare/v1.1.10...v1.1.11 [1.1.10]: https://github.com/theta42/sso-manager-node/compare/v1.1.9...v1.1.10 [1.1.9]: https://github.com/theta42/sso-manager-node/compare/v1.1.8...v1.1.9 diff --git a/docs/concepts-accounts.md b/docs/concepts-accounts.md new file mode 100644 index 0000000..51cb6bf --- /dev/null +++ b/docs/concepts-accounts.md @@ -0,0 +1,102 @@ +--- +layout: default +title: Accounts, Groups & Managers +description: A plain-language guide to users, service accounts, personal groups, and managers in SSO Manager. +--- + +# Accounts, Groups & Managers + +This page explains the concepts behind the Users and Groups pages in plain +language. If you want the technical schema/attribute-level detail instead, +see the [LDAP reference](ldap.html). + +## What's an account? + +Every person (or app) that can sign in through this SSO Manager has an +**account** — a username, a display name, maybe an email address, and a +password (or, for service accounts, no password at all — see below). +Accounts live in the directory this app manages, and any other app you've +connected (Gitea, Home Assistant, your Wi-Fi, whatever) checks against these +same accounts instead of keeping its own separate list of users and +passwords. + +## Two kinds of account: people and service accounts + +Most accounts belong to an actual person — check **Users → People** to see +them. But sometimes you need an account for something that *isn't* a +person: a media server, a backup script, a bind account another app uses to +look people up. These are **service accounts**, listed separately under +**Users → Service Accounts**, and they're different from a person's account +in two ways that matter: + +- **No email required.** A service account doesn't need a mailbox, so the + form doesn't ask for one. +- **A password is optional.** If you leave it blank, nobody can log in as + that account — which is exactly what you want for something that only + ever gets used programmatically (a script authenticating with an API + token, or another app binding with a fixed, separately-configured + password you set yourself). Only give it a password if the account + genuinely needs to log in or bind somewhere as itself. + +Aside from those two differences, a service account is a completely normal +account under the hood — it can belong to groups, have a manager, and so +on, just like anyone else's. + +## Groups: who can do what + +A **group** is just a named list of accounts, used to control access. This +app has a handful of built-in groups that grant admin powers (e.g. only +people in the `app_sso_admin` group can see the Users/Groups/Integrations +pages at all), but you can also make your own groups for any app you +connect — say, a group listing everyone who should be allowed into your +photo server. Once a group exists, add or remove members from the +**Groups** page, and point the other app's "who's allowed in" setting at +that group's name. + +## Every account's personal group + +Separately from the groups above, every single account — person or +service account — automatically gets its own small, personal group when +it's created, named after the account itself. Most of the time you'll +never think about this; it exists so that, on a Linux system connected to +this directory, each account "owns" its own files by default the same way +a normal Unix user account would. + +Occasionally you'll want to share that ownership with someone else — for +example, letting a second account also have write access to files a +service account owns. That's what the **"Members of ``'s group"** +section on a profile page is for: add another account there, and the +underlying Linux permissions treat them as if they belong to that same +personal group too. + +## What's a "manager"? + +Every account has one or more **managers** — the people allowed to edit +that account's profile (phone number, SSH key, home directory, and so on) +without needing full admin rights. By default, whoever created an account +(the admin who added it, or whoever sent the invite) becomes its first +manager, but you can add or remove managers later from the account's Edit +form. + +This is useful for service accounts especially: if a service account +belongs to a particular project or person, make them its manager so they +can maintain it — rotate its SSH key, adjust its description — without +needing to be a full SSO administrator. + +## Inviting someone vs. adding them yourself + +From the Users page you can either fill in someone's details yourself +("Add new user"), or send them an **invite** — an email (or a link you copy +and send however you like) that lets them pick their own username and +password. Either way, the resulting account is identical; invites are just +a convenience so you don't have to know someone's preferred username or +handle their password directly. + +## Want more detail? + +This page deliberately leaves out LDAP schema names, attribute types, and +protocol-level detail. If you're connecting a third-party app directly to +the LDAP directory, or you just want to know exactly what's stored where, +see the [LDAP reference](ldap.html). + +[← Back to Home](index.html) diff --git a/docs/concepts-api-tokens.md b/docs/concepts-api-tokens.md new file mode 100644 index 0000000..6540ff8 --- /dev/null +++ b/docs/concepts-api-tokens.md @@ -0,0 +1,59 @@ +--- +layout: default +title: API Tokens +description: A plain-language guide to personal access tokens in SSO Manager. +--- + +# API Tokens + +This page explains what an API token is and when you'd want one. For the +full list of API endpoints a token can call, see the +[API reference](api.html). + +## What's an API token, in plain terms? + +Normally, you interact with this app by logging in through a web browser. +An **API token** (also called a personal access token, or PAT) is an +alternative way in — a long, random string that a script, a scheduled job, +or another program can use instead of a username and password, to act on +your behalf without a human typing a login in each time. + +If you've ever set up a script to talk to GitHub, GitLab, or a similar +service using a "token" instead of your real password, this is the same +idea. + +## When would you actually need one? + +Most people never need to create one of these — you'll only want a token +if you're automating something, for example: + +- A script that syncs users or groups from somewhere else into this SSO + Manager on a schedule. +- A backup or monitoring job that checks this app's health via its API. +- A CI/CD pipeline that needs to register or update an OAuth client + automatically. + +If you're not doing any of that, you don't need an API token — just log in +normally through the web UI. + +## How it works + +Create a token from your Profile page, give it a name so you remember what +it's for later, and optionally an expiry. You'll be shown the token's +value **exactly once** — copy it somewhere safe immediately, because it +can't be viewed again afterward (only revoked or rotated). Whatever script +or tool you're using it with sends it along with each request, the same +way a browser sends your login session. + +A token acts **as you**, with **your** permissions — if you're not an +admin, a token you create can't do admin-only things either. If you ever +suspect a token has leaked (ended up somewhere it shouldn't have, like a +public script or log file), revoke it immediately from your Profile page; +it stops working right away. + +## Want more detail? + +This page doesn't attempt to list every API endpoint or show request/ +response examples — for that, see the full [API reference](api.html). + +[← Back to Home](index.html) diff --git a/docs/concepts-oauth-apps.md b/docs/concepts-oauth-apps.md new file mode 100644 index 0000000..12be819 --- /dev/null +++ b/docs/concepts-oauth-apps.md @@ -0,0 +1,79 @@ +--- +layout: default +title: Connecting Apps (Single Sign-On) +description: A plain-language guide to OAuth/OIDC clients and single sign-on in SSO Manager. +--- + +# Connecting Apps (Single Sign-On) + +This page explains, in plain language, what happens when you "connect" an +app to your SSO Manager so people can log into it with their existing +account. For the technical endpoint/token detail, see the +[OAuth reference](oauth.html). + +## What does "single sign-on" actually mean? + +Instead of every app you run having its own separate list of usernames and +passwords, they all check with this SSO Manager instead. You log in once, +here, and any connected app trusts that login — no separate password to +remember or manage for each one. If you ever need to lock someone out +everywhere at once, you do it in one place (deactivate their account here) +instead of hunting down every app individually. + +The technology behind this is called **OAuth 2.0** and **OpenID Connect +(OIDC)** — you'll see both names used, often together, referring to the +same thing. You don't need to understand the protocol to use this page; +what matters practically is the handful of concepts below. + +## What's a "client"? + +Every app you connect is registered here as a **client** — a single entry +on the Integrations page representing that one app. Registering a client +gives you a **Client ID** and **Client Secret**: think of these like a +username and password, but for the *app itself* rather than for a person. +You paste them into the other app's own "Single Sign-On" or "OIDC" setup +screen, along with the discovery URL shown at the top of this page, and +that app is now able to ask this SSO Manager to authenticate people on its +behalf. + +**Treat the Client Secret like a password** — anyone who has it can +impersonate that app when talking to your SSO Manager. If you ever suspect +it's leaked, rotate it from the client's card. + +## What are "scopes"? + +**Scopes** control what information a connected app is allowed to ask for +about the person logging in — their username, email, group memberships, +and so on. Most apps tell you exactly which scopes they need in their own +setup instructions; when in doubt, the default set (`openid`, `profile`, +`email`, `groups`) covers what nearly every app expects. + +## "Restrict to Groups" + +By default, *any* account with an SSO Manager login can sign into a +connected app. If that's not what you want — say, a home automation +dashboard that only certain family members should reach — set **Restrict +to Groups** on that client to one of your [groups](concepts-accounts.html). +Only members of that group will be allowed to log into that particular +app; everyone else gets turned away at the login step, even though their +SSO Manager account still works everywhere else. + +## Redirect URIs + +A **Redirect URI** is the exact web address the connected app wants people +sent back to once they've logged in here — it's a security measure so an +attacker can't trick the login flow into redirecting somewhere else. The +app's own setup instructions will tell you this value; copy it in exactly +as given. If the app is reachable via more than one hostname (for example, +because it sits behind [theta42/proxy](https://theta42.github.io/proxy/)), +this field supports wildcard patterns — see the inline help under the +field itself for the exact syntax. + +## Want more detail? + +This page intentionally skips the protocol-level detail (exact endpoint +URLs, token formats, claim names). If you're troubleshooting a connection +or building something against the API directly, see the +[OAuth reference](oauth.html). + +[← Back to Home](index.html) diff --git a/docs/ldap.md b/docs/ldap.md index 93a6478..cae91af 100644 --- a/docs/ldap.md +++ b/docs/ldap.md @@ -8,6 +8,10 @@ description: SSO Manager's bundled OpenLDAP directory — schema, service accoun [← Back to Home](index.html) +> Looking for a plainer explanation of accounts, groups, and managers +> instead of schema/attribute detail? See +> [Accounts, Groups & Managers](concepts-accounts.html). + SSO Manager runs an OpenLDAP directory holding your users and groups. The app authenticates against it over `localhost:389` (inside the all-in-one container) and exposes **LDAPS** (`ldaps://…:636`, TLS) for legacy apps that bind LDAP diff --git a/docs/oauth.md b/docs/oauth.md index 02b8cb1..13c95c5 100644 --- a/docs/oauth.md +++ b/docs/oauth.md @@ -8,6 +8,10 @@ description: SSO Manager's OpenID Connect / OAuth 2.0 provider — discovery doc [← Back to Home](index.html) +> Looking for a plainer explanation of clients/scopes/redirect URIs instead +> of endpoint-level detail? See +> [Connecting Apps (Single Sign-On)](concepts-oauth-apps.html). + SSO Manager is an **OpenID Connect / OAuth 2.0 provider**: it issues its own access, refresh, and ID tokens that your apps can consume to authenticate users and authorize API calls. It also runs a full OpenLDAP directory, so it diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 2a26bc0..8230fc0 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -1,12 +1,12 @@ { "name": "t42-sso-manager", - "version": "1.1.11", + "version": "1.1.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "t42-sso-manager", - "version": "1.1.11", + "version": "1.1.12", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^7.3.0", diff --git a/nodejs/package.json b/nodejs/package.json index d409a72..a5a6ef3 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "t42-sso-manager", - "version": "1.1.11", + "version": "1.1.12", "private": true, "author": [ { diff --git a/nodejs/routes/docs.js b/nodejs/routes/docs.js index 5e36439..5f9c050 100644 --- a/nodejs/routes/docs.js +++ b/nodejs/routes/docs.js @@ -25,6 +25,14 @@ const values = { // back at the root DEPLOYMENT.md (see docs/deployment.md itself), which is // already covered by the "deployment" entry. const DOCS = { + // Plain-language "what is this and why would I use it" guides -- linked + // directly from the relevant card in the UI (see the help icon on each + // card). Each links onward to the deeper technical doc below for readers + // who want the schema/protocol-level detail. + accounts: {title: 'Accounts, Groups & Managers', file: path.join(__dirname, '../../docs/concepts-accounts.md')}, + 'oauth-apps': {title: 'Connecting Apps (SSO)', file: path.join(__dirname, '../../docs/concepts-oauth-apps.md')}, + 'api-tokens': {title: 'API Tokens', file: path.join(__dirname, '../../docs/concepts-api-tokens.md')}, + overview: {title: 'Overview', file: path.join(__dirname, '../../README.md')}, changelog: {title: 'Changelog', file: path.join(__dirname, '../../CHANGELOG.md')}, deployment: {title: 'Deployment', file: path.join(__dirname, '../../DEPLOYMENT.md')}, @@ -46,6 +54,30 @@ function fixImagePaths(html) { return html.replace(/(["(])docs\/images\//g, '$1/docs/images/'); } +// Docs cross-link each other as ".html" (correct for the Jekyll/GitHub +// Pages build, which is what these same .md files also feed) and +// "index.html" for the docs home -- neither resolves here, where a doc lives +// at /docs/ with no .html suffix. Rewrite known doc links to the +// in-app route, same idea as fixImagePaths() above. Only touches slugs that +// actually exist, so an unrelated "foo.html" link is left alone. +function fixDocLinks(html) { + return html + .replace(/href="index\.html"/g, 'href="/docs"') + .replace(/href="([a-z0-9-]+)\.html"/g, (match, slug) => DOCS[slug] ? `href="/docs/${slug}"` : match); +} + +// docs/*.md files (not the repo-root README/CHANGELOG/API.md) carry Jekyll +// front matter for the GitHub Pages build and a "← Back to Home" link back +// to that site's index -- both meaningless here (this viewer has its own +// doc-list sidebar, docs_page.ejs) and, worse, marked() doesn't know front +// matter isn't regular markdown: it rendered as a garbled heading + stray +//
at the top of every page. Strip both before rendering. +function stripJekyllCruft(content) { + return content + .replace(/^---\n[\s\S]*?\n---\n/, '') + .replace(/^\s*\[← Back to Home\]\([^)]*\)\s*\n/m, ''); +} + router.use(rateLimit.docs); router.get('/', function(req, res) { @@ -65,7 +97,7 @@ router.get('/search', function(req, res) { const results = []; for (const [slug, doc] of Object.entries(DOCS)) { try { - const content = fs.readFileSync(doc.file, 'utf8'); + const content = stripJekyllCruft(fs.readFileSync(doc.file, 'utf8')); const matchLine = content.split('\n').find(line => line.toLowerCase().includes(qLower)); if (matchLine) { results.push({slug, title: doc.title, snippet: matchLine.trim().slice(0, 200)}); @@ -81,13 +113,13 @@ router.get('/:slug', function(req, res, next) { if (!doc) return next({status: 404, message: 'Doc not found'}); try { - const content = fs.readFileSync(doc.file, 'utf8'); + const content = stripJekyllCruft(fs.readFileSync(doc.file, 'utf8')); res.render('docs_page', { ...values, docs: docList, currentSlug: req.params.slug, docTitle: doc.title, - docHtml: fixImagePaths(marked(content)), + docHtml: fixDocLinks(fixImagePaths(marked(content))), }); } catch (error) { next(error); diff --git a/nodejs/views/groups.ejs b/nodejs/views/groups.ejs index 51bae6a..938d40c 100644 --- a/nodejs/views/groups.ejs +++ b/nodejs/views/groups.ejs @@ -142,7 +142,7 @@
Add new group - +
@@ -168,7 +168,7 @@
Group: {{ cn }} - +