feat(编辑角色): editor 只能写并发布自己的文章
- D1 users 加 role 列(''/editor/admin),与 is_admin 成对写入 - Worker routes/editor.ts 放行 admin+editor,反代注入 X-Editor-Uid/User/Role(昵称 encodeURIComponent) - editor-api 引入 identify():身份取自注入头或本机会话;文章归属记 frontmatter author_id - 编辑发布改精确 pathspec(只提交自己文章目录),管理员保持全量;空 pathspec 显式拦截 - 国内机直连登录改为转发 CF /user/access_token 校验,CF 不可达回退本机管理员 - handoff 签名覆盖身份(ts/uid/name/role),防编辑一键跳转变管理员 - admin.js:编辑只渲染「文章编辑」tab、作者框只读;用户管理加角色下拉 + 新建用户
This commit is contained in:
1 parent
57dbfe6968
commit
39677e7b40
14 files changed
+1172
-139
No files matched your search
@@ -13,6 +13,7 @@ import type {
|
||||
import { findPagesByKeys, findUsersByIds, siteFirstUrl } from './db';
|
||||
import { renderMarkdown } from './md';
|
||||
import { md5Lower } from './md5';
|
||||
import { roleOf } from './role';
|
||||
import { formatDateCN } from './util';
|
||||
|
||||
// ============================================================================
|
||||
@@ -126,6 +127,7 @@ export function cookUser(u: UserRow): CookedUser {
|
||||
badge_color: u.badge_color || '',
|
||||
title_name: u.title_name || '',
|
||||
is_admin: !!u.is_admin,
|
||||
role: roleOf(u),
|
||||
receive_email: !!u.receive_email,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 后台角色。
|
||||
*
|
||||
* 为什么单独一个小模块:`cook.ts`(输出用户结构)、`session.ts`(会话)、
|
||||
* `routes/editor.ts`(编辑器鉴权)、`routes/admin.ts`(用户管理)都要判角色,
|
||||
* 谁 import 谁都不合适 —— 放这里谁都能用,也不会绕出循环依赖。
|
||||
*
|
||||
* 两处事实源的关系:
|
||||
* · `users.is_admin` 是历史字段,评论/通知/设置等一大堆既有逻辑都在用它 —— 不动。
|
||||
* · `users.role` 是这次新增的('' 普通评论用户 | 'editor' 编辑 | 'admin' 管理员),
|
||||
* 只有它才能表达「编辑」这一级。
|
||||
* · 两者必须同步:role='admin' ⇔ is_admin=1。写入走 `normalizeRole()`,
|
||||
* 读取走 `roleOf()`(以 is_admin 优先,兜住没同步过的历史行)。
|
||||
*/
|
||||
import type { UserRole } from '../types';
|
||||
|
||||
/** 判角色只需要这两个字段,别把整个 UserRow 拖进来 */
|
||||
export interface RoleCarrier {
|
||||
is_admin?: number | boolean | null;
|
||||
role?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 归一化读取:只认三种结果。
|
||||
* ★ is_admin 优先 —— 历史行可能没有 role 列的值(ALTER 之前建的账号),
|
||||
* 但 is_admin=1 是确定的,不能因为 role 是空串就把老管理员降级。
|
||||
*/
|
||||
export function roleOf(u: RoleCarrier | null | undefined): UserRole {
|
||||
if (!u) return 'user';
|
||||
if (u.is_admin) return 'admin';
|
||||
return String(u.role || '') === 'editor' ? 'editor' : 'user';
|
||||
}
|
||||
|
||||
/** 写入方向:把前端传来的一对(role / is_admin)折叠成一个规范值 */
|
||||
export function normalizeRole(role: unknown, isAdmin: unknown): 'admin' | 'editor' | '' {
|
||||
const r = String(role ?? '').trim().toLowerCase();
|
||||
if (r === 'admin' || r === 'editor') return r;
|
||||
if (r === '' && isAdmin === undefined) return '';
|
||||
// 没给 role(老前端只给 is_admin)时按 is_admin 推
|
||||
if (role === undefined || role === null) return isAdmin ? 'admin' : '';
|
||||
// 给了个不认识的值:不猜,退回 is_admin 的语义
|
||||
return isAdmin ? 'admin' : '';
|
||||
}
|
||||
|
||||
/** 能进写作后台的角色(管理员 + 编辑) */
|
||||
export function canWritePosts(u: RoleCarrier | null | undefined): boolean {
|
||||
const r = roleOf(u);
|
||||
return r === 'admin' || r === 'editor';
|
||||
}
|
||||
|
||||
/** 编辑角色在文章维度上只能碰自己的(管理员不受限) */
|
||||
export function isScopedEditor(u: RoleCarrier | null | undefined): boolean {
|
||||
return roleOf(u) === 'editor';
|
||||
}
|
||||
|
||||
/** 给前端展示的角色名(后台用户管理列表用) */
|
||||
export function roleLabel(role: UserRole): string {
|
||||
return role === 'admin' ? '管理员' : role === 'editor' ? '编辑' : '';
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Env, SessionUser, UserRow } from '../types';
|
||||
import { findUserByEmail, findUserById, getAdminUsers } from './db';
|
||||
import { md5 } from './md5';
|
||||
import { roleOf } from './role';
|
||||
import { now } from './util';
|
||||
|
||||
// ================================================================= 编解码
|
||||
@@ -166,7 +167,7 @@ export async function userFromToken(env: Env, authHeader: string | null): Promis
|
||||
|
||||
export function sessionOf(user: UserRow | null): SessionUser | null {
|
||||
if (!user) return null;
|
||||
return { id: user.id, name: user.name, email: user.email, isAdmin: !!user.is_admin };
|
||||
return { id: user.id, name: user.name, email: user.email, isAdmin: !!user.is_admin, role: roleOf(user) };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user