@m1a0rz/agent-identity 0.4.0 → 0.4.2
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/README-cn.md +34 -0
- package/README.md +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -6
- package/dist/scripts/demo-get-session.d.ts +15 -0
- package/dist/scripts/demo-get-session.d.ts.map +1 -0
- package/dist/scripts/demo-get-session.js +58 -0
- package/dist/src/actions/identity-actions.d.ts +74 -8
- package/dist/src/actions/identity-actions.d.ts.map +1 -1
- package/dist/src/actions/identity-actions.js +209 -83
- package/dist/src/commands/identity-commands.d.ts.map +1 -1
- package/dist/src/commands/identity-commands.js +139 -11
- package/dist/src/gateway/identity-session-methods.d.ts +67 -0
- package/dist/src/gateway/identity-session-methods.d.ts.map +1 -0
- package/dist/src/gateway/identity-session-methods.js +134 -0
- package/dist/src/hooks/after-tool-call.d.ts.map +1 -1
- package/dist/src/hooks/after-tool-call.js +12 -0
- package/dist/src/hooks/before-agent-start.d.ts +2 -0
- package/dist/src/hooks/before-agent-start.d.ts.map +1 -1
- package/dist/src/hooks/before-agent-start.js +33 -6
- package/dist/src/hooks/before-tool-call.d.ts +1 -0
- package/dist/src/hooks/before-tool-call.d.ts.map +1 -1
- package/dist/src/hooks/before-tool-call.js +29 -5
- package/dist/src/hooks/llm-input.d.ts.map +1 -1
- package/dist/src/hooks/llm-input.js +32 -4
- package/dist/src/hooks/sessions-send-propagation.d.ts.map +1 -1
- package/dist/src/hooks/sessions-send-propagation.js +1 -0
- package/dist/src/hooks/sessions-spawn-propagation.d.ts.map +1 -1
- package/dist/src/hooks/sessions-spawn-propagation.js +1 -0
- package/dist/src/hooks/tool-result-persist.d.ts +20 -0
- package/dist/src/hooks/tool-result-persist.d.ts.map +1 -0
- package/dist/src/hooks/tool-result-persist.js +50 -0
- package/dist/src/preflight/plugin-preflight.d.ts +55 -0
- package/dist/src/preflight/plugin-preflight.d.ts.map +1 -0
- package/dist/src/preflight/plugin-preflight.js +226 -0
- package/dist/src/preflight/plugin-state.d.ts +18 -0
- package/dist/src/preflight/plugin-state.d.ts.map +1 -0
- package/dist/src/preflight/plugin-state.js +19 -0
- package/dist/src/routes/oidc-login.js +2 -2
- package/dist/src/services/identity-client.d.ts +106 -1
- package/dist/src/services/identity-client.d.ts.map +1 -1
- package/dist/src/services/identity-client.js +123 -1
- package/dist/src/services/identity-credentials.d.ts +1 -1
- package/dist/src/services/identity-credentials.d.ts.map +1 -1
- package/dist/src/services/identity-credentials.js +32 -16
- package/dist/src/services/oidc-client.d.ts +12 -1
- package/dist/src/services/oidc-client.d.ts.map +1 -1
- package/dist/src/services/oidc-client.js +20 -3
- package/dist/src/services/session-refresh.d.ts +10 -0
- package/dist/src/services/session-refresh.d.ts.map +1 -1
- package/dist/src/services/session-refresh.js +29 -5
- package/dist/src/services/skill-contract-metadata.d.ts +35 -0
- package/dist/src/services/skill-contract-metadata.d.ts.map +1 -0
- package/dist/src/services/skill-contract-metadata.js +145 -0
- package/dist/src/services/skill-contract-renderer.d.ts +14 -0
- package/dist/src/services/skill-contract-renderer.d.ts.map +1 -0
- package/dist/src/services/skill-contract-renderer.js +120 -0
- package/dist/src/services/tip-propagation.d.ts +2 -0
- package/dist/src/services/tip-propagation.d.ts.map +1 -1
- package/dist/src/services/tip-propagation.js +4 -3
- package/dist/src/services/tip-with-refresh.d.ts +1 -1
- package/dist/src/services/tip-with-refresh.d.ts.map +1 -1
- package/dist/src/services/tip-with-refresh.js +24 -39
- package/dist/src/store/credential-store.d.ts +6 -1
- package/dist/src/store/credential-store.d.ts.map +1 -1
- package/dist/src/store/credential-store.js +3 -0
- package/dist/src/store/oidc-state-store.d.ts +3 -3
- package/dist/src/store/oidc-state-store.d.ts.map +1 -1
- package/dist/src/store/oidc-state-store.js +2 -2
- package/dist/src/store/sender-session-store.d.ts +8 -0
- package/dist/src/store/sender-session-store.d.ts.map +1 -1
- package/dist/src/store/sender-session-store.js +46 -11
- package/dist/src/store/skill-contract-store.d.ts +19 -0
- package/dist/src/store/skill-contract-store.d.ts.map +1 -0
- package/dist/src/store/skill-contract-store.js +65 -0
- package/dist/src/store/skill-path-store.d.ts +5 -0
- package/dist/src/store/skill-path-store.d.ts.map +1 -1
- package/dist/src/store/skill-path-store.js +13 -1
- package/dist/src/tools/identity-approve-tool.d.ts +2 -11
- package/dist/src/tools/identity-approve-tool.d.ts.map +1 -1
- package/dist/src/tools/identity-config-suggest.d.ts +2 -13
- package/dist/src/tools/identity-config-suggest.d.ts.map +1 -1
- package/dist/src/tools/identity-config.d.ts +2 -7
- package/dist/src/tools/identity-config.d.ts.map +1 -1
- package/dist/src/tools/identity-fetch.d.ts +2 -13
- package/dist/src/tools/identity-fetch.d.ts.map +1 -1
- package/dist/src/tools/identity-fetch.js +3 -3
- package/dist/src/tools/identity-get-role-credentials.d.ts +10 -0
- package/dist/src/tools/identity-get-role-credentials.d.ts.map +1 -0
- package/dist/src/tools/identity-get-role-credentials.js +56 -0
- package/dist/src/tools/identity-get-session-token.d.ts +8 -0
- package/dist/src/tools/identity-get-session-token.d.ts.map +1 -0
- package/dist/src/tools/identity-get-session-token.js +46 -0
- package/dist/src/tools/identity-get-tip-token.d.ts +8 -0
- package/dist/src/tools/identity-get-tip-token.d.ts.map +1 -0
- package/dist/src/tools/identity-get-tip-token.js +46 -0
- package/dist/src/tools/identity-list-credentials.d.ts +2 -11
- package/dist/src/tools/identity-list-credentials.d.ts.map +1 -1
- package/dist/src/tools/identity-list-credentials.js +4 -3
- package/dist/src/tools/identity-list-risk-patterns.d.ts +2 -7
- package/dist/src/tools/identity-list-risk-patterns.d.ts.map +1 -1
- package/dist/src/tools/identity-list-roles.d.ts +8 -0
- package/dist/src/tools/identity-list-roles.d.ts.map +1 -0
- package/dist/src/tools/identity-list-roles.js +43 -0
- package/dist/src/tools/identity-list-tips.d.ts +2 -7
- package/dist/src/tools/identity-list-tips.d.ts.map +1 -1
- package/dist/src/tools/identity-login.d.ts +2 -7
- package/dist/src/tools/identity-login.d.ts.map +1 -1
- package/dist/src/tools/identity-logout.d.ts +2 -7
- package/dist/src/tools/identity-logout.d.ts.map +1 -1
- package/dist/src/tools/identity-risk-check.d.ts +3 -17
- package/dist/src/tools/identity-risk-check.d.ts.map +1 -1
- package/dist/src/tools/identity-set-binding.d.ts +2 -10
- package/dist/src/tools/identity-set-binding.d.ts.map +1 -1
- package/dist/src/tools/identity-status.d.ts +2 -7
- package/dist/src/tools/identity-status.d.ts.map +1 -1
- package/dist/src/tools/identity-unset-binding.d.ts +2 -9
- package/dist/src/tools/identity-unset-binding.d.ts.map +1 -1
- package/dist/src/tools/identity-whoami.d.ts +2 -7
- package/dist/src/tools/identity-whoami.d.ts.map +1 -1
- package/dist/src/types.d.ts +21 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/derive-session-key.d.ts +11 -1
- package/dist/src/utils/derive-session-key.d.ts.map +1 -1
- package/dist/src/utils/derive-session-key.js +46 -6
- package/openclaw.plugin.json +18 -0
- package/package.json +33 -7
|
@@ -18,7 +18,8 @@ import { getOrRefreshTIPToken } from "../services/tip-with-refresh.js";
|
|
|
18
18
|
import { fetchOIDCDiscovery, buildAuthorizationUrl, generateState, generatePKCE, generateNonce, } from "../services/oidc-client.js";
|
|
19
19
|
import { loadCredentialEnvBindings, loadAllCredentialEnvBindings, setCredentialEnvBinding, deleteCredentialEnvBinding, } from "../store/credential-env-bindings.js";
|
|
20
20
|
import { loadCredentials, setCredential, getCredential, deleteCredentialsForSession, } from "../store/credential-store.js";
|
|
21
|
-
import {
|
|
21
|
+
import { deleteSession } from "../store/session-store.js";
|
|
22
|
+
import { getSessionWithRefresh } from "../services/session-refresh.js";
|
|
22
23
|
import { createState } from "../store/oidc-state-store.js";
|
|
23
24
|
import { getAllTIPTokens, deleteTIPToken } from "../store/tip-store.js";
|
|
24
25
|
import { extractDelegationChainFromJwt } from "../utils/auth.js";
|
|
@@ -30,6 +31,34 @@ function inferFlowFromProvider(info) {
|
|
|
30
31
|
return "oauth2-m2m";
|
|
31
32
|
return "oauth2-user";
|
|
32
33
|
}
|
|
34
|
+
function buildTipRefreshOptions(deps, ctxAgentId, errorHolder) {
|
|
35
|
+
if (!deps.getOidcConfigForRefresh)
|
|
36
|
+
return undefined;
|
|
37
|
+
return {
|
|
38
|
+
identityService: deps.identityService,
|
|
39
|
+
getOidcConfigForRefresh: deps.getOidcConfigForRefresh,
|
|
40
|
+
configWorkloadName: deps.configWorkloadName,
|
|
41
|
+
ctxAgentId,
|
|
42
|
+
logger: deps.logger,
|
|
43
|
+
...(errorHolder ? { errorHolder } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function resolveAndRefreshTIP(deps, sessionKey, config, errorHolder) {
|
|
47
|
+
const ctxAgentId = resolveAgentId({ sessionKey, config: config });
|
|
48
|
+
return getOrRefreshTIPToken(deps.storeDir, sessionKey, buildTipRefreshOptions(deps, ctxAgentId, errorHolder));
|
|
49
|
+
}
|
|
50
|
+
function formatCredentialStatus(c) {
|
|
51
|
+
if (!c)
|
|
52
|
+
return "—";
|
|
53
|
+
if (c.type === "oauth2")
|
|
54
|
+
return c.status;
|
|
55
|
+
if (c.type === "api_key") {
|
|
56
|
+
if (c.valueFromEnv)
|
|
57
|
+
return `from \`${c.valueFromEnv}\``;
|
|
58
|
+
return c.value ? "✓" : "empty";
|
|
59
|
+
}
|
|
60
|
+
return "—";
|
|
61
|
+
}
|
|
33
62
|
const OAUTH_POLL_INTERVAL_MS = 2500;
|
|
34
63
|
const OAUTH_POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
35
64
|
const OAUTH_QUICK_RETRY_MS = 1000;
|
|
@@ -70,19 +99,9 @@ async function pollOAuthAndNotify(params) {
|
|
|
70
99
|
await sendCredentialMessage?.(target, `⚠️ Authorization timed out for \`${provider}\`. Run \`/identity fetch ${provider}\` again.`);
|
|
71
100
|
}
|
|
72
101
|
export async function runStatus(deps, sessionKey, config) {
|
|
73
|
-
const { storeDir
|
|
74
|
-
const session = await
|
|
75
|
-
const
|
|
76
|
-
const tipRefreshOptions = getOidcConfigForRefresh
|
|
77
|
-
? {
|
|
78
|
-
identityService,
|
|
79
|
-
getOidcConfigForRefresh,
|
|
80
|
-
configWorkloadName,
|
|
81
|
-
ctxAgentId,
|
|
82
|
-
logger,
|
|
83
|
-
}
|
|
84
|
-
: undefined;
|
|
85
|
-
const tip = await getOrRefreshTIPToken(storeDir, sessionKey, tipRefreshOptions);
|
|
102
|
+
const { storeDir } = deps;
|
|
103
|
+
const session = await getSessionWithRefresh(storeDir, sessionKey, deps.getOidcConfigForRefresh);
|
|
104
|
+
const tip = await resolveAndRefreshTIP(deps, sessionKey, config);
|
|
86
105
|
const credentials = await loadCredentials(storeDir, sessionKey);
|
|
87
106
|
const bindings = await loadCredentialEnvBindings(storeDir, sessionKey);
|
|
88
107
|
const tipChain = tip
|
|
@@ -92,7 +111,7 @@ export async function runStatus(deps, sessionKey, config) {
|
|
|
92
111
|
})()
|
|
93
112
|
: undefined;
|
|
94
113
|
return {
|
|
95
|
-
loggedIn: !!(session
|
|
114
|
+
loggedIn: !!(session?.userToken),
|
|
96
115
|
sub: session?.sub ?? null,
|
|
97
116
|
hasTip: Boolean(tip),
|
|
98
117
|
sessionLoginAt: session?.loginAt,
|
|
@@ -108,22 +127,10 @@ export async function runLogin(deps, sessionKey, options) {
|
|
|
108
127
|
const { storeDir, identityService, getOidcConfig, logger } = deps;
|
|
109
128
|
const config = options?.config;
|
|
110
129
|
const deliveryTarget = options?.deliveryTarget ?? null;
|
|
111
|
-
const session = await
|
|
112
|
-
|
|
113
|
-
if (hasValidCred && session) {
|
|
114
|
-
const ctxAgentId = resolveAgentId({ sessionKey, config: config });
|
|
130
|
+
const session = await getSessionWithRefresh(storeDir, sessionKey, deps.getOidcConfigForRefresh);
|
|
131
|
+
if (session) {
|
|
115
132
|
const errorHolder = {};
|
|
116
|
-
const
|
|
117
|
-
? {
|
|
118
|
-
identityService,
|
|
119
|
-
getOidcConfigForRefresh: deps.getOidcConfigForRefresh,
|
|
120
|
-
configWorkloadName: deps.configWorkloadName,
|
|
121
|
-
ctxAgentId,
|
|
122
|
-
logger,
|
|
123
|
-
errorHolder,
|
|
124
|
-
}
|
|
125
|
-
: undefined;
|
|
126
|
-
const tip = await getOrRefreshTIPToken(storeDir, sessionKey, tipRefreshOptions);
|
|
133
|
+
const tip = await resolveAndRefreshTIP(deps, sessionKey, config, errorHolder);
|
|
127
134
|
if (tip) {
|
|
128
135
|
return { kind: "already_logged_in", sub: session.sub };
|
|
129
136
|
}
|
|
@@ -138,10 +145,13 @@ export async function runLogin(deps, sessionKey, options) {
|
|
|
138
145
|
try {
|
|
139
146
|
const oidcConfig = await getOidcConfig();
|
|
140
147
|
const discovery = await fetchOIDCDiscovery(oidcConfig.discoveryUrl);
|
|
141
|
-
|
|
148
|
+
// Prefer explicit config, then fall back to what resolveOIDCConfig already cached
|
|
149
|
+
const identityProvider = deps.pluginConfig?.userpool?.identityProvider ?? oidcConfig.identityProvider;
|
|
150
|
+
const workloadName = deps.configWorkloadName;
|
|
151
|
+
const state = await generateState({ target: workloadName });
|
|
142
152
|
const { codeVerifier, codeChallenge } = await generatePKCE();
|
|
143
153
|
const nonce = await generateNonce();
|
|
144
|
-
|
|
154
|
+
createState(sessionKey, "", state, deliveryTarget, { codeVerifier, nonce });
|
|
145
155
|
const authUrl = buildAuthorizationUrl({
|
|
146
156
|
authorizationEndpoint: discovery.authorization_endpoint,
|
|
147
157
|
clientId: oidcConfig.clientId,
|
|
@@ -151,6 +161,8 @@ export async function runLogin(deps, sessionKey, options) {
|
|
|
151
161
|
codeChallenge,
|
|
152
162
|
codeChallengeMethod: "S256",
|
|
153
163
|
nonce,
|
|
164
|
+
redirectRelayUri: oidcConfig.callbackUrl,
|
|
165
|
+
identityProvider,
|
|
154
166
|
});
|
|
155
167
|
logInfo(logger, `login returning IdP URL for sessionKey=${sessionKey.slice(0, 24)}...`);
|
|
156
168
|
return { kind: "auth_url", authUrl };
|
|
@@ -176,11 +188,11 @@ export async function runListCredentials(deps, sessionKey, page = 1, filter) {
|
|
|
176
188
|
const { storeDir, identityClient, identityService, logger } = deps;
|
|
177
189
|
const creds = await loadCredentials(storeDir, sessionKey);
|
|
178
190
|
const bindings = await loadCredentialEnvBindings(storeDir, sessionKey);
|
|
179
|
-
let
|
|
191
|
+
let credProviders = [];
|
|
180
192
|
let totalCount = 0;
|
|
181
193
|
if (identityClient) {
|
|
182
|
-
const session = await
|
|
183
|
-
if (!session
|
|
194
|
+
const session = await getSessionWithRefresh(storeDir, sessionKey, deps.getOidcConfigForRefresh);
|
|
195
|
+
if (!session) {
|
|
184
196
|
logWarn(logger, `list-credentials: no valid session for key=${sessionKey.slice(0, 24)}...`);
|
|
185
197
|
}
|
|
186
198
|
else {
|
|
@@ -190,12 +202,15 @@ export async function runListCredentials(deps, sessionKey, page = 1, filter) {
|
|
|
190
202
|
apiFilter.Name = filter.name;
|
|
191
203
|
if (filter?.flow)
|
|
192
204
|
apiFilter.Flow = filter.flow;
|
|
205
|
+
if (filter?.type)
|
|
206
|
+
apiFilter.Type = filter.type;
|
|
193
207
|
const result = await identityClient.listCredentialProviders({
|
|
194
208
|
PageNumber: page,
|
|
195
209
|
PageSize: LIST_PAGE_SIZE,
|
|
210
|
+
PoolName: deps.workloadPoolName,
|
|
196
211
|
...(Object.keys(apiFilter).length > 0 ? { Filter: apiFilter } : {}),
|
|
197
212
|
});
|
|
198
|
-
|
|
213
|
+
credProviders = result.CredentialProviders ?? result.Data ?? [];
|
|
199
214
|
totalCount = result.TotalCount ?? 0;
|
|
200
215
|
}
|
|
201
216
|
catch (e) {
|
|
@@ -203,46 +218,23 @@ export async function runListCredentials(deps, sessionKey, page = 1, filter) {
|
|
|
203
218
|
}
|
|
204
219
|
}
|
|
205
220
|
}
|
|
206
|
-
const providerNames = new Set(
|
|
221
|
+
const providerNames = new Set(credProviders.map((p) => p.Name));
|
|
207
222
|
const storedNames = Object.keys(creds);
|
|
208
|
-
const providerRows =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
? c.valueFromEnv
|
|
216
|
-
? `from \`${c.valueFromEnv}\``
|
|
217
|
-
: c.value
|
|
218
|
-
? "✓"
|
|
219
|
-
: "empty"
|
|
220
|
-
: "—";
|
|
221
|
-
return {
|
|
222
|
-
name: p.Name,
|
|
223
|
-
type: typeLabel,
|
|
224
|
-
flow: p.Flow,
|
|
225
|
-
status,
|
|
226
|
-
binding: bind,
|
|
227
|
-
};
|
|
228
|
-
});
|
|
223
|
+
const providerRows = credProviders.map((p) => ({
|
|
224
|
+
name: p.Name,
|
|
225
|
+
type: p.Type === "api_key" ? "api_key" : `oauth2${p.Flow === "M2M" ? " (m2m)" : ""}`,
|
|
226
|
+
flow: p.Flow,
|
|
227
|
+
status: formatCredentialStatus(creds[p.Name]),
|
|
228
|
+
binding: bindings[p.Name],
|
|
229
|
+
}));
|
|
229
230
|
const storedOnly = storedNames
|
|
230
231
|
.filter((n) => !providerNames.has(n))
|
|
231
232
|
.sort()
|
|
232
|
-
.map((name) => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
: c.type === "api_key"
|
|
238
|
-
? c.valueFromEnv
|
|
239
|
-
? `from \`${c.valueFromEnv}\``
|
|
240
|
-
: c.value
|
|
241
|
-
? "✓"
|
|
242
|
-
: "empty"
|
|
243
|
-
: "—";
|
|
244
|
-
return { name, status, binding: bind };
|
|
245
|
-
});
|
|
233
|
+
.map((name) => ({
|
|
234
|
+
name,
|
|
235
|
+
status: formatCredentialStatus(creds[name]),
|
|
236
|
+
binding: bindings[name],
|
|
237
|
+
}));
|
|
246
238
|
return {
|
|
247
239
|
providers: providerRows,
|
|
248
240
|
storedOnly,
|
|
@@ -251,6 +243,48 @@ export async function runListCredentials(deps, sessionKey, page = 1, filter) {
|
|
|
251
243
|
totalCount,
|
|
252
244
|
};
|
|
253
245
|
}
|
|
246
|
+
export async function runListRoleCredentials(deps, sessionKey, filter) {
|
|
247
|
+
const { storeDir, identityClient, identityService, logger } = deps;
|
|
248
|
+
let roleProviders = [];
|
|
249
|
+
if (identityClient) {
|
|
250
|
+
const session = await getSessionWithRefresh(storeDir, sessionKey, deps.getOidcConfigForRefresh);
|
|
251
|
+
if (!session) {
|
|
252
|
+
logWarn(logger, `list-roles: no valid session for key=${sessionKey.slice(0, 24)}...`);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
try {
|
|
256
|
+
const roleResult = await identityClient.listRoleCredentialProviders({
|
|
257
|
+
PageNumber: 1,
|
|
258
|
+
PageSize: 50,
|
|
259
|
+
PoolName: deps.workloadPoolName,
|
|
260
|
+
UserPoolName: deps.userPoolName,
|
|
261
|
+
});
|
|
262
|
+
roleProviders = roleResult.RoleCredentialProviders ?? [];
|
|
263
|
+
if (filter?.name) {
|
|
264
|
+
const n = filter.name.toLowerCase();
|
|
265
|
+
roleProviders = roleProviders.filter((rp) => rp.Name.toLowerCase().includes(n));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
logWarn(logger, `list-role-credential-providers API error: ${String(e)}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const providerRows = roleProviders.map((rp) => {
|
|
274
|
+
const pool = rp.Config?.IdentityPool;
|
|
275
|
+
const identitySource = pool?.UserPool?.PoolName
|
|
276
|
+
? `userpool:${pool.UserPool.PoolName}`
|
|
277
|
+
: pool?.AgentPool?.PoolName
|
|
278
|
+
? `agentpool:${pool.AgentPool.PoolName}`
|
|
279
|
+
: undefined;
|
|
280
|
+
return { name: rp.Name, identitySource };
|
|
281
|
+
});
|
|
282
|
+
return {
|
|
283
|
+
providers: providerRows,
|
|
284
|
+
page: 1,
|
|
285
|
+
hasMore: roleProviders.length >= 50,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
254
288
|
export async function runListTips(deps) {
|
|
255
289
|
const { storeDir } = deps;
|
|
256
290
|
const tokens = getAllTIPTokens();
|
|
@@ -318,17 +352,7 @@ export async function runFetch(deps, sessionKey, params) {
|
|
|
318
352
|
if (!identityClient) {
|
|
319
353
|
return { kind: "error", message: "Identity service not configured. Cannot add credentials." };
|
|
320
354
|
}
|
|
321
|
-
const
|
|
322
|
-
const tipRefreshOptions = deps.getOidcConfigForRefresh
|
|
323
|
-
? {
|
|
324
|
-
identityService: deps.identityService,
|
|
325
|
-
getOidcConfigForRefresh: deps.getOidcConfigForRefresh,
|
|
326
|
-
configWorkloadName: deps.configWorkloadName,
|
|
327
|
-
ctxAgentId,
|
|
328
|
-
logger: deps.logger,
|
|
329
|
-
}
|
|
330
|
-
: undefined;
|
|
331
|
-
const tip = await getOrRefreshTIPToken(storeDir, sessionKey, tipRefreshOptions);
|
|
355
|
+
const tip = await resolveAndRefreshTIP(deps, sessionKey, config);
|
|
332
356
|
if (!tip) {
|
|
333
357
|
return {
|
|
334
358
|
kind: "error",
|
|
@@ -341,6 +365,7 @@ export async function runFetch(deps, sessionKey, params) {
|
|
|
341
365
|
const listResult = await identityClient.listCredentialProviders({
|
|
342
366
|
PageNumber: 1,
|
|
343
367
|
PageSize: 20,
|
|
368
|
+
PoolName: deps.workloadPoolName,
|
|
344
369
|
Filter: { Name: provider },
|
|
345
370
|
});
|
|
346
371
|
const list = listResult.CredentialProviders ?? listResult.Data ?? [];
|
|
@@ -365,6 +390,18 @@ export async function runFetch(deps, sessionKey, params) {
|
|
|
365
390
|
});
|
|
366
391
|
return { kind: "success", message: `✓ API key for \`${provider}\` added.` };
|
|
367
392
|
}
|
|
393
|
+
if (effectiveFlow === "user") {
|
|
394
|
+
const result = await identityClient.getUserCredential({
|
|
395
|
+
credentialId: provider,
|
|
396
|
+
identityToken: tip.token,
|
|
397
|
+
});
|
|
398
|
+
await setCredential(storeDir, sessionKey, provider, {
|
|
399
|
+
type: "user",
|
|
400
|
+
key: result.credentialId,
|
|
401
|
+
value: result.credentialData,
|
|
402
|
+
});
|
|
403
|
+
return { kind: "success", message: `✓ User credential for \`${provider}\` added.` };
|
|
404
|
+
}
|
|
368
405
|
const oauth2Flow = effectiveFlow === "oauth2-m2m" ? "M2M" : "USER_FEDERATION";
|
|
369
406
|
const oauthResult = await identityClient.getResourceOauth2Token({
|
|
370
407
|
providerName: provider,
|
|
@@ -498,3 +535,92 @@ export async function runUnsetBinding(deps, sessionKey, params) {
|
|
|
498
535
|
await deleteCredentialEnvBinding(storeDir, sessionKey, provider);
|
|
499
536
|
return { ok: true, message: `✓ Unset env binding for \`${provider}\`.` };
|
|
500
537
|
}
|
|
538
|
+
export async function runGetRoleCredentials(deps, sessionKey, params) {
|
|
539
|
+
const { identityClient, storeDir, logger } = deps;
|
|
540
|
+
if (!identityClient) {
|
|
541
|
+
return { kind: "error", message: "Identity service not configured." };
|
|
542
|
+
}
|
|
543
|
+
let identityToken;
|
|
544
|
+
if (params.useTip) {
|
|
545
|
+
const tip = await resolveAndRefreshTIP(deps, sessionKey, params.config);
|
|
546
|
+
if (!tip) {
|
|
547
|
+
return {
|
|
548
|
+
kind: "error",
|
|
549
|
+
message: "No TIP token available. Login first with `/identity login`.",
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
identityToken = tip.token;
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
const session = await getSessionWithRefresh(storeDir, sessionKey, deps.getOidcConfigForRefresh);
|
|
556
|
+
if (!session?.userToken) {
|
|
557
|
+
return {
|
|
558
|
+
kind: "error",
|
|
559
|
+
message: "No session found. Login first with `/identity login`.",
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
identityToken = session.userToken;
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
const result = await identityClient.getRoleCredentials({
|
|
566
|
+
IdentityToken: identityToken,
|
|
567
|
+
ProviderName: params.providerName,
|
|
568
|
+
PoolName: deps.workloadPoolName,
|
|
569
|
+
});
|
|
570
|
+
logInfo(logger, `getRoleCredentials OK for provider=${params.providerName}`);
|
|
571
|
+
return {
|
|
572
|
+
kind: "success",
|
|
573
|
+
credentials: {
|
|
574
|
+
AccessKeyId: result.Credentials.AccessKeyId,
|
|
575
|
+
SecretAccessKey: result.Credentials.SecretAccessKey,
|
|
576
|
+
SessionToken: result.Credentials.SessionToken,
|
|
577
|
+
Expiration: result.Credentials.Expiration,
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
catch (err) {
|
|
582
|
+
logWarn(logger, `getRoleCredentials failed: ${String(err)}`);
|
|
583
|
+
return {
|
|
584
|
+
kind: "error",
|
|
585
|
+
message: `Failed to get role credentials: ${err.message}`,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Return the current TIP JWT for the session (refresh/obtain via user token if needed).
|
|
591
|
+
*/
|
|
592
|
+
export async function runGetTipToken(deps, sessionKey, config) {
|
|
593
|
+
const tip = await resolveAndRefreshTIP(deps, sessionKey, config);
|
|
594
|
+
if (!tip) {
|
|
595
|
+
return {
|
|
596
|
+
kind: "error",
|
|
597
|
+
message: "No TIP token available. Log in with `/identity login` or ensure the session has a valid OIDC user token.",
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
kind: "success",
|
|
602
|
+
tipToken: tip.token,
|
|
603
|
+
sub: tip.sub,
|
|
604
|
+
issuedAt: tip.issuedAt,
|
|
605
|
+
expiresAt: tip.expiresAt,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Return the OIDC id_token (user / session identity token) for the session.
|
|
610
|
+
*/
|
|
611
|
+
export async function runGetSessionToken(deps, sessionKey) {
|
|
612
|
+
const session = await getSessionWithRefresh(deps.storeDir, sessionKey, deps.getOidcConfigForRefresh);
|
|
613
|
+
if (!session?.userToken) {
|
|
614
|
+
return {
|
|
615
|
+
kind: "error",
|
|
616
|
+
message: "No OIDC session. Log in with `/identity login` or use identity.session.put to inject a token.",
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
kind: "success",
|
|
621
|
+
sessionIdToken: session.userToken,
|
|
622
|
+
sub: session.sub,
|
|
623
|
+
loginAt: session.loginAt,
|
|
624
|
+
expiresAt: session.expiresAt,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-commands.d.ts","sourceRoot":"","sources":["../../../src/commands/identity-commands.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,
|
|
1
|
+
{"version":3,"file":"identity-commands.d.ts","sourceRoot":"","sources":["../../../src/commands/identity-commands.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAYL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACf,MAAM,gCAAgC,CAAC;AAaxC,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,CAAC;AAEhD,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AAyxBvD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB;;;;;mBA3nB3C,oBAAoB,KAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;EAooBpE;AAED,0CAA0C;AAC1C,wBAAgB,eAAe,CAAC,IAAI,EAAE,oBAAoB;;;;;mBAvoBrC,oBAAoB,KAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;EAgpBpE"}
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { runStatus, runLogin, runLogout, runListCredentials, runListTips, runConfig, runFetch, runSetBinding, runUnsetBinding, } from "../actions/identity-actions.js";
|
|
16
|
+
import { runStatus, runLogin, runLogout, runListCredentials, runListRoleCredentials, runGetRoleCredentials, runListTips, runConfig, runFetch, runSetBinding, runUnsetBinding, } from "../actions/identity-actions.js";
|
|
17
17
|
import { deriveSessionKey, deriveDeliveryTargetFromContext, } from "../utils/derive-session-key.js";
|
|
18
18
|
import { buildEffectiveSessionKey } from "../store/sender-session-store.js";
|
|
19
|
+
import { pluginState } from "../preflight/plugin-state.js";
|
|
19
20
|
import { logDebug } from "../utils/logger.js";
|
|
20
21
|
import { diagnoseRisk } from "../risk/diagnose-risk.js";
|
|
21
22
|
import { getRiskPatterns } from "../risk/classify-risk.js";
|
|
@@ -27,10 +28,12 @@ Subcommands:
|
|
|
27
28
|
• \`status\` – Full status: session/TIP details (issued, expires, chain), credentials, bindings
|
|
28
29
|
• \`login\` – Log in via OIDC (returns auth URL if not logged in)
|
|
29
30
|
• \`logout\` – Clear session and TIP
|
|
30
|
-
• \`list-credentials\` or \`list [page]\` – List providers
|
|
31
|
+
• \`list-credentials\` or \`list [page]\` – List credential providers (OAuth/API key) and stored credentials
|
|
32
|
+
• \`list-roles\` – List role credential providers (STS)
|
|
33
|
+
• \`get-role <provider> [--use-tip] [--show-secrets]\` – Get STS credentials for a role provider (credentials masked by default)
|
|
31
34
|
• \`list-tips\` – List all valid TIP tokens with delegation chain and bindings
|
|
32
35
|
• \`config\` – Show identity plugin configuration (redacted)
|
|
33
|
-
• \`fetch <provider> [--flow=oauth2-user|oauth2-m2m|apikey] [--redirectUrl=<url>] [--scopes=a,b]\` – Add credential
|
|
36
|
+
• \`fetch <provider> [--flow=oauth2-user|oauth2-m2m|apikey|user] [--redirectUrl=<url>] [--scopes=a,b]\` – Add credential
|
|
34
37
|
• \`set <provider> <envVar>\` – Bind credential to env var for tool injection
|
|
35
38
|
• \`unset <provider>\` – Remove credential env binding
|
|
36
39
|
• \`risk <command>\` – Diagnose risk for a shell command (e.g. exec)
|
|
@@ -38,7 +41,7 @@ Subcommands:
|
|
|
38
41
|
• \`approve <approval_id>\` – Approve a pending high-risk tool call
|
|
39
42
|
• \`reject <approval_id>\` – Reject a pending high-risk tool call
|
|
40
43
|
|
|
41
|
-
Fetch: flow auto-inferred from control-plane provider type; override with \`--flow=oauth2-user|oauth2-m2m|apikey\`.
|
|
44
|
+
Fetch: flow auto-inferred from control-plane provider type; override with \`--flow=oauth2-user|oauth2-m2m|apikey|user\`.
|
|
42
45
|
|
|
43
46
|
Examples:
|
|
44
47
|
\`/identity\` – show this help
|
|
@@ -48,6 +51,8 @@ Examples:
|
|
|
48
51
|
\`/identity fetch google\`
|
|
49
52
|
\`/identity fetch openai --flow=apikey\`
|
|
50
53
|
\`/identity list 2\` – load more (page 2)
|
|
54
|
+
\`/identity list-roles\` – list role credential providers (STS)
|
|
55
|
+
\`/identity get-role test_for_workload_token\` – get STS credentials
|
|
51
56
|
\`/identity risk "rm -rf /"\` – check if command would require approval
|
|
52
57
|
\`/identity risk-patterns\` – list risky patterns
|
|
53
58
|
\`/identity approve abc123\` – approve pending tool call
|
|
@@ -88,7 +93,7 @@ function parseSubcommand(args) {
|
|
|
88
93
|
rest: raw.slice(space + 1).trim(),
|
|
89
94
|
};
|
|
90
95
|
}
|
|
91
|
-
/** Parse list args: optional page and filters. E.g. "list", "list 2", "list --name=github --flow=M2M". */
|
|
96
|
+
/** Parse list args: optional page and filters. E.g. "list", "list 2", "list --name=github --flow=M2M --type=api_key". */
|
|
92
97
|
function parseListArgs(rest) {
|
|
93
98
|
const parts = rest.split(/\s+/).filter(Boolean);
|
|
94
99
|
const flags = {};
|
|
@@ -108,6 +113,7 @@ function parseListArgs(rest) {
|
|
|
108
113
|
page: Number.isFinite(page) && page >= 1 ? page : 1,
|
|
109
114
|
name: flags.name || undefined,
|
|
110
115
|
flow: flags.flow || undefined,
|
|
116
|
+
type: flags.type || undefined,
|
|
111
117
|
};
|
|
112
118
|
}
|
|
113
119
|
/** Parse fetch args: provider and flags (--flow, --redirectUrl, --scopes). flow can be omitted to auto-infer from provider. */
|
|
@@ -129,7 +135,7 @@ function parseFetchArgs(rest) {
|
|
|
129
135
|
const provider = positional[0]?.trim();
|
|
130
136
|
if (!provider) {
|
|
131
137
|
return {
|
|
132
|
-
error: "Usage: `/identity fetch <provider> [--flow=oauth2-user|oauth2-m2m|apikey] [--redirectUrl=<url>] [--scopes=a,b,c]`",
|
|
138
|
+
error: "Usage: `/identity fetch <provider> [--flow=oauth2-user|oauth2-m2m|apikey|user] [--redirectUrl=<url>] [--scopes=a,b,c]`",
|
|
133
139
|
};
|
|
134
140
|
}
|
|
135
141
|
const flowRaw = flags.flow;
|
|
@@ -139,7 +145,9 @@ function parseFetchArgs(rest) {
|
|
|
139
145
|
? "apikey"
|
|
140
146
|
: flowRaw === "oauth2-user"
|
|
141
147
|
? "oauth2-user"
|
|
142
|
-
:
|
|
148
|
+
: flowRaw === "user"
|
|
149
|
+
? "user"
|
|
150
|
+
: undefined;
|
|
143
151
|
const redirectUrl = flags.redirectUrl?.trim() || undefined;
|
|
144
152
|
const scopes = flags.scopes
|
|
145
153
|
? flags.scopes
|
|
@@ -179,6 +187,8 @@ function createIdentityHandler(deps) {
|
|
|
179
187
|
"fetch",
|
|
180
188
|
"list-credentials",
|
|
181
189
|
"list",
|
|
190
|
+
"list-roles",
|
|
191
|
+
"get-role",
|
|
182
192
|
"set",
|
|
183
193
|
"unset",
|
|
184
194
|
"approve",
|
|
@@ -204,6 +214,10 @@ function createIdentityHandler(deps) {
|
|
|
204
214
|
case "list-credentials":
|
|
205
215
|
case "list":
|
|
206
216
|
return handleListCredentials(deps, sessionKey, rest);
|
|
217
|
+
case "list-roles":
|
|
218
|
+
return handleListRoles(deps, sessionKey, rest);
|
|
219
|
+
case "get-role":
|
|
220
|
+
return handleGetRoleCredentials(ctx, deps, sessionKey, rest);
|
|
207
221
|
case "fetch":
|
|
208
222
|
return handleFetch(ctx, deps, sessionKey, rest);
|
|
209
223
|
case "set":
|
|
@@ -266,6 +280,16 @@ async function handleStatus(ctx, deps, sessionKey) {
|
|
|
266
280
|
const result = await runStatus(deps, sessionKey, ctx.config);
|
|
267
281
|
const lines = [];
|
|
268
282
|
const now = Date.now();
|
|
283
|
+
// Show degraded banner when preflight detected config problems
|
|
284
|
+
if (pluginState.degraded && pluginState.failures.length > 0) {
|
|
285
|
+
lines.push("⚠️ **[DEGRADED] Plugin is running in degraded mode. Interception disabled.**");
|
|
286
|
+
lines.push("All login prompts and tool authorization checks are skipped until the following issues are resolved:");
|
|
287
|
+
lines.push("");
|
|
288
|
+
for (const f of pluginState.failures) {
|
|
289
|
+
lines.push(`• **${f.check}**: ${f.reason}`);
|
|
290
|
+
}
|
|
291
|
+
lines.push("");
|
|
292
|
+
}
|
|
269
293
|
if (result.loggedIn) {
|
|
270
294
|
lines.push(`✓ Logged in as \`${result.sub}\``);
|
|
271
295
|
lines.push("");
|
|
@@ -334,13 +358,117 @@ async function handleLogout(deps, sessionKey) {
|
|
|
334
358
|
await runLogout(deps, sessionKey);
|
|
335
359
|
return { text: "✓ Logged out." };
|
|
336
360
|
}
|
|
361
|
+
/** Parse list-roles args: optional name filter. E.g. "list-roles", "list-roles github", "list-roles --name=foo". */
|
|
362
|
+
function parseListRolesArgs(rest) {
|
|
363
|
+
const parts = rest.split(/\s+/).filter(Boolean);
|
|
364
|
+
const flags = {};
|
|
365
|
+
const positional = [];
|
|
366
|
+
for (const p of parts) {
|
|
367
|
+
if (p.startsWith("--")) {
|
|
368
|
+
const eq = p.indexOf("=");
|
|
369
|
+
if (eq > 2)
|
|
370
|
+
flags[p.slice(2, eq)] = p.slice(eq + 1);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
positional.push(p);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
name: flags.name || positional[0] || undefined,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
async function handleListRoles(deps, sessionKey, rest) {
|
|
381
|
+
const { name } = parseListRolesArgs(rest);
|
|
382
|
+
const filter = name ? { name } : undefined;
|
|
383
|
+
const result = await runListRoleCredentials(deps, sessionKey, filter);
|
|
384
|
+
if (result.providers.length === 0) {
|
|
385
|
+
return {
|
|
386
|
+
text: "No role credential providers. Configure role providers in the control plane, or use `/identity list` for OAuth/API key providers.",
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const lines = ["**Role credential providers (STS):**"];
|
|
390
|
+
for (const p of result.providers) {
|
|
391
|
+
const src = p.identitySource ? ` [${p.identitySource}]` : "";
|
|
392
|
+
lines.push(`• **${p.name}**${src}`);
|
|
393
|
+
}
|
|
394
|
+
lines.push("");
|
|
395
|
+
lines.push("Use `identity_get_role_credentials` tool to obtain STS credentials for a provider.");
|
|
396
|
+
return { text: lines.join("\n") };
|
|
397
|
+
}
|
|
398
|
+
/** Mask sensitive value: show prefix + suffix only. Returns full value when showSecrets is true. */
|
|
399
|
+
function maskSecret(value, showSecrets) {
|
|
400
|
+
if (showSecrets)
|
|
401
|
+
return value;
|
|
402
|
+
if (!value || value.length <= 8)
|
|
403
|
+
return "***";
|
|
404
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
405
|
+
}
|
|
406
|
+
/** Parse get-role args: provider name (required), optional --use-tip, --show-secrets. */
|
|
407
|
+
function parseGetRoleArgs(rest) {
|
|
408
|
+
const parts = rest.split(/\s+/).filter(Boolean);
|
|
409
|
+
const flags = {};
|
|
410
|
+
const positional = [];
|
|
411
|
+
for (const p of parts) {
|
|
412
|
+
if (p.startsWith("--")) {
|
|
413
|
+
const eq = p.indexOf("=");
|
|
414
|
+
if (eq > 2)
|
|
415
|
+
flags[p.slice(2, eq)] = p.slice(eq + 1);
|
|
416
|
+
else
|
|
417
|
+
flags[p.slice(2)] = "true";
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
positional.push(p);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const providerName = positional[0]?.trim();
|
|
424
|
+
if (!providerName) {
|
|
425
|
+
return {
|
|
426
|
+
error: "Usage: `/identity get-role <provider> [--use-tip] [--show-secrets]`\nExample: `/identity get-role test_for_workload_token`",
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const useTip = flags["use-tip"] === "true" || flags.useTip === "true";
|
|
430
|
+
const showSecrets = flags["show-secrets"] === "true" || flags.showSecrets === "true";
|
|
431
|
+
return { providerName, useTip, showSecrets };
|
|
432
|
+
}
|
|
433
|
+
async function handleGetRoleCredentials(ctx, deps, sessionKey, rest) {
|
|
434
|
+
const parsed = parseGetRoleArgs(rest);
|
|
435
|
+
if ("error" in parsed)
|
|
436
|
+
return { text: parsed.error };
|
|
437
|
+
const result = await runGetRoleCredentials(deps, sessionKey, {
|
|
438
|
+
providerName: parsed.providerName,
|
|
439
|
+
useTip: parsed.useTip,
|
|
440
|
+
config: ctx.config,
|
|
441
|
+
});
|
|
442
|
+
if (result.kind === "error") {
|
|
443
|
+
return { text: `⚠️ ${result.message}` };
|
|
444
|
+
}
|
|
445
|
+
const c = result.credentials;
|
|
446
|
+
const showSecrets = parsed.showSecrets;
|
|
447
|
+
const lines = [
|
|
448
|
+
`✓ STS credentials for \`${parsed.providerName}\``,
|
|
449
|
+
"",
|
|
450
|
+
"**Credentials:**",
|
|
451
|
+
`• AccessKeyId: \`${maskSecret(c.AccessKeyId, showSecrets)}\``,
|
|
452
|
+
`• SecretAccessKey: \`${maskSecret(c.SecretAccessKey, showSecrets)}\``,
|
|
453
|
+
`• SessionToken: \`${maskSecret(c.SessionToken, showSecrets)}\``,
|
|
454
|
+
];
|
|
455
|
+
if (c.Expiration)
|
|
456
|
+
lines.push(`• Expiration: ${c.Expiration}`);
|
|
457
|
+
if (showSecrets) {
|
|
458
|
+
lines.push("", "⚠️ **Sensitive values are visible.** Avoid sharing this output.");
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
lines.push("", "Use `--show-secrets` to reveal full values (use with caution).");
|
|
462
|
+
}
|
|
463
|
+
return { text: lines.join("\n") };
|
|
464
|
+
}
|
|
337
465
|
async function handleListCredentials(deps, sessionKey, rest) {
|
|
338
|
-
const { page, name, flow } = parseListArgs(rest);
|
|
339
|
-
const filter = name || flow ? { name, flow } : undefined;
|
|
466
|
+
const { page, name, flow, type } = parseListArgs(rest);
|
|
467
|
+
const filter = name || flow || type ? { name, flow, type } : undefined;
|
|
340
468
|
const result = await runListCredentials(deps, sessionKey, page, filter);
|
|
341
469
|
const lines = [];
|
|
342
470
|
if (result.providers.length > 0) {
|
|
343
|
-
lines.push(`**
|
|
471
|
+
lines.push(`**Providers (page ${result.page}):**`);
|
|
344
472
|
for (const p of result.providers) {
|
|
345
473
|
const bind = p.binding ? ` → \`${p.binding}\`` : "";
|
|
346
474
|
lines.push(`• **${p.name}** (${p.type}): ${p.status}${bind}`);
|
|
@@ -361,7 +489,7 @@ async function handleListCredentials(deps, sessionKey, rest) {
|
|
|
361
489
|
};
|
|
362
490
|
}
|
|
363
491
|
const footer = [];
|
|
364
|
-
footer.push("Use `/identity set <provider> <envVar>` to bind.");
|
|
492
|
+
footer.push("Use `/identity set <provider> <envVar>` to bind, or `/identity fetch <provider>` to add credentials.");
|
|
365
493
|
if (result.hasMore) {
|
|
366
494
|
footer.push(`\`/identity list ${result.page + 1}\` to load more.`);
|
|
367
495
|
}
|