@unbrained/pm-web 2026.7.11 → 2026.7.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +26 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +38 -2
- package/dist/app.js.map +1 -1
- package/dist/auth.d.ts +2 -1
- package/dist/auth.js +10 -0
- package/dist/auth.js.map +1 -1
- package/dist/db.js +17 -1
- package/dist/db.js.map +1 -1
- package/dist/ical.js.map +1 -1
- package/dist/index.d.ts +1 -22
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/oidc.d.ts +89 -0
- package/dist/oidc.js +305 -0
- package/dist/oidc.js.map +1 -0
- package/dist/routes/auth.js +3 -15
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/github.js +3 -3
- package/dist/routes/github.js.map +1 -1
- package/dist/routes/oidc.d.ts +4 -0
- package/dist/routes/oidc.js +147 -0
- package/dist/routes/oidc.js.map +1 -0
- package/dist/routes/pm.js +121 -107
- package/dist/routes/pm.js.map +1 -1
- package/dist/routes/projects.js +9 -2
- package/dist/routes/projects.js.map +1 -1
- package/dist/server.js +7 -2
- package/dist/server.js.map +1 -1
- package/dist/services/pm-runner.d.ts +11 -3
- package/dist/services/pm-runner.js +167 -54
- package/dist/services/pm-runner.js.map +1 -1
- package/dist/services/realtime-bus.d.ts +1 -0
- package/dist/services/realtime-bus.js +148 -0
- package/dist/services/realtime-bus.js.map +1 -0
- package/dist/services/sse.d.ts +3 -1
- package/dist/services/sse.js +83 -48
- package/dist/services/sse.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +11 -11
- package/public/cookie-consent.js +54 -39
- package/public/cookie-settings.html +7 -7
- package/public/index.html +2 -0
- package/public/legal-notice.html +10 -12
- package/public/privacy-policy.html +14 -24
- package/public/src/app.js +4 -4
- package/public/src/app.js.map +1 -1
- package/public/src/app.ts +4 -4
- package/public/src/components/toast.js.map +1 -1
- package/public/src/cookie-consent.ts +68 -0
- package/public/src/sw.ts +443 -0
- package/public/src/views/auth.js +22 -0
- package/public/src/views/auth.js.map +1 -1
- package/public/src/views/auth.ts +20 -0
- package/public/src/views/graph.js.map +1 -1
- package/public/styles.css +2 -0
- package/public/sw.js +320 -256
- package/public/terms.html +7 -9
- package/public/tsconfig.json +1 -1
- package/public/tsconfig.scripts.json +18 -0
- package/public/tsconfig.sw.json +18 -0
- package/sql/schema.sql +16 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { Router } from "express";
|
|
3
|
+
import { authorizationCodeGrant, buildAuthorizationUrl, ClientSecretBasic, Configuration, discovery, } from "openid-client";
|
|
4
|
+
import { pool } from "../db.js";
|
|
5
|
+
import { setSessionCookie } from "../auth.js";
|
|
6
|
+
import { createOidcState, decodeOidcStateCookie, encodeOidcStateCookie, OIDC_STATE_COOKIE, OIDC_STATE_MAX_AGE_MS, oidcGrantChecks, OidcFlowError, oidcPublicConfig, resolveExternalIdentity, resolveOidcSettings, validateOidcClaims, } from "../oidc.js";
|
|
7
|
+
const router = Router();
|
|
8
|
+
const stateCookieOptions = {
|
|
9
|
+
httpOnly: true,
|
|
10
|
+
secure: process.env.NODE_ENV === "production",
|
|
11
|
+
sameSite: "lax",
|
|
12
|
+
path: "/api/auth/oidc",
|
|
13
|
+
};
|
|
14
|
+
let cachedDiscovery = null;
|
|
15
|
+
function enabledSettings() {
|
|
16
|
+
const settings = resolveOidcSettings();
|
|
17
|
+
if (!settings.enabled) {
|
|
18
|
+
throw new OidcFlowError("oidc_disabled", "OpenID Connect login is not enabled.", 404);
|
|
19
|
+
}
|
|
20
|
+
return settings;
|
|
21
|
+
}
|
|
22
|
+
function discoveryConfiguration(settings) {
|
|
23
|
+
const fingerprint = crypto
|
|
24
|
+
.createHash("sha256")
|
|
25
|
+
.update(`${settings.issuer}\0${settings.clientId}\0${settings.clientSecret}`)
|
|
26
|
+
.digest("base64url");
|
|
27
|
+
if (!cachedDiscovery || cachedDiscovery.fingerprint !== fingerprint) {
|
|
28
|
+
const promise = discoverProvider(settings).catch((error) => {
|
|
29
|
+
if (cachedDiscovery?.fingerprint === fingerprint)
|
|
30
|
+
cachedDiscovery = null;
|
|
31
|
+
throw error;
|
|
32
|
+
});
|
|
33
|
+
cachedDiscovery = { fingerprint, promise };
|
|
34
|
+
}
|
|
35
|
+
return cachedDiscovery.promise;
|
|
36
|
+
}
|
|
37
|
+
async function discoverProvider(settings) {
|
|
38
|
+
const issuer = new URL(settings.issuer);
|
|
39
|
+
const initial = await discovery(issuer, settings.clientId, settings.clientSecret);
|
|
40
|
+
const methods = initial.serverMetadata().token_endpoint_auth_methods_supported;
|
|
41
|
+
if (Array.isArray(methods) &&
|
|
42
|
+
!methods.includes("client_secret_post") &&
|
|
43
|
+
methods.includes("client_secret_basic")) {
|
|
44
|
+
return new Configuration(initial.serverMetadata(), settings.clientId, { client_secret: settings.clientSecret }, ClientSecretBasic(settings.clientSecret));
|
|
45
|
+
}
|
|
46
|
+
return initial;
|
|
47
|
+
}
|
|
48
|
+
export function providerAuthorizationError(value) {
|
|
49
|
+
if (typeof value !== "string" || !value)
|
|
50
|
+
return null;
|
|
51
|
+
if (value === "access_denied") {
|
|
52
|
+
return new OidcFlowError("provider_access_denied", "OpenID Connect login was canceled or denied.", 403);
|
|
53
|
+
}
|
|
54
|
+
return new OidcFlowError("provider_error", "The OpenID Connect provider could not complete login.");
|
|
55
|
+
}
|
|
56
|
+
function callbackUrl(req, configuredRedirectUri) {
|
|
57
|
+
const url = new URL(configuredRedirectUri);
|
|
58
|
+
const incoming = new URL(req.originalUrl, "http://localhost");
|
|
59
|
+
url.search = incoming.search;
|
|
60
|
+
return url;
|
|
61
|
+
}
|
|
62
|
+
function clearStateCookie(res) {
|
|
63
|
+
res.clearCookie(OIDC_STATE_COOKIE, stateCookieOptions);
|
|
64
|
+
}
|
|
65
|
+
router.get("/oidc/config", (_req, res) => {
|
|
66
|
+
res.json(oidcPublicConfig());
|
|
67
|
+
});
|
|
68
|
+
router.get("/oidc/start", async (_req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
const settings = enabledSettings();
|
|
71
|
+
const configuration = await discoveryConfiguration(settings);
|
|
72
|
+
const flow = createOidcState();
|
|
73
|
+
const authorizationUrl = buildAuthorizationUrl(configuration, {
|
|
74
|
+
redirect_uri: settings.redirectUri,
|
|
75
|
+
scope: settings.scopes,
|
|
76
|
+
response_type: "code",
|
|
77
|
+
state: flow.state,
|
|
78
|
+
nonce: flow.nonce,
|
|
79
|
+
code_challenge: flow.codeChallenge,
|
|
80
|
+
code_challenge_method: "S256",
|
|
81
|
+
});
|
|
82
|
+
res.cookie(OIDC_STATE_COOKIE, encodeOidcStateCookie(flow, settings.cookieSecret), { ...stateCookieOptions, maxAge: OIDC_STATE_MAX_AGE_MS });
|
|
83
|
+
res.redirect(302, authorizationUrl.href);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof OidcFlowError) {
|
|
87
|
+
res.status(error.status).json({ error: error.message, code: error.code });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
res.status(502).json({ error: "OpenID Connect provider discovery failed." });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
router.get("/oidc/callback", async (req, res) => {
|
|
94
|
+
let client;
|
|
95
|
+
try {
|
|
96
|
+
const settings = enabledSettings();
|
|
97
|
+
const stateCookie = req.cookies?.[OIDC_STATE_COOKIE];
|
|
98
|
+
const flow = decodeOidcStateCookie(stateCookie, settings.cookieSecret);
|
|
99
|
+
if (req.query.state !== flow.state) {
|
|
100
|
+
throw new OidcFlowError("state_mismatch", "OIDC state does not match the login request.");
|
|
101
|
+
}
|
|
102
|
+
const providerError = providerAuthorizationError(req.query.error);
|
|
103
|
+
if (providerError)
|
|
104
|
+
throw providerError;
|
|
105
|
+
if (typeof req.query.code !== "string" || !req.query.code) {
|
|
106
|
+
throw new OidcFlowError("missing_code", "OIDC authorization code is missing.");
|
|
107
|
+
}
|
|
108
|
+
const configuration = await discoveryConfiguration(settings);
|
|
109
|
+
const tokens = await authorizationCodeGrant(configuration, callbackUrl(req, settings.redirectUri), oidcGrantChecks(flow));
|
|
110
|
+
const claims = tokens.claims();
|
|
111
|
+
if (!claims) {
|
|
112
|
+
throw new OidcFlowError("missing_id_token", "OIDC provider did not return an ID token.");
|
|
113
|
+
}
|
|
114
|
+
const identity = validateOidcClaims(claims, settings, flow.nonce);
|
|
115
|
+
client = await pool.connect();
|
|
116
|
+
const user = await resolveExternalIdentity(client, identity);
|
|
117
|
+
setSessionCookie(res, { userId: user.id, email: user.email });
|
|
118
|
+
clearStateCookie(res);
|
|
119
|
+
res.redirect(303, "/");
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
clearStateCookie(res);
|
|
123
|
+
if (error instanceof OidcFlowError) {
|
|
124
|
+
res.status(error.status).json({ error: error.message, code: error.code });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Never log the full exception: OAuth failures may contain authorization
|
|
128
|
+
// codes or token data. A class + PostgreSQL-style code is sufficient to
|
|
129
|
+
// distinguish infrastructure/schema faults without exposing the payload.
|
|
130
|
+
const diagnostic = error instanceof Error
|
|
131
|
+
? {
|
|
132
|
+
name: error.name,
|
|
133
|
+
...(error.code &&
|
|
134
|
+
/^[A-Z0-9_]{1,32}$/i.test(String(error.code))
|
|
135
|
+
? { code: String(error.code) }
|
|
136
|
+
: {}),
|
|
137
|
+
}
|
|
138
|
+
: { name: typeof error };
|
|
139
|
+
console.error("OIDC callback failed", diagnostic);
|
|
140
|
+
res.status(400).json({ error: "OpenID Connect login failed." });
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
client?.release();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
export { router as oidcRouter };
|
|
147
|
+
//# sourceMappingURL=oidc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc.js","sourceRoot":"","sources":["../../src/routes/oidc.ts"],"names":[],"mappings":"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"}
|