Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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;
}