Sign in

    moongrd

    MoonGrd — Guardian 式认证管道框架(access/refresh 双 token 旋转 + 四段分发管线;transport/storage/crypto-free,核心零依赖)

    auth
    authentication
    token
    pipeline
    guardian
    moonbit
    Download zip
    Author
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    2 hours ago
    Downloads
    6

    #MoonGrd

    Guardian 式认证管道框架(MoonBit):access/refresh 双 token 旋转 + 四段 分发管线(verify → revocation → resolve → dispatch,段段审计)。三针采自 Elixir Guardian:双 token 旋转、allowed_drift 时钟漂移容忍、Plug 式 段间纯值管线。

    transport-free、storage-free、crypto-impl-free——核心包零依赖,密码学藏 Signer/Cipher trait 后面(参考实现绑 mooncry,与 Guardian 委托 Joken 同构)。

    包结构:thy1016/moongrd(token 核与四段门)+ thy1016/moongrd/memstore (内存账本)+ thy1016/moongrd/style/handler(回调方言)+ thy1016/moongrd/adapter/mooncry(真加密面)。

    安全信道(PSK-CR 握手 / HKDF / AEAD seq-IV / 滑窗防重放)不属于本框架—— 那是 TLS 层的活,住传输侧(moonkcp secchan)。本框架的 token 走信道传。

    状态:v0.1.0——G0(token 核)→ G1(旋转+重放检测)→ G2(四段管线) → G3(mooncry 绑定)全线走完;38 罐绿;四个可运行示例;设计决策表 (D-G1~G20)与演进锚公开于 spec.md。0.x 实验期:API 仍可能随反馈调整。

    #特性

    • token 核:双 token 签发/校验/带门校验,定序 JSON + base64url 线形, HMAC 签名;时间与熵全参数注入(核心零时钟零随机)。
    • 旋转语义:refresh 一次性作废,已用票再现 = 重放,自动撤全族。
    • 四段门:段段审计、拒绝短路带拒因;三种姿势任选(回调方言 / 标签 钩子 / 泛型全权)。
    • 五个换挡位(全 trait):算法 Signer、AEAD Cipher、认证协议 AuthProtocol、存储 RefreshStore/RevocationStore、信道 Handshake (外置 secchan)——新实现 = 新 impl,框架零改。
    • stock 预制 + 一键换装:名单准入/回声派工/审计三形现成,FlexDoor swap_* 运行时热换(紧急停门一行)。

    #安装

    确保环境中已安装 MoonBit 工具链,然后在模块根目录:

    moon add thy1016/moongrd

    核心包在消费包的 moon.pkg import 块中添加 "thy1016/moongrd" (@moongrd 前缀);回调方言另加 "thy1016/moongrd/style/handler" (@handler 前缀);真加密面另加 "thy1016/moongrd/adapter/mooncry" (@mooncry 前缀);内存账本另加 "thy1016/moongrd/memstore" (@memstore 前缀)。

    #快速上手

    Guides 文档廊(guides/,按手感选路):初级—— 五分钟上一门、 门房回调;高级—— token 核与旋转、 四段管线与 Guard 全形、 五换挡位与 adapter。索引见 guides/README.md。

    #登录与校验(token 核)

    let config = @moongrd.GuardConfig::make(
    issuer="demo-iss",
    audience="demo-app",
    access_ttl_ms=60000L,
    refresh_ttl_ms=600000L,
    )
    let signer : &@moongrd.Signer = @mooncry.HmacSha256Signer::new(session_key)
    // 签发:时间与熵(jti)由调用方注入
    let access = @moongrd.issue(config, signer, now, "alice", @moongrd.TokenType::Access, jti_a)
    let refresh = @moongrd.issue(config, signer, now, "alice", @moongrd.TokenType::Refresh, jti_r)
    // 业务门:typ 不对即拒
    match @moongrd.verify_for(config, signer, now, access.token, @moongrd.TokenType::Access) {
    Ok(claims) => // claims.sub 即主体
    Err(e) => // 拒因:过期/签名不符/型不符……
    }

    #四段门(三种姿势,按手感选)

    最简形(回调方言)——实现三个具名回调,mount 即得一门;主体即 sub,req/resp 走 Json,全程不写 async:

    pub impl DoorHandler for MyDoor with admit(self : MyDoor, sub : String) -> Bool { ... }
    pub impl DoorHandler for MyDoor with dispatch(self : MyDoor, sub : String, req : Json) -> Json { ... }
    pub impl DoorHandler for MyDoor with audit(self : MyDoor, e : @moongrd.AuditEvent) -> Unit { ... }

    let door = @handler.mount(config=config, signer=signer,
    revocations=revocations, handler=my_door)
    let resp = door.run(now, access_token, req)

    中级形(标签钩子)——audit 可省(省略 = 吞流,进阶再挂):

    let door = @moongrd.Guard::new(
    config=config, signer=signer, revocations=revocations,
    resolve=async fn(sub : String) -> User? { ... }, // sub → 主体
    dispatch=async fn(u : User, req : Req) -> Resp { ... }, // 业务到达
    )

    进阶形(挂审计 + 协议换挡)——每段落一行审计事件;protocol= 可换认证协议(缺省 bearer token):

    let door = @moongrd.Guard::new(
    config=config, signer=signer, revocations=revocations,
    resolve=..., dispatch=...,
    audit=async fn(e : @moongrd.AuditEvent) -> Unit { ... },
    protocol=my_protocol,
    )

    #换发与旋转

    match @moongrd.refresh(config, signer, store, now, refresh_token, next_jti) {
    Ok(r) => // 新 refresh 到手;access 用 issue() 另发
    Err(@moongrd.RefreshErr::Replayed(family)) => // 盗用!全族已自动作废
    Err(@moongrd.RefreshErr::Verify(Expired(_))) => // 回握手重登录
    ...
    }

    #预制回调与一键换装

    let handler = @handler.FlexDoor::demo(allow=["alice"], prefix="[admin] ")
    // 运行时一键换装(已挂门即时生效):
    handler.swap_admit(admit=@handler.admit_none()) // 紧急停门
    handler.swap_admit(admit=@handler.admit_allowlist(allow=["alice", "carol"]))
    handler.swap_audit(audit=@handler.audit_collector(log=lines))

    stock 七工厂:admit_allowlist / admit_all / admit_none / dispatch_echo / audit_println / audit_silent / audit_collector。

    #示例廊(四口,一键可跑)

    moon run examples/walkthrough # 全链六幕:登录/门/旋转/重放撤族/篡改拒 moon run examples/session_lifecycle # 会话一生五幕:登录→过期→换发→登出 moon run examples/handler_demo # 回调方言三幕 moon run examples/signer_swap # 换 verify 算法三幕(HMAC-SHA512/CMAC/Ed25519)

    #API 总览

    入口(@moongrd)作用
    GuardConfig::make(issuer~, audience~, access_ttl_ms~, refresh_ttl_ms~)配置(drift 默认 5000ms)
    issue(config, signer, now, sub, typ, jti)签发(纯函数)
    verify(config, signer, now, token)校验(任一 typ)
    verify_for(config, signer, now, token, typ)带门校验
    refresh(config, signer, store, now, token, next_jti)换发(旋转 + 重放撤全族)
    Guard::new(config~, signer~, revocations~, resolve~, dispatch~, audit?, protocol?)四段门单构造点
    Guard::run(now, token, req)过门
    trait 五位Signer Cipher RefreshStore RevocationStore AuthProtocol

    入口(@handler)作用
    DoorHandler(admit/dispatch/audit)+ mount(...)回调方言接线
    admit_allowlist / admit_all / admit_none预制准入
    dispatch_echo预制派工
    audit_println / audit_silent / audit_collector预制审计
    FlexDoor::new / demo + swap_admit / swap_dispatch / swap_audit可换装门房

    入口(@mooncry / @memstore)作用
    HmacSha256Signer::new(key)真签名面(RFC 4231 KAT 过 trait)
    AesGcmCipher::new(key)AEAD 密封面
    MemRefreshStore::new / sweep、MemRevocationStore::new内存账本

    #错误面

    VerifyErr Malformed(线形/编码/定型败) · BadMac(签名不符/篡改) · Expired(claims) · NotYetValid(claims) · WrongIssuer(claims) · WrongAudience(claims) · WrongType(claims) RefreshErr Verify(内含 VerifyErr;Expired = 回握手信号) · UnknownToken(mac 过但账本无记录) · Replayed(family)(已撤族) · Revoked(claims) GuardErr Verify(内含) · Revoked · Unresolved(sub)

    语义三条:核心面全 Result/Option 化,无 abort 无 trap;mac 校验前的 错误不携带载荷(不可信物不外传),mac 后的错误携带 claims 供审计定罪; 拒绝一律无 oracle(失败与失败不可区分到超出拒因)。

    #设计边界

    回调收在数据面(数据从哪来、结果到哪去),不收在控制面(校验序、 短路逻辑)——verify 段是框架私产,不开放回调;换算法(Signer)≠ 关校验。

    扩展点形
    Signer / Cipher / AuthProtocol(有状态算法/协议面)trait
    RefreshStore / RevocationStore(多式共享状态)trait
    resolve / dispatch / audit(单钩子)闭包
    TTL / drift / issuer / audience字段配置
    校验序、线形格式、Claims 形状、旋转策略钉死(安全语义)

    已知边界(0.x):记录层钉 AES-GCM(ChaCha20 换挡 = trait 位,需求触发); rekey 留接口未实现;Ed25519 Signer 备位未落;未经独立安全审计——公网 部署请配合信道层 PFS 并自行评审。

    #测试与验证

    moon test --target native # 38 罐

    构成:token 核与线形边界(b64url 边界/claims 反解/drift 界)/旋转与账本 (重放/撤族/清扫)/四段管线(审计序/短路/协议换挡)/回调方言与一键换装。 含 RFC 4231 HMAC 已知答案向量过 Signer trait。四个示例为可跑文档 (moon run examples/<名字> --target native)。

    #许可证

    Apache-2.0。

    AuthProtocol

    pub(open) trait AuthProtocol {
    async fn verify_access(self : Self, config~ : GuardConfig, signer~ : &Signer, now~ : Int64, token~ : String) -> Result[Claims, VerifyErr]
    }

    会话认证协议位(D-G19:开放集,trait 非 enum)。框架私产的是校验序 与短路逻辑;verify 的算法/协议在此换挡。缺省 TokenProtocol = bearer token(初级无感);新协议 = 新 impl,管线零改。 注:refresh 旋转属 token 协议的会话续命面,不在本 trait。

    Cipher

    pub(open) trait Cipher {
    fn seal(Self, plaintext : Bytes, nonce : Bytes, aad : Bytes) -> Bytes
    fn open(Self, ciphertext : Bytes, nonce : Bytes, aad : Bytes) -> Bytes?
    }

    AEAD 面(可选腿):封/启。G0 仅定义,G3 落 mooncry 实现(D-G6)。

    RefreshStore

    pub(open) trait RefreshStore {
    fn put(Self, jti : String, record : RefreshRecord) -> Unit
    fn get(Self, jti : String) -> RefreshRecord?
    fn mark_used(Self, jti : String) -> Unit
    fn revoke_family(Self, family_id : String) -> Unit
    }

    refresh 存储面:旋转/重放检测的底座。G0 同步色(memstore G1 即用); async 色待 D-G12 探针定谳,分叉只影响消费方底座,不破本 trait 语义。

    RevocationStore

    pub(open) trait RevocationStore {
    fn revoked(Self, jti : String) -> Bool
    fn revoke(Self, jti : String) -> Unit
    }

    吊销表:access 即时吊销面(登出/被盗)。

    Signer

    pub(open) trait Signer {
    fn sign(Self, payload : Bytes) -> Bytes
    fn verify(Self, payload : Bytes, mac : Bytes) -> Bool
    }

    签名面:mac 签发/核验。核心只定义,实现在 adapter/mooncry(G3); 换算法 ≠ 关校验——verify 段调用序是框架私产,不开放回调(D-G10)。

    AuditEvent

    pub(all) struct AuditEvent {
    stage : String
    sub : String
    jti : String
    ok : Bool
    detail : String
    } derive(Eq,
    Debug
    )

    审计事件:每段落一行;早期拒绝 sub/jti 可为空串(detail 带拒因)。

    AuditEvent::equal

    fn AuditEvent::equal(AuditEvent, AuditEvent) -> Bool

    AuditEvent::not_equal

    fn AuditEvent::not_equal(x : AuditEvent, y : AuditEvent) -> Bool

    Claims

    pub(all) struct Claims {
    sub : String
    iss : String
    aud : String
    iat : Int64
    exp : Int64
    typ : TokenType
    jti : String
    } derive(Eq,
    Debug
    )

    声明集:token 载荷定型字段。iat/exp 单位毫秒;时间全参数注入, 核心零时钟读(D-G2)。

    Claims::equal

    fn Claims::equal(Claims, Claims) -> Bool

    Claims::not_equal

    fn Claims::not_equal(x : Claims, y : Claims) -> Bool

    Claims::to_repr

    Guard

    pub(all) struct Guard[TSubj, TReq, TResp] {
    config : GuardConfig
    signer : &Signer
    revocations : &RevocationStore
    protocol : &AuthProtocol
    resolve : async (String) -> TSubj?
    dispatch : async (TSubj, TReq) -> TResp
    audit : async (AuditEvent) -> Unit?
    }

    单构造点(D-G13):三钩子由消费方注入;主体型 TSubj 泛型——框架 不假设任何用户库(Resolver 消费方实现,茗篇 §2.6)。

    Guard::new

    fn[TSubj, TReq, TResp] Guard::new(config~ : GuardConfig, signer~ : &Signer, revocations~ : &RevocationStore, resolve~ : async (String) -> TSubj?, dispatch~ : async (TSubj, TReq) -> TResp, audit? : async (AuditEvent) -> Unit, protocol? : &AuthProtocol) -> Guard[TSubj, TReq, TResp]

    初级方言(D-G17):全标签实参 + audit 可省——省略即吞流形 no-op (D-G11 的合法沉没),初级三钩起步,进阶再挂 audit=。 protocol 可省——省略即缺省 bearer token(D-G19)。

    Guard::run

    async fn[TSubj, TReq, TResp] Guard::run(self : Guard[TSubj, TReq, TResp], now : Int64, token : String, req : TReq) -> Result[TResp, GuardErr]

    access 门 runner:verify → 吊销核 → resolve → dispatch,段段审计 (fail 即短路,拒因落审计;成功路四行全 ok)。

    GuardConfig

    pub(all) struct GuardConfig {
    issuer : String
    audience : String
    allowed_drift_ms : Int64
    access_ttl_ms : Int64
    refresh_ttl_ms : Int64
    } derive(Eq,
    Debug
    )

    框架配置。allowed_drift 界内算有效(时钟漂移容忍);TTL 单位毫秒。

    GuardConfig::equal

    fn GuardConfig::equal(GuardConfig, GuardConfig) -> Bool

    GuardConfig::make

    fn GuardConfig::make(issuer~ : String, audience~ : String, access_ttl_ms~ : Int64, refresh_ttl_ms~ : Int64) -> GuardConfig

    默认形:allowed_drift 5000ms(D-AUTH8/D-G5);TTL 由消费方给。 标签实参形——调用点自述每个参数的身份。

    GuardConfig::not_equal

    fn GuardConfig::not_equal(x : GuardConfig, y : GuardConfig) -> Bool

    GuardErr

    pub(all) enum GuardErr {
    Verify(VerifyErr)
    Revoked
    Unresolved(String)
    } derive(Eq,
    Debug
    )

    守护拒错(认证面)。

    GuardErr::equal

    fn GuardErr::equal(GuardErr, GuardErr) -> Bool

    GuardErr::not_equal

    fn GuardErr::not_equal(x : GuardErr, y : GuardErr) -> Bool

    GuardErr::to_repr

    Issued

    pub(all) struct Issued {
    token : String
    claims : Claims
    } derive(Eq,
    Debug
    )

    签发产物:线形 token(payload.mac 两段)+ 定型 claims。

    Issued::equal

    fn Issued::equal(Issued, Issued) -> Bool

    Issued::not_equal

    fn Issued::not_equal(x : Issued, y : Issued) -> Bool

    Issued::to_repr

    RefreshErr

    pub(all) enum RefreshErr {
    Verify(VerifyErr)
    UnknownToken
    Replayed(String)
    Revoked(Claims)
    } derive(Eq,
    Debug
    )

    换发错误。Verify(Expired) 即"回握手"信号(A0 语义,由消费方解释)。

    RefreshErr::equal

    fn RefreshErr::equal(RefreshErr, RefreshErr) -> Bool

    RefreshErr::not_equal

    fn RefreshErr::not_equal(x : RefreshErr, y : RefreshErr) -> Bool

    RefreshRecord

    pub(all) struct RefreshRecord {
    family_id : String
    sub : String
    issued_at : Int64
    expires_at : Int64
    used : Bool
    revoked : Bool
    } derive(Eq,
    Debug
    )

    refresh 族谱记录:同 family_id = 同一会话的历次 refresh 串 (重放检测撤全族的底座,D-G3)。used = 已消费(再现即重放); revoked = 族被撤(盗用或登出)。

    RefreshRecord::equal

    RefreshRecord::not_equal

    fn RefreshRecord::not_equal(x : RefreshRecord, y : RefreshRecord) -> Bool

    Refreshed

    pub(all) struct Refreshed {
    refresh : Issued
    family_id : String
    sub : String
    } derive(Eq,
    Debug
    )

    换发产物:新 refresh 票 + 族号 + 主体。

    Refreshed::equal

    fn Refreshed::equal(Refreshed, Refreshed) -> Bool

    Refreshed::not_equal

    fn Refreshed::not_equal(x : Refreshed, y : Refreshed) -> Bool

    TokenProtocol

    pub struct TokenProtocol {
    }

    bearer token 协议(缺省实现):HMAC 签名 access 票,核心 verify_for 形。

    TokenProtocol::new

    TokenProtocol::verify_access

    async fn TokenProtocol::verify_access(self : TokenProtocol, config~ : GuardConfig, signer~ : &Signer, now~ : Int64, token~ : String) -> Result[Claims, VerifyErr]

    trait 方法显式晋升([0079] 标准动作)。

    TokenType

    pub(all) enum TokenType {
    Access
    Refresh
    } derive(Eq,
    Debug
    )

    token 型:access(业务门)/ refresh(换发门)。

    TokenType::equal

    fn TokenType::equal(TokenType, TokenType) -> Bool

    TokenType::not_equal

    fn TokenType::not_equal(x : TokenType, y : TokenType) -> Bool

    TokenType::of_wire

    fn TokenType::of_wire(w : String) -> TokenType?

    线形名反解(未知值 None)。

    TokenType::wire

    fn TokenType::wire(self : TokenType) -> String

    线形名(JSON typ 字段取值)。

    VerifyErr

    pub(all) enum VerifyErr {
    Malformed(String)
    BadMac
    Expired(Claims)
    NotYetValid(Claims)
    WrongIssuer(Claims)
    WrongAudience(Claims)
    WrongType(Claims)
    } derive(Eq,
    Debug
    )

    校验错误:mac 之后的错携带 claims(签名已过,载荷可信,供审计定罪); mac 之前的错不携带(不可信物不外传)。

    VerifyErr::equal

    fn VerifyErr::equal(VerifyErr, VerifyErr) -> Bool

    VerifyErr::not_equal

    fn VerifyErr::not_equal(x : VerifyErr, y : VerifyErr) -> Bool

    issue

    fn issue(config : GuardConfig, signer : &Signer, now : Int64, sub : String, typ : TokenType, jti : String) -> Issued

    签发:claims 定型 → 定序载荷 JSON → mac → base64url 两段线形。 纯函数:时间(now)与熵(jti)全由参数注入,核心零时钟零随机(D-G2)。

    refresh

    fn refresh(config : GuardConfig, signer : &Signer, store : &RefreshStore, now : Int64, token : String, next_jti : String) -> Result[Refreshed, RefreshErr]

    换发:verify_for(refresh) → 账本核 → 旧票 mark_used → 新票入族。 纯函数 + 账本参数:时间与熵(next_jti)全由消费方注入(D-G2), 核心零时钟零随机。

    verify

    fn verify(config : GuardConfig, signer : &Signer, now : Int64, token : String) -> Result[Claims, VerifyErr]

    校验:分段 → 解码 → mac 核(先于解析,不碰未信物)→ 定型 → iss/aud → 时间窗(漂移界)。管线 verify 段的核(四段 runner G2 组)。

    verify_for

    fn verify_for(config : GuardConfig, signer : &Signer, now : Int64, token : String, typ : TokenType) -> Result[Claims, VerifyErr]

    带门校验:typ 不对即 WrongType(门面用——access 门不收 refresh)。