@modesto_ma/dsh-channels 0.5.0-beta.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/lib/client.js.map +1 -1
- package/lib/index.js +42 -15
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access/cards.ts +5 -1
- package/src/access/controller.ts +14 -6
- package/src/access/policy.ts +7 -2
- package/src/access/requests.ts +1 -0
- package/src/config.ts +9 -2
- package/src/domain.ts +2 -2
- package/src/service.ts +1 -0
- package/src/settings/contract.ts +1 -0
- package/src/settings/surface.ts +8 -1
package/package.json
CHANGED
package/src/access/cards.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface UserRequestCardInput {
|
|
|
49
49
|
requesterName?: string
|
|
50
50
|
/** Opaque request token. */
|
|
51
51
|
token: string
|
|
52
|
+
/** Whether whitelisted users may also use private chats (`dmAllowWhitelist`). */
|
|
53
|
+
dmAllowWhitelist?: boolean
|
|
52
54
|
/** Render timestamp (defaults to now). */
|
|
53
55
|
now?: number
|
|
54
56
|
}
|
|
@@ -158,7 +160,9 @@ export function buildUserRequestCard(input: UserRequestCardInput): object {
|
|
|
158
160
|
}
|
|
159
161
|
elements.push(field('申请时间', formatTime(input.now)))
|
|
160
162
|
elements.push(divider())
|
|
161
|
-
elements.push(note(
|
|
163
|
+
elements.push(note(input.dmAllowWhitelist === true
|
|
164
|
+
? '授权后该用户可在所有已授权群里使用,也可以私聊机器人。'
|
|
165
|
+
: '授权后该用户可在所有已授权群里使用;私聊仍仅管理员可用。'))
|
|
162
166
|
elements.push(decisionButtons(input.token, '授权用户', '忽略'))
|
|
163
167
|
return standaloneCard('**🙋 用户授权申请**', elements)
|
|
164
168
|
}
|
package/src/access/controller.ts
CHANGED
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
* matrix lives in `policy.ts`; this module owns the user-facing behavior on
|
|
7
7
|
* denial:
|
|
8
8
|
*
|
|
9
|
-
* - private chats: non-administrators
|
|
9
|
+
* - private chats: denied senders (non-administrators, or non-whitelisted
|
|
10
|
+
* users with `dmAllowWhitelist`) get one fixed notice (10 s per-sender
|
|
10
11
|
* cooldown); with no administrator configured they get the bootstrap
|
|
11
12
|
* notice instead;
|
|
12
13
|
* - group chats: fully silent, except that addressed denials call back into
|
|
13
14
|
* the authorization-request lifecycle so the administrator can be asked;
|
|
14
15
|
* - slash commands: `/userid` stays available to every private sender, the
|
|
15
16
|
* administrator's commands always pass (including inside an unauthorized
|
|
16
|
-
* group), everything else must pass admission.
|
|
17
|
+
* group), everything else must pass admission (in private chats too).
|
|
17
18
|
*
|
|
18
19
|
* The controller never creates a Session or a workspace.
|
|
19
20
|
*
|
|
@@ -30,6 +31,9 @@ import type { AccessStore, AccessStoreSnapshot } from './store.ts'
|
|
|
30
31
|
/** Fixed private-chat notice for non-administrators (no model call). */
|
|
31
32
|
export const DM_DENY_TEXT = '非管理员禁用私聊功能哦~如需使用,请在管理员授权的群里 @ 我;或联系管理员把你的 user_id 加入白名单。'
|
|
32
33
|
|
|
34
|
+
/** Fixed private-chat notice for non-whitelisted users while `dmAllowWhitelist` is on. */
|
|
35
|
+
export const DM_DENY_WHITELIST_TEXT = '你还不在白名单里,暂时不能私聊哦~发送 /userid 查看你的 user_id,请管理员加入白名单后即可使用。'
|
|
36
|
+
|
|
33
37
|
/** Fixed private-chat notice while no administrator is configured. */
|
|
34
38
|
export const DM_UNCONFIGURED_TEXT = '机器人尚未配置管理员。发送 /userid 可查看你的 user_id,交给部署者配置后即可启用。'
|
|
35
39
|
|
|
@@ -123,8 +127,9 @@ export class AccessController {
|
|
|
123
127
|
/**
|
|
124
128
|
* Slash-command admission. Runs before any command card/topic/Session side
|
|
125
129
|
* effect. `open` mode keeps the legacy behavior; otherwise private chats
|
|
126
|
-
*
|
|
127
|
-
* group chats must pass the full admission
|
|
130
|
+
* admit whoever the admission matrix admits (with `/userid` as the
|
|
131
|
+
* bootstrap exception) and group chats must pass the full admission
|
|
132
|
+
* matrix; the administrator is
|
|
128
133
|
* never constrained by the whitelists, so all of their commands pass even
|
|
129
134
|
* inside an unauthorized group (`/auth` is the in-conversation grant
|
|
130
135
|
* entry).
|
|
@@ -136,7 +141,7 @@ export class AccessController {
|
|
|
136
141
|
// `/userid` is the bootstrap entry for everyone: it only echoes the
|
|
137
142
|
// sender's own user_id and has no storage/model side effect.
|
|
138
143
|
if (commandName === 'userid') return true
|
|
139
|
-
if (this.
|
|
144
|
+
if (this.decide(msg).kind === 'allow') return true
|
|
140
145
|
await this.noticeDeniedDm(msg)
|
|
141
146
|
return false
|
|
142
147
|
}
|
|
@@ -207,7 +212,10 @@ export class AccessController {
|
|
|
207
212
|
const last = this.dmNoticeAt.get(msg.senderId)
|
|
208
213
|
if (last !== undefined && now - last < DM_NOTICE_COOLDOWN_MS) return
|
|
209
214
|
this.dmNoticeAt.set(msg.senderId, now)
|
|
210
|
-
const
|
|
215
|
+
const config = this.config()
|
|
216
|
+
const text = config.adminUserId === undefined
|
|
217
|
+
? DM_UNCONFIGURED_TEXT
|
|
218
|
+
: config.dmAllowWhitelist ? DM_DENY_WHITELIST_TEXT : DM_DENY_TEXT
|
|
211
219
|
await this.sendText(msg.chatId, text, msg.messageId)
|
|
212
220
|
}
|
|
213
221
|
|
package/src/access/policy.ts
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* Access model:
|
|
12
12
|
* - the administrator may start/continue a conversation in any private chat
|
|
13
13
|
* or any group, regardless of the whitelists;
|
|
14
|
-
* - private chats: non-administrators are refused
|
|
14
|
+
* - private chats: non-administrators are refused, unless
|
|
15
|
+
* `dmAllowWhitelist` is on and their user_id is on the global user
|
|
16
|
+
* whitelist;
|
|
15
17
|
* - group chats: non-administrators need the group to be authorized AND
|
|
16
18
|
* their user_id to be on the global user whitelist.
|
|
17
19
|
*
|
|
@@ -56,7 +58,7 @@ export type AccessDecision =
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/** The configuration slice admission reads (allows easy fakes in tests). */
|
|
59
|
-
export type AccessPolicyConfig = Pick<FeishuConfig, 'accessMode' | 'adminUserId'>
|
|
61
|
+
export type AccessPolicyConfig = Pick<FeishuConfig, 'accessMode' | 'adminUserId' | 'dmAllowWhitelist'>
|
|
60
62
|
|
|
61
63
|
/** True when `userId` is exactly the configured administrator. */
|
|
62
64
|
export function isAdminUserId(
|
|
@@ -90,6 +92,9 @@ export function decideAccess(
|
|
|
90
92
|
if (subject.senderId.trim() === admin) return { kind: 'allow', reason: 'admin' }
|
|
91
93
|
|
|
92
94
|
if (subject.chatType === 'p2p') {
|
|
95
|
+
if (config.dmAllowWhitelist && snapshot.users.has(subject.senderId)) {
|
|
96
|
+
return { kind: 'allow', reason: 'authorized-user' }
|
|
97
|
+
}
|
|
93
98
|
return { kind: 'deny', reason: 'dm-non-admin' }
|
|
94
99
|
}
|
|
95
100
|
|
package/src/access/requests.ts
CHANGED
|
@@ -350,6 +350,7 @@ export class AccessRequests {
|
|
|
350
350
|
...(groupName === undefined ? {} : { groupName }),
|
|
351
351
|
...(msg.senderName === undefined ? {} : { requesterName: msg.senderName }),
|
|
352
352
|
token: record.token,
|
|
353
|
+
dmAllowWhitelist: this.config().dmAllowWhitelist,
|
|
353
354
|
now,
|
|
354
355
|
}), record)
|
|
355
356
|
return true
|
package/src/config.ts
CHANGED
|
@@ -384,13 +384,20 @@ export const configSchema = z.object({
|
|
|
384
384
|
* Admission policy for inbound Feishu traffic.
|
|
385
385
|
*
|
|
386
386
|
* - `guarded` (default): only the configured administrator can talk in
|
|
387
|
-
* private chats
|
|
388
|
-
*
|
|
387
|
+
* private chats (plus whitelisted users when `dmAllowWhitelist` is on),
|
|
388
|
+
* and a group conversation additionally requires the group to be
|
|
389
|
+
* authorized and the sender to be the administrator or an explicitly
|
|
389
390
|
* authorized user.
|
|
390
391
|
* - `open`: legacy behavior — any user inside the app's availability scope
|
|
391
392
|
* can start a conversation. Debug/compatibility only; unsafe.
|
|
392
393
|
*/
|
|
393
394
|
accessMode: z.enum(['open', 'guarded']).default('guarded'),
|
|
395
|
+
/**
|
|
396
|
+
* In `guarded` mode, also admit users on the global user whitelist
|
|
397
|
+
* (`/auth user`) in private chats. Off by default: private chats stay
|
|
398
|
+
* administrator-only. Each private-chat user already has an own workspace.
|
|
399
|
+
*/
|
|
400
|
+
dmAllowWhitelist: z.boolean().default(false),
|
|
394
401
|
/**
|
|
395
402
|
* The unique administrator's Feishu `user_id`. Set it in the Web
|
|
396
403
|
* settings panel (or the profile config). Get it by sending `/userid` to the
|
package/src/domain.ts
CHANGED
|
@@ -217,8 +217,8 @@ export type AccessGroupRecord = z.infer<typeof accessGroupRecordSchema>
|
|
|
217
217
|
|
|
218
218
|
/**
|
|
219
219
|
* One authorized user (`user_id`), global across every authorized group.
|
|
220
|
-
* Private chats are administrator-only
|
|
221
|
-
* conversations.
|
|
220
|
+
* Private chats are administrator-only unless `dmAllowWhitelist` is on, so
|
|
221
|
+
* by default this grant only affects group conversations.
|
|
222
222
|
*/
|
|
223
223
|
export const accessUserRecordSchema = z.object({
|
|
224
224
|
/** Feishu `user_id` — also the table key. */
|
package/src/service.ts
CHANGED
|
@@ -1922,6 +1922,7 @@ export class FeishuService {
|
|
|
1922
1922
|
await this.delivery.standaloneReply(msg, standaloneInfoCard('**🔐 访问授权状态**', [
|
|
1923
1923
|
`**准入模式**:\`${this.config.accessMode}\``,
|
|
1924
1924
|
`**管理员**:${this.adminStatusLine()}`,
|
|
1925
|
+
`**私聊**:${this.config.dmAllowWhitelist ? '管理员与白名单用户' : '仅管理员'}`,
|
|
1925
1926
|
...(msg.chatType === 'group'
|
|
1926
1927
|
? [`**本群**:${this.accessStore.hasGroup(msg.chatId) ? '已授权' : '未授权'}(\`${msg.chatId}\`)`]
|
|
1927
1928
|
: []),
|
package/src/settings/contract.ts
CHANGED
package/src/settings/surface.ts
CHANGED
|
@@ -153,6 +153,7 @@ const BOOLEAN_KEYS = new Set<EditableKey>([
|
|
|
153
153
|
'welcomeOnboarding',
|
|
154
154
|
'slashCommandPanel',
|
|
155
155
|
'allowPermissionSwitch',
|
|
156
|
+
'dmAllowWhitelist',
|
|
156
157
|
'acceptAttachments',
|
|
157
158
|
'prefetchImages',
|
|
158
159
|
'feishuTools.attachmentFetch',
|
|
@@ -401,7 +402,13 @@ const FIELD_META: Record<EditableKey, FieldMeta> = {
|
|
|
401
402
|
label: '调试模式',
|
|
402
403
|
group: 'access',
|
|
403
404
|
summary: '打开后所有人都能聊,不安全,只在调试时用',
|
|
404
|
-
help: '
|
|
405
|
+
help: '关闭时(默认):管理员随时可用;非管理员私聊会被拒绝(打开「白名单用户可私聊」后白名单用户也可以);群聊要同时满足群已授权、发言人在用户白名单里。授权入口有三个:群里 /auth 命令、授权卡、本页。撤销立即生效。',
|
|
406
|
+
},
|
|
407
|
+
dmAllowWhitelist: {
|
|
408
|
+
label: '白名单用户可私聊',
|
|
409
|
+
group: 'access',
|
|
410
|
+
summary: '关闭时私聊仅管理员可用',
|
|
411
|
+
help: '打开后「已授权用户」里的人也可以私聊机器人,各自使用独立的私聊工作区,能力与群聊相同;不在白名单的人仍收到固定提示。撤销用户立即生效。',
|
|
405
412
|
},
|
|
406
413
|
adminUserId: {
|
|
407
414
|
label: '管理员 user_id',
|