Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bd14f2a
Replace builtin proxy auth with oauth2-proxy
runleveldev Jun 16, 2026
bfbf47b
oauth2-proxy auth_request: support central auth host on the same LB
runleveldev Jun 16, 2026
43cee14
Address PR review: validate authServer in API, use $proxy_host
runleveldev Jun 16, 2026
724779b
cleanup
runleveldev Jun 16, 2026
d35d7b6
Add X-Forwarded-Uri to the oauth2-proxy auth subrequest
runleveldev Jun 16, 2026
da2ad4d
Proxy /oauth2/* to oauth2-proxy in a single hop
runleveldev Jun 16, 2026
1505aad
Fix @502 on protected domains; document redis session store
runleveldev Jun 16, 2026
f0a5837
Re-declare error_page in protected location / so @502/@403 work
runleveldev Jun 16, 2026
2db1683
Consolidate error_page to a single server-scope block
runleveldev Jun 16, 2026
407ee8d
docs: drop stale oauth2-proxy reverse-proxy framing
runleveldev Jun 16, 2026
f8d4e4e
simplify nginx config, pass preferred username to backends using auth
runleveldev Jun 25, 2026
3a69160
docs: add "Adding Authentication" user guide for the auth header cont…
runleveldev Jun 25, 2026
499296b
Enlarge nginx header buffers for large JWT access tokens
runleveldev Jun 25, 2026
e590ad6
docs: add copy-paste oauth2-proxy config template
runleveldev Jun 25, 2026
75e9e6f
docs: lead auth setup with the config; highlight it as TOML
runleveldev Jun 25, 2026
04bbbd1
docs: drop upstreams from oauth2-proxy config, add tested options
runleveldev Jun 25, 2026
80415c3
docs: whitelist_domains is required in the oauth2-proxy config
runleveldev Jun 25, 2026
db984fa
docs: recommend __Host- cookie name and cookie_samesite=lax
runleveldev Jun 25, 2026
c8c7c3e
docs: remove shared sign-in explainations
runleveldev Jun 25, 2026
8163a86
docs: remove unnessecary and confusing reverse-proxy warning
runleveldev Jun 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,9 @@ export function ExternalDomainFormPage() {
{...register('cloudflareApiKey')}
/>
<Input
label="Auth server"
helperText="Optional URL for nginx auth_request"
label="oauth2-proxy URL"
placeholder="http://127.0.0.1:4180"
helperText="Optional. Address of an oauth2-proxy process used for nginx auth_request. nginx proxies /oauth2/* straight to it."
{...register('authServer')}
/>
{mutation.error && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function ExternalDomainsListPage() {
<TableHead>Domain</TableHead>
<TableHead>Site</TableHead>
<TableHead>Cloudflare</TableHead>
<TableHead>Auth server</TableHead>
<TableHead>oauth2-proxy</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
Expand Down
19 changes: 17 additions & 2 deletions create-a-container/models/external-domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,24 @@ module.exports = (sequelize) => {
type: DataTypes.STRING,
allowNull: true,
validate: {
isUrl: true
// Must be an absolute http(s) URL — it is interpolated directly into
// nginx `proxy_pass`, which requires a scheme. Reject scheme-less hosts
// (e.g. "oauth2-proxy:4180") and non-http schemes here so a bad value
// can never reach the generated config.
isHttpUrl(value) {
if (value === null || value === undefined || value === '') return;
let url;
try {
url = new URL(value);
} catch (e) {
throw new Error('authServer must be an absolute URL, e.g. http://127.0.0.1:4180');
}
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
throw new Error('authServer must use http or https');
}
}
Comment thread
runleveldev marked this conversation as resolved.
},
comment: 'Auth server URL for auth_request (e.g., https://manager.example.com). Must implement GET /verify and GET /login?redirect='
comment: "Address of the oauth2-proxy process for nginx auth_request, e.g. http://127.0.0.1:4180. nginx proxies /oauth2/* straight to it in a single hop; do not point this at a path-prefixed URL."
}
}, {
tableName: 'ExternalDomains',
Expand Down
61 changes: 0 additions & 61 deletions create-a-container/routers/verify.js

This file was deleted.

15 changes: 1 addition & 14 deletions create-a-container/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const SequelizeStore = require('express-session-sequelize')(session.Store);
const path = require('path');
const RateLimit = require('express-rate-limit');
const crypto = require('crypto');
const net = require('net');
const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');
const { sequelize, SessionSecret } = require('./models');
Expand Down Expand Up @@ -58,25 +57,15 @@ async function main() {
store: sessionStore,
resave: false,
saveUninitialized: false,
// Dynamic cookie: drop the host part and set domain to the parent domain
// (e.g., manager.example.com → .example.com) so the session cookie is
// shared across sibling subdomains for nginx auth_request.
// For IP addresses (IPv4/IPv6) and single-label hosts like "localhost",
// omit the domain attribute so the browser scopes the cookie to the
// exact host (RFC 6265 forbids domain attributes on IP literals).
// `secure` is derived from the request protocol (honoring `trust proxy`
// and X-Forwarded-Proto from nginx) rather than NODE_ENV, so the flag
// tracks the actual transport — set on HTTPS, omitted on plain HTTP
// bootstrap/dev access.
cookie: function(req) {
const hostname = req.hostname || '';
const parts = hostname.split('.');
const shouldDropHost = !net.isIP(hostname) && parts.length > 2;
return {
secure: req.secure,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
sameSite: 'lax',
domain: shouldDropHost ? `.${parts.slice(1).join('.')}` : hostname
};
}
}));
Expand All @@ -103,10 +92,8 @@ async function main() {
// --- Mount Routers ---
const apiV1Router = require('./routers/api/v1');
const templatesRouter = require('./routers/templates');
const verifyRouter = require('./routers/verify');

app.use('/api/v1', apiV1Router);
app.use('/verify', verifyRouter); // nginx auth_request subrequest endpoint
app.use('/', templatesRouter); // serves /sites/:siteId/nginx and /sites/:siteId/dnsmasq/:file

// --- API Documentation (Swagger UI) ---
Expand All @@ -123,7 +110,7 @@ async function main() {
// --- SPA: serve compiled React app for everything else ---
const clientDist = path.join(__dirname, 'client', 'dist');
app.use(express.static(clientDist));
app.get(/^\/(?!api(\/|$)|verify(\/|$)|sites\/[^/]+\/(nginx$|dnsmasq\/)).*$/, (req, res) => {
app.get(/^\/(?!api(\/|$)|sites\/[^/]+\/(nginx$|dnsmasq\/)).*$/, (req, res) => {
res.sendFile(path.join(clientDist, 'index.html'));
});

Expand Down
Loading
Loading