@jskit-ai/connectors-core 0.2.4 → 0.2.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/locales/en.json CHANGED
@@ -27,6 +27,7 @@
27
27
  "schemaVersionUnsupported": "Use numeric schemaVersion 1; other versions require an explicit migration.",
28
28
  "scopeRequired": "Select at least one permission.",
29
29
  "scopeUnsupported": "A selected permission is not supported by this provider configuration.",
30
+ "secretReference": "Use a reference such as env:VARIABLE_NAME.",
30
31
  "serviceAccountPerUser": "A service account cannot connect as each app user.",
31
32
  "serviceAccountRegistration": "Service-account credentials do not use a browser OAuth registration.",
32
33
  "stableName": "Use a stable lowercase name containing letters, digits and hyphens.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/connectors-core",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Portable integration configuration and server-side account connection runtime.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -55,7 +55,7 @@
55
55
  }
56
56
  },
57
57
  "peerDependencies": {
58
- "@jskit-ai/http-runtime": "0.2.4",
59
- "@jskit-ai/kernel": "0.2.4"
58
+ "@jskit-ai/http-runtime": "0.2.5",
59
+ "@jskit-ai/kernel": "0.2.5"
60
60
  }
61
61
  }
@@ -92,17 +92,23 @@ const FIELD_ERRORS = Object.freeze({
92
92
  clientId: { code: "connectors.configuration.clientId", message: "Check this provider's client identifier." },
93
93
  requiredScopes: { code: "connectors.configuration.requiredScopes", message: "Include this provider's required permissions." },
94
94
  scopeUnsupported: { code: "connectors.configuration.scopeUnsupported", message: "A selected permission is not supported by this provider configuration." },
95
- scopeRequired: { code: "connectors.configuration.scopeRequired", message: "Select at least one permission." }
95
+ scopeRequired: { code: "connectors.configuration.scopeRequired", message: "Select at least one permission." },
96
+ secretReference: { code: "connectors.configuration.secretReference", message: "Use a reference such as env:VARIABLE_NAME." }
96
97
  });
97
98
 
98
- // A schema validation error's field errors with the codes the validator gave them.
99
+ // A schema validation error's field errors with the codes the validator gave them. A
100
+ // credential reference that fails secretReference's pattern says what a reference looks like.
99
101
  function schemaFieldErrors(error, fallback, prefix = "") {
100
102
  const messages = {};
101
103
  const codes = {};
102
104
  for (const [field, message] of Object.entries(error.fieldErrors || fallback)) {
103
- messages[`${prefix}${field}`] = message;
104
105
  const coded = error.fieldErrorCodes?.[field];
105
- if (coded) codes[`${prefix}${field}`] = coded;
106
+ if (coded?.code === "kernel.validation.field.pattern" && coded.params.pattern === secretReference.pattern) {
107
+ messages[`${prefix}${field}`] = FIELD_ERRORS.secretReference;
108
+ } else {
109
+ messages[`${prefix}${field}`] = message;
110
+ if (coded) codes[`${prefix}${field}`] = coded;
111
+ }
106
112
  }
107
113
  return { messages, codes };
108
114
  }
@@ -12,6 +12,8 @@ import { googleCalendarProvider } from "../../connector-google-calendar/src/serv
12
12
  import { createConnectionService, createConnectorsFeature, createEnvironmentReferenceResolver } from "../src/server/index.js";
13
13
  import { integrationsSchema, parseIntegrationConfiguration, validateIntegrationConfiguration } from "../src/shared/configuration.js";
14
14
  import { clickhouseProvider } from "../../connectors-catalog/src/server/clickhouse.js";
15
+ import { algoliaDefinition } from "../../connectors-catalog/src/shared/algolia.js";
16
+ import { firebaseCloudMessagingDefinition, firebaseMessagingScope } from "../../connectors-catalog/src/shared/firebase-cloud-messaging.js";
15
17
 
16
18
  const listScope = "https://www.googleapis.com/auth/calendar.calendarlist.readonly";
17
19
  const eventsScope = "https://www.googleapis.com/auth/calendar.events.readonly";
@@ -489,6 +491,44 @@ test("reference fields reject pasted web URLs while preserving environment and c
489
491
  }
490
492
  });
491
493
 
494
+ function assertReferenceFieldError(input, providers, field) {
495
+ assert.throws(() => validateIntegrationConfiguration(input, { providers }), (error) => {
496
+ assert.equal(error.fieldErrors[field], "Use a reference such as env:VARIABLE_NAME.", field);
497
+ assert.deepEqual(error.fieldErrorCodes[field], { code: "connectors.configuration.secretReference", params: {} }, field);
498
+ return true;
499
+ });
500
+ }
501
+
502
+ test("a secretRef that is not a reference says to use a reference such as env:VARIABLE_NAME", () => {
503
+ for (const value of ["https://x", "plain"]) {
504
+ assertReferenceFieldError(databaseConfiguration({ method: "api-key", secretRef: value }), [clickhouseProvider], "integrations.database.authentication.secretRef");
505
+ }
506
+ });
507
+
508
+ test("a registration clientSecretRef that is not a reference says to use a reference such as env:VARIABLE_NAME", () => {
509
+ for (const value of ["https://x", "plain"]) {
510
+ const input = configuration();
511
+ input.registrations.google.clientSecretRef = value;
512
+ assertReferenceFieldError(input, [googleCalendarProvider], "registrations.google.clientSecretRef");
513
+ }
514
+ });
515
+
516
+ test("a provider setting's reference says the same, while its other pattern failures keep the kernel's sentence", () => {
517
+ const input = { schemaVersion: 1, registrations: {}, integrations: {
518
+ search: { provider: "algolia", accountMode: "shared", scopes: [], authentication: { method: "api-key", secretRef: "env:ALGOLIA_KEY" },
519
+ settings: { applicationId: "APP1", publicApiKeyRef: "raw-search-key" } },
520
+ push: { provider: "firebase-cloud-messaging", accountMode: "shared", scopes: [firebaseMessagingScope], authentication: { method: "service-account", secretRef: "env:FCM" },
521
+ settings: { projectId: "push-project", clientMode: "web", apiKey: "not-a-key" } }
522
+ } };
523
+ const providers = [algoliaDefinition, firebaseCloudMessagingDefinition];
524
+ assertReferenceFieldError(input, providers, "integrations.search.settings.publicApiKeyRef");
525
+ assert.throws(() => validateIntegrationConfiguration(input, { providers }), (error) => {
526
+ assert.equal(error.fieldErrors["integrations.push.settings.apiKey"], "Value does not match the required pattern.");
527
+ assert.equal(error.fieldErrorCodes["integrations.push.settings.apiKey"].code, "kernel.validation.field.pattern");
528
+ return true;
529
+ });
530
+ });
531
+
492
532
  test("the exported configuration contract enforces reference constraints for every credential field", async (t) => {
493
533
  const app = Fastify({ ajv: { plugins: [(ajv) => ajv.addKeyword("x-json-rest-schema")] } });
494
534
  t.after(() => app.close());