@postman-cse/onboarding-insights 0.9.1 → 0.10.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/action.yml +4 -0
- package/dist/action.cjs +398 -17
- package/dist/cli.cjs +403 -17
- package/dist/index.cjs +404 -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,312 @@ 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 root = asRecord(payload.session) ?? payload;
|
|
21041
|
+
const identity = asRecord(root.identity);
|
|
21042
|
+
const data = asRecord(root.data);
|
|
21043
|
+
const user = asRecord(data?.user);
|
|
21044
|
+
const roleEntries = Array.isArray(user?.roles) ? user.roles.map((entry) => coerceText(entry) ?? coerceId(entry)).filter((entry) => Boolean(entry)) : [];
|
|
21045
|
+
const singleRole = coerceText(user?.role);
|
|
21046
|
+
const roles = roleEntries.length > 0 ? roleEntries : singleRole ? [singleRole] : void 0;
|
|
21047
|
+
const resolved = {
|
|
21048
|
+
source: "iapub/sessions",
|
|
21049
|
+
userId: coerceId(identity?.user) ?? coerceId(user?.id),
|
|
21050
|
+
fullName: coerceText(user?.fullName) ?? coerceText(user?.name) ?? coerceText(user?.username),
|
|
21051
|
+
teamId: coerceId(identity?.team),
|
|
21052
|
+
teamName: coerceText(user?.teamName),
|
|
21053
|
+
teamDomain: coerceText(identity?.domain),
|
|
21054
|
+
...roles ? { roles } : {},
|
|
21055
|
+
consumerType: coerceText(root.consumerType) ?? coerceText(data?.consumerType) ?? coerceText(user?.consumerType)
|
|
21056
|
+
};
|
|
21057
|
+
memoizedSessionIdentity = resolved;
|
|
21058
|
+
return resolved;
|
|
21059
|
+
} catch {
|
|
21060
|
+
return void 0;
|
|
21061
|
+
}
|
|
21062
|
+
}
|
|
21063
|
+
function describeTeam(id) {
|
|
21064
|
+
const label = id?.teamName ?? id?.teamDomain;
|
|
21065
|
+
return `team ${id?.teamId ?? "unresolved"}${label ? ` (${label})` : ""}`;
|
|
21066
|
+
}
|
|
21067
|
+
function formatIdentityLine(id, mask) {
|
|
21068
|
+
const teamPart = id.teamId ? describeTeam(id) : "team unresolved";
|
|
21069
|
+
const domainPart = id.teamDomain ? `, domain ${id.teamDomain}` : "";
|
|
21070
|
+
if (id.source === "pmak/me") {
|
|
21071
|
+
const userPart = id.userId ? `user ${id.userId}${id.fullName ? ` (${id.fullName})` : ""}, ` : "";
|
|
21072
|
+
return mask(`postman: PMAK identity - ${userPart}${teamPart}${domainPart}`);
|
|
21073
|
+
}
|
|
21074
|
+
return mask(
|
|
21075
|
+
`postman: access-token session identity - ${teamPart}${domainPart} [source: iapub/sessions]`
|
|
21076
|
+
);
|
|
21077
|
+
}
|
|
21078
|
+
function crossCheckIdentities(args) {
|
|
21079
|
+
if (args.mode === "off") {
|
|
21080
|
+
return { ok: true, level: "ok", message: "" };
|
|
21081
|
+
}
|
|
21082
|
+
const pmakTeamId = args.pmak?.teamId;
|
|
21083
|
+
const sessionTeamId = args.session?.teamId;
|
|
21084
|
+
if (pmakTeamId && sessionTeamId && pmakTeamId !== sessionTeamId) {
|
|
21085
|
+
const level = args.mode === "enforce" ? "fail" : "note";
|
|
21086
|
+
const lead = level === "fail" ? "credential preflight FAILED" : "credential preflight note";
|
|
21087
|
+
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.";
|
|
21088
|
+
return {
|
|
21089
|
+
ok: false,
|
|
21090
|
+
level,
|
|
21091
|
+
message: args.mask(
|
|
21092
|
+
`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
|
|
21093
|
+
)
|
|
21094
|
+
};
|
|
21095
|
+
}
|
|
21096
|
+
if (pmakTeamId && sessionTeamId) {
|
|
21097
|
+
const scope = args.workspaceTeamId || args.explicitTeamId ? "parent org team" : "team";
|
|
21098
|
+
const label = args.pmak?.teamName ?? args.pmak?.teamDomain ?? args.session?.teamName ?? args.session?.teamDomain;
|
|
21099
|
+
return {
|
|
21100
|
+
ok: true,
|
|
21101
|
+
level: "ok",
|
|
21102
|
+
message: args.mask(
|
|
21103
|
+
`postman: credential preflight OK - PMAK and access token both resolve to ${scope} ${pmakTeamId}${label ? ` (${label})` : ""}`
|
|
21104
|
+
)
|
|
21105
|
+
};
|
|
21106
|
+
}
|
|
21107
|
+
const missing = [
|
|
21108
|
+
!pmakTeamId ? "PMAK identity" : void 0,
|
|
21109
|
+
!sessionTeamId ? "access-token session identity" : void 0
|
|
21110
|
+
].filter(Boolean).join(" and ");
|
|
21111
|
+
return {
|
|
21112
|
+
ok: false,
|
|
21113
|
+
level: "note",
|
|
21114
|
+
message: args.mask(
|
|
21115
|
+
`postman: credential preflight note - cross-check skipped because the ${missing} did not resolve a team id; continuing with reactive error guidance only`
|
|
21116
|
+
)
|
|
21117
|
+
};
|
|
21118
|
+
}
|
|
21119
|
+
async function runCredentialPreflight(args) {
|
|
21120
|
+
if (args.mode === "off") {
|
|
21121
|
+
return;
|
|
21122
|
+
}
|
|
21123
|
+
const mask = args.mask;
|
|
21124
|
+
const apiKey = String(args.postmanApiKey || "").trim();
|
|
21125
|
+
const accessToken = String(args.postmanAccessToken || "").trim();
|
|
21126
|
+
let pmak = args.pmak;
|
|
21127
|
+
if (pmak) {
|
|
21128
|
+
args.log.info(formatIdentityLine(pmak, mask));
|
|
21129
|
+
} else if (apiKey) {
|
|
21130
|
+
try {
|
|
21131
|
+
pmak = await resolvePmakIdentity({
|
|
21132
|
+
apiBaseUrl: args.apiBaseUrl,
|
|
21133
|
+
apiKey,
|
|
21134
|
+
fetchImpl: args.fetchImpl
|
|
21135
|
+
});
|
|
21136
|
+
} catch (error2) {
|
|
21137
|
+
args.log.warning(
|
|
21138
|
+
mask(
|
|
21139
|
+
`postman: credential preflight could not resolve PMAK identity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21140
|
+
)
|
|
21141
|
+
);
|
|
21142
|
+
}
|
|
21143
|
+
if (pmak) {
|
|
21144
|
+
args.log.info(formatIdentityLine(pmak, mask));
|
|
21145
|
+
} else {
|
|
21146
|
+
args.log.warning(
|
|
21147
|
+
mask("postman: credential preflight could not resolve PMAK identity from GET /me; continuing")
|
|
21148
|
+
);
|
|
21149
|
+
}
|
|
21150
|
+
}
|
|
21151
|
+
if (!accessToken) {
|
|
21152
|
+
args.log.info(mask("postman: Bifrost diagnostics limited: no access token"));
|
|
21153
|
+
return;
|
|
21154
|
+
}
|
|
21155
|
+
let session;
|
|
21156
|
+
try {
|
|
21157
|
+
session = await resolveSessionIdentity({
|
|
21158
|
+
iapubBaseUrl: args.iapubBaseUrl,
|
|
21159
|
+
accessToken,
|
|
21160
|
+
fetchImpl: args.fetchImpl
|
|
21161
|
+
});
|
|
21162
|
+
} catch (error2) {
|
|
21163
|
+
args.log.warning(
|
|
21164
|
+
mask(
|
|
21165
|
+
`postman: credential preflight could not resolve access-token session identity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21166
|
+
)
|
|
21167
|
+
);
|
|
21168
|
+
}
|
|
21169
|
+
if (session) {
|
|
21170
|
+
args.log.info(formatIdentityLine(session, mask));
|
|
21171
|
+
} else {
|
|
21172
|
+
args.log.warning(
|
|
21173
|
+
mask(
|
|
21174
|
+
"postman: credential preflight could not resolve the access-token session identity from iapub; continuing with reactive error guidance only"
|
|
21175
|
+
)
|
|
21176
|
+
);
|
|
21177
|
+
}
|
|
21178
|
+
const result = crossCheckIdentities({
|
|
21179
|
+
pmak,
|
|
21180
|
+
session,
|
|
21181
|
+
workspaceTeamId: args.workspaceTeamId,
|
|
21182
|
+
explicitTeamId: args.explicitTeamId,
|
|
21183
|
+
mode: args.mode,
|
|
21184
|
+
mask
|
|
21185
|
+
});
|
|
21186
|
+
if (!result.message) {
|
|
21187
|
+
return;
|
|
21188
|
+
}
|
|
21189
|
+
if (result.level === "fail") {
|
|
21190
|
+
throw new Error(result.message);
|
|
21191
|
+
}
|
|
21192
|
+
if (result.level === "note") {
|
|
21193
|
+
args.log.warning(result.message);
|
|
21194
|
+
return;
|
|
21195
|
+
}
|
|
21196
|
+
args.log.info(result.message);
|
|
21197
|
+
}
|
|
21198
|
+
|
|
21199
|
+
// src/lib/error-advice.ts
|
|
21200
|
+
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.";
|
|
21201
|
+
function expiryAdvice(code) {
|
|
21202
|
+
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.`;
|
|
21203
|
+
}
|
|
21204
|
+
function forbiddenAdvice(ctx) {
|
|
21205
|
+
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)` : "";
|
|
21206
|
+
const scopedTeamId = ctx.workspaceTeamId || ctx.explicitTeamId;
|
|
21207
|
+
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";
|
|
21208
|
+
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.`;
|
|
21209
|
+
}
|
|
21210
|
+
function buildAdvice(status, body, ctx) {
|
|
21211
|
+
if (body.includes("UNAUTHENTICATED")) {
|
|
21212
|
+
return expiryAdvice("UNAUTHENTICATED");
|
|
21213
|
+
}
|
|
21214
|
+
if (body.includes("authenticationError")) {
|
|
21215
|
+
return expiryAdvice("authenticationError");
|
|
21216
|
+
}
|
|
21217
|
+
if (body.includes("Only personal workspaces")) {
|
|
21218
|
+
return WORKSPACE_PERSONAL_ONLY_ADVICE;
|
|
21219
|
+
}
|
|
21220
|
+
if (body.includes("projectAlreadyConnected")) {
|
|
21221
|
+
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.`;
|
|
21222
|
+
}
|
|
21223
|
+
if (body.includes("invalidParamError") && body.includes("already exists")) {
|
|
21224
|
+
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.`;
|
|
21225
|
+
}
|
|
21226
|
+
if (body.includes("Team feature is not available for your organization")) {
|
|
21227
|
+
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.`;
|
|
21228
|
+
}
|
|
21229
|
+
if (body.includes("You are not authorized to perform this action") || status === 403 && ctx.hasAccessToken) {
|
|
21230
|
+
return forbiddenAdvice(ctx);
|
|
21231
|
+
}
|
|
21232
|
+
return void 0;
|
|
21233
|
+
}
|
|
21234
|
+
function adviseFromHttpError(err, ctx) {
|
|
21235
|
+
const body = err.responseBody || err.message || "";
|
|
21236
|
+
const advice = buildAdvice(err.status, body, ctx);
|
|
21237
|
+
if (!advice) {
|
|
21238
|
+
return void 0;
|
|
21239
|
+
}
|
|
21240
|
+
return new Error(ctx.mask(advice), { cause: err });
|
|
21241
|
+
}
|
|
21242
|
+
function adviseFromBifrostBody(status, body, ctx) {
|
|
21243
|
+
const advice = buildAdvice(status, String(body || ""), ctx);
|
|
21244
|
+
if (!advice) {
|
|
21245
|
+
return void 0;
|
|
21246
|
+
}
|
|
21247
|
+
return new Error(ctx.mask(advice), {
|
|
21248
|
+
cause: new Error(ctx.mask(`HTTP ${status}: ${String(body || "").slice(0, 800)}`))
|
|
21249
|
+
});
|
|
21250
|
+
}
|
|
21251
|
+
|
|
20946
21252
|
// src/lib/secrets.ts
|
|
20947
21253
|
var REDACTED = "[REDACTED]";
|
|
20948
21254
|
var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -20995,6 +21301,9 @@ function redactSecrets(input, secretValues, replacement = REDACTED) {
|
|
|
20995
21301
|
return sanitized.split(secret).join(replacement);
|
|
20996
21302
|
}, source);
|
|
20997
21303
|
}
|
|
21304
|
+
function createSecretMasker(secretValues, replacement = REDACTED) {
|
|
21305
|
+
return (input) => redactSecrets(input, secretValues, replacement);
|
|
21306
|
+
}
|
|
20998
21307
|
function headerEntries(headers) {
|
|
20999
21308
|
if (headers instanceof Headers) {
|
|
21000
21309
|
return Array.from(headers.entries());
|
|
@@ -21126,12 +21435,14 @@ var POSTMAN_ENDPOINT_PROFILES = {
|
|
|
21126
21435
|
prod: {
|
|
21127
21436
|
apiBaseUrl: "https://api.getpostman.com",
|
|
21128
21437
|
bifrostBaseUrl: "https://bifrost-premium-https-v4.gw.postman.com",
|
|
21438
|
+
iapubBaseUrl: "https://iapub.postman.co",
|
|
21129
21439
|
observabilityBaseUrl: "https://api.observability.postman.com",
|
|
21130
21440
|
observabilityEnv: "production"
|
|
21131
21441
|
},
|
|
21132
21442
|
beta: {
|
|
21133
21443
|
apiBaseUrl: "https://api.getpostman-beta.com",
|
|
21134
21444
|
bifrostBaseUrl: "https://bifrost-https-v4.gw.postman-beta.com",
|
|
21445
|
+
iapubBaseUrl: "https://iapub.postman.co",
|
|
21135
21446
|
observabilityBaseUrl: "https://api.observability.postman-beta.com",
|
|
21136
21447
|
observabilityEnv: "beta"
|
|
21137
21448
|
}
|
|
@@ -21195,7 +21506,23 @@ var BifrostCatalogClient = class {
|
|
|
21195
21506
|
}
|
|
21196
21507
|
return h;
|
|
21197
21508
|
}
|
|
21198
|
-
|
|
21509
|
+
/**
|
|
21510
|
+
* Reactive error-advice context. The session identity comes from the credential
|
|
21511
|
+
* preflight's in-process memo when it ran; the advice degrades gracefully without it.
|
|
21512
|
+
*/
|
|
21513
|
+
adviceContext(operation) {
|
|
21514
|
+
const session = getMemoizedSessionIdentity();
|
|
21515
|
+
return {
|
|
21516
|
+
operation,
|
|
21517
|
+
hasAccessToken: Boolean(this.accessToken),
|
|
21518
|
+
sessionTeamId: session?.teamId,
|
|
21519
|
+
sessionRoles: session?.roles,
|
|
21520
|
+
sessionConsumerType: session?.consumerType,
|
|
21521
|
+
explicitTeamId: this.teamId || void 0,
|
|
21522
|
+
mask: createSecretMasker(this.secretValues)
|
|
21523
|
+
};
|
|
21524
|
+
}
|
|
21525
|
+
async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
|
|
21199
21526
|
const response = await this.fetchFn(this.bifrostProxyUrl, {
|
|
21200
21527
|
method: "POST",
|
|
21201
21528
|
headers: this.headers(),
|
|
@@ -21207,20 +21534,27 @@ var BifrostCatalogClient = class {
|
|
|
21207
21534
|
})
|
|
21208
21535
|
});
|
|
21209
21536
|
if (!response.ok) {
|
|
21210
|
-
|
|
21537
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21211
21538
|
method: "POST",
|
|
21212
21539
|
url: `bifrost:api-catalog:${method} ${path6}`,
|
|
21213
21540
|
secretValues: this.secretValues
|
|
21214
21541
|
});
|
|
21542
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
|
|
21543
|
+
throw advised ?? httpErr;
|
|
21215
21544
|
}
|
|
21216
21545
|
const data = await response.json();
|
|
21217
21546
|
if (data && typeof data === "object" && "error" in data && data.error) {
|
|
21218
21547
|
const errObj = data.error;
|
|
21219
|
-
|
|
21548
|
+
const advised = adviseFromBifrostBody(
|
|
21549
|
+
response.status,
|
|
21550
|
+
JSON.stringify({ error: errObj }),
|
|
21551
|
+
this.adviceContext(operation)
|
|
21552
|
+
);
|
|
21553
|
+
throw advised ?? new Error(`api-catalog error: ${errObj?.message || errObj?.code || "unknown"}`);
|
|
21220
21554
|
}
|
|
21221
21555
|
return data;
|
|
21222
21556
|
}
|
|
21223
|
-
async akitaProxyRequest(method, path6, body = {}) {
|
|
21557
|
+
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
|
|
21224
21558
|
const response = await this.fetchFn(this.bifrostProxyUrl, {
|
|
21225
21559
|
method: "POST",
|
|
21226
21560
|
headers: this.headers(),
|
|
@@ -21233,7 +21567,10 @@ var BifrostCatalogClient = class {
|
|
|
21233
21567
|
});
|
|
21234
21568
|
if (!response.ok) {
|
|
21235
21569
|
const text = await response.text().catch(() => "");
|
|
21236
|
-
|
|
21570
|
+
const advised = adviseFromBifrostBody(response.status, text, this.adviceContext(operation));
|
|
21571
|
+
const errorText = advised ? text ? `${text}
|
|
21572
|
+
${advised.message}` : advised.message : text;
|
|
21573
|
+
return { ok: false, status: response.status, data: null, errorText };
|
|
21237
21574
|
}
|
|
21238
21575
|
const data = await response.json();
|
|
21239
21576
|
return { ok: true, status: response.status, data, errorText: "" };
|
|
@@ -21248,7 +21585,9 @@ var BifrostCatalogClient = class {
|
|
|
21248
21585
|
const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
|
|
21249
21586
|
const data = await this.proxyRequest(
|
|
21250
21587
|
"GET",
|
|
21251
|
-
`/api/v1/onboarding/discovered-services?status=discovered${cursorParam}
|
|
21588
|
+
`/api/v1/onboarding/discovered-services?status=discovered${cursorParam}`,
|
|
21589
|
+
{},
|
|
21590
|
+
"discovered-services listing"
|
|
21252
21591
|
);
|
|
21253
21592
|
allItems.push(...data.items || []);
|
|
21254
21593
|
if (data.total && allItems.length >= data.total) {
|
|
@@ -21272,7 +21611,8 @@ var BifrostCatalogClient = class {
|
|
|
21272
21611
|
const data = await this.proxyRequest(
|
|
21273
21612
|
"POST",
|
|
21274
21613
|
"/api/v1/onboarding/prepare-collection",
|
|
21275
|
-
{ service_id: String(serviceId), workspace_id: workspaceId }
|
|
21614
|
+
{ service_id: String(serviceId), workspace_id: workspaceId },
|
|
21615
|
+
"collection preparation"
|
|
21276
21616
|
);
|
|
21277
21617
|
return data.id;
|
|
21278
21618
|
},
|
|
@@ -21296,7 +21636,8 @@ var BifrostCatalogClient = class {
|
|
|
21296
21636
|
await this.proxyRequest(
|
|
21297
21637
|
"POST",
|
|
21298
21638
|
"/api/v1/onboarding/git",
|
|
21299
|
-
body
|
|
21639
|
+
body,
|
|
21640
|
+
"git onboarding"
|
|
21300
21641
|
);
|
|
21301
21642
|
},
|
|
21302
21643
|
{ maxAttempts: 2, delayMs: 3e3 }
|
|
@@ -21309,7 +21650,9 @@ var BifrostCatalogClient = class {
|
|
|
21309
21650
|
for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
|
|
21310
21651
|
const result = await this.akitaProxyRequest(
|
|
21311
21652
|
"GET",
|
|
21312
|
-
`/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}
|
|
21653
|
+
`/v2/api-catalog/services?status=discovered&populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
|
|
21654
|
+
{},
|
|
21655
|
+
"provider service resolution"
|
|
21313
21656
|
);
|
|
21314
21657
|
if (!result.ok || !result.data) return null;
|
|
21315
21658
|
const services = result.data.services || [];
|
|
@@ -21339,7 +21682,8 @@ var BifrostCatalogClient = class {
|
|
|
21339
21682
|
workspace_id: workspaceId,
|
|
21340
21683
|
system_env: systemEnvironmentId
|
|
21341
21684
|
}]
|
|
21342
|
-
}
|
|
21685
|
+
},
|
|
21686
|
+
"Insights onboarding acknowledgment"
|
|
21343
21687
|
);
|
|
21344
21688
|
if (!result.ok) {
|
|
21345
21689
|
throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
|
|
@@ -21348,7 +21692,9 @@ var BifrostCatalogClient = class {
|
|
|
21348
21692
|
async acknowledgeWorkspace(workspaceId) {
|
|
21349
21693
|
const result = await this.akitaProxyRequest(
|
|
21350
21694
|
"POST",
|
|
21351
|
-
`/v2/workspaces/${workspaceId}/onboarding/acknowledge
|
|
21695
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
21696
|
+
{},
|
|
21697
|
+
"workspace onboarding acknowledgment"
|
|
21352
21698
|
);
|
|
21353
21699
|
if (!result.ok) {
|
|
21354
21700
|
throw new Error(`Workspace acknowledge failed: ${result.status} ${result.errorText}`);
|
|
@@ -21368,18 +21714,22 @@ var BifrostCatalogClient = class {
|
|
|
21368
21714
|
}
|
|
21369
21715
|
);
|
|
21370
21716
|
if (!response.ok) {
|
|
21371
|
-
|
|
21717
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21372
21718
|
method: "POST",
|
|
21373
21719
|
url: `observability:createApplication(${workspaceId})`,
|
|
21374
21720
|
secretValues: this.secretValues
|
|
21375
21721
|
});
|
|
21722
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
21723
|
+
throw advised ?? httpErr;
|
|
21376
21724
|
}
|
|
21377
21725
|
return response.json();
|
|
21378
21726
|
}
|
|
21379
21727
|
async getTeamVerificationToken(workspaceId) {
|
|
21380
21728
|
const result = await this.akitaProxyRequest(
|
|
21381
21729
|
"GET",
|
|
21382
|
-
`/v2/workspaces/${workspaceId}/team-verification-token
|
|
21730
|
+
`/v2/workspaces/${workspaceId}/team-verification-token`,
|
|
21731
|
+
{},
|
|
21732
|
+
"team verification token retrieval"
|
|
21383
21733
|
);
|
|
21384
21734
|
if (!result.ok || !result.data) return null;
|
|
21385
21735
|
return result.data.team_verification_token || null;
|
|
@@ -21396,11 +21746,13 @@ var BifrostCatalogClient = class {
|
|
|
21396
21746
|
})
|
|
21397
21747
|
});
|
|
21398
21748
|
if (!response.ok) {
|
|
21399
|
-
|
|
21749
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
21400
21750
|
method: "POST",
|
|
21401
21751
|
url: "bifrost:identity:POST /api/keys",
|
|
21402
21752
|
secretValues: this.secretValues
|
|
21403
21753
|
});
|
|
21754
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("API key creation"));
|
|
21755
|
+
throw advised ?? httpErr;
|
|
21404
21756
|
}
|
|
21405
21757
|
const data = await response.json();
|
|
21406
21758
|
const apikey = data?.apikey;
|
|
@@ -21428,7 +21780,17 @@ var POLL_INTERVAL_DEFAULT = 10;
|
|
|
21428
21780
|
var PROD_ENDPOINTS = resolvePostmanEndpointProfile("prod");
|
|
21429
21781
|
var DEFAULT_POSTMAN_API_BASE = PROD_ENDPOINTS.apiBaseUrl;
|
|
21430
21782
|
var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
|
|
21783
|
+
var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
|
|
21431
21784
|
var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
|
|
21785
|
+
function parsePreflightMode(value) {
|
|
21786
|
+
const normalized = String(value || "warn").trim().toLowerCase();
|
|
21787
|
+
if (normalized === "enforce" || normalized === "warn" || normalized === "off") {
|
|
21788
|
+
return normalized;
|
|
21789
|
+
}
|
|
21790
|
+
throw new Error(
|
|
21791
|
+
`Unsupported credential-preflight "${value}". Supported values: enforce, warn, off`
|
|
21792
|
+
);
|
|
21793
|
+
}
|
|
21432
21794
|
function trimTrailingSlash(value) {
|
|
21433
21795
|
return value.replace(/\/+$/, "");
|
|
21434
21796
|
}
|
|
@@ -21505,11 +21867,13 @@ function resolveInputs(env = process.env) {
|
|
|
21505
21867
|
postmanApiKey,
|
|
21506
21868
|
postmanTeamId,
|
|
21507
21869
|
githubToken: get("github-token", env.GITHUB_TOKEN || ""),
|
|
21870
|
+
credentialPreflight: parsePreflightMode(get("credential-preflight", "warn")),
|
|
21508
21871
|
pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
|
|
21509
21872
|
pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
|
|
21510
21873
|
postmanStack,
|
|
21511
21874
|
postmanApiBase: endpointProfile.apiBaseUrl,
|
|
21512
21875
|
postmanBifrostBase: endpointProfile.bifrostBaseUrl,
|
|
21876
|
+
postmanIapubBase: endpointProfile.iapubBaseUrl,
|
|
21513
21877
|
postmanObservabilityBase: endpointProfile.observabilityBaseUrl,
|
|
21514
21878
|
postmanObservabilityEnv: endpointProfile.observabilityEnv
|
|
21515
21879
|
};
|
|
@@ -21615,11 +21979,14 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
21615
21979
|
let apiKey = inputs.postmanApiKey;
|
|
21616
21980
|
const teamId = inputs.postmanTeamId;
|
|
21617
21981
|
let keyValid = false;
|
|
21982
|
+
let pmakIdentity;
|
|
21618
21983
|
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
21619
21984
|
if (apiKey) {
|
|
21620
21985
|
const result = await validateApiKey(apiKey, apiBase);
|
|
21621
21986
|
keyValid = result.valid;
|
|
21622
|
-
if (
|
|
21987
|
+
if (keyValid) {
|
|
21988
|
+
pmakIdentity = { source: "pmak/me", teamId: result.teamId };
|
|
21989
|
+
} else {
|
|
21623
21990
|
reporter.warning("Provided postman-api-key is invalid or expired.");
|
|
21624
21991
|
}
|
|
21625
21992
|
}
|
|
@@ -21666,7 +22033,20 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
21666
22033
|
} else {
|
|
21667
22034
|
reporter.info("No postman-team-id resolved; omitting x-entity-team-id so Bifrost resolves team from the access token.");
|
|
21668
22035
|
}
|
|
21669
|
-
return { apiKey, teamId: resolvedTeamId };
|
|
22036
|
+
return { apiKey, teamId: resolvedTeamId, pmakIdentity };
|
|
22037
|
+
}
|
|
22038
|
+
async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl) {
|
|
22039
|
+
await runCredentialPreflight({
|
|
22040
|
+
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22041
|
+
iapubBaseUrl: inputs.postmanIapubBase || DEFAULT_POSTMAN_IAPUB_BASE,
|
|
22042
|
+
pmak,
|
|
22043
|
+
postmanAccessToken: inputs.postmanAccessToken,
|
|
22044
|
+
explicitTeamId: inputs.postmanTeamId || void 0,
|
|
22045
|
+
mode: inputs.credentialPreflight,
|
|
22046
|
+
mask: createSecretMasker([inputs.postmanApiKey, inputs.postmanAccessToken]),
|
|
22047
|
+
log: reporter,
|
|
22048
|
+
fetchImpl
|
|
22049
|
+
});
|
|
21670
22050
|
}
|
|
21671
22051
|
async function runAction() {
|
|
21672
22052
|
const inputs = resolveInputs();
|
|
@@ -21685,7 +22065,8 @@ async function runAction() {
|
|
|
21685
22065
|
observabilityBaseUrl: inputs.postmanObservabilityBase,
|
|
21686
22066
|
observabilityEnv: inputs.postmanObservabilityEnv
|
|
21687
22067
|
});
|
|
21688
|
-
const { apiKey, teamId } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22068
|
+
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22069
|
+
await runCredentialPreflightForInputs(inputs, pmakIdentity, core_exports);
|
|
21689
22070
|
const client = new BifrostCatalogClient({
|
|
21690
22071
|
accessToken: inputs.postmanAccessToken,
|
|
21691
22072
|
teamId,
|