@hazem3500/komo 0.9.0 → 0.9.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/README.md +100 -0
- package/dist/{accent-picker-F5UI2NP7.js → accent-picker-OADSUS4S.js} +1 -1
- package/dist/adapters/class-theme.d.ts +22 -0
- package/dist/adapters/class-theme.js +1 -0
- package/dist/adapters/tailwind.d.ts +62 -0
- package/dist/adapters/tailwind.js +1 -0
- package/dist/{chunk-YZP3KX4Y.js → chunk-62YUEO4G.js} +1 -1
- package/dist/chunk-HCGLCO3R.js +1 -0
- package/dist/chunk-JXIYWNFL.js +1 -0
- package/dist/{chunk-Q7WSWLMV.js → chunk-KMYXE4O6.js} +1 -1
- package/dist/design-comment-MWNQNKWT.js +1 -0
- package/dist/design-comment.d.ts +11 -0
- package/dist/design-export-ZHCBPSWY.js +1 -0
- package/dist/design-export.d.ts +5 -0
- package/dist/design-mode-styles.d.ts +1 -1
- package/dist/design-mode.d.ts +5 -0
- package/dist/design-mode.js +1 -1
- package/dist/design-system.d.ts +154 -0
- package/dist/design-tokens.d.ts +12 -0
- package/dist/dom.d.ts +2 -0
- package/dist/emoji-sequence.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/inline-text-editor-3JLDAUPO.js +1 -0
- package/dist/inline-text-editor.d.ts +3 -0
- package/dist/node.mjs +1 -1
- package/dist/{onboarding-NO3RQWR7.js → onboarding-YBSYCJZH.js} +1 -1
- package/dist/react-inspector.d.ts +9 -1
- package/dist/react-props-panel-R5XEVATL.js +1 -0
- package/dist/react-props-panel.d.ts +9 -2
- package/dist/react.d.ts +2 -0
- package/dist/react.js +1 -1
- package/dist/review-layout.d.ts +3 -1
- package/dist/setup.js +1 -1
- package/dist/tag-editor-KGH3ZCAT.js +1 -0
- package/dist/token-adapter.d.ts +44 -0
- package/dist/{toolbar-runtime-2SIUE6SQ.js → toolbar-runtime-XA72XTPD.js} +1 -1
- package/dist/types.d.ts +7 -0
- package/package.json +11 -2
- package/server/iap.ts +133 -0
- package/server/setup-client.txt +142 -75
- package/src/types.ts +8 -0
- package/dist/chunk-JQVBZ2BB.js +0 -1
- package/dist/chunk-Z2ABBESO.js +0 -1
- package/dist/design-comment-QPTW66JO.js +0 -1
- package/dist/design-export-BRBI7S3V.js +0 -1
- package/dist/inline-text-editor-F7B6FQQ3.js +0 -1
- package/dist/react-props-panel-XSMDR2CY.js +0 -1
- package/dist/tag-editor-VDPZ6CO3.js +0 -1
package/server/iap.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Google Cloud Identity-Aware Proxy (IAP) sign-in. When KOMO_IAP_AUDIENCE names
|
|
2
|
+
// the IAP-protected backend (/projects/<number>/global/backendServices/<id>),
|
|
3
|
+
// the load balancer routes the sign-in paths through IAP. Sessions are minted
|
|
4
|
+
// from IAP's signed identity instead of a Google OAuth client, and reading
|
|
5
|
+
// comments requires one of those sessions.
|
|
6
|
+
import { check, HttpError } from "./validation";
|
|
7
|
+
|
|
8
|
+
const KEYS_URL = "https://www.gstatic.com/iap/verify/public_key-jwk";
|
|
9
|
+
const ISSUER = "https://cloud.google.com/iap";
|
|
10
|
+
let cached: { keys: Map<string, CryptoKey>; expires: number } | undefined;
|
|
11
|
+
|
|
12
|
+
async function keys(refresh = false) {
|
|
13
|
+
if (!refresh && cached && cached.expires > Date.now()) return cached.keys;
|
|
14
|
+
const response = await fetch(KEYS_URL);
|
|
15
|
+
check(response.ok, 503, "Could not verify sign-in. Try again shortly.");
|
|
16
|
+
const { keys: list } = (await response.json()) as {
|
|
17
|
+
keys?: (JsonWebKey & { kid?: string })[];
|
|
18
|
+
};
|
|
19
|
+
const imported = new Map<string, CryptoKey>();
|
|
20
|
+
for (const { kid, ...key } of list ?? [])
|
|
21
|
+
if (kid && key.kty === "EC" && key.crv === "P-256")
|
|
22
|
+
imported.set(
|
|
23
|
+
kid,
|
|
24
|
+
await crypto.subtle.importKey(
|
|
25
|
+
"jwk",
|
|
26
|
+
key,
|
|
27
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
28
|
+
false,
|
|
29
|
+
["verify"],
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
cached = { keys: imported, expires: Date.now() + 3600000 };
|
|
33
|
+
return imported;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const decode = (part: string) =>
|
|
37
|
+
Uint8Array.from(atob(part.replace(/-/g, "+").replace(/_/g, "/")), (c) =>
|
|
38
|
+
c.charCodeAt(0),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export type IapIdentity = { id: string; email: string };
|
|
42
|
+
|
|
43
|
+
/** Verify IAP's signed header and return the Google account it vouches for. */
|
|
44
|
+
export async function iapIdentity(
|
|
45
|
+
request: Request,
|
|
46
|
+
audience: string,
|
|
47
|
+
): Promise<IapIdentity> {
|
|
48
|
+
const jwt = request.headers.get("x-goog-iap-jwt-assertion") ?? "";
|
|
49
|
+
const [head, payload, signature, extra] = jwt.split(".");
|
|
50
|
+
check(
|
|
51
|
+
head && payload && signature && extra === undefined,
|
|
52
|
+
401,
|
|
53
|
+
"Sign in with your Google account.",
|
|
54
|
+
);
|
|
55
|
+
let header: { alg?: unknown; kid?: unknown };
|
|
56
|
+
let claims: Record<string, unknown>;
|
|
57
|
+
try {
|
|
58
|
+
header = JSON.parse(new TextDecoder().decode(decode(head)));
|
|
59
|
+
claims = JSON.parse(new TextDecoder().decode(decode(payload)));
|
|
60
|
+
} catch {
|
|
61
|
+
throw new HttpError(401, "Invalid sign-in.");
|
|
62
|
+
}
|
|
63
|
+
check(
|
|
64
|
+
header.alg === "ES256" && typeof header.kid === "string",
|
|
65
|
+
401,
|
|
66
|
+
"Invalid sign-in.",
|
|
67
|
+
);
|
|
68
|
+
const kid = header.kid;
|
|
69
|
+
// IAP rotates keys; refetch once before rejecting an unknown key id.
|
|
70
|
+
const key = (await keys()).get(kid) ?? (await keys(true)).get(kid);
|
|
71
|
+
check(key, 401, "Invalid sign-in.");
|
|
72
|
+
const valid = await crypto.subtle.verify(
|
|
73
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
74
|
+
key,
|
|
75
|
+
decode(signature),
|
|
76
|
+
new TextEncoder().encode(`${head}.${payload}`),
|
|
77
|
+
);
|
|
78
|
+
const now = Date.now() / 1000;
|
|
79
|
+
check(
|
|
80
|
+
valid &&
|
|
81
|
+
claims.iss === ISSUER &&
|
|
82
|
+
claims.aud === audience &&
|
|
83
|
+
typeof claims.exp === "number" &&
|
|
84
|
+
claims.exp > now &&
|
|
85
|
+
typeof claims.iat === "number" &&
|
|
86
|
+
claims.iat < now + 60,
|
|
87
|
+
401,
|
|
88
|
+
"Invalid sign-in.",
|
|
89
|
+
);
|
|
90
|
+
// IAP's subject is the same numeric Google account id the OAuth flow stores.
|
|
91
|
+
const sub =
|
|
92
|
+
typeof claims.sub === "string"
|
|
93
|
+
? claims.sub.match(/^accounts\.google\.com:(\d{1,40})$/)?.[1]
|
|
94
|
+
: undefined;
|
|
95
|
+
check(
|
|
96
|
+
sub &&
|
|
97
|
+
typeof claims.email === "string" &&
|
|
98
|
+
claims.email.length <= 254 &&
|
|
99
|
+
claims.email.includes("@"),
|
|
100
|
+
401,
|
|
101
|
+
"Invalid sign-in.",
|
|
102
|
+
);
|
|
103
|
+
return { id: `google:${sub}`, email: claims.email.toLowerCase() };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Path prefix for IAP sign-in handlers, e.g. "/backoffice"; "" when unset. */
|
|
107
|
+
export function authPrefix(value: string | undefined): string {
|
|
108
|
+
if (!value) return "";
|
|
109
|
+
const prefix = value.replace(/\/+$/, "");
|
|
110
|
+
if (!/^(\/[A-Za-z0-9._~-]+)+$/.test(prefix))
|
|
111
|
+
throw new Error("KOMO_AUTH_PREFIX must be a path such as /backoffice");
|
|
112
|
+
return prefix;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Split PUBLIC_URL into its origin and an optional path the app is served under. */
|
|
116
|
+
export function publicLocation(value: string): { origin: string; path: string } {
|
|
117
|
+
const url = new URL(value);
|
|
118
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
119
|
+
if (
|
|
120
|
+
url.search ||
|
|
121
|
+
url.hash ||
|
|
122
|
+
url.username ||
|
|
123
|
+
url.password ||
|
|
124
|
+
(path && !/^(\/[A-Za-z0-9._~-]+)+$/.test(path)) ||
|
|
125
|
+
`${url.origin}${path}` !== value.replace(/\/+$/, "") ||
|
|
126
|
+
(url.protocol !== "https:" &&
|
|
127
|
+
!["localhost", "127.0.0.1"].includes(url.hostname))
|
|
128
|
+
)
|
|
129
|
+
throw new Error(
|
|
130
|
+
"PUBLIC_URL must be an HTTPS origin, optionally with a path such as https://example.com/komo (localhost allowed for development)",
|
|
131
|
+
);
|
|
132
|
+
return { origin: url.origin, path };
|
|
133
|
+
}
|