@postman-cse/onboarding-insights 0.9.1 → 0.10.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/action.yml +4 -0
- package/dist/action.cjs +396 -17
- package/dist/cli.cjs +401 -17
- package/dist/index.cjs +402 -17
- package/package.json +1 -1
package/action.yml
CHANGED
|
@@ -31,6 +31,10 @@ inputs:
|
|
|
31
31
|
postman-api-key:
|
|
32
32
|
description: 'Postman API key (PMAK-*) for the application binding call. Auto-created from postman-access-token when omitted or invalid after a clear 401/403 validation failure.'
|
|
33
33
|
required: false
|
|
34
|
+
credential-preflight:
|
|
35
|
+
description: 'Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any onboarding write; off skips the identity probes entirely (the reactive error guidance still applies). A rejected or auto-created postman-api-key is never failed on.'
|
|
36
|
+
required: false
|
|
37
|
+
default: 'warn'
|
|
34
38
|
poll-timeout-seconds:
|
|
35
39
|
description: 'Maximum seconds to wait for the service to appear in the discovered list'
|
|
36
40
|
required: false
|
package/dist/action.cjs
CHANGED
|
@@ -20943,6 +20943,310 @@ function getIDToken(aud) {
|
|
|
20943
20943
|
});
|
|
20944
20944
|
}
|
|
20945
20945
|
|
|
20946
|
+
// src/lib/credential-identity.ts
|
|
20947
|
+
var sessionPath = "/api/sessions/current";
|
|
20948
|
+
var pmakMemo = /* @__PURE__ */ new Map();
|
|
20949
|
+
var sessionMemo = /* @__PURE__ */ new Map();
|
|
20950
|
+
var memoizedSessionIdentity;
|
|
20951
|
+
function getMemoizedSessionIdentity() {
|
|
20952
|
+
return memoizedSessionIdentity;
|
|
20953
|
+
}
|
|
20954
|
+
function asRecord(value) {
|
|
20955
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20956
|
+
return void 0;
|
|
20957
|
+
}
|
|
20958
|
+
return value;
|
|
20959
|
+
}
|
|
20960
|
+
function coerceId(raw) {
|
|
20961
|
+
return raw ? String(raw) : void 0;
|
|
20962
|
+
}
|
|
20963
|
+
function coerceText(raw) {
|
|
20964
|
+
if (typeof raw !== "string") {
|
|
20965
|
+
return void 0;
|
|
20966
|
+
}
|
|
20967
|
+
const trimmed = raw.trim();
|
|
20968
|
+
return trimmed ? trimmed : void 0;
|
|
20969
|
+
}
|
|
20970
|
+
function normalizeBaseUrl(raw) {
|
|
20971
|
+
return String(raw || "").replace(/\/+$/, "");
|
|
20972
|
+
}
|
|
20973
|
+
async function resolvePmakIdentity(opts) {
|
|
20974
|
+
const apiKey = String(opts.apiKey || "").trim();
|
|
20975
|
+
if (!apiKey) {
|
|
20976
|
+
return void 0;
|
|
20977
|
+
}
|
|
20978
|
+
const baseUrl = normalizeBaseUrl(opts.apiBaseUrl);
|
|
20979
|
+
const memoKey = `${baseUrl}::${apiKey}`;
|
|
20980
|
+
let pending = pmakMemo.get(memoKey);
|
|
20981
|
+
if (!pending) {
|
|
20982
|
+
pending = probePmakIdentity(baseUrl, apiKey, opts.fetchImpl ?? fetch);
|
|
20983
|
+
pmakMemo.set(memoKey, pending);
|
|
20984
|
+
}
|
|
20985
|
+
return pending;
|
|
20986
|
+
}
|
|
20987
|
+
async function probePmakIdentity(baseUrl, apiKey, fetchImpl) {
|
|
20988
|
+
try {
|
|
20989
|
+
const response = await fetchImpl(`${baseUrl}/me`, {
|
|
20990
|
+
method: "GET",
|
|
20991
|
+
headers: { "X-Api-Key": apiKey }
|
|
20992
|
+
});
|
|
20993
|
+
if (!response.ok) {
|
|
20994
|
+
return void 0;
|
|
20995
|
+
}
|
|
20996
|
+
const payload = asRecord(await response.json());
|
|
20997
|
+
const user = asRecord(payload?.user);
|
|
20998
|
+
if (!user) {
|
|
20999
|
+
return void 0;
|
|
21000
|
+
}
|
|
21001
|
+
return {
|
|
21002
|
+
source: "pmak/me",
|
|
21003
|
+
userId: coerceId(user.id),
|
|
21004
|
+
fullName: coerceText(user.fullName) ?? coerceText(user.username),
|
|
21005
|
+
teamId: coerceId(user.teamId),
|
|
21006
|
+
teamName: coerceText(user.teamName),
|
|
21007
|
+
teamDomain: coerceText(user.teamDomain)
|
|
21008
|
+
};
|
|
21009
|
+
} catch {
|
|
21010
|
+
return void 0;
|
|
21011
|
+
}
|
|
21012
|
+
}
|
|
21013
|
+
async function resolveSessionIdentity(opts) {
|
|
21014
|
+
const accessToken = String(opts.accessToken || "").trim();
|
|
21015
|
+
if (!accessToken) {
|
|
21016
|
+
return void 0;
|
|
21017
|
+
}
|
|
21018
|
+
const baseUrl = normalizeBaseUrl(opts.iapubBaseUrl);
|
|
21019
|
+
const memoKey = `${baseUrl}::${accessToken}`;
|
|
21020
|
+
let pending = sessionMemo.get(memoKey);
|
|
21021
|
+
if (!pending) {
|
|
21022
|
+
pending = probeSessionIdentity(baseUrl, accessToken, opts.fetchImpl ?? fetch);
|
|
21023
|
+
sessionMemo.set(memoKey, pending);
|
|
21024
|
+
}
|
|
21025
|
+
return pending;
|
|
21026
|
+
}
|
|
21027
|
+
async function probeSessionIdentity(baseUrl, accessToken, fetchImpl) {
|
|
21028
|
+
try {
|
|
21029
|
+
const response = await fetchImpl(`${baseUrl}${sessionPath}`, {
|
|
21030
|
+
method: "GET",
|
|
21031
|
+
headers: { "x-access-token": accessToken }
|
|
21032
|
+
});
|
|
21033
|
+
if (!response.ok) {
|
|
21034
|
+
return void 0;
|
|
21035
|
+
}
|
|
21036
|
+
const payload = asRecord(await response.json());
|
|
21037
|
+
if (!payload) {
|
|
21038
|
+
return void 0;
|
|
21039
|
+
}
|
|
21040
|
+
const identity = asRecord(payload.identity);
|
|
21041
|
+
const data = asRecord(payload.data);
|
|
21042
|
+
const user = asRecord(data?.user);
|
|
21043
|
+
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21044
|
+
const singleRole = coerceText(user?.role);
|
|
21045
|
+
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21046
|
+
const resolved = {
|
|
21047
|
+
source: "iapub/sessions",
|
|
21048
|
+
userId: coerceId(user?.id),
|
|
21049
|
+
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21050
|
+
teamId: coerceId(identity?.team),
|
|
21051
|
+
teamDomain: coerceText(identity?.domain),
|
|
21052
|
+
...roles ? { roles } : {},
|
|
21053
|
+
consumerType: coerceText(payload.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21054
|
+
};
|
|
21055
|
+
memoizedSessionIdentity = resolved;
|
|
21056
|
+
return resolved;
|
|
21057
|
+
} catch {
|
|
21058
|
+
return void 0;
|
|
21059
|
+
}
|
|
21060
|
+
}
|
|
21061
|
+
function describeTeam(id) {
|
|
21062
|
+
const label = id?.teamName ?? id?.teamDomain;
|
|
21063
|
+
return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
|
|
21064
|
+
}
|
|
21065
|
+
function formatIdentityLine(id, mask) {
|
|
21066
|
+
const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
|
|
21067
|
+
const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
|
|
21068
|
+
if (id.source === "pmak/me") {
|
|
21069
|
+
const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
|
|
21070
|
+
return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
|
|
21071
|
+
}
|
|
21072
|
+
return mask(
|
|
21073
|
+
`postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
|
|
21074
|
+
);
|
|
21075
|
+
}
|
|
21076
|
+
function crossCheckIdentities(args) {
|
|
21077
|
+
if (args.mode === "off") {
|
|
21078
|
+
return { ok: true, level: "ok", message: "" };
|
|
21079
|
+
}
|
|
21080
|
+
const pmakTeamId = args.pmak?.teamId;
|
|
21081
|
+
const sessionTeamId = args.session?.teamId;
|
|
21082
|
+
if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
|
|
21083
|
+
const level = args.mode === "enforce" ? "fail" : "note";
|
|
21084
|
+
const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
|
|
21085
|
+
const fix = level === "fail" ? "Use one credential pair from a single parent org: re-mint the access token from the same parent org as postman-api-key (postman-resolve-service-token-action, or POST https://api.getpostman.com/service-account-tokens with that team's PMAK), or set postman-api-key to the matching parent org." : "Use one credential pair from a single parent org. Set credential-preflight: enforce to fail the run on this condition.";
|
|
21086
|
+
return {
|
|
21087
|
+
ok: false,
|
|
21088
|
+
level,
|
|
21089
|
+
message: args.mask(
|
|
21090
|
+
`postman: ${lead} - PMAK belongs to ${describeTeam(args.pmak)} but the access token's session belongs to a different parent org, ${describeTeam(args.session)}. Assets would be created against one team while Bifrost linking and governance act under the other, producing duplicate-link 400s and workspaces not visible to the other credential. ` + fix
|
|
21091
|
+
)
|
|
21092
|
+
};
|
|
21093
|
+
}
|
|
21094
|
+
if (pmakTeamId && sessionTeamId) {
|
|
21095
|
+
const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
|
|
21096
|
+
const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
|
|
21097
|
+
return {
|
|
21098
|
+
ok: true,
|
|
21099
|
+
level: "ok",
|
|
21100
|
+
message: args.mask(
|
|
21101
|
+
`postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
|
|
21102
|
+
)
|
|
21103
|
+
};
|
|
21104
|
+
}
|
|
21105
|
+
const missing = [
|
|
21106
|
+
!pmakTeamId ? "PMAK identity" : void 0,
|
|
21107
|
+
!sessionTeamId ? "access-token session identity" : void 0
|
|
21108
|
+
].filter(Boolean).join(" and ");
|
|
21109
|
+
return {
|
|
21110
|
+
ok: false,
|
|
21111
|
+
level: "note",
|
|
21112
|
+
message: args.mask(
|
|
21113
|
+
`postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
|
|
21114
|
+
)
|
|
21115
|
+
};
|
|
21116
|
+
}
|
|
21117
|
+
async function runCredentialPreflight(args) {
|
|
21118
|
+
if (args.mode === "off") {
|
|
21119
|
+
return;
|
|
21120
|
+
}
|
|
21121
|
+
const mask = args.mask;
|
|
21122
|
+
const apiKey = String(args.postmanApiKey || "").trim();
|
|
21123
|
+
const accessToken = String(args.postmanAccessToken || "").trim();
|
|
21124
|
+
let pmak = args.pmak;
|
|
21125
|
+
if (pmak) {
|
|
21126
|
+
args.log.info(formatIdentityLine(pmak, mask));
|
|
21127
|
+
} else if (apiKey) {
|
|
21128
|
+
try {
|
|
21129
|
+
pmak = await resolvePmakIdentity({
|
|
21130
|
+
apiBaseUrl: args.apiBaseUrl,
|
|
21131
|
+
apiKey,
|
|
21132
|
+
fetchImpl: args.fetchImpl
|
|
21133
|
+
});
|
|
21134
|
+
} catch (error2) {
|
|
21135
|
+
args.log.warning(
|
|
21136
|
+
mask(
|
|
21137
|
+
`postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21138
|
+
)
|
|
21139
|
+
);
|
|
21140
|
+
}
|
|
21141
|
+
if (pmak) {
|
|
21142
|
+
args.log.info(formatIdentityLine(pmak, mask));
|
|
21143
|
+
} else {
|
|
21144
|
+
args.log.warning(
|
|
21145
|
+
mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
|
|
21146
|
+
);
|
|
21147
|
+
}
|
|
21148
|
+
}
|
|
21149
|
+
if (!accessToken) {
|
|
21150
|
+
args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
|
|
21151
|
+
return;
|
|
21152
|
+
}
|
|
21153
|
+
let session;
|
|
21154
|
+
try {
|
|
21155
|
+
session = await resolveSessionIdentity({
|
|
21156
|
+
iapubBaseUrl: args.iapubBaseUrl,
|
|
21157
|
+
accessToken,
|
|
21158
|
+
fetchImpl: args.fetchImpl
|
|
21159
|
+
});
|
|
21160
|
+
} catch (error2) {
|
|
21161
|
+
args.log.warning(
|
|
21162
|
+
mask(
|
|
21163
|
+
`postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21164
|
+
)
|
|
21165
|
+
);
|
|
21166
|
+
}
|
|
21167
|
+
if (session) {
|
|
21168
|
+
args.log.info(formatIdentityLine(session, mask));
|
|
21169
|
+
} else {
|
|
21170
|
+
args.log.warning(
|
|
21171
|
+
mask(
|
|
21172
|
+
"postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
|
|
21173
|
+
)
|
|
21174
|
+
);
|
|
21175
|
+
}
|
|
21176
|
+
const result = crossCheckIdentities({
|
|
21177
|
+
pmak,
|
|
21178
|
+
session,
|
|
21179
|
+
workspaceTeamId: args.workspaceTeamId,
|
|
21180
|
+
explicitTeamId: args.explicitTeamId,
|
|
21181
|
+
mode: args.mode,
|
|
21182
|
+
mask
|
|
21183
|
+
});
|
|
21184
|
+
if (!result.message) {
|
|
21185
|
+
return;
|
|
21186
|
+
}
|
|
21187
|
+
if (result.level === "fail") {
|
|
21188
|
+
throw new Error(result.message);
|
|
21189
|
+
}
|
|
21190
|
+
if (result.level === "note") {
|
|
21191
|
+
args.log.warning(result.message);
|
|
21192
|
+
return;
|
|
21193
|
+
}
|
|
21194
|
+
args.log.info(result.message);
|
|
21195
|
+
}
|
|
21196
|
+
|
|
21197
|
+
// src/lib/error-advice.ts
|
|
21198
|
+
var WORKSPACE_PERSONAL_ONLY_ADVICE = "Workspace creation failed: This may be an Org-mode account that requires a workspace-team-id input. The Postman API does not allow creating team workspaces at the organization level. Use the workspace-team-id input to specify which sub-team should own this workspace.";
|
|
21199
|
+
function expiryAdvice(code) {
|
|
21200
|
+
return `postman: Bifrost rejected the access token (${code}). Service-account access tokens expire after about 1 to 1.5 hours; this run likely outlived its token. Re-mint a fresh token (postman-resolve-service-token-action, or POST https://api.getpostman.com/service-account-tokens) and re-run. If it was just minted, confirm postman-access-token is the token for the same parent org as postman-api-key.`;
|
|
21201
|
+
}
|
|
21202
|
+
function forbiddenAdvice(ctx) {
|
|
21203
|
+
const sessionDetail = ctx.sessionTeamId ? ` while the access token is valid (it resolved to team ${ctx.sessionTeamId}${ctx.sessionRoles && ctx.sessionRoles.length > 0 ? `, roles [${ctx.sessionRoles.join(", ")}]` : ""}${ctx.sessionConsumerType ? `, consumerType ${ctx.sessionConsumerType}` : ""} at preflight)` : "";
|
|
21204
|
+
const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
|
|
21205
|
+
const teamClause = scopedTeamId ? `, or workspace-team-id ${scopedTeamId} names a sub-team it cannot act in` : ", or the workspace-team-id / POSTMAN_TEAM_ID in use names a sub-team it cannot act in";
|
|
21206
|
+
return `postman: Bifrost refused ${ctx.operation || "this operation"} with 403${sessionDetail}. The token's identity lacks permission for this endpoint${teamClause}. Verify the token's role and that workspace-team-id / POSTMAN_TEAM_ID matches a sub-team from GET https://api.getpostman.com/teams.`;
|
|
21207
|
+
}
|
|
21208
|
+
function buildAdvice(status, body, ctx) {
|
|
21209
|
+
if (body.includes("UNAUTHENTICATED")) {
|
|
21210
|
+
return expiryAdvice("UNAUTHENTICATED");
|
|
21211
|
+
}
|
|
21212
|
+
if (body.includes("authenticationError")) {
|
|
21213
|
+
return expiryAdvice("authenticationError");
|
|
21214
|
+
}
|
|
21215
|
+
if (body.includes("Only personal workspaces")) {
|
|
21216
|
+
return WORKSPACE_PERSONAL_ONLY_ADVICE;
|
|
21217
|
+
}
|
|
21218
|
+
if (body.includes("projectAlreadyConnected")) {
|
|
21219
|
+
return `postman: ${ctx.operation || "this operation"} reports projectAlreadyConnected with no workspace id in the error body. The repository is already linked to a workspace this credential cannot see, usually one created by a different credential pair or sub-team. Delete the stale link or its workspace, then re-run with one credential pair from a single parent org.`;
|
|
21220
|
+
}
|
|
21221
|
+
if (body.includes("invalidParamError") && body.includes("already exists")) {
|
|
21222
|
+
return `postman: ${ctx.operation || "this operation"} hit a duplicate resource error (invalidParamError: already exists). A matching resource already exists, possibly under another credential pair or sub-team where this credential cannot see it. Identify which workspace holds the existing resource and re-run with one credential pair from a single parent org.`;
|
|
21223
|
+
}
|
|
21224
|
+
if (body.includes("Team feature is not available for your organization")) {
|
|
21225
|
+
return `postman: ${ctx.operation || "this operation"} failed because the team feature is not available for this organization. The credential belongs to an account whose plan lacks team features; use credentials from the intended team and confirm the plan supports this operation.`;
|
|
21226
|
+
}
|
|
21227
|
+
if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
|
|
21228
|
+
return forbiddenAdvice(ctx);
|
|
21229
|
+
}
|
|
21230
|
+
return void 0;
|
|
21231
|
+
}
|
|
21232
|
+
function adviseFromHttpError(err, ctx) {
|
|
21233
|
+
const body = err.responseBody || err.message || "";
|
|
21234
|
+
const advice = buildAdvice(err.status, body, ctx);
|
|
21235
|
+
if (!advice) {
|
|
21236
|
+
return void 0;
|
|
21237
|
+
}
|
|
21238
|
+
return new Error(ctx.mask(advice), { cause: err });
|
|
21239
|
+
}
|
|
21240
|
+
function adviseFromBifrostBody(status, body, ctx) {
|
|
21241
|
+
const advice = buildAdvice(status, String(body || ""), ctx);
|
|
21242
|
+
if (!advice) {
|
|
21243
|
+
return void 0;
|
|
21244
|
+
}
|
|
21245
|
+
return new Error(ctx.mask(advice), {
|
|
21246
|
+
cause: new Error(ctx.mask(`HTTP ${status}: ${String(body || "").slice(0, 800)}`))
|
|
21247
|
+
});
|
|
21248
|
+
}
|
|
21249
|
+
|
|
20946
21250
|
// src/lib/secrets.ts
|
|
20947
21251
|
var REDACTED = "[REDACTED]";
|
|
20948
21252
|
var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -20995,6 +21299,9 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
|
|
|
20995
21299
|
return sanitized.split(secret).join(replacement);
|
|
20996
21300
|
}, source);
|
|
20997
21301
|
}
|
|
21302
|
+
function createSecretMasker(secretValues, replacement = REDACTED) {
|
|
21303
|
+
return (input) => redactSecrets(input, secretValues, replacement);
|
|
21304
|
+
}
|
|
20998
21305
|
function headerEntries(headers) {
|
|
20999
21306
|
if (headers instanceof Headers) {
|
|
21000
21307
|
return Array.from(headers.entries());
|
|
@@ -21126,12 +21433,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
|
|
|
21126
21433
|
prod: {
|
|
21127
21434
|
apiBaseUrl: "https://api.getpostman.com",
|
|
21128
21435
|
bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
|
|
21436
|
+
iapubBaseUrl: "https://iapub.postman.co",
|
|
21129
21437
|
observabilityBaseUrl: "https://api.observability.postman.com",
|
|
21130
21438
|
observabilityEnv: "production"
|
|
21131
21439
|
},
|
|
21132
21440
|
beta: {
|
|
21133
21441
|
apiBaseUrl: "https://api.getpostman-beta.com",
|
|
21134
21442
|
bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
|
|
21443
|
+
iapubBaseUrl: "https://iapub.postman.co",
|
|
21135
21444
|
observabilityBaseUrl: "https://api.observability.postman-beta.com",
|
|
21136
21445
|
observabilityEnv: "beta"
|
|
21137
21446
|
}
|
|
@@ -21195,7 +21504,23 @@ var BifrostCatalogClient = class {
|
|
|
21195
21504
|
}
|
|
21196
21505
|
return h;
|
|
21197
21506
|
}
|
|
21198
|
-
|
|
21507
|
+
/**
|
|
21508
|
+
* Reactive error-advice context. The session identity comes from the credential
|
|
21509
|
+
* preflight's in-process memo when it ran; the advice degrades gracefully without it.
|
|
21510
|
+
*/
|
|
21511
|
+
adviceContext(operation) {
|
|
21512
|
+
const session = getMemoizedSessionIdentity();
|
|
21513
|
+
return {
|
|
21514
|
+
operation,
|
|
21515
|
+
hasAccessToken: Boolean(this.accessToken),
|
|
21516
|
+
sessionTeamId: session?.teamId,
|
|
21517
|
+
sessionRoles: session?.roles,
|
|
21518
|
+
sessionConsumerType: session?.consumerType,
|
|
21519
|
+
explicitTeamId: this.teamId || void 0,
|
|
21520
|
+
mask: createSecretMasker(this.secretValues)
|
|
21521
|
+
};
|
|
21522
|
+
}
|
|
21523
|
+
async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
|
|
21199
21524
|
const response = await this.fetchFn(this.bifrostProxyUrl, {
|
|
21200
21525
|
method: "POST",
|
|
21201
21526
|
headers: this.headers(),
|
|
@@ -21207,20 +21532,27 @@ var BifrostCatalogClient = class {
|
|
|
21207
21532
|
})
|
|
21208
21533
|
});
|
|
21209
21534
|
if (!response.ok) {
|
|
21210
|
-
|
|
21535
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21211
21536
|
method: "POST",
|
|
21212
21537
|
url: `bifrost:api-catalog:${method} ${path6}`,
|
|
21213
21538
|
secretValues: this.secretValues
|
|
21214
21539
|
});
|
|
21540
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
|
|
21541
|
+
throw advised ?? httpErr;
|
|
21215
21542
|
}
|
|
21216
21543
|
const data = await response.json();
|
|
21217
21544
|
if (data && typeof data === "object" && "error" in data && data.error) {
|
|
21218
21545
|
const errObj = data.error;
|
|
21219
|
-
|
|
21546
|
+
const advised = adviseFromBifrostBody(
|
|
21547
|
+
response.status,
|
|
21548
|
+
JSON.stringify({ error: errObj }),
|
|
21549
|
+
this.adviceContext(operation)
|
|
21550
|
+
);
|
|
21551
|
+
throw advised ?? new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
|
|
21220
21552
|
}
|
|
21221
21553
|
return data;
|
|
21222
21554
|
}
|
|
21223
|
-
async akitaProxyRequest(method, path6, body = {}) {
|
|
21555
|
+
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
|
|
21224
21556
|
const response = await this.fetchFn(this.bifrostProxyUrl, {
|
|
21225
21557
|
method: "POST",
|
|
21226
21558
|
headers: this.headers(),
|
|
@@ -21233,7 +21565,10 @@ var BifrostCatalogClient = class {
|
|
|
21233
21565
|
});
|
|
21234
21566
|
if (!response.ok) {
|
|
21235
21567
|
const text = await response.text().catch(() => "");
|
|
21236
|
-
|
|
21568
|
+
const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
|
|
21569
|
+
const errorText = advised ? text ? `${text}
|
|
21570
|
+
${advised.message}` : advised.message : text;
|
|
21571
|
+
return { ok: false, status: response.status, data: null, errorText };
|
|
21237
21572
|
}
|
|
21238
21573
|
const data = await response.json();
|
|
21239
21574
|
return { ok: true, status: response.status, data, errorText: "" };
|
|
@@ -21248,7 +21583,9 @@ var BifrostCatalogClient = class {
|
|
|
21248
21583
|
const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
|
|
21249
21584
|
const data = await this.proxyRequest(
|
|
21250
21585
|
"GET",
|
|
21251
|
-
`/api/v1/onboarding/discovered-services?status=discovered${cursorParam}
|
|
21586
|
+
`/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`,
|
|
21587
|
+
{},
|
|
21588
|
+
"discovered-services listing"
|
|
21252
21589
|
);
|
|
21253
21590
|
allItems.push(...data.items || []);
|
|
21254
21591
|
if (data.total && allItems.length >= data.total) {
|
|
@@ -21272,7 +21609,8 @@ var BifrostCatalogClient = class {
|
|
|
21272
21609
|
const data = await this.proxyRequest(
|
|
21273
21610
|
"POST",
|
|
21274
21611
|
"/api/v1/onboarding/prepare-collection",
|
|
21275
|
-
{ service_id: String(serviceId), workspace_id: workspaceId }
|
|
21612
|
+
{ service_id: String(serviceId), workspace_id: workspaceId },
|
|
21613
|
+
"collection preparation"
|
|
21276
21614
|
);
|
|
21277
21615
|
return data.id;
|
|
21278
21616
|
},
|
|
@@ -21296,7 +21634,8 @@ var BifrostCatalogClient = class {
|
|
|
21296
21634
|
await this.proxyRequest(
|
|
21297
21635
|
"POST",
|
|
21298
21636
|
"/api/v1/onboarding/git",
|
|
21299
|
-
body
|
|
21637
|
+
body,
|
|
21638
|
+
"git onboarding"
|
|
21300
21639
|
);
|
|
21301
21640
|
},
|
|
21302
21641
|
{ maxAttempts: 2, delayMs: 3e3 }
|
|
@@ -21309,7 +21648,9 @@ var BifrostCatalogClient = class {
|
|
|
21309
21648
|
for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
|
|
21310
21649
|
const result = await this.akitaProxyRequest(
|
|
21311
21650
|
"GET",
|
|
21312
|
-
`/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}
|
|
21651
|
+
`/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
|
|
21652
|
+
{},
|
|
21653
|
+
"provider service resolution"
|
|
21313
21654
|
);
|
|
21314
21655
|
if (!result.ok || !result.data) return null;
|
|
21315
21656
|
const services = result.data.services || [];
|
|
@@ -21339,7 +21680,8 @@ var BifrostCatalogClient = class {
|
|
|
21339
21680
|
workspace_id: workspaceId,
|
|
21340
21681
|
system_env: systemEnvironmentId
|
|
21341
21682
|
}]
|
|
21342
|
-
}
|
|
21683
|
+
},
|
|
21684
|
+
"Insights onboarding acknowledgment"
|
|
21343
21685
|
);
|
|
21344
21686
|
if (!result.ok) {
|
|
21345
21687
|
throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
|
|
@@ -21348,7 +21690,9 @@ var BifrostCatalogClient = class {
|
|
|
21348
21690
|
async acknowledgeWorkspace(workspaceId) {
|
|
21349
21691
|
const result = await this.akitaProxyRequest(
|
|
21350
21692
|
"POST",
|
|
21351
|
-
`/v2/workspaces/${workspaceId}/onboarding/acknowledge
|
|
21693
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
21694
|
+
{},
|
|
21695
|
+
"workspace onboarding acknowledgment"
|
|
21352
21696
|
);
|
|
21353
21697
|
if (!result.ok) {
|
|
21354
21698
|
throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
|
|
@@ -21368,18 +21712,22 @@ var BifrostCatalogClient = class {
|
|
|
21368
21712
|
}
|
|
21369
21713
|
);
|
|
21370
21714
|
if (!response.ok) {
|
|
21371
|
-
|
|
21715
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21372
21716
|
method: "POST",
|
|
21373
21717
|
url: `observability:createApplication(${workspaceId})`,
|
|
21374
21718
|
secretValues: this.secretValues
|
|
21375
21719
|
});
|
|
21720
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
21721
|
+
throw advised ?? httpErr;
|
|
21376
21722
|
}
|
|
21377
21723
|
return response.json();
|
|
21378
21724
|
}
|
|
21379
21725
|
async getTeamVerificationToken(workspaceId) {
|
|
21380
21726
|
const result = await this.akitaProxyRequest(
|
|
21381
21727
|
"GET",
|
|
21382
|
-
`/v2/workspaces/${workspaceId}/team-verification-token
|
|
21728
|
+
`/v2/workspaces/${workspaceId}/team-verification-token`,
|
|
21729
|
+
{},
|
|
21730
|
+
"team verification token retrieval"
|
|
21383
21731
|
);
|
|
21384
21732
|
if (!result.ok || !result.data) return null;
|
|
21385
21733
|
return result.data.team_verification_token || null;
|
|
@@ -21396,11 +21744,13 @@ var BifrostCatalogClient = class {
|
|
|
21396
21744
|
})
|
|
21397
21745
|
});
|
|
21398
21746
|
if (!response.ok) {
|
|
21399
|
-
|
|
21747
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21400
21748
|
method: "POST",
|
|
21401
21749
|
url: "bifrost:identity:POST /api/keys",
|
|
21402
21750
|
secretValues: this.secretValues
|
|
21403
21751
|
});
|
|
21752
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("API key creation"));
|
|
21753
|
+
throw advised ?? httpErr;
|
|
21404
21754
|
}
|
|
21405
21755
|
const data = await response.json();
|
|
21406
21756
|
const apikey = data?.apikey;
|
|
@@ -21428,7 +21778,17 @@ var POLL_INTERVAL_DEFAULT = 10;
|
|
|
21428
21778
|
var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
|
|
21429
21779
|
var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
|
|
21430
21780
|
var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
|
|
21781
|
+
var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
|
|
21431
21782
|
var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
|
|
21783
|
+
function parsePreflightMode(value) {
|
|
21784
|
+
const normalized = String(value || "warn").trim().toLowerCase();
|
|
21785
|
+
if (normalized === "enforce" || normalized === "warn" || normalized === "off") {
|
|
21786
|
+
return normalized;
|
|
21787
|
+
}
|
|
21788
|
+
throw new Error(
|
|
21789
|
+
`Unsupported credential-preflight "${value}". Supported values: enforce, warn, off`
|
|
21790
|
+
);
|
|
21791
|
+
}
|
|
21432
21792
|
function trimTrailingSlash(value) {
|
|
21433
21793
|
return value.replace(/\/+$/, "");
|
|
21434
21794
|
}
|
|
@@ -21505,11 +21865,13 @@ function resolveInputs(env = process.env) {
|
|
|
21505
21865
|
postmanApiKey,
|
|
21506
21866
|
postmanTeamId,
|
|
21507
21867
|
githubToken: get("github-token", env.GITHUB_TOKEN || ""),
|
|
21868
|
+
credentialPreflight: parsePreflightMode(get("credential-preflight", "warn")),
|
|
21508
21869
|
pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
|
|
21509
21870
|
pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
|
|
21510
21871
|
postmanStack,
|
|
21511
21872
|
postmanApiBase: endpointProfile.apiBaseUrl,
|
|
21512
21873
|
postmanBifrostBase: endpointProfile.bifrostBaseUrl,
|
|
21874
|
+
postmanIapubBase: endpointProfile.iapubBaseUrl,
|
|
21513
21875
|
postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
|
|
21514
21876
|
postmanObservabilityEnv: endpointProfile.observabilityEnv
|
|
21515
21877
|
};
|
|
@@ -21615,11 +21977,14 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
21615
21977
|
let apiKey = inputs.postmanApiKey;
|
|
21616
21978
|
const teamId = inputs.postmanTeamId;
|
|
21617
21979
|
let keyValid = false;
|
|
21980
|
+
let pmakIdentity;
|
|
21618
21981
|
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
21619
21982
|
if (apiKey) {
|
|
21620
21983
|
const result = await validateApiKey(apiKey, apiBase);
|
|
21621
21984
|
keyValid = result.valid;
|
|
21622
|
-
if (
|
|
21985
|
+
if (keyValid) {
|
|
21986
|
+
pmakIdentity = { source: "pmak/me", teamId: result.teamId };
|
|
21987
|
+
} else {
|
|
21623
21988
|
reporter.warning("Provided postman-api-key is invalid or expired.");
|
|
21624
21989
|
}
|
|
21625
21990
|
}
|
|
@@ -21666,7 +22031,20 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
21666
22031
|
} else {
|
|
21667
22032
|
reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
|
|
21668
22033
|
}
|
|
21669
|
-
return { apiKey, teamId: resolvedTeamId };
|
|
22034
|
+
return { apiKey, teamId: resolvedTeamId, pmakIdentity };
|
|
22035
|
+
}
|
|
22036
|
+
async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
|
|
22037
|
+
await runCredentialPreflight({
|
|
22038
|
+
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22039
|
+
iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
|
|
22040
|
+
pmak,
|
|
22041
|
+
postmanAccessToken: inputs.postmanAccessToken,
|
|
22042
|
+
explicitTeamId: inputs.postmanTeamId || void 0,
|
|
22043
|
+
mode: inputs.credentialPreflight,
|
|
22044
|
+
mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
|
|
22045
|
+
log: reporter,
|
|
22046
|
+
fetchImpl
|
|
22047
|
+
});
|
|
21670
22048
|
}
|
|
21671
22049
|
async function runAction() {
|
|
21672
22050
|
const inputs = resolveInputs();
|
|
@@ -21685,7 +22063,8 @@ async function runAction() {
|
|
|
21685
22063
|
observabilityBaseUrl: inputs.postmanObservabilityBase,
|
|
21686
22064
|
observabilityEnv: inputs.postmanObservabilityEnv
|
|
21687
22065
|
});
|
|
21688
|
-
const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22066
|
+
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22067
|
+
await runCredentialPreflightForInputs(inputs, pmakIdentity, core_exports);
|
|
21689
22068
|
const client = new BifrostCatalogClient({
|
|
21690
22069
|
accessToken: inputs.postmanAccessToken,
|
|
21691
22070
|
teamId,
|