@kwiz/node 1.0.58 → 1.0.60

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.
@@ -1,2 +1,3 @@
1
1
  export * from './discovery';
2
+ export * from './key-vault';
2
3
  export * from './msal';
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./discovery"), exports);
18
+ __exportStar(require("./key-vault"), exports);
18
19
  __exportStar(require("./msal"), exports);
19
20
  //# sourceMappingURL=exports-index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"exports-index.js","sourceRoot":"","sources":["../../src/auth/exports-index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,yCAAuB"}
1
+ {"version":3,"file":"exports-index.js","sourceRoot":"","sources":["../../src/auth/exports-index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,8CAA4B;AAC5B,yCAAuB"}
@@ -0,0 +1,23 @@
1
+ /** provide the URL for azure key vault
2
+ * to use from an Azure App Service - grant access to "Microsoft Azure App Service" for "Key Vault Certificate User" and "Key Vault Secrets User" roles
3
+ * For local dev session, grant access to [your app registration name], and add these environment variables:
4
+ * AZURE_TENANT_ID - your tenant ID
5
+ * AZURE_CLIENT_ID - your azure app registration id
6
+ * AZURE_CLIENT_SECRET - your azure app secret
7
+ */
8
+ export declare function ConfigureKeyVault(config: {
9
+ url: string;
10
+ }): void;
11
+ type tCertificate = {
12
+ privateKey: string;
13
+ certificate: string;
14
+ thumbprint: string;
15
+ fetched: number;
16
+ };
17
+ /**
18
+ * Fetches the active certificate from Azure Key Vault.
19
+ * It loads the last 2 certificates, uses the old one as long as it is not expired, once it is expired - switches to using the newer one.
20
+ * by default it caches the result and reuses it, forceRefresh to bypass
21
+ */
22
+ export declare function getLatestCertificate(certName: string, forceRefresh?: boolean): Promise<tCertificate>;
23
+ export {};
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigureKeyVault = ConfigureKeyVault;
4
+ exports.getLatestCertificate = getLatestCertificate;
5
+ const identity_1 = require("@azure/identity");
6
+ const keyvault_secrets_1 = require("@azure/keyvault-secrets");
7
+ const common_1 = require("@kwiz/common");
8
+ const forge = require("node-forge");
9
+ var logger;
10
+ var keyVaultUrl = null;
11
+ var bias;
12
+ /** provide the URL for azure key vault
13
+ * to use from an Azure App Service - grant access to "Microsoft Azure App Service" for "Key Vault Certificate User" and "Key Vault Secrets User" roles
14
+ * For local dev session, grant access to [your app registration name], and add these environment variables:
15
+ * AZURE_TENANT_ID - your tenant ID
16
+ * AZURE_CLIENT_ID - your azure app registration id
17
+ * AZURE_CLIENT_SECRET - your azure app secret
18
+ */
19
+ function ConfigureKeyVault(config) {
20
+ keyVaultUrl = config.url;
21
+ bias = common_1.CommonConfig.i.IsLocalDev ? common_1.shiftDateValues.m10 : common_1.shiftDateValues.h24;
22
+ logger = new common_1.CommonLogger("key-vault");
23
+ logger.log(`Configured for ${config.url}, bias: ${common_1.CommonConfig.i.IsLocalDev ? '10 minutes' : '24 hours'}`);
24
+ }
25
+ /**
26
+ * Fetches the active certificate from Azure Key Vault.
27
+ * It loads the last 2 certificates, uses the old one as long as it is not expired, once it is expired - switches to using the newer one.
28
+ * by default it caches the result and reuses it, forceRefresh to bypass
29
+ */
30
+ async function getLatestCertificate(certName, forceRefresh = false) {
31
+ try {
32
+ if ((0, common_1.isNullOrEmptyString)(keyVaultUrl)) {
33
+ console.error("Call ConfigureKeyVault first!");
34
+ throw Error("Call ConfigureKeyVault first!");
35
+ }
36
+ const res = await (0, common_1.promiseOnce)(`getLatestCertificate(${certName})`, async () => {
37
+ logger.log(`Fetching ${certName}`);
38
+ const vaultUrl = keyVaultUrl;
39
+ if (!vaultUrl) {
40
+ throw new Error("Missing AZURE_KEYVAULT_URL environment variable.");
41
+ }
42
+ try {
43
+ // DefaultAzureCredential - in azure: automatically discovers your App Service's Managed Identity
44
+ // In local dev: add AZURE_TENANT_ID,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET to your env file
45
+ const credential = new identity_1.DefaultAzureCredential();
46
+ const client = new keyvault_secrets_1.SecretClient(vaultUrl, credential);
47
+ // List all versions of the secret
48
+ const versionProperties = [];
49
+ for await (const prop of client.listPropertiesOfSecretVersions(certName)) {
50
+ // Only consider enabled versions
51
+ if (prop.enabled) {
52
+ versionProperties.push(prop);
53
+ }
54
+ //do not rely on prop.expiresOn - manually uploaded certificates might input wrong/missing date here!
55
+ }
56
+ if (versionProperties.length === 0) {
57
+ throw new Error(`No enabled versions found for secret '${certName}'.`);
58
+ }
59
+ // Sort them by creation date to get the newest ones first
60
+ versionProperties.sort((a, b) => b.createdOn.getTime() - a.createdOn.getTime());
61
+ //most recent certificate
62
+ const latestSecret = await client.getSecret(certName, { version: versionProperties[0].version });
63
+ const latestCreds = splitPfxToPemStrings(latestSecret.value);
64
+ if (versionProperties.length === 1) //only 1 - return it
65
+ return { ...latestCreds, fetched: new Date().getTime() };
66
+ //previous certificate
67
+ const previousSecret = await client.getSecret(certName, { version: versionProperties[1].version });
68
+ const previousCreds = splitPfxToPemStrings(previousSecret.value);
69
+ const creds = stillValid(previousCreds.certificate, true) ? previousCreds : latestCreds;
70
+ return { ...creds, fetched: new Date().getTime() };
71
+ }
72
+ catch (error) {
73
+ logger.error(`Failed to fetch certificate [${certName}] from Key Vault: ${(0, common_1.GetError)(error)}`);
74
+ throw error;
75
+ }
76
+ }, async (res) => {
77
+ if (forceRefresh)
78
+ return false; //never valid... get a new one.
79
+ if (res?.fetched > 0) {
80
+ const valid = (0, common_1.shiftDate)("h24", new Date(res.fetched)).getTime() > new Date().getTime();
81
+ if (!valid)
82
+ logger.log(`${certName} too stale`);
83
+ return valid;
84
+ }
85
+ return false;
86
+ });
87
+ return res;
88
+ }
89
+ catch (e) {
90
+ return null;
91
+ }
92
+ }
93
+ /** withBias :
94
+ * The certificate is valid only if:
95
+ * - It started at least 1 day before today (oneDayAgo >= notBefore)
96
+ * - There is at least 1 more day before it expires (oneDayFromNow <= notAfter)
97
+ * */
98
+ function stillValid(certificate, withBias = false) {
99
+ const prevCertObj = forge.pki.certificateFromPem(certificate);
100
+ const now = new Date();
101
+ const notAfter = withBias ? (0, common_1.shiftDate)(bias) : now;
102
+ const notBefore = withBias ? (0, common_1.shiftDate)(bias * -1) : now;
103
+ const isStillValid = notBefore >= prevCertObj.validity.notBefore &&
104
+ notAfter <= prevCertObj.validity.notAfter;
105
+ return isStillValid;
106
+ }
107
+ function splitPfxToPemStrings(fullPemString) {
108
+ try {
109
+ // 1. Extract the Private Key using regex
110
+ const privateKeyMatch = fullPemString.match(/-----BEGIN [\s\S]+?-----END [\s\S]+?KEY-----/);
111
+ const privateKey = privateKeyMatch ? privateKeyMatch[0] : null;
112
+ // 2. Extract the Public Certificate using regex
113
+ const certificateMatch = fullPemString.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/);
114
+ const certificate = certificateMatch ? certificateMatch[0] : null;
115
+ if (!privateKey || !certificate) {
116
+ throw new Error("The retrieved secret did not contain a valid private key and certificate pair.");
117
+ }
118
+ // ==========================================
119
+ // ⚡ COMPUTE THE THUMBPRINT (Required for MSAL)
120
+ // ==========================================
121
+ // MSAL needs the unique SHA-1 thumbprint of the public certificate
122
+ const cert = forge.pki.certificateFromPem(certificate);
123
+ const der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes();
124
+ const thumbprint = forge.md.sha1.create().update(der).digest().toHex().toUpperCase();
125
+ return {
126
+ privateKey,
127
+ certificate,
128
+ thumbprint
129
+ };
130
+ }
131
+ catch (error) {
132
+ logger.error("Failed to split PFX archive: " + (0, common_1.GetError)(error));
133
+ throw error;
134
+ }
135
+ }
136
+ //# sourceMappingURL=key-vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-vault.js","sourceRoot":"","sources":["../../src/auth/key-vault.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kwiz/node",
3
- "version": "1.0.58",
3
+ "version": "1.0.60",
4
4
  "description": "KWIZ utilities and helpers for node applications",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -63,6 +63,7 @@
63
63
  "packageManager": "npm@9.5.1",
64
64
  "devDependencies": {
65
65
  "@types/node": "^22.19.15",
66
+ "@types/node-forge": "^1.3.14",
66
67
  "@types/nodemailer": "^7.0.9",
67
68
  "@types/xml2js": "^0.4.14",
68
69
  "check-node-version": "^4.2.1",
@@ -75,13 +76,16 @@
75
76
  },
76
77
  "dependencies": {
77
78
  "@azure/data-tables": "^13.2.2",
79
+ "@azure/identity": "^4.10.1",
80
+ "@azure/keyvault-secrets": "^4.10.0",
78
81
  "@azure/msal-node": "^2.6.4",
79
82
  "@azure/storage-blob": "^12.31.0",
80
83
  "@jsforce/jsforce-node": "^3.10.14",
81
- "@kwiz/common": "^1.0.235",
84
+ "@kwiz/common": "^1.0.242",
82
85
  "axios": "^1.6.7",
83
86
  "esbuild": "^0.19.12",
84
87
  "get-tsconfig": "^4.7.2",
88
+ "node-forge": "^1.4.0",
85
89
  "nodemailer": "^8.0.1",
86
90
  "resolve-pkg-maps": "^1.0.0",
87
91
  "xml2js": "^0.6.2"