fix(course-package): extract role_key from JWT sub roles objects

JWT sub.roles is an array of objects with role_key field, not plain
strings. Fix extractRoleKeys to map role_key from each role object.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 08:41:54 +08:00
parent 1ddeb21ca7
commit 894ca05566
2 changed files with 25 additions and 12 deletions
+3
View File
@@ -7,5 +7,8 @@ HUIKE_ADMIN_BASE_URL=https://admin.moicen.com
MOICEN_ADMIN_USER= MOICEN_ADMIN_USER=
MOICEN_ADMIN_PASSWORD= MOICEN_ADMIN_PASSWORD=
# 课包(course_package)测试依赖 MOICEN_E2E_UNIONID(共用 teacher/student unionid
# 测试通过 JWT roles 验证只有 TEACHER/SUPERVISOR 角色可访问课包 API。
# 可选:任意 HTTP 200 健康检查 URLCI 可在仓库 Settings → Variables 配置 MOICEN_HEALTHCHECK_URL # 可选:任意 HTTP 200 健康检查 URLCI 可在仓库 Settings → Variables 配置 MOICEN_HEALTHCHECK_URL
MOICEN_HEALTHCHECK_URL= MOICEN_HEALTHCHECK_URL=
+22 -12
View File
@@ -6,21 +6,31 @@ const kcBase =
process.env.HUIKE_ADMIN_BASE_URL?.trim() || 'https://admin.moicen.com'; process.env.HUIKE_ADMIN_BASE_URL?.trim() || 'https://admin.moicen.com';
/** /**
* 从 JWT payload 中提取 roles 数组(可能嵌在 `sub` JSON 字段内)。 * 从 JWT payload 中提取 role_key 字符串数组(可能嵌在 `sub` JSON 字段内)。
* roles 是对象数组(含 `role_key` 字段),也可能在顶层。
*/ */
function extractRoles(payload: Record<string, unknown> | null): string[] { function extractRoleKeys(payload: Record<string, unknown> | null): string[] {
if (!payload) return []; if (!payload) return [];
if (Array.isArray(payload.roles)) return payload.roles as string[]; const rawRoles: unknown[] =
const subRaw = payload.sub; (Array.isArray(payload.roles) ? payload.roles : []) as unknown[];
if (typeof subRaw === 'string') { if (rawRoles.length === 0) {
try { const subRaw = payload.sub;
const sub = JSON.parse(subRaw) as Record<string, unknown>; if (typeof subRaw === 'string') {
if (Array.isArray(sub.roles)) return sub.roles as string[]; try {
} catch { const sub = JSON.parse(subRaw) as Record<string, unknown>;
/* 非 JSON sub,忽略 */ if (Array.isArray(sub.roles)) {
rawRoles.push(...(sub.roles as unknown[]));
}
} catch {
/* 非 JSON sub,忽略 */
}
} }
} }
return []; return rawRoles
.map((r) =>
typeof r === 'string' ? r : (r as Record<string, unknown>)?.role_key as string | undefined,
)
.filter((k): k is string => typeof k === 'string' && k.length > 0);
} }
test.describe('课包(course_package', () => { test.describe('课包(course_package', () => {
@@ -61,7 +71,7 @@ test.describe('课包(course_package', () => {
const payload = decodeJwtPayload(authToken!); const payload = decodeJwtPayload(authToken!);
expect(payload, 'JWT 应可解码').not.toBeNull(); expect(payload, 'JWT 应可解码').not.toBeNull();
const roles = extractRoles(payload); const roles = extractRoleKeys(payload);
const hasCoursePkgRole = roles.some((r) => const hasCoursePkgRole = roles.some((r) =>
['TEACHER', 'SUPERVISOR'].includes(r), ['TEACHER', 'SUPERVISOR'].includes(r),
); );