@m1a0rz/agent-identity 0.1.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 +223 -0
- package/README.md +223 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +306 -0
- package/dist/src/actions/identity-actions.d.ts +142 -0
- package/dist/src/actions/identity-actions.d.ts.map +1 -0
- package/dist/src/actions/identity-actions.js +429 -0
- package/dist/src/commands/identity-commands.d.ts +33 -0
- package/dist/src/commands/identity-commands.d.ts.map +1 -0
- package/dist/src/commands/identity-commands.js +572 -0
- package/dist/src/hooks/after-tool-call.d.ts +22 -0
- package/dist/src/hooks/after-tool-call.d.ts.map +1 -0
- package/dist/src/hooks/after-tool-call.js +35 -0
- package/dist/src/hooks/before-agent-start.d.ts +30 -0
- package/dist/src/hooks/before-agent-start.d.ts.map +1 -0
- package/dist/src/hooks/before-agent-start.js +93 -0
- package/dist/src/hooks/before-tool-call.d.ts +38 -0
- package/dist/src/hooks/before-tool-call.d.ts.map +1 -0
- package/dist/src/hooks/before-tool-call.js +138 -0
- package/dist/src/risk/classify-risk.d.ts +24 -0
- package/dist/src/risk/classify-risk.d.ts.map +1 -0
- package/dist/src/risk/classify-risk.js +61 -0
- package/dist/src/risk/diagnose-risk.d.ts +21 -0
- package/dist/src/risk/diagnose-risk.d.ts.map +1 -0
- package/dist/src/risk/diagnose-risk.js +37 -0
- package/dist/src/risk/llm-risk-check.d.ts +27 -0
- package/dist/src/risk/llm-risk-check.d.ts.map +1 -0
- package/dist/src/risk/llm-risk-check.js +274 -0
- package/dist/src/risk/low-risk-tools.d.ts +5 -0
- package/dist/src/risk/low-risk-tools.d.ts.map +1 -0
- package/dist/src/risk/low-risk-tools.js +29 -0
- package/dist/src/routes/oidc-login.d.ts +51 -0
- package/dist/src/routes/oidc-login.d.ts.map +1 -0
- package/dist/src/routes/oidc-login.js +153 -0
- package/dist/src/services/identity-client.d.ts +366 -0
- package/dist/src/services/identity-client.d.ts.map +1 -0
- package/dist/src/services/identity-client.js +578 -0
- package/dist/src/services/identity-credentials.d.ts +28 -0
- package/dist/src/services/identity-credentials.d.ts.map +1 -0
- package/dist/src/services/identity-credentials.js +170 -0
- package/dist/src/services/identity-service.d.ts +33 -0
- package/dist/src/services/identity-service.d.ts.map +1 -0
- package/dist/src/services/identity-service.js +53 -0
- package/dist/src/services/oidc-client.d.ts +57 -0
- package/dist/src/services/oidc-client.d.ts.map +1 -0
- package/dist/src/services/oidc-client.js +127 -0
- package/dist/src/services/send-notification-feishu.d.ts +27 -0
- package/dist/src/services/send-notification-feishu.d.ts.map +1 -0
- package/dist/src/services/send-notification-feishu.js +148 -0
- package/dist/src/services/session-refresh.d.ts +16 -0
- package/dist/src/services/session-refresh.d.ts.map +1 -0
- package/dist/src/services/session-refresh.js +38 -0
- package/dist/src/store/credential-env-bindings.d.ts +16 -0
- package/dist/src/store/credential-env-bindings.d.ts.map +1 -0
- package/dist/src/store/credential-env-bindings.js +61 -0
- package/dist/src/store/credential-store.d.ts +31 -0
- package/dist/src/store/credential-store.d.ts.map +1 -0
- package/dist/src/store/credential-store.js +57 -0
- package/dist/src/store/oidc-state-store.d.ts +15 -0
- package/dist/src/store/oidc-state-store.d.ts.map +1 -0
- package/dist/src/store/oidc-state-store.js +32 -0
- package/dist/src/store/session-store.d.ts +21 -0
- package/dist/src/store/session-store.d.ts.map +1 -0
- package/dist/src/store/session-store.js +69 -0
- package/dist/src/store/tip-store.d.ts +21 -0
- package/dist/src/store/tip-store.d.ts.map +1 -0
- package/dist/src/store/tip-store.js +60 -0
- package/dist/src/store/tool-approval-store.d.ts +44 -0
- package/dist/src/store/tool-approval-store.d.ts.map +1 -0
- package/dist/src/store/tool-approval-store.js +147 -0
- package/dist/src/tools/identity-approve-tool.d.ts +24 -0
- package/dist/src/tools/identity-approve-tool.d.ts.map +1 -0
- package/dist/src/tools/identity-approve-tool.js +36 -0
- package/dist/src/tools/identity-config.d.ts +13 -0
- package/dist/src/tools/identity-config.d.ts.map +1 -0
- package/dist/src/tools/identity-config.js +18 -0
- package/dist/src/tools/identity-fetch.d.ts +21 -0
- package/dist/src/tools/identity-fetch.d.ts.map +1 -0
- package/dist/src/tools/identity-fetch.js +63 -0
- package/dist/src/tools/identity-list-credentials.d.ts +15 -0
- package/dist/src/tools/identity-list-credentials.d.ts.map +1 -0
- package/dist/src/tools/identity-list-credentials.js +30 -0
- package/dist/src/tools/identity-list-risk-patterns.d.ts +13 -0
- package/dist/src/tools/identity-list-risk-patterns.d.ts.map +1 -0
- package/dist/src/tools/identity-list-risk-patterns.js +23 -0
- package/dist/src/tools/identity-list-tips.d.ts +13 -0
- package/dist/src/tools/identity-list-tips.d.ts.map +1 -0
- package/dist/src/tools/identity-list-tips.js +21 -0
- package/dist/src/tools/identity-login.d.ts +14 -0
- package/dist/src/tools/identity-login.d.ts.map +1 -0
- package/dist/src/tools/identity-login.js +40 -0
- package/dist/src/tools/identity-logout.d.ts +13 -0
- package/dist/src/tools/identity-logout.d.ts.map +1 -0
- package/dist/src/tools/identity-logout.js +24 -0
- package/dist/src/tools/identity-risk-check.d.ts +29 -0
- package/dist/src/tools/identity-risk-check.d.ts.map +1 -0
- package/dist/src/tools/identity-risk-check.js +54 -0
- package/dist/src/tools/identity-set-binding.d.ts +16 -0
- package/dist/src/tools/identity-set-binding.d.ts.map +1 -0
- package/dist/src/tools/identity-set-binding.js +31 -0
- package/dist/src/tools/identity-status.d.ts +13 -0
- package/dist/src/tools/identity-status.d.ts.map +1 -0
- package/dist/src/tools/identity-status.js +41 -0
- package/dist/src/tools/identity-unset-binding.d.ts +15 -0
- package/dist/src/tools/identity-unset-binding.d.ts.map +1 -0
- package/dist/src/tools/identity-unset-binding.js +25 -0
- package/dist/src/tools/identity-whoami.d.ts +13 -0
- package/dist/src/tools/identity-whoami.d.ts.map +1 -0
- package/dist/src/tools/identity-whoami.js +38 -0
- package/dist/src/types.d.ts +93 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/utils/approval-channel.d.ts +11 -0
- package/dist/src/utils/approval-channel.d.ts.map +1 -0
- package/dist/src/utils/approval-channel.js +13 -0
- package/dist/src/utils/auth.d.ts +24 -0
- package/dist/src/utils/auth.d.ts.map +1 -0
- package/dist/src/utils/auth.js +44 -0
- package/dist/src/utils/derive-session-key.d.ts +78 -0
- package/dist/src/utils/derive-session-key.d.ts.map +1 -0
- package/dist/src/utils/derive-session-key.js +198 -0
- package/openclaw.plugin.json +162 -0
- package/package.json +33 -0
- package/skills/SKILL.md +230 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared identity actions: pure logic returning structured data.
|
|
3
|
+
* Used by both commands (format to text) and tools (return jsonResult).
|
|
4
|
+
*/
|
|
5
|
+
import { fetchOIDCDiscovery, buildAuthorizationUrl, generateState, } from "../services/oidc-client.js";
|
|
6
|
+
import { loadCredentialEnvBindings, loadAllCredentialEnvBindings, setCredentialEnvBinding, deleteCredentialEnvBinding, } from "../store/credential-env-bindings.js";
|
|
7
|
+
import { loadCredentials, setCredential, getCredential, deleteCredentialsForSession, } from "../store/credential-store.js";
|
|
8
|
+
import { getSession, deleteSession } from "../store/session-store.js";
|
|
9
|
+
import { createState } from "../store/oidc-state-store.js";
|
|
10
|
+
import { getTIPToken, setTIPToken, loadTIPTokens, saveTIPTokens } from "../store/tip-store.js";
|
|
11
|
+
import { extractDelegationChainFromJwt } from "../utils/auth.js";
|
|
12
|
+
import { resolveAgentId, } from "../utils/derive-session-key.js";
|
|
13
|
+
function inferFlowFromProvider(info) {
|
|
14
|
+
if (info.Type === "api_key")
|
|
15
|
+
return "apikey";
|
|
16
|
+
if (info.Type === "oauth2" && info.Flow === "M2M")
|
|
17
|
+
return "oauth2-m2m";
|
|
18
|
+
return "oauth2-user";
|
|
19
|
+
}
|
|
20
|
+
const OAUTH_POLL_INTERVAL_MS = 2500;
|
|
21
|
+
const OAUTH_POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
22
|
+
function sleep(ms) {
|
|
23
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
24
|
+
}
|
|
25
|
+
/** Poll GetResourceOauth2Token until accessToken or timeout; store credential and notify. */
|
|
26
|
+
async function pollOAuthAndNotify(params) {
|
|
27
|
+
const { identityClient, provider, identityToken, flow, redirectUrl, scopes, sessionKey, storeDir, deliveryTarget, sendCredentialMessage, logger, } = params;
|
|
28
|
+
const start = Date.now();
|
|
29
|
+
while (Date.now() - start < OAUTH_POLL_TIMEOUT_MS) {
|
|
30
|
+
await sleep(OAUTH_POLL_INTERVAL_MS);
|
|
31
|
+
try {
|
|
32
|
+
const result = await identityClient.getResourceOauth2Token({
|
|
33
|
+
providerName: provider,
|
|
34
|
+
identityToken,
|
|
35
|
+
flow,
|
|
36
|
+
redirectUrl,
|
|
37
|
+
scopes: scopes?.length ? scopes : undefined,
|
|
38
|
+
});
|
|
39
|
+
if (result.accessToken) {
|
|
40
|
+
await setCredential(storeDir, sessionKey, provider, {
|
|
41
|
+
type: "oauth2",
|
|
42
|
+
status: "authenticated",
|
|
43
|
+
accessToken: result.accessToken,
|
|
44
|
+
expiresAt: Date.now() + 3600 * 1000,
|
|
45
|
+
});
|
|
46
|
+
const target = deliveryTarget ?? sessionKey;
|
|
47
|
+
await sendCredentialMessage?.(target, `✓ Credential for \`${provider}\` added.`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger?.debug?.(`[identity fetch] poll attempt failed: ${String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const target = deliveryTarget ?? sessionKey;
|
|
56
|
+
await sendCredentialMessage?.(target, `⚠️ Authorization timed out for \`${provider}\`. Run \`/identity fetch ${provider}\` again.`);
|
|
57
|
+
}
|
|
58
|
+
export async function runStatus(deps, sessionKey, config) {
|
|
59
|
+
const { storeDir, identityService, logger } = deps;
|
|
60
|
+
const session = await getSession(storeDir, sessionKey);
|
|
61
|
+
let tip = await getTIPToken(storeDir, sessionKey);
|
|
62
|
+
if (session && identityService.parseUserToken(session.userToken).valid && !tip) {
|
|
63
|
+
try {
|
|
64
|
+
const agentId = resolveAgentId({ sessionKey, config: config });
|
|
65
|
+
const fresh = await identityService.getWorkloadAccessToken({
|
|
66
|
+
agentId,
|
|
67
|
+
userToken: session.userToken,
|
|
68
|
+
sub: session.sub,
|
|
69
|
+
});
|
|
70
|
+
await setTIPToken(storeDir, sessionKey, fresh);
|
|
71
|
+
tip = fresh;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
logger?.debug?.(`[identity status] TIP refresh failed: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (session && tip && tip.expiresAt - Date.now() < 5 * 60 * 1000) {
|
|
78
|
+
try {
|
|
79
|
+
const agentId = resolveAgentId({ sessionKey, config: config });
|
|
80
|
+
const fresh = await identityService.getWorkloadAccessToken({
|
|
81
|
+
agentId,
|
|
82
|
+
userToken: session.userToken,
|
|
83
|
+
sub: session.sub,
|
|
84
|
+
});
|
|
85
|
+
await setTIPToken(storeDir, sessionKey, fresh);
|
|
86
|
+
tip = fresh;
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
logger?.debug?.(`[identity status] TIP refresh failed: ${err.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const credentials = await loadCredentials(storeDir, sessionKey);
|
|
93
|
+
const bindings = await loadCredentialEnvBindings(storeDir, sessionKey);
|
|
94
|
+
return {
|
|
95
|
+
loggedIn: !!(session && identityService.parseUserToken(session.userToken).valid),
|
|
96
|
+
sub: session?.sub ?? null,
|
|
97
|
+
hasTip: Boolean(tip),
|
|
98
|
+
sessionLoginAt: session?.loginAt,
|
|
99
|
+
sessionExpiresAt: session?.expiresAt ?? null,
|
|
100
|
+
tipIssuedAt: tip?.issuedAt,
|
|
101
|
+
tipExpiresAt: tip?.expiresAt,
|
|
102
|
+
tipChain: tip?.chain,
|
|
103
|
+
credentials,
|
|
104
|
+
bindings,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export async function runLogin(deps, sessionKey, options) {
|
|
108
|
+
const { storeDir, identityService, getOidcConfig, logger } = deps;
|
|
109
|
+
const config = options?.config;
|
|
110
|
+
const deliveryTarget = options?.deliveryTarget ?? null;
|
|
111
|
+
const session = await getSession(storeDir, sessionKey);
|
|
112
|
+
const hasValidCred = session && identityService.parseUserToken(session.userToken).valid;
|
|
113
|
+
if (hasValidCred && session) {
|
|
114
|
+
try {
|
|
115
|
+
const agentId = resolveAgentId({ sessionKey, config: config });
|
|
116
|
+
const tip = await identityService.getWorkloadAccessToken({
|
|
117
|
+
agentId,
|
|
118
|
+
userToken: session.userToken,
|
|
119
|
+
sub: session.sub,
|
|
120
|
+
});
|
|
121
|
+
await setTIPToken(storeDir, sessionKey, tip);
|
|
122
|
+
return { kind: "already_logged_in", sub: session.sub };
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
return {
|
|
126
|
+
kind: "error",
|
|
127
|
+
message: `Session valid but TIP refresh failed: ${err.message}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const oidcConfig = await getOidcConfig();
|
|
133
|
+
const discovery = await fetchOIDCDiscovery(oidcConfig.discoveryUrl);
|
|
134
|
+
const state = await generateState();
|
|
135
|
+
await createState(storeDir, sessionKey, "", state, deliveryTarget);
|
|
136
|
+
const authUrl = buildAuthorizationUrl({
|
|
137
|
+
authorizationEndpoint: discovery.authorization_endpoint,
|
|
138
|
+
clientId: oidcConfig.clientId,
|
|
139
|
+
redirectUri: oidcConfig.callbackUrl,
|
|
140
|
+
scope: oidcConfig.scope ?? "openid profile email",
|
|
141
|
+
state,
|
|
142
|
+
});
|
|
143
|
+
logger?.info?.(`[identity login] returning IdP URL for sessionKey=${sessionKey.slice(0, 24)}...`);
|
|
144
|
+
return { kind: "auth_url", authUrl };
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
logger?.warn?.(`[identity login] error: ${String(err)}`);
|
|
148
|
+
return {
|
|
149
|
+
kind: "error",
|
|
150
|
+
message: `${err.message}. Ensure userpool is configured (discoveryUrl+clientId+callbackUrl or userPoolName+clientName+callbackUrl).`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export async function runLogout(deps, sessionKey) {
|
|
155
|
+
const { storeDir, logger } = deps;
|
|
156
|
+
logger?.debug?.(`[identity logout] sessionKey=${sessionKey.slice(0, 24)}...`);
|
|
157
|
+
await deleteSession(storeDir, sessionKey);
|
|
158
|
+
const tokens = await loadTIPTokens(storeDir);
|
|
159
|
+
delete tokens[sessionKey];
|
|
160
|
+
await saveTIPTokens(storeDir, tokens);
|
|
161
|
+
deleteCredentialsForSession(storeDir, sessionKey);
|
|
162
|
+
return { ok: true };
|
|
163
|
+
}
|
|
164
|
+
const LIST_PAGE_SIZE = 10;
|
|
165
|
+
export async function runListCredentials(deps, sessionKey, page = 1) {
|
|
166
|
+
const { storeDir, identityClient, logger } = deps;
|
|
167
|
+
const creds = await loadCredentials(storeDir, sessionKey);
|
|
168
|
+
const bindings = await loadCredentialEnvBindings(storeDir, sessionKey);
|
|
169
|
+
let providers = [];
|
|
170
|
+
let totalCount = 0;
|
|
171
|
+
if (identityClient) {
|
|
172
|
+
try {
|
|
173
|
+
const result = await identityClient.listCredentialProviders({
|
|
174
|
+
PageNumber: page,
|
|
175
|
+
PageSize: LIST_PAGE_SIZE,
|
|
176
|
+
});
|
|
177
|
+
providers = result.CredentialProviders ?? result.Data ?? [];
|
|
178
|
+
totalCount = result.TotalCount ?? 0;
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
logger?.warn?.(`[identity list] API error: ${String(e)}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const providerNames = new Set(providers.map((p) => p.Name));
|
|
185
|
+
const storedNames = Object.keys(creds);
|
|
186
|
+
const providerRows = providers.map((p) => {
|
|
187
|
+
const c = creds[p.Name];
|
|
188
|
+
const bind = bindings[p.Name];
|
|
189
|
+
const typeLabel = p.Type === "api_key" ? "api_key" : `oauth2${p.Flow === "M2M" ? " (m2m)" : ""}`;
|
|
190
|
+
const status = c?.type === "oauth2"
|
|
191
|
+
? c.status
|
|
192
|
+
: c?.type === "api_key"
|
|
193
|
+
? c.valueFromEnv
|
|
194
|
+
? `from \`${c.valueFromEnv}\``
|
|
195
|
+
: c.value
|
|
196
|
+
? "✓"
|
|
197
|
+
: "empty"
|
|
198
|
+
: "—";
|
|
199
|
+
return {
|
|
200
|
+
name: p.Name,
|
|
201
|
+
type: typeLabel,
|
|
202
|
+
flow: p.Flow,
|
|
203
|
+
status,
|
|
204
|
+
binding: bind,
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
const storedOnly = storedNames
|
|
208
|
+
.filter((n) => !providerNames.has(n))
|
|
209
|
+
.sort()
|
|
210
|
+
.map((name) => {
|
|
211
|
+
const c = creds[name];
|
|
212
|
+
const bind = bindings[name];
|
|
213
|
+
const status = c.type === "oauth2"
|
|
214
|
+
? c.status
|
|
215
|
+
: c.type === "api_key"
|
|
216
|
+
? c.valueFromEnv
|
|
217
|
+
? `from \`${c.valueFromEnv}\``
|
|
218
|
+
: c.value
|
|
219
|
+
? "✓"
|
|
220
|
+
: "empty"
|
|
221
|
+
: "—";
|
|
222
|
+
return { name, status, binding: bind };
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
providers: providerRows,
|
|
226
|
+
storedOnly,
|
|
227
|
+
page,
|
|
228
|
+
hasMore: totalCount > page * LIST_PAGE_SIZE,
|
|
229
|
+
totalCount,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
export async function runListTips(deps) {
|
|
233
|
+
const { storeDir } = deps;
|
|
234
|
+
const tokens = await loadTIPTokens(storeDir);
|
|
235
|
+
const bindingsBySession = await loadAllCredentialEnvBindings(storeDir);
|
|
236
|
+
const now = Date.now();
|
|
237
|
+
const tips = [];
|
|
238
|
+
for (const [key, entry] of Object.entries(tokens)) {
|
|
239
|
+
if (entry.expiresAt < now)
|
|
240
|
+
continue;
|
|
241
|
+
const chain = extractDelegationChainFromJwt(entry.token);
|
|
242
|
+
tips.push({
|
|
243
|
+
sessionKey: key,
|
|
244
|
+
sub: entry.sub,
|
|
245
|
+
chain: chain ? [chain.principalId, ...chain.actors] : [],
|
|
246
|
+
expiresAt: entry.expiresAt,
|
|
247
|
+
ttlSec: Math.floor((entry.expiresAt - now) / 1000),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return { tips, bindingsBySession };
|
|
251
|
+
}
|
|
252
|
+
export async function runConfig(deps) {
|
|
253
|
+
const cfg = deps.pluginConfig;
|
|
254
|
+
if (!cfg)
|
|
255
|
+
return {};
|
|
256
|
+
const out = {};
|
|
257
|
+
const id = cfg.identity ?? cfg.cis;
|
|
258
|
+
if (id) {
|
|
259
|
+
out.identity = {
|
|
260
|
+
endpoint: id.endpoint,
|
|
261
|
+
accessKeyId: id.accessKeyId ? "***" : undefined,
|
|
262
|
+
secretAccessKey: id.secretAccessKey ? "***" : undefined,
|
|
263
|
+
credentialsFile: id.credentialsFile,
|
|
264
|
+
roleTrn: id.roleTrn,
|
|
265
|
+
workloadPoolName: id.workloadPoolName,
|
|
266
|
+
workloadName: id.workloadName,
|
|
267
|
+
audience: id.audience,
|
|
268
|
+
durationSeconds: id.durationSeconds,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (cfg.userpool) {
|
|
272
|
+
out.userpool = {
|
|
273
|
+
discoveryUrl: cfg.userpool.discoveryUrl,
|
|
274
|
+
clientId: cfg.userpool.clientId,
|
|
275
|
+
clientSecret: cfg.userpool.clientSecret ? "***" : undefined,
|
|
276
|
+
callbackUrl: cfg.userpool.callbackUrl,
|
|
277
|
+
scope: cfg.userpool.scope,
|
|
278
|
+
userPoolName: cfg.userpool.userPoolName,
|
|
279
|
+
clientName: cfg.userpool.clientName,
|
|
280
|
+
autoCreate: cfg.userpool.autoCreate,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (cfg.authz) {
|
|
284
|
+
out.authz = {
|
|
285
|
+
enable: cfg.authz.enable,
|
|
286
|
+
namespaceName: cfg.authz.namespaceName,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
export async function runFetch(deps, sessionKey, params) {
|
|
292
|
+
const { identityClient, storeDir, sendCredentialMessage, logger } = deps;
|
|
293
|
+
const { provider, flow, flowExplicit, redirectUrl, scopes, deliveryTarget, config } = params;
|
|
294
|
+
if (!identityClient) {
|
|
295
|
+
return { kind: "error", message: "Identity service not configured. Cannot add credentials." };
|
|
296
|
+
}
|
|
297
|
+
let effectiveFlow = flow;
|
|
298
|
+
if (!flowExplicit) {
|
|
299
|
+
try {
|
|
300
|
+
const listResult = await identityClient.listCredentialProviders({
|
|
301
|
+
PageNumber: 1,
|
|
302
|
+
PageSize: 20,
|
|
303
|
+
Filter: { Name: provider },
|
|
304
|
+
});
|
|
305
|
+
const list = listResult.CredentialProviders ?? listResult.Data ?? [];
|
|
306
|
+
const info = list.find((p) => p.Name === provider);
|
|
307
|
+
if (info)
|
|
308
|
+
effectiveFlow = inferFlowFromProvider(info);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// keep default
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const tip = await getTIPToken(storeDir, sessionKey);
|
|
315
|
+
if (!tip) {
|
|
316
|
+
return {
|
|
317
|
+
kind: "error",
|
|
318
|
+
message: "Login first with `/identity login` to establish identity before adding credentials.",
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
if (effectiveFlow === "apikey") {
|
|
323
|
+
const result = await identityClient.getResourceApiKey({
|
|
324
|
+
providerName: provider,
|
|
325
|
+
identityToken: tip.token,
|
|
326
|
+
});
|
|
327
|
+
await setCredential(storeDir, sessionKey, provider, {
|
|
328
|
+
type: "api_key",
|
|
329
|
+
key: provider,
|
|
330
|
+
value: result.apiKey,
|
|
331
|
+
});
|
|
332
|
+
return { kind: "success", message: `✓ API key for \`${provider}\` added.` };
|
|
333
|
+
}
|
|
334
|
+
const oauth2Flow = effectiveFlow === "oauth2-m2m" ? "M2M" : "USER_FEDERATION";
|
|
335
|
+
const oauthResult = await identityClient.getResourceOauth2Token({
|
|
336
|
+
providerName: provider,
|
|
337
|
+
identityToken: tip.token,
|
|
338
|
+
flow: oauth2Flow,
|
|
339
|
+
redirectUrl: redirectUrl,
|
|
340
|
+
scopes: scopes?.length ? scopes : undefined,
|
|
341
|
+
});
|
|
342
|
+
if (oauthResult.accessToken) {
|
|
343
|
+
await setCredential(storeDir, sessionKey, provider, {
|
|
344
|
+
type: "oauth2",
|
|
345
|
+
status: "authenticated",
|
|
346
|
+
accessToken: oauthResult.accessToken,
|
|
347
|
+
expiresAt: Date.now() + 3600 * 1000,
|
|
348
|
+
});
|
|
349
|
+
return { kind: "success", message: `✓ Credential for \`${provider}\` added (direct token).` };
|
|
350
|
+
}
|
|
351
|
+
if (oauthResult.authorizationUrl) {
|
|
352
|
+
logger?.info?.(`[identity fetch] returning auth URL for provider=${provider}, starting poll`);
|
|
353
|
+
const target = deliveryTarget ?? sessionKey;
|
|
354
|
+
pollOAuthAndNotify({
|
|
355
|
+
identityClient,
|
|
356
|
+
provider,
|
|
357
|
+
identityToken: tip.token,
|
|
358
|
+
flow: oauth2Flow,
|
|
359
|
+
redirectUrl,
|
|
360
|
+
scopes,
|
|
361
|
+
sessionKey,
|
|
362
|
+
storeDir,
|
|
363
|
+
deliveryTarget: deliveryTarget ?? null,
|
|
364
|
+
sendCredentialMessage,
|
|
365
|
+
logger,
|
|
366
|
+
}).catch((err) => {
|
|
367
|
+
logger?.warn?.(`[identity fetch] poll error: ${String(err)}`);
|
|
368
|
+
void sendCredentialMessage?.(target, `⚠️ Credential fetch failed: ${err.message}`).catch(() => { });
|
|
369
|
+
});
|
|
370
|
+
return {
|
|
371
|
+
kind: "auth_url",
|
|
372
|
+
authUrl: oauthResult.authorizationUrl,
|
|
373
|
+
message: `Open this URL to authorize \`${provider}\`. After authorization, a success message will be sent to this chat.`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
kind: "error",
|
|
378
|
+
message: `Provider \`${provider}\` returned neither token nor authorization URL.`,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
logger?.warn?.(`[identity fetch] error: ${String(err)}`);
|
|
383
|
+
return {
|
|
384
|
+
kind: "error",
|
|
385
|
+
message: `Credential setup failed: ${err.message}`,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
export async function runSetBinding(deps, sessionKey, params) {
|
|
390
|
+
const { storeDir } = deps;
|
|
391
|
+
const { provider, envVar } = params;
|
|
392
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(envVar)) {
|
|
393
|
+
return {
|
|
394
|
+
ok: false,
|
|
395
|
+
error: `Invalid env var name: \`${envVar}\`. Use letters, numbers, underscores.`,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const cred = await getCredential(storeDir, sessionKey, provider);
|
|
399
|
+
if (cred) {
|
|
400
|
+
await setCredentialEnvBinding(storeDir, sessionKey, provider, envVar);
|
|
401
|
+
return { ok: true, message: `✓ Bound \`${provider}\` → \`${envVar}\`.` };
|
|
402
|
+
}
|
|
403
|
+
if (process.env[envVar] === undefined || process.env[envVar] === "") {
|
|
404
|
+
return {
|
|
405
|
+
ok: false,
|
|
406
|
+
error: `No credential for \`${provider}\` and env var \`${envVar}\` is not set. Add credential with \`/identity fetch ${provider}\` or set \`${envVar}\` in the gateway environment.`,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
await setCredential(storeDir, sessionKey, provider, {
|
|
410
|
+
type: "api_key",
|
|
411
|
+
key: envVar,
|
|
412
|
+
valueFromEnv: envVar,
|
|
413
|
+
});
|
|
414
|
+
await setCredentialEnvBinding(storeDir, sessionKey, provider, envVar);
|
|
415
|
+
return {
|
|
416
|
+
ok: true,
|
|
417
|
+
message: `✓ Bound \`${provider}\` to \`${envVar}\` (resolved from gateway env).`,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
export async function runUnsetBinding(deps, sessionKey, params) {
|
|
421
|
+
const { storeDir } = deps;
|
|
422
|
+
const { provider } = params;
|
|
423
|
+
const bindings = await loadCredentialEnvBindings(storeDir, sessionKey);
|
|
424
|
+
if (!bindings[provider]) {
|
|
425
|
+
return { ok: false, error: `No env binding for \`${provider}\`. Nothing to unset.` };
|
|
426
|
+
}
|
|
427
|
+
await deleteCredentialEnvBinding(storeDir, sessionKey, provider);
|
|
428
|
+
return { ok: true, message: `✓ Unset env binding for \`${provider}\`.` };
|
|
429
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified /identity command: login, status, logout, list-credentials, fetch, set.
|
|
3
|
+
* UserPool OIDC + credential hosting. Uses shared identity-actions for logic.
|
|
4
|
+
*/
|
|
5
|
+
import type { PluginCommandContext } from "../types.js";
|
|
6
|
+
import { type IdentityActionsDeps, type OIDCConfigForCommand, type FetchFlow } from "../actions/identity-actions.js";
|
|
7
|
+
export type { OIDCConfigForCommand, FetchFlow };
|
|
8
|
+
export type IdentityCommandsLogger = {
|
|
9
|
+
info?: (msg: string) => void;
|
|
10
|
+
warn?: (msg: string) => void;
|
|
11
|
+
debug?: (msg: string) => void;
|
|
12
|
+
};
|
|
13
|
+
export type IdentityCommandsDeps = IdentityActionsDeps;
|
|
14
|
+
export declare function createIdentityCommand(deps: IdentityCommandsDeps): {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
acceptsArgs: boolean;
|
|
18
|
+
requireAuth: boolean;
|
|
19
|
+
handler: (ctx: PluginCommandContext) => Promise<{
|
|
20
|
+
text: string;
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
/** Alias command /id same as /identity */
|
|
24
|
+
export declare function createIdCommand(deps: IdentityCommandsDeps): {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
acceptsArgs: boolean;
|
|
28
|
+
requireAuth: boolean;
|
|
29
|
+
handler: (ctx: PluginCommandContext) => Promise<{
|
|
30
|
+
text: string;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=identity-commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-commands.d.ts","sourceRoot":"","sources":["../../../src/commands/identity-commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAUL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACf,MAAM,gCAAgC,CAAC;AAUxC,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;AAioBvD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB;;;;;mBA9e3C,oBAAoB,KAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;EAufpE;AAED,0CAA0C;AAC1C,wBAAgB,eAAe,CAAC,IAAI,EAAE,oBAAoB;;;;;mBA1frC,oBAAoB,KAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;EAmgBpE"}
|