feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,18 @@
|
||||
# 账户命令
|
||||
|
||||
首次部署通过显式命令建立租户、用户主体、凭据和所有者成员关系。
|
||||
|
||||
- 包:`user.account.api`
|
||||
- 公开类型:无
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 初始化平台账户
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 声明:`cmd bootstrap`
|
||||
|
||||
```dever
|
||||
cmd bootstrap = app.bootstrap
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
# 后台会话接口
|
||||
|
||||
后台站点登录公开,登出要求认证。
|
||||
|
||||
- 包:`user.account.api.admin.session`
|
||||
- 公开类型:无
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 登录
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 声明:`post login`
|
||||
|
||||
```dever
|
||||
public post login = app.login
|
||||
```
|
||||
|
||||
## 登出
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 声明:`post logout`
|
||||
|
||||
```dever
|
||||
post logout = app.logout
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
# 前台会话接口
|
||||
|
||||
前台复用账户和会话,但由 front 站点签发独立受众令牌。
|
||||
|
||||
- 包:`user.account.api.front.session`
|
||||
- 公开类型:无
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 登录
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 声明:`post login`
|
||||
|
||||
```dever
|
||||
public post login = app.login
|
||||
```
|
||||
|
||||
## 登出
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 声明:`post logout`
|
||||
|
||||
```dever
|
||||
post logout = app.logout
|
||||
```
|
||||
@@ -0,0 +1,350 @@
|
||||
# 账户应用
|
||||
|
||||
账户应用拥有凭据、登录、会话签发和认证身份组装。
|
||||
|
||||
- 包:`user.account.app`
|
||||
- 公开类型:
|
||||
- `AccountView`
|
||||
- `BootstrapView`
|
||||
- `LoginView`
|
||||
- `LogoutView`
|
||||
- `Identity`
|
||||
- 公开方法:
|
||||
- `bootstrap`
|
||||
- `login`
|
||||
- `establish_login`
|
||||
- `logout`
|
||||
- `verify`
|
||||
- `active`
|
||||
- `current_user`
|
||||
- `require_user`
|
||||
- 使用:
|
||||
- `user.account.bootstrap(tenant_key, tenant_name, email, display_name, password)`
|
||||
- `user.account.login(credential_id, password, tenant)`
|
||||
- `user.account.establish_login(credential_id, tenant, site)`
|
||||
- `user.account.logout()`
|
||||
- `user.account.verify(claims)`
|
||||
- `user.account.active(user_id)`
|
||||
- `user.account.current_user()`
|
||||
- `user.account.require_user(value)`
|
||||
|
||||
## 账户视图
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`AccountView`
|
||||
- 字段:
|
||||
- `id: model.principal.id`:用户主体
|
||||
- `uuid: Uuid`:身份 UUID
|
||||
- `email: Text`:邮箱
|
||||
- `display_name: Text`:显示名
|
||||
|
||||
```dever
|
||||
type AccountView {
|
||||
id: model.principal.id
|
||||
uuid: Uuid
|
||||
email: Text
|
||||
display_name: Text
|
||||
}
|
||||
```
|
||||
|
||||
## 初始化结果
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`BootstrapView`
|
||||
- 字段:
|
||||
- `tenant_id: platform.tenant.model.id`:租户
|
||||
- `user_id: model.principal.id`:用户主体
|
||||
- `credential_id: model.id`:凭据 ID
|
||||
|
||||
```dever
|
||||
type BootstrapView {
|
||||
tenant_id: platform.tenant.model.id
|
||||
user_id: model.principal.id
|
||||
credential_id: model.id
|
||||
}
|
||||
```
|
||||
|
||||
## 登录结果
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`LoginView`
|
||||
- 字段:
|
||||
- `account: AccountView`:账户
|
||||
- `tenant_id: platform.tenant.model.id`:租户
|
||||
- `site: Text`:站点
|
||||
|
||||
```dever
|
||||
type LoginView {
|
||||
account: AccountView
|
||||
tenant_id: platform.tenant.model.id
|
||||
site: Text
|
||||
}
|
||||
```
|
||||
|
||||
## 登出结果
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`LogoutView`
|
||||
- 字段:
|
||||
- `revoked: Bool`:是否撤销
|
||||
|
||||
```dever
|
||||
type LogoutView {
|
||||
revoked: Bool
|
||||
}
|
||||
```
|
||||
|
||||
## 认证身份
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`Identity`
|
||||
- 字段:
|
||||
- `id: Text`:JWT subject
|
||||
- `user_id: model.principal.id?`:用户主体
|
||||
- `tenant_id: platform.tenant.model.id?`:租户
|
||||
|
||||
```dever
|
||||
type Identity {
|
||||
id: Text
|
||||
user_id: model.principal.id?
|
||||
tenant_id: platform.tenant.model.id?
|
||||
}
|
||||
```
|
||||
|
||||
## 初始化平台账户
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`bootstrap`
|
||||
- 输入:
|
||||
- `tenant_key: Text`:租户键
|
||||
- `tenant_name: Text`:租户名称
|
||||
- `email: Text`:邮箱
|
||||
- `display_name: Text`:显示名
|
||||
- `password: Secret`:初始密码
|
||||
- 输出:
|
||||
- `result: BootstrapView`:初始化结果
|
||||
|
||||
```dever
|
||||
bootstrap(
|
||||
tenant_key: Text,
|
||||
tenant_name: Text,
|
||||
email: Text,
|
||||
display_name: Text,
|
||||
password: Secret
|
||||
) (result: BootstrapView) {
|
||||
tenant = platform.tenant.provision(tenant_key, tenant_name)
|
||||
input = domain.prepare(email, display_name)
|
||||
hash = blocking(dever.crypto.password_hash(password))
|
||||
principal = model.principal.create(
|
||||
{
|
||||
display_name = input.name
|
||||
}
|
||||
)
|
||||
stored = model.create(
|
||||
{
|
||||
principal_id = principal.id
|
||||
email = input.address
|
||||
password_hash = hash
|
||||
}
|
||||
)
|
||||
user.membership.grant_owner(principal.id, tenant.id)
|
||||
result = BootstrapView {
|
||||
tenant_id = tenant.id
|
||||
user_id = principal.id
|
||||
credential_id = stored.id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 登录
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`login`
|
||||
- 输入:
|
||||
- `credential_id: user.account.model.id`:凭据 ID
|
||||
- `password: Secret`:密码
|
||||
- `tenant: Text`:租户键
|
||||
- 输出:
|
||||
- `result: LoginView`:登录结果
|
||||
|
||||
```dever
|
||||
login(credential_id: model.id, password: Secret, tenant: Text) (result: LoginView) {
|
||||
domain.require_active(
|
||||
model.exists(
|
||||
{
|
||||
where = id == credential_id
|
||||
}
|
||||
)
|
||||
)
|
||||
stored = model.get(credential_id)
|
||||
principal = model.principal.get(stored.principal_id)
|
||||
selected = platform.tenant.active(tenant)
|
||||
domain.require_active(stored.status == model.AccountStatus.Active)
|
||||
domain.require_active(principal.status == model.principal.PrincipalStatus.Active)
|
||||
domain.require_active(
|
||||
blocking(dever.crypto.password_verify(password, stored.password_hash))
|
||||
)
|
||||
authenticated = establish_login(credential_id, selected.key, dever.site.key())
|
||||
dever.auth.issue_cookie(
|
||||
authenticated.subject,
|
||||
uuid.to_text(authenticated.session_id),
|
||||
authenticated.tenant_key
|
||||
)
|
||||
result = authenticated.result
|
||||
}
|
||||
```
|
||||
|
||||
## 建立登录会话
|
||||
|
||||
密码校验在事务外完成。本函数只负责在短事务内重新检查账户、租户和成员关系,再创建会话。
|
||||
|
||||
- 函数:`establish_login`
|
||||
- 输入:
|
||||
- `credential_id: user.account.model.id`:凭据 ID
|
||||
- `tenant: Text`:租户键
|
||||
- `site: Text`:站点键
|
||||
- 输出:
|
||||
- `result: LoginView`:登录视图
|
||||
- `subject: Text`:认证主体
|
||||
- `session_id: Uuid`:会话 UUID
|
||||
- `tenant_key: Text`:租户键
|
||||
|
||||
```dever
|
||||
transaction establish_login(credential_id: model.id, tenant: Text, site: Text) (
|
||||
result: LoginView,
|
||||
subject: Text,
|
||||
session_id: Uuid,
|
||||
tenant_key: Text
|
||||
) {
|
||||
stored = model.get(credential_id)
|
||||
principal = model.principal.get(stored.principal_id)
|
||||
selected = platform.tenant.active(tenant)
|
||||
domain.require_active(stored.status == model.AccountStatus.Active)
|
||||
domain.require_active(principal.status == model.principal.PrincipalStatus.Active)
|
||||
user.membership.require_active(principal.id, selected.id)
|
||||
session = user.session.open(
|
||||
uuid.to_text(principal.uuid),
|
||||
principal.id,
|
||||
selected.id,
|
||||
selected.key,
|
||||
site
|
||||
)
|
||||
result = LoginView {
|
||||
account = AccountView {
|
||||
id = principal.id
|
||||
uuid = principal.uuid
|
||||
email = stored.email
|
||||
display_name = principal.display_name
|
||||
}
|
||||
tenant_id = selected.id
|
||||
site = site
|
||||
}
|
||||
subject = uuid.to_text(principal.uuid)
|
||||
session_id = session.uuid
|
||||
tenant_key = selected.key
|
||||
}
|
||||
```
|
||||
|
||||
## 登出
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`logout`
|
||||
- 输入:无
|
||||
- 输出:
|
||||
- `result: LogoutView`:登出结果
|
||||
|
||||
```dever
|
||||
logout() (result: LogoutView) {
|
||||
user.session.revoke(uuid.parse(dever.auth.session()))
|
||||
dever.auth.clear_cookie()
|
||||
result = LogoutView {
|
||||
revoked = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 验证认证声明
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`verify`
|
||||
- 输入:
|
||||
- `claims: dever.auth.Claims`:已验签声明
|
||||
- 输出:
|
||||
- `identity: Identity`:可信身份
|
||||
|
||||
```dever
|
||||
verify(claims: dever.auth.Claims) (identity: Identity) {
|
||||
session = user.session.authenticate(uuid.parse(claims.session), claims.site)
|
||||
domain.require_active(session.subject == claims.subject)
|
||||
domain.require_tenant(session.tenant_key, claims.tenant)
|
||||
selected = platform.tenant.active(session.tenant_key)
|
||||
domain.require_active(selected.id == session.tenant_id)
|
||||
active(session.user_id)
|
||||
user.membership.require_active(session.user_id, session.tenant_id)
|
||||
identity = Identity {
|
||||
id = claims.subject
|
||||
user_id = session.user_id
|
||||
tenant_id = session.tenant_id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 验证用户主体
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`active`
|
||||
- 输入:
|
||||
- `user_id: user.account.model.principal.id`:用户主体
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
active(user_id: model.principal.id) () {
|
||||
principal = model.principal.get(user_id)
|
||||
domain.require_active(principal.status == model.principal.PrincipalStatus.Active)
|
||||
}
|
||||
```
|
||||
|
||||
## 当前用户的类型化标识
|
||||
|
||||
读取入口已经验证的可信用户标识,保留 Model ID 类型;不重新查询全局会话数据库,也不接受请求传入的作者 ID。
|
||||
|
||||
- 函数:`current_user`
|
||||
- 输入:无
|
||||
- 输出:
|
||||
- `id: user.account.model.principal.id`:可信用户主体
|
||||
|
||||
```dever
|
||||
current_user() (id: model.principal.id) {
|
||||
id = require_user(dever.auth.user_id())
|
||||
}
|
||||
```
|
||||
|
||||
## 要求可信用户标识
|
||||
|
||||
有可信用户时保留其名义类型;没有用户身份时明确拒绝。
|
||||
|
||||
- 函数:`require_user`
|
||||
- 输入:
|
||||
- `value: user.account.model.principal.id?`:已验证的可空用户标识
|
||||
- 输出:
|
||||
- `id: user.account.model.principal.id`:可信用户主体
|
||||
|
||||
```dever
|
||||
require_user(value: model.principal.id) (id: model.principal.id) {
|
||||
id = value
|
||||
}
|
||||
|
||||
require_user(value: null) (id: model.principal.id) {
|
||||
fail(dever.api.Error.Unauthorized)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,141 @@
|
||||
# 账户规则
|
||||
|
||||
账户输入和认证断言集中在纯 Domain。
|
||||
|
||||
- 包:`user.account.domain`
|
||||
- 公开类型:无
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 账户输入
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`AccountInput`
|
||||
- 字段:
|
||||
- `address: Text`:邮箱
|
||||
- `name: Text`:显示名
|
||||
|
||||
```dever
|
||||
type AccountInput {
|
||||
address: Text
|
||||
name: Text
|
||||
}
|
||||
```
|
||||
|
||||
## 规范邮箱
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`normalize_email`
|
||||
- 输入:
|
||||
- `email: Text`:邮箱
|
||||
- 输出:
|
||||
- `address: Text`:规范邮箱
|
||||
|
||||
```dever
|
||||
normalize_email(email: Text) (address: Text) pure {
|
||||
address = text.lower(text.trim(email))
|
||||
}
|
||||
```
|
||||
|
||||
## 规范显示名
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`normalize_name`
|
||||
- 输入:
|
||||
- `display_name: Text`:显示名
|
||||
- 输出:
|
||||
- `name: Text`:规范名称
|
||||
|
||||
```dever
|
||||
normalize_name(display_name: Text) (name: Text) pure {
|
||||
name = text.trim(display_name)
|
||||
}
|
||||
```
|
||||
|
||||
## 准备账户输入
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`prepare`
|
||||
- 输入:
|
||||
- `email: Text`:邮箱
|
||||
- `display_name: Text`:显示名
|
||||
- 输出:
|
||||
- `input: AccountInput`:规范输入
|
||||
|
||||
```dever
|
||||
prepare(email: Text, display_name: Text) (input: AccountInput) pure {
|
||||
address = normalize_email(email)
|
||||
name = normalize_name(display_name)
|
||||
require(
|
||||
text.contains(address, "@") and not text.starts_with(address, "@") and not text.ends_with(
|
||||
address,
|
||||
"@"
|
||||
),
|
||||
"email must contain a local part and domain"
|
||||
)
|
||||
require(not text.is_empty(name), "display name cannot be blank")
|
||||
input = AccountInput {
|
||||
address = address
|
||||
name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 检查输入
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`require`
|
||||
- 输入:
|
||||
- `valid: Bool`:是否有效
|
||||
- `message: Text`:内部原因
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
require(valid: true, message: Text) () pure {}
|
||||
|
||||
require(valid: false, message: Text) () pure {
|
||||
fail(dever.api.Error.Invalid)
|
||||
}
|
||||
```
|
||||
|
||||
## 检查认证状态
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`require_active`
|
||||
- 输入:
|
||||
- `active: Bool`:是否有效
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
require_active(active: true) () pure {}
|
||||
|
||||
require_active(active: false) () pure {
|
||||
fail(dever.api.Error.Unauthorized)
|
||||
}
|
||||
```
|
||||
|
||||
## 检查租户声明
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`require_tenant`
|
||||
- 输入:
|
||||
- `expected: Text`:可信租户键
|
||||
- `actual: Text?`:JWT 租户声明
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
require_tenant(expected: Text, actual: Text) () pure {
|
||||
require_active(expected == actual)
|
||||
}
|
||||
|
||||
require_tenant(expected: Text, actual: null) () pure {
|
||||
fail(dever.api.Error.Unauthorized)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
# 账户凭据模型
|
||||
|
||||
控制库只在该模型保存密码哈希,公开身份由 Principal 承担。
|
||||
|
||||
- 包:`user.account.model`
|
||||
- 公开类型:
|
||||
- `AccountStatus`
|
||||
- `Account`
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 凭据状态
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`AccountStatus`
|
||||
- 分支:
|
||||
- `Active`:启用
|
||||
- `Disabled`:禁用
|
||||
|
||||
```dever
|
||||
type AccountStatus {
|
||||
Active = "启用"
|
||||
Disabled = "禁用"
|
||||
}
|
||||
```
|
||||
|
||||
## 账户凭据
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`Account`
|
||||
- 字段:
|
||||
- `principal_id: user.account.model.principal.id`:用户主体
|
||||
- `email: Text(254)`:登录邮箱
|
||||
- `private password_hash: Text(255)`:私密密码哈希
|
||||
- `status: AccountStatus`:凭据状态
|
||||
|
||||
```dever
|
||||
global type Account {
|
||||
principal_id: user.account.model.principal.id unique
|
||||
email: Text(254) unique
|
||||
private password_hash: Text(255)
|
||||
status: AccountStatus default AccountStatus.Active index
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
# 用户主体模型
|
||||
|
||||
Principal 是会话、成员关系和认证 Identity 共用的公开用户身份。
|
||||
|
||||
- 包:`user.account.model.principal`
|
||||
- 公开类型:
|
||||
- `PrincipalStatus`
|
||||
- `Principal`
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 主体状态
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`PrincipalStatus`
|
||||
- 分支:
|
||||
- `Active`:启用
|
||||
- `Disabled`:禁用
|
||||
|
||||
```dever
|
||||
type PrincipalStatus {
|
||||
Active = "启用"
|
||||
Disabled = "禁用"
|
||||
}
|
||||
```
|
||||
|
||||
## 用户主体
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`Principal`
|
||||
- 字段:
|
||||
- `uuid: Uuid`:身份标识
|
||||
- `display_name: Text(1, 64)`:显示名称
|
||||
- `status: PrincipalStatus`:状态
|
||||
|
||||
```dever
|
||||
global type Principal {
|
||||
uuid: Uuid generated unique
|
||||
display_name: Text(1, 64)
|
||||
status: PrincipalStatus default PrincipalStatus.Active index
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
# 后台授权接口
|
||||
|
||||
后台接口管理当前租户、当前站点的角色授权。
|
||||
|
||||
- 包:`user.authorization.api.admin.manage`
|
||||
- 公开类型:无
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 权限目录
|
||||
|
||||
本节声明当前站点权限目录接口。
|
||||
|
||||
- 声明:`get permissions`
|
||||
|
||||
```dever
|
||||
get permissions = app.permissions
|
||||
```
|
||||
|
||||
## 保存角色
|
||||
|
||||
本节声明保存当前站点角色及权限集合的接口。
|
||||
|
||||
- 声明:`post save`
|
||||
|
||||
```dever
|
||||
post save = app.save
|
||||
```
|
||||
|
||||
## 授予角色
|
||||
|
||||
本节声明向用户授予当前站点角色的接口。
|
||||
|
||||
- 声明:`post grant`
|
||||
|
||||
```dever
|
||||
post grant = app.grant
|
||||
```
|
||||
|
||||
## 撤销角色
|
||||
|
||||
本节声明从用户撤销当前站点角色的接口。
|
||||
|
||||
- 声明:`delete revoke`
|
||||
|
||||
```dever
|
||||
delete revoke = app.revoke
|
||||
```
|
||||
|
||||
## 禁用角色
|
||||
|
||||
本节声明禁用当前站点角色的接口。
|
||||
|
||||
- 声明:`delete disable`
|
||||
|
||||
```dever
|
||||
delete disable = app.disable
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
# 前台授权接口
|
||||
|
||||
前台接口管理当前租户、当前站点的角色授权。
|
||||
|
||||
- 包:`user.authorization.api.front.manage`
|
||||
- 公开类型:无
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 权限目录
|
||||
|
||||
本节声明当前站点权限目录接口。
|
||||
|
||||
- 声明:`get permissions`
|
||||
|
||||
```dever
|
||||
get permissions = app.permissions
|
||||
```
|
||||
|
||||
## 保存角色
|
||||
|
||||
本节声明保存当前站点角色及权限集合的接口。
|
||||
|
||||
- 声明:`post save`
|
||||
|
||||
```dever
|
||||
post save = app.save
|
||||
```
|
||||
|
||||
## 授予角色
|
||||
|
||||
本节声明向用户授予当前站点角色的接口。
|
||||
|
||||
- 声明:`post grant`
|
||||
|
||||
```dever
|
||||
post grant = app.grant
|
||||
```
|
||||
|
||||
## 撤销角色
|
||||
|
||||
本节声明从用户撤销当前站点角色的接口。
|
||||
|
||||
- 声明:`delete revoke`
|
||||
|
||||
```dever
|
||||
delete revoke = app.revoke
|
||||
```
|
||||
|
||||
## 禁用角色
|
||||
|
||||
本节声明禁用当前站点角色的接口。
|
||||
|
||||
- 声明:`delete disable`
|
||||
|
||||
```dever
|
||||
delete disable = app.disable
|
||||
```
|
||||
@@ -0,0 +1,108 @@
|
||||
# 授权管理应用
|
||||
|
||||
授权管理应用只适配 Dever 核心角色存储,不保存业务权限副本。
|
||||
|
||||
- 包:`user.authorization.app`
|
||||
- 公开类型:无
|
||||
- 公开方法:
|
||||
- `permissions`
|
||||
- `save`
|
||||
- `grant`
|
||||
- `revoke`
|
||||
- `disable`
|
||||
- 使用:
|
||||
- `user.authorization.permissions()`
|
||||
- `user.authorization.save(id, name, all_permissions, permission_keys)`
|
||||
- `user.authorization.grant(user_id, role_id)`
|
||||
- `user.authorization.revoke(user_id, role_id)`
|
||||
- `user.authorization.disable(role_id)`
|
||||
|
||||
## 权限目录
|
||||
|
||||
本节定义当前站点权限目录的读取入口。
|
||||
|
||||
- 函数:`permissions`
|
||||
- 输入:无
|
||||
- 输出:
|
||||
- `values: List<dever.auth.Permission>`:当前站点权限目录
|
||||
|
||||
```dever
|
||||
permissions() (values: List<dever.auth.Permission>) {
|
||||
values = dever.auth.permissions()
|
||||
}
|
||||
```
|
||||
|
||||
## 保存角色
|
||||
|
||||
本节定义当前站点角色及其权限集合的保存入口。
|
||||
|
||||
- 函数:`save`
|
||||
- 输入:
|
||||
- `id: Text`:角色 ID
|
||||
- `name: Text`:角色名称
|
||||
- `all_permissions: Bool`:是否拥有当前站点全部权限
|
||||
- `permission_keys: List<Text>`:权限键
|
||||
- 输出:
|
||||
- `ok: Bool`:保存成功
|
||||
|
||||
```dever
|
||||
save(id: Text, name: Text, all_permissions: Bool, permission_keys: List<Text>) (
|
||||
ok: Bool
|
||||
) {
|
||||
dever.auth.save_role(id, name, all_permissions, permission_keys)
|
||||
ok = true
|
||||
}
|
||||
```
|
||||
|
||||
## 授予角色
|
||||
|
||||
本节定义向用户授予当前站点角色的入口。
|
||||
|
||||
- 函数:`grant`
|
||||
- 输入:
|
||||
- `user_id: Int`:用户 ID
|
||||
- `role_id: Text`:角色 ID
|
||||
- 输出:
|
||||
- `ok: Bool`:授予成功
|
||||
|
||||
```dever
|
||||
grant(user_id: Int, role_id: Text) (ok: Bool) {
|
||||
dever.auth.grant_role(user_id, role_id)
|
||||
ok = true
|
||||
}
|
||||
```
|
||||
|
||||
## 撤销角色
|
||||
|
||||
本节定义从用户撤销当前站点角色的入口。
|
||||
|
||||
- 函数:`revoke`
|
||||
- 输入:
|
||||
- `user_id: Int`:用户 ID
|
||||
- `role_id: Text`:角色 ID
|
||||
- 输出:
|
||||
- `ok: Bool`:撤销成功
|
||||
|
||||
```dever
|
||||
revoke(user_id: Int, role_id: Text) (ok: Bool) {
|
||||
dever.auth.revoke_role(user_id, role_id)
|
||||
ok = true
|
||||
}
|
||||
```
|
||||
|
||||
## 禁用角色
|
||||
|
||||
本节定义禁用当前站点角色的入口。
|
||||
|
||||
- 函数:`disable`
|
||||
- 输入:
|
||||
- `role_id: Text`:角色 ID
|
||||
- 输出:
|
||||
- `ok: Bool`:禁用成功
|
||||
|
||||
```dever
|
||||
disable(role_id: Text) (ok: Bool) {
|
||||
dever.auth.disable_role(role_id)
|
||||
ok = true
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,81 @@
|
||||
# 成员应用
|
||||
|
||||
成员应用建立租户关系并验证有效成员。
|
||||
|
||||
- 包:`user.membership.app`
|
||||
- 公开类型:无
|
||||
- 公开方法:
|
||||
- `grant_owner`
|
||||
- `require_active`
|
||||
- `active`
|
||||
- 使用:
|
||||
- `user.membership.grant_owner(account_id, selected_tenant_id)`
|
||||
- `user.membership.require_active(account_id, selected_tenant_id)`
|
||||
- `user.membership.active(stored)`
|
||||
|
||||
## 授予所有者
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`grant_owner`
|
||||
- 输入:
|
||||
- `account_id: user.account.model.principal.id`:用户主体
|
||||
- `selected_tenant_id: platform.tenant.model.id`:租户
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
grant_owner(
|
||||
account_id: user.account.model.principal.id,
|
||||
selected_tenant_id: platform.tenant.model.id
|
||||
) () {
|
||||
stored = model.create(
|
||||
{
|
||||
user_id = account_id
|
||||
tenant_id = selected_tenant_id
|
||||
role = model.MembershipRole.Owner
|
||||
}
|
||||
)
|
||||
discarded = stored
|
||||
}
|
||||
```
|
||||
|
||||
## 验证有效成员
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`require_active`
|
||||
- 输入:
|
||||
- `account_id: user.account.model.principal.id`:用户主体
|
||||
- `selected_tenant_id: platform.tenant.model.id`:租户
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
require_active(
|
||||
account_id: user.account.model.principal.id,
|
||||
selected_tenant_id: platform.tenant.model.id
|
||||
) () {
|
||||
stored = model.first(
|
||||
{
|
||||
where = user_id == account_id and tenant_id == selected_tenant_id and status == model.MembershipStatus.Active
|
||||
}
|
||||
)
|
||||
active(stored)
|
||||
}
|
||||
```
|
||||
|
||||
## 检查查询结果
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`active`
|
||||
- 输入:
|
||||
- `stored: user.membership.model.Membership?`:成员查询结果
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
active(stored: model.Membership) () {}
|
||||
|
||||
active(stored: null) () {
|
||||
fail(dever.api.Error.Unauthorized)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
# 租户成员模型
|
||||
|
||||
控制库保存用户主体在租户中的角色。
|
||||
|
||||
- 包:`user.membership.model`
|
||||
- 公开类型:
|
||||
- `MembershipRole`
|
||||
- `MembershipStatus`
|
||||
- `Membership`
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 成员角色
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`MembershipRole`
|
||||
- 分支:
|
||||
- `Owner`:所有者
|
||||
- `Editor`:编辑
|
||||
- `Reader`:读者
|
||||
|
||||
```dever
|
||||
type MembershipRole {
|
||||
Owner = "所有者"
|
||||
Editor = "编辑"
|
||||
Reader = "读者"
|
||||
}
|
||||
```
|
||||
|
||||
## 成员状态
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`MembershipStatus`
|
||||
- 分支:
|
||||
- `Active`:启用
|
||||
- `Disabled`:禁用
|
||||
|
||||
```dever
|
||||
type MembershipStatus {
|
||||
Active = "启用"
|
||||
Disabled = "禁用"
|
||||
}
|
||||
```
|
||||
|
||||
## 成员记录
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`Membership`
|
||||
- 字段:
|
||||
- `user_id: user.account.model.principal.id`:用户主体
|
||||
- `tenant_id: platform.tenant.model.id`:租户
|
||||
- `role: MembershipRole`:角色
|
||||
- `status: MembershipStatus`:状态
|
||||
|
||||
```dever
|
||||
global type Membership {
|
||||
user_id: user.account.model.principal.id
|
||||
tenant_id: platform.tenant.model.id
|
||||
role: MembershipRole index
|
||||
status: MembershipStatus default MembershipStatus.Active index
|
||||
}
|
||||
```
|
||||
|
||||
## 成员唯一约束
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 声明:`unique(user_id, tenant_id)`
|
||||
|
||||
```dever
|
||||
unique(user_id, tenant_id)
|
||||
```
|
||||
@@ -0,0 +1,213 @@
|
||||
# 会话应用
|
||||
|
||||
会话应用创建、验证和撤销站点绑定的数据库会话。
|
||||
|
||||
- 包:`user.session.app`
|
||||
- 公开类型:
|
||||
- `SessionView`
|
||||
- `AuthSession`
|
||||
- 公开方法:
|
||||
- `open`
|
||||
- `authenticate`
|
||||
- `revoke`
|
||||
- `auth_session`
|
||||
- `revoke_found`
|
||||
- `cleanup`
|
||||
- 使用:
|
||||
- `user.session.open(subject, account_id, selected_tenant_id, selected_tenant_key, site_key)`
|
||||
- `user.session.authenticate(session_id, site_key)`
|
||||
- `user.session.revoke(session_id)`
|
||||
- `user.session.auth_session(stored)`
|
||||
- `user.session.revoke_found(stored)`
|
||||
- `user.session.cleanup()`
|
||||
|
||||
## 会话视图
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`SessionView`
|
||||
- 字段:
|
||||
- `uuid: Uuid`:会话 UUID
|
||||
|
||||
```dever
|
||||
type SessionView {
|
||||
uuid: Uuid
|
||||
}
|
||||
```
|
||||
|
||||
## 认证会话
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`AuthSession`
|
||||
- 字段:
|
||||
- `subject: Text`:subject
|
||||
- `user_id: user.account.model.principal.id`:用户主体
|
||||
- `tenant_id: platform.tenant.model.id`:租户
|
||||
- `tenant_key: Text`:租户声明
|
||||
|
||||
```dever
|
||||
type AuthSession {
|
||||
subject: Text
|
||||
user_id: user.account.model.principal.id
|
||||
tenant_id: platform.tenant.model.id
|
||||
tenant_key: Text
|
||||
}
|
||||
```
|
||||
|
||||
## 创建会话
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`open`
|
||||
- 输入:
|
||||
- `subject: Text`:subject
|
||||
- `account_id: user.account.model.principal.id`:用户主体
|
||||
- `selected_tenant_id: platform.tenant.model.id`:租户
|
||||
- `selected_tenant_key: Text`:租户键
|
||||
- `site_key: Text`:站点
|
||||
- 输出:
|
||||
- `session: SessionView`:会话
|
||||
|
||||
```dever
|
||||
open(
|
||||
subject: Text,
|
||||
account_id: user.account.model.principal.id,
|
||||
selected_tenant_id: platform.tenant.model.id,
|
||||
selected_tenant_key: Text,
|
||||
site_key: Text
|
||||
) (session: SessionView) {
|
||||
stored = model.create(
|
||||
{
|
||||
subject = subject
|
||||
user_id = account_id
|
||||
tenant_id = selected_tenant_id
|
||||
tenant_key = selected_tenant_key
|
||||
site = site_key
|
||||
expires_at = dever.time.add(dever.time.now(), dever.time.duration(3600000))
|
||||
}
|
||||
)
|
||||
session = SessionView {
|
||||
uuid = stored.uuid
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 验证会话
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`authenticate`
|
||||
- 输入:
|
||||
- `session_id: Uuid?`:会话 UUID
|
||||
- `site_key: Text`:站点
|
||||
- 输出:
|
||||
- `result: AuthSession`:认证会话
|
||||
|
||||
```dever
|
||||
authenticate(session_id: Uuid, site_key: Text) (result: AuthSession) {
|
||||
stored = model.first(
|
||||
{
|
||||
where = uuid == session_id and site == site_key and revoked == false and expires_at > dever.time.now(
|
||||
)
|
||||
}
|
||||
)
|
||||
result = auth_session(stored)
|
||||
}
|
||||
|
||||
authenticate(session_id: null, site_key: Text) (result: AuthSession) {
|
||||
fail(dever.api.Error.Unauthorized)
|
||||
}
|
||||
```
|
||||
|
||||
## 撤销会话
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`revoke`
|
||||
- 输入:
|
||||
- `session_id: Uuid?`:会话 UUID
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
revoke(session_id: Uuid) () {
|
||||
stored = model.first(
|
||||
{
|
||||
where = uuid == session_id and revoked == false
|
||||
}
|
||||
)
|
||||
revoke_found(stored)
|
||||
}
|
||||
|
||||
revoke(session_id: null) () {
|
||||
fail(dever.api.Error.Unauthorized)
|
||||
}
|
||||
```
|
||||
|
||||
## 映射认证会话
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`auth_session`
|
||||
- 输入:
|
||||
- `stored: user.session.model.Session?`:查询结果
|
||||
- 输出:
|
||||
- `result: AuthSession`:认证会话
|
||||
|
||||
```dever
|
||||
auth_session(stored: model.Session) (result: AuthSession) {
|
||||
result = AuthSession {
|
||||
subject = stored.subject
|
||||
user_id = stored.user_id
|
||||
tenant_id = stored.tenant_id
|
||||
tenant_key = stored.tenant_key
|
||||
}
|
||||
}
|
||||
|
||||
auth_session(stored: null) (result: AuthSession) {
|
||||
fail(dever.api.Error.Unauthorized)
|
||||
}
|
||||
```
|
||||
|
||||
## 更新撤销状态
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 函数:`revoke_found`
|
||||
- 输入:
|
||||
- `stored: user.session.model.Session?`:查询结果
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
revoke_found(stored: model.Session) () {
|
||||
changed = model.update(
|
||||
stored.id,
|
||||
{
|
||||
revoked = true
|
||||
}
|
||||
)
|
||||
discarded = changed
|
||||
}
|
||||
|
||||
revoke_found(stored: null) () {
|
||||
fail(dever.api.Error.Unauthorized)
|
||||
}
|
||||
```
|
||||
|
||||
## 清理过期会话
|
||||
|
||||
清理已撤销或已过期的会话记录。
|
||||
|
||||
- 函数:`cleanup`
|
||||
- 输入:无
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
cleanup() () {
|
||||
removed = model.delete(
|
||||
{
|
||||
where = revoked == true or expires_at <= dever.time.now()
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
# 会话清理任务
|
||||
|
||||
周期任务清理撤销或过期会话。
|
||||
|
||||
- 包:`user.session.job`
|
||||
- 公开类型:无
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 队列连接
|
||||
|
||||
使用默认数据库连接。
|
||||
|
||||
- 声明:`database default`
|
||||
|
||||
```dever
|
||||
database default
|
||||
```
|
||||
|
||||
## 清理任务
|
||||
|
||||
任务委托给会话应用。
|
||||
|
||||
- 函数:`cleanup`
|
||||
- 输入:无
|
||||
- 输出:无
|
||||
|
||||
```dever
|
||||
job cleanup() () retry(3) timeout(30000) {
|
||||
app.cleanup()
|
||||
}
|
||||
```
|
||||
|
||||
## 每小时执行
|
||||
|
||||
按 UTC 每小时清理。
|
||||
|
||||
- 声明:`schedule cleanup`
|
||||
|
||||
```dever
|
||||
schedule cleanup = "0 * * * *"
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
# 会话模型
|
||||
|
||||
控制库保存站点绑定、租户绑定且可撤销的登录会话。
|
||||
|
||||
- 包:`user.session.model`
|
||||
- 公开类型:
|
||||
- `Session`
|
||||
- 公开方法:无
|
||||
- 使用:无
|
||||
|
||||
## 会话记录
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 类型:`Session`
|
||||
- 字段:
|
||||
- `uuid: Uuid`:会话标识
|
||||
- `subject: Text(1, 128)`:JWT subject
|
||||
- `user_id: user.account.model.principal.id`:用户主体
|
||||
- `tenant_id: platform.tenant.model.id`:租户
|
||||
- `tenant_key: Text(1, 64)`:JWT 租户声明
|
||||
- `site: Text(1, 32)`:站点
|
||||
- `expires_at: DateTime`:过期时间
|
||||
- `revoked: Bool`:撤销状态
|
||||
|
||||
```dever
|
||||
global type Session {
|
||||
uuid: Uuid generated unique
|
||||
subject: Text(1, 128)
|
||||
user_id: user.account.model.principal.id
|
||||
tenant_id: platform.tenant.model.id
|
||||
tenant_key: Text(1, 64)
|
||||
site: Text(1, 32)
|
||||
expires_at: DateTime index
|
||||
revoked: Bool default false index
|
||||
}
|
||||
```
|
||||
|
||||
## 会话查询索引
|
||||
|
||||
本节定义对应的源码合同。
|
||||
|
||||
- 声明:`index(user_id, tenant_id, site)`
|
||||
|
||||
```dever
|
||||
index(user_id, tenant_id, site)
|
||||
```
|
||||
Reference in new issue
Block a user