@indecisivedev/openplan 0.2.0-alpha.2
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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/access-policy.d.ts +35 -0
- package/dist/access-policy.js +107 -0
- package/dist/access-policy.js.map +1 -0
- package/dist/access-store.d.ts +33 -0
- package/dist/access-store.js +212 -0
- package/dist/access-store.js.map +1 -0
- package/dist/admin.d.ts +1 -0
- package/dist/admin.js +13 -0
- package/dist/admin.js.map +1 -0
- package/dist/api.d.ts +2 -0
- package/dist/api.js +867 -0
- package/dist/api.js.map +1 -0
- package/dist/bootstrap-admin.d.ts +1 -0
- package/dist/bootstrap-admin.js +35 -0
- package/dist/bootstrap-admin.js.map +1 -0
- package/dist/browser-security.d.ts +5 -0
- package/dist/browser-security.js +53 -0
- package/dist/browser-security.js.map +1 -0
- package/dist/celld-quickstart.d.ts +51 -0
- package/dist/celld-quickstart.js +1175 -0
- package/dist/celld-quickstart.js.map +1 -0
- package/dist/cli-api-client.d.ts +78 -0
- package/dist/cli-api-client.js +272 -0
- package/dist/cli-api-client.js.map +1 -0
- package/dist/cli-credentials.d.ts +47 -0
- package/dist/cli-credentials.js +447 -0
- package/dist/cli-credentials.js.map +1 -0
- package/dist/cli-endpoint.d.ts +19 -0
- package/dist/cli-endpoint.js +56 -0
- package/dist/cli-endpoint.js.map +1 -0
- package/dist/cli-program.d.ts +11 -0
- package/dist/cli-program.js +934 -0
- package/dist/cli-program.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -0
- package/dist/client-ip.d.ts +4 -0
- package/dist/client-ip.js +49 -0
- package/dist/client-ip.js.map +1 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +113 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +3 -0
- package/dist/crypto.js +11 -0
- package/dist/crypto.js.map +1 -0
- package/dist/database.d.ts +15 -0
- package/dist/database.js +155 -0
- package/dist/database.js.map +1 -0
- package/dist/db.d.ts +31 -0
- package/dist/db.js +388 -0
- package/dist/db.js.map +1 -0
- package/dist/drafts.d.ts +43 -0
- package/dist/drafts.js +90 -0
- package/dist/drafts.js.map +1 -0
- package/dist/feature-settings.d.ts +7 -0
- package/dist/feature-settings.js +32 -0
- package/dist/feature-settings.js.map +1 -0
- package/dist/generated/annotation-client.d.ts +1 -0
- package/dist/generated/annotation-client.js +2 -0
- package/dist/generated/annotation-client.js.map +1 -0
- package/dist/html-policy.d.ts +15 -0
- package/dist/html-policy.js +172 -0
- package/dist/html-policy.js.map +1 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +10 -0
- package/dist/ids.js.map +1 -0
- package/dist/local-auth.d.ts +36 -0
- package/dist/local-auth.js +215 -0
- package/dist/local-auth.js.map +1 -0
- package/dist/maintenance.d.ts +1 -0
- package/dist/maintenance.js +18 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/oidc.d.ts +39 -0
- package/dist/oidc.js +194 -0
- package/dist/oidc.js.map +1 -0
- package/dist/public-url.d.ts +17 -0
- package/dist/public-url.js +96 -0
- package/dist/public-url.js.map +1 -0
- package/dist/rate-limit.d.ts +25 -0
- package/dist/rate-limit.js +55 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/reconcile.d.ts +5 -0
- package/dist/reconcile.js +18 -0
- package/dist/reconcile.js.map +1 -0
- package/dist/render-annotations.d.ts +10 -0
- package/dist/render-annotations.js +73 -0
- package/dist/render-annotations.js.map +1 -0
- package/dist/render-web.d.ts +74 -0
- package/dist/render-web.js +439 -0
- package/dist/render-web.js.map +1 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +85 -0
- package/dist/render.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +26 -0
- package/dist/server.js.map +1 -0
- package/dist/shoo.d.ts +26 -0
- package/dist/shoo.js +92 -0
- package/dist/shoo.js.map +1 -0
- package/dist/sqlite-migrations.d.ts +6 -0
- package/dist/sqlite-migrations.js +214 -0
- package/dist/sqlite-migrations.js.map +1 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.js +86 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/upload-state.d.ts +19 -0
- package/dist/upload-state.js +118 -0
- package/dist/upload-state.js.map +1 -0
- package/dist/web-auth.d.ts +33 -0
- package/dist/web-auth.js +122 -0
- package/dist/web-auth.js.map +1 -0
- package/dist/web.d.ts +2 -0
- package/dist/web.js +550 -0
- package/dist/web.js.map +1 -0
- package/package.json +70 -0
package/dist/oidc.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import * as client from "openid-client";
|
|
2
|
+
/** Parse deployment-owned configuration; no issuer or client comes from a request. */
|
|
3
|
+
export function parseOidcSettings(env) {
|
|
4
|
+
const issuer = env.OPENPLAN_OIDC_ISSUER?.trim() || "";
|
|
5
|
+
try {
|
|
6
|
+
const url = new URL(issuer);
|
|
7
|
+
if (url.protocol !== "https:" || url.username || url.password || /[?#\x00-\x20\x7f\\]/.test(issuer)) {
|
|
8
|
+
throw new Error();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new Error("OPENPLAN_OIDC_ISSUER must be an HTTPS issuer URL without credentials, query, or fragment.");
|
|
13
|
+
}
|
|
14
|
+
const clientId = env.OPENPLAN_OIDC_CLIENT_ID?.trim() || "";
|
|
15
|
+
if (!clientId)
|
|
16
|
+
throw new Error("OPENPLAN_OIDC_CLIENT_ID is required for OIDC authentication.");
|
|
17
|
+
const clientSecret = env.OPENPLAN_OIDC_CLIENT_SECRET || undefined;
|
|
18
|
+
const configuredMethod = env.OPENPLAN_OIDC_TOKEN_ENDPOINT_AUTH_METHOD?.trim();
|
|
19
|
+
const tokenEndpointAuthMethod = configuredMethod || (clientSecret ? "client_secret_basic" : "none");
|
|
20
|
+
if (tokenEndpointAuthMethod !== "client_secret_basic" && tokenEndpointAuthMethod !== "client_secret_post" && tokenEndpointAuthMethod !== "none") {
|
|
21
|
+
throw new Error("OPENPLAN_OIDC_TOKEN_ENDPOINT_AUTH_METHOD must be client_secret_basic, client_secret_post, or none.");
|
|
22
|
+
}
|
|
23
|
+
if (tokenEndpointAuthMethod === "none" && clientSecret) {
|
|
24
|
+
throw new Error("OPENPLAN_OIDC_CLIENT_SECRET must be unset when token endpoint authentication is none.");
|
|
25
|
+
}
|
|
26
|
+
if (tokenEndpointAuthMethod !== "none" && !clientSecret?.trim()) {
|
|
27
|
+
throw new Error("OPENPLAN_OIDC_CLIENT_SECRET is required for the configured token endpoint authentication method.");
|
|
28
|
+
}
|
|
29
|
+
const scopes = [...new Set((env.OPENPLAN_OIDC_SCOPES?.trim() || "openid profile email").split(/\s+/))];
|
|
30
|
+
if (!scopes.includes("openid") || scopes.some((scope) => !/^[\x21\x23-\x5b\x5d-\x7e]+$/.test(scope))) {
|
|
31
|
+
throw new Error("OPENPLAN_OIDC_SCOPES must contain openid and valid space-separated OAuth scopes.");
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
issuer,
|
|
35
|
+
clientId,
|
|
36
|
+
...(clientSecret ? { clientSecret } : {}),
|
|
37
|
+
scopes: scopes.join(" "),
|
|
38
|
+
tokenEndpointAuthMethod
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** OIDC subjects are unique within an exact issuer, never by email address. */
|
|
42
|
+
export function oidcProviderKey(settings) {
|
|
43
|
+
return `oidc:${settings.issuer}`;
|
|
44
|
+
}
|
|
45
|
+
/** Changing the issuer or client registration invalidates existing browser sessions. */
|
|
46
|
+
export function oidcSessionBinding(settings) {
|
|
47
|
+
return JSON.stringify(["oidc", settings.issuer, settings.clientId]);
|
|
48
|
+
}
|
|
49
|
+
const discoveryCache = new Map();
|
|
50
|
+
const DISCOVERY_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
51
|
+
const DISCOVERY_CACHE_LIMIT = 8;
|
|
52
|
+
function getConfiguration(settings) {
|
|
53
|
+
// Include registration credentials so a rotated secret never reuses stale client authentication.
|
|
54
|
+
const key = JSON.stringify([settings.issuer, settings.clientId, settings.clientSecret, settings.tokenEndpointAuthMethod]);
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
for (const [cachedKey, entry] of discoveryCache) {
|
|
57
|
+
if (entry.expiresAt <= now)
|
|
58
|
+
discoveryCache.delete(cachedKey);
|
|
59
|
+
}
|
|
60
|
+
const cached = discoveryCache.get(key);
|
|
61
|
+
if (cached)
|
|
62
|
+
return cached.configuration;
|
|
63
|
+
if (discoveryCache.size >= DISCOVERY_CACHE_LIMIT) {
|
|
64
|
+
const oldest = discoveryCache.keys().next().value;
|
|
65
|
+
if (oldest !== undefined)
|
|
66
|
+
discoveryCache.delete(oldest);
|
|
67
|
+
}
|
|
68
|
+
const configuration = discoverConfiguration(settings).catch(() => {
|
|
69
|
+
if (discoveryCache.get(key)?.configuration === configuration)
|
|
70
|
+
discoveryCache.delete(key);
|
|
71
|
+
// Provider response bodies, request URLs, and client secrets must not reach browser errors/logs.
|
|
72
|
+
throw new Error("OIDC provider discovery failed. Check the issuer and client configuration.");
|
|
73
|
+
});
|
|
74
|
+
discoveryCache.set(key, { expiresAt: now + DISCOVERY_CACHE_TTL_MS, configuration });
|
|
75
|
+
return configuration;
|
|
76
|
+
}
|
|
77
|
+
async function discoverConfiguration(settings) {
|
|
78
|
+
const authentication = settings.tokenEndpointAuthMethod === "none"
|
|
79
|
+
? client.None()
|
|
80
|
+
: settings.tokenEndpointAuthMethod === "client_secret_basic"
|
|
81
|
+
? client.ClientSecretBasic(settings.clientSecret)
|
|
82
|
+
: client.ClientSecretPost(settings.clientSecret);
|
|
83
|
+
const configuration = await client.discovery(new URL(settings.issuer), settings.clientId, {}, authentication, {
|
|
84
|
+
algorithm: "oidc",
|
|
85
|
+
timeout: 10,
|
|
86
|
+
execute: [client.enableNonRepudiationChecks]
|
|
87
|
+
});
|
|
88
|
+
const metadata = configuration.serverMetadata();
|
|
89
|
+
// Preserve exact issuer spelling, including whether the operator supplied a trailing slash.
|
|
90
|
+
if (metadata.issuer !== settings.issuer)
|
|
91
|
+
throw new Error("Unexpected OIDC issuer.");
|
|
92
|
+
for (const value of [metadata.authorization_endpoint, metadata.token_endpoint, metadata.jwks_uri]) {
|
|
93
|
+
validateEndpoint(value);
|
|
94
|
+
}
|
|
95
|
+
if (metadata.userinfo_endpoint !== undefined)
|
|
96
|
+
validateEndpoint(metadata.userinfo_endpoint);
|
|
97
|
+
if (metadata.token_endpoint_auth_methods_supported && !metadata.token_endpoint_auth_methods_supported.includes(settings.tokenEndpointAuthMethod)) {
|
|
98
|
+
throw new Error("Unsupported OIDC client authentication method.");
|
|
99
|
+
}
|
|
100
|
+
if (metadata.code_challenge_methods_supported && !metadata.code_challenge_methods_supported.includes("S256")) {
|
|
101
|
+
throw new Error("OIDC provider does not support S256 PKCE.");
|
|
102
|
+
}
|
|
103
|
+
return configuration;
|
|
104
|
+
}
|
|
105
|
+
function validateEndpoint(value) {
|
|
106
|
+
if (typeof value !== "string")
|
|
107
|
+
throw new Error("Missing OIDC endpoint.");
|
|
108
|
+
const url = new URL(value);
|
|
109
|
+
if (url.protocol !== "https:" || url.username || url.password || /[#\x00-\x20\x7f\\]/.test(value)) {
|
|
110
|
+
throw new Error("Invalid OIDC endpoint.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function validateRedirectUri(value) {
|
|
114
|
+
try {
|
|
115
|
+
const url = new URL(value);
|
|
116
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
117
|
+
if ((url.protocol !== "https:" && !(url.protocol === "http:" && loopback))
|
|
118
|
+
|| url.username || url.password || /[?#\x00-\x20\x7f\\]/.test(value)) {
|
|
119
|
+
throw new Error();
|
|
120
|
+
}
|
|
121
|
+
return url;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
throw new Error("OIDC callback must use HTTPS (or loopback HTTP), without credentials, query, or fragment.");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export async function beginOidcLogin(settings, redirectUri) {
|
|
128
|
+
// Validate before discovery or building a URL that could disclose misconfigured credentials.
|
|
129
|
+
validateRedirectUri(redirectUri);
|
|
130
|
+
const configuration = await getConfiguration(settings);
|
|
131
|
+
const state = client.randomState();
|
|
132
|
+
const verifier = client.randomPKCECodeVerifier();
|
|
133
|
+
const nonce = client.randomNonce();
|
|
134
|
+
const url = client.buildAuthorizationUrl(configuration, {
|
|
135
|
+
redirect_uri: redirectUri,
|
|
136
|
+
response_type: "code",
|
|
137
|
+
response_mode: "query",
|
|
138
|
+
scope: settings.scopes,
|
|
139
|
+
state,
|
|
140
|
+
nonce,
|
|
141
|
+
code_challenge: await client.calculatePKCECodeChallenge(verifier),
|
|
142
|
+
code_challenge_method: "S256"
|
|
143
|
+
});
|
|
144
|
+
return { url: url.href, state, verifier, nonce };
|
|
145
|
+
}
|
|
146
|
+
export async function completeOidcLogin(settings, callbackUrl, transaction, redirectUri) {
|
|
147
|
+
try {
|
|
148
|
+
const expectedRedirect = validateRedirectUri(redirectUri);
|
|
149
|
+
if (callbackUrl.origin !== expectedRedirect.origin || callbackUrl.pathname !== expectedRedirect.pathname
|
|
150
|
+
|| callbackUrl.username || callbackUrl.password || callbackUrl.hash || expectedRedirect.search || expectedRedirect.hash
|
|
151
|
+
|| !transaction.state || !transaction.nonce || !/^[A-Za-z0-9._~-]{43,128}$/.test(transaction.verifier)) {
|
|
152
|
+
throw new Error("Invalid OIDC callback transaction.");
|
|
153
|
+
}
|
|
154
|
+
const configuration = await getConfiguration(settings);
|
|
155
|
+
const tokens = await client.authorizationCodeGrant(configuration, callbackUrl, {
|
|
156
|
+
pkceCodeVerifier: transaction.verifier,
|
|
157
|
+
expectedState: transaction.state,
|
|
158
|
+
expectedNonce: transaction.nonce,
|
|
159
|
+
idTokenExpected: true
|
|
160
|
+
});
|
|
161
|
+
const claims = tokens.claims();
|
|
162
|
+
if (!claims || claims.iss !== settings.issuer || typeof claims.sub !== "string" || !claims.sub
|
|
163
|
+
|| (claims.azp !== undefined && claims.azp !== settings.clientId)) {
|
|
164
|
+
throw new Error("Invalid OIDC identity.");
|
|
165
|
+
}
|
|
166
|
+
const scopes = new Set(settings.scopes.split(" "));
|
|
167
|
+
const needsEmail = scopes.has("email") && (typeof claims.email !== "string" || typeof claims.email_verified !== "boolean");
|
|
168
|
+
const needsProfile = scopes.has("profile") && (typeof claims.name !== "string" || typeof claims.picture !== "string");
|
|
169
|
+
let userInfo = {};
|
|
170
|
+
if (configuration.serverMetadata().userinfo_endpoint && (needsEmail || needsProfile)) {
|
|
171
|
+
// The library checks that UserInfo's subject exactly matches the verified ID token.
|
|
172
|
+
userInfo = await client.fetchUserInfo(configuration, tokens.access_token, claims.sub);
|
|
173
|
+
}
|
|
174
|
+
// Keep email and its verification flag from the same response; neither grants account ownership.
|
|
175
|
+
const emailClaims = typeof userInfo.email === "string" ? userInfo : claims;
|
|
176
|
+
return {
|
|
177
|
+
issuer: claims.iss,
|
|
178
|
+
subject: claims.sub,
|
|
179
|
+
profile: {
|
|
180
|
+
email: optionalString(emailClaims.email),
|
|
181
|
+
emailVerified: emailClaims.email_verified === true,
|
|
182
|
+
displayName: optionalString(userInfo.name) ?? optionalString(claims.name) ?? optionalString(userInfo.preferred_username) ?? optionalString(claims.preferred_username),
|
|
183
|
+
pictureUrl: optionalString(userInfo.picture) ?? optionalString(claims.picture)
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
throw new Error("OIDC sign-in failed. Start a new sign-in attempt.");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function optionalString(value) {
|
|
192
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=oidc.js.map
|
package/dist/oidc.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc.js","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Request } from "express";
|
|
2
|
+
interface BaseUrlOptions {
|
|
3
|
+
publicBaseUrl: string | undefined;
|
|
4
|
+
requestBaseUrl: string;
|
|
5
|
+
}
|
|
6
|
+
interface DraftUrlOptions extends BaseUrlOptions {
|
|
7
|
+
draftId: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function getRequestBaseUrl(req: Request): string;
|
|
10
|
+
export declare function getHomeUrl({ publicBaseUrl, requestBaseUrl }: BaseUrlOptions): string;
|
|
11
|
+
export declare function getDraftPublicUrl({ draftId, publicBaseUrl, requestBaseUrl }: DraftUrlOptions): string;
|
|
12
|
+
export declare function getDraftRawUrl({ draftId, publicBaseUrl, requestBaseUrl }: DraftUrlOptions): string;
|
|
13
|
+
export declare function getDraftIdFromHost({ publicBaseUrl, host }: {
|
|
14
|
+
publicBaseUrl: string | undefined;
|
|
15
|
+
host: string | undefined;
|
|
16
|
+
}): string | null;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const DRAFT_ID_PATTERN = /^[a-z0-9]{12}$/;
|
|
2
|
+
export function getRequestBaseUrl(req) {
|
|
3
|
+
const forwardedProto = req.get("x-forwarded-proto");
|
|
4
|
+
const protocol = forwardedProto
|
|
5
|
+
? forwardedProto.split(",")[0].trim()
|
|
6
|
+
: req.protocol || "http";
|
|
7
|
+
return `${protocol}://${req.get("host")}`;
|
|
8
|
+
}
|
|
9
|
+
export function getHomeUrl({ publicBaseUrl, requestBaseUrl }) {
|
|
10
|
+
const configured = normalizeUrl(publicBaseUrl);
|
|
11
|
+
const wildcard = parseWildcardBaseUrl(configured);
|
|
12
|
+
if (wildcard) {
|
|
13
|
+
wildcard.hostname = wildcard.hostname.slice(2);
|
|
14
|
+
wildcard.pathname = "/";
|
|
15
|
+
wildcard.search = "";
|
|
16
|
+
wildcard.hash = "";
|
|
17
|
+
return stripTrailingSlash(wildcard.toString());
|
|
18
|
+
}
|
|
19
|
+
return configured || normalizeUrl(requestBaseUrl);
|
|
20
|
+
}
|
|
21
|
+
export function getDraftPublicUrl({ draftId, publicBaseUrl, requestBaseUrl }) {
|
|
22
|
+
const configured = normalizeUrl(publicBaseUrl);
|
|
23
|
+
const wildcard = parseWildcardBaseUrl(configured);
|
|
24
|
+
if (wildcard) {
|
|
25
|
+
wildcard.hostname = `${draftId}.${wildcard.hostname.slice(2)}`;
|
|
26
|
+
wildcard.pathname = "/";
|
|
27
|
+
wildcard.search = "";
|
|
28
|
+
wildcard.hash = "";
|
|
29
|
+
return stripTrailingSlash(wildcard.toString());
|
|
30
|
+
}
|
|
31
|
+
const baseUrl = configured || normalizeUrl(requestBaseUrl);
|
|
32
|
+
return `${baseUrl}/d/${draftId}`;
|
|
33
|
+
}
|
|
34
|
+
export function getDraftRawUrl({ draftId, publicBaseUrl, requestBaseUrl }) {
|
|
35
|
+
const configured = normalizeUrl(publicBaseUrl);
|
|
36
|
+
const wildcard = parseWildcardBaseUrl(configured);
|
|
37
|
+
if (wildcard) {
|
|
38
|
+
wildcard.hostname = wildcard.hostname.slice(2);
|
|
39
|
+
wildcard.pathname = `/d/${draftId}/raw`;
|
|
40
|
+
wildcard.search = "";
|
|
41
|
+
wildcard.hash = "";
|
|
42
|
+
return wildcard.toString();
|
|
43
|
+
}
|
|
44
|
+
const baseUrl = configured || normalizeUrl(requestBaseUrl);
|
|
45
|
+
return `${baseUrl}/d/${draftId}/raw`;
|
|
46
|
+
}
|
|
47
|
+
export function getDraftIdFromHost({ publicBaseUrl, host }) {
|
|
48
|
+
const wildcard = parseWildcardBaseUrl(publicBaseUrl);
|
|
49
|
+
if (!wildcard)
|
|
50
|
+
return null;
|
|
51
|
+
const rootHost = wildcard.hostname.slice(2).toLowerCase();
|
|
52
|
+
const requestHost = parseHost(host);
|
|
53
|
+
if (!requestHost || !requestHost.endsWith(`.${rootHost}`))
|
|
54
|
+
return null;
|
|
55
|
+
const draftId = requestHost.slice(0, -(rootHost.length + 1));
|
|
56
|
+
if (draftId.includes(".") || !DRAFT_ID_PATTERN.test(draftId))
|
|
57
|
+
return null;
|
|
58
|
+
return draftId;
|
|
59
|
+
}
|
|
60
|
+
function parseWildcardBaseUrl(value) {
|
|
61
|
+
const url = parseUrl(value);
|
|
62
|
+
if (!url || !url.hostname.startsWith("*."))
|
|
63
|
+
return null;
|
|
64
|
+
return url;
|
|
65
|
+
}
|
|
66
|
+
function parseHost(value) {
|
|
67
|
+
const normalized = String(value || "").trim();
|
|
68
|
+
if (!normalized)
|
|
69
|
+
return null;
|
|
70
|
+
try {
|
|
71
|
+
return new URL(`http://${normalized}`).hostname.toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function parseUrl(value) {
|
|
78
|
+
const normalized = normalizeUrl(value);
|
|
79
|
+
if (!normalized)
|
|
80
|
+
return null;
|
|
81
|
+
try {
|
|
82
|
+
return new URL(normalized);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function normalizeUrl(value) {
|
|
89
|
+
if (typeof value !== "string")
|
|
90
|
+
return "";
|
|
91
|
+
return value.trim().replace(/\/+$/, "");
|
|
92
|
+
}
|
|
93
|
+
function stripTrailingSlash(value) {
|
|
94
|
+
return value.replace(/\/+$/, "");
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=public-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-url.js","sourceRoot":"","sources":["../src/public-url.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Request, RequestHandler } from "express";
|
|
2
|
+
interface RateLimiterOptions {
|
|
3
|
+
windowMs: number;
|
|
4
|
+
max: number;
|
|
5
|
+
keyPrefix: string;
|
|
6
|
+
key?: (req: Request) => string;
|
|
7
|
+
}
|
|
8
|
+
export declare class RateLimitStore {
|
|
9
|
+
private readonly capacity;
|
|
10
|
+
private readonly clock;
|
|
11
|
+
private readonly buckets;
|
|
12
|
+
private nextSweepAt;
|
|
13
|
+
constructor(capacity?: number, clock?: () => number);
|
|
14
|
+
get size(): number;
|
|
15
|
+
check(bucketKey: string, windowMs: number, max: number): {
|
|
16
|
+
allowed: boolean;
|
|
17
|
+
retryAfter: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare function checkRateLimit(bucketKey: string, windowMs: number, max: number): {
|
|
21
|
+
allowed: boolean;
|
|
22
|
+
retryAfter: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function createRateLimiter({ windowMs, max, keyPrefix, key }: RateLimiterOptions): RequestHandler;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Single-process protection: expired entries are swept on traffic, cardinality
|
|
2
|
+
// is capped, and capacity pressure never evicts a live bucket to reset its limit.
|
|
3
|
+
export class RateLimitStore {
|
|
4
|
+
capacity;
|
|
5
|
+
clock;
|
|
6
|
+
buckets = new Map();
|
|
7
|
+
nextSweepAt = 0;
|
|
8
|
+
constructor(capacity = 10_000, clock = Date.now) {
|
|
9
|
+
this.capacity = capacity;
|
|
10
|
+
this.clock = clock;
|
|
11
|
+
if (!Number.isInteger(capacity) || capacity < 1)
|
|
12
|
+
throw new Error("Rate-limit capacity must be positive.");
|
|
13
|
+
}
|
|
14
|
+
get size() { return this.buckets.size; }
|
|
15
|
+
check(bucketKey, windowMs, max) {
|
|
16
|
+
if (bucketKey.length > 1024 || !Number.isFinite(windowMs) || windowMs <= 0 ||
|
|
17
|
+
!Number.isInteger(max) || max < 1)
|
|
18
|
+
return { allowed: false, retryAfter: 1 };
|
|
19
|
+
const now = this.clock();
|
|
20
|
+
if (now >= this.nextSweepAt) {
|
|
21
|
+
for (const [key, bucket] of this.buckets)
|
|
22
|
+
if (bucket.resetAt <= now)
|
|
23
|
+
this.buckets.delete(key);
|
|
24
|
+
this.nextSweepAt = now + 1000;
|
|
25
|
+
}
|
|
26
|
+
const current = this.buckets.get(bucketKey);
|
|
27
|
+
if (!current || current.resetAt <= now) {
|
|
28
|
+
if (!current && this.buckets.size >= this.capacity)
|
|
29
|
+
return { allowed: false, retryAfter: 1 };
|
|
30
|
+
this.buckets.set(bucketKey, { count: 1, resetAt: now + windowMs });
|
|
31
|
+
return { allowed: true, retryAfter: 0 };
|
|
32
|
+
}
|
|
33
|
+
current.count += 1;
|
|
34
|
+
return current.count > max
|
|
35
|
+
? { allowed: false, retryAfter: Math.ceil((current.resetAt - now) / 1000) }
|
|
36
|
+
: { allowed: true, retryAfter: 0 };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const store = new RateLimitStore();
|
|
40
|
+
export function checkRateLimit(bucketKey, windowMs, max) {
|
|
41
|
+
return store.check(bucketKey, windowMs, max);
|
|
42
|
+
}
|
|
43
|
+
export function createRateLimiter({ windowMs, max, keyPrefix, key }) {
|
|
44
|
+
return function rateLimiter(req, res, next) {
|
|
45
|
+
const identity = key ? key(req) : req.auth?.id || req.ip || "anonymous";
|
|
46
|
+
const bucketKey = `${keyPrefix}:${identity}`;
|
|
47
|
+
const result = checkRateLimit(bucketKey, windowMs, max);
|
|
48
|
+
if (!result.allowed) {
|
|
49
|
+
res.setHeader("Retry-After", String(result.retryAfter));
|
|
50
|
+
return res.status(429).json({ ok: false, error: "Upload rate limit exceeded." });
|
|
51
|
+
}
|
|
52
|
+
next();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAcA,+EAA+E;AAC/E,kFAAkF;AAClF,MAAM,OAAO,cAAc;IAII;IAAoC;IAHhD,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IACtD,WAAW,GAAG,CAAC,CAAC;IAExB,YAA6B,WAAW,MAAM,EAAmB,QAAQ,IAAI,CAAC,GAAG;QAApD,aAAQ,GAAR,QAAQ,CAAS;QAAmB,UAAK,GAAL,KAAK,CAAW;QAC/E,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC5G,CAAC;IAED,IAAI,IAAI,KAAa,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhD,KAAK,CAAC,SAAiB,EAAE,QAAgB,EAAE,GAAW;QACpD,IAAI,SAAS,CAAC,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC;YACtE,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QAChF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC5B,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO;gBAAE,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG;oBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC9F,IAAI,CAAC,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC;QAChC,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;YACvC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ;gBAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YAC7F,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAC,CAAC;YACnE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QAC1C,CAAC;QACD,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;QACnB,OAAO,OAAO,CAAC,KAAK,GAAG,GAAG;YACxB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE;YAC3E,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IACvC,CAAC;CACF;AAED,MAAM,KAAK,GAAG,IAAI,cAAc,EAAE,CAAC;AAEnC,MAAM,UAAU,cAAc,CAC5B,SAAiB,EACjB,QAAgB,EAChB,GAAW;IAEX,OAAO,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAsB;IACrF,OAAO,SAAS,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;QACxC,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,WAAW,CAAC;QACxE,MAAM,SAAS,GAAG,GAAG,SAAS,IAAI,QAAQ,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YACxD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { reconcilePendingUploads } from "./upload-state.js";
|
|
2
|
+
let running = false;
|
|
3
|
+
export async function runReconciliation() {
|
|
4
|
+
if (running)
|
|
5
|
+
return { finalized: 0, failed: 0, pending: 0 };
|
|
6
|
+
running = true;
|
|
7
|
+
try {
|
|
8
|
+
const result = await reconcilePendingUploads();
|
|
9
|
+
if (result.finalized || result.failed || result.pending) {
|
|
10
|
+
console.log("openplan reconciliation completed", result);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
running = false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,IAAI,OAAO,GAAG,KAAK,CAAC;AAEpB,MAAM,CAAC,KAAK,UAAU,iBAAiB;IAKrC,IAAI,OAAO;QAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5D,OAAO,GAAG,IAAI,CAAC;IACf,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,uBAAuB,EAAE,CAAC;QAC/C,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACxD,OAAO,CAAC,GAAG,CAAC,mCAAmC,EAAE,MAAM,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,OAAO,GAAG,KAAK,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface AnnotationViewerInput {
|
|
2
|
+
title: string;
|
|
3
|
+
frameSrc: string;
|
|
4
|
+
draftId: string;
|
|
5
|
+
versionId: string;
|
|
6
|
+
versionNumber: number;
|
|
7
|
+
contentHash: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function renderAnnotationViewer(input: AnnotationViewerInput): string;
|
|
10
|
+
export declare function annotationViewerContentSecurityPolicy(): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export function renderAnnotationViewer(input) {
|
|
2
|
+
return `<!doctype html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<title>Annotate — ${escapeHtml(input.title)}</title>
|
|
8
|
+
<style>
|
|
9
|
+
* { box-sizing: border-box; }
|
|
10
|
+
html, body { height: 100%; }
|
|
11
|
+
body { margin: 0; display: grid; grid-template-rows: auto 1fr; background: #0f172a; color: #e2e8f0; font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
12
|
+
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; padding: 9px 12px; background: #111827; border-bottom: 1px solid #334155; box-shadow: 0 4px 18px rgba(0,0,0,.22); z-index: 2; }
|
|
13
|
+
.toolbar strong { margin-right: 6px; }
|
|
14
|
+
button, .export { border: 1px solid #475569; border-radius: 7px; padding: 7px 10px; background: #1e293b; color: #e2e8f0; cursor: pointer; font: inherit; font-size: 13px; }
|
|
15
|
+
button:hover, button.active { background: #0f766e; border-color: #2dd4bf; }
|
|
16
|
+
button.danger:hover { background: #991b1b; border-color: #f87171; }
|
|
17
|
+
label { display: inline-flex; align-items: center; gap: 5px; color: #cbd5e1; font-size: 12px; }
|
|
18
|
+
input[type="color"] { width: 34px; height: 30px; padding: 1px; border: 0; background: transparent; }
|
|
19
|
+
input[type="range"] { width: 86px; }
|
|
20
|
+
.status { flex: 1 1 260px; margin: 0 4px; color: #94a3b8; font-size: 12px; text-align: right; }
|
|
21
|
+
.privacy { flex: 1 1 100%; color: #cbd5e1; font-size: 12px; }
|
|
22
|
+
.privacy summary { width: fit-content; color: #99f6e4; cursor: pointer; }
|
|
23
|
+
.privacy p { max-width: 980px; margin: 7px 0 0; line-height: 1.45; }
|
|
24
|
+
iframe { width: 100%; height: 100%; border: 0; background: white; }
|
|
25
|
+
@media (max-width: 720px) { .status { order: 2; flex-basis: 100%; text-align: left; } button { padding: 7px 8px; } }
|
|
26
|
+
</style>
|
|
27
|
+
<script src="/assets/openplan-annotations.v1.js" defer></script>
|
|
28
|
+
</head>
|
|
29
|
+
<body data-draft-id="${escapeHtml(input.draftId)}" data-version-id="${escapeHtml(input.versionId)}" data-version-number="${input.versionNumber}" data-content-hash="${escapeHtml(input.contentHash)}">
|
|
30
|
+
<header class="toolbar" id="openplan-annotation-toolbar">
|
|
31
|
+
<strong>Annotate</strong>
|
|
32
|
+
<button type="button" data-action="pan">Browse</button>
|
|
33
|
+
<button type="button" data-action="pen">Pen</button>
|
|
34
|
+
<button type="button" data-action="highlighter">Marker</button>
|
|
35
|
+
<button type="button" data-action="text">Text highlight</button>
|
|
36
|
+
<label>Color <input id="openplan-annotation-color" type="color" value="#ef4444" aria-label="Annotation color"></label>
|
|
37
|
+
<label>Width <input id="openplan-annotation-width" type="range" min="1" max="16" value="4" aria-label="Stroke width"></label>
|
|
38
|
+
<button type="button" data-action="undo">Undo</button>
|
|
39
|
+
<button type="button" data-action="redo">Redo</button>
|
|
40
|
+
<button type="button" data-action="visibility">Show / hide</button>
|
|
41
|
+
<button type="button" data-action="export-svg">Export SVG</button>
|
|
42
|
+
<button type="button" data-action="export-json">Export JSON</button>
|
|
43
|
+
<button class="danger" type="button" data-action="clear">Clear</button>
|
|
44
|
+
<p class="status" id="openplan-annotation-status" aria-live="polite">Loading plan…</p>
|
|
45
|
+
<details class="privacy">
|
|
46
|
+
<summary>Local-only storage, loss, and recovery</summary>
|
|
47
|
+
<p>Annotations stay in this browser profile and do not follow you to another browser or device. Clearing site data, private browsing, changing browser profiles, or uninstalling the browser can remove them. Openplan support and plan owners cannot recover lost annotations. You are responsible for keeping any exported files.</p>
|
|
48
|
+
</details>
|
|
49
|
+
</header>
|
|
50
|
+
<iframe id="openplan-plan-frame" src="${escapeHtml(input.frameSrc)}" title="${escapeHtml(input.title)}" sandbox="allow-same-origin"></iframe>
|
|
51
|
+
</body>
|
|
52
|
+
</html>`;
|
|
53
|
+
}
|
|
54
|
+
export function annotationViewerContentSecurityPolicy() {
|
|
55
|
+
return [
|
|
56
|
+
"default-src 'none'",
|
|
57
|
+
"script-src 'self'",
|
|
58
|
+
"style-src 'unsafe-inline'",
|
|
59
|
+
"frame-src 'self'",
|
|
60
|
+
"img-src 'self' data:",
|
|
61
|
+
"connect-src 'none'",
|
|
62
|
+
"base-uri 'none'",
|
|
63
|
+
"form-action 'none'"
|
|
64
|
+
].join("; ");
|
|
65
|
+
}
|
|
66
|
+
function escapeHtml(value) {
|
|
67
|
+
return String(value)
|
|
68
|
+
.replaceAll("&", "&")
|
|
69
|
+
.replaceAll("<", "<")
|
|
70
|
+
.replaceAll(">", ">")
|
|
71
|
+
.replaceAll('"', """);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=render-annotations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-annotations.js","sourceRoot":"","sources":["../src/render-annotations.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,sBAAsB,CAAC,KAA4B;IACjE,OAAO;;;;;sBAKa,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;uBAsBtB,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,sBAAsB,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,0BAA0B,KAAK,CAAC,aAAa,wBAAwB,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;0CAqBzJ,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;;QAE/F,CAAC;AACT,CAAC;AAED,MAAM,UAAU,qCAAqC;IACnD,OAAO;QACL,oBAAoB;QACpB,mBAAmB;QACnB,2BAA2B;QAC3B,kBAAkB;QAClB,sBAAsB;QACtB,oBAAoB;QACpB,iBAAiB;QACjB,oBAAoB;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,MAAM,CAAC,KAAK,CAAC;SACjB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { DraftDetailResult, DraftSummary, DraftVersion, DateValue } from "./drafts.js";
|
|
2
|
+
import type { AccessPolicyView } from "./access-policy.js";
|
|
3
|
+
import type { LocalUserSummary } from "./local-auth.js";
|
|
4
|
+
export interface SessionView {
|
|
5
|
+
accountId?: string;
|
|
6
|
+
accountName?: string;
|
|
7
|
+
email?: string | null;
|
|
8
|
+
pictureUrl?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ApiKeyView {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
created_at: DateValue;
|
|
14
|
+
last_used_at: DateValue | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function renderSignIn({ next, provider, error, message }: {
|
|
17
|
+
next?: string;
|
|
18
|
+
provider?: "shoo" | "local" | "oidc";
|
|
19
|
+
error?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
}): string;
|
|
22
|
+
export declare function renderAccountSecurity({ session, local, message, error, isAdmin }: {
|
|
23
|
+
session: SessionView;
|
|
24
|
+
local: boolean;
|
|
25
|
+
message?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
isAdmin?: boolean;
|
|
28
|
+
}): string;
|
|
29
|
+
export declare function renderAdminUsers({ session, users, error }: {
|
|
30
|
+
session: SessionView;
|
|
31
|
+
users: LocalUserSummary[];
|
|
32
|
+
error?: string;
|
|
33
|
+
}): string;
|
|
34
|
+
export declare function renderAuthError({ message }: {
|
|
35
|
+
message: string;
|
|
36
|
+
}): string;
|
|
37
|
+
export declare function renderSharePassword({ title, action, error }: {
|
|
38
|
+
title: string;
|
|
39
|
+
action: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
}): string;
|
|
42
|
+
export declare function renderDashboard({ session, drafts, isAdmin }: {
|
|
43
|
+
session: SessionView;
|
|
44
|
+
drafts: DraftSummary[];
|
|
45
|
+
isAdmin?: boolean;
|
|
46
|
+
}): string;
|
|
47
|
+
export declare function renderAdminFeatures({ session, enabled, updatedAt, localUsers }: {
|
|
48
|
+
session: SessionView;
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
updatedAt: DateValue | null;
|
|
51
|
+
localUsers?: boolean;
|
|
52
|
+
}): string;
|
|
53
|
+
export declare function renderDraftDetail({ session, draft, versions, access, annotationsEnabled, isAdmin }: {
|
|
54
|
+
session: SessionView;
|
|
55
|
+
draft: DraftDetailResult["draft"];
|
|
56
|
+
versions: DraftVersion[];
|
|
57
|
+
access?: {
|
|
58
|
+
plan: AccessPolicyView;
|
|
59
|
+
versions: Map<number, AccessPolicyView>;
|
|
60
|
+
};
|
|
61
|
+
annotationsEnabled?: boolean;
|
|
62
|
+
isAdmin?: boolean;
|
|
63
|
+
}): string;
|
|
64
|
+
export declare function renderCliAuth({ session, keys, isAdmin }: {
|
|
65
|
+
session: SessionView;
|
|
66
|
+
keys?: ApiKeyView[];
|
|
67
|
+
isAdmin?: boolean;
|
|
68
|
+
}): string;
|
|
69
|
+
export declare function renderCliAuthKey({ session, token, keyName, isAdmin }: {
|
|
70
|
+
session: SessionView;
|
|
71
|
+
token: string;
|
|
72
|
+
keyName: string;
|
|
73
|
+
isAdmin?: boolean;
|
|
74
|
+
}): string;
|