Merge pull request 'feat(api): add branch prefixes to tokens' (#6) from feat/token-branch-prefixes into main
Reviewed-on: https://git.bnema.dev/bnema/ermite-api/pulls/6
2 files · +52 -10
M gen/ermite/token.pb.go +38 -9
| @@ -133,9 +133,15 @@ type Token struct { | |||
| 133 | 133 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | |
| 134 | 134 | // LastUsedAt is when the token last authenticated a request. Unset if never | |
| 135 | 135 | // used. It may lag behind the real time by a short delay. | |
| 136 | - | LastUsedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_used_at,json=lastUsedAt,proto3" json:"last_used_at,omitempty"` | |
| 137 | - | unknownFields protoimpl.UnknownFields | |
| 138 | - | sizeCache protoimpl.SizeCache | |
| 136 | + | LastUsedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_used_at,json=lastUsedAt,proto3" json:"last_used_at,omitempty"` | |
| 137 | + | // BranchPrefixes limits the branches the token may write. Each prefix ends | |
| 138 | + | // with "/" (for example "agent/claude/"). Empty means every branch. A limited | |
| 139 | + | // token can push, force-push and delete only branches under its prefixes; it | |
| 140 | + | // cannot push tags or other refs, create repositories, or merge into or | |
| 141 | + | // delete a branch outside its prefixes. | |
| 142 | + | BranchPrefixes []string `protobuf:"bytes,8,rep,name=branch_prefixes,json=branchPrefixes,proto3" json:"branch_prefixes,omitempty"` | |
| 143 | + | unknownFields protoimpl.UnknownFields | |
| 144 | + | sizeCache protoimpl.SizeCache | |
| 139 | 145 | } | |
| 140 | 146 | ||
| 141 | 147 | func (x *Token) Reset() { | |
| @@ -217,6 +223,13 @@ func (x *Token) GetLastUsedAt() *timestamppb.Timestamp { | |||
| 217 | 223 | return nil | |
| 218 | 224 | } | |
| 219 | 225 | ||
| 226 | + | func (x *Token) GetBranchPrefixes() []string { | |
| 227 | + | if x != nil { | |
| 228 | + | return x.BranchPrefixes | |
| 229 | + | } | |
| 230 | + | return nil | |
| 231 | + | } | |
| 232 | + | ||
| 220 | 233 | // CreateTokenRequest describes the token to create. | |
| 221 | 234 | type CreateTokenRequest struct { | |
| 222 | 235 | state protoimpl.MessageState `protogen:"open.v1"` | |
| @@ -230,9 +243,16 @@ type CreateTokenRequest struct { | |||
| 230 | 243 | Repos []string `protobuf:"bytes,3,rep,name=repos,proto3" json:"repos,omitempty"` | |
| 231 | 244 | // ExpiresAt is when the token stops working. Unset means no expiry. It must | |
| 232 | 245 | // be in the future. | |
| 233 | - | ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` | |
| 234 | - | unknownFields protoimpl.UnknownFields | |
| 235 | - | sizeCache protoimpl.SizeCache | |
| 246 | + | ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` | |
| 247 | + | // BranchPrefixes limits the branches the token may write, for example | |
| 248 | + | // "agent/claude/". Each prefix must end with "/" and be a valid branch name | |
| 249 | + | // once the "/" is removed; at most 8 are allowed. Empty means every branch; | |
| 250 | + | // a limited token cannot push tags or create repositories. A token that has | |
| 251 | + | // prefixes can only create tokens whose prefixes each lie within its own. | |
| 252 | + | // Fails with invalid_argument when a prefix is malformed. | |
| 253 | + | BranchPrefixes []string `protobuf:"bytes,5,rep,name=branch_prefixes,json=branchPrefixes,proto3" json:"branch_prefixes,omitempty"` | |
| 254 | + | unknownFields protoimpl.UnknownFields | |
| 255 | + | sizeCache protoimpl.SizeCache | |
| 236 | 256 | } | |
| 237 | 257 | ||
| 238 | 258 | func (x *CreateTokenRequest) Reset() { | |
| @@ -293,6 +313,13 @@ func (x *CreateTokenRequest) GetExpiresAt() *timestamppb.Timestamp { | |||
| 293 | 313 | return nil | |
| 294 | 314 | } | |
| 295 | 315 | ||
| 316 | + | func (x *CreateTokenRequest) GetBranchPrefixes() []string { | |
| 317 | + | if x != nil { | |
| 318 | + | return x.BranchPrefixes | |
| 319 | + | } | |
| 320 | + | return nil | |
| 321 | + | } | |
| 322 | + | ||
| 296 | 323 | // CreateTokenResponse holds the new token and its secret. | |
| 297 | 324 | type CreateTokenResponse struct { | |
| 298 | 325 | state protoimpl.MessageState `protogen:"open.v1"` | |
| @@ -549,7 +576,7 @@ var File_ermite_token_proto protoreflect.FileDescriptor | |||
| 549 | 576 | ||
| 550 | 577 | const file_ermite_token_proto_rawDesc = "" + | |
| 551 | 578 | "\n" + | |
| 552 | - | "\x12ermite/token.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\x9e\x02\n" + | |
| 579 | + | "\x12ermite/token.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\xc7\x02\n" + | |
| 553 | 580 | "\x05Token\x12\x0e\n" + | |
| 554 | 581 | "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + | |
| 555 | 582 | "\x05label\x18\x02 \x01(\tR\x05label\x12%\n" + | |
| @@ -560,13 +587,15 @@ const file_ermite_token_proto_rawDesc = "" + | |||
| 560 | 587 | "\n" + | |
| 561 | 588 | "created_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12<\n" + | |
| 562 | 589 | "\flast_used_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + | |
| 563 | - | "lastUsedAt\"\xa2\x01\n" + | |
| 590 | + | "lastUsedAt\x12'\n" + | |
| 591 | + | "\x0fbranch_prefixes\x18\b \x03(\tR\x0ebranchPrefixes\"\xcb\x01\n" + | |
| 564 | 592 | "\x12CreateTokenRequest\x12\x14\n" + | |
| 565 | 593 | "\x05label\x18\x01 \x01(\tR\x05label\x12%\n" + | |
| 566 | 594 | "\x06scopes\x18\x02 \x03(\x0e2\r.ermite.ScopeR\x06scopes\x12\x14\n" + | |
| 567 | 595 | "\x05repos\x18\x03 \x03(\tR\x05repos\x129\n" + | |
| 568 | 596 | "\n" + | |
| 569 | - | "expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\"R\n" + | |
| 597 | + | "expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12'\n" + | |
| 598 | + | "\x0fbranch_prefixes\x18\x05 \x03(\tR\x0ebranchPrefixes\"R\n" + | |
| 570 | 599 | "\x13CreateTokenResponse\x12#\n" + | |
| 571 | 600 | "\x05token\x18\x01 \x01(\v2\r.ermite.TokenR\x05token\x12\x16\n" + | |
| 572 | 601 | "\x06secret\x18\x02 \x01(\tR\x06secret\"O\n" + | |
| @@ -133,9 +133,15 @@ type Token struct { | |||||
| 133 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | 133 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | ||
| 134 | // LastUsedAt is when the token last authenticated a request. Unset if never | 134 | // LastUsedAt is when the token last authenticated a request. Unset if never | ||
| 135 | // used. It may lag behind the real time by a short delay. | 135 | // used. It may lag behind the real time by a short delay. | ||
| 136 | - | LastUsedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_used_at,json=lastUsedAt,proto3" json:"last_used_at,omitempty"` | 136 | + | LastUsedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_used_at,json=lastUsedAt,proto3" json:"last_used_at,omitempty"` |
| 137 | - | unknownFields protoimpl.UnknownFields | 137 | + | // BranchPrefixes limits the branches the token may write. Each prefix ends |
| 138 | - | sizeCache protoimpl.SizeCache | 138 | + | // with "/" (for example "agent/claude/"). Empty means every branch. A limited |
| 139 | + | // token can push, force-push and delete only branches under its prefixes; it | |||
| 140 | + | // cannot push tags or other refs, create repositories, or merge into or | |||
| 141 | + | // delete a branch outside its prefixes. | |||
| 142 | + | BranchPrefixes []string `protobuf:"bytes,8,rep,name=branch_prefixes,json=branchPrefixes,proto3" json:"branch_prefixes,omitempty"` | |||
| 143 | + | unknownFields protoimpl.UnknownFields | |||
| 144 | + | sizeCache protoimpl.SizeCache | |||
| 139 | } | 145 | } | ||
| 140 | 146 | ||||
| 141 | func (x *Token) Reset() { | 147 | func (x *Token) Reset() { | ||
| @@ -217,6 +223,13 @@ func (x *Token) GetLastUsedAt() *timestamppb.Timestamp { | |||||
| 217 | return nil | 223 | return nil | ||
| 218 | } | 224 | } | ||
| 219 | 225 | ||||
| 226 | + | func (x *Token) GetBranchPrefixes() []string { | |||
| 227 | + | if x != nil { | |||
| 228 | + | return x.BranchPrefixes | |||
| 229 | + | } | |||
| 230 | + | return nil | |||
| 231 | + | } | |||
| 232 | + | ||||
| 220 | // CreateTokenRequest describes the token to create. | 233 | // CreateTokenRequest describes the token to create. | ||
| 221 | type CreateTokenRequest struct { | 234 | type CreateTokenRequest struct { | ||
| 222 | state protoimpl.MessageState `protogen:"open.v1"` | 235 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| @@ -230,9 +243,16 @@ type CreateTokenRequest struct { | |||||
| 230 | Repos []string `protobuf:"bytes,3,rep,name=repos,proto3" json:"repos,omitempty"` | 243 | Repos []string `protobuf:"bytes,3,rep,name=repos,proto3" json:"repos,omitempty"` | ||
| 231 | // ExpiresAt is when the token stops working. Unset means no expiry. It must | 244 | // ExpiresAt is when the token stops working. Unset means no expiry. It must | ||
| 232 | // be in the future. | 245 | // be in the future. | ||
| 233 | - | ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` | 246 | + | ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` |
| 234 | - | unknownFields protoimpl.UnknownFields | 247 | + | // BranchPrefixes limits the branches the token may write, for example |
| 235 | - | sizeCache protoimpl.SizeCache | 248 | + | // "agent/claude/". Each prefix must end with "/" and be a valid branch name |
| 249 | + | // once the "/" is removed; at most 8 are allowed. Empty means every branch; | |||
| 250 | + | // a limited token cannot push tags or create repositories. A token that has | |||
| 251 | + | // prefixes can only create tokens whose prefixes each lie within its own. | |||
| 252 | + | // Fails with invalid_argument when a prefix is malformed. | |||
| 253 | + | BranchPrefixes []string `protobuf:"bytes,5,rep,name=branch_prefixes,json=branchPrefixes,proto3" json:"branch_prefixes,omitempty"` | |||
| 254 | + | unknownFields protoimpl.UnknownFields | |||
| 255 | + | sizeCache protoimpl.SizeCache | |||
| 236 | } | 256 | } | ||
| 237 | 257 | ||||
| 238 | func (x *CreateTokenRequest) Reset() { | 258 | func (x *CreateTokenRequest) Reset() { | ||
| @@ -293,6 +313,13 @@ func (x *CreateTokenRequest) GetExpiresAt() *timestamppb.Timestamp { | |||||
| 293 | return nil | 313 | return nil | ||
| 294 | } | 314 | } | ||
| 295 | 315 | ||||
| 316 | + | func (x *CreateTokenRequest) GetBranchPrefixes() []string { | |||
| 317 | + | if x != nil { | |||
| 318 | + | return x.BranchPrefixes | |||
| 319 | + | } | |||
| 320 | + | return nil | |||
| 321 | + | } | |||
| 322 | + | ||||
| 296 | // CreateTokenResponse holds the new token and its secret. | 323 | // CreateTokenResponse holds the new token and its secret. | ||
| 297 | type CreateTokenResponse struct { | 324 | type CreateTokenResponse struct { | ||
| 298 | state protoimpl.MessageState `protogen:"open.v1"` | 325 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| @@ -549,7 +576,7 @@ var File_ermite_token_proto protoreflect.FileDescriptor | |||||
| 549 | 576 | ||||
| 550 | const file_ermite_token_proto_rawDesc = "" + | 577 | const file_ermite_token_proto_rawDesc = "" + | ||
| 551 | "\n" + | 578 | "\n" + | ||
| 552 | - | "\x12ermite/token.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\x9e\x02\n" + | 579 | + | "\x12ermite/token.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\xc7\x02\n" + |
| 553 | "\x05Token\x12\x0e\n" + | 580 | "\x05Token\x12\x0e\n" + | ||
| 554 | "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + | 581 | "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + | ||
| 555 | "\x05label\x18\x02 \x01(\tR\x05label\x12%\n" + | 582 | "\x05label\x18\x02 \x01(\tR\x05label\x12%\n" + | ||
| @@ -560,13 +587,15 @@ const file_ermite_token_proto_rawDesc = "" + | |||||
| 560 | "\n" + | 587 | "\n" + | ||
| 561 | "created_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12<\n" + | 588 | "created_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12<\n" + | ||
| 562 | "\flast_used_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + | 589 | "\flast_used_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + | ||
| 563 | - | "lastUsedAt\"\xa2\x01\n" + | 590 | + | "lastUsedAt\x12'\n" + |
| 591 | + | "\x0fbranch_prefixes\x18\b \x03(\tR\x0ebranchPrefixes\"\xcb\x01\n" + | |||
| 564 | "\x12CreateTokenRequest\x12\x14\n" + | 592 | "\x12CreateTokenRequest\x12\x14\n" + | ||
| 565 | "\x05label\x18\x01 \x01(\tR\x05label\x12%\n" + | 593 | "\x05label\x18\x01 \x01(\tR\x05label\x12%\n" + | ||
| 566 | "\x06scopes\x18\x02 \x03(\x0e2\r.ermite.ScopeR\x06scopes\x12\x14\n" + | 594 | "\x06scopes\x18\x02 \x03(\x0e2\r.ermite.ScopeR\x06scopes\x12\x14\n" + | ||
| 567 | "\x05repos\x18\x03 \x03(\tR\x05repos\x129\n" + | 595 | "\x05repos\x18\x03 \x03(\tR\x05repos\x129\n" + | ||
| 568 | "\n" + | 596 | "\n" + | ||
| 569 | - | "expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\"R\n" + | 597 | + | "expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12'\n" + |
| 598 | + | "\x0fbranch_prefixes\x18\x05 \x03(\tR\x0ebranchPrefixes\"R\n" + | |||
| 570 | "\x13CreateTokenResponse\x12#\n" + | 599 | "\x13CreateTokenResponse\x12#\n" + | ||
| 571 | "\x05token\x18\x01 \x01(\v2\r.ermite.TokenR\x05token\x12\x16\n" + | 600 | "\x05token\x18\x01 \x01(\v2\r.ermite.TokenR\x05token\x12\x16\n" + | ||
| 572 | "\x06secret\x18\x02 \x01(\tR\x06secret\"O\n" + | 601 | "\x06secret\x18\x02 \x01(\tR\x06secret\"O\n" + | ||
M proto/ermite/token.proto +14 -1
| @@ -58,13 +58,19 @@ message Token { | |||
| 58 | 58 | // LastUsedAt is when the token last authenticated a request. Unset if never | |
| 59 | 59 | // used. It may lag behind the real time by a short delay. | |
| 60 | 60 | google.protobuf.Timestamp last_used_at = 7; | |
| 61 | + | // BranchPrefixes limits the branches the token may write. Each prefix ends | |
| 62 | + | // with "/" (for example "agent/claude/"). Empty means every branch. A limited | |
| 63 | + | // token can push, force-push and delete only branches under its prefixes; it | |
| 64 | + | // cannot push tags or other refs, create repositories, or merge into or | |
| 65 | + | // delete a branch outside its prefixes. | |
| 66 | + | repeated string branch_prefixes = 8; | |
| 61 | 67 | } | |
| 62 | 68 | ||
| 63 | 69 | // TokenService manages stored tokens. Every RPC needs the token:admin scope. | |
| 64 | 70 | // | |
| 65 | 71 | // A token can only create tokens that are within its own limits: scopes that | |
| 66 | 72 | // it holds, repositories that it may access and an expiry no later than its | |
| 67 | - | // own. The root token (ERMITE_ADMIN_TOKEN) is not a stored token and is never | |
| 73 | + | // own (including its branch prefixes). The root token (ERMITE_ADMIN_TOKEN) is not a stored token and is never | |
| 68 | 74 | // listed. | |
| 69 | 75 | service TokenService { | |
| 70 | 76 | // CreateToken creates a token and returns its secret. The secret is shown | |
| @@ -92,6 +98,13 @@ message CreateTokenRequest { | |||
| 92 | 98 | // ExpiresAt is when the token stops working. Unset means no expiry. It must | |
| 93 | 99 | // be in the future. | |
| 94 | 100 | google.protobuf.Timestamp expires_at = 4; | |
| 101 | + | // BranchPrefixes limits the branches the token may write, for example | |
| 102 | + | // "agent/claude/". Each prefix must end with "/" and be a valid branch name | |
| 103 | + | // once the "/" is removed; at most 8 are allowed. Empty means every branch; | |
| 104 | + | // a limited token cannot push tags or create repositories. A token that has | |
| 105 | + | // prefixes can only create tokens whose prefixes each lie within its own. | |
| 106 | + | // Fails with invalid_argument when a prefix is malformed. | |
| 107 | + | repeated string branch_prefixes = 5; | |
| 95 | 108 | } | |
| 96 | 109 | ||
| 97 | 110 | // CreateTokenResponse holds the new token and its secret. | |
| @@ -58,13 +58,19 @@ message Token { | |||||
| 58 | // LastUsedAt is when the token last authenticated a request. Unset if never | 58 | // LastUsedAt is when the token last authenticated a request. Unset if never | ||
| 59 | // used. It may lag behind the real time by a short delay. | 59 | // used. It may lag behind the real time by a short delay. | ||
| 60 | google.protobuf.Timestamp last_used_at = 7; | 60 | google.protobuf.Timestamp last_used_at = 7; | ||
| 61 | + | // BranchPrefixes limits the branches the token may write. Each prefix ends | |||
| 62 | + | // with "/" (for example "agent/claude/"). Empty means every branch. A limited | |||
| 63 | + | // token can push, force-push and delete only branches under its prefixes; it | |||
| 64 | + | // cannot push tags or other refs, create repositories, or merge into or | |||
| 65 | + | // delete a branch outside its prefixes. | |||
| 66 | + | repeated string branch_prefixes = 8; | |||
| 61 | } | 67 | } | ||
| 62 | 68 | ||||
| 63 | // TokenService manages stored tokens. Every RPC needs the token:admin scope. | 69 | // TokenService manages stored tokens. Every RPC needs the token:admin scope. | ||
| 64 | // | 70 | // | ||
| 65 | // A token can only create tokens that are within its own limits: scopes that | 71 | // A token can only create tokens that are within its own limits: scopes that | ||
| 66 | // it holds, repositories that it may access and an expiry no later than its | 72 | // it holds, repositories that it may access and an expiry no later than its | ||
| 67 | - | // own. The root token (ERMITE_ADMIN_TOKEN) is not a stored token and is never | 73 | + | // own (including its branch prefixes). The root token (ERMITE_ADMIN_TOKEN) is not a stored token and is never |
| 68 | // listed. | 74 | // listed. | ||
| 69 | service TokenService { | 75 | service TokenService { | ||
| 70 | // CreateToken creates a token and returns its secret. The secret is shown | 76 | // CreateToken creates a token and returns its secret. The secret is shown | ||
| @@ -92,6 +98,13 @@ message CreateTokenRequest { | |||||
| 92 | // ExpiresAt is when the token stops working. Unset means no expiry. It must | 98 | // ExpiresAt is when the token stops working. Unset means no expiry. It must | ||
| 93 | // be in the future. | 99 | // be in the future. | ||
| 94 | google.protobuf.Timestamp expires_at = 4; | 100 | google.protobuf.Timestamp expires_at = 4; | ||
| 101 | + | // BranchPrefixes limits the branches the token may write, for example | |||
| 102 | + | // "agent/claude/". Each prefix must end with "/" and be a valid branch name | |||
| 103 | + | // once the "/" is removed; at most 8 are allowed. Empty means every branch; | |||
| 104 | + | // a limited token cannot push tags or create repositories. A token that has | |||
| 105 | + | // prefixes can only create tokens whose prefixes each lie within its own. | |||
| 106 | + | // Fails with invalid_argument when a prefix is malformed. | |||
| 107 | + | repeated string branch_prefixes = 5; | |||
| 95 | } | 108 | } | ||
| 96 | 109 | ||||
| 97 | // CreateTokenResponse holds the new token and its secret. | 110 | // CreateTokenResponse holds the new token and its secret. | ||