Sign in

    moongard

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

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

    #MoonGard

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

    核心包零依赖:不内置传输、存储与密码学实现。算法藏在 Signer / Cipher trait 后(参考实现绑定 mooncry);安全信道属 TLS 层职责,不在 本框架范围,token 由既有信道承载。

    #包结构

    包内容
    thy1016/moongardtoken 核与四段门
    thy1016/moongard/memstore内存账本(测试与单机用)
    thy1016/moongard/style/handler回调方言(具名回调,免写 async)
    thy1016/moongard/adapter/mooncry真加密面(HMAC-SHA256 / AES-GCM)

    状态:0.1.2。0.x 实验期,API 仍可能随反馈调整;未经独立安全审计。 设计决策表(D-G1~G20)见 spec.md。

    #特性

    • token 核:双 token 签发/校验/带门校验,定序 JSON + base64url 线形,HMAC 签名;时间与熵全参数注入(核心零时钟零随机)。
    • 旋转语义:refresh 一次性作废;已用票再现判定为重放,自动撤销 全族。
    • 四段门:段段审计、拒绝短路带拒因;回调方言 / 标签钩子 / 泛型 钩子三种接入方式。
    • 五个 trait 换挡位:Signer、Cipher、AuthProtocol、 RefreshStore、RevocationStore——新实现即新 impl,框架不改。
    • 预制回调与运行时换装:名单准入、回声派工、审计三形现成; FlexDoor swap_* 运行时替换,即时生效。

    #安装

    moon add thy1016/moongard

    消费包 moon.pkg 按需添加 import:thy1016/moongard(@moongard), 及子包 style/handler(@handler)、adapter/mooncry(@mooncry)、 memstore(@memstore)。

    #快速上手

    #签发与校验(token 核)

    let config = @moongard.GuardConfig::make(
    issuer="demo-iss",
    audience="demo-app",
    access_ttl_ms=60000L,
    refresh_ttl_ms=600000L,
    )
    let signer : &@moongard.Signer = @mooncry.HmacSha256Signer::new(session_key)
    // 签发:时间与熵(jti)由调用方注入
    let access = @moongard.issue(config, signer, now, "alice", @moongard.TokenType::Access, jti_a)
    let refresh = @moongard.issue(config, signer, now, "alice", @moongard.TokenType::Refresh, jti_r)
    // 业务门:typ 不对即拒
    match @moongard.verify_for(config, signer, now, access.token, @moongard.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 : @moongard.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 = @moongard.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 = @moongard.Guard::new(
    config=config, signer=signer, revocations=revocations,
    resolve=..., dispatch=...,
    audit=async fn(e : @moongard.AuditEvent) -> Unit { ... },
    protocol=my_protocol,
    )

    #换发与旋转

    match @moongard.refresh(config, signer, store, now, refresh_token, next_jti) {
    Ok(r) => // 新 refresh 到手;access 用 issue() 另发
    Err(@moongard.RefreshErr::Replayed(family)) => // 重放:全族已自动作废
    Err(@moongard.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))

    @handler 提供七个预制工厂: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 总览

    入口(@moongard)作用
    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 核线形边界、旋转与账本、四段管线、回调方言与换装;含 RFC 4231 HMAC 已知答案向量。四个示例即文档,可直接运行 (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)。