@markwharton/pwa-core 3.4.2 → 3.5.0

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/dist/server.d.ts CHANGED
@@ -676,4 +676,31 @@ export declare function deleteExpiredSessions(): Promise<number>;
676
676
  * context.log(`Cleaned up ${deleted} expired magic links`);
677
677
  */
678
678
  export declare function deleteExpiredMagicLinks(): Promise<number>;
679
+ /**
680
+ * Checks if any environment variables contain Key Vault references.
681
+ * Useful for skipping resolution in development environments.
682
+ * @returns True if any env vars match the @Microsoft.KeyVault pattern
683
+ * @example
684
+ * if (hasKeyVaultReferences()) {
685
+ * await resolveKeyVaultReferences();
686
+ * }
687
+ */
688
+ export declare function hasKeyVaultReferences(): boolean;
689
+ /**
690
+ * Resolves all @Microsoft.KeyVault(SecretUri=...) references in process.env.
691
+ * Replaces env var values in-place with the resolved secret values.
692
+ * Uses DefaultAzureCredential for authentication (managed identity in Azure, az cli locally).
693
+ *
694
+ * Requires @azure/keyvault-secrets as a peer dependency.
695
+ *
696
+ * @returns Number of secrets resolved
697
+ * @throws Error if any secret resolution fails (fail-fast)
698
+ * @example
699
+ * // In sessionAuthInit.ts (before any other initialization):
700
+ * if (hasKeyVaultReferences()) {
701
+ * const count = await resolveKeyVaultReferences();
702
+ * console.log(`Resolved ${count} Key Vault secrets`);
703
+ * }
704
+ */
705
+ export declare function resolveKeyVaultReferences(): Promise<number>;
679
706
  export { Result, ok, okVoid, err, getErrorMessage, BaseJwtPayload, UserTokenPayload, UsernameTokenPayload, RoleTokenPayload, hasUsername, hasRole, isAdmin, HTTP_STATUS, HttpStatus, ErrorResponse, SessionUser, SessionInfo, MagicLinkRequest, SessionAuthResponse } from './shared';
package/dist/server.js CHANGED
@@ -4,6 +4,39 @@
4
4
  *
5
5
  * Includes: JWT auth, API keys, HTTP responses, Azure Table Storage
6
6
  */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
7
40
  var __importDefault = (this && this.__importDefault) || function (mod) {
8
41
  return (mod && mod.__esModule) ? mod : { "default": mod };
9
42
  };
@@ -66,6 +99,8 @@ exports.withSessionAuth = withSessionAuth;
66
99
  exports.withSessionAdminAuth = withSessionAdminAuth;
67
100
  exports.deleteExpiredSessions = deleteExpiredSessions;
68
101
  exports.deleteExpiredMagicLinks = deleteExpiredMagicLinks;
102
+ exports.hasKeyVaultReferences = hasKeyVaultReferences;
103
+ exports.resolveKeyVaultReferences = resolveKeyVaultReferences;
69
104
  const crypto_1 = require("crypto");
70
105
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
71
106
  const data_tables_1 = require("@azure/data-tables");
@@ -1290,6 +1325,113 @@ async function deleteExpiredMagicLinks() {
1290
1325
  return deleted;
1291
1326
  }
1292
1327
  // =============================================================================
1328
+ // Key Vault Secret Resolution
1329
+ // =============================================================================
1330
+ /**
1331
+ * Pattern to match Azure Key Vault references in environment variables.
1332
+ * Format: @Microsoft.KeyVault(SecretUri=https://{vault}.vault.azure.net/secrets/{name}[/{version}])
1333
+ */
1334
+ const KEY_VAULT_PATTERN = /^@Microsoft\.KeyVault\(SecretUri=(.+)\)$/;
1335
+ /**
1336
+ * Parses a Key Vault secret URI into vault URL, secret name, and optional version.
1337
+ * @param secretUri - Full Key Vault secret URI
1338
+ * @returns Parsed components or null if invalid
1339
+ */
1340
+ function parseSecretUri(secretUri) {
1341
+ try {
1342
+ const url = new URL(secretUri);
1343
+ const pathParts = url.pathname.split('/').filter(Boolean);
1344
+ // Expected: /secrets/{name} or /secrets/{name}/{version}
1345
+ if (pathParts.length < 2 || pathParts[0] !== 'secrets') {
1346
+ return null;
1347
+ }
1348
+ return {
1349
+ vaultUrl: `${url.protocol}//${url.host}`,
1350
+ secretName: pathParts[1],
1351
+ secretVersion: pathParts[2]
1352
+ };
1353
+ }
1354
+ catch {
1355
+ return null;
1356
+ }
1357
+ }
1358
+ /**
1359
+ * Checks if any environment variables contain Key Vault references.
1360
+ * Useful for skipping resolution in development environments.
1361
+ * @returns True if any env vars match the @Microsoft.KeyVault pattern
1362
+ * @example
1363
+ * if (hasKeyVaultReferences()) {
1364
+ * await resolveKeyVaultReferences();
1365
+ * }
1366
+ */
1367
+ function hasKeyVaultReferences() {
1368
+ for (const value of Object.values(process.env)) {
1369
+ if (value && KEY_VAULT_PATTERN.test(value)) {
1370
+ return true;
1371
+ }
1372
+ }
1373
+ return false;
1374
+ }
1375
+ /**
1376
+ * Resolves all @Microsoft.KeyVault(SecretUri=...) references in process.env.
1377
+ * Replaces env var values in-place with the resolved secret values.
1378
+ * Uses DefaultAzureCredential for authentication (managed identity in Azure, az cli locally).
1379
+ *
1380
+ * Requires @azure/keyvault-secrets as a peer dependency.
1381
+ *
1382
+ * @returns Number of secrets resolved
1383
+ * @throws Error if any secret resolution fails (fail-fast)
1384
+ * @example
1385
+ * // In sessionAuthInit.ts (before any other initialization):
1386
+ * if (hasKeyVaultReferences()) {
1387
+ * const count = await resolveKeyVaultReferences();
1388
+ * console.log(`Resolved ${count} Key Vault secrets`);
1389
+ * }
1390
+ */
1391
+ async function resolveKeyVaultReferences() {
1392
+ // Dynamic import - only loaded when Key Vault references are present
1393
+ const { SecretClient } = await Promise.resolve().then(() => __importStar(require('@azure/keyvault-secrets')));
1394
+ // Group env vars by vault URL to reuse clients
1395
+ const vaultSecrets = new Map();
1396
+ for (const [key, value] of Object.entries(process.env)) {
1397
+ if (!value)
1398
+ continue;
1399
+ const match = value.match(KEY_VAULT_PATTERN);
1400
+ if (!match)
1401
+ continue;
1402
+ const parsed = parseSecretUri(match[1]);
1403
+ if (!parsed) {
1404
+ throw new Error(`Invalid Key Vault secret URI in ${key}: ${match[1]}`);
1405
+ }
1406
+ const existing = vaultSecrets.get(parsed.vaultUrl) ?? [];
1407
+ existing.push({ envKey: key, secretName: parsed.secretName, secretVersion: parsed.secretVersion });
1408
+ vaultSecrets.set(parsed.vaultUrl, existing);
1409
+ }
1410
+ if (vaultSecrets.size === 0) {
1411
+ return 0;
1412
+ }
1413
+ const cred = getCredential();
1414
+ let resolved = 0;
1415
+ for (const [vaultUrl, secrets] of vaultSecrets) {
1416
+ const client = new SecretClient(vaultUrl, cred);
1417
+ for (const { envKey, secretName, secretVersion } of secrets) {
1418
+ try {
1419
+ const secret = await client.getSecret(secretName, secretVersion ? { version: secretVersion } : undefined);
1420
+ if (!secret.value) {
1421
+ throw new Error(`Secret '${secretName}' in ${vaultUrl} has no value`);
1422
+ }
1423
+ process.env[envKey] = secret.value;
1424
+ resolved++;
1425
+ }
1426
+ catch (error) {
1427
+ const message = (0, shared_1.getErrorMessage)(error, 'Unknown error');
1428
+ throw new Error(`Failed to resolve Key Vault secret '${secretName}' for ${envKey}: ${message}`);
1429
+ }
1430
+ }
1431
+ }
1432
+ return resolved;
1433
+ }
1434
+ // =============================================================================
1293
1435
  // Re-exports from shared (for convenience)
1294
1436
  // =============================================================================
1295
1437
  var shared_2 = require("./shared");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markwharton/pwa-core",
3
- "version": "3.4.2",
3
+ "version": "3.5.0",
4
4
  "description": "Shared patterns for Azure PWA projects",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,12 +30,19 @@
30
30
  "peerDependencies": {
31
31
  "@azure/data-tables": "^13.0.0",
32
32
  "@azure/identity": "^4.0.0",
33
+ "@azure/keyvault-secrets": "^4.0.0",
33
34
  "jsonwebtoken": "^9.0.0"
34
35
  },
36
+ "peerDependenciesMeta": {
37
+ "@azure/keyvault-secrets": {
38
+ "optional": true
39
+ }
40
+ },
35
41
  "devDependencies": {
36
42
  "@azure/data-tables": "^13.2.2",
37
43
  "@azure/functions": "^4.5.0",
38
44
  "@azure/identity": "^4.5.0",
45
+ "@azure/keyvault-secrets": "^4.10.0",
39
46
  "@types/jsonwebtoken": "^9.0.5",
40
47
  "@types/node": "^20.10.0",
41
48
  "jsonwebtoken": "^9.0.2",