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
|
||||
}
|
||||
```
|
||||
Reference in new issue
Block a user