P4.3: token security at rest + dist token CLI
Subplan 02 Slice 5: Token domain entity (scopes, app/channel scoping, expiry, revocation, last-used) with boundary validation; secrets are dist_<64 hex> drawn from arc4random_buf and only their SHA-256 is persisted. check_token gates revocation > expiry > scope > app/channel; mark_token_used stamps usage for the P4.4 server auth. CLI: dist token create (raw secret shown exactly once; works on a fresh store so CI tokens can predate the first publish), list (lifecycle status, never the secret), revoke (unknown id and double-revoke are distinct errors). Every mutation appends an audit event; tokens joins db.json's persisted arrays, with an absent member loading as empty so older db.json files stay readable. make test 16/16 (new: token_check.sx unit suite, token_ops.sx pinned CLI acceptance).
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#import "../domain/release.sx";
|
||||
#import "../domain/artifact.sx";
|
||||
#import "../domain/channel.sx";
|
||||
#import "../domain/token.sx";
|
||||
#import "../domain/audit.sx";
|
||||
#import "../domain/validate.sx";
|
||||
|
||||
@@ -44,6 +45,7 @@ Repo :: struct {
|
||||
releases: List(Release);
|
||||
artifacts: List(Artifact);
|
||||
channels: List(Channel);
|
||||
tokens: List(Token);
|
||||
audit_events: List(AuditEvent);
|
||||
|
||||
// Capture the owning allocator. The List fields default to empty
|
||||
@@ -170,6 +172,39 @@ Repo :: struct {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Tokens ───────────────────────────────────────────────────────
|
||||
create_token :: (self: *Repo, t: Token) {
|
||||
self.tokens.append(t, self.own_allocator);
|
||||
}
|
||||
get_token :: (self: *Repo, id: string) -> ?Token {
|
||||
i := 0;
|
||||
while i < self.tokens.len {
|
||||
if self.tokens.items[i].id == id { return self.tokens.items[i]; }
|
||||
i += 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// The auth lookup: a presented secret is hashed and matched here.
|
||||
find_token_by_hash :: (self: *Repo, token_hash: string) -> ?Token {
|
||||
i := 0;
|
||||
while i < self.tokens.len {
|
||||
if self.tokens.items[i].token_hash == token_hash { return self.tokens.items[i]; }
|
||||
i += 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
list_tokens :: (self: *Repo) -> []Token {
|
||||
return .{ ptr = self.tokens.items, len = self.tokens.len };
|
||||
}
|
||||
update_token :: (self: *Repo, t: Token) -> bool {
|
||||
i := 0;
|
||||
while i < self.tokens.len {
|
||||
if self.tokens.items[i].id == t.id { self.tokens.items[i] = t; return true; }
|
||||
i += 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ── Audit events ─────────────────────────────────────────────────
|
||||
create_audit_event :: (self: *Repo, e: AuditEvent) {
|
||||
self.audit_events.append(e, self.own_allocator);
|
||||
|
||||
Reference in New Issue
Block a user