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 }} - +