@incorta/auth 1.6.0 → 1.7.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/README.md +40 -0
- package/dist/{config-DmeXKgtO.d.cts → app-access-BSsKtvbi.d.cts} +58 -1
- package/dist/{config-DmeXKgtO.d.ts → app-access-BSsKtvbi.d.ts} +58 -1
- package/dist/chunk-TMXOEIA2.js +102 -0
- package/dist/chunk-TMXOEIA2.js.map +1 -0
- package/dist/express.cjs +52 -2
- package/dist/express.cjs.map +1 -1
- package/dist/express.d.cts +20 -3
- package/dist/express.d.ts +20 -3
- package/dist/express.js +47 -2
- package/dist/express.js.map +1 -1
- package/dist/index.cjs +279 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +190 -3
- package/dist/index.js.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppAccessTokenRejectedError,
|
|
3
|
+
AppAccessUnavailableError,
|
|
4
|
+
EDIT_BIT,
|
|
5
|
+
SHARE_BIT,
|
|
6
|
+
VIEW_BIT,
|
|
7
|
+
createAppAccessChecker
|
|
8
|
+
} from "./chunk-TMXOEIA2.js";
|
|
9
|
+
|
|
1
10
|
// src/core/config.ts
|
|
11
|
+
function resolveAppAccess(input, envValue) {
|
|
12
|
+
if (input) return input;
|
|
13
|
+
const raw = envValue?.trim().toLowerCase();
|
|
14
|
+
if (!raw) return "authenticated";
|
|
15
|
+
if (raw === "public" || raw === "authenticated" || raw === "catalog") return raw;
|
|
16
|
+
console.warn(
|
|
17
|
+
`[incorta-auth] Unrecognized INCORTA_APP_ACCESS value "${envValue}" \u2014 using "authenticated".`
|
|
18
|
+
);
|
|
19
|
+
return "authenticated";
|
|
20
|
+
}
|
|
2
21
|
function required(value, name, envVar) {
|
|
3
22
|
if (!value) {
|
|
4
23
|
throw new Error(
|
|
@@ -72,6 +91,7 @@ function resolveConfig(input = {}) {
|
|
|
72
91
|
cookieSameSite,
|
|
73
92
|
cookiePartitioned,
|
|
74
93
|
exposeAccessToken: input.exposeAccessToken ?? false,
|
|
94
|
+
appAccess: resolveAppAccess(input.appAccess, env.INCORTA_APP_ACCESS),
|
|
75
95
|
embeddedSignIn: input.embeddedSignIn ?? (env.INCORTA_EMBEDDED_SIGN_IN?.toLowerCase() === "new-tab" ? "new-tab" : "redirect"),
|
|
76
96
|
issuer,
|
|
77
97
|
discoveryUrl: input.advanced?.discoveryUrl ?? `${issuer}/.well-known/openid-configuration`,
|
|
@@ -381,7 +401,17 @@ function withErrorParam(target, code) {
|
|
|
381
401
|
function randomToken() {
|
|
382
402
|
return randomBytes(16).toString("base64url");
|
|
383
403
|
}
|
|
404
|
+
function escapeHtml(value) {
|
|
405
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
406
|
+
}
|
|
407
|
+
function wantsHtml(request) {
|
|
408
|
+
return request.method.toUpperCase() === "GET" && (request.headers.get("accept") ?? "").includes("text/html");
|
|
409
|
+
}
|
|
410
|
+
function requestPath(url) {
|
|
411
|
+
return `${url.pathname}${url.search}`;
|
|
412
|
+
}
|
|
384
413
|
function createHandler(config) {
|
|
414
|
+
const appAccessChecker = createAppAccessChecker(config);
|
|
385
415
|
const sessionCookieOptions = (secure) => ({
|
|
386
416
|
httpOnly: true,
|
|
387
417
|
// SameSite=None requires Secure — force the pair even on plain http
|
|
@@ -531,6 +561,18 @@ function createHandler(config) {
|
|
|
531
561
|
setCookies
|
|
532
562
|
);
|
|
533
563
|
}
|
|
564
|
+
async function refreshRoute(request, url) {
|
|
565
|
+
const origin = requestOrigin(config, request);
|
|
566
|
+
const returnTo = validateReturnTo(url.searchParams.get("redirect_to"), origin);
|
|
567
|
+
const { session, setCookies } = await loadSession(request);
|
|
568
|
+
if (!session) {
|
|
569
|
+
return redirect(
|
|
570
|
+
`${config.basePath}/login?redirect_to=${encodeURIComponent(returnTo)}`,
|
|
571
|
+
setCookies
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
return redirect(returnTo, setCookies);
|
|
575
|
+
}
|
|
534
576
|
async function logout(request, url) {
|
|
535
577
|
const secure = isSecureRequest(config, request);
|
|
536
578
|
const clearSession = deleteCookie(config.cookieName, sessionCookieOptions(secure));
|
|
@@ -631,6 +673,95 @@ function createHandler(config) {
|
|
|
631
673
|
for (const cookie of cookies) headers.append("set-cookie", cookie);
|
|
632
674
|
return new Response(body, { status: 200, headers });
|
|
633
675
|
}
|
|
676
|
+
function accessDeniedResponse(request, session) {
|
|
677
|
+
if (!wantsHtml(request)) {
|
|
678
|
+
return json(403, { error: "app_access_denied" });
|
|
679
|
+
}
|
|
680
|
+
const identity = escapeHtml(session.user.name?.trim() || session.user.sub);
|
|
681
|
+
const detail = session.user.name?.trim() && session.user.name.trim() !== session.user.sub ? `${identity} (${escapeHtml(session.user.sub)})` : identity;
|
|
682
|
+
const switchHref = `${config.basePath}/logout`;
|
|
683
|
+
return new Response(
|
|
684
|
+
`<!doctype html>
|
|
685
|
+
<html lang="en">
|
|
686
|
+
<head>
|
|
687
|
+
<meta charset="utf-8" />
|
|
688
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
689
|
+
<title>No access</title>
|
|
690
|
+
<style>
|
|
691
|
+
body { margin: 0; display: grid; place-items: center; min-height: 100vh;
|
|
692
|
+
background: #fafafa; color: #18181b;
|
|
693
|
+
font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
694
|
+
main { max-width: 26rem; padding: 2rem; text-align: center; }
|
|
695
|
+
h1 { font-size: 1.25rem; margin: 0 0 .5rem; }
|
|
696
|
+
p { color: #52525b; font-size: .9rem; line-height: 1.5; margin: 0 0 1.25rem; }
|
|
697
|
+
strong { color: #18181b; font-weight: 600; }
|
|
698
|
+
a { display: inline-block; padding: .55rem 1.1rem; border-radius: .5rem;
|
|
699
|
+
background: #18181b; color: #fafafa; font-size: .9rem;
|
|
700
|
+
text-decoration: none; }
|
|
701
|
+
</style>
|
|
702
|
+
</head>
|
|
703
|
+
<body>
|
|
704
|
+
<main>
|
|
705
|
+
<h1>You don’t have access to this application</h1>
|
|
706
|
+
<p>
|
|
707
|
+
Ask the app’s owner to share it with you in Incorta.
|
|
708
|
+
You are signed in as <strong>${detail}</strong>.
|
|
709
|
+
</p>
|
|
710
|
+
<a href="${switchHref}">Sign in with a different account</a>
|
|
711
|
+
</main>
|
|
712
|
+
</body>
|
|
713
|
+
</html>`,
|
|
714
|
+
{
|
|
715
|
+
status: 403,
|
|
716
|
+
headers: {
|
|
717
|
+
"content-type": "text/html; charset=utf-8",
|
|
718
|
+
"cache-control": "no-store"
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
function accessUnavailableResponse(request) {
|
|
724
|
+
if (!wantsHtml(request)) {
|
|
725
|
+
return json(503, { error: "app_access_unavailable" });
|
|
726
|
+
}
|
|
727
|
+
return new Response(
|
|
728
|
+
`<!doctype html>
|
|
729
|
+
<html lang="en">
|
|
730
|
+
<head>
|
|
731
|
+
<meta charset="utf-8" />
|
|
732
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
733
|
+
<meta http-equiv="refresh" content="15" />
|
|
734
|
+
<title>Temporarily unavailable</title>
|
|
735
|
+
<style>
|
|
736
|
+
body { margin: 0; display: grid; place-items: center; min-height: 100vh;
|
|
737
|
+
background: #fafafa; color: #18181b;
|
|
738
|
+
font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
739
|
+
main { max-width: 26rem; padding: 2rem; text-align: center; }
|
|
740
|
+
h1 { font-size: 1.25rem; margin: 0 0 .5rem; }
|
|
741
|
+
p { color: #52525b; font-size: .9rem; line-height: 1.5; margin: 0; }
|
|
742
|
+
</style>
|
|
743
|
+
</head>
|
|
744
|
+
<body>
|
|
745
|
+
<main>
|
|
746
|
+
<h1>This app is temporarily unavailable</h1>
|
|
747
|
+
<p>
|
|
748
|
+
Your access could not be verified because Incorta did not respond.
|
|
749
|
+
This page retries automatically — you can also refresh in a
|
|
750
|
+
moment.
|
|
751
|
+
</p>
|
|
752
|
+
</main>
|
|
753
|
+
</body>
|
|
754
|
+
</html>`,
|
|
755
|
+
{
|
|
756
|
+
status: 503,
|
|
757
|
+
headers: {
|
|
758
|
+
"content-type": "text/html; charset=utf-8",
|
|
759
|
+
"cache-control": "no-store",
|
|
760
|
+
"retry-after": "30"
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
}
|
|
634
765
|
async function handler(request) {
|
|
635
766
|
const url = new URL(request.url);
|
|
636
767
|
if (url.pathname !== config.basePath && !url.pathname.startsWith(`${config.basePath}/`)) {
|
|
@@ -643,6 +774,7 @@ function createHandler(config) {
|
|
|
643
774
|
if (route === "/callback" && method === "GET") return await callback(request, url);
|
|
644
775
|
if (route === "/session" && method === "GET") return await sessionRoute(request);
|
|
645
776
|
if (route === "/token" && method === "GET") return await tokenRoute(request);
|
|
777
|
+
if (route === "/refresh" && method === "GET") return await refreshRoute(request, url);
|
|
646
778
|
if (route === "/logout" && (method === "POST" || method === "GET")) {
|
|
647
779
|
return await logout(request, url);
|
|
648
780
|
}
|
|
@@ -668,7 +800,57 @@ function createHandler(config) {
|
|
|
668
800
|
expiresAt
|
|
669
801
|
};
|
|
670
802
|
}
|
|
671
|
-
|
|
803
|
+
function getAppAccess(session) {
|
|
804
|
+
return appAccessChecker.check(session.accessToken, session.user.sub);
|
|
805
|
+
}
|
|
806
|
+
async function gate(request) {
|
|
807
|
+
const url = new URL(request.url);
|
|
808
|
+
if (url.pathname === config.basePath || url.pathname.startsWith(`${config.basePath}/`)) {
|
|
809
|
+
return handler(request);
|
|
810
|
+
}
|
|
811
|
+
if (config.appAccess === "public") return null;
|
|
812
|
+
const session = await getSession(request);
|
|
813
|
+
if (!session) {
|
|
814
|
+
if (wantsHtml(request)) {
|
|
815
|
+
return redirect(
|
|
816
|
+
`${config.basePath}/login?redirect_to=${encodeURIComponent(requestPath(url))}`
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
return json(401, { error: "unauthenticated" });
|
|
820
|
+
}
|
|
821
|
+
if (config.appAccess !== "catalog") return null;
|
|
822
|
+
if (session.accessTokenExpiresAt <= Date.now()) {
|
|
823
|
+
if (wantsHtml(request)) {
|
|
824
|
+
return redirect(
|
|
825
|
+
`${config.basePath}/refresh?redirect_to=${encodeURIComponent(requestPath(url))}`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
return json(401, { error: "token_expired" });
|
|
829
|
+
}
|
|
830
|
+
try {
|
|
831
|
+
const verdict = await getAppAccess(session);
|
|
832
|
+
if (verdict.allowed) return null;
|
|
833
|
+
return accessDeniedResponse(request, session);
|
|
834
|
+
} catch (error) {
|
|
835
|
+
if (error instanceof AppAccessTokenRejectedError) {
|
|
836
|
+
const secure = isSecureRequest(config, request);
|
|
837
|
+
const clearSession = deleteCookie(config.cookieName, sessionCookieOptions(secure));
|
|
838
|
+
if (wantsHtml(request)) {
|
|
839
|
+
return redirect(
|
|
840
|
+
`${config.basePath}/login?redirect_to=${encodeURIComponent(requestPath(url))}`,
|
|
841
|
+
[clearSession]
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
return json(401, { error: "unauthenticated" }, [clearSession]);
|
|
845
|
+
}
|
|
846
|
+
if (!(error instanceof AppAccessUnavailableError)) {
|
|
847
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
848
|
+
console.error("[incorta-auth] app access check failed unexpectedly:", message);
|
|
849
|
+
}
|
|
850
|
+
return accessUnavailableResponse(request);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return { handler, getSession, gate, getAppAccess };
|
|
672
854
|
}
|
|
673
855
|
|
|
674
856
|
// src/core/register.ts
|
|
@@ -803,11 +985,16 @@ async function deleteClient(options) {
|
|
|
803
985
|
// src/index.ts
|
|
804
986
|
function createIncortaAuth(config = {}) {
|
|
805
987
|
const resolved = resolveConfig(config);
|
|
806
|
-
const { handler, getSession } = createHandler(resolved);
|
|
807
|
-
return { handler, getSession, config: resolved };
|
|
988
|
+
const { handler, getSession, gate, getAppAccess } = createHandler(resolved);
|
|
989
|
+
return { handler, getSession, gate, getAppAccess, config: resolved };
|
|
808
990
|
}
|
|
809
991
|
export {
|
|
992
|
+
AppAccessTokenRejectedError,
|
|
993
|
+
AppAccessUnavailableError,
|
|
994
|
+
EDIT_BIT,
|
|
810
995
|
IncortaAuthError,
|
|
996
|
+
SHARE_BIT,
|
|
997
|
+
VIEW_BIT,
|
|
811
998
|
createIncortaAuth,
|
|
812
999
|
deleteClient,
|
|
813
1000
|
getClient,
|