@hazem3500/komo 0.8.0 → 0.9.1
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/NODE.md +20 -0
- package/README.md +64 -0
- package/dist/accent-picker-F5UI2NP7.js +1 -0
- 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-5LHGJFWQ.js +1 -0
- package/dist/chunk-JC6FXUNO.js +1 -0
- package/dist/{chunk-RPKLGTEU.js → chunk-Q7WSWLMV.js} +1 -1
- package/dist/{chunk-E5X4IQJ2.js → chunk-YZP3KX4Y.js} +1 -1
- package/dist/design-comment-RTVUFVDE.js +1 -0
- package/dist/design-comment.d.ts +50 -0
- package/dist/design-mode-styles.d.ts +1 -1
- package/dist/design-mode.d.ts +2 -0
- package/dist/design-mode.js +1 -1
- package/dist/design-system.d.ts +150 -0
- package/dist/design-tokens.d.ts +2 -0
- package/dist/dom.d.ts +10 -0
- package/dist/index.js +1 -1
- package/dist/inline-text-editor-SWPVB35B.js +1 -0
- package/dist/inline-text-editor.d.ts +3 -0
- package/dist/node.mjs +218 -65
- package/dist/{onboarding-YA63RUKK.js → onboarding-NO3RQWR7.js} +1 -1
- package/dist/react-inspector.d.ts +9 -1
- package/dist/react-props-panel-XQ4T3RLV.js +1 -0
- package/dist/react-props-panel.d.ts +3 -1
- package/dist/react.js +1 -1
- package/dist/review-layout.d.ts +3 -1
- package/dist/setup.js +1 -1
- package/dist/tag-editor-ZA7NLPOJ.js +1 -0
- package/dist/toolbar-runtime-2JO6YXD5.js +1 -0
- package/dist/types.d.ts +20 -0
- package/package.json +11 -2
- package/server/iap.ts +133 -0
- package/server/index.ts +108 -17
- package/server/secrets.d.ts +6 -0
- package/server/setup-client.txt +142 -70
- package/src/types.ts +21 -0
- package/dist/accent-picker-HSUCA4KE.js +0 -1
- package/dist/chunk-Z2ABBESO.js +0 -1
- package/dist/chunk-ZUNTYWJF.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/dist/toolbar-runtime-MYOLQPTJ.js +0 -1
package/server/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
requireMember,
|
|
6
6
|
} from "./project-management";
|
|
7
7
|
import { setupPage } from "./setup-page";
|
|
8
|
+
import { authPrefix, iapIdentity, type IapIdentity } from "./iap";
|
|
8
9
|
import setupClient from "./setup-client.txt";
|
|
9
10
|
import {
|
|
10
11
|
dispatchWebhook,
|
|
@@ -338,7 +339,7 @@ async function oauthAuthorize(
|
|
|
338
339
|
authorize.search = new URLSearchParams({
|
|
339
340
|
client_id:
|
|
340
341
|
provider === "google" ? env.GOOGLE_CLIENT_ID : env.GITHUB_CLIENT_ID,
|
|
341
|
-
redirect_uri: `${url.origin}/auth/${provider}/callback`,
|
|
342
|
+
redirect_uri: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}/auth/${provider}/callback`,
|
|
342
343
|
...(provider === "google"
|
|
343
344
|
? { response_type: "code", scope: "openid profile email" }
|
|
344
345
|
: {}),
|
|
@@ -413,7 +414,7 @@ async function oauthCallback(
|
|
|
413
414
|
: env.GITHUB_CLIENT_SECRET,
|
|
414
415
|
grant_type: "authorization_code",
|
|
415
416
|
code,
|
|
416
|
-
redirect_uri: `${url.origin}/auth/${provider}/callback`,
|
|
417
|
+
redirect_uri: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}/auth/${provider}/callback`,
|
|
417
418
|
code_verifier: saved.verifier,
|
|
418
419
|
}),
|
|
419
420
|
}
|
|
@@ -495,16 +496,23 @@ async function oauthCallback(
|
|
|
495
496
|
)
|
|
496
497
|
: undefined;
|
|
497
498
|
const accessToken = await session(env, created?.project ?? saved.project, id);
|
|
499
|
+
return signedInPage(
|
|
500
|
+
{
|
|
501
|
+
type: created ? "komo:setup" : "branch-comments:auth",
|
|
502
|
+
...(created ? { ...created, code: setupCode } : {}),
|
|
503
|
+
token: accessToken,
|
|
504
|
+
user,
|
|
505
|
+
},
|
|
506
|
+
saved.origin
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
// The popup posts the session back to the site that opened it, then closes.
|
|
510
|
+
function signedInPage(message: Record<string, unknown>, origin: string) {
|
|
498
511
|
const nonce = crypto.randomUUID();
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
...(created ? { ...created, code: setupCode } : {}),
|
|
502
|
-
token: accessToken,
|
|
503
|
-
user,
|
|
504
|
-
}).replace(/</g, "\\u003c");
|
|
505
|
-
const target = JSON.stringify(saved.origin).replace(/</g, "\\u003c");
|
|
512
|
+
const body = JSON.stringify(message).replace(/</g, "\\u003c");
|
|
513
|
+
const target = JSON.stringify(origin).replace(/</g, "\\u003c");
|
|
506
514
|
return new Response(
|
|
507
|
-
`<!doctype html><meta charset="utf-8"><title>Signed in</title><p>You’re signed in. You can close this window and return to your comments.</p><script nonce="${nonce}">if(window.opener){window.opener.postMessage(${
|
|
515
|
+
`<!doctype html><meta charset="utf-8"><title>Signed in</title><p>You’re signed in. You can close this window and return to your comments.</p><script nonce="${nonce}">if(window.opener){window.opener.postMessage(${body},${target});window.close();}</script>`,
|
|
508
516
|
{
|
|
509
517
|
headers: {
|
|
510
518
|
"Content-Type": "text/html; charset=utf-8",
|
|
@@ -516,6 +524,44 @@ async function oauthCallback(
|
|
|
516
524
|
}
|
|
517
525
|
);
|
|
518
526
|
}
|
|
527
|
+
// Records the IAP-verified Google account as a verified reviewer.
|
|
528
|
+
async function iapUser(env: Env, signedIn: IapIdentity) {
|
|
529
|
+
await env.DB.prepare(
|
|
530
|
+
"INSERT INTO users(id,name,verified) VALUES(?,?,1) ON CONFLICT(id) DO NOTHING"
|
|
531
|
+
)
|
|
532
|
+
.bind(signedIn.id, signedIn.email.split("@")[0].slice(0, 60))
|
|
533
|
+
.run();
|
|
534
|
+
await env.DB.prepare("UPDATE users SET email=?,verified=1 WHERE id=?")
|
|
535
|
+
.bind(signedIn.email, signedIn.id)
|
|
536
|
+
.run();
|
|
537
|
+
return identity(
|
|
538
|
+
(await env.DB.prepare("SELECT * FROM users WHERE id=?")
|
|
539
|
+
.bind(signedIn.id)
|
|
540
|
+
.first<UserRow>())!
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
// Behind IAP the authorize step is the whole sign-in: IAP has already
|
|
544
|
+
// authenticated the browser, so there is no Google redirect or callback.
|
|
545
|
+
async function iapSignIn(request: Request, env: Env) {
|
|
546
|
+
const url = new URL(request.url);
|
|
547
|
+
const state = string(url.searchParams.get("state"), 200, "state");
|
|
548
|
+
const signedIn = await iapIdentity(request, env.KOMO_IAP_AUDIENCE!);
|
|
549
|
+
const saved = await env.DB.prepare(
|
|
550
|
+
"DELETE FROM oauth_states WHERE state_hash=? AND expires_at>? AND provider=? RETURNING project,origin"
|
|
551
|
+
)
|
|
552
|
+
.bind(await hash(state), Date.now(), "google")
|
|
553
|
+
.first<{ project: string; origin: string }>();
|
|
554
|
+
check(saved, 400, "Sign-in expired. Return to the site and try again.");
|
|
555
|
+
const user = await iapUser(env, signedIn);
|
|
556
|
+
return signedInPage(
|
|
557
|
+
{
|
|
558
|
+
type: "branch-comments:auth",
|
|
559
|
+
token: await session(env, saved.project, user.id),
|
|
560
|
+
user,
|
|
561
|
+
},
|
|
562
|
+
saved.origin
|
|
563
|
+
);
|
|
564
|
+
}
|
|
519
565
|
async function route(
|
|
520
566
|
request: Request,
|
|
521
567
|
env: Env,
|
|
@@ -640,7 +686,7 @@ async function route(
|
|
|
640
686
|
)
|
|
641
687
|
.bind(id, await hash(secret), JSON.stringify(config), Date.now() + 600000)
|
|
642
688
|
.run();
|
|
643
|
-
return json({ id, secret, url: `${url.origin}/setup?code=${id}` });
|
|
689
|
+
return json({ id, secret, url: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}/setup?code=${id}` });
|
|
644
690
|
}
|
|
645
691
|
if (
|
|
646
692
|
url.pathname === "/setup/poll" &&
|
|
@@ -668,7 +714,7 @@ async function route(
|
|
|
668
714
|
? {
|
|
669
715
|
project: row.project,
|
|
670
716
|
repo: JSON.parse(row.config).repo,
|
|
671
|
-
endpoint: url.origin
|
|
717
|
+
endpoint: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}`,
|
|
672
718
|
}
|
|
673
719
|
: { pending: true }
|
|
674
720
|
);
|
|
@@ -676,6 +722,33 @@ async function route(
|
|
|
676
722
|
const oauthPath = url.pathname.match(
|
|
677
723
|
/^\/auth\/(github|google)\/(authorize|callback)$/
|
|
678
724
|
);
|
|
725
|
+
if (env.KOMO_IAP_AUDIENCE) {
|
|
726
|
+
// IAP sign-in replaces OAuth. Its handlers live only under KOMO_AUTH_PREFIX,
|
|
727
|
+
// the path the load balancer puts behind IAP.
|
|
728
|
+
const prefix = authPrefix(env.KOMO_AUTH_PREFIX);
|
|
729
|
+
const signInPath =
|
|
730
|
+
!prefix || url.pathname.startsWith(`${prefix}/auth/`)
|
|
731
|
+
? url.pathname.slice(prefix.length)
|
|
732
|
+
: "";
|
|
733
|
+
if (signInPath === "/auth/google/authorize") return iapSignIn(request, env);
|
|
734
|
+
// For agents: an IAP-authenticated caller exchanges its identity for a
|
|
735
|
+
// project session, the non-interactive counterpart of the popup.
|
|
736
|
+
if (signInPath === "/auth/iap/session" && request.method === "POST") {
|
|
737
|
+
const signedIn = await iapIdentity(request, env.KOMO_IAP_AUDIENCE);
|
|
738
|
+
const target = string(url.searchParams.get("project"), 100, "project");
|
|
739
|
+
check(await projectConfig(env, target), 404, "Unknown comments project.");
|
|
740
|
+
const user = await iapUser(env, signedIn);
|
|
741
|
+
return json({ token: await session(env, target, user.id), user }, 201);
|
|
742
|
+
}
|
|
743
|
+
check(!oauthPath || oauthPath[1] === "google", 403, "Sign in with your Google account.");
|
|
744
|
+
check(
|
|
745
|
+
!oauthPath &&
|
|
746
|
+
url.pathname !== "/auth/iap/session" &&
|
|
747
|
+
!(prefix && url.pathname.startsWith(`${prefix}/`)),
|
|
748
|
+
404,
|
|
749
|
+
"Not found."
|
|
750
|
+
);
|
|
751
|
+
}
|
|
679
752
|
if (oauthPath)
|
|
680
753
|
return oauthPath[2] === "authorize"
|
|
681
754
|
? oauthAuthorize(request, env, oauthPath[1] as "github" | "google")
|
|
@@ -861,8 +934,12 @@ async function route(
|
|
|
861
934
|
return json({
|
|
862
935
|
repo: config.repo,
|
|
863
936
|
github: !!(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET),
|
|
864
|
-
google: !!(
|
|
937
|
+
google: !!(
|
|
938
|
+
env.KOMO_IAP_AUDIENCE ||
|
|
939
|
+
(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET)
|
|
940
|
+
),
|
|
865
941
|
guests:
|
|
942
|
+
!env.KOMO_IAP_AUDIENCE &&
|
|
866
943
|
config.allowGuests !== false &&
|
|
867
944
|
(!config.requireOwner || !!owner) &&
|
|
868
945
|
!(await privateAccess(env, project)),
|
|
@@ -931,6 +1008,7 @@ async function route(
|
|
|
931
1008
|
});
|
|
932
1009
|
}
|
|
933
1010
|
if (url.pathname === "/auth/guest" && request.method === "POST") {
|
|
1011
|
+
check(!env.KOMO_IAP_AUDIENCE, 403, "Guest comments are disabled.");
|
|
934
1012
|
check(
|
|
935
1013
|
!(await privateAccess(env, project)),
|
|
936
1014
|
403,
|
|
@@ -958,9 +1036,11 @@ async function route(
|
|
|
958
1036
|
) {
|
|
959
1037
|
const provider = url.pathname.includes("/google/") ? "google" : "github";
|
|
960
1038
|
check(
|
|
961
|
-
|
|
962
|
-
?
|
|
963
|
-
:
|
|
1039
|
+
env.KOMO_IAP_AUDIENCE
|
|
1040
|
+
? provider === "google"
|
|
1041
|
+
: provider === "google"
|
|
1042
|
+
? env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET
|
|
1043
|
+
: env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET,
|
|
964
1044
|
503,
|
|
965
1045
|
"Sign-in is not configured."
|
|
966
1046
|
);
|
|
@@ -984,7 +1064,8 @@ async function route(
|
|
|
984
1064
|
)
|
|
985
1065
|
.run();
|
|
986
1066
|
return json({
|
|
987
|
-
|
|
1067
|
+
// KOMO_PUBLIC_PATH covers apps served under a path behind a proxy that strips it.
|
|
1068
|
+
url: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}${env.KOMO_IAP_AUDIENCE ? authPrefix(env.KOMO_AUTH_PREFIX) : ""}/auth/${provider}/authorize?state=${encodeURIComponent(state)}`,
|
|
988
1069
|
});
|
|
989
1070
|
}
|
|
990
1071
|
if (url.pathname === "/me") {
|
|
@@ -1047,6 +1128,16 @@ async function route(
|
|
|
1047
1128
|
check(request.method === "GET", 405, "Method not allowed.");
|
|
1048
1129
|
return json({ user });
|
|
1049
1130
|
}
|
|
1131
|
+
// Behind IAP, comments are never readable or writable without a session
|
|
1132
|
+
// minted from an IAP-verified Google account.
|
|
1133
|
+
if (env.KOMO_IAP_AUDIENCE) {
|
|
1134
|
+
const reader = await authenticate(request, env, project);
|
|
1135
|
+
check(
|
|
1136
|
+
reader.verified && reader.id.startsWith("google:"),
|
|
1137
|
+
403,
|
|
1138
|
+
"Sign in with your Google account to see comments."
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1050
1141
|
if (await privateAccess(env, project)) {
|
|
1051
1142
|
await requireMember(
|
|
1052
1143
|
env,
|
package/server/secrets.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ interface Env {
|
|
|
8
8
|
) => Promise<void>;
|
|
9
9
|
KOMO_HOSTED?: string;
|
|
10
10
|
KOMO_PAUSED?: string;
|
|
11
|
+
/** Enables Google Cloud IAP sign-in: the IAP backend service audience. */
|
|
12
|
+
KOMO_IAP_AUDIENCE?: string;
|
|
13
|
+
/** Path prefix for IAP sign-in handlers, e.g. "/backoffice". */
|
|
14
|
+
KOMO_AUTH_PREFIX?: string;
|
|
15
|
+
/** Path the app is served under behind a proxy that strips it, e.g. "/komo". */
|
|
16
|
+
KOMO_PUBLIC_PATH?: string;
|
|
11
17
|
EDGE_LIMIT?: RateLimit;
|
|
12
18
|
KOMO_VERSION?: { id: string };
|
|
13
19
|
}
|