Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pac
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tools
pac
Commits
2c1e4a14
Commit
2c1e4a14
authored
Oct 13, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Plain Diff
Merge Friday host auth policy fix into test
parents
6939bd13
1211c4ab
Pipeline
#3780
failed in 0 seconds
Changes
7
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
314 additions
and
0 deletions
+314
-0
apps/pac-docs/content/docs/integration/auth-login.mdx
+4
-0
apps/pac-service/data/friday/manifest.yaml
+6
-0
apps/pac-service/src/modules/auth/auth.service.ts
+19
-0
apps/pac-service/src/modules/auth/host-auth-policy.ts
+62
-0
apps/pac-service/src/modules/sync/cold-import/manifest.schema.ts
+10
-0
apps/pac-service/tests/host-auth-exchange.spec.ts
+109
-0
apps/pac-service/tests/host-auth-policy.spec.ts
+104
-0
No files found.
apps/pac-docs/content/docs/integration/auth-login.mdx
View file @
2c1e4a14
...
@@ -68,6 +68,10 @@ iframe 内的 token 落地与刷新由 PAC 前端完成。`refreshToken` 过期(
...
@@ -68,6 +68,10 @@ iframe 内的 token 落地与刷新由 PAC 前端完成。`refreshToken` 过期(
| `user.orgScope` | string[] | ✅ | 数据权限:用户可达的组织节点 id 列表;传 `[]` = 全集团不限。见下「`tenantId` 与 `orgScope`」 |
| `user.orgScope` | string[] | ✅ | 数据权限:用户可达的组织节点 id 列表;传 `[]` = 全集团不限。见下「`tenantId` 与 `orgScope`」 |
| `user.dictionary` | object | 选填 | `id → 名` 映射,仅用于页面把 id 显示成名字,**不影响权限**。含 `clinics`、`users` 两个子字典。建议提供,否则页面显示原始 id |
| `user.dictionary` | object | 选填 | `id → 名` 映射,仅用于页面把 id 显示成名字,**不影响权限**。含 `clinics`、`users` 两个子字典。建议提供,否则页面显示原始 id |
**Friday SaaS 换票边界**:服务端 `data/friday/manifest.yaml` 的 `auth` 策略限制最高角色为 `leader`,组织范围不能为空或包含空白项;数据租户必须为 `friday-market`,范围不能包含市场根 `friday-market`。品牌管理员应传本品牌节点,诊所用户传所属诊所节点。拒绝返回业务码 `10107`,不会签发令牌。策略同样用于刷新令牌;Friday 配置缺失或损坏时拒绝签发。未配置策略的其他宿主保持原行为(如 jvs-dw 集团管理员)。
宿主后端仍须核实用户身份、角色及所传品牌/诊所归属;PAC 这道上限校验不替代宿主用户授权。Friday 跨品牌运营账号不能复用普通品牌换票入口。已有 access token 和短期换票 code 不会被本次修改主动撤销,生效前如需立即收回旧授权,须另行安排令牌撤销;超范围 refresh token 无法续期。本次不改变开发用 mock-login 的入口权限,生产应关闭模拟登录。
> `orgScope` 字段本身必传(哪怕是空数组);请求体为严格模式,未定义的字段会被拒绝。
> `orgScope` 字段本身必传(哪怕是空数组);请求体为严格模式,未定义的字段会被拒绝。
**`role`** — 功能权限:
**`role`** — 功能权限:
...
...
apps/pac-service/data/friday/manifest.yaml
View file @
2c1e4a14
...
@@ -17,6 +17,12 @@
...
@@ -17,6 +17,12 @@
# pnpm sync -- --dir=./data/friday --dry-run # 看翻译效果,不写库
# pnpm sync -- --dir=./data/friday --dry-run # 看翻译效果,不写库
# pnpm sync -- --dir=./data/friday --no-recompute
# pnpm sync -- --dir=./data/friday --no-recompute
# 普通宿主换票只允许品牌/诊所范围;跨品牌运营不得复用此入口。
auth
:
require_org_scope
:
true
max_role
:
leader
forbid_root_scope
:
true
host_name
:
friday
host_name
:
friday
display_name
:
FRIDAY SaaS(多品牌市场)
display_name
:
FRIDAY SaaS(多品牌市场)
auto_sync
:
false
auto_sync
:
false
...
...
apps/pac-service/src/modules/auth/auth.service.ts
View file @
2c1e4a14
...
@@ -27,6 +27,7 @@ import { AlertService } from '../../common/alerting/alert.service';
...
@@ -27,6 +27,7 @@ import { AlertService } from '../../common/alerting/alert.service';
import
{
parseDurationToSeconds
}
from
'./duration'
;
import
{
parseDurationToSeconds
}
from
'./duration'
;
import
{
loadMockUsers
}
from
'./mock-users'
;
import
{
loadMockUsers
}
from
'./mock-users'
;
import
{
OrgTreeService
}
from
'./org-tree'
;
import
{
OrgTreeService
}
from
'./org-tree'
;
import
{
checkHostAuthPolicy
,
loadHostAuthPolicy
}
from
'./host-auth-policy'
;
const
CODE_PREFIX
=
'pac:exchange-code:'
;
const
CODE_PREFIX
=
'pac:exchange-code:'
;
/// code 失效策略:不再"消费一次即删",改由 TTL(EXCHANGE_CODE_TTL_SECONDS)控制 ——
/// code 失效策略:不再"消费一次即删",改由 TTL(EXCHANGE_CODE_TTL_SECONDS)控制 ——
...
@@ -145,6 +146,8 @@ export class AuthService {
...
@@ -145,6 +146,8 @@ export class AuthService {
throw
new
BizError
(
ApiCode
.
AUTH_INVALID_CREDENTIALS
);
throw
new
BizError
(
ApiCode
.
AUTH_INVALID_CREDENTIALS
);
}
}
this
.
assertHostAuthPolicy
(
host
.
name
,
req
.
user
);
// 契约 user 上下文 → token,与 mockLogin 共用同一条 issueTokens(展开 + 签发)。
// 契约 user 上下文 → token,与 mockLogin 共用同一条 issueTokens(展开 + 签发)。
const
{
accessToken
,
refreshToken
}
=
await
this
.
issueTokens
(
host
,
req
.
user
);
const
{
accessToken
,
refreshToken
}
=
await
this
.
issueTokens
(
host
,
req
.
user
);
...
@@ -493,6 +496,10 @@ export class AuthService {
...
@@ -493,6 +496,10 @@ export class AuthService {
const stored = await this.redis.get(REFRESH_PREFIX + payload.jti);
const stored = await this.redis.get(REFRESH_PREFIX + payload.jti);
if (!stored) throw new BizError(ApiCode.AUTH_INVALID_REFRESH_TOKEN, 'refresh token revoked');
if (!stored) throw new BizError(ApiCode.AUTH_INVALID_REFRESH_TOKEN, 'refresh token revoked');
const host = await this.prisma.host.findUnique({ where: { id: payload.hostId } });
if (!host?.active) throw new BizError(ApiCode.AUTH_INVALID_REFRESH_TOKEN);
this.assertHostAuthPolicy(host.name, payload);
// ── 步骤 3:重签 access token(payload 里有完整 user 上下文)──
// ── 步骤 3:重签 access token(payload 里有完整 user 上下文)──
const permissions = this.resolvePermissions(payload.role);
const permissions = this.resolvePermissions(payload.role);
const accessToken = await this.signAccessToken({
const accessToken = await this.signAccessToken({
...
@@ -550,6 +557,18 @@ export class AuthService {
...
@@ -550,6 +557,18 @@ export class AuthService {
return token;
return token;
}
}
private assertHostAuthPolicy(hostName: string, user: { role: string; orgScope: string[]; tenantId: string }): void {
let violation: string | null;
try {
violation = checkHostAuthPolicy(loadHostAuthPolicy(hostName), user);
} catch {
// 配置损坏也拒绝签发;日志不包含凭据或用户资料。
this.logger.error(`
宿主换票策略不可用
host
=
$
{
hostName
}
`);
throw new BizError(ApiCode.AUTH_PERMISSION_DENIED, '宿主权限配置不可用');
}
if (violation) throw new BizError(ApiCode.AUTH_PERMISSION_DENIED, violation);
}
resolvePermissions(role: UserRole): Permission[] {
resolvePermissions(role: UserRole): Permission[] {
return ROLE_PERMISSIONS[role];
return ROLE_PERMISSIONS[role];
}
}
...
...
apps/pac-service/src/modules/auth/host-auth-policy.ts
0 → 100644
View file @
2c1e4a14
import
*
as
fs
from
'node:fs'
;
import
*
as
path
from
'node:path'
;
import
*
as
yaml
from
'js-yaml'
;
import
{
z
}
from
'zod'
;
import
{
UserRole
}
from
'@pac/types'
;
import
{
ManifestAuthSchema
}
from
'../sync/cold-import/manifest.schema'
;
const
PolicyFileSchema
=
z
.
object
({
host_name
:
z
.
string
(),
tenant_id
:
z
.
string
().
min
(
1
).
optional
(),
auth
:
ManifestAuthSchema
.
optional
(),
});
export
type
HostAuthPolicy
=
{
auth
:
z
.
infer
<
typeof
ManifestAuthSchema
>
;
tenantId
?:
string
};
const
ROLE_RANK
:
readonly
string
[]
=
[
UserRole
.
STAFF
,
UserRole
.
LEADER
,
UserRole
.
ADMIN
];
/** 只读取服务端部署的配置;Friday 缺配置时拒绝签发,不能退回无限制。 */
export
function
loadHostAuthPolicy
(
hostName
:
string
):
HostAuthPolicy
{
if
(
!
/^
[
a-zA-Z0-9_-
]
+$/
.
test
(
hostName
))
throw
new
Error
(
'Invalid host name'
);
const
dir
=
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
??
path
.
resolve
(
process
.
cwd
(),
'data'
);
const
file
=
path
.
join
(
dir
,
hostName
,
'manifest.yaml'
);
if
(
!
fs
.
existsSync
(
file
))
{
if
(
hostName
===
'friday'
)
throw
new
Error
(
'Friday auth policy missing'
);
return
{
auth
:
ManifestAuthSchema
.
parse
({})
};
}
const
config
=
PolicyFileSchema
.
parse
(
yaml
.
load
(
fs
.
readFileSync
(
file
,
'utf8'
)));
if
(
config
.
host_name
!==
hostName
)
throw
new
Error
(
'Host auth policy mismatch'
);
if
(
hostName
===
'friday'
&&
(
!
config
.
auth
||
!
config
.
tenant_id
))
{
throw
new
Error
(
'Friday auth policy incomplete'
);
}
const
auth
=
ManifestAuthSchema
.
parse
(
config
.
auth
??
{});
if
(
hostName
===
'friday'
&&
(
!
auth
.
require_org_scope
||
!
auth
.
forbid_root_scope
||
auth
.
max_role
===
'admin'
)
)
{
throw
new
Error
(
'Friday auth policy must retain brand isolation'
);
}
if
(
auth
.
forbid_root_scope
&&
!
config
.
tenant_id
)
throw
new
Error
(
'Root scope policy requires tenant_id'
);
return
{
auth
,
tenantId
:
config
.
tenant_id
};
}
export
function
checkHostAuthPolicy
(
policy
:
HostAuthPolicy
,
user
:
{
role
:
string
;
orgScope
:
string
[];
tenantId
:
string
},
):
string
|
null
{
const
{
auth
}
=
policy
;
if
(
ROLE_RANK
.
indexOf
(
user
.
role
)
<
0
||
ROLE_RANK
.
indexOf
(
user
.
role
)
>
ROLE_RANK
.
indexOf
(
auth
.
max_role
)
)
{
return
'宿主不允许签发该角色'
;
}
if
(
auth
.
require_org_scope
&&
(
!
user
.
orgScope
.
length
||
user
.
orgScope
.
some
((
id
)
=>
!
id
.
trim
())))
{
return
'该宿主必须提供非空组织范围'
;
}
if
(
auth
.
forbid_root_scope
)
{
if
(
user
.
tenantId
!==
policy
.
tenantId
)
return
'宿主数据租户不匹配'
;
if
(
user
.
orgScope
.
some
((
id
)
=>
id
.
trim
()
===
policy
.
tenantId
))
return
'该宿主不允许市场根范围'
;
}
return
null
;
}
apps/pac-service/src/modules/sync/cold-import/manifest.schema.ts
View file @
2c1e4a14
import
{
z
}
from
'zod'
;
import
{
z
}
from
'zod'
;
import
{
UserRoleSchema
}
from
'@pac/types'
;
import
{
TransformsSchema
}
from
'../transforms/transforms.schema'
;
import
{
TransformsSchema
}
from
'../transforms/transforms.schema'
;
/**
/**
...
@@ -152,10 +153,19 @@ export const DEFAULT_REVERSE_PULL_FROM = [
...
@@ -152,10 +153,19 @@ export const DEFAULT_REVERSE_PULL_FROM = [
]
as
const
;
]
as
const
;
export
type
ClickHouseSource
=
z
.
infer
<
typeof
ClickHouseSourceSchema
>
;
export
type
ClickHouseSource
=
z
.
infer
<
typeof
ClickHouseSourceSchema
>
;
/** 宿主换票上限;组织属于哪个用户仍由宿主服务端核实。 */
export
const
ManifestAuthSchema
=
z
.
object
({
require_org_scope
:
z
.
boolean
().
default
(
false
),
max_role
:
UserRoleSchema
.
default
(
'admin'
),
forbid_root_scope
:
z
.
boolean
().
default
(
false
),
}).
strict
();
export
type
ManifestAuth
=
z
.
infer
<
typeof
ManifestAuthSchema
>
;
export
const
ColdImportManifestSchema
=
z
export
const
ColdImportManifestSchema
=
z
.
object
({
.
object
({
/// 关联到 PAC hosts.name(slug,平台必须先 seed/admin 注册)
/// 关联到 PAC hosts.name(slug,平台必须先 seed/admin 注册)
host_name
:
z
.
string
().
min
(
1
),
host_name
:
z
.
string
().
min
(
1
),
auth
:
ManifestAuthSchema
.
optional
(),
display_name
:
z
.
string
().
optional
(),
display_name
:
z
.
string
().
optional
(),
/// 是否纳入每日自动增量同步(SyncIncrementalSchedulerService 启动时自动发现)。
/// 是否纳入每日自动增量同步(SyncIncrementalSchedulerService 启动时自动发现)。
...
...
apps/pac-service/tests/host-auth-exchange.spec.ts
0 → 100644
View file @
2c1e4a14
import
{
join
}
from
'node:path'
;
import
{
hashSecret
}
from
'@pac/utils'
;
import
{
ApiCode
,
UserRole
}
from
'@pac/types'
;
import
{
AuthService
}
from
'../src/modules/auth/auth.service'
;
// 只替换外部依赖;真实换票/刷新逻辑、密钥校验、manifest 读取和策略均执行。
describe
(
'host policy in token issuance and refresh'
,
()
=>
{
const
original
=
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
;
const
valid
=
{
userId
:
'u1'
,
tenantId
:
'friday-market'
,
role
:
UserRole
.
LEADER
,
orgScope
:
[
'clinic-a'
],
};
const
host
=
{
id
:
'host1'
,
name
:
'friday'
,
active
:
true
,
appSecretHash
:
hashSecret
(
'test-secret'
),
};
let
service
:
AuthService
;
let
prisma
:
any
,
redis
:
any
,
jwt
:
any
;
beforeEach
(()
=>
{
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
=
join
(
__dirname
,
'../data'
);
prisma
=
{
host
:
{
findUnique
:
jest
.
fn
().
mockResolvedValue
(
host
)
}
};
redis
=
{
get
:
jest
.
fn
().
mockResolvedValue
(
'stored'
),
setEx
:
jest
.
fn
().
mockResolvedValue
(
null
),
del
:
jest
.
fn
().
mockResolvedValue
(
null
),
};
jwt
=
{
signAsync
:
jest
.
fn
().
mockResolvedValue
(
'signed-token'
),
verifyAsync
:
jest
.
fn
()
.
mockResolvedValue
({
...
valid
,
sub
:
'u1'
,
hostId
:
'host1'
,
jti
:
'jti1'
}),
};
service
=
new
AuthService
(
prisma
,
redis
,
jwt
,
{
getOrThrow
:
(
key
:
string
)
=>
key
===
'jwt.expiresIn'
?
'1h'
:
key
===
'jwt.refreshExpiresIn'
?
'7d'
:
60
,
}
as
any
,
{}
as
any
,
{}
as
any
,
{
trackServerSide
:
jest
.
fn
().
mockResolvedValue
(
null
)
}
as
any
,
);
});
afterEach
(()
=>
{
if
(
original
===
undefined
)
delete
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
;
else
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
=
original
;
});
test
.
each
([
{
...
valid
,
orgScope
:
[]
},
{
...
valid
,
role
:
UserRole
.
ADMIN
},
{
...
valid
,
orgScope
:
[
'friday-market'
]
},
{
...
valid
,
tenantId
:
'different'
},
])(
'blocks invalid exchange before signing or storing code'
,
async
(
user
)
=>
{
await
expect
(
service
.
exchangeToken
({
appId
:
'app1'
,
appSecret
:
'test-secret'
,
user
}),
).
rejects
.
toMatchObject
({
code
:
ApiCode
.
AUTH_PERMISSION_DENIED
});
expect
(
jwt
.
signAsync
).
not
.
toHaveBeenCalled
();
expect
(
redis
.
setEx
).
not
.
toHaveBeenCalled
();
});
it
(
'verifies credentials before checking policy'
,
async
()
=>
{
await
expect
(
service
.
exchangeToken
({
appId
:
'app1'
,
appSecret
:
'wrong'
,
user
:
valid
}),
).
rejects
.
toMatchObject
({
code
:
ApiCode
.
AUTH_INVALID_CREDENTIALS
});
expect
(
jwt
.
signAsync
).
not
.
toHaveBeenCalled
();
});
it
(
'issues a normal scoped login'
,
async
()
=>
{
await
expect
(
service
.
exchangeToken
({
appId
:
'app1'
,
appSecret
:
'test-secret'
,
user
:
valid
}),
).
resolves
.
toMatchObject
({
accessToken
:
'signed-token'
});
expect
(
jwt
.
signAsync
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
role
:
'leader'
,
orgScope
:
[
'clinic-a'
]
}),
expect
.
anything
(),
);
});
test
.
each
([
{
orgScope
:
[]
},
{
role
:
UserRole
.
ADMIN
},
{
orgScope
:
[
'friday-market'
]
},
{
tenantId
:
'other'
},
])(
'does not renew legacy excessive tokens: %j'
,
async
(
patch
)
=>
{
jwt
.
verifyAsync
.
mockResolvedValue
({
...
valid
,
sub
:
'u1'
,
hostId
:
'host1'
,
jti
:
'jti1'
,
...
patch
,
});
await
expect
(
service
.
refresh
(
'old'
)).
rejects
.
toMatchObject
({
code
:
ApiCode
.
AUTH_PERMISSION_DENIED
,
});
expect
(
jwt
.
signAsync
).
not
.
toHaveBeenCalled
();
});
it
(
'refreshes scoped tokens'
,
async
()
=>
{
await
expect
(
service
.
refresh
(
'old'
)).
resolves
.
toMatchObject
({
accessToken
:
'signed-token'
});
});
it
(
'rejects disabled hosts during refresh'
,
async
()
=>
{
prisma
.
host
.
findUnique
.
mockResolvedValue
({
...
host
,
active
:
false
});
await
expect
(
service
.
refresh
(
'old'
)).
rejects
.
toMatchObject
({
code
:
ApiCode
.
AUTH_INVALID_REFRESH_TOKEN
,
});
expect
(
jwt
.
signAsync
).
not
.
toHaveBeenCalled
();
});
});
apps/pac-service/tests/host-auth-policy.spec.ts
0 → 100644
View file @
2c1e4a14
import
{
mkdtempSync
,
mkdirSync
,
writeFileSync
,
rmSync
}
from
'node:fs'
;
import
{
tmpdir
}
from
'node:os'
;
import
{
join
}
from
'node:path'
;
import
{
checkHostAuthPolicy
,
loadHostAuthPolicy
}
from
'../src/modules/auth/host-auth-policy'
;
import
{
ManifestAuthSchema
}
from
'../src/modules/sync/cold-import/manifest.schema'
;
const
user
=
{
role
:
'leader'
,
tenantId
:
'friday-market'
,
orgScope
:
[
'clinic-a'
]
};
const
policy
=
{
auth
:
ManifestAuthSchema
.
parse
({
require_org_scope
:
true
,
max_role
:
'leader'
,
forbid_root_scope
:
true
,
}),
tenantId
:
'friday-market'
,
};
describe
(
'host auth boundaries'
,
()
=>
{
test
.
each
([
'staff'
,
'leader'
])(
'allows scoped %s'
,
(
role
)
=>
{
expect
(
checkHostAuthPolicy
(
policy
,
{
...
user
,
role
})).
toBeNull
();
});
test
.
each
([
{
role
:
'admin'
},
{
role
:
'unknown'
},
{
orgScope
:
[]
},
{
orgScope
:
[
''
]
},
{
orgScope
:
[
' '
]
},
{
orgScope
:
[
'clinic-a'
,
''
]
},
{
orgScope
:
[
'friday-market'
]
},
{
orgScope
:
[
'clinic-a'
,
'friday-market'
]
},
{
orgScope
:
[
' friday-market '
]
},
{
tenantId
:
'other'
,
orgScope
:
[
'clinic-a'
]
},
])(
'rejects excessive context %j'
,
(
patch
)
=>
{
expect
(
checkHostAuthPolicy
(
policy
,
{
...
user
,
...
patch
})).
not
.
toBeNull
();
});
it
(
'permits a brand node, without converting it into the market root'
,
()
=>
{
expect
(
checkHostAuthPolicy
(
policy
,
{
...
user
,
orgScope
:
[
'brand-a'
]
})).
toBeNull
();
});
it
(
'preserves unrestricted host defaults including group administrators'
,
()
=>
{
expect
(
checkHostAuthPolicy
(
{
auth
:
ManifestAuthSchema
.
parse
({})
},
{
role
:
'admin'
,
tenantId
:
'ruier-grp'
,
orgScope
:
[]
},
),
).
toBeNull
();
});
it
(
'rejects malformed policy options'
,
()
=>
{
expect
(()
=>
ManifestAuthSchema
.
parse
({
max_role
:
'owner'
})).
toThrow
();
expect
(()
=>
ManifestAuthSchema
.
parse
({
require_org_scope
:
'true'
})).
toThrow
();
expect
(()
=>
ManifestAuthSchema
.
parse
({
typo
:
true
})).
toThrow
();
});
});
describe
(
'server-side manifest loader'
,
()
=>
{
let
dir
:
string
;
const
original
=
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
;
beforeEach
(()
=>
{
dir
=
mkdtempSync
(
join
(
tmpdir
(),
'pac-auth-'
));
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
=
dir
;
});
afterEach
(()
=>
{
rmSync
(
dir
,
{
recursive
:
true
,
force
:
true
});
if
(
original
===
undefined
)
delete
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
;
else
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
=
original
;
});
function
write
(
content
:
string
)
{
mkdirSync
(
join
(
dir
,
'friday'
),
{
recursive
:
true
});
writeFileSync
(
join
(
dir
,
'friday'
,
'manifest.yaml'
),
content
);
}
it
(
'fails closed when Friday config is missing'
,
()
=>
{
expect
(()
=>
loadHostAuthPolicy
(
'friday'
)).
toThrow
();
});
it
(
'allows unconfigured hosts as before'
,
()
=>
{
expect
(
loadHostAuthPolicy
(
'jvs-dw'
).
auth
.
max_role
).
toBe
(
'admin'
);
});
test
.
each
([
'host_name: friday'
,
'host_name: friday
\
ntenant_id: friday-market
\
nauth: {}'
,
'host_name: different
\
nauth: {}'
,
'host_name: friday
\
ntenant_id: friday-market
\
nauth:
\
n unknown: true'
,
])(
'rejects incomplete or malformed config'
,
(
content
)
=>
{
write
(
content
);
expect
(()
=>
loadHostAuthPolicy
(
'friday'
)).
toThrow
();
});
it
(
'loads the configured policy'
,
()
=>
{
write
(
'host_name: friday
\
ntenant_id: friday-market
\
nauth:
\
n require_org_scope: true
\
n max_role: leader
\
n forbid_root_scope: true'
,
);
expect
(
loadHostAuthPolicy
(
'friday'
)).
toEqual
(
policy
);
});
it
(
'rejects traversal in host name'
,
()
=>
{
expect
(()
=>
loadHostAuthPolicy
(
'../friday'
)).
toThrow
();
});
it
(
'checks the actual committed Friday configuration'
,
()
=>
{
process
.
env
.
PAC_INCREMENTAL_DATA_DIR
=
join
(
__dirname
,
'../data'
);
expect
(
loadHostAuthPolicy
(
'friday'
)).
toEqual
(
policy
);
expect
(
checkHostAuthPolicy
(
loadHostAuthPolicy
(
'jvs-dw'
),
{
role
:
'admin'
,
tenantId
:
'ruier-grp'
,
orgScope
:
[],
}),
).
toBeNull
();
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment