@henosia/app-next 1.0.4 → 1.0.6
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/.agents/skills/henosia-app-next/SKILL.md +9 -8
- package/README.md +9 -4
- package/dist/api/auth.d.mts +3 -1
- package/dist/api/auth.mjs +32 -2
- package/dist/api/platform.mjs +42 -6
- package/dist/auth/middleware.d.mts +6 -21
- package/dist/auth/middleware.mjs +43 -128
- package/dist/auth/server-guards.d.mts +1 -1
- package/dist/auth/server-guards.mjs +2 -2
- package/dist/auth/server.d.mts +1 -106
- package/dist/auth/server.mjs +40 -79
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +4 -3
- package/dist/middleware-shared-CZbIZuBw.mjs +140 -0
- package/dist/platform/app-switcher.d.mts +7 -10
- package/dist/platform/app-switcher.mjs +4 -4
- package/dist/server-DfD6Dc91.d.mts +112 -0
- package/dist/session-sync-Cva_oreV.mjs +114 -0
- package/dist/shared-BWPBaubT.d.mts +30 -0
- package/dist/shared.d.mts +2 -2
- package/dist/shared.mjs +11 -2
- package/package.json +1 -1
- package/dist/shared-BWt7Sysv.d.mts +0 -19
package/dist/auth/server.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*! Copyright (c) 2026 Henosia ApS. Licensed under the Henosia Commercial Source License v1.0. See LICENSE */
|
|
2
|
-
import { HENOSIA_AUTH_GET_SESSION_PATH_NAME
|
|
2
|
+
import { HENOSIA_AUTH_GET_SESSION_PATH_NAME } from "../shared.mjs";
|
|
3
|
+
import { i as cookiePrefix, l as getRequiredEnvValue, n as HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES, s as isPageRequest, t as HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES } from "../middleware-shared-CZbIZuBw.mjs";
|
|
3
4
|
import { APIError, betterAuth } from "better-auth";
|
|
4
5
|
import { createAuthMiddleware } from "better-auth/api";
|
|
5
6
|
import { bearer, genericOAuth, jwt } from "better-auth/plugins";
|
|
@@ -7,38 +8,8 @@ import { nextCookies } from "better-auth/next-js";
|
|
|
7
8
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
8
9
|
import { parseSetCookieHeader } from "better-auth/cookies";
|
|
9
10
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
-
import { headers } from "next/headers.js";
|
|
11
11
|
//#region src/auth/server.ts
|
|
12
12
|
/**
|
|
13
|
-
* URL pathname values that should not check for auth in middleware, e.g. the sign-in page.
|
|
14
|
-
* Additional pathname values can be added for public paths, but ONLY if they should not be intercepted by the
|
|
15
|
-
* middleware auth pre-check and redirect.
|
|
16
|
-
*/
|
|
17
|
-
const HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES = new Set([HENOSIA_AUTH_SIGN_IN_PATH_NAME]);
|
|
18
|
-
if (HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES.has("/")) throw new Error(`[Henosia Auth] Invalid path name '/'. Values must be non-root paths`);
|
|
19
|
-
/**
|
|
20
|
-
* URL pathname prefixes that should not be blocked by auth in middleware, e.g. the better-auth API routes used to sign in.
|
|
21
|
-
* Additional pathname prefixes can be added for public paths, but ONLY if they should not be intercepted by the
|
|
22
|
-
* middleware auth pre-check and redirect. Include the relevant `/` ending character to prevent unexpected partial
|
|
23
|
-
* route matches.
|
|
24
|
-
*/
|
|
25
|
-
const HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES = ["/api/auth/", "/.well-known/"];
|
|
26
|
-
if (HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES.find((p) => p === "/" || !p.trim() || !p.endsWith("/"))) throw new Error("[Henosia Auth] Invalid path name prefixes. Values must be non-empty sub paths ending with slash");
|
|
27
|
-
const requiredEnvProxy = new Proxy(process.env, { get(target, property, receiver) {
|
|
28
|
-
const value = Reflect.get(target, property, receiver);
|
|
29
|
-
if (!value || typeof value !== "string") throw new Error(`[Henosia Auth] Missing required env var: ${property.toString()}`);
|
|
30
|
-
return value;
|
|
31
|
-
} });
|
|
32
|
-
function getRequiredEnvValue(getter, fallback) {
|
|
33
|
-
try {
|
|
34
|
-
return { get: () => getter(requiredEnvProxy) };
|
|
35
|
-
} catch (e) {
|
|
36
|
-
if (fallback) return { get: () => fallback };
|
|
37
|
-
console.log(e.message);
|
|
38
|
-
throw e;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
13
|
* Environment-provided configuration for Henosia Auth.
|
|
43
14
|
* When the consuming app runs in the Henosia preview, the env vars are provided by the runtime environment and should not be
|
|
44
15
|
* hard-coded or present in the `.env.local` file. The Henosia Publish feature automatically sets production versions of
|
|
@@ -56,16 +27,6 @@ const henosiaAuthConfig = {
|
|
|
56
27
|
projectId: getRequiredEnvValue((env) => env.HENOSIA_AUTH_PROJECT_ID)
|
|
57
28
|
};
|
|
58
29
|
/**
|
|
59
|
-
* Gets whether the specified request is for a page (a top level page or iframe page)
|
|
60
|
-
*/
|
|
61
|
-
function isPageRequest(request) {
|
|
62
|
-
const dest = request.headers.get("Sec-Fetch-Dest");
|
|
63
|
-
if (dest === "document" || dest === "iframe" || dest === "fencedframe") return true;
|
|
64
|
-
const isRsc = request.headers.get("RSC") === "1";
|
|
65
|
-
const isPrefetch = request.headers.get("Next-Router-Prefetch") === "1";
|
|
66
|
-
return isRsc && !isPrefetch;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
30
|
* Gets whether the specified request is allowed to spend the single-use OIDC refresh token to obtain a new
|
|
70
31
|
* access token.
|
|
71
32
|
*
|
|
@@ -76,7 +37,6 @@ function isPageOrRefreshTokenRequest(request) {
|
|
|
76
37
|
}
|
|
77
38
|
const currentRequestStorage = new AsyncLocalStorage();
|
|
78
39
|
let henosiaOAuthProviderInitialized = false;
|
|
79
|
-
const cookiePrefix = `henosia-auth-${henosiaAuthConfig.projectId.get()}`;
|
|
80
40
|
const authInstance = betterAuth({
|
|
81
41
|
baseURL: henosiaAuthConfig.baseURL.get(),
|
|
82
42
|
secret: henosiaAuthConfig.secret.get(),
|
|
@@ -162,44 +122,45 @@ const auth = authInstance;
|
|
|
162
122
|
* @throws APIError when the current credentials are missing or invalid
|
|
163
123
|
*/
|
|
164
124
|
async function verifyHenosiaAuthToken(request) {
|
|
165
|
-
currentRequestStorage.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
if (!accessTokenResponse.idToken) throw new APIError("UNAUTHORIZED", { message: "No id_token returned for openid scope" });
|
|
172
|
-
const payload = await verifyAccessToken(accessTokenResponse.idToken, {
|
|
173
|
-
jwksUrl: `${henosiaAuthConfig.henosiaAuthPlatformServiceBaseUrl.get()}/api/auth/jwks`,
|
|
174
|
-
verifyOptions: {
|
|
175
|
-
audience: henosiaAuthConfig.clientId.get(),
|
|
176
|
-
issuer: `${henosiaAuthConfig.henosiaAuthPlatformServiceBaseUrl.get()}/api/auth`
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
const { "https://henosia.com/project": projectId } = payload;
|
|
180
|
-
if (!projectId) throw new APIError("UNAUTHORIZED", { message: "token does not grant access" });
|
|
181
|
-
if (!henosiaAuthConfig.projectId) throw new APIError("INTERNAL_SERVER_ERROR", { message: "HENOSIA_AUTH_PROJECT_ID env var has not been set" });
|
|
182
|
-
if (henosiaAuthConfig.projectId.get() !== projectId) throw new APIError("UNAUTHORIZED", { message: "token has invalid projectId" });
|
|
183
|
-
const setCookieHeaders = accessTokenResponseHeaders.getSetCookie();
|
|
184
|
-
const transferBetterAuthCookiesToNext = (nextRequest, response) => {
|
|
185
|
-
for (const cookie of setCookieHeaders) parseSetCookieHeader(cookie).forEach((attributes, name) => {
|
|
186
|
-
const { value, ...betterOptions } = attributes;
|
|
187
|
-
const options = {
|
|
188
|
-
...betterOptions,
|
|
189
|
-
maxAge: betterOptions["max-age"],
|
|
190
|
-
httpOnly: betterOptions.httponly,
|
|
191
|
-
sameSite: betterOptions.samesite
|
|
192
|
-
};
|
|
193
|
-
nextRequest.cookies.set(name, value);
|
|
194
|
-
response.cookies.set(name, value, options);
|
|
125
|
+
return await currentRequestStorage.run(request, async () => {
|
|
126
|
+
const { response: accessTokenResponse, headers: accessTokenResponseHeaders } = await authInstance.api.getAccessToken({
|
|
127
|
+
body: { providerId: henosiaAuthConfig.provider },
|
|
128
|
+
headers: request.headers,
|
|
129
|
+
returnHeaders: true
|
|
195
130
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
131
|
+
if (!accessTokenResponse.idToken) throw new APIError("UNAUTHORIZED", { message: "No id_token returned for openid scope" });
|
|
132
|
+
const payload = await verifyAccessToken(accessTokenResponse.idToken, {
|
|
133
|
+
jwksUrl: `${henosiaAuthConfig.henosiaAuthPlatformServiceBaseUrl.get()}/api/auth/jwks`,
|
|
134
|
+
verifyOptions: {
|
|
135
|
+
audience: henosiaAuthConfig.clientId.get(),
|
|
136
|
+
issuer: `${henosiaAuthConfig.henosiaAuthPlatformServiceBaseUrl.get()}/api/auth`
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const { "https://henosia.com/project": projectId } = payload;
|
|
140
|
+
if (!projectId) throw new APIError("UNAUTHORIZED", { message: "token does not grant access" });
|
|
141
|
+
if (!henosiaAuthConfig.projectId) throw new APIError("INTERNAL_SERVER_ERROR", { message: "HENOSIA_AUTH_PROJECT_ID env var has not been set" });
|
|
142
|
+
if (henosiaAuthConfig.projectId.get() !== projectId) throw new APIError("UNAUTHORIZED", { message: "token has invalid projectId" });
|
|
143
|
+
const setCookieHeaders = accessTokenResponseHeaders.getSetCookie();
|
|
144
|
+
const transferBetterAuthCookiesToNext = (nextRequest, response) => {
|
|
145
|
+
for (const cookie of setCookieHeaders) parseSetCookieHeader(cookie).forEach((attributes, name) => {
|
|
146
|
+
const { value, ...betterOptions } = attributes;
|
|
147
|
+
const options = {
|
|
148
|
+
...betterOptions,
|
|
149
|
+
maxAge: betterOptions["max-age"],
|
|
150
|
+
httpOnly: betterOptions.httponly,
|
|
151
|
+
sameSite: betterOptions.samesite
|
|
152
|
+
};
|
|
153
|
+
nextRequest.cookies.set(name, value);
|
|
154
|
+
response.cookies.set(name, value, options);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
accessTokenResponse,
|
|
159
|
+
payload,
|
|
160
|
+
setCookieHeaders,
|
|
161
|
+
transferBetterAuthCookiesToNext
|
|
162
|
+
};
|
|
163
|
+
});
|
|
203
164
|
}
|
|
204
165
|
const unauthorizedExceptionCodes = new Set(["ACCOUNT_NOT_FOUND", "FAILED_TO_GET_ACCESS_TOKEN"]);
|
|
205
166
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { c as HENOSIA_ORGANIZATION_CTX_COOKIE, l as HenosiaOrganizationContext, n as HENOSIA_AUTH_INVALID_SESSION_BODY, o as HENOSIA_AUTH_SESSION_SYNC_PATH_NAME, s as HENOSIA_AUTH_SIGN_IN_PATH_NAME, t as HENOSIA_AUTH_GET_SESSION_PATH_NAME } from "./shared-BWPBaubT.mjs";
|
|
3
|
+
import { a as isPageOrRefreshTokenRequest, c as HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES, d as isPageRequest, i as henosiaAuthConfig, l as HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES, n as HenosiaAuthTokenClaims, o as isUnauthorizedException, r as auth, s as verifyHenosiaAuthToken, t as HenosiaAuthConfig, u as cookiePrefix } from "./server-DfD6Dc91.mjs";
|
|
4
4
|
import { HenosiaAuthContext, getHenosiaAuth, requireHenosiaAuth, routeWithHenosiaAuth } from "./auth/server-guards.mjs";
|
|
5
|
-
export { HENOSIA_AUTH_GET_SESSION_PATH_NAME, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES, HENOSIA_AUTH_SIGN_IN_PATH_NAME, HENOSIA_ORGANIZATION_CTX_COOKIE, type HenosiaAuthConfig, type HenosiaAuthContext, type HenosiaAuthTokenClaims, type HenosiaOrganizationContext, auth, cookiePrefix, getHenosiaAuth, henosiaAuthConfig, isPageOrRefreshTokenRequest, isPageRequest, isUnauthorizedException, requireHenosiaAuth, routeWithHenosiaAuth, verifyHenosiaAuthToken };
|
|
5
|
+
export { HENOSIA_AUTH_GET_SESSION_PATH_NAME, HENOSIA_AUTH_INVALID_SESSION_BODY, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES, HENOSIA_AUTH_SESSION_SYNC_PATH_NAME, HENOSIA_AUTH_SIGN_IN_PATH_NAME, HENOSIA_ORGANIZATION_CTX_COOKIE, type HenosiaAuthConfig, type HenosiaAuthContext, type HenosiaAuthTokenClaims, type HenosiaOrganizationContext, auth, cookiePrefix, getHenosiaAuth, henosiaAuthConfig, isPageOrRefreshTokenRequest, isPageRequest, isUnauthorizedException, requireHenosiaAuth, routeWithHenosiaAuth, verifyHenosiaAuthToken };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*! Copyright (c) 2026 Henosia ApS. Licensed under the Henosia Commercial Source License v1.0. See LICENSE */
|
|
2
|
-
import { HENOSIA_AUTH_GET_SESSION_PATH_NAME, HENOSIA_AUTH_SIGN_IN_PATH_NAME, HENOSIA_ORGANIZATION_CTX_COOKIE } from "./shared.mjs";
|
|
3
|
-
import {
|
|
2
|
+
import { HENOSIA_AUTH_GET_SESSION_PATH_NAME, HENOSIA_AUTH_INVALID_SESSION_BODY, HENOSIA_AUTH_SESSION_SYNC_PATH_NAME, HENOSIA_AUTH_SIGN_IN_PATH_NAME, HENOSIA_ORGANIZATION_CTX_COOKIE } from "./shared.mjs";
|
|
3
|
+
import { i as cookiePrefix, n as HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES, s as isPageRequest, t as HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES } from "./middleware-shared-CZbIZuBw.mjs";
|
|
4
|
+
import { auth, henosiaAuthConfig, isPageOrRefreshTokenRequest, isUnauthorizedException, verifyHenosiaAuthToken } from "./auth/server.mjs";
|
|
4
5
|
import { getHenosiaAuth, requireHenosiaAuth, routeWithHenosiaAuth } from "./auth/server-guards.mjs";
|
|
5
|
-
export { HENOSIA_AUTH_GET_SESSION_PATH_NAME, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES, HENOSIA_AUTH_SIGN_IN_PATH_NAME, HENOSIA_ORGANIZATION_CTX_COOKIE, auth, cookiePrefix, getHenosiaAuth, henosiaAuthConfig, isPageOrRefreshTokenRequest, isPageRequest, isUnauthorizedException, requireHenosiaAuth, routeWithHenosiaAuth, verifyHenosiaAuthToken };
|
|
6
|
+
export { HENOSIA_AUTH_GET_SESSION_PATH_NAME, HENOSIA_AUTH_INVALID_SESSION_BODY, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES, HENOSIA_AUTH_SESSION_SYNC_PATH_NAME, HENOSIA_AUTH_SIGN_IN_PATH_NAME, HENOSIA_ORGANIZATION_CTX_COOKIE, auth, cookiePrefix, getHenosiaAuth, henosiaAuthConfig, isPageOrRefreshTokenRequest, isPageRequest, isUnauthorizedException, requireHenosiaAuth, routeWithHenosiaAuth, verifyHenosiaAuthToken };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*! Copyright (c) 2026 Henosia ApS. Licensed under the Henosia Commercial Source License v1.0. See LICENSE */
|
|
2
|
+
import { HENOSIA_AUTH_SIGN_IN_PATH_NAME } from "./shared.mjs";
|
|
3
|
+
import "next/server.js";
|
|
4
|
+
//#region src/auth/env.ts
|
|
5
|
+
const requiredEnvProxy = new Proxy(process.env, { get(target, property, receiver) {
|
|
6
|
+
const value = Reflect.get(target, property, receiver);
|
|
7
|
+
if (!value || typeof value !== "string") throw new Error(`[Henosia Auth] Missing required env var: ${property.toString()}`);
|
|
8
|
+
return value;
|
|
9
|
+
} });
|
|
10
|
+
function getRequiredEnvValue(getter, fallback) {
|
|
11
|
+
return { get: () => {
|
|
12
|
+
try {
|
|
13
|
+
return getter(requiredEnvProxy);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
if (fallback !== void 0) return fallback;
|
|
16
|
+
console.log(e.message);
|
|
17
|
+
throw e;
|
|
18
|
+
}
|
|
19
|
+
} };
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/auth/middleware-shared.ts
|
|
23
|
+
/**
|
|
24
|
+
* URL pathname values that should not check for auth in middleware, e.g. the sign-in page.
|
|
25
|
+
* Additional pathname values can be added for public paths, but ONLY if they should not be intercepted by the
|
|
26
|
+
* middleware auth pre-check and redirect.
|
|
27
|
+
*/
|
|
28
|
+
const HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES = new Set([HENOSIA_AUTH_SIGN_IN_PATH_NAME]);
|
|
29
|
+
if (HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES.has("/")) throw new Error(`[Henosia Auth] Invalid path name '/'. Values must be non-root paths`);
|
|
30
|
+
/**
|
|
31
|
+
* URL pathname prefixes that should not be blocked by auth in middleware, e.g. the better-auth API routes used to sign in.
|
|
32
|
+
* Additional pathname prefixes can be added for public paths, but ONLY if they should not be intercepted by the
|
|
33
|
+
* middleware auth pre-check and redirect. Include the relevant `/` ending character to prevent unexpected partial
|
|
34
|
+
* route matches.
|
|
35
|
+
*/
|
|
36
|
+
const HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES = ["/api/auth/", "/.well-known/"];
|
|
37
|
+
if (HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES.find((p) => p === "/" || !p.trim() || !p.endsWith("/"))) throw new Error("[Henosia Auth] Invalid path name prefixes. Values must be non-empty sub paths ending with slash");
|
|
38
|
+
const middlewareConfig = {
|
|
39
|
+
baseURL: getRequiredEnvValue((env) => env.BETTER_AUTH_URL),
|
|
40
|
+
projectId: getRequiredEnvValue((env) => env.HENOSIA_AUTH_PROJECT_ID)
|
|
41
|
+
};
|
|
42
|
+
const cookiePrefix = `henosia-auth-${middlewareConfig.projectId.get()}`;
|
|
43
|
+
function parseCookies(cookieHeader) {
|
|
44
|
+
const cookieMap = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const cookie of cookieHeader.split(/;\s*/)) {
|
|
46
|
+
const [name, value] = cookie.split(/=(.*)/s);
|
|
47
|
+
if (name) cookieMap.set(name, value);
|
|
48
|
+
}
|
|
49
|
+
return cookieMap;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Lightweight Better Auth session-cookie lookup for Next.js middleware.
|
|
53
|
+
*
|
|
54
|
+
* This intentionally mirrors Better Auth's `getSessionCookie` naming rules without importing
|
|
55
|
+
* `better-auth/cookies`, because that module imports JWT helpers at module scope and pulls the
|
|
56
|
+
* server auth graph into Edge middleware bundles.
|
|
57
|
+
*/
|
|
58
|
+
function getHenosiaSessionCookie(request) {
|
|
59
|
+
const cookies = request.headers.get("cookie");
|
|
60
|
+
if (!cookies) return null;
|
|
61
|
+
const parsedCookie = parseCookies(cookies);
|
|
62
|
+
const getCookie = (name) => parsedCookie.get(name) || parsedCookie.get(`__Secure-${name}`);
|
|
63
|
+
return getCookie(`${cookiePrefix}.session_token`) || getCookie(`${cookiePrefix}-session_token`) || null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets whether the specified request is for a page (a top level page or iframe page).
|
|
67
|
+
*/
|
|
68
|
+
function isPageRequest(request) {
|
|
69
|
+
const { headers } = request;
|
|
70
|
+
const dest = headers.get("X-Henosia-Fetch-Dest") ?? headers.get("Sec-Fetch-Dest");
|
|
71
|
+
if (dest === "document" || dest === "iframe" || dest === "fencedframe") return true;
|
|
72
|
+
const isRsc = request.headers.get("RSC") === "1";
|
|
73
|
+
const isPrefetch = request.headers.get("Next-Router-Prefetch") === "1";
|
|
74
|
+
return isRsc && !isPrefetch;
|
|
75
|
+
}
|
|
76
|
+
function getSetCookieHeaders(headers) {
|
|
77
|
+
const getSetCookie = headers.getSetCookie?.();
|
|
78
|
+
if (getSetCookie?.length) return getSetCookie;
|
|
79
|
+
const setCookie = headers.get("set-cookie");
|
|
80
|
+
return setCookie ? splitSetCookieHeader(setCookie) : [];
|
|
81
|
+
}
|
|
82
|
+
function splitSetCookieHeader(setCookieHeader) {
|
|
83
|
+
const cookies = [];
|
|
84
|
+
let start = 0;
|
|
85
|
+
for (let i = 0; i < setCookieHeader.length; i += 1) {
|
|
86
|
+
if (setCookieHeader[i] !== ",") continue;
|
|
87
|
+
let cursor = i + 1;
|
|
88
|
+
while (cursor < setCookieHeader.length && setCookieHeader[cursor] === " ") cursor += 1;
|
|
89
|
+
while (cursor < setCookieHeader.length && setCookieHeader[cursor] !== "=" && setCookieHeader[cursor] !== ";" && setCookieHeader[cursor] !== ",") cursor += 1;
|
|
90
|
+
if (setCookieHeader[cursor] === "=") {
|
|
91
|
+
cookies.push(setCookieHeader.slice(start, i).trim());
|
|
92
|
+
start = i + 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const lastCookie = setCookieHeader.slice(start).trim();
|
|
96
|
+
if (lastCookie) cookies.push(lastCookie);
|
|
97
|
+
return cookies;
|
|
98
|
+
}
|
|
99
|
+
function applySetCookieHeadersToRequestHeaders(requestHeaders, setCookieHeaders) {
|
|
100
|
+
if (!setCookieHeaders.length) return;
|
|
101
|
+
const cookies = parseCookies(requestHeaders.get("cookie") ?? "");
|
|
102
|
+
for (const setCookie of setCookieHeaders) {
|
|
103
|
+
const parsed = parseSetCookie(setCookie);
|
|
104
|
+
if (!parsed) continue;
|
|
105
|
+
if (parsed.deleted) cookies.delete(parsed.name);
|
|
106
|
+
else cookies.set(parsed.name, parsed.value);
|
|
107
|
+
}
|
|
108
|
+
requestHeaders.set("cookie", Array.from(cookies.entries()).map(([name, value]) => `${name}=${value}`).join("; "));
|
|
109
|
+
}
|
|
110
|
+
function parseSetCookie(setCookie) {
|
|
111
|
+
const [cookiePair, ...attributes] = setCookie.split(";");
|
|
112
|
+
if (!cookiePair) return null;
|
|
113
|
+
const [name, value] = cookiePair.trim().split(/=(.*)/s);
|
|
114
|
+
if (!name) return null;
|
|
115
|
+
const deleted = attributes.some((attribute) => {
|
|
116
|
+
const normalized = attribute.trim().toLowerCase();
|
|
117
|
+
return normalized === "max-age=0" || normalized.startsWith("expires=thu, 01 jan 1970");
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
name,
|
|
121
|
+
value: value ?? "",
|
|
122
|
+
deleted
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Removes potentially invalid cached account data from cookies before the next better-auth sign in attempt.
|
|
127
|
+
*/
|
|
128
|
+
function removeCachedAccountData(response) {
|
|
129
|
+
const baseURL = middlewareConfig.baseURL.get();
|
|
130
|
+
const secure = baseURL.startsWith("https");
|
|
131
|
+
const name = `${secure ? "__Secure-" : ""}${cookiePrefix}.account_data`;
|
|
132
|
+
response.cookies.delete({
|
|
133
|
+
name,
|
|
134
|
+
secure,
|
|
135
|
+
httpOnly: true,
|
|
136
|
+
domain: new URL(baseURL).hostname
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
export { getHenosiaSessionCookie as a, removeCachedAccountData as c, cookiePrefix as i, getRequiredEnvValue as l, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES as n, getSetCookieHeaders as o, applySetCookieHeadersToRequestHeaders as r, isPageRequest as s, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import { l as HenosiaOrganizationContext } from "../shared-BWPBaubT.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/platform/app-switcher.d.ts
|
|
5
5
|
/**
|
|
@@ -23,10 +23,7 @@ interface AppSwitcherAppGroup {
|
|
|
23
23
|
*/
|
|
24
24
|
interface AppSwitcherSuccessResponse {
|
|
25
25
|
groupedApps: AppSwitcherAppGroup[];
|
|
26
|
-
organization:
|
|
27
|
-
id: string;
|
|
28
|
-
name: string;
|
|
29
|
-
};
|
|
26
|
+
organization: HenosiaOrganizationContext;
|
|
30
27
|
}
|
|
31
28
|
/**
|
|
32
29
|
* Error response shape returned by `/api/henosia-platform/v1/app-switcher`.
|
|
@@ -51,8 +48,8 @@ interface UseAppSwitcherOptions {
|
|
|
51
48
|
*/
|
|
52
49
|
interface UseAppSwitcherResult {
|
|
53
50
|
/**
|
|
54
|
-
* The current organization context, derived from the
|
|
55
|
-
*
|
|
51
|
+
* The current organization context, derived from the app-switcher response or the cached
|
|
52
|
+
* {@link HENOSIA_ORGANIZATION_CTX_COOKIE} cookie. `null` until either source is available.
|
|
56
53
|
*/
|
|
57
54
|
organization: HenosiaOrganizationContext;
|
|
58
55
|
/**
|
|
@@ -71,9 +68,9 @@ interface UseAppSwitcherResult {
|
|
|
71
68
|
/**
|
|
72
69
|
* React hook backing the Henosia app-switcher UI.
|
|
73
70
|
*
|
|
74
|
-
* Reads the
|
|
75
|
-
*
|
|
76
|
-
* `@henosia/app-next/api/platform`).
|
|
71
|
+
* Reads the cached organization context from the {@link HENOSIA_ORGANIZATION_CTX_COOKIE} cookie and queries the
|
|
72
|
+
* Henosia Platform `app-switcher` endpoint mounted at `/api/henosia-platform/v1/app-switcher` (provided by
|
|
73
|
+
* `@henosia/app-next/api/platform`). Once loaded, the verified API response is preferred over the cached cookie.
|
|
77
74
|
*
|
|
78
75
|
*
|
|
79
76
|
* @example
|
|
@@ -24,9 +24,9 @@ async function fetchAppSwitcher() {
|
|
|
24
24
|
/**
|
|
25
25
|
* React hook backing the Henosia app-switcher UI.
|
|
26
26
|
*
|
|
27
|
-
* Reads the
|
|
28
|
-
*
|
|
29
|
-
* `@henosia/app-next/api/platform`).
|
|
27
|
+
* Reads the cached organization context from the {@link HENOSIA_ORGANIZATION_CTX_COOKIE} cookie and queries the
|
|
28
|
+
* Henosia Platform `app-switcher` endpoint mounted at `/api/henosia-platform/v1/app-switcher` (provided by
|
|
29
|
+
* `@henosia/app-next/api/platform`). Once loaded, the verified API response is preferred over the cached cookie.
|
|
30
30
|
*
|
|
31
31
|
*
|
|
32
32
|
* @example
|
|
@@ -64,7 +64,7 @@ function useAppSwitcher(options) {
|
|
|
64
64
|
enabled
|
|
65
65
|
});
|
|
66
66
|
return {
|
|
67
|
-
organization,
|
|
67
|
+
organization: data?.organization ?? organization,
|
|
68
68
|
groupedApps: data?.groupedApps ?? [],
|
|
69
69
|
error,
|
|
70
70
|
isLoading
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
|
|
2
|
+
import { l as HenosiaOrganizationContext } from "./shared-BWPBaubT.mjs";
|
|
3
|
+
import { Auth } from "better-auth";
|
|
4
|
+
import { NextRequest, NextResponse } from "next/server.js";
|
|
5
|
+
|
|
6
|
+
//#region src/auth/env.d.ts
|
|
7
|
+
type EnvLiveGetString = {
|
|
8
|
+
get(): string;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/auth/middleware-shared.d.ts
|
|
12
|
+
/**
|
|
13
|
+
* URL pathname values that should not check for auth in middleware, e.g. the sign-in page.
|
|
14
|
+
* Additional pathname values can be added for public paths, but ONLY if they should not be intercepted by the
|
|
15
|
+
* middleware auth pre-check and redirect.
|
|
16
|
+
*/
|
|
17
|
+
declare const HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES: Set<string>;
|
|
18
|
+
/**
|
|
19
|
+
* URL pathname prefixes that should not be blocked by auth in middleware, e.g. the better-auth API routes used to sign in.
|
|
20
|
+
* Additional pathname prefixes can be added for public paths, but ONLY if they should not be intercepted by the
|
|
21
|
+
* middleware auth pre-check and redirect. Include the relevant `/` ending character to prevent unexpected partial
|
|
22
|
+
* route matches.
|
|
23
|
+
*/
|
|
24
|
+
declare const HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES: string[];
|
|
25
|
+
declare const cookiePrefix: `henosia-auth-${string}`;
|
|
26
|
+
/**
|
|
27
|
+
* Gets whether the specified request is for a page (a top level page or iframe page).
|
|
28
|
+
*/
|
|
29
|
+
declare function isPageRequest(request: Request): boolean;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/auth/server.d.ts
|
|
32
|
+
interface HenosiaAuthConfig {
|
|
33
|
+
/** Name of the Henosia Auth OAuth2 provider */
|
|
34
|
+
provider: 'henosia';
|
|
35
|
+
/** The consuming app's own deployment or preview browser URL. Used as the better-auth base url */
|
|
36
|
+
baseURL: EnvLiveGetString;
|
|
37
|
+
/** The consuming app's own deployment or preview auth secret. Used as the better-auth secret */
|
|
38
|
+
secret: EnvLiveGetString;
|
|
39
|
+
/** OAuth client ID from HenosiaAuthClientService */
|
|
40
|
+
clientId: EnvLiveGetString;
|
|
41
|
+
/** OAuth client secret from HenosiaAuthClientService */
|
|
42
|
+
clientSecret: EnvLiveGetString;
|
|
43
|
+
/** The Henosia auth and platform service base URL, under which the OAuth client is registered */
|
|
44
|
+
henosiaAuthPlatformServiceBaseUrl: EnvLiveGetString;
|
|
45
|
+
/** The provider identifier that Henosia Auth is registered with as a OIDC-based Custom Provider in Supabase Auth */
|
|
46
|
+
henosiaAuthSupabaseProvider: EnvLiveGetString;
|
|
47
|
+
/**
|
|
48
|
+
* The project id that the JWT project claim should match in HenosiaAuthTokenClaims.
|
|
49
|
+
* Ensures that signed-in Henosia users cannot access projects that they have not been granted access to,
|
|
50
|
+
* for example that User A from Org A cannot access User B from Org B's project.
|
|
51
|
+
* */
|
|
52
|
+
projectId: EnvLiveGetString;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Environment-provided configuration for Henosia Auth.
|
|
56
|
+
* When the consuming app runs in the Henosia preview, the env vars are provided by the runtime environment and should not be
|
|
57
|
+
* hard-coded or present in the `.env.local` file. The Henosia Publish feature automatically sets production versions of
|
|
58
|
+
* the values on the deployed app. For self-publish flows or local development, see the `Environment settings`
|
|
59
|
+
* in the Henosia builder navbar for the relevant values. The preview values cannot be used for production deployments.
|
|
60
|
+
*/
|
|
61
|
+
declare const henosiaAuthConfig: HenosiaAuthConfig;
|
|
62
|
+
interface HenosiaAuthTokenClaims {
|
|
63
|
+
/** The project id that the claim is associated with. A `null` value indicates no project access was granted. */
|
|
64
|
+
'https://henosia.com/project': string | null;
|
|
65
|
+
/** Whether the claim is associated with Henosia's preview browser in the builder */
|
|
66
|
+
'https://henosia.com/preview': boolean;
|
|
67
|
+
/** The organization that the app belongs to */
|
|
68
|
+
'https://henosia.com/organization': HenosiaOrganizationContext;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Gets whether the specified request is allowed to spend the single-use OIDC refresh token to obtain a new
|
|
72
|
+
* access token.
|
|
73
|
+
*
|
|
74
|
+
*/
|
|
75
|
+
declare function isPageOrRefreshTokenRequest(request: Request): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The Henosia Auth instance.
|
|
78
|
+
*
|
|
79
|
+
* Typed as the minimal {@link Auth} type from better-auth (which is parameterised on the default
|
|
80
|
+
* {@link BetterAuthOptions}) rather than the deeply-inferred concrete type returned by {@link betterAuth}.
|
|
81
|
+
* The deeply-inferred graph references non-portable internals from `better-auth` and `zod` that cannot be
|
|
82
|
+
* named in published declarations (TS2883).
|
|
83
|
+
*/
|
|
84
|
+
declare const auth: Auth;
|
|
85
|
+
/**
|
|
86
|
+
* Verifies the Henosia Auth JWT on the current request, or throws in case it's missing or invalid.
|
|
87
|
+
* This method must be called before allowing access to any protected pages or routes,
|
|
88
|
+
* and before accessing data in server side page methods or actions.
|
|
89
|
+
* @param request the current request which provides auth headers
|
|
90
|
+
* @return the better-auth `getAccessToken` response, the verified token `payload` (a {@link HenosiaAuthTokenClaims}),
|
|
91
|
+
* the `Set-Cookie` headers produced while obtaining/refreshing the token, and a
|
|
92
|
+
* `transferBetterAuthCookiesToNext` helper that forwards those cookies onto a Next.js request/response pair
|
|
93
|
+
* @throws APIError when the current credentials are missing or invalid
|
|
94
|
+
*/
|
|
95
|
+
declare function verifyHenosiaAuthToken(request: Request): Promise<{
|
|
96
|
+
accessTokenResponse: {
|
|
97
|
+
accessToken: string;
|
|
98
|
+
accessTokenExpiresAt: Date | undefined;
|
|
99
|
+
scopes: string[];
|
|
100
|
+
idToken: string | undefined;
|
|
101
|
+
};
|
|
102
|
+
payload: HenosiaAuthTokenClaims;
|
|
103
|
+
setCookieHeaders: string[];
|
|
104
|
+
transferBetterAuthCookiesToNext: (nextRequest: NextRequest, response: NextResponse) => void;
|
|
105
|
+
}>;
|
|
106
|
+
/**
|
|
107
|
+
* Determines whether the specified exception should start the sign-in flow to authenticate the user
|
|
108
|
+
* @param error the error thrown by `verifyHenosiaAuthToken`
|
|
109
|
+
*/
|
|
110
|
+
declare function isUnauthorizedException(error: unknown): boolean;
|
|
111
|
+
//#endregion
|
|
112
|
+
export { isPageOrRefreshTokenRequest as a, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAMES as c, isPageRequest as d, henosiaAuthConfig as i, HENOSIA_AUTH_MIDDLEWARE_UNAUTHENTICATED_PATH_NAME_PREFIXES as l, HenosiaAuthTokenClaims as n, isUnauthorizedException as o, auth as r, verifyHenosiaAuthToken as s, HenosiaAuthConfig as t, cookiePrefix as u };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*! Copyright (c) 2026 Henosia ApS. Licensed under the Henosia Commercial Source License v1.0. See LICENSE */
|
|
2
|
+
import { HENOSIA_AUTH_INVALID_SESSION_BODY, HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_FETCH_DEST_HEADER, HENOSIA_ORGANIZATION_CTX_COOKIE } from "./shared.mjs";
|
|
3
|
+
import { henosiaAuthConfig, isUnauthorizedException, verifyHenosiaAuthToken } from "./auth/server.mjs";
|
|
4
|
+
import { NextResponse } from "next/server.js";
|
|
5
|
+
//#region src/auth/utils.ts
|
|
6
|
+
function delayWithCancel(delayMs) {
|
|
7
|
+
let timer;
|
|
8
|
+
let reject = null;
|
|
9
|
+
const promise = new Promise((resolve, _reject) => {
|
|
10
|
+
reject = _reject;
|
|
11
|
+
timer = setTimeout(() => resolve(), delayMs);
|
|
12
|
+
});
|
|
13
|
+
const cancel = () => {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
queueMicrotask(() => {
|
|
16
|
+
reject();
|
|
17
|
+
reject = null;
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
promise,
|
|
22
|
+
cancel
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/auth/session-sync.ts
|
|
27
|
+
function syncOrganizationContextCookie(request, response, payload) {
|
|
28
|
+
const cachedOrg = request.cookies.get("henosia.org")?.value ?? null;
|
|
29
|
+
const org = JSON.stringify(payload["https://henosia.com/organization"] ?? null);
|
|
30
|
+
if (cachedOrg !== org) response.cookies.set(HENOSIA_ORGANIZATION_CTX_COOKIE, org);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Transfers cookies and downstream sessions that require the server auth graph.
|
|
34
|
+
*
|
|
35
|
+
* This runs from Node route handlers directly or through the middleware's internal sync route so
|
|
36
|
+
* `@henosia/app-next/auth/middleware` remains safe to bundle for Edge runtimes.
|
|
37
|
+
*/
|
|
38
|
+
async function syncHenosiaAuthSession(request, response, verified, options = {}) {
|
|
39
|
+
if (options.transferBetterAuthCookies ?? true) verified.transferBetterAuthCookiesToNext(request, response);
|
|
40
|
+
syncOrganizationContextCookie(request, response, verified.payload);
|
|
41
|
+
if (process.env.NEXT_PUBLIC_SUPABASE_URL) await exchangeHenosiaTokenForSupabaseSession(request, response, verified.accessTokenResponse.idToken);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Internal route handler used by middleware to perform server-only session sync before the matched request renders.
|
|
45
|
+
*/
|
|
46
|
+
async function handleHenosiaAuthSessionSync(request) {
|
|
47
|
+
try {
|
|
48
|
+
const verified = await verifyHenosiaAuthToken(buildOriginalRequestForSessionSync(request));
|
|
49
|
+
const response = new NextResponse(null, { status: 204 });
|
|
50
|
+
await syncHenosiaAuthSession(request, response, verified);
|
|
51
|
+
return applySessionSyncResponseHeaders(response);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (isUnauthorizedException(e)) return applySessionSyncResponseHeaders(NextResponse.json(HENOSIA_AUTH_INVALID_SESSION_BODY, { status: 401 }));
|
|
54
|
+
console.error("[Henosia Auth] session sync error", e);
|
|
55
|
+
return applySessionSyncResponseHeaders(NextResponse.json({ error: "Server error" }, { status: 500 }));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function applySessionSyncResponseHeaders(response) {
|
|
59
|
+
response.headers.set("Cache-Control", "private, no-store");
|
|
60
|
+
response.headers.set("Vary", "Cookie");
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
function buildOriginalRequestForSessionSync(request) {
|
|
64
|
+
const originalUrl = request.headers.get("x-henosia-auth-original-url") ?? request.url;
|
|
65
|
+
const originalMethod = request.headers.get("x-henosia-auth-original-method") ?? "GET";
|
|
66
|
+
const originalHeaders = new Headers(request.headers);
|
|
67
|
+
const originalFetchDest = request.headers.get(HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_FETCH_DEST_HEADER);
|
|
68
|
+
if (originalFetchDest) originalHeaders.set("X-Henosia-Fetch-Dest", originalFetchDest);
|
|
69
|
+
return new Request(originalUrl, {
|
|
70
|
+
headers: originalHeaders,
|
|
71
|
+
method: originalMethod
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Lazy-load `@supabase/ssr` so apps that don't use Supabase don't have to install it.
|
|
76
|
+
*/
|
|
77
|
+
async function exchangeHenosiaTokenForSupabaseSession(request, response, idToken) {
|
|
78
|
+
let createServerClient;
|
|
79
|
+
try {
|
|
80
|
+
({createServerClient} = await import("@supabase/ssr"));
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error("[Henosia Auth] NEXT_PUBLIC_SUPABASE_URL is set but `@supabase/ssr` is not installed. Install it as a peer dependency to enable Supabase session exchange.", e);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const supabase = createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, { cookies: {
|
|
86
|
+
getAll() {
|
|
87
|
+
return request.cookies.getAll();
|
|
88
|
+
},
|
|
89
|
+
setAll(cookiesToSet) {
|
|
90
|
+
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
|
|
91
|
+
try {
|
|
92
|
+
cookiesToSet.forEach(({ name, value, options }) => response.cookies.set(name, value, options));
|
|
93
|
+
} catch {}
|
|
94
|
+
}
|
|
95
|
+
} });
|
|
96
|
+
const { promise: supabaseTimeout, cancel } = delayWithCancel(5e3);
|
|
97
|
+
await Promise.race([supabaseTimeout.then(() => {
|
|
98
|
+
console.error(`[Supabase] Timed out getting a valid Supabase session: ${process.env.NEXT_PUBLIC_SUPABASE_URL} may be paused or down.`);
|
|
99
|
+
}), (async () => {
|
|
100
|
+
try {
|
|
101
|
+
const { data } = await supabase.auth.getSession();
|
|
102
|
+
if (!data.session) await supabase.auth.signInWithIdToken({
|
|
103
|
+
provider: henosiaAuthConfig.henosiaAuthSupabaseProvider.get(),
|
|
104
|
+
token: idToken
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error("[Supabase] Unable to get valid Supabase session", error);
|
|
108
|
+
} finally {
|
|
109
|
+
cancel();
|
|
110
|
+
}
|
|
111
|
+
})()]);
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { syncHenosiaAuthSession as n, syncOrganizationContextCookie as r, handleHenosiaAuthSessionSync as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/shared.d.ts
|
|
3
|
+
declare const HENOSIA_AUTH_SIGN_IN_PATH_NAME = "/sign-in";
|
|
4
|
+
/**
|
|
5
|
+
* The pathname for better-auth's `/get-session` endpoint, used by the React `useSession` hook.
|
|
6
|
+
*
|
|
7
|
+
* `useSession` automatically refetches this endpoint on browser tab focus (and optionally on a polling
|
|
8
|
+
* interval), which we leverage as a built-in keep-alive for the OIDC access/refresh tokens — see
|
|
9
|
+
* `isPageOrRefreshTokenRequest` and the middleware-backed session sync route.
|
|
10
|
+
*/
|
|
11
|
+
declare const HENOSIA_AUTH_GET_SESSION_PATH_NAME = "/api/auth/get-session";
|
|
12
|
+
/**
|
|
13
|
+
* Internal route used by the Edge middleware to delegate server-only token refresh and downstream session sync
|
|
14
|
+
* without importing the Better Auth server graph into the middleware bundle.
|
|
15
|
+
*/
|
|
16
|
+
declare const HENOSIA_AUTH_SESSION_SYNC_PATH_NAME = "/api/auth/henosia-session-sync";
|
|
17
|
+
declare const HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_URL_HEADER = "x-henosia-auth-original-url";
|
|
18
|
+
declare const HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_METHOD_HEADER = "x-henosia-auth-original-method";
|
|
19
|
+
declare const HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_FETCH_DEST_HEADER = "x-henosia-auth-original-fetch-dest";
|
|
20
|
+
type HenosiaOrganizationContext = {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
logoUrl: string | null;
|
|
24
|
+
} | null;
|
|
25
|
+
declare const HENOSIA_ORGANIZATION_CTX_COOKIE = "henosia.org";
|
|
26
|
+
declare const HENOSIA_AUTH_INVALID_SESSION_BODY: {
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_URL_HEADER as a, HENOSIA_ORGANIZATION_CTX_COOKIE as c, HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_METHOD_HEADER as i, HenosiaOrganizationContext as l, HENOSIA_AUTH_INVALID_SESSION_BODY as n, HENOSIA_AUTH_SESSION_SYNC_PATH_NAME as o, HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_FETCH_DEST_HEADER as r, HENOSIA_AUTH_SIGN_IN_PATH_NAME as s, HENOSIA_AUTH_GET_SESSION_PATH_NAME as t };
|
package/dist/shared.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { i as HenosiaOrganizationContext, n as
|
|
3
|
-
export { HENOSIA_AUTH_GET_SESSION_PATH_NAME, HENOSIA_AUTH_SIGN_IN_PATH_NAME, HENOSIA_ORGANIZATION_CTX_COOKIE, HenosiaOrganizationContext };
|
|
2
|
+
import { a as HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_URL_HEADER, c as HENOSIA_ORGANIZATION_CTX_COOKIE, i as HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_METHOD_HEADER, l as HenosiaOrganizationContext, n as HENOSIA_AUTH_INVALID_SESSION_BODY, o as HENOSIA_AUTH_SESSION_SYNC_PATH_NAME, r as HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_FETCH_DEST_HEADER, s as HENOSIA_AUTH_SIGN_IN_PATH_NAME, t as HENOSIA_AUTH_GET_SESSION_PATH_NAME } from "./shared-BWPBaubT.mjs";
|
|
3
|
+
export { HENOSIA_AUTH_GET_SESSION_PATH_NAME, HENOSIA_AUTH_INVALID_SESSION_BODY, HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_FETCH_DEST_HEADER, HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_METHOD_HEADER, HENOSIA_AUTH_SESSION_SYNC_ORIGINAL_URL_HEADER, HENOSIA_AUTH_SESSION_SYNC_PATH_NAME, HENOSIA_AUTH_SIGN_IN_PATH_NAME, HENOSIA_ORGANIZATION_CTX_COOKIE, HenosiaOrganizationContext };
|