@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,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity credentials loader (veadk-style).
|
|
3
|
+
* Loads AK/SK from:
|
|
4
|
+
* 1. Explicit config (accessKeyId, secretAccessKey, sessionToken)
|
|
5
|
+
* 2. Environment variables (VOLCENGINE_ACCESS_KEY, VOLCENGINE_SECRET_KEY, VOLCENGINE_SESSION_TOKEN)
|
|
6
|
+
* 3. Credential file (VOLCENGINE_CREDENTIALS_FILE or /var/run/secrets/iam/credential)
|
|
7
|
+
* Supports STS session token. Optional AssumeRole via roleTrn.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
const ENV_AK = "VOLCENGINE_ACCESS_KEY";
|
|
13
|
+
const ENV_SK = "VOLCENGINE_SECRET_KEY";
|
|
14
|
+
const ENV_SESSION = "VOLCENGINE_SESSION_TOKEN";
|
|
15
|
+
const ENV_CRED_FILE = "VOLCENGINE_CREDENTIALS_FILE";
|
|
16
|
+
const DEFAULT_CRED_PATH = "/var/run/secrets/iam/credential";
|
|
17
|
+
const ENV_ROLE_TRN = "RUNTIME_IAM_ROLE_TRN";
|
|
18
|
+
/**
|
|
19
|
+
* Load credentials from config, env, or file (veadk-style).
|
|
20
|
+
* Order: explicit > env > file.
|
|
21
|
+
* Returns resolved credentials or throws if none found.
|
|
22
|
+
*/
|
|
23
|
+
export async function loadIdentityCredentials(opts = {}) {
|
|
24
|
+
const resolvePath = opts.resolvePath ?? ((p) => resolve(p));
|
|
25
|
+
let ak = opts.accessKeyId ?? process.env[ENV_AK] ?? "";
|
|
26
|
+
let sk = opts.secretAccessKey ?? process.env[ENV_SK] ?? "";
|
|
27
|
+
let sessionToken = opts.sessionToken ?? process.env[ENV_SESSION] ?? "";
|
|
28
|
+
if (ak && sk) {
|
|
29
|
+
if (sessionToken || !opts.roleTrn) {
|
|
30
|
+
return {
|
|
31
|
+
accessKeyId: ak.trim(),
|
|
32
|
+
secretAccessKey: sk.trim(),
|
|
33
|
+
sessionToken: sessionToken ? sessionToken.trim() : undefined,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (opts.roleTrn) {
|
|
37
|
+
const stsCred = await assumeRole({
|
|
38
|
+
accessKeyId: ak,
|
|
39
|
+
secretAccessKey: sk,
|
|
40
|
+
roleTrn: opts.roleTrn,
|
|
41
|
+
region: "cn-beijing",
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
accessKeyId: stsCred.accessKeyId,
|
|
45
|
+
secretAccessKey: stsCred.secretAccessKey,
|
|
46
|
+
sessionToken: stsCred.sessionToken,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const credPath = opts.credentialsFile ?? process.env[ENV_CRED_FILE] ?? DEFAULT_CRED_PATH;
|
|
51
|
+
const resolvedPath = resolvePath(credPath);
|
|
52
|
+
if (existsSync(resolvedPath)) {
|
|
53
|
+
const cred = await loadCredentialsFromFile(resolvedPath);
|
|
54
|
+
const { roleTrn: _rn, ...rest } = cred;
|
|
55
|
+
const roleTrn = cred.roleTrn ?? opts.roleTrn ?? process.env[ENV_ROLE_TRN];
|
|
56
|
+
if (roleTrn && cred.accessKeyId && cred.secretAccessKey && !cred.sessionToken) {
|
|
57
|
+
const stsCred = await assumeRole({
|
|
58
|
+
accessKeyId: cred.accessKeyId,
|
|
59
|
+
secretAccessKey: cred.secretAccessKey,
|
|
60
|
+
roleTrn,
|
|
61
|
+
region: "cn-beijing",
|
|
62
|
+
});
|
|
63
|
+
return stsCred;
|
|
64
|
+
}
|
|
65
|
+
return rest;
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`Identity credentials not found. Set ${ENV_AK}/${ENV_SK} or ${ENV_CRED_FILE} (default: ${DEFAULT_CRED_PATH})`);
|
|
68
|
+
}
|
|
69
|
+
async function loadCredentialsFromFile(path) {
|
|
70
|
+
const raw = await readFile(path, "utf-8");
|
|
71
|
+
const data = JSON.parse(raw);
|
|
72
|
+
const ak = data.access_key_id ?? "";
|
|
73
|
+
const sk = data.secret_access_key ?? "";
|
|
74
|
+
const sessionToken = data.session_token ?? "";
|
|
75
|
+
const roleTrn = data.role_trn?.trim();
|
|
76
|
+
if (!ak || !sk) {
|
|
77
|
+
throw new Error(`Credential file ${path} missing access_key_id or secret_access_key`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
accessKeyId: ak.trim(),
|
|
81
|
+
secretAccessKey: sk.trim(),
|
|
82
|
+
sessionToken: sessionToken ? sessionToken.trim() : undefined,
|
|
83
|
+
roleTrn,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const STS_ENDPOINT = "https://sts.volcengineapi.com";
|
|
87
|
+
/**
|
|
88
|
+
* Call STS AssumeRole to get temporary credentials.
|
|
89
|
+
* Caches result and refreshes when expired (5 min buffer).
|
|
90
|
+
*/
|
|
91
|
+
const assumeRoleCache = new Map();
|
|
92
|
+
const CACHE_KEY_BUFFER_SEC = 300;
|
|
93
|
+
async function assumeRole(params) {
|
|
94
|
+
const { accessKeyId, secretAccessKey, roleTrn, region = "cn-beijing", roleSessionName = "clawdbot-identity", } = params;
|
|
95
|
+
const cacheKey = `${roleTrn}:${roleSessionName}`;
|
|
96
|
+
const cached = assumeRoleCache.get(cacheKey);
|
|
97
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
98
|
+
if (cached && cached.expiresAt > nowSec + CACHE_KEY_BUFFER_SEC) {
|
|
99
|
+
return cached.cred;
|
|
100
|
+
}
|
|
101
|
+
const body = {
|
|
102
|
+
RoleTrn: roleTrn,
|
|
103
|
+
RoleSessionName: roleSessionName,
|
|
104
|
+
DurationSeconds: 3600,
|
|
105
|
+
};
|
|
106
|
+
const bodyStr = JSON.stringify(body);
|
|
107
|
+
const xDate = new Date()
|
|
108
|
+
.toISOString()
|
|
109
|
+
.replace(/\.\d{3}Z$/, "Z")
|
|
110
|
+
.replace(/[:\-]/g, "");
|
|
111
|
+
const { createHash, createHmac } = await import("node:crypto");
|
|
112
|
+
const sha256 = (s) => createHash("sha256").update(s, "utf-8").digest("hex");
|
|
113
|
+
const hmac = (key, data) => createHmac("sha256", key).update(data, "utf-8").digest();
|
|
114
|
+
const xContentSha256 = sha256(bodyStr);
|
|
115
|
+
const canonicalQuery = ["Action=AssumeRole", "Version=2018-01-01"].join("&");
|
|
116
|
+
const url = new URL(`${STS_ENDPOINT}?${canonicalQuery}`);
|
|
117
|
+
const signedHeaders = "host;x-content-sha256;x-date";
|
|
118
|
+
const canonicalRequest = [
|
|
119
|
+
"POST",
|
|
120
|
+
url.pathname || "/",
|
|
121
|
+
canonicalQuery,
|
|
122
|
+
`host:${url.host}`,
|
|
123
|
+
`x-content-sha256:${xContentSha256}`,
|
|
124
|
+
`x-date:${xDate}`,
|
|
125
|
+
"",
|
|
126
|
+
signedHeaders,
|
|
127
|
+
xContentSha256,
|
|
128
|
+
].join("\n");
|
|
129
|
+
const credentialScope = `${xDate.slice(0, 8)}/${region}/sts/request`;
|
|
130
|
+
const stringToSign = ["HMAC-SHA256", xDate, credentialScope, sha256(canonicalRequest)].join("\n");
|
|
131
|
+
const kDate = hmac(secretAccessKey, xDate.slice(0, 8));
|
|
132
|
+
const kRegion = hmac(kDate, region);
|
|
133
|
+
const kService = hmac(kRegion, "sts");
|
|
134
|
+
const kSigning = hmac(kService, "request");
|
|
135
|
+
const signature = createHmac("sha256", kSigning).update(stringToSign, "utf-8").digest("hex");
|
|
136
|
+
const authorization = `HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
137
|
+
const res = await fetch(url.toString(), {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: {
|
|
140
|
+
"Content-Type": "application/json; charset=UTF-8",
|
|
141
|
+
"X-Date": xDate,
|
|
142
|
+
"X-Content-Sha256": xContentSha256,
|
|
143
|
+
Authorization: authorization,
|
|
144
|
+
},
|
|
145
|
+
body: bodyStr,
|
|
146
|
+
});
|
|
147
|
+
if (!res.ok) {
|
|
148
|
+
const text = await res.text();
|
|
149
|
+
throw new Error(`STS AssumeRole failed ${res.status}: ${text}`);
|
|
150
|
+
}
|
|
151
|
+
const json = (await res.json());
|
|
152
|
+
const creds = json.Result?.Credentials;
|
|
153
|
+
if (!creds?.AccessKeyId || !creds?.SecretAccessKey || !creds?.SessionToken) {
|
|
154
|
+
throw new Error("STS AssumeRole response missing credentials");
|
|
155
|
+
}
|
|
156
|
+
let expiresAt = nowSec + 3600;
|
|
157
|
+
if (creds.ExpiredTime) {
|
|
158
|
+
const parsed = Date.parse(creds.ExpiredTime);
|
|
159
|
+
if (!Number.isNaN(parsed)) {
|
|
160
|
+
expiresAt = Math.floor(parsed / 1000);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const result = {
|
|
164
|
+
accessKeyId: creds.AccessKeyId,
|
|
165
|
+
secretAccessKey: creds.SecretAccessKey,
|
|
166
|
+
sessionToken: creds.SessionToken,
|
|
167
|
+
};
|
|
168
|
+
assumeRoleCache.set(cacheKey, { cred: result, expiresAt });
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity service: bridges session store with CIS client.
|
|
3
|
+
* getWorkloadAccessToken: fetches TIP token for a session's userToken via CIS GetWorkloadAccessTokenForJWT.
|
|
4
|
+
*/
|
|
5
|
+
import type { TIPTokenEntry } from "../store/tip-store.js";
|
|
6
|
+
import type { IdentityClientInterface } from "./identity-client.js";
|
|
7
|
+
export type IdentityServiceConfig = {
|
|
8
|
+
identityClient: IdentityClientInterface;
|
|
9
|
+
workloadPoolName?: string;
|
|
10
|
+
workloadName?: string;
|
|
11
|
+
audience?: string[];
|
|
12
|
+
durationSeconds?: number;
|
|
13
|
+
/** When set (AssumeRole), do not pass workload name; backend uses roleName. */
|
|
14
|
+
roleTrn?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare class IdentityService {
|
|
17
|
+
private readonly config;
|
|
18
|
+
constructor(config: IdentityServiceConfig);
|
|
19
|
+
getWorkloadAccessToken(params: {
|
|
20
|
+
agentId?: string;
|
|
21
|
+
userToken: string;
|
|
22
|
+
sub?: string;
|
|
23
|
+
}): Promise<TIPTokenEntry>;
|
|
24
|
+
/**
|
|
25
|
+
* Validate user token (basic JWT decode for sub; full validation would use UserPool JWKS).
|
|
26
|
+
* For now we trust tokens that are stored via login flow.
|
|
27
|
+
*/
|
|
28
|
+
parseUserToken(userToken: string): {
|
|
29
|
+
valid: boolean;
|
|
30
|
+
sub?: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=identity-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-service.d.ts","sourceRoot":"","sources":["../../../src/services/identity-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAEpE,MAAM,MAAM,qBAAqB,GAAG;IAClC,cAAc,EAAE,uBAAuB,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,qBAAqB;IAEpD,sBAAsB,CAAC,MAAM,EAAE;QACnC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,GAAG,OAAO,CAAC,aAAa,CAAC;IA2B1B;;;OAGG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;CAiBpE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity service: bridges session store with CIS client.
|
|
3
|
+
* getWorkloadAccessToken: fetches TIP token for a session's userToken via CIS GetWorkloadAccessTokenForJWT.
|
|
4
|
+
*/
|
|
5
|
+
export class IdentityService {
|
|
6
|
+
config;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
async getWorkloadAccessToken(params) {
|
|
11
|
+
const { identityClient } = this.config;
|
|
12
|
+
// When roleTrn is set (AssumeRole), backend uses roleName; do not pass name/agentId.
|
|
13
|
+
const name = this.config.roleTrn
|
|
14
|
+
? undefined
|
|
15
|
+
: this.config.workloadName ?? params.agentId ?? "openclaw-agent";
|
|
16
|
+
const result = await identityClient.getWorkloadAccessTokenForJWT({
|
|
17
|
+
userToken: params.userToken,
|
|
18
|
+
workloadPoolName: this.config.workloadPoolName,
|
|
19
|
+
name,
|
|
20
|
+
audience: this.config.audience,
|
|
21
|
+
durationSeconds: this.config.durationSeconds,
|
|
22
|
+
});
|
|
23
|
+
const expiresAtMs = new Date(result.expiresAt).getTime();
|
|
24
|
+
return {
|
|
25
|
+
token: result.workloadAccessToken,
|
|
26
|
+
sub: params.sub ?? "unknown",
|
|
27
|
+
agentId: params.agentId,
|
|
28
|
+
chain: [],
|
|
29
|
+
issuedAt: Date.now(),
|
|
30
|
+
expiresAt: expiresAtMs,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate user token (basic JWT decode for sub; full validation would use UserPool JWKS).
|
|
35
|
+
* For now we trust tokens that are stored via login flow.
|
|
36
|
+
*/
|
|
37
|
+
parseUserToken(userToken) {
|
|
38
|
+
try {
|
|
39
|
+
const parts = userToken.split(".");
|
|
40
|
+
if (parts.length !== 3)
|
|
41
|
+
return { valid: false };
|
|
42
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
43
|
+
const sub = payload.sub;
|
|
44
|
+
if (payload.exp && payload.exp * 1000 < Date.now()) {
|
|
45
|
+
return { valid: false };
|
|
46
|
+
}
|
|
47
|
+
return { valid: true, sub };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { valid: false };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OIDC client for UserPool login flow.
|
|
3
|
+
* Fetches .well-known/openid-configuration from data plane, then:
|
|
4
|
+
* - Build authorization URL for login
|
|
5
|
+
* - Exchange code for tokens
|
|
6
|
+
*
|
|
7
|
+
* Reference: veadk auth/middleware/oauth2_auth.py
|
|
8
|
+
*/
|
|
9
|
+
export type OIDCDiscovery = {
|
|
10
|
+
issuer: string;
|
|
11
|
+
authorization_endpoint: string;
|
|
12
|
+
token_endpoint: string;
|
|
13
|
+
userinfo_endpoint?: string;
|
|
14
|
+
end_session_endpoint?: string;
|
|
15
|
+
jwks_uri?: string;
|
|
16
|
+
scopes_supported?: string[];
|
|
17
|
+
response_types_supported?: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function fetchOIDCDiscovery(discoveryUrl: string, timeoutMs?: number): Promise<OIDCDiscovery>;
|
|
20
|
+
export declare function buildAuthorizationUrl(params: {
|
|
21
|
+
authorizationEndpoint: string;
|
|
22
|
+
clientId: string;
|
|
23
|
+
redirectUri: string;
|
|
24
|
+
scope?: string;
|
|
25
|
+
state: string;
|
|
26
|
+
responseType?: string;
|
|
27
|
+
codeChallenge?: string;
|
|
28
|
+
codeChallengeMethod?: string;
|
|
29
|
+
}): string;
|
|
30
|
+
export declare function exchangeCodeForTokens(params: {
|
|
31
|
+
tokenEndpoint: string;
|
|
32
|
+
clientId: string;
|
|
33
|
+
clientSecret?: string;
|
|
34
|
+
code: string;
|
|
35
|
+
redirectUri: string;
|
|
36
|
+
codeVerifier?: string;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
access_token: string;
|
|
39
|
+
token_type?: string;
|
|
40
|
+
expires_in?: number;
|
|
41
|
+
refresh_token?: string;
|
|
42
|
+
id_token?: string;
|
|
43
|
+
}>;
|
|
44
|
+
/** Refresh access/id token using refresh_token grant. */
|
|
45
|
+
export declare function refreshAccessToken(params: {
|
|
46
|
+
tokenEndpoint: string;
|
|
47
|
+
clientId: string;
|
|
48
|
+
clientSecret?: string;
|
|
49
|
+
refreshToken: string;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
access_token: string;
|
|
52
|
+
id_token?: string;
|
|
53
|
+
refresh_token?: string;
|
|
54
|
+
expires_in?: number;
|
|
55
|
+
}>;
|
|
56
|
+
export declare function generateState(): Promise<string>;
|
|
57
|
+
//# sourceMappingURL=oidc-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-client.d.ts","sourceRoot":"","sources":["../../../src/services/oidc-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC,CAAC;AAEF,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,SAAS,SAAS,GACjB,OAAO,CAAC,aAAa,CAAC,CAoBxB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,GAAG,MAAM,CA0BT;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC;IACV,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CA6CD;AAED,yDAAyD;AACzD,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IACV,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC,CAwCD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAGrD"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OIDC client for UserPool login flow.
|
|
3
|
+
* Fetches .well-known/openid-configuration from data plane, then:
|
|
4
|
+
* - Build authorization URL for login
|
|
5
|
+
* - Exchange code for tokens
|
|
6
|
+
*
|
|
7
|
+
* Reference: veadk auth/middleware/oauth2_auth.py
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchOIDCDiscovery(discoveryUrl, timeoutMs = 10_000) {
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(discoveryUrl, { signal: controller.signal });
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
throw new Error(`OIDC discovery failed: HTTP ${res.status} from ${discoveryUrl}`);
|
|
17
|
+
}
|
|
18
|
+
const data = (await res.json());
|
|
19
|
+
if (!data.authorization_endpoint || !data.token_endpoint) {
|
|
20
|
+
throw new Error("OIDC discovery missing authorization_endpoint or token_endpoint");
|
|
21
|
+
}
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
if (err instanceof Error)
|
|
27
|
+
throw err;
|
|
28
|
+
throw new Error(String(err));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function buildAuthorizationUrl(params) {
|
|
32
|
+
const { authorizationEndpoint, clientId, redirectUri, scope = "openid profile email", state, responseType = "code", codeChallenge, codeChallengeMethod, } = params;
|
|
33
|
+
const search = new URLSearchParams({
|
|
34
|
+
response_type: responseType,
|
|
35
|
+
client_id: clientId,
|
|
36
|
+
scope,
|
|
37
|
+
redirect_uri: redirectUri,
|
|
38
|
+
state,
|
|
39
|
+
});
|
|
40
|
+
if (codeChallenge) {
|
|
41
|
+
search.set("code_challenge", codeChallenge);
|
|
42
|
+
search.set("code_challenge_method", codeChallengeMethod ?? "S256");
|
|
43
|
+
}
|
|
44
|
+
const sep = authorizationEndpoint.includes("?") ? "&" : "?";
|
|
45
|
+
return `${authorizationEndpoint}${sep}${search.toString()}`;
|
|
46
|
+
}
|
|
47
|
+
export async function exchangeCodeForTokens(params) {
|
|
48
|
+
const { tokenEndpoint, clientId, clientSecret, code, redirectUri, codeVerifier } = params;
|
|
49
|
+
const body = new URLSearchParams({
|
|
50
|
+
grant_type: "authorization_code",
|
|
51
|
+
code,
|
|
52
|
+
redirect_uri: redirectUri,
|
|
53
|
+
});
|
|
54
|
+
if (codeVerifier)
|
|
55
|
+
body.set("code_verifier", codeVerifier);
|
|
56
|
+
const headers = {
|
|
57
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
58
|
+
};
|
|
59
|
+
if (clientSecret) {
|
|
60
|
+
const creds = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
61
|
+
headers["Authorization"] = `Basic ${creds}`;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
body.set("client_id", clientId);
|
|
65
|
+
}
|
|
66
|
+
const res = await fetch(tokenEndpoint, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers,
|
|
69
|
+
body: body.toString(),
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
const text = await res.text();
|
|
73
|
+
throw new Error(`Token exchange failed: HTTP ${res.status} - ${text}`);
|
|
74
|
+
}
|
|
75
|
+
const data = (await res.json());
|
|
76
|
+
const accessToken = data.access_token;
|
|
77
|
+
if (!accessToken || typeof accessToken !== "string") {
|
|
78
|
+
throw new Error("Token response missing access_token");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
access_token: accessToken,
|
|
82
|
+
token_type: data.token_type,
|
|
83
|
+
expires_in: typeof data.expires_in === "number" ? data.expires_in : undefined,
|
|
84
|
+
refresh_token: data.refresh_token,
|
|
85
|
+
id_token: data.id_token,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Refresh access/id token using refresh_token grant. */
|
|
89
|
+
export async function refreshAccessToken(params) {
|
|
90
|
+
const { tokenEndpoint, clientId, clientSecret, refreshToken } = params;
|
|
91
|
+
const body = new URLSearchParams({
|
|
92
|
+
grant_type: "refresh_token",
|
|
93
|
+
refresh_token: refreshToken,
|
|
94
|
+
});
|
|
95
|
+
body.set("client_id", clientId);
|
|
96
|
+
const headers = {
|
|
97
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
98
|
+
};
|
|
99
|
+
if (clientSecret) {
|
|
100
|
+
const creds = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
101
|
+
headers["Authorization"] = `Basic ${creds}`;
|
|
102
|
+
}
|
|
103
|
+
const res = await fetch(tokenEndpoint, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers,
|
|
106
|
+
body: body.toString(),
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok) {
|
|
109
|
+
const text = await res.text();
|
|
110
|
+
throw new Error(`Token refresh failed: HTTP ${res.status} - ${text}`);
|
|
111
|
+
}
|
|
112
|
+
const data = (await res.json());
|
|
113
|
+
const accessToken = data.access_token;
|
|
114
|
+
if (!accessToken || typeof accessToken !== "string") {
|
|
115
|
+
throw new Error("Token refresh response missing access_token");
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
access_token: accessToken,
|
|
119
|
+
id_token: data.id_token,
|
|
120
|
+
refresh_token: data.refresh_token,
|
|
121
|
+
expires_in: typeof data.expires_in === "number" ? data.expires_in : undefined,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export async function generateState() {
|
|
125
|
+
const { randomBytes } = await import("node:crypto");
|
|
126
|
+
return randomBytes(32).toString("base64url");
|
|
127
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Feishu notification sender using Open API (no @larksuiteoapi dependency).
|
|
3
|
+
* Credentials resolved from cfg.channels.feishu same as feishu extension listEnabledFeishuAccounts.
|
|
4
|
+
* See https://open.feishu.cn/document/ukTMukTMukTM/uYjM04SO2QjL1IDN
|
|
5
|
+
*/
|
|
6
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
7
|
+
type ResolvedCreds = {
|
|
8
|
+
appId: string;
|
|
9
|
+
appSecret: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Resolve Feishu credentials from main config channels.feishu.
|
|
14
|
+
* Same merge logic as feishu extension resolveFeishuAccount.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveFeishuCredsFromConfig(cfg: OpenClawConfig, accountId?: string | null): ResolvedCreds | null;
|
|
17
|
+
/**
|
|
18
|
+
* Pick first enabled+configured account when accountId not specified.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveDefaultFeishuCreds(cfg: OpenClawConfig): ResolvedCreds | null;
|
|
21
|
+
/**
|
|
22
|
+
* Send a text message to a Feishu user or chat.
|
|
23
|
+
* Credentials from cfg.channels.feishu (same source as listEnabledFeishuAccounts).
|
|
24
|
+
*/
|
|
25
|
+
export declare function sendNotificationFeishu(cfg: OpenClawConfig, to: string, text: string, accountId?: string | null): Promise<void>;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=send-notification-feishu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send-notification-feishu.d.ts","sourceRoot":"","sources":["../../../src/services/send-notification-feishu.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAY1D,KAAK,aAAa,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAmB3E;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,cAAc,EACnB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,aAAa,GAAG,IAAI,CAetB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI,CAOnF;AAiED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,IAAI,CAAC,CAgDf"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Feishu notification sender using Open API (no @larksuiteoapi dependency).
|
|
3
|
+
* Credentials resolved from cfg.channels.feishu same as feishu extension listEnabledFeishuAccounts.
|
|
4
|
+
* See https://open.feishu.cn/document/ukTMukTMukTM/uYjM04SO2QjL1IDN
|
|
5
|
+
*/
|
|
6
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
7
|
+
function listFeishuAccountIds(cfg) {
|
|
8
|
+
const feishu = cfg.channels?.feishu;
|
|
9
|
+
const accounts = feishu?.accounts;
|
|
10
|
+
if (!accounts || typeof accounts !== "object") {
|
|
11
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
12
|
+
}
|
|
13
|
+
const ids = Object.keys(accounts).filter(Boolean);
|
|
14
|
+
return ids.length > 0 ? [...ids].sort((a, b) => a.localeCompare(b)) : [DEFAULT_ACCOUNT_ID];
|
|
15
|
+
}
|
|
16
|
+
function mergeFeishuAccountConfig(cfg, accountId) {
|
|
17
|
+
const feishu = cfg.channels?.feishu;
|
|
18
|
+
const { accounts: _ignored, ...base } = feishu ?? {};
|
|
19
|
+
const account = feishu?.accounts?.[accountId] ?? {};
|
|
20
|
+
return { ...base, ...account };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve Feishu credentials from main config channels.feishu.
|
|
24
|
+
* Same merge logic as feishu extension resolveFeishuAccount.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveFeishuCredsFromConfig(cfg, accountId) {
|
|
27
|
+
const accId = normalizeAccountId(accountId);
|
|
28
|
+
const merged = mergeFeishuAccountConfig(cfg, accId);
|
|
29
|
+
if (merged.enabled === false)
|
|
30
|
+
return null;
|
|
31
|
+
const appId = merged.appId?.trim();
|
|
32
|
+
const appSecret = merged.appSecret?.trim();
|
|
33
|
+
if (!appId || !appSecret)
|
|
34
|
+
return null;
|
|
35
|
+
const domain = merged.domain ?? "feishu";
|
|
36
|
+
const baseUrl = domain === "lark"
|
|
37
|
+
? "https://open.larksuite.com"
|
|
38
|
+
: domain.startsWith("https://")
|
|
39
|
+
? domain.replace(/\/$/, "")
|
|
40
|
+
: "https://open.feishu.cn";
|
|
41
|
+
return { appId, appSecret, baseUrl };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Pick first enabled+configured account when accountId not specified.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveDefaultFeishuCreds(cfg) {
|
|
47
|
+
const ids = listFeishuAccountIds(cfg);
|
|
48
|
+
for (const id of ids) {
|
|
49
|
+
const creds = resolveFeishuCredsFromConfig(cfg, id);
|
|
50
|
+
if (creds)
|
|
51
|
+
return creds;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
let cachedToken = null;
|
|
56
|
+
const TOKEN_BUFFER_MS = 5 * 60 * 1000;
|
|
57
|
+
async function getTenantAccessToken(creds) {
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
if (cachedToken &&
|
|
60
|
+
cachedToken.baseUrl === creds.baseUrl &&
|
|
61
|
+
cachedToken.expiresAt > now + TOKEN_BUFFER_MS) {
|
|
62
|
+
return cachedToken.token;
|
|
63
|
+
}
|
|
64
|
+
const res = await fetch(`${creds.baseUrl}/open-apis/auth/v3/tenant_access_token/internal`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: { "Content-Type": "application/json" },
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
app_id: creds.appId,
|
|
69
|
+
app_secret: creds.appSecret,
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
throw new Error(`Feishu auth failed: ${res.status} ${await res.text()}`);
|
|
74
|
+
}
|
|
75
|
+
const json = (await res.json());
|
|
76
|
+
if (json.code !== 0 || !json.tenant_access_token) {
|
|
77
|
+
throw new Error(`Feishu auth error: ${json.msg ?? json.code ?? "unknown"}`);
|
|
78
|
+
}
|
|
79
|
+
const expireMs = (json.expire ?? 7200) * 1000;
|
|
80
|
+
cachedToken = {
|
|
81
|
+
token: json.tenant_access_token,
|
|
82
|
+
expiresAt: now + expireMs,
|
|
83
|
+
baseUrl: creds.baseUrl,
|
|
84
|
+
};
|
|
85
|
+
return cachedToken.token;
|
|
86
|
+
}
|
|
87
|
+
function resolveReceiveIdType(to) {
|
|
88
|
+
const t = to.trim().toLowerCase();
|
|
89
|
+
if (t.startsWith("chat:"))
|
|
90
|
+
return "chat_id";
|
|
91
|
+
if (t.startsWith("user:"))
|
|
92
|
+
return "open_id";
|
|
93
|
+
if (t.startsWith("open_id:"))
|
|
94
|
+
return "open_id";
|
|
95
|
+
if (t.startsWith("oc_"))
|
|
96
|
+
return "chat_id";
|
|
97
|
+
if (t.startsWith("ou_"))
|
|
98
|
+
return "open_id";
|
|
99
|
+
return "user_id";
|
|
100
|
+
}
|
|
101
|
+
function extractReceiveId(to) {
|
|
102
|
+
const t = to.trim();
|
|
103
|
+
if (/^(chat|user|open_id):/i.test(t)) {
|
|
104
|
+
return t.replace(/^(chat|user|open_id):/i, "").trim();
|
|
105
|
+
}
|
|
106
|
+
return t;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Send a text message to a Feishu user or chat.
|
|
110
|
+
* Credentials from cfg.channels.feishu (same source as listEnabledFeishuAccounts).
|
|
111
|
+
*/
|
|
112
|
+
export async function sendNotificationFeishu(cfg, to, text, accountId) {
|
|
113
|
+
const creds = accountId != null
|
|
114
|
+
? resolveFeishuCredsFromConfig(cfg, accountId)
|
|
115
|
+
: resolveDefaultFeishuCreds(cfg);
|
|
116
|
+
if (!creds) {
|
|
117
|
+
throw new Error("Feishu not configured. Set channels.feishu.appId and appSecret in openclaw.json.");
|
|
118
|
+
}
|
|
119
|
+
const token = await getTenantAccessToken(creds);
|
|
120
|
+
const receiveId = extractReceiveId(to);
|
|
121
|
+
const receiveIdType = resolveReceiveIdType(to);
|
|
122
|
+
if (!receiveId) {
|
|
123
|
+
throw new Error(`Invalid Feishu target: ${to}`);
|
|
124
|
+
}
|
|
125
|
+
const content = JSON.stringify({
|
|
126
|
+
zh_cn: { title: "", content: [[{ tag: "text", text }]] },
|
|
127
|
+
en_us: { title: "", content: [[{ tag: "text", text }]] },
|
|
128
|
+
});
|
|
129
|
+
const res = await fetch(`${creds.baseUrl}/open-apis/im/v1/messages?receive_id_type=${receiveIdType}`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: {
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
Authorization: `Bearer ${token}`,
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
receive_id: receiveId,
|
|
137
|
+
msg_type: "post",
|
|
138
|
+
content,
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
throw new Error(`Feishu send failed: ${res.status} ${await res.text()}`);
|
|
143
|
+
}
|
|
144
|
+
const json = (await res.json());
|
|
145
|
+
if (json.code !== 0) {
|
|
146
|
+
throw new Error(`Feishu send error: ${json.msg ?? json.code ?? "unknown"}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session token refresh: when userToken expires, use refresh_token to get new tokens.
|
|
3
|
+
* Used by before_agent_start when GetWorkloadAccessTokenForJWT fails with "token has expired".
|
|
4
|
+
*/
|
|
5
|
+
export type OIDCConfigForRefresh = {
|
|
6
|
+
discoveryUrl: string;
|
|
7
|
+
clientId: string;
|
|
8
|
+
clientSecret?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Refresh session userToken using refresh_token grant.
|
|
12
|
+
* Updates session with new userToken (and rotated refresh_token if returned).
|
|
13
|
+
* Returns the new userToken or null if refresh failed or no refresh token.
|
|
14
|
+
*/
|
|
15
|
+
export declare function refreshSessionUserToken(storeDir: string, sessionKey: string, getOidcConfig: () => Promise<OIDCConfigForRefresh>): Promise<string | null>;
|
|
16
|
+
//# sourceMappingURL=session-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-refresh.d.ts","sourceRoot":"","sources":["../../../src/services/session-refresh.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,GACjD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4BxB"}
|