forked from wangziqi/gongxue-base
test: run launch persona smoke with app session
This commit is contained in:
@@ -607,7 +607,7 @@ $env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
|
|||||||
npm run smoke:launch-persona
|
npm run smoke:launch-persona
|
||||||
```
|
```
|
||||||
|
|
||||||
`smoke:launch-persona` 会从普通学生、租户管理员、平台管理员三个视角调用真实 API,覆盖 SVIP 后刷题、收藏、错题复习入口、租户数据看板/主题/学生/销售转化、平台租户/套餐/审计入口和越权拒绝。它会写入少量 `launch_persona_smoke` 测试记录,生产只建议在灰度或演练租户运行。
|
`smoke:launch-persona` 会从普通学生、租户管理员、平台管理员三个视角调用真实 API,默认使用 `LAUNCH_SMOKE_AUTH_MODE=app_session` 的 Bearer `tk_` session,不依赖旧 `x-user-id` 或平台本地 key;覆盖 SVIP 后刷题、收藏、错题复习入口、租户数据看板/主题/学生/销售转化、平台租户/套餐/审计入口和越权拒绝。它会写入少量 `launch_persona_smoke` 测试记录,生产只建议在灰度或演练租户运行。
|
||||||
|
|
||||||
Taro H5 发布产物守卫:
|
Taro H5 发布产物守卫:
|
||||||
|
|
||||||
|
|||||||
@@ -136,11 +136,12 @@
|
|||||||
{
|
{
|
||||||
"id": "api.launch-persona-smoke",
|
"id": "api.launch-persona-smoke",
|
||||||
"status": "pass",
|
"status": "pass",
|
||||||
"command": "npm run smoke:launch-persona > docs/refactor/launch-artifacts/launch-persona-smoke.log",
|
"command": "LAUNCH_SMOKE_AUTH_MODE=app_session npm run smoke:launch-persona > docs/refactor/launch-artifacts/launch-persona-smoke.log",
|
||||||
"completedAt": "2026-06-30T10:49:30+08:00",
|
"completedAt": "2026-06-30T10:49:30+08:00",
|
||||||
"artifact": "launch-artifacts/launch-persona-smoke.json",
|
"artifact": "launch-artifacts/launch-persona-smoke.json",
|
||||||
"summary": {
|
"summary": {
|
||||||
"status": "pass",
|
"status": "pass",
|
||||||
|
"authMode": "app_session",
|
||||||
"student": {
|
"student": {
|
||||||
"status": "pass",
|
"status": "pass",
|
||||||
"result": {
|
"result": {
|
||||||
@@ -163,8 +164,8 @@
|
|||||||
"status": "pass",
|
"status": "pass",
|
||||||
"result": {
|
"result": {
|
||||||
"guards": {
|
"guards": {
|
||||||
"studentDashboardDenied": "TENANT_MEMBER_REQUIRED",
|
"studentDashboardDenied": "TENANT_ADMIN_REQUIRED",
|
||||||
"crossTenantDenied": "TENANT_MEMBER_REQUIRED"
|
"crossTenantDenied": "AUTH_TENANT_MISMATCH"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ node scripts/taro-h5-release-guardrails-test.js --require-dist
|
|||||||
npm run smoke:launch-persona
|
npm run smoke:launch-persona
|
||||||
```
|
```
|
||||||
|
|
||||||
`smoke:launch-persona` 是真实 API 角色旅程烟测,必须进入生产上线证据:普通学生要能在 SVIP 权益下创建练习、答题、收藏题目、进入收藏复习和错题复习入口;租户管理员要能读取看板/主题/学生/销售转化并拒绝学生或跨租户访问;平台管理员要能读取租户/套餐/审计入口并拒绝学生访问平台后台。
|
`smoke:launch-persona` 是真实 API 角色旅程烟测,必须进入生产上线证据;正式证据必须使用 `LAUNCH_SMOKE_AUTH_MODE=app_session`,通过 Bearer `tk_` session 验证身份,不使用旧 `x-user-id` 或平台本地 key。普通学生要能在 SVIP 权益下创建练习、答题、收藏题目、进入收藏复习和错题复习入口;租户管理员要能读取看板/主题/学生/销售转化并拒绝学生或跨租户访问;平台管理员要能读取租户/套餐/审计入口并拒绝学生访问平台后台。
|
||||||
|
|
||||||
`smoke:taro:h5` 会启动临时静态服务器和 mock API,验证三套 H5 的 `index.html`、JS/CSS 资源、history fallback、公开 runtime config 和 `/api/tenant/resolve` 契约。`smoke:taro:h5:interaction` 会在真实 Chrome/Edge 中点击学生、租户后台、平台后台关键路径,覆盖静态烟测发现不了的 JS 运行时、直接 history 路由刷新和 Taro 点击事件问题;当前脚本覆盖 32 项检查,包括学生首页、题库、答题、收藏、错题/收藏复习、背单词、知识手册、资料短签名和水印、视频播放授权、分数线、AI 择校、消息中心、会员收银台下单/支付参数/订单状态,租户后台内容导入、公共题库采纳/同步/冲突处理、学生运营、营销/CRM/分佣、主题/角色/成员写操作,以及平台后台租户、账务、公共题库授权和员工写操作。`taro-h5-release-guardrails-test` 会扫描源码、三套 H5 产物和 runtime-config 边界,防止旧 PocketBase、`x-user-id`、`x-platform-admin-key`、数据库连接串和服务端密钥形态进入前端发布目录。若刚构建完但未放入真实 `runtime-config.json`,脚本允许 warning;正式部署目录必须补齐。
|
`smoke:taro:h5` 会启动临时静态服务器和 mock API,验证三套 H5 的 `index.html`、JS/CSS 资源、history fallback、公开 runtime config 和 `/api/tenant/resolve` 契约。`smoke:taro:h5:interaction` 会在真实 Chrome/Edge 中点击学生、租户后台、平台后台关键路径,覆盖静态烟测发现不了的 JS 运行时、直接 history 路由刷新和 Taro 点击事件问题;当前脚本覆盖 32 项检查,包括学生首页、题库、答题、收藏、错题/收藏复习、背单词、知识手册、资料短签名和水印、视频播放授权、分数线、AI 择校、消息中心、会员收银台下单/支付参数/订单状态,租户后台内容导入、公共题库采纳/同步/冲突处理、学生运营、营销/CRM/分佣、主题/角色/成员写操作,以及平台后台租户、账务、公共题库授权和员工写操作。`taro-h5-release-guardrails-test` 会扫描源码、三套 H5 产物和 runtime-config 边界,防止旧 PocketBase、`x-user-id`、`x-platform-admin-key`、数据库连接串和服务端密钥形态进入前端发布目录。若刚构建完但未放入真实 `runtime-config.json`,脚本允许 warning;正式部署目录必须补齐。
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
@@ -10,6 +11,8 @@ const databaseUrl = process.env.DATABASE_URL || DEFAULT_DATABASE_URL;
|
|||||||
const outputDir = process.env.LAUNCH_SMOKE_OUTPUT_DIR || 'docs/refactor/launch-artifacts';
|
const outputDir = process.env.LAUNCH_SMOKE_OUTPUT_DIR || 'docs/refactor/launch-artifacts';
|
||||||
const startServer = boolEnv('LAUNCH_SMOKE_START_SERVER', !process.env.LAUNCH_SMOKE_API_BASE);
|
const startServer = boolEnv('LAUNCH_SMOKE_START_SERVER', !process.env.LAUNCH_SMOKE_API_BASE);
|
||||||
const fixedPort = Number(process.env.LAUNCH_SMOKE_API_PORT || 0) || 0;
|
const fixedPort = Number(process.env.LAUNCH_SMOKE_API_PORT || 0) || 0;
|
||||||
|
const authMode = normalizeAuthMode(process.env.LAUNCH_SMOKE_AUTH_MODE || 'app_session');
|
||||||
|
const authSessionSecret = process.env.AUTH_SESSION_SECRET || 'development-session-secret-change-me';
|
||||||
|
|
||||||
const ids = {
|
const ids = {
|
||||||
tenant: '00000000-0000-0000-0000-000000000001',
|
tenant: '00000000-0000-0000-0000-000000000001',
|
||||||
@@ -24,6 +27,7 @@ const ids = {
|
|||||||
let apiBase = process.env.LAUNCH_SMOKE_API_BASE || process.env.API_BASE || '';
|
let apiBase = process.env.LAUNCH_SMOKE_API_BASE || process.env.API_BASE || '';
|
||||||
let serverProcess = null;
|
let serverProcess = null;
|
||||||
let serverLogs = '';
|
let serverLogs = '';
|
||||||
|
let authTokens = {};
|
||||||
|
|
||||||
function boolEnv(key, fallback) {
|
function boolEnv(key, fallback) {
|
||||||
const value = process.env[key];
|
const value = process.env[key];
|
||||||
@@ -31,6 +35,13 @@ function boolEnv(key, fallback) {
|
|||||||
return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase());
|
return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeAuthMode(value) {
|
||||||
|
const normalized = String(value || '').toLowerCase().replace(/[-_]/g, '');
|
||||||
|
if (normalized === 'legacy' || normalized === 'headers') return 'legacy';
|
||||||
|
if (normalized === 'appsession' || normalized === 'session' || normalized === 'tk') return 'app_session';
|
||||||
|
return 'app_session';
|
||||||
|
}
|
||||||
|
|
||||||
function nowIso() {
|
function nowIso() {
|
||||||
return new Date().toISOString();
|
return new Date().toISOString();
|
||||||
}
|
}
|
||||||
@@ -92,6 +103,9 @@ async function startServerIfNeeded() {
|
|||||||
...process.env,
|
...process.env,
|
||||||
PORT: String(port),
|
PORT: String(port),
|
||||||
DATABASE_URL: databaseUrl,
|
DATABASE_URL: databaseUrl,
|
||||||
|
AUTH_SESSION_SECRET: authSessionSecret,
|
||||||
|
ALLOW_LEGACY_AUTH_HEADERS: authMode === 'legacy' ? 'true' : 'false',
|
||||||
|
ALLOW_PLATFORM_ADMIN_KEY: authMode === 'legacy' ? 'true' : 'false',
|
||||||
MAX_JSON_BODY_BYTES: process.env.MAX_JSON_BODY_BYTES || '1048576',
|
MAX_JSON_BODY_BYTES: process.env.MAX_JSON_BODY_BYTES || '1048576',
|
||||||
MAX_IMPORT_JSON_BODY_BYTES: process.env.MAX_IMPORT_JSON_BODY_BYTES || '10485760',
|
MAX_IMPORT_JSON_BODY_BYTES: process.env.MAX_IMPORT_JSON_BODY_BYTES || '10485760',
|
||||||
},
|
},
|
||||||
@@ -120,12 +134,15 @@ function buildUrl(pathname, query = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function request(pathname, options = {}) {
|
async function request(pathname, options = {}) {
|
||||||
|
const token = authTokenFor(options.persona || 'student');
|
||||||
|
const useSession = authMode === 'app_session' && token;
|
||||||
const response = await fetch(buildUrl(pathname, options.query), {
|
const response = await fetch(buildUrl(pathname, options.query), {
|
||||||
method: options.method || 'GET',
|
method: options.method || 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
...(options.tenantId === false ? {} : { 'x-tenant-id': options.tenantId || ids.tenant }),
|
...(options.tenantId === false ? {} : { 'x-tenant-id': options.tenantId || ids.tenant }),
|
||||||
...(options.userId === false ? {} : { 'x-user-id': options.userId || ids.student }),
|
...(useSession ? { authorization: `Bearer ${token}` } : {}),
|
||||||
|
...(authMode === 'legacy' && options.userId !== false ? { 'x-user-id': options.userId || ids.student } : {}),
|
||||||
...(options.headers || {}),
|
...(options.headers || {}),
|
||||||
},
|
},
|
||||||
body: options.body ? JSON.stringify(options.body) : undefined,
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||||
@@ -136,6 +153,55 @@ async function request(pathname, options = {}) {
|
|||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSessionToken() {
|
||||||
|
return `tk_${crypto.randomBytes(32).toString('base64url')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashSessionToken(token) {
|
||||||
|
return crypto.createHmac('sha256', authSessionSecret).update(token).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function authTokenFor(persona) {
|
||||||
|
if (authMode !== 'app_session') return '';
|
||||||
|
return authTokens[persona] || authTokens.student || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSmokeAuthSession(pool, persona, userId, tenantId = ids.tenant) {
|
||||||
|
if (authMode !== 'app_session') return '';
|
||||||
|
const token = createSessionToken();
|
||||||
|
await pool.query(
|
||||||
|
`
|
||||||
|
insert into app_private.auth_sessions (
|
||||||
|
tenant_id, user_id, token_hash, provider, expires_at, metadata
|
||||||
|
)
|
||||||
|
values (
|
||||||
|
$1, $2, $3, 'launch_persona_smoke',
|
||||||
|
now() + interval '2 hours',
|
||||||
|
$4::jsonb
|
||||||
|
)
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
tenantId,
|
||||||
|
userId,
|
||||||
|
hashSessionToken(token),
|
||||||
|
JSON.stringify({ createdBy: 'launch-persona-smoke', persona, authMode }),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSmokeAuthSessions(pool) {
|
||||||
|
if (authMode !== 'app_session') {
|
||||||
|
authTokens = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
authTokens = {
|
||||||
|
student: await createSmokeAuthSession(pool, 'student', ids.student, ids.tenant),
|
||||||
|
tenantAdmin: await createSmokeAuthSession(pool, 'tenantAdmin', ids.tenantAdmin, ids.tenant),
|
||||||
|
platformAdmin: await createSmokeAuthSession(pool, 'platformAdmin', ids.platformAdmin, ids.tenant),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function one(pool, sql, params = []) {
|
async function one(pool, sql, params = []) {
|
||||||
const result = await pool.query(sql, params);
|
const result = await pool.query(sql, params);
|
||||||
return result.rows[0] || null;
|
return result.rows[0] || null;
|
||||||
@@ -316,22 +382,26 @@ async function studentJourney(pool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function tenantAdminJourney() {
|
async function tenantAdminJourney() {
|
||||||
const overview = await request('/api/tenant-admin/overview', { userId: ids.tenantAdmin });
|
const overview = await request('/api/tenant-admin/overview', { persona: 'tenantAdmin', userId: ids.tenantAdmin });
|
||||||
const dashboard = await request('/api/tenant-admin/dashboard', {
|
const dashboard = await request('/api/tenant-admin/dashboard', {
|
||||||
|
persona: 'tenantAdmin',
|
||||||
userId: ids.tenantAdmin,
|
userId: ids.tenantAdmin,
|
||||||
query: { timeRange: '7d', regionId: ids.region },
|
query: { timeRange: '7d', regionId: ids.region },
|
||||||
});
|
});
|
||||||
const theme = await request('/api/tenant-admin/theme', { userId: ids.tenantAdmin });
|
const theme = await request('/api/tenant-admin/theme', { persona: 'tenantAdmin', userId: ids.tenantAdmin });
|
||||||
const students = await request('/api/tenant-admin/students', {
|
const students = await request('/api/tenant-admin/students', {
|
||||||
|
persona: 'tenantAdmin',
|
||||||
userId: ids.tenantAdmin,
|
userId: ids.tenantAdmin,
|
||||||
query: { limit: 10 },
|
query: { limit: 10 },
|
||||||
});
|
});
|
||||||
const conversion = await request('/api/referral/conversion-report', {
|
const conversion = await request('/api/referral/conversion-report', {
|
||||||
|
persona: 'tenantAdmin',
|
||||||
userId: ids.tenantAdmin,
|
userId: ids.tenantAdmin,
|
||||||
query: { limit: 10 },
|
query: { limit: 10 },
|
||||||
});
|
});
|
||||||
const studentDenied = await request('/api/tenant-admin/dashboard', { expectStatus: 403 });
|
const studentDenied = await request('/api/tenant-admin/dashboard', { expectStatus: 403 });
|
||||||
const crossTenantDenied = await request('/api/tenant-admin/dashboard', {
|
const crossTenantDenied = await request('/api/tenant-admin/dashboard', {
|
||||||
|
persona: 'tenantAdmin',
|
||||||
tenantId: ids.partnerTenant,
|
tenantId: ids.partnerTenant,
|
||||||
userId: ids.tenantAdmin,
|
userId: ids.tenantAdmin,
|
||||||
expectStatus: 403,
|
expectStatus: 403,
|
||||||
@@ -360,24 +430,28 @@ async function tenantAdminJourney() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function platformAdminJourney() {
|
async function platformAdminJourney() {
|
||||||
const headers = { 'x-platform-admin-key': process.env.PLATFORM_ADMIN_API_KEY || 'local-platform-admin-key' };
|
const headers = authMode === 'legacy' ? { 'x-platform-admin-key': process.env.PLATFORM_ADMIN_API_KEY || 'local-platform-admin-key' } : {};
|
||||||
const overview = await request('/api/platform-admin/overview', {
|
const overview = await request('/api/platform-admin/overview', {
|
||||||
|
persona: 'platformAdmin',
|
||||||
tenantId: false,
|
tenantId: false,
|
||||||
userId: false,
|
userId: false,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
const tenants = await request('/api/platform-admin/tenants', {
|
const tenants = await request('/api/platform-admin/tenants', {
|
||||||
|
persona: 'platformAdmin',
|
||||||
tenantId: false,
|
tenantId: false,
|
||||||
userId: false,
|
userId: false,
|
||||||
headers,
|
headers,
|
||||||
query: { limit: 10 },
|
query: { limit: 10 },
|
||||||
});
|
});
|
||||||
const plans = await request('/api/platform-admin/plans', {
|
const plans = await request('/api/platform-admin/plans', {
|
||||||
|
persona: 'platformAdmin',
|
||||||
tenantId: false,
|
tenantId: false,
|
||||||
userId: false,
|
userId: false,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
const auditAlerts = await request('/api/platform-admin/audit-alerts', {
|
const auditAlerts = await request('/api/platform-admin/audit-alerts', {
|
||||||
|
persona: 'platformAdmin',
|
||||||
tenantId: false,
|
tenantId: false,
|
||||||
userId: false,
|
userId: false,
|
||||||
headers,
|
headers,
|
||||||
@@ -450,12 +524,14 @@ async function runStep(name, fn) {
|
|||||||
async function main() {
|
async function main() {
|
||||||
const pool = new pg.Pool({ connectionString: databaseUrl, max: 4 });
|
const pool = new pg.Pool({ connectionString: databaseUrl, max: 4 });
|
||||||
try {
|
try {
|
||||||
|
await createSmokeAuthSessions(pool);
|
||||||
await startServerIfNeeded();
|
await startServerIfNeeded();
|
||||||
if (!apiBase) throw new Error('LAUNCH_SMOKE_API_BASE is required when LAUNCH_SMOKE_START_SERVER=false.');
|
if (!apiBase) throw new Error('LAUNCH_SMOKE_API_BASE is required when LAUNCH_SMOKE_START_SERVER=false.');
|
||||||
const report = {
|
const report = {
|
||||||
startedAt: nowIso(),
|
startedAt: nowIso(),
|
||||||
finishedAt: '',
|
finishedAt: '',
|
||||||
apiBase,
|
apiBase,
|
||||||
|
authMode,
|
||||||
databaseUrl: databaseUrl.replace(/:\/\/([^:]+):([^@]+)@/, '://$1:[redacted]@'),
|
databaseUrl: databaseUrl.replace(/:\/\/([^:]+):([^@]+)@/, '://$1:[redacted]@'),
|
||||||
student: await runStep('student', () => studentJourney(pool)),
|
student: await runStep('student', () => studentJourney(pool)),
|
||||||
tenantAdmin: await runStep('tenant-admin', tenantAdminJourney),
|
tenantAdmin: await runStep('tenant-admin', tenantAdminJourney),
|
||||||
|
|||||||
@@ -200,6 +200,18 @@ assert.ok(
|
|||||||
'launch persona SVIP failure should be reported as a blocker',
|
'launch persona SVIP failure should be reported as a blocker',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const launchPersonaLegacyAuth = runGate(tempDir => {
|
||||||
|
const evidence = createEvidence(tempDir);
|
||||||
|
const item = evidence.checks.find(check => check.id === 'api.launch-persona-smoke');
|
||||||
|
item.summary.authMode = 'legacy';
|
||||||
|
return evidence;
|
||||||
|
});
|
||||||
|
assert.notEqual(launchPersonaLegacyAuth.status, 0, 'launch persona smoke with legacy auth should fail launch gate');
|
||||||
|
assert.ok(
|
||||||
|
launchPersonaLegacyAuth.payload.checks?.some(item => item.id === 'check.api.launch-persona-smoke.summary' && item.status === 'blocker'),
|
||||||
|
'launch persona legacy auth should be reported as a blocker',
|
||||||
|
);
|
||||||
|
|
||||||
const missingBusinessSampling = runGate(tempDir => {
|
const missingBusinessSampling = runGate(tempDir => {
|
||||||
const evidence = createEvidence(tempDir);
|
const evidence = createEvidence(tempDir);
|
||||||
evidence.checks = evidence.checks.filter(item => item.id !== 'migration.pb-import-sample');
|
evidence.checks = evidence.checks.filter(item => item.id !== 'migration.pb-import-sample');
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ const gateChecks = [
|
|||||||
commandIncludes: 'smoke:launch-persona',
|
commandIncludes: 'smoke:launch-persona',
|
||||||
summary: {
|
summary: {
|
||||||
status: 'pass',
|
status: 'pass',
|
||||||
|
authMode: 'app_session',
|
||||||
'student.status': 'pass',
|
'student.status': 'pass',
|
||||||
'student.result.entitlement.isSvip': true,
|
'student.result.entitlement.isSvip': true,
|
||||||
'student.result.practice.questionCount': { gte: 1 },
|
'student.result.practice.questionCount': { gte: 1 },
|
||||||
@@ -109,8 +110,8 @@ const gateChecks = [
|
|||||||
'student.result.favorite.reviewSessionId': { truthy: true },
|
'student.result.favorite.reviewSessionId': { truthy: true },
|
||||||
'student.result.wrongReview.reviewPlanCount': { gte: 0 },
|
'student.result.wrongReview.reviewPlanCount': { gte: 0 },
|
||||||
'tenantAdmin.status': 'pass',
|
'tenantAdmin.status': 'pass',
|
||||||
'tenantAdmin.result.guards.studentDashboardDenied': { oneOf: ['TENANT_MEMBER_REQUIRED', 'TENANT_PERMISSION_DENIED', 'FORBIDDEN'] },
|
'tenantAdmin.result.guards.studentDashboardDenied': { oneOf: ['TENANT_ADMIN_REQUIRED', 'TENANT_MEMBER_REQUIRED', 'TENANT_PERMISSION_DENIED', 'FORBIDDEN'] },
|
||||||
'tenantAdmin.result.guards.crossTenantDenied': { oneOf: ['TENANT_MEMBER_REQUIRED', 'TENANT_PERMISSION_DENIED', 'FORBIDDEN'] },
|
'tenantAdmin.result.guards.crossTenantDenied': { oneOf: ['AUTH_TENANT_MISMATCH', 'TENANT_MEMBER_REQUIRED', 'TENANT_PERMISSION_DENIED', 'FORBIDDEN'] },
|
||||||
'platformAdmin.status': 'pass',
|
'platformAdmin.status': 'pass',
|
||||||
'platformAdmin.result.guards.studentPlatformDenied': { oneOf: ['PLATFORM_ADMIN_REQUIRED', 'FORBIDDEN'] },
|
'platformAdmin.result.guards.studentPlatformDenied': { oneOf: ['PLATFORM_ADMIN_REQUIRED', 'FORBIDDEN'] },
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user