@treasuryspatial/surface-kit 0.1.2 → 0.1.4

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.
@@ -0,0 +1,243 @@
1
+ import { NextResponse, type NextRequest } from 'next/server.js';
2
+ import type { UiManifest } from '@treasuryspatial/ui-manifest';
3
+ import { type SurfaceBrandingPayload, type SurfaceHostContext } from './index.js';
4
+ export declare const SURFACE_AUTH_COOKIE_NAME = "treasury_auth_token";
5
+ export type HeaderBag = {
6
+ get(name: string): string | null | undefined;
7
+ };
8
+ export type AssetTokenRequest = {
9
+ scope: string[];
10
+ prefix?: string;
11
+ ttl?: number;
12
+ audience?: string;
13
+ };
14
+ export type AssetTokenResponse = {
15
+ token: string;
16
+ expiresIn?: number;
17
+ expiresAt?: string;
18
+ };
19
+ export type AssetTokenRequester = (payload: AssetTokenRequest) => Promise<AssetTokenResponse>;
20
+ export type BrandingAssetCookie = {
21
+ name: string;
22
+ value: string;
23
+ maxAge: number;
24
+ secure: boolean;
25
+ sameSite: 'none' | 'lax';
26
+ domain?: string;
27
+ path: '/';
28
+ };
29
+ export type SurfaceMembership = {
30
+ tenantId?: string;
31
+ tenant_id?: string;
32
+ slug?: string;
33
+ tenantSlug?: string;
34
+ tenantName?: string;
35
+ membershipType?: string;
36
+ membership_type?: string;
37
+ productScopes?: string[];
38
+ roles?: string[];
39
+ };
40
+ type SurfacePermissions = {
41
+ isSuperAdmin?: boolean;
42
+ canManageTenants?: boolean;
43
+ canManageUsers?: boolean;
44
+ };
45
+ export type SurfaceAuthUser = {
46
+ email?: string | null;
47
+ displayName?: string | null;
48
+ givenName?: string | null;
49
+ familyName?: string | null;
50
+ memberships?: SurfaceMembership[];
51
+ subtenant?: {
52
+ slug?: string | null;
53
+ } | null;
54
+ subtenantSlug?: string | null;
55
+ permissions?: SurfacePermissions | null;
56
+ };
57
+ export type SurfaceRequestContext = {
58
+ host: string;
59
+ surfaceContext: SurfaceHostContext;
60
+ };
61
+ export type SurfaceServerFetchOptions = {
62
+ adminApiUrl?: string;
63
+ fetchImpl?: typeof fetch;
64
+ timeoutMs?: number;
65
+ };
66
+ export type SurfaceBootstrapOptions = SurfaceServerFetchOptions & {
67
+ headers: HeaderBag;
68
+ surfaceContext: SurfaceHostContext;
69
+ manifest?: UiManifest | null;
70
+ brandingPayload?: unknown;
71
+ requestAssetToken?: AssetTokenRequester;
72
+ assetScope?: string[];
73
+ assetPrefix?: string;
74
+ assetTtl?: number;
75
+ assetAudience?: string;
76
+ };
77
+ export type SurfaceBootstrapResult = {
78
+ branding: SurfaceBrandingPayload | null;
79
+ manifest: UiManifest | null;
80
+ assetCookie: BrandingAssetCookie | null;
81
+ };
82
+ export type SurfaceSessionValidation = {
83
+ valid: boolean;
84
+ authorized: boolean;
85
+ user: SurfaceAuthUser | null;
86
+ membership: SurfaceMembership | null;
87
+ payload: Record<string, unknown> | null;
88
+ };
89
+ export type SurfaceBrowserSession = {
90
+ browserToken: string;
91
+ user: SurfaceAuthUser | null;
92
+ membership: SurfaceMembership;
93
+ surfaceContext: SurfaceHostContext;
94
+ };
95
+ export type SurfaceBrowserSessionOptions = SurfaceServerFetchOptions & {
96
+ email: string;
97
+ password: string;
98
+ surfaceContext: SurfaceHostContext;
99
+ };
100
+ export type SurfaceCookieWriter = {
101
+ set: (name: string, value: string, options?: {
102
+ httpOnly?: boolean;
103
+ sameSite?: 'lax' | 'strict' | 'none';
104
+ secure?: boolean;
105
+ maxAge?: number;
106
+ domain?: string;
107
+ path?: string;
108
+ }) => void;
109
+ };
110
+ export type SurfaceCookieReader = {
111
+ get(name: string): {
112
+ value?: string;
113
+ } | undefined;
114
+ };
115
+ export type CreateSurfaceMiddlewareOptions = SurfaceServerFetchOptions & {
116
+ resolveSurfaceHostContext: (host?: string | null) => SurfaceHostContext;
117
+ requestAssetToken?: AssetTokenRequester;
118
+ bypass?: (pathname: string, surfaceContext: SurfaceHostContext) => boolean;
119
+ allowUnauthenticated?: (pathname: string, surfaceContext: SurfaceHostContext) => boolean;
120
+ attachBrandingCookie?: (pathname: string, surfaceContext: SurfaceHostContext) => boolean;
121
+ loginPath?: string;
122
+ clearCookieNames?: string[];
123
+ redirectAuthenticatedFromLogin?: (request: NextRequest, surfaceContext: SurfaceHostContext) => URL;
124
+ redirectUnauthenticated?: (request: NextRequest, surfaceContext: SurfaceHostContext) => URL;
125
+ redirectUnauthorized?: (request: NextRequest, surfaceContext: SurfaceHostContext) => URL;
126
+ onAuthorized?: (request: NextRequest, validation: SurfaceSessionValidation, surfaceContext: SurfaceHostContext) => Promise<NextResponse | null | undefined> | NextResponse | null | undefined;
127
+ };
128
+ export declare class SurfaceBrowserSessionError extends Error {
129
+ status: number;
130
+ constructor(status: number, message: string);
131
+ }
132
+ export declare const normalizeRequestHost: (value?: string | null) => string;
133
+ export declare const resolveRequestHost: (headers: HeaderBag) => string;
134
+ export declare const resolveSurfaceRequestContext: (requestOrHeaders: NextRequest | HeaderBag, resolveSurfaceHostContext: (host?: string | null) => SurfaceHostContext) => SurfaceRequestContext;
135
+ export declare const resolveAdminApiUrl: (pathname: string, explicitBase?: string) => string;
136
+ export declare const resolveTenantLookupSlug: (surfaceContext: SurfaceHostContext) => string;
137
+ export declare const fetchSurfaceTenant: (surfaceContext: SurfaceHostContext, options?: SurfaceServerFetchOptions & {
138
+ view?: string;
139
+ }) => Promise<any>;
140
+ export declare const fetchSurfaceBranding: (surfaceContext: SurfaceHostContext, options?: SurfaceServerFetchOptions) => Promise<any>;
141
+ export declare const resolveSurfaceBranding: (raw: unknown, fallbackSlug: string, isMarketingSlug?: (slug?: string | null) => boolean) => {
142
+ slug: string;
143
+ invalidSubdomain: boolean;
144
+ isEnvironment: boolean;
145
+ tenant?: {
146
+ displayName?: string | null;
147
+ } | null;
148
+ logos?: {
149
+ primary?: string | null;
150
+ [key: string]: unknown;
151
+ } | null;
152
+ organization?: {
153
+ logoUrl?: string | null;
154
+ } | null;
155
+ subtenant?: {
156
+ slug?: string | null;
157
+ name?: string | null;
158
+ logoUrl?: string | null;
159
+ logo_url?: string | null;
160
+ logos?: {
161
+ primary?: string | null;
162
+ [key: string]: unknown;
163
+ } | null;
164
+ branding?: {
165
+ name?: string | null;
166
+ logos?: {
167
+ primary?: string | null;
168
+ [key: string]: unknown;
169
+ } | null;
170
+ colors?: Record<string, string> | null;
171
+ typography?: Record<string, string> | null;
172
+ theme?: Record<string, string> | null;
173
+ } | null;
174
+ } | null;
175
+ branding?: {
176
+ name?: string | null;
177
+ logos?: {
178
+ primary?: string | null;
179
+ [key: string]: unknown;
180
+ } | null;
181
+ colors?: Record<string, string> | null;
182
+ typography?: Record<string, string> | null;
183
+ theme?: Record<string, string> | null;
184
+ } | null;
185
+ logoUrl?: string | null;
186
+ logo_url?: string | null;
187
+ } | null;
188
+ export declare const findSurfaceMembership: (memberships: SurfaceMembership[] | undefined, tenantSlug: string) => SurfaceMembership | undefined;
189
+ export declare const buildAdminAuthSelection: (surfaceContext: SurfaceHostContext) => {
190
+ tenantId: string;
191
+ requestedSubtenantSlug: string;
192
+ } | {
193
+ tenantId: string;
194
+ requestedSubtenantSlug?: undefined;
195
+ };
196
+ export declare const resolveSurfaceAccess: (user: SurfaceAuthUser | null | undefined, surfaceContext: SurfaceHostContext) => {
197
+ authorized: boolean;
198
+ membership: SurfaceMembership | null;
199
+ };
200
+ export declare const readSurfaceAuthToken: (cookies: SurfaceCookieReader) => string | null;
201
+ export declare const writeSurfaceAuthCookie: (cookies: SurfaceCookieWriter, token: string, options: {
202
+ secure: boolean;
203
+ maxAge: number;
204
+ path?: string;
205
+ domain?: string;
206
+ }) => void;
207
+ export declare const clearSurfaceAuthCookie: (cookies: SurfaceCookieWriter, path?: string) => void;
208
+ export declare const resolveSecureCookie: (forwardedProto: string | null, protocol: string) => boolean;
209
+ export declare const validateSurfaceBrowserSession: ({ token, surfaceContext, fetchImpl, adminApiUrl, }: SurfaceServerFetchOptions & {
210
+ token: string;
211
+ surfaceContext: SurfaceHostContext;
212
+ }) => Promise<SurfaceSessionValidation>;
213
+ export declare const exchangeSurfaceBrowserSession: ({ email, password, surfaceContext, fetchImpl, adminApiUrl, }: SurfaceBrowserSessionOptions) => Promise<SurfaceBrowserSession>;
214
+ export declare const shouldSignSurfaceBrandingAsset: (logoUrl: string | null) => boolean;
215
+ export declare const resolveBrandingAssetCookieOptions: (headers: HeaderBag) => {
216
+ name: string;
217
+ domain: string | undefined;
218
+ sameSite: "none" | "lax";
219
+ secure: boolean;
220
+ };
221
+ export declare const buildBrandingAssetCookie: ({ headers, logoUrl, requestAssetToken, assetScope, assetPrefix, assetTtl, assetAudience, }: {
222
+ headers: HeaderBag;
223
+ logoUrl: string | null;
224
+ requestAssetToken: AssetTokenRequester;
225
+ assetScope?: string[];
226
+ assetPrefix?: string;
227
+ assetTtl?: number;
228
+ assetAudience?: string;
229
+ }) => Promise<BrandingAssetCookie | null>;
230
+ export declare const buildSurfaceBrandingAssetCookie: ({ headers, surfaceContext, requestAssetToken, assetScope, assetPrefix, assetTtl, assetAudience, ...fetchOptions }: SurfaceServerFetchOptions & {
231
+ headers: HeaderBag;
232
+ surfaceContext: SurfaceHostContext;
233
+ requestAssetToken: AssetTokenRequester;
234
+ assetScope?: string[];
235
+ assetPrefix?: string;
236
+ assetTtl?: number;
237
+ assetAudience?: string;
238
+ }) => Promise<BrandingAssetCookie | null>;
239
+ export declare const bootstrapSurfaceBranding: ({ headers, surfaceContext, manifest, brandingPayload, requestAssetToken, assetScope, assetPrefix, assetTtl, assetAudience, ...fetchOptions }: SurfaceBootstrapOptions) => Promise<SurfaceBootstrapResult>;
240
+ export declare const isSurfaceStaticBypassPath: (pathname: string, extraPrefixes?: string[]) => boolean;
241
+ export declare const createSurfaceMiddleware: ({ resolveSurfaceHostContext, requestAssetToken, bypass, allowUnauthenticated, attachBrandingCookie, loginPath, clearCookieNames, redirectAuthenticatedFromLogin, redirectUnauthenticated, redirectUnauthorized, onAuthorized, ...fetchOptions }: CreateSurfaceMiddlewareOptions) => (request: NextRequest) => Promise<NextResponse<unknown>>;
242
+ export {};
243
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/server.js ADDED
@@ -0,0 +1,439 @@
1
+ import { NextResponse } from 'next/server.js';
2
+ import { applySurfaceBrandingToManifest, normalizeSurfaceBrandingPayload, } from './index.js';
3
+ export const SURFACE_AUTH_COOKIE_NAME = 'treasury_auth_token';
4
+ const DEFAULT_ADMIN_API_URL = 'https://admin-api.treasury.space/api';
5
+ const DEFAULT_BRANDING_SCOPE = ['admin'];
6
+ const DEFAULT_BRANDING_PREFIX = 'admin';
7
+ const DEFAULT_BRANDING_TTL = 600;
8
+ const DEFAULT_BRANDING_AUDIENCE = 'assets';
9
+ const DEFAULT_BYPASS_EXACT = new Set(['/favicon.ico', '/robots.txt', '/sitemap.xml']);
10
+ const DEFAULT_BYPASS_PREFIXES = ['/_next', '/fonts', '/textures'];
11
+ const DEFAULT_BYPASS_PATTERN = /\.(?:css|js|map|ico|png|jpg|jpeg|gif|svg|webp|avif|woff2?|ttf|otf|eot|wasm|json|txt|xml|csv)$/i;
12
+ const readEnv = (...values) => {
13
+ for (const value of values) {
14
+ const trimmed = value?.trim();
15
+ if (trimmed)
16
+ return trimmed;
17
+ }
18
+ return '';
19
+ };
20
+ const normalizeTenant = (value) => String(value || '').trim().toLowerCase();
21
+ const isLocalUrl = (value) => value.includes('localhost') || value.includes('127.0.0.1');
22
+ const buildServiceAuthHeaders = () => {
23
+ const serviceToken = readEnv(process.env.TREASURY_SERVICE_TOKEN);
24
+ const headers = {
25
+ Accept: 'application/json',
26
+ };
27
+ if (serviceToken) {
28
+ headers.Authorization = `TreasuryService ${serviceToken}`;
29
+ }
30
+ return headers;
31
+ };
32
+ const fetchJson = async (url, { fetchImpl = fetch, timeoutMs = 2500, } = {}) => {
33
+ const controller = new AbortController();
34
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
35
+ try {
36
+ const response = await fetchImpl(url, {
37
+ signal: controller.signal,
38
+ cache: 'no-store',
39
+ headers: buildServiceAuthHeaders(),
40
+ });
41
+ if (!response.ok)
42
+ return null;
43
+ return response.json();
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ finally {
49
+ clearTimeout(timeoutId);
50
+ }
51
+ };
52
+ export class SurfaceBrowserSessionError extends Error {
53
+ status;
54
+ constructor(status, message) {
55
+ super(message);
56
+ this.status = status;
57
+ }
58
+ }
59
+ export const normalizeRequestHost = (value) => (value || '')
60
+ .split(',')[0]
61
+ .trim()
62
+ .toLowerCase()
63
+ .replace(/:\d+$/, '');
64
+ export const resolveRequestHost = (headers) => normalizeRequestHost(headers.get('x-forwarded-host') || headers.get('host'));
65
+ const isHeaderBag = (value) => Boolean(value) && typeof value === 'object' && typeof value.get === 'function';
66
+ const coerceHeaderBag = (requestOrHeaders) => {
67
+ if (isHeaderBag(requestOrHeaders)) {
68
+ return requestOrHeaders;
69
+ }
70
+ if (requestOrHeaders &&
71
+ typeof requestOrHeaders === 'object' &&
72
+ 'headers' in requestOrHeaders &&
73
+ isHeaderBag(requestOrHeaders.headers)) {
74
+ return requestOrHeaders.headers;
75
+ }
76
+ throw new TypeError('Surface request context requires a headers-like object');
77
+ };
78
+ export const resolveSurfaceRequestContext = (requestOrHeaders, resolveSurfaceHostContext) => {
79
+ const headers = coerceHeaderBag(requestOrHeaders);
80
+ const host = resolveRequestHost(headers);
81
+ return {
82
+ host,
83
+ surfaceContext: resolveSurfaceHostContext(host),
84
+ };
85
+ };
86
+ export const resolveAdminApiUrl = (pathname, explicitBase) => {
87
+ const rawBase = explicitBase ||
88
+ process.env.ADMIN_API_URL ||
89
+ process.env.ADMIN_API_BASE_URL ||
90
+ process.env.NEXT_PUBLIC_ADMIN_API_URL ||
91
+ DEFAULT_ADMIN_API_URL;
92
+ const base = rawBase.replace(/\/+$/, '');
93
+ if (base.endsWith('/api'))
94
+ return `${base}${pathname}`;
95
+ return `${base}/api${pathname}`;
96
+ };
97
+ const getAdminCandidates = (explicitBase) => {
98
+ const baseUrl = resolveAdminApiUrl('', explicitBase).replace(/\/$/, '');
99
+ if (isLocalUrl(baseUrl) && baseUrl !== DEFAULT_ADMIN_API_URL) {
100
+ return [baseUrl, DEFAULT_ADMIN_API_URL];
101
+ }
102
+ return [baseUrl];
103
+ };
104
+ export const resolveTenantLookupSlug = (surfaceContext) => surfaceContext.subtenantSlug ? surfaceContext.tenantSlug : surfaceContext.effectiveTenantSlug;
105
+ export const fetchSurfaceTenant = async (surfaceContext, options = {}) => {
106
+ const slug = resolveTenantLookupSlug(surfaceContext);
107
+ const subtenantQuery = surfaceContext.subtenantSlug
108
+ ? `&subtenantSlug=${encodeURIComponent(surfaceContext.subtenantSlug)}`
109
+ : '';
110
+ for (const baseUrl of getAdminCandidates(options.adminApiUrl)) {
111
+ const url = `${baseUrl}/tenants/${encodeURIComponent(slug)}?view=${encodeURIComponent(options.view ?? 'composer')}${subtenantQuery}`;
112
+ const payload = await fetchJson(url, options);
113
+ if (payload)
114
+ return payload;
115
+ }
116
+ return null;
117
+ };
118
+ export const fetchSurfaceBranding = async (surfaceContext, options = {}) => {
119
+ const slug = resolveTenantLookupSlug(surfaceContext);
120
+ const subtenantQuery = surfaceContext.subtenantSlug
121
+ ? `?subtenantSlug=${encodeURIComponent(surfaceContext.subtenantSlug)}`
122
+ : '';
123
+ for (const baseUrl of getAdminCandidates(options.adminApiUrl)) {
124
+ const urls = slug
125
+ ? [
126
+ `${baseUrl}/branding/${encodeURIComponent(slug)}${subtenantQuery}`,
127
+ `${baseUrl}/platform/branding?tenantId=${encodeURIComponent(slug)}${surfaceContext.subtenantSlug
128
+ ? `&subtenantSlug=${encodeURIComponent(surfaceContext.subtenantSlug)}`
129
+ : ''}`,
130
+ ]
131
+ : [`${baseUrl}/branding`];
132
+ for (const url of urls) {
133
+ const payload = await fetchJson(url, options);
134
+ if (payload)
135
+ return payload;
136
+ }
137
+ }
138
+ return null;
139
+ };
140
+ export const resolveSurfaceBranding = (raw, fallbackSlug, isMarketingSlug) => {
141
+ const payload = normalizeSurfaceBrandingPayload(raw) ?? null;
142
+ const source = raw && typeof raw === 'object' && !Array.isArray(raw) && 'data' in raw && raw.data
143
+ ? raw.data
144
+ : (raw ?? {});
145
+ const slug = normalizeTenant(source?.slug || source?.tenant?.slug || fallbackSlug);
146
+ return payload
147
+ ? {
148
+ ...payload,
149
+ slug: slug || fallbackSlug,
150
+ invalidSubdomain: source?.invalidSubdomain ?? payload.invalidSubdomain ?? false,
151
+ isEnvironment: isMarketingSlug ? isMarketingSlug(slug || fallbackSlug) : payload.isEnvironment ?? false,
152
+ }
153
+ : null;
154
+ };
155
+ export const findSurfaceMembership = (memberships = [], tenantSlug) => {
156
+ const target = normalizeTenant(tenantSlug);
157
+ return memberships.find((membership) => {
158
+ const membershipTenant = normalizeTenant(membership.tenantSlug || membership.slug || membership.tenantId || membership.tenant_id);
159
+ return membershipTenant === target;
160
+ });
161
+ };
162
+ const isPrivilegedUser = (user) => Boolean(user?.permissions?.isSuperAdmin ||
163
+ user?.permissions?.canManageTenants ||
164
+ user?.permissions?.canManageUsers);
165
+ export const buildAdminAuthSelection = (surfaceContext) => {
166
+ const tenantId = surfaceContext.subtenantSlug
167
+ ? surfaceContext.tenantSlug
168
+ : surfaceContext.effectiveTenantSlug;
169
+ return surfaceContext.subtenantSlug
170
+ ? {
171
+ tenantId,
172
+ requestedSubtenantSlug: surfaceContext.subtenantSlug,
173
+ }
174
+ : { tenantId };
175
+ };
176
+ export const resolveSurfaceAccess = (user, surfaceContext) => {
177
+ const memberships = Array.isArray(user?.memberships) ? user.memberships : [];
178
+ const canonicalMembership = findSurfaceMembership(memberships, surfaceContext.tenantSlug);
179
+ const compatibilityMembership = findSurfaceMembership(memberships, surfaceContext.effectiveTenantSlug);
180
+ const requestedSubtenantSlug = normalizeTenant(surfaceContext.subtenantSlug);
181
+ const resolvedSubtenantSlug = normalizeTenant(user?.subtenantSlug || user?.subtenant?.slug);
182
+ if (!requestedSubtenantSlug) {
183
+ const membership = canonicalMembership || compatibilityMembership || null;
184
+ return { authorized: Boolean(membership), membership };
185
+ }
186
+ if (resolvedSubtenantSlug && resolvedSubtenantSlug === requestedSubtenantSlug) {
187
+ const membership = canonicalMembership || compatibilityMembership || null;
188
+ return { authorized: Boolean(membership), membership };
189
+ }
190
+ if (compatibilityMembership) {
191
+ return { authorized: true, membership: compatibilityMembership };
192
+ }
193
+ if (canonicalMembership && isPrivilegedUser(user)) {
194
+ return { authorized: true, membership: canonicalMembership };
195
+ }
196
+ return {
197
+ authorized: false,
198
+ membership: canonicalMembership || compatibilityMembership || null,
199
+ };
200
+ };
201
+ export const readSurfaceAuthToken = (cookies) => cookies.get(SURFACE_AUTH_COOKIE_NAME)?.value?.trim() || null;
202
+ export const writeSurfaceAuthCookie = (cookies, token, options) => {
203
+ cookies.set(SURFACE_AUTH_COOKIE_NAME, token, {
204
+ httpOnly: true,
205
+ sameSite: 'lax',
206
+ secure: options.secure,
207
+ maxAge: options.maxAge,
208
+ domain: options.domain,
209
+ path: options.path || '/',
210
+ });
211
+ };
212
+ export const clearSurfaceAuthCookie = (cookies, path = '/') => {
213
+ cookies.set(SURFACE_AUTH_COOKIE_NAME, '', { path, maxAge: 0 });
214
+ };
215
+ export const resolveSecureCookie = (forwardedProto, protocol) => forwardedProto ? forwardedProto === 'https' : protocol === 'https:';
216
+ export const validateSurfaceBrowserSession = async ({ token, surfaceContext, fetchImpl = fetch, adminApiUrl, }) => {
217
+ try {
218
+ const response = await fetchImpl(resolveAdminApiUrl('/auth/validate', adminApiUrl), {
219
+ method: 'POST',
220
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
221
+ body: JSON.stringify({ token, ...buildAdminAuthSelection(surfaceContext) }),
222
+ cache: 'no-store',
223
+ });
224
+ const payload = (await response.json().catch(() => null));
225
+ const valid = Boolean(payload?.valid ?? payload?.success);
226
+ if (!response.ok || !valid) {
227
+ return { valid: false, authorized: false, user: null, membership: null, payload };
228
+ }
229
+ const user = payload?.user ?? null;
230
+ const access = resolveSurfaceAccess(user, surfaceContext);
231
+ return {
232
+ valid: Boolean(access.authorized && access.membership),
233
+ authorized: access.authorized,
234
+ user,
235
+ membership: access.membership,
236
+ payload,
237
+ };
238
+ }
239
+ catch {
240
+ return { valid: false, authorized: false, user: null, membership: null, payload: null };
241
+ }
242
+ };
243
+ export const exchangeSurfaceBrowserSession = async ({ email, password, surfaceContext, fetchImpl = fetch, adminApiUrl, }) => {
244
+ let response;
245
+ try {
246
+ response = await fetchImpl(resolveAdminApiUrl('/auth/login', adminApiUrl), {
247
+ method: 'POST',
248
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
249
+ body: JSON.stringify({ email, password, ...buildAdminAuthSelection(surfaceContext) }),
250
+ cache: 'no-store',
251
+ });
252
+ }
253
+ catch (error) {
254
+ throw new SurfaceBrowserSessionError(502, `Admin auth bridge failed: ${error?.message || 'fetch failed'}`);
255
+ }
256
+ const payload = (await response.json().catch(() => null));
257
+ const missingBrowserToken = payload?.success && !payload?.browserToken;
258
+ if (!response.ok || !payload?.success || missingBrowserToken) {
259
+ throw new SurfaceBrowserSessionError(missingBrowserToken ? 502 : response.status || 401, payload?.error || (missingBrowserToken ? 'Admin login response missing browserToken' : 'Invalid credentials'));
260
+ }
261
+ const user = payload.user ?? null;
262
+ const access = resolveSurfaceAccess(user, surfaceContext);
263
+ if (!access.authorized || !access.membership) {
264
+ throw new SurfaceBrowserSessionError(403, 'Account not enabled for this Treasury tenant');
265
+ }
266
+ return {
267
+ browserToken: payload.browserToken,
268
+ user,
269
+ membership: access.membership,
270
+ surfaceContext,
271
+ };
272
+ };
273
+ export const shouldSignSurfaceBrandingAsset = (logoUrl) => {
274
+ if (!logoUrl)
275
+ return false;
276
+ try {
277
+ const parsed = new URL(logoUrl);
278
+ return parsed.hostname === 'assets.treasury.space' && parsed.pathname.toLowerCase().startsWith('/admin/');
279
+ }
280
+ catch {
281
+ return false;
282
+ }
283
+ };
284
+ export const resolveBrandingAssetCookieOptions = (headers) => {
285
+ const host = resolveRequestHost(headers);
286
+ const protocol = headers.get('x-forwarded-proto') ?? 'https';
287
+ const isTreasury = /(?:^|\.)treasury\.space$/.test(host);
288
+ return {
289
+ name: readEnv(process.env.ASSETS_COOKIE_NAME) || 'treasury_assets_token',
290
+ domain: isTreasury ? '.treasury.space' : undefined,
291
+ sameSite: isTreasury ? 'none' : 'lax',
292
+ secure: isTreasury ? true : protocol === 'https',
293
+ };
294
+ };
295
+ export const buildBrandingAssetCookie = async ({ headers, logoUrl, requestAssetToken, assetScope = DEFAULT_BRANDING_SCOPE, assetPrefix = DEFAULT_BRANDING_PREFIX, assetTtl = DEFAULT_BRANDING_TTL, assetAudience = DEFAULT_BRANDING_AUDIENCE, }) => {
296
+ if (!shouldSignSurfaceBrandingAsset(logoUrl)) {
297
+ return null;
298
+ }
299
+ const token = await requestAssetToken({
300
+ scope: assetScope,
301
+ prefix: assetPrefix,
302
+ ttl: assetTtl,
303
+ audience: assetAudience,
304
+ });
305
+ const cookie = resolveBrandingAssetCookieOptions(headers);
306
+ return {
307
+ ...cookie,
308
+ value: token.token,
309
+ maxAge: token.expiresIn ?? assetTtl,
310
+ path: '/',
311
+ };
312
+ };
313
+ export const buildSurfaceBrandingAssetCookie = async ({ headers, surfaceContext, requestAssetToken, assetScope = DEFAULT_BRANDING_SCOPE, assetPrefix = DEFAULT_BRANDING_PREFIX, assetTtl = DEFAULT_BRANDING_TTL, assetAudience = DEFAULT_BRANDING_AUDIENCE, ...fetchOptions }) => {
314
+ const brandingPayload = await fetchSurfaceBranding(surfaceContext, fetchOptions);
315
+ const branding = normalizeSurfaceBrandingPayload(brandingPayload);
316
+ return buildBrandingAssetCookie({
317
+ headers,
318
+ logoUrl: branding?.logoUrl ?? null,
319
+ requestAssetToken,
320
+ assetScope,
321
+ assetPrefix,
322
+ assetTtl,
323
+ assetAudience,
324
+ });
325
+ };
326
+ export const bootstrapSurfaceBranding = async ({ headers, surfaceContext, manifest = null, brandingPayload, requestAssetToken, assetScope, assetPrefix, assetTtl, assetAudience, ...fetchOptions }) => {
327
+ const brandingSource = brandingPayload ?? (await fetchSurfaceBranding(surfaceContext, fetchOptions));
328
+ const branding = normalizeSurfaceBrandingPayload(brandingSource);
329
+ const brandedManifest = manifest ? applySurfaceBrandingToManifest(manifest, branding) : null;
330
+ const assetCookie = requestAssetToken && branding?.logoUrl
331
+ ? await buildBrandingAssetCookie({
332
+ headers,
333
+ logoUrl: branding.logoUrl,
334
+ requestAssetToken,
335
+ assetScope,
336
+ assetPrefix,
337
+ assetTtl,
338
+ assetAudience,
339
+ })
340
+ : null;
341
+ return {
342
+ branding,
343
+ manifest: brandedManifest,
344
+ assetCookie,
345
+ };
346
+ };
347
+ export const isSurfaceStaticBypassPath = (pathname, extraPrefixes = []) => {
348
+ if (DEFAULT_BYPASS_EXACT.has(pathname))
349
+ return true;
350
+ if ([...DEFAULT_BYPASS_PREFIXES, ...extraPrefixes].some((prefix) => pathname.startsWith(prefix)))
351
+ return true;
352
+ return DEFAULT_BYPASS_PATTERN.test(pathname);
353
+ };
354
+ export const createSurfaceMiddleware = ({ resolveSurfaceHostContext, requestAssetToken, bypass, allowUnauthenticated, attachBrandingCookie, loginPath = '/login', clearCookieNames = [], redirectAuthenticatedFromLogin, redirectUnauthenticated, redirectUnauthorized, onAuthorized, ...fetchOptions }) => {
355
+ const defaultRedirectToLogin = (request) => {
356
+ const redirectUrl = request.nextUrl.clone();
357
+ redirectUrl.pathname = loginPath;
358
+ redirectUrl.search = `?from=${encodeURIComponent(`${request.nextUrl.pathname}${request.nextUrl.search}`)}`;
359
+ return redirectUrl;
360
+ };
361
+ const maybeAttachBrandingCookie = async (request, response, surfaceContext) => {
362
+ const shouldAttach = attachBrandingCookie ? attachBrandingCookie(request.nextUrl.pathname, surfaceContext) : false;
363
+ if (!shouldAttach || !requestAssetToken) {
364
+ return response;
365
+ }
366
+ try {
367
+ const { assetCookie } = await bootstrapSurfaceBranding({
368
+ headers: request.headers,
369
+ surfaceContext,
370
+ requestAssetToken,
371
+ ...fetchOptions,
372
+ });
373
+ if (assetCookie) {
374
+ response.cookies.set(assetCookie.name, assetCookie.value, {
375
+ httpOnly: false,
376
+ secure: assetCookie.secure,
377
+ sameSite: assetCookie.sameSite,
378
+ domain: assetCookie.domain,
379
+ path: assetCookie.path,
380
+ maxAge: assetCookie.maxAge,
381
+ });
382
+ }
383
+ }
384
+ catch {
385
+ // Branding bootstrap is best-effort; page delivery should not fail.
386
+ }
387
+ return response;
388
+ };
389
+ const clearCookies = (response) => {
390
+ clearSurfaceAuthCookie(response.cookies);
391
+ clearCookieNames.forEach((name) => response.cookies.set(name, '', { path: '/', maxAge: 0 }));
392
+ return response;
393
+ };
394
+ return async (request) => {
395
+ const { pathname } = request.nextUrl;
396
+ const { surfaceContext } = resolveSurfaceRequestContext(request, resolveSurfaceHostContext);
397
+ if (isSurfaceStaticBypassPath(pathname) || bypass?.(pathname, surfaceContext) || pathname.startsWith('/api/')) {
398
+ return NextResponse.next();
399
+ }
400
+ const token = readSurfaceAuthToken(request.cookies);
401
+ if (pathname === loginPath) {
402
+ if (!token) {
403
+ return maybeAttachBrandingCookie(request, NextResponse.next(), surfaceContext);
404
+ }
405
+ const validation = await validateSurfaceBrowserSession({
406
+ token,
407
+ surfaceContext,
408
+ ...fetchOptions,
409
+ });
410
+ if (validation.valid) {
411
+ return NextResponse.redirect(redirectAuthenticatedFromLogin?.(request, surfaceContext) ?? new URL('/', request.url));
412
+ }
413
+ return maybeAttachBrandingCookie(request, clearCookies(NextResponse.next()), surfaceContext);
414
+ }
415
+ const isPublic = allowUnauthenticated?.(pathname, surfaceContext) ?? false;
416
+ if (!token) {
417
+ if (isPublic) {
418
+ return maybeAttachBrandingCookie(request, NextResponse.next(), surfaceContext);
419
+ }
420
+ return NextResponse.redirect(redirectUnauthenticated?.(request, surfaceContext) ?? defaultRedirectToLogin(request));
421
+ }
422
+ const validation = await validateSurfaceBrowserSession({
423
+ token,
424
+ surfaceContext,
425
+ ...fetchOptions,
426
+ });
427
+ if (!validation.valid) {
428
+ if (isPublic) {
429
+ return maybeAttachBrandingCookie(request, clearCookies(NextResponse.next()), surfaceContext);
430
+ }
431
+ return clearCookies(NextResponse.redirect(redirectUnauthorized?.(request, surfaceContext) ?? defaultRedirectToLogin(request)));
432
+ }
433
+ const authorizedResponse = await onAuthorized?.(request, validation, surfaceContext);
434
+ if (authorizedResponse) {
435
+ return authorizedResponse;
436
+ }
437
+ return maybeAttachBrandingCookie(request, NextResponse.next(), surfaceContext);
438
+ };
439
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treasuryspatial/surface-kit",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/index.js",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
+ },
13
+ "./server": {
14
+ "types": "./dist/server.d.ts",
15
+ "default": "./dist/server.js"
12
16
  }
13
17
  },
14
18
  "files": [
@@ -21,6 +25,9 @@
21
25
  "@treasuryspatial/mode-policy": "^0.1.5",
22
26
  "@treasuryspatial/ui-manifest": "^0.1.7"
23
27
  },
28
+ "peerDependencies": {
29
+ "next": "^15.0.0 || ^16.0.0"
30
+ },
24
31
  "scripts": {
25
32
  "build": "tsc -b",
26
33
  "test": "npm run build && node --test test/*.test.mjs",