proto/holger-token.proto
syntax = "proto3";
// holger.token.v1 — repository tokens for package clients (2026-10-21).
//
// A person signs in with a passkey; cargo, pip, npm and maven cannot. A
// repository token is what those clients carry instead: minted by a signed-in
// person for one or more repositories, read or read and write, with an expiry.
// Holger keeps only its hash, shows the secret once (in the Mint reply), lists
// it with its last use, and refuses it once revoked or expired, with its own
// error code (holger-token-<n>). The token acts as its owner and never as more:
// every use is decided against what the owner holds at that moment.
//
// Its own package, like holger.room.v1: holger.proto, and every downstream
// copy of it, does not move.
package holger.token.v1;
service TokenService {
// A signed-in PERSON (a passkey session, stepped up in the last ten minutes)
// mints a token for repositories they may use themselves: read needs read on
// each, read and write needs write on each. The secret is in this reply and
// nowhere else.
rpc Mint(MintTokenRequest) returns (MintTokenReply);
// The caller's own tokens; an admin of this server sees every token. Never a
// secret.
rpc List(ListTokensRequest) returns (ListTokensReply);
// Revoke a token by its id: its owner, or an admin of this server. A revoked
// token is refused at once and stays listed, so the audit still names it.
rpc Revoke(RevokeTokenRequest) returns (RevokeTokenReply);
}
enum TokenScope {
// Read only: the least a token can be.
TOKEN_SCOPE_UNSPECIFIED = 0;
TOKEN_SCOPE_READ = 1;
TOKEN_SCOPE_READ_WRITE = 2;
}
message MintTokenRequest {
// What the person calls it ("laptop cargo", "ci publish").
string label = 1;
// The repositories it is for, by name: at least one.
repeated string repositories = 2;
TokenScope scope = 3;
// Days until it expires: 1 to 366, 0 for the default of 90.
uint32 days = 4;
}
message MintTokenReply {
// The secret, shown ONCE. Holger keeps only its hash.
string secret = 1;
TokenRow token = 2;
// One sentence for the console to show as it is.
string words = 3;
}
message TokenRow {
// The token's public id (the hash of its secret), what List and Revoke name.
string id = 1;
string label = 2;
// The owner's subject and the name to show for them.
string owner = 3;
string owner_display = 4;
repeated string repositories = 5;
TokenScope scope = 6;
// Unix seconds.
int64 created = 7;
int64 expires = 8;
// 0 when never used.
int64 last_used = 9;
// "live", "expired" or "revoked".
string state = 10;
// 0 unless revoked.
int64 revoked_at = 11;
string revoked_by = 12;
}
message ListTokensRequest {}
message ListTokensReply {
repeated TokenRow tokens = 1;
}
message RevokeTokenRequest {
string id = 1;
}
message RevokeTokenReply {
TokenRow token = 1;
}