swallowkit 1.0.0-beta.53 → 1.0.0-beta.55

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.
@@ -1439,60 +1439,122 @@ export async function callFunction<TInput = any, TOutput = any>(
1439
1439
  `;
1440
1440
  }
1441
1441
 
1442
- /** SWA built-in auth variant: forward only a platform-derived client principal. */
1443
- export function generateBFFCallFunctionWithSwaAuth(): string {
1444
- return generateBFFCallFunctionWithAuth()
1445
- .replace(
1446
- ` // Authorization ヘッダーの転送(Proxy が cookie → Authorization に変換済み)`,
1447
- ` // SWA client principal is accepted only from the platform request context.`
1448
- )
1449
- .replace(
1450
- ` const authorization = reqHeaders.get('authorization');
1451
- if (authorization) {
1452
- fetchHeaders['Authorization'] = authorization;
1453
- }`,
1454
- ` // Never forward a browser-supplied principal header. Resolve it from the SWA session endpoint.
1455
- let principal: string | null = null;
1456
- {
1442
+ function generateBFFSwaCredentialForwarding(includeBearer: boolean): string {
1443
+ const bearerForwarding = includeBearer
1444
+ ? ` const authorization = reqHeaders.get('authorization');
1445
+ if (authorization?.startsWith('Bearer ')) {
1446
+ fetchHeaders.Authorization = authorization;
1447
+ }
1448
+
1449
+ `
1450
+ : "";
1451
+
1452
+ return `${bearerForwarding} let principal = reqHeaders.get('x-ms-client-principal');
1453
+
1454
+ if (!principal) {
1457
1455
  const cookie = reqHeaders.get('cookie');
1458
- const host = reqHeaders.get('host');
1456
+ const forwardedHost = reqHeaders.get('x-forwarded-host');
1457
+ const requestHost = reqHeaders.get('host');
1458
+ const host = (forwardedHost || requestHost)?.split(',')[0].trim();
1459
+
1459
1460
  if (cookie && host) {
1460
- const proto = reqHeaders.get('x-forwarded-proto') || 'https';
1461
+ const forwardedProto = reqHeaders.get('x-forwarded-proto');
1462
+ const proto =
1463
+ forwardedProto?.split(',')[0].trim() ||
1464
+ (isLocalFunctionsUrl(functionsBaseUrl) ? 'http' : 'https');
1465
+
1461
1466
  const meResponse = await fetch(\`\${proto}://\${host}/.auth/me\`, {
1462
1467
  headers: { cookie },
1463
1468
  cache: 'no-store',
1464
1469
  });
1470
+
1465
1471
  if (meResponse.ok) {
1466
- const payload = await meResponse.json() as { clientPrincipal?: unknown };
1472
+ const payload = (await meResponse.json()) as {
1473
+ clientPrincipal?: unknown;
1474
+ };
1475
+
1467
1476
  if (payload.clientPrincipal) {
1468
- principal = Buffer.from(JSON.stringify(payload.clientPrincipal), 'utf8').toString('base64');
1477
+ principal = Buffer.from(
1478
+ JSON.stringify(payload.clientPrincipal),
1479
+ 'utf8'
1480
+ ).toString('base64');
1469
1481
  }
1470
1482
  }
1471
1483
  }
1472
1484
  }
1485
+
1473
1486
  if (principal) {
1474
- const parsed = JSON.parse(Buffer.from(principal, 'base64').toString('utf8'));
1487
+ const decodedPrincipal = Buffer.from(principal, 'base64');
1488
+ if (decodedPrincipal.toString('base64') !== principal) {
1489
+ throw new Error('Invalid SWA client principal encoding');
1490
+ }
1491
+ const parsed = JSON.parse(
1492
+ decodedPrincipal.toString('utf8')
1493
+ ) as Record<string, unknown>;
1494
+
1475
1495
  const normalized = {
1476
- identityProvider: String(parsed.identityProvider || ''),
1477
- userId: String(parsed.userId || ''),
1478
- userDetails: String(parsed.userDetails || ''),
1479
- userRoles: Array.isArray(parsed.userRoles) ? parsed.userRoles.filter((role: unknown) => typeof role === 'string') : [],
1496
+ identityProvider:
1497
+ typeof parsed.identityProvider === 'string'
1498
+ ? parsed.identityProvider
1499
+ : '',
1500
+ userId: typeof parsed.userId === 'string' ? parsed.userId : '',
1501
+ userDetails:
1502
+ typeof parsed.userDetails === 'string'
1503
+ ? parsed.userDetails
1504
+ : '',
1505
+ userRoles: Array.isArray(parsed.userRoles)
1506
+ ? parsed.userRoles.filter(
1507
+ (role: unknown): role is string => typeof role === 'string'
1508
+ )
1509
+ : [],
1480
1510
  };
1481
- if (normalized.userId && normalized.userRoles.includes('authenticated')) {
1482
- fetchHeaders['x-ms-client-principal'] = Buffer.from(JSON.stringify(normalized), 'utf8').toString('base64');
1511
+
1512
+ if (
1513
+ normalized.identityProvider &&
1514
+ normalized.userId &&
1515
+ normalized.userRoles.includes('authenticated')
1516
+ ) {
1517
+ fetchHeaders['x-ms-client-principal'] = Buffer.from(
1518
+ JSON.stringify(normalized),
1519
+ 'utf8'
1520
+ ).toString('base64');
1483
1521
  }
1484
- }`
1522
+ }`;
1523
+ }
1524
+
1525
+ function generateBFFCallFunctionWithSwaCredentials(includeBearer: boolean): string {
1526
+ return generateBFFCallFunctionWithAuth()
1527
+ .replace(
1528
+ ` // Authorization ヘッダーの転送(Proxy が cookie → Authorization に変換済み)`,
1529
+ ` // Forward only validated credentials from the current request.`
1530
+ )
1531
+ .replace(
1532
+ ` const authorization = reqHeaders.get('authorization');
1533
+ if (authorization) {
1534
+ fetchHeaders['Authorization'] = authorization;
1535
+ }`,
1536
+ generateBFFSwaCredentialForwarding(includeBearer)
1537
+ )
1538
+ .replace(
1539
+ ` } catch {
1540
+ // headers() が使えないコンテキスト(ISR 等)では無視
1541
+ }`,
1542
+ ` } catch (error) {
1543
+ // Do not include credentials or request header values in authentication logs.
1544
+ const errorType = error instanceof Error ? error.name : 'UnknownError';
1545
+ console.error('[BFF] Authentication forwarding failed:', errorType);
1546
+ }`
1485
1547
  );
1486
1548
  }
1487
1549
 
1550
+ /** SWA built-in auth variant: forward only a validated SWA client principal. */
1551
+ export function generateBFFCallFunctionWithSwaAuth(): string {
1552
+ return generateBFFCallFunctionWithSwaCredentials(false);
1553
+ }
1554
+
1488
1555
  /** Combined transport for named schemes. Functions still performs all verification. */
1489
1556
  export function generateBFFCallFunctionWithMultipleAuth(): string {
1490
- return generateBFFCallFunctionWithSwaAuth().replace(
1491
- ` // Never forward a browser-supplied principal header. Resolve it from the SWA session endpoint.`,
1492
- ` const authorization = reqHeaders.get('authorization');
1493
- if (authorization?.startsWith('Bearer ')) fetchHeaders['Authorization'] = authorization;
1494
- // Never forward a browser-supplied principal header. Resolve it from the SWA session endpoint.`
1495
- );
1557
+ return generateBFFCallFunctionWithSwaCredentials(true);
1496
1558
  }
1497
1559
 
1498
1560
  export function generateSwaAuthHelperTS(): string {
@@ -15,6 +15,27 @@ export interface UIAuthOptions {
15
15
  authContextImport?: string;
16
16
  }
17
17
 
18
+ /**
19
+ * 認証付きCRUDルートを、ページ側のuseAuthと同じAuthProviderで囲むレイアウトを生成する。
20
+ */
21
+ export function generateAuthLayout(model: ModelInfo, authOptions?: UIAuthOptions): string | undefined {
22
+ if (!authOptions) return undefined;
23
+
24
+ const authContextImport = authOptions.authContextImport ?? '@/lib/auth/auth-context';
25
+
26
+ return `import { AuthProvider } from '${authContextImport}';
27
+ import type { ReactNode } from 'react';
28
+
29
+ export default function ${model.name}Layout({
30
+ children,
31
+ }: {
32
+ children: ReactNode;
33
+ }) {
34
+ return <AuthProvider>{children}</AuthProvider>;
35
+ }
36
+ `;
37
+ }
38
+
18
39
  /**
19
40
  * 一覧画面を生成
20
41
  */