Compare commits

...

4 Commits

Author SHA1 Message Date
wmantly ff10a23e78 Merge pull request #65 from theta42/bump-1.1.2
Bump version to 1.1.2
2026-07-16 15:38:27 -04:00
wmantly c2851ea537 Bump version to 1.1.2 2026-07-16 15:36:59 -04:00
wmantly 98d767a201 Merge pull request #64 from theta42/airgap-and-docs
Air-gap: remove dead CDN reference + in-app /docs
2026-07-16 15:34:12 -04:00
wmantly 955189d08a Air-gap: remove dead CDN reference + in-app /docs
- Removed a dead IE<9-only html5shim script tag pointing at a domain
  that no longer resolves.
- New GET /docs (index) and /docs/:slug routes render this project's
  own README, DEPLOYMENT, API.md, docs/*.md, and directory_spec.md
  server-side via marked -- so the documentation is readable from the
  running app with no route to GitHub Pages, where it otherwise only
  lives. Public, no auth, rate-limited (middleware/rate_limit.js) like
  the other public routes.
- .dockerignore/Dockerfile.openldap updated to copy DEPLOYMENT.md,
  API.md, directory_spec.md, and docs/ into the image, mirroring the
  existing tos.md -> /tos.md convention.
2026-07-16 15:33:46 -04:00
11 changed files with 158 additions and 10 deletions
+7 -1
View File
@@ -9,9 +9,15 @@
.claude
*.md
# README.md and tos.md are both read at runtime (tos.md is loaded by
# routes/index.js at boot), so they must stay in the build context.
# routes/index.js at boot). DEPLOYMENT.md/API.md/directory_spec.md/docs/*.md
# are read at runtime too, by routes/docs.js -- all must stay in the build
# context.
!README.md
!tos.md
!DEPLOYMENT.md
!API.md
!directory_spec.md
!docs/**/*.md
# Tests
nodejs/tests/
+8
View File
@@ -95,6 +95,14 @@ COPY nodejs/public ./public
# level above the nodejs/ app dir). Without this the app crashes on startup.
COPY tos.md /tos.md
# Documentation, served in-app at /docs (routes/docs.js) so it's readable
# without internet access. Same flattened-path convention as tos.md above.
COPY README.md /README.md
COPY DEPLOYMENT.md /DEPLOYMENT.md
COPY API.md /API.md
COPY directory_spec.md /directory_spec.md
COPY docs /docs
# Baked commit hash from the gitinfo stage (see build_info.js).
COPY --from=gitinfo /commit.txt ./.build_commit
+5
View File
@@ -68,6 +68,11 @@ app.use('/static', express.static(path.join(__dirname, 'public'), {maxAge: '1h'}
// Routes for front end content.
app.use('/', require('./routes/index'));
// Local, in-app copy of the project's documentation (README, DEPLOYMENT,
// API.md, docs/*) -- public, no auth, so it's readable even by a locked-out
// admin or an air-gapped operator with no route to GitHub Pages.
app.use('/docs', require('./routes/docs'));
// API routes for authentication.
app.use('/api/auth', require('./routes/auth'));
+8
View File
@@ -40,3 +40,11 @@ exports.invite = rateLimit({
limit: 20,
handler: handler({ name: 'RateLimitError', message: 'Too many requests, try again later.' }),
});
// Public, unauthenticated, reads from disk on every request -- generous
// since it's just docs, but still throttled per IP.
exports.docs = rateLimit({
windowMs: 60 * 1000,
limit: 120,
handler: handler({ name: 'RateLimitError', message: 'Too many requests, try again later.' }),
});
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "t42-sso-manager",
"version": "1.1.1",
"version": "1.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "t42-sso-manager",
"version": "1.1.1",
"version": "1.1.2",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "t42-sso-manager",
"version": "1.1.1",
"version": "1.1.2",
"private": true,
"author": [
{
+71
View File
@@ -0,0 +1,71 @@
'use strict';
const fs = require('fs');
const path = require('path');
const router = require('express').Router();
const {marked} = require('marked');
const conf = require('@simpleworkjs/conf');
const buildInfo = require('../utils/build_info');
const rateLimit = require('../middleware/rate_limit');
const values = {
title: conf.environment !== 'production' ? `dev` : '',
titleIcon: conf.environment !== 'production' ? `<i class="fa-brands fa-dev"></i>` : '',
name: conf.name,
...buildInfo,
};
// Full local copy of the project's documentation, rendered server-side --
// so an operator running air-gapped (no route to GitHub Pages, where this
// content otherwise only lives) can still read it from the running app.
// An explicit slug -> file allowlist, never a user-suppliable path, so
// there's no way to make this read outside the doc set below.
// docs/deployment.md is deliberately excluded -- it's just a stub pointing
// back at the root DEPLOYMENT.md (see docs/deployment.md itself), which is
// already covered by the "deployment" entry.
const DOCS = {
overview: {title: 'Overview', file: path.join(__dirname, '../../README.md')},
deployment: {title: 'Deployment', file: path.join(__dirname, '../../DEPLOYMENT.md')},
api: {title: 'API Reference', file: path.join(__dirname, '../../API.md')},
ldap: {title: 'LDAP', file: path.join(__dirname, '../../docs/ldap.md')},
oauth: {title: 'OAuth', file: path.join(__dirname, '../../docs/oauth.md')},
configuration: {title: 'Configuration', file: path.join(__dirname, '../../docs/configuration.md')},
'directory-spec': {title: 'Directory Spec (draft)', file: path.join(__dirname, '../../directory_spec.md')},
};
const docList = Object.entries(DOCS).map(([slug, d]) => ({slug, title: d.title}));
// README.md links its screenshots as repo-relative "docs/images/...", which
// only resolves correctly on GitHub. Serve that same folder here and rewrite
// the rendered markup to point at it absolutely, so the images work when
// read from /docs/overview too.
router.use('/images', require('express').static(path.join(__dirname, '../../docs/images')));
function fixImagePaths(html) {
return html.replace(/(["(])docs\/images\//g, '$1/docs/images/');
}
router.use(rateLimit.docs);
router.get('/', function(req, res) {
res.render('docs_index', {...values, docs: docList});
});
router.get('/:slug', function(req, res, next) {
const doc = DOCS[req.params.slug];
if (!doc) return next({status: 404, message: 'Doc not found'});
try {
const content = fs.readFileSync(doc.file, 'utf8');
res.render('docs_page', {
...values,
docs: docList,
currentSlug: req.params.slug,
docTitle: doc.title,
docHtml: fixImagePaths(marked(content)),
});
} catch (error) {
next(error);
}
});
module.exports = router;
+3
View File
@@ -10,6 +10,9 @@
<a href="https://github.com/theta42/sso-manager-node/blob/master/LICENSE" target="_blank" class="text-light">MIT License</a>
</span>
<span class="d-flex align-items-center gap-3">
<a href="/docs" class="text-light text-decoration-none">
<i class="fa-solid fa-book"></i> Docs
</a>
<a href="https://github.com/theta42/sso-manager-node" target="_blank" class="text-light text-decoration-none">
<i class="fa-brands fa-github"></i> GitHub
</a>
+24
View File
@@ -0,0 +1,24 @@
<%- include('top') %>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg mt-4 mb-4">
<div class="card-header shadow">
<i class="fa-solid fa-book"></i> Documentation
</div>
<div class="card-body">
<p class="text-muted">
A local copy of this project's documentation, readable from the
running app -- no internet access required.
</p>
<ul class="list-group">
<% docs.forEach(function(doc){ %>
<li class="list-group-item">
<a href="/docs/<%= doc.slug %>"><%= doc.title %></a>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
</div>
<%- include('bottom') %>
+29
View File
@@ -0,0 +1,29 @@
<%- include('top') %>
<div class="row">
<div class="col-md-3 d-none d-md-block">
<div class="card shadow-lg mt-4 mb-4">
<div class="card-header shadow">
<i class="fa-solid fa-book"></i> Documentation
</div>
<div class="list-group list-group-flush">
<% docs.forEach(function(doc){ %>
<a href="/docs/<%= doc.slug %>"
class="list-group-item list-group-item-action<%= doc.slug === currentSlug ? ' active' : '' %>">
<%= doc.title %>
</a>
<% }) %>
</div>
</div>
</div>
<div class="col-md-9">
<div class="card shadow-lg mt-4 mb-4">
<div class="card-header shadow">
<i class="fa-solid fa-file-lines"></i> <%= docTitle %>
</div>
<div class="card-body">
<%- docHtml %>
</div>
</div>
</div>
</div>
<%- include('bottom') %>
-6
View File
@@ -24,12 +24,6 @@
<script type="text/javascript" src="/static-modules/moment/moment.js"></script>
<script type="text/javascript" src="/static/lib/js/app-base.js"></script>
<script type="text/javascript" src="/static/js/app.js"></script>
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>