- 搜索引擎
- 全文检索
- 后端
- 前端
- CLI
【免费下载链接】hister
Your own search engine
本指南基于 Hister 仓库中的官方文档 user-handling.md 编写,并结合仓库源码(config/config.go、server/model/user.go、cmd/users.go、server/session.go、server/server.go 等)对每一个配置项、命令与安全机制进行源码级印证与深入展开。读完本文,你将能够在一台 Hister 实例上启用多用户隔离,为团队成员创建账号、配置 OAuth 单点登录、管理个人访问令牌,并理解文档所有权与搜索作用域在底层是如何实现的。
一、功能总览:多用户如何共享同一实例
Hister 的多用户处理(User Handling)允许多个相互独立的用户共享单个 Hister 实例。开启后,每个用户拥有:
- 独立的登录凭据(用户名 + 密码或 OAuth 身份);
- 独立的文档集合与按作用域过滤的搜索结果;
- 独立的个人访问令牌(Personal Access Token),供 API 客户端与 CLI 使用;
- 独立的规则(Skip / Priority / Versioning)与搜索别名(Aliases)。
底层实现上,服务器共享同一套索引文件,但在搜索时强制执行文档所有权(ownership)。用户 ID 被写入每个文档索引记录,查询时按用户 ID 过滤(详见下文「文档隔离」一节)。
关键的设计取向是默认关闭:
app: user_handling: false # 默认值,即单用户模式由于用户处理默认关闭,Hister 保持完全向后兼容——现有单用户部署无需任何改动。已索引的文档存储在用户 ID0之下,在开启用户处理之后依然对所有已认证用户可见(见「单用户兼容」一节)。
在 config/config.go 中,app段的结构体字段与 YAML 键一一对应:UserHandling bool \yaml:"user_handling"`,与AccessToken、Public` 等同级。
二、激活多用户模式
在配置文件的app段设置:
app: user_handling: true注意:当
user_handling生效时,app.access_token的含义发生变化——它不再作为全局客户端令牌,而仅用于将请求认证为对应 token 的用户。典型用法是:Hister 管理员在配置文件中把app.access_token设为自己的个人访问令牌,从而用命令行 Hister 命令以管理员身份执行操作(见 server/server.go 的populateUserContext:请求头携带的X-Access-Token会先尝试按用户 token 解析)。
开启后,先重启服务器,并至少创建一个用户账号,然后才能登录。创建用户的方法见下文「用户管理命令」。
三、认证机制
3.1 Web 界面登录与会话
启用用户处理后,Web 界面会向未登录访客展示登录页,输入用户名与密码即可登录。会话的底层机制在 server/session.go 中实现,要点如下:
- Cookie 内容:仅包含一个随机的、不透明的会话标识符(32 字节随机数,Base64 编码),不包含任何用户数据;会话数据与标识符的哈希存储在 SQL 数据库中。
- 过期策略:会话在最近一次有效请求后30 天过期(
sessionMaxAge,见 server/session.go);每次有效请求都会刷新会话与 Cookie 的过期时间,重新计算为距该请求 30 天。 - Cookie 属性:
HttpOnly=true、SameSite=Lax;Secure属性由server.base_url决定——当 base URL 使用 HTTPS 时启用,使用 HTTP 时禁用,因此本机回环(loopback)HTTP 实例仍可正常登录。 - 令牌存储:数据库只保存会话令牌的SHA-256 哈希(
sessionTokenHash,见 server/session.go),数据库泄露不会直接暴露可用会话。
明文 HTTP 无法保护会话免受网络窃听,因此任何暴露到网络的实例都应使用 HTTPS。对Secure属性的判定可在 server/session.go 中看到:Secure: parsedBaseURL != nil && parsedBaseURL.Scheme == "https"。
如果配置了 OAuth 提供方,登录页还会显示Sign in with <Provider>按钮(见下文 OAuth 登录)。
3.2 OAuth 登录
当server.oauth配置了 GitHub、Google 或任意 OpenID Connect(OIDC)提供方时,用户可以通过 OAuth 登录,OAuth 账号无需密码。第一次通过 OAuth 登录时,Hister 会自动创建与之绑定的本地账号,用户名的来源因提供方而异(见 server/oauth 目录):
| 提供方 | 用户名来源 | 回退方案 | 源码 |
|---|---|---|---|
| GitHub | 登录名(login) | — | github.go |
账号名称(name) | 完整邮箱地址 | google.go | |
| OIDC | preferred_username | 完整邮箱地址 | oidc.go |
此后使用同一提供方身份登录会复用同一账号(通过OAuthID字段匹配,见 server/model/user.go 的GetUserByOAuthID/CreateOAuthUser)。
OAuth 账号与密码账号功能完全一致:拥有作用域内的文档与搜索结果、个人访问令牌、规则和别名。OAuth 用户可以在个人资料页(Profile)生成个人访问令牌,用于 CLI 或浏览器扩展。
配置方法见 configuration.md 的 OAuth 章节。配置校验逻辑在 config/config.go 的validateOAuth中:合法提供方名称为github、google、oidc,client_id与client_secret必填,OIDC 还需提供configuration_url或auth_url。
3.3 OAuth-Only 模式
设置server.oauth_only: true可彻底禁用密码登录,Web 界面只接受 OAuth 登录:登录页隐藏凭据表单,只显示 OAuth 提供方按钮。这适用于强制单点登录(SSO)策略、防止用户用本地设置的密码绕过 SSO 的场景。
在oauth_only模式下个人访问令牌依然有效,因此 API 客户端和 CLI 工具无需浏览器登录即可完成认证。在多用户模式下,app.access_token作为客户端默认令牌,必须包含某个用户的个人令牌才能认证成功。
完整配置参考见 configuration.md 的 OAuth-Only 模式章节,配置结构体定义在 config/config.go(OAuthOnly bool \yaml:"oauth_only"``)。
3.4 浏览器扩展认证
浏览器扩展有两种认证方式:
- 个人访问令牌:在扩展弹窗(popup)或选项页(options page)中输入令牌并保存设置;
- 复制浏览器会话:
- 先在同一个浏览器中登录 Hister Web 界面;
- 点击扩展弹窗或选项页中的Authenticate with Browser Session按钮;
- 扩展会复制 Web 界面当前活跃的会话 Cookie。
所有经扩展索引的页面都会存入该用户的账号之下(文档归属由认证用户 ID 决定,见「文档隔离」)。
3.5 API 与命令行客户端认证
任何 API 客户端都可以通过X-Access-Token请求头携带个人访问令牌完成认证:
curl -H "X-Access-Token: <your-token>" http://localhost:4433/api/statsHister CLI 使用-t标志传入令牌:
hister -t <your-token> search "query"在源码层面,令牌认证有两套路径:请求头X-Access-Token,以及Authorization: Bearer <token>形式(见 server/server.go 的requestAccessToken)。认证后,populateUserContext会依据令牌查询用户并填充用户上下文(server/server.go)。
四、用户管理命令
所有用户管理命令都要求user_handling: true,并且需要能直接访问服务器主机(它们会直接更新用户数据库,因此不应在远程通过 API 暴露)。其中delete-user还会联系正在运行的 Hister 服务器,搜索该用户拥有的文档,并在提供--purge时通过 API 删除它们。命令实现在 cmd/users.go 中,对应的数据库操作在 server/model/user.go 中。
4.1 create-user
创建新用户账号,交互式提示输入密码(最少 8 个字符,需输入两次确认)。
hister create-user USERNAME [--admin]| 标志 | 说明 |
|---|---|
--admin | 授予该用户管理员权限 |
底层实现:model.CreateUser使用bcrypt(默认成本)对密码加盐哈希后存储,并生成随机访问令牌(server/model/user.go)。密码在 cmd/users.go 的promptConfirmedPassword中完成长度校验(少于 8 字符直接报错)与两次输入一致性校验,并通过交互式 TUI 以*回显输入。
4.2 delete-user
软删除用户账号。若服务器发现该用户拥有已索引的文档,除非提供--purge,否则命令拒绝继续;--purge会在软删除账号前,先删除预检(preflight)发现的文档。需要注意:这不是完整的数据擦除,关于多用户所有权与数据保留的细节见>hister delete-user USERNAME [--purge]
| 标志 | 说明 |
|---|---|
--purge | 先删除该用户名下找到的已索引文档,再删除账号 |
源码逻辑(cmd/users.go):命令先用user_id:<ID>查询该用户的文档数,若Total > 0且未提供--purge则直接退出并提示;提供--purge时调用DeleteDocuments清理文档,最后执行model.DeleteUser软删除。
4.3 show-user
显示用户账号信息。
hister show-user USERNAME [--token]| 标志 | 说明 |
|---|---|
--token | 同时显示该用户的访问令牌(默认隐藏) |
示例输出:
Username: alice ID: 1 Admin: yes Created at: 2026-03-26 09:00:00 Updated at: 2026-03-26 09:00:004.4 update-user
修改既有用户账号,至少必须提供一个标志。
hister update-user USERNAME [--username NEW] [--password] [--regen-token] [--toggle-admin]| 标志 | 说明 |
|---|---|
--username NEW | 将用户名改为NEW |
--password | 交互式提示并设置新密码 |
--regen-token | 生成并打印新访问令牌,旧令牌立即失效 |
--toggle-admin | 切换管理员状态(开/关) |
标志可以组合使用;当--username与其他标志一起使用时,先执行改名(cmd/users.go 中先调用model.UpdateUsername再继续后续标志处理)。密码同样要求至少 8 个字符并输入两次确认。未提供任何标志时,命令会报错no changes specified退出(cmd/users.go)。
五、每用户规则与别名
开启用户处理后,每个用户的规则与别名独立存储于数据库(不是配置文件)。通过 Web UI 或 API 的修改只影响当前认证用户的规则,不会改动配置文件。
- Skip rules(跳过规则):命中用户跳过规则的 URL 在索引时被静默忽略,与单用户模式行为一致;
- Priority rules(优先规则):用户的优先规则将匹配结果提升到其搜索结果顶部;
- Versioning rules(版本化规则):命中版本化规则的 URL 在每次重新索引时对其内容做 diff 并保存;
- Search aliases(搜索别名):用户定义的别名仅作用于该用户的搜索。
用户可以通过 Web 界面的Rules标签页或 API 端点查看和编辑自己的规则与别名。而单用户模式下,规则与别名继续从应用数据目录下的rules.json读写。
底层实现:用户模型中的RulesJSON字段以 JSON 形式存储规则(默认'{}'),ParseRules在读取时反序列化并补齐缺失的 Skip/Priority/Versioning 与 Aliases 字段,然后调用Compile()编译正则;SaveUserRules将规则序列化写回数据库(server/model/user.go)。在请求处理中,webContext.effectiveRules()会优先返回认证用户的规则(server/server.go)。
六、规则正则语法(Regexp)
规则正则的匹配行为对实际配置至关重要,以下是官方文档明确的行为约定:
- 跳过规则作用于完整的 URL(从协议到查询字符串参数),并且匹配范围是受限的;
- 锚定必须包含协议:例如
^https://foo.com或^https?://(login|mail)\.是合法的,而^foo.com无法命中; /login$不会匹配https://foo.com/login?auth=1(因为 URL 还带有查询字符串);- URL 中的 hash 会被移除:
https://foo.com/#active-tab归一化为https://foo.com/; - 查询字符串参数不会被重排,仅剥离
utm_*参数; - Go 正则表达式(
regexp/syntax语法)不支持 look-ahead / look-behind(前瞻/后瞻)断言。
这些约定在规则编译与 URL 归一化流程中生效(见 config/config.go 的Rules结构与规则编译逻辑),编写规则时务必留意。
七、管理员用户
管理员(Admin)用户拥有特权操作权限。目前以下端点要求管理员权限:
POST /api/reindex:重建整个全文搜索索引;POST /api/cleanup:移除不再匹配所配置目录的本地文档,并删除没有任何当前文档引用的已存 HTML 与 favicon 文件。
非管理员用户调用仅管理员端点会收到403 Forbidden。端点到权限的映射在 server/endpoints.go 中实现:当userHandling开启且端点需要认证时,AdminOnly端点套用withAdminAuth,其余套用withUserAuth;而 server/server.go 的withAdminAuth会检查UserID == 0(未登录)与IsAdmin两个条件,任一不满足即返回 403。
管理员状态可在创建时授予(create-user --admin),也可随时切换(update-user --toggle-admin,底层为 server/model/user.go 的ToggleAdmin)。另外,多用户模式下管理员可通过X-Hister-Target-User-ID请求头以其他用户身份执行操作(server/server.go 的targetUserID)。
八、单用户兼容与存量数据迁移
Hister保留用户 ID0用于未认证(匿名)场景。未开启用户处理时索引的文档存储于用户 ID0之下,开启功能后这些文档仍然对所有已认证用户可见。这意味着你可以在既有实例上开启用户处理,而不会丢失对之前索引内容的访问。
若想把既有的全局文档收归某个特定用户私有,流程如下:
- 用
hister show-user USERNAME找到该用户的数字 ID; - 以管理员身份执行查询更新:
hister update 'user_id:0' --user-id USER_ID几点注意事项:
- 先加
--dry预览受影响的文档数量; - 当该用户已经拥有相同 URL 时,所有权冲突会被跳过;
- 被监视的本地文件(watched local files)需要目录配置中的
user值与新所有者一致——该配置字段见 config/config.go 的Directory.User,文件索引队列会依据它确定归属用户(server/indexer/files.go 的directoryUserID)。
九、文档隔离与搜索作用域
每个用户的已索引文档都携带其用户 ID 存储。搜索自动限定在以下范围内:
- 由当前已认证用户索引的文档;
- 未开启用户处理时索引的文档(用户 ID
0),它们作为共享的只读基线,对所有用户可见。
用户之间无法看到彼此的文档。首页显示的文档计数反映的是当前认证用户自己的文档数,而非所有用户的总数。
源码佐证:查询构建在 server/indexer/history.go 的latestDocumentsQuery中,当userID > 0时为查询附加user_id字段过滤,否则走全局查询;写入侧 server/indexer/files.go 的IndexFile接收userID参数并写入文档记录。单文件索引时以GetByURLAndUser(fileURL, userID)区分归属,确保同一 URL 在不同用户下可独立存在。
十、公共模式(Public Mode)
当app.public: true与用户处理同时启用时:
- 匿名访客只能搜索用户 ID
0下的全局文档;具名用户拥有的文档对匿名访客保持私有,仅对各自已认证用户可见; - 已认证用户依然可以按正常规则添加、删除、打标签、管理自己的内容,并访问自己的 Web 历史;
- Web 历史对匿名访客不可用(源码见 server/server.go 的
historyEnabled:!c.Config.App.Public || c.Authenticated)。
配置合法性校验在 config/config.go 的ValidatePublicMode:启用public时必须同时配置app.access_token或app.user_handling,否则报错app.public requires app.access_token or app.user_handling。
十一、个人访问令牌
每个用户账号都有一个用于 API 认证的个人访问令牌。令牌是随机生成的,存储在数据库中(server/model/user.go 使用crypto/rand的rand.Text(),RegenerateToken同理)。
- 从 Web UI 生成:Profile → Generate Token;命令行:
hister update-user --regen-token; - 生成新令牌会立即作废旧令牌——记得同步更新所有客户端(浏览器扩展、脚本);
show-user默认不显示令牌,需要--token标志才会揭示。
令牌认证路径在 server/model/user.go 的GetUserByToken中按明文 token 精确匹配数据库记录;为降低泄露风险,建议将令牌视为机密,妥善保管并在可能泄露时及时重新生成。
十二、安全考量汇总
- 密码:使用bcrypt加盐哈希后存储(默认成本),任何 API 都不会返回密码(
User.Password带json:"-"标签,server/model/user.go); - 浏览器 Cookie:只包含随机会话标识符;会话数据与标识符哈希存储在配置的 SQL 数据库中;登出会立即撤销数据库中的会话记录;
- 个人访问令牌:绕过会话 Cookie,可用于脚本。请保密保存,泄露后立即重新生成;
- OAuth state 令牌:单次使用的随机值,存储在服务端会话中,用于防止 OAuth 重定向流程中的跨站请求伪造(CSRF);
- OAuth 登录默认使用 S256 PKCE:私有 verifier 将授权请求绑定到其令牌交换,与 state 和提供方信息一起存储在服务端会话中。老旧提供方的兼容配置与升级行为见 configuration.md;
- OAuth 账号没有密码:管理员可用
hister update-user USERNAME --password为其分配密码;如需停用某个 OAuth 用户的访问,用hister delete-user删除账号,或从配置中移除对应提供方; - 强制 OAuth:启用
server.oauth_only: true可禁止密码认证;个人访问令牌对 API 与 CLI 依然有效。多用户模式下app.access_token必须包含某个用户的个人令牌; - 适用场景边界:用户处理面向同一实例上的可信用户群体(家庭、团队)。对公网部署,应将 Hister 置于带 HTTPS 的反向代理之后,并且只索引允许公开展示的内容。
十三、快速上手清单
- 在配置文件的
app段设置user_handling: true,按需配置server.oauth(可选)与app.public; - 重启 Hister 服务器;
- 在服务器主机上执行
hister create-user <USERNAME>(需要管理员就用--admin)创建首个账号; - 通过 Web 界面用用户名/密码或 OAuth 登录,并在 Profile 页生成个人访问令牌;
- 用
hister -t <your-token> search "query"或curl -H "X-Access-Token: <your-token>" ...验证 API 认证; - 按需使用
update-user --toggle-admin/--regen-token管理账号,用delete-user --purge彻底清理用户及其文档。
至此,你已经掌握了 Hister 多用户模式从配置、认证、命令管理到文档隔离与安全的完整体系。更进一步,可以结合 configuration.md 的 OAuth 与 PKCE 章节、data-lifecycle.md 的多用户所有权章节,以及 cmd/users.go 与 server/model/user.go 的源码继续深入。
- 搜索引擎
- 全文检索
- 后端
- 前端
- CLI
【免费下载链接】hister
Your own search engine
相关推荐
Ling-2.0到Ling-2.6的进化之路:万亿模型的架构迁移与优化策略
Ling 2.0到Ling 2.6的进化之路:万亿模型的架构迁移与优化策略 🚀 Ling 2.6 1T base 作为新一代万亿参数语言模型,代表了从Ling
分布式AI工程平台架构深度解析:Langfuse多语言支持与国际化部署实战
分布式AI工程平台架构深度解析:Langfuse多语言支持与国际化部署实战 Langfuse作为开源AI工程平台,为LLM应用提供全面的可观测性、评估、指标监控
人工智能LLMOps可观测性AI 评测LLM 网关后端前端一台电脑玩遍 800 多款联机游戏:Nucleus Co-op 本地分屏工具完整上手指南
一台电脑玩遍 800 多款联机游戏:Nucleus Co op 本地分屏工具完整上手指南 周末朋友突然上门,想一起打游戏,可客厅里只有一台电脑、一份游戏。这个画
游戏开发桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考