@aifabrix/builder 2.51.1 → 2.52.1
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/jest.isolated-projects.js +25 -2
- package/jest.projects.js +4 -1
- package/lib/api/datasources-extended.api.js +8 -8
- package/lib/api/keycloak.api.js +92 -0
- package/lib/api/token-providers.api.js +77 -0
- package/lib/api/types/keycloak.types.js +23 -0
- package/lib/api/types/token-providers.types.js +65 -0
- package/lib/app/down.js +16 -1
- package/lib/app/run-container-start.js +4 -0
- package/lib/app/run-deploy-job.js +135 -27
- package/lib/app/run-env-compose.js +32 -0
- package/lib/app/run-env-recovery.js +35 -0
- package/lib/app/run-helpers.js +5 -6
- package/lib/app/run.js +11 -3
- package/lib/channels/add-channel.js +126 -19
- package/lib/channels/approval-guides/chatgpt.js +12 -9
- package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
- package/lib/channels/channel-add-ensure.js +128 -0
- package/lib/channels/channel-artifact-generator.js +11 -1
- package/lib/channels/channel-branding.js +94 -0
- package/lib/channels/channel-credential.js +8 -0
- package/lib/channels/channel-discovery.js +330 -0
- package/lib/channels/channel-external-system.js +1 -1
- package/lib/channels/channel-runtime-resolver.js +103 -14
- package/lib/channels/channel-status-display.js +7 -1
- package/lib/channels/channel-targets.js +18 -1
- package/lib/channels/chatgpt-runtime-contract.js +1 -1
- package/lib/channels/microsoft-copilot-channel-add.js +138 -0
- package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
- package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
- package/lib/channels/microsoft-copilot-icons.js +74 -0
- package/lib/channels/microsoft-copilot-limits.js +84 -0
- package/lib/channels/microsoft-copilot-package.js +130 -49
- package/lib/channels/microsoft-copilot-validate.js +197 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/infra-guided-footers.js +1 -1
- package/lib/cli/infra-guided-public-url-warn.js +55 -0
- package/lib/cli/infra-guided.js +78 -62
- package/lib/cli/setup-app.help.js +4 -0
- package/lib/cli/setup-app.test-commands.js +59 -28
- package/lib/cli/setup-external-system.js +50 -12
- package/lib/cli/setup-infra.js +31 -4
- package/lib/commands/channel-add-setup.js +35 -10
- package/lib/commands/channel.js +27 -12
- package/lib/commands/deploy-gate-orchestrator.js +3 -2
- package/lib/commands/governance-verify-external.js +2 -5
- package/lib/commands/integration-definition.js +158 -0
- package/lib/commands/lifecycle-external.js +14 -7
- package/lib/commands/repair-auth-apply.js +19 -5
- package/lib/commands/repair-datasource-sync-operations.js +4 -1
- package/lib/commands/role-assistant-test-output.js +200 -0
- package/lib/commands/role-assistant.js +131 -114
- package/lib/commands/test-governance-external.js +2 -5
- package/lib/commands/up-miso.js +61 -24
- package/lib/commands/upload-data-sync-finalize.js +20 -3
- package/lib/commands/verify-governance-external.js +2 -5
- package/lib/commands/verify-operations-external.js +2 -5
- package/lib/commands/verify-operations-step-plan.js +31 -0
- package/lib/commands/verify-trust-command-action.js +2 -5
- package/lib/datasource/capability/capability-resolve.js +15 -5
- package/lib/datasource/capability/copy-operations.js +14 -4
- package/lib/datasource/datasource-identifier.js +4 -3
- package/lib/datasource/integration-context.js +3 -3
- package/lib/datasource/resolve-app.js +90 -8
- package/lib/datasource/test-e2e.js +2 -2
- package/lib/datasource/unified-validation-run-body.js +15 -1
- package/lib/datasource/unified-validation-run-resolve.js +2 -2
- package/lib/external-system/deploy-helpers.js +9 -5
- package/lib/external-system/deploy.js +2 -1
- package/lib/external-system/test-system-level.js +21 -3
- package/lib/external-system/test.js +4 -4
- package/lib/generator/builders.js +9 -15
- package/lib/infrastructure/index.js +19 -4
- package/lib/integration-definition/apply.js +478 -0
- package/lib/integration-definition/canonical-json.js +51 -0
- package/lib/integration-definition/emit-suite-bid.js +94 -0
- package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
- package/lib/integration-definition/enrich-openapi-operations.js +434 -0
- package/lib/integration-definition/from-discovery.js +339 -0
- package/lib/integration-definition/hash.js +37 -0
- package/lib/integration-definition/index.js +37 -0
- package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
- package/lib/integration-definition/resource-type-catalog.js +59 -0
- package/lib/integration-definition/system-kind-enum.js +40 -0
- package/lib/integration-definition/validate.js +148 -0
- package/lib/programmatic/builder-api-request.js +5 -1
- package/lib/programmatic/builder-help-governance.js +3 -2
- package/lib/programmatic/capability-registry.js +19 -0
- package/lib/programmatic/openapi-component-schemas.js +6 -1
- package/lib/programmatic/openapi-descriptions.js +40 -0
- package/lib/programmatic/openapi-envelope-examples.js +269 -0
- package/lib/programmatic/openapi-operation-bindings.js +45 -0
- package/lib/programmatic/openapi-request-schemas.js +14 -0
- package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
- package/lib/programmatic/openapi-result-schemas.js +46 -99
- package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
- package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
- package/lib/programmatic/openapi-schema-helpers.js +9 -9
- package/lib/programmatic/openapi-spec.js +11 -1
- package/lib/programmatic/orchestration-openapi-meta.js +10 -0
- package/lib/programmatic/route-handler-map.js +29 -1
- package/lib/programmatic/run-channel-package.js +236 -0
- package/lib/programmatic/run-integration-definition.js +69 -0
- package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
- package/lib/role-assistant/knowledge-sync-logging.js +58 -0
- package/lib/role-assistant/knowledge-sync.js +104 -89
- package/lib/role-assistant/test-cases-bid.js +155 -0
- package/lib/role-assistant/test-cases-evaluate.js +9 -1
- package/lib/role-assistant/test-cases-honesty.js +24 -10
- package/lib/role-assistant/test-cases.js +28 -0
- package/lib/role-assistant/test-runner-bid-apply.js +122 -0
- package/lib/role-assistant/test-runner-bid-document.js +278 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
- package/lib/role-assistant/test-runner-workhub.js +121 -25
- package/lib/role-assistant/test-suite.js +119 -0
- package/lib/schema/application-schema.json +64 -32
- package/lib/schema/external-datasource.schema.json +103 -17
- package/lib/schema/external-system.schema.json +54 -3
- package/lib/schema/infra.parameter.yaml +0 -20
- package/lib/schema/integration-definition.schema.json +195 -0
- package/lib/utils/authentication-shape.js +6 -0
- package/lib/utils/certify-external-workspace.js +40 -0
- package/lib/utils/docker-build.js +3 -3
- package/lib/utils/docker-run-ephemeral.js +77 -17
- package/lib/utils/health-check-public-warn.js +31 -13
- package/lib/utils/health-check.js +11 -1
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +13 -15
- package/lib/utils/promote-dev-docker-image.js +97 -18
- package/lib/utils/role-assistant-paths.js +76 -14
- package/lib/utils/transient-developer-id.js +28 -0
- package/lib/utils/upload-sync-options.js +7 -4
- package/lib/validation/reserved-system-key-prefixes.js +56 -0
- package/lib/validation/validate.js +54 -19
- package/package.json +1 -1
- package/scripts/pnpm/af.mjs +24 -9
- package/scripts/pnpm/help.mjs +1 -1
- package/templates/applications/builder-api/application.yaml +3 -3
- package/templates/applications/dataplane/application.yaml +5 -5
- package/templates/applications/dataplane/env.template +8 -7
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/env.template +5 -2
- package/templates/applications/miso-controller/application.yaml +25 -11
- package/templates/applications/miso-controller/env.template +24 -5
- package/templates/channels/README.md +11 -1
- package/templates/channels/brand/color-192.png +0 -0
- package/templates/channels/brand/dark/128.png +0 -0
- package/templates/channels/brand/dark/192.png +0 -0
- package/templates/channels/brand/dark/256.png +0 -0
- package/templates/channels/brand/dark/32.png +0 -0
- package/templates/channels/brand/dark/512.png +0 -0
- package/templates/channels/brand/dark/64.png +0 -0
- package/templates/channels/brand/light/128.png +0 -0
- package/templates/channels/brand/light/192.png +0 -0
- package/templates/channels/brand/light/256.png +0 -0
- package/templates/channels/brand/light/32.png +0 -0
- package/templates/channels/brand/light/512.png +0 -0
- package/templates/channels/brand/light/64.png +0 -0
- package/templates/channels/brand/mono-white/128.png +0 -0
- package/templates/channels/brand/mono-white/192.png +0 -0
- package/templates/channels/brand/mono-white/256.png +0 -0
- package/templates/channels/brand/mono-white/32.png +0 -0
- package/templates/channels/brand/mono-white/512.png +0 -0
- package/templates/channels/brand/mono-white/64.png +0 -0
- package/templates/channels/brand/outline-32.png +0 -0
- package/templates/channels/chatgpt/install.template.md.hbs +76 -36
- package/templates/channels/chatgpt/integration/README.md +4 -4
- package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
- package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
- package/templates/channels/microsoft-copilot/integration/README.md +2 -3
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
- package/templates/channels/slack/install.template.md.hbs +6 -1
- package/templates/channels/slack/integration/README.md +2 -4
- package/templates/channels/teams-bot/install.template.md.hbs +4 -0
- package/templates/channels/teams-bot/integration/README.md +3 -5
- package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
- package/templates/python/docker-compose.hbs +10 -0
- package/templates/typescript/docker-compose.hbs +10 -0
- /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* microsoft-copilot channel-add helpers (Entra SSO form context for operator errors).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Keep add-channel.js under size limits
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { resolveChannelAuthContext } = require('./channel-auth-context');
|
|
12
|
+
const {
|
|
13
|
+
normalizeBaseChannelName,
|
|
14
|
+
resolveChannelDisplayName
|
|
15
|
+
} = require('./channel-display-name');
|
|
16
|
+
const { getDefaultChannelDescription } = require('./channel-targets');
|
|
17
|
+
const { resolveAppDescription } = require('./channel-description');
|
|
18
|
+
const { loadChannelPackageContext } = require('./channel-discovery');
|
|
19
|
+
const { getInboundMicrosoftEntraIdProvider } = require('../api/token-providers.api');
|
|
20
|
+
const { getKeycloakEntraIdentityProvider } = require('../api/keycloak.api');
|
|
21
|
+
const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
|
|
22
|
+
const { assertOauthPluginVaultReferenceId } = require('./microsoft-copilot-entra-sso');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
function hasOauthPluginVaultReference(options) {
|
|
29
|
+
return (
|
|
30
|
+
typeof options.oauthPluginVaultReferenceId === 'string' &&
|
|
31
|
+
Boolean(options.oauthPluginVaultReferenceId.trim())
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load Trust source + Keycloak Entra IdP for Copilot packaging (null on miss/error).
|
|
37
|
+
* @param {Object} ctx
|
|
38
|
+
* @returns {Promise<{ entraProvider: Object|null, entraIdentityProvider: Object|null }>}
|
|
39
|
+
*/
|
|
40
|
+
async function loadMicrosoftCopilotEntraContext(ctx) {
|
|
41
|
+
let entraProvider = null;
|
|
42
|
+
let entraIdentityProvider = null;
|
|
43
|
+
if (!ctx?.controllerUrl) {
|
|
44
|
+
return { entraProvider, entraIdentityProvider };
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
entraProvider = await getInboundMicrosoftEntraIdProvider(ctx.controllerUrl, ctx.authConfig);
|
|
48
|
+
} catch {
|
|
49
|
+
entraProvider = null;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
entraIdentityProvider = await getKeycloakEntraIdentityProvider(
|
|
53
|
+
ctx.controllerUrl,
|
|
54
|
+
ctx.authConfig
|
|
55
|
+
);
|
|
56
|
+
} catch {
|
|
57
|
+
entraIdentityProvider = null;
|
|
58
|
+
}
|
|
59
|
+
return { entraProvider, entraIdentityProvider };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {Object} params
|
|
64
|
+
* @returns {Promise<Object>}
|
|
65
|
+
*/
|
|
66
|
+
async function resolveCopilotFormContextForError(params) {
|
|
67
|
+
const { options, ctx, name, baseName, description } = params;
|
|
68
|
+
let packageContext = null;
|
|
69
|
+
try {
|
|
70
|
+
packageContext = await loadChannelPackageContext({
|
|
71
|
+
dataplaneBaseUrl: ctx.dataplaneUrl,
|
|
72
|
+
authConfig: ctx.authConfig,
|
|
73
|
+
channelTarget: 'microsoft-copilot',
|
|
74
|
+
branding: { displayName: name, description }
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
packageContext = null;
|
|
78
|
+
}
|
|
79
|
+
const { entraProvider, entraIdentityProvider } = await loadMicrosoftCopilotEntraContext(ctx);
|
|
80
|
+
return resolveChannelRuntimeValues({
|
|
81
|
+
dataplaneBaseUrl: ctx.dataplaneUrl,
|
|
82
|
+
channelTarget: 'microsoft-copilot',
|
|
83
|
+
appName: name,
|
|
84
|
+
baseName,
|
|
85
|
+
environment: ctx.environment,
|
|
86
|
+
appDescription: description,
|
|
87
|
+
packageContext,
|
|
88
|
+
entraProvider,
|
|
89
|
+
entraIdentityProvider,
|
|
90
|
+
oauthPluginVaultReferenceId: options.oauthPluginVaultReferenceId
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {Object} params
|
|
96
|
+
* @throws {Error}
|
|
97
|
+
*/
|
|
98
|
+
async function assertMicrosoftCopilotOauthReference(params) {
|
|
99
|
+
const { options } = params;
|
|
100
|
+
if (hasOauthPluginVaultReference(options)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const formContext = await resolveCopilotFormContextForError(params);
|
|
104
|
+
assertOauthPluginVaultReferenceId({ ...options, formContext });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fail before force upload when Teams Entra SSO auth-config id is missing.
|
|
109
|
+
* @param {Object} options
|
|
110
|
+
* @throws {Error}
|
|
111
|
+
*/
|
|
112
|
+
async function assertMicrosoftCopilotOauthReferenceBeforeForce(options) {
|
|
113
|
+
if (hasOauthPluginVaultReference(options)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const ctx = await resolveChannelAuthContext('microsoft-copilot', { silent: options.quiet });
|
|
117
|
+
const baseName = normalizeBaseChannelName(options.name);
|
|
118
|
+
const name = resolveChannelDisplayName(baseName, ctx.environment);
|
|
119
|
+
const rawDescription =
|
|
120
|
+
options.description && String(options.description).trim()
|
|
121
|
+
? String(options.description).trim()
|
|
122
|
+
: getDefaultChannelDescription();
|
|
123
|
+
await assertMicrosoftCopilotOauthReference({
|
|
124
|
+
options,
|
|
125
|
+
ctx,
|
|
126
|
+
name,
|
|
127
|
+
baseName,
|
|
128
|
+
description: resolveAppDescription('microsoft-copilot', rawDescription)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
hasOauthPluginVaultReference,
|
|
134
|
+
loadMicrosoftCopilotEntraContext,
|
|
135
|
+
resolveCopilotFormContextForError,
|
|
136
|
+
assertMicrosoftCopilotOauthReference,
|
|
137
|
+
assertMicrosoftCopilotOauthReferenceBeforeForce
|
|
138
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Copilot Entra SSO form field resolution + template rendering.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Template-backed Teams Entra SSO registration guidance
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('../internal/fs-real-sync');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const Handlebars = require('handlebars');
|
|
14
|
+
const { BUILDER_ROOT } = require('./channel-integration-template');
|
|
15
|
+
|
|
16
|
+
const ENTRA_SSO_REGISTRATION_URL =
|
|
17
|
+
'https://dev.teams.microsoft.com/tools/entra-configuration';
|
|
18
|
+
const TEMPLATES_DIR = path.join(BUILDER_ROOT, 'templates', 'channels', 'microsoft-copilot');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} issuer
|
|
22
|
+
* @returns {string|null}
|
|
23
|
+
*/
|
|
24
|
+
function extractTenantIdFromIssuer(issuer) {
|
|
25
|
+
if (typeof issuer !== 'string' || !issuer.trim()) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const match = issuer.match(
|
|
29
|
+
/login\.microsoftonline\.com\/([0-9a-fA-F-]{36})(?:\/|$)/i
|
|
30
|
+
);
|
|
31
|
+
return match ? match[1] : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} [url]
|
|
36
|
+
* @returns {string|null}
|
|
37
|
+
*/
|
|
38
|
+
function originFromUrl(url) {
|
|
39
|
+
if (typeof url !== 'string' || !url.trim()) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const parsed = new URL(url.trim());
|
|
44
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Prefer Keycloak status enterpriseAppId (Entra SSO application/client id).
|
|
52
|
+
* @param {Object|null} entraIdentityProvider
|
|
53
|
+
* @param {Object|null} entraProvider
|
|
54
|
+
* @param {Object|null} auth
|
|
55
|
+
* @returns {string|null}
|
|
56
|
+
*/
|
|
57
|
+
function resolveEntraSsoClientId(entraIdentityProvider, entraProvider, auth) {
|
|
58
|
+
if (
|
|
59
|
+
entraIdentityProvider &&
|
|
60
|
+
typeof entraIdentityProvider.enterpriseAppId === 'string' &&
|
|
61
|
+
entraIdentityProvider.enterpriseAppId.trim()
|
|
62
|
+
) {
|
|
63
|
+
return entraIdentityProvider.enterpriseAppId.trim();
|
|
64
|
+
}
|
|
65
|
+
if (entraProvider && typeof entraProvider.clientId === 'string' && entraProvider.clientId.trim()) {
|
|
66
|
+
return entraProvider.clientId.trim();
|
|
67
|
+
}
|
|
68
|
+
if (auth && typeof auth.clientId === 'string' && auth.clientId.trim()) {
|
|
69
|
+
return auth.clientId.trim();
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {Object} resolved
|
|
76
|
+
* @param {Object|null} entraIdentityProvider
|
|
77
|
+
* @param {Object|null} entraProvider
|
|
78
|
+
* @param {Object|null} auth
|
|
79
|
+
* @returns {string|null}
|
|
80
|
+
*/
|
|
81
|
+
function resolveEntraTenantId(resolved, entraIdentityProvider, entraProvider, auth) {
|
|
82
|
+
if (
|
|
83
|
+
entraIdentityProvider &&
|
|
84
|
+
typeof entraIdentityProvider.tenantId === 'string' &&
|
|
85
|
+
entraIdentityProvider.tenantId.trim()
|
|
86
|
+
) {
|
|
87
|
+
return entraIdentityProvider.tenantId.trim();
|
|
88
|
+
}
|
|
89
|
+
return extractTenantIdFromIssuer(
|
|
90
|
+
resolved.entraIssuer || entraProvider?.issuer || auth?.issuer || null
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {Object} resolved
|
|
96
|
+
* @param {Object} [params]
|
|
97
|
+
* @returns {void}
|
|
98
|
+
*/
|
|
99
|
+
function applyCopilotEntraSsoFormFields(resolved, params = {}) {
|
|
100
|
+
const entraProvider = params.entraProvider || null;
|
|
101
|
+
const entraIdentityProvider = params.entraIdentityProvider || null;
|
|
102
|
+
const auth = resolved.discoveryAuthentication || null;
|
|
103
|
+
resolved.entraSsoRegistrationUrl = ENTRA_SSO_REGISTRATION_URL;
|
|
104
|
+
resolved.entraSsoBaseUrl =
|
|
105
|
+
originFromUrl(resolved.dataplaneBaseUrl) || originFromUrl(resolved.mcpUrl) || null;
|
|
106
|
+
resolved.entraSsoClientId = resolveEntraSsoClientId(
|
|
107
|
+
entraIdentityProvider,
|
|
108
|
+
entraProvider,
|
|
109
|
+
auth
|
|
110
|
+
);
|
|
111
|
+
const scopes = Array.isArray(resolved.scopes) ? resolved.scopes : [];
|
|
112
|
+
resolved.entraSsoScope = scopes.length > 0 ? scopes.join(', ') : null;
|
|
113
|
+
resolved.entraTenantId = resolveEntraTenantId(
|
|
114
|
+
resolved,
|
|
115
|
+
entraIdentityProvider,
|
|
116
|
+
entraProvider,
|
|
117
|
+
auth
|
|
118
|
+
);
|
|
119
|
+
if (entraIdentityProvider?.enterpriseAppId) {
|
|
120
|
+
resolved.entraEnterpriseAppId = String(entraIdentityProvider.enterpriseAppId).trim();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {string} fileName
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
function readCopilotTemplate(fileName) {
|
|
129
|
+
const templatePath = path.join(TEMPLATES_DIR, fileName);
|
|
130
|
+
if (!fs.existsSync(templatePath)) {
|
|
131
|
+
throw new Error(`Microsoft Copilot template not found: ${templatePath}`);
|
|
132
|
+
}
|
|
133
|
+
return fs.readFileSync(templatePath, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {string} fileName
|
|
138
|
+
* @param {Object} context
|
|
139
|
+
* @returns {string}
|
|
140
|
+
*/
|
|
141
|
+
function renderCopilotChannelTemplate(fileName, context) {
|
|
142
|
+
const hb = Handlebars.create();
|
|
143
|
+
hb.registerPartial('entraSsoRegistration', readCopilotTemplate('entra-sso-registration.template.md.hbs'));
|
|
144
|
+
const template = hb.compile(readCopilotTemplate(fileName));
|
|
145
|
+
return template(context).trim();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Operator-facing error when Teams Entra SSO auth-config id is missing.
|
|
150
|
+
* Prose lives in templates — not inline CLI copy.
|
|
151
|
+
* @param {Object|string} [context]
|
|
152
|
+
* @returns {string}
|
|
153
|
+
*/
|
|
154
|
+
function formatOauthPluginVaultReferenceRequiredError(context = {}) {
|
|
155
|
+
const values = typeof context === 'string' ? { appName: context, baseName: context } : context || {};
|
|
156
|
+
const appName =
|
|
157
|
+
(typeof values.appName === 'string' && values.appName.trim()) ||
|
|
158
|
+
(typeof values.baseName === 'string' && values.baseName.trim()) ||
|
|
159
|
+
'Your Assistant';
|
|
160
|
+
const baseName =
|
|
161
|
+
(typeof values.baseName === 'string' && values.baseName.trim()) || appName;
|
|
162
|
+
return renderCopilotChannelTemplate('oauth-plugin-vault-required.template.md.hbs', {
|
|
163
|
+
...values,
|
|
164
|
+
appName,
|
|
165
|
+
baseName,
|
|
166
|
+
channelTarget: 'microsoft-copilot',
|
|
167
|
+
entraSsoRegistrationUrl: values.entraSsoRegistrationUrl || ENTRA_SSO_REGISTRATION_URL
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
ENTRA_SSO_REGISTRATION_URL,
|
|
173
|
+
extractTenantIdFromIssuer,
|
|
174
|
+
originFromUrl,
|
|
175
|
+
resolveEntraSsoClientId,
|
|
176
|
+
resolveEntraTenantId,
|
|
177
|
+
applyCopilotEntraSsoFormFields,
|
|
178
|
+
renderCopilotChannelTemplate,
|
|
179
|
+
formatOauthPluginVaultReferenceRequiredError
|
|
180
|
+
};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Copilot Entra SSO / trustedIssuer packaging helpers (plan 169.0).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Readiness, scopes, and auth selection for microsoft-copilot
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const TRUSTED_ISSUER = 'trustedIssuer';
|
|
12
|
+
const CUSTOMER_OAUTH = 'customerOAuth';
|
|
13
|
+
const OPENID_ONLY = new Set(['openid', 'profile', 'offline_access']);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {*} value
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
function isPresent(value) {
|
|
20
|
+
return value !== undefined && value !== null && value !== '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} channelTarget
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
function skipsChannelOauthCredential(channelTarget) {
|
|
28
|
+
return channelTarget === 'microsoft-copilot';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {unknown} scopes
|
|
33
|
+
* @returns {string[]}
|
|
34
|
+
*/
|
|
35
|
+
function normalizeScopeList(scopes) {
|
|
36
|
+
if (!Array.isArray(scopes)) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
return scopes.map((s) => String(s).trim()).filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True when scopes are empty or only OIDC login scopes (insufficient for Runtime MCP).
|
|
44
|
+
* @param {string[]} scopes
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
function isOpenIdOnlyOrEmptyScopes(scopes) {
|
|
48
|
+
const list = normalizeScopeList(scopes);
|
|
49
|
+
if (list.length === 0) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return list.every((scope) => OPENID_ONLY.has(scope));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Object|null|undefined} auth
|
|
57
|
+
* @returns {string|null}
|
|
58
|
+
*/
|
|
59
|
+
function resolveRecommendedCapability(auth) {
|
|
60
|
+
const value = auth && typeof auth.recommendedCapability === 'string'
|
|
61
|
+
? auth.recommendedCapability.trim()
|
|
62
|
+
: '';
|
|
63
|
+
return value || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Map adapter auth for Copilot packaging: keep trustedIssuer public fields;
|
|
68
|
+
* drop Keycloak/customerOAuth AS metadata from the package surface.
|
|
69
|
+
* @param {Object|null} auth
|
|
70
|
+
* @returns {Object|null}
|
|
71
|
+
*/
|
|
72
|
+
function mapCopilotPackageAuthentication(auth) {
|
|
73
|
+
if (!auth || typeof auth !== 'object') {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const recommended = resolveRecommendedCapability(auth);
|
|
77
|
+
const base = {
|
|
78
|
+
recommendedCapability: recommended,
|
|
79
|
+
issuer: auth.issuer || null,
|
|
80
|
+
openidConfigurationUrl: auth.openidConfigurationUrl || null,
|
|
81
|
+
protectedResourceMetadataUrl: auth.protectedResourceMetadataUrl || null,
|
|
82
|
+
resource: auth.resource || auth.apiResource || null,
|
|
83
|
+
operatorNote: auth.operatorNote || null,
|
|
84
|
+
pkceRequired: auth.pkceRequired === true,
|
|
85
|
+
audience: isPresent(auth.audience) ? auth.audience : null,
|
|
86
|
+
scopes: normalizeScopeList(auth.scopes || auth.allowedScopes)
|
|
87
|
+
};
|
|
88
|
+
if (recommended === TRUSTED_ISSUER) {
|
|
89
|
+
return {
|
|
90
|
+
...base,
|
|
91
|
+
authorizationServerMetadataUrl: null,
|
|
92
|
+
clientRegistrationModes: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
...base,
|
|
97
|
+
authorizationServerMetadataUrl: auth.authorizationServerMetadataUrl || null,
|
|
98
|
+
clientRegistrationModes: Array.isArray(auth.clientRegistrationModes)
|
|
99
|
+
? [...auth.clientRegistrationModes]
|
|
100
|
+
: []
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Resolve Runtime MCP Entra scopes — never default to openid/profile/offline_access.
|
|
106
|
+
* @param {Object} params
|
|
107
|
+
* @param {string[]} [params.explicitScopes]
|
|
108
|
+
* @param {Object|null} [params.packageContext]
|
|
109
|
+
* @param {Object|null} [params.entraProvider]
|
|
110
|
+
* @returns {string[]}
|
|
111
|
+
*/
|
|
112
|
+
function resolveCopilotEntraScopes(params) {
|
|
113
|
+
const explicit = normalizeScopeList(params.explicitScopes);
|
|
114
|
+
if (explicit.length > 0 && !isOpenIdOnlyOrEmptyScopes(explicit)) {
|
|
115
|
+
return explicit;
|
|
116
|
+
}
|
|
117
|
+
const fromProvider = normalizeScopeList(params.entraProvider?.allowedScopes);
|
|
118
|
+
if (fromProvider.length > 0 && !isOpenIdOnlyOrEmptyScopes(fromProvider)) {
|
|
119
|
+
return fromProvider;
|
|
120
|
+
}
|
|
121
|
+
const auth = params.packageContext?.authentication || null;
|
|
122
|
+
const fromAuth = normalizeScopeList(auth?.scopes || auth?.allowedScopes);
|
|
123
|
+
if (fromAuth.length > 0 && !isOpenIdOnlyOrEmptyScopes(fromAuth)) {
|
|
124
|
+
return fromAuth;
|
|
125
|
+
}
|
|
126
|
+
const resource = typeof auth?.resource === 'string' ? auth.resource.trim() : '';
|
|
127
|
+
if (resource.startsWith('api://')) {
|
|
128
|
+
return [resource];
|
|
129
|
+
}
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {Object} params
|
|
135
|
+
* @returns {string|null}
|
|
136
|
+
*/
|
|
137
|
+
function firstCopilotEntraBlocker(params) {
|
|
138
|
+
const {
|
|
139
|
+
discoveryReady,
|
|
140
|
+
discoveryBlocker,
|
|
141
|
+
recommended,
|
|
142
|
+
referenceId,
|
|
143
|
+
issuer,
|
|
144
|
+
audience,
|
|
145
|
+
scopes
|
|
146
|
+
} = params;
|
|
147
|
+
if (!discoveryReady) {
|
|
148
|
+
return discoveryBlocker || 'remoteMcpUnavailable';
|
|
149
|
+
}
|
|
150
|
+
if (recommended !== TRUSTED_ISSUER) {
|
|
151
|
+
return recommended === CUSTOMER_OAUTH
|
|
152
|
+
? 'customerOAuthNotAllowed'
|
|
153
|
+
: 'trustedIssuerRequired';
|
|
154
|
+
}
|
|
155
|
+
if (!referenceId) {
|
|
156
|
+
return 'oauthPluginVaultReferenceRequired';
|
|
157
|
+
}
|
|
158
|
+
if (!issuer) {
|
|
159
|
+
return 'entraIssuerRequired';
|
|
160
|
+
}
|
|
161
|
+
if (!isPresent(audience)) {
|
|
162
|
+
return 'runtimeMcpAudienceRequired';
|
|
163
|
+
}
|
|
164
|
+
if (isOpenIdOnlyOrEmptyScopes(scopes)) {
|
|
165
|
+
return 'runtimeMcpScopeRequired';
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {Object|null} auth
|
|
172
|
+
* @param {Object|null} entraProvider
|
|
173
|
+
* @returns {{ audience: * , issuer: string|null }}
|
|
174
|
+
*/
|
|
175
|
+
function resolveAudienceAndIssuer(auth, entraProvider) {
|
|
176
|
+
const audience = isPresent(entraProvider?.audience)
|
|
177
|
+
? entraProvider.audience
|
|
178
|
+
: isPresent(auth?.audience)
|
|
179
|
+
? auth.audience
|
|
180
|
+
: null;
|
|
181
|
+
const issuer =
|
|
182
|
+
(entraProvider && entraProvider.issuer) || (auth && auth.issuer) || null;
|
|
183
|
+
return { audience, issuer };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {Object} params
|
|
188
|
+
* @returns {{ installReady: boolean, blocker: string|null, packageAuthentication: Object|null }}
|
|
189
|
+
*/
|
|
190
|
+
function evaluateCopilotEntraSsoReadiness(params) {
|
|
191
|
+
const packageContext = params.packageContext || null;
|
|
192
|
+
const auth = mapCopilotPackageAuthentication(packageContext?.authentication || null);
|
|
193
|
+
const recommended = resolveRecommendedCapability(auth);
|
|
194
|
+
const referenceId =
|
|
195
|
+
typeof params.oauthPluginVaultReferenceId === 'string'
|
|
196
|
+
? params.oauthPluginVaultReferenceId.trim()
|
|
197
|
+
: '';
|
|
198
|
+
const scopes = normalizeScopeList(params.scopes);
|
|
199
|
+
const { audience, issuer } = resolveAudienceAndIssuer(auth, params.entraProvider || null);
|
|
200
|
+
|
|
201
|
+
if (auth) {
|
|
202
|
+
auth.issuer = issuer;
|
|
203
|
+
auth.audience = audience;
|
|
204
|
+
auth.scopes = scopes;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const blocker = firstCopilotEntraBlocker({
|
|
208
|
+
discoveryReady: packageContext?.installReady === true,
|
|
209
|
+
discoveryBlocker: packageContext?.blocker || null,
|
|
210
|
+
recommended,
|
|
211
|
+
referenceId,
|
|
212
|
+
issuer,
|
|
213
|
+
audience,
|
|
214
|
+
scopes
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
installReady: blocker === null,
|
|
218
|
+
blocker,
|
|
219
|
+
packageAuthentication: auth
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const {
|
|
224
|
+
formatOauthPluginVaultReferenceRequiredError
|
|
225
|
+
} = require('./microsoft-copilot-entra-sso-form');
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @param {Object} [options]
|
|
229
|
+
* @throws {Error}
|
|
230
|
+
*/
|
|
231
|
+
function assertOauthPluginVaultReferenceId(options = {}) {
|
|
232
|
+
const ref = options.oauthPluginVaultReferenceId;
|
|
233
|
+
if (typeof ref === 'string' && ref.trim()) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
throw new Error(formatOauthPluginVaultReferenceRequiredError(options.formContext || options));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @param {Object} [runtimeValues]
|
|
241
|
+
* @throws {Error}
|
|
242
|
+
*/
|
|
243
|
+
function assertCopilotPackageOauthPluginVaultReference(runtimeValues = {}) {
|
|
244
|
+
const ref = runtimeValues.oauthPluginVaultReferenceId;
|
|
245
|
+
if (typeof ref === 'string' && ref.trim()) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
throw new Error(formatOauthPluginVaultReferenceRequiredError(runtimeValues));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
module.exports = {
|
|
252
|
+
TRUSTED_ISSUER,
|
|
253
|
+
CUSTOMER_OAUTH,
|
|
254
|
+
skipsChannelOauthCredential,
|
|
255
|
+
normalizeScopeList,
|
|
256
|
+
isOpenIdOnlyOrEmptyScopes,
|
|
257
|
+
resolveRecommendedCapability,
|
|
258
|
+
mapCopilotPackageAuthentication,
|
|
259
|
+
resolveCopilotEntraScopes,
|
|
260
|
+
evaluateCopilotEntraSsoReadiness,
|
|
261
|
+
formatOauthPluginVaultReferenceRequiredError,
|
|
262
|
+
assertOauthPluginVaultReferenceId,
|
|
263
|
+
assertCopilotPackageOauthPluginVaultReference
|
|
264
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid Microsoft Teams / Copilot package icons (192×192 color, 32×32 outline).
|
|
3
|
+
*
|
|
4
|
+
* Defaults from templates/channels/brand (plan 167.0):
|
|
5
|
+
* - color-192.png ← light/192 (color app icon)
|
|
6
|
+
* - outline-32.png ← mono-white/32 (Teams outline)
|
|
7
|
+
* Variant packs: light/, dark/, mono-white/ at 32–512.
|
|
8
|
+
*
|
|
9
|
+
* @fileoverview Loads AI Fabrix brand PNGs from templates/channels/brand
|
|
10
|
+
* @author AI Fabrix Team
|
|
11
|
+
* @version 2.1.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const fs = require('../internal/fs-real-sync');
|
|
18
|
+
const { BUILDER_ROOT } = require('./channel-integration-template');
|
|
19
|
+
|
|
20
|
+
const BRAND_DIR = path.join(BUILDER_ROOT, 'templates', 'channels', 'brand');
|
|
21
|
+
const DEFAULT_COLOR_PATH = path.join(BRAND_DIR, 'color-192.png');
|
|
22
|
+
const DEFAULT_OUTLINE_PATH = path.join(BRAND_DIR, 'outline-32.png');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} filePath
|
|
26
|
+
* @returns {Buffer}
|
|
27
|
+
*/
|
|
28
|
+
function readPngFile(filePath) {
|
|
29
|
+
if (!fs.existsSync(filePath)) {
|
|
30
|
+
throw new Error(`Brand icon not found: ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
return fs.readFileSync(filePath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {{ colorPath?: string, outlinePath?: string }} [options]
|
|
37
|
+
* @returns {{ color: Buffer, outline: Buffer }}
|
|
38
|
+
*/
|
|
39
|
+
function getMicrosoftCopilotIcons(options = {}) {
|
|
40
|
+
const colorPath =
|
|
41
|
+
typeof options.colorPath === 'string' && options.colorPath.trim()
|
|
42
|
+
? options.colorPath.trim()
|
|
43
|
+
: DEFAULT_COLOR_PATH;
|
|
44
|
+
const outlinePath =
|
|
45
|
+
typeof options.outlinePath === 'string' && options.outlinePath.trim()
|
|
46
|
+
? options.outlinePath.trim()
|
|
47
|
+
: DEFAULT_OUTLINE_PATH;
|
|
48
|
+
return {
|
|
49
|
+
color: readPngFile(colorPath),
|
|
50
|
+
outline: readPngFile(outlinePath)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read PNG IHDR width/height without external deps.
|
|
56
|
+
* @param {Buffer} buf
|
|
57
|
+
* @returns {{ width: number, height: number } | null}
|
|
58
|
+
*/
|
|
59
|
+
function readPngDimensions(buf) {
|
|
60
|
+
if (!Buffer.isBuffer(buf) || buf.length < 24) return null;
|
|
61
|
+
if (buf.toString('hex', 0, 8) !== '89504e470d0a1a0a') return null;
|
|
62
|
+
return {
|
|
63
|
+
width: buf.readUInt32BE(16),
|
|
64
|
+
height: buf.readUInt32BE(20)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
getMicrosoftCopilotIcons,
|
|
70
|
+
readPngDimensions,
|
|
71
|
+
BRAND_DIR,
|
|
72
|
+
DEFAULT_COLOR_PATH,
|
|
73
|
+
DEFAULT_OUTLINE_PATH
|
|
74
|
+
};
|