docs: say that owner rights also need no branch prefixes
Fixes the session error text, the web and token revoke help, the CLI skill and the features.go comment (the pre-check is the main guard, the post-check a backstop).
- commit
- 160bcbb875ec211b2c7603af2efc3184dc98f52f
- author
- brice · · 2026-10-12 16:14 UTC
- parent
- e0dab23
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +9 -7
M internal/adapters/client/features.go +4 -2
| @@ -24,8 +24,10 @@ const ( | |||
| 24 | 24 | FeatureSession = "session" | |
| 25 | 25 | // FeatureTokenBranchPrefixes is Token.branch_prefixes and | |
| 26 | 26 | // CreateTokenRequest.branch_prefixes (`ermite token create --branch-prefix`). | |
| 27 | - | // A server without it ignores the field: `token create` checks the | |
| 28 | - | // token it gets back. | |
| 27 | + | // A server without it ignores the field and would create an unlimited | |
| 28 | + | // token: `token create --branch-prefix` reads the features first and | |
| 29 | + | // refuses when it is missing (the main guard), and also checks the token it | |
| 30 | + | // gets back (a backstop). | |
| 29 | 31 | FeatureTokenBranchPrefixes = "token.branch_prefixes" | |
| 30 | 32 | ) | |
| 31 | 33 | ||
| @@ -24,8 +24,10 @@ const ( | |||||
| 24 | FeatureSession = "session" | 24 | FeatureSession = "session" | ||
| 25 | // FeatureTokenBranchPrefixes is Token.branch_prefixes and | 25 | // FeatureTokenBranchPrefixes is Token.branch_prefixes and | ||
| 26 | // CreateTokenRequest.branch_prefixes (`ermite token create --branch-prefix`). | 26 | // CreateTokenRequest.branch_prefixes (`ermite token create --branch-prefix`). | ||
| 27 | - | // A server without it ignores the field: `token create` checks the | 27 | + | // A server without it ignores the field and would create an unlimited |
| 28 | - | // token it gets back. | 28 | + | // token: `token create --branch-prefix` reads the features first and |
| 29 | + | // refuses when it is missing (the main guard), and also checks the token it | |||
| 30 | + | // gets back (a backstop). | |||
| 29 | FeatureTokenBranchPrefixes = "token.branch_prefixes" | 31 | FeatureTokenBranchPrefixes = "token.branch_prefixes" | ||
| 30 | ) | 32 | ) | ||
| 31 | 33 | ||||
M internal/app/cli_token.go +1 -1
| @@ -287,7 +287,7 @@ func newTokenRevokeCmd(env *cliEnv) *cobra.Command { | |||
| 287 | 287 | Use: "revoke <id>", | |
| 288 | 288 | Short: "Revoke a token", | |
| 289 | 289 | Long: "Revoke a token by id (see `ermite token list`). It stops working immediately.\n\n" + | |
| 290 | - | "A token with every scope and no --repo list has the rights of the owner (it can open web\n" + | |
| 290 | + | "A token with every scope, no --repo list and no --branch-prefix has the rights of the owner (it can open web\n" + | |
| 291 | 291 | "sessions). Revoking one also signs every browser out, like `ermite web logout --all`:\n" + | |
| 292 | 292 | "web sessions are not tied to the token that opened them. Sign in again with `ermite web login`.", | |
| 293 | 293 | Args: cobra.ExactArgs(1), | |
| @@ -287,7 +287,7 @@ func newTokenRevokeCmd(env *cliEnv) *cobra.Command { | |||||
| 287 | Use: "revoke <id>", | 287 | Use: "revoke <id>", | ||
| 288 | Short: "Revoke a token", | 288 | Short: "Revoke a token", | ||
| 289 | Long: "Revoke a token by id (see `ermite token list`). It stops working immediately.\n\n" + | 289 | Long: "Revoke a token by id (see `ermite token list`). It stops working immediately.\n\n" + | ||
| 290 | - | "A token with every scope and no --repo list has the rights of the owner (it can open web\n" + | 290 | + | "A token with every scope, no --repo list and no --branch-prefix has the rights of the owner (it can open web\n" + |
| 291 | "sessions). Revoking one also signs every browser out, like `ermite web logout --all`:\n" + | 291 | "sessions). Revoking one also signs every browser out, like `ermite web logout --all`:\n" + | ||
| 292 | "web sessions are not tied to the token that opened them. Sign in again with `ermite web login`.", | 292 | "web sessions are not tied to the token that opened them. Sign in again with `ermite web login`.", | ||
| 293 | Args: cobra.ExactArgs(1), | 293 | Args: cobra.ExactArgs(1), | ||
M internal/app/cli_web.go +1 -1
| @@ -23,7 +23,7 @@ func newWebCmd(env *cliEnv) *cobra.Command { | |||
| 23 | 23 | Use: "web", | |
| 24 | 24 | Short: "Sign the owner in to the web interface of a remote server", | |
| 25 | 25 | Long: "Manage the owner's browser sessions on a remote server. They need the server's admin token, or a\n" + | |
| 26 | - | "token that holds every scope and is not limited to repositories.", | |
| 26 | + | "token that holds every scope and is limited neither to repositories nor to branches.", | |
| 27 | 27 | } | |
| 28 | 28 | web.AddCommand(newWebLoginCmd(env), newWebLogoutCmd(env)) | |
| 29 | 29 | return web | |
| @@ -23,7 +23,7 @@ func newWebCmd(env *cliEnv) *cobra.Command { | |||||
| 23 | Use: "web", | 23 | Use: "web", | ||
| 24 | Short: "Sign the owner in to the web interface of a remote server", | 24 | Short: "Sign the owner in to the web interface of a remote server", | ||
| 25 | Long: "Manage the owner's browser sessions on a remote server. They need the server's admin token, or a\n" + | 25 | Long: "Manage the owner's browser sessions on a remote server. They need the server's admin token, or a\n" + | ||
| 26 | - | "token that holds every scope and is not limited to repositories.", | 26 | + | "token that holds every scope and is limited neither to repositories nor to branches.", |
| 27 | } | 27 | } | ||
| 28 | web.AddCommand(newWebLoginCmd(env), newWebLogoutCmd(env)) | 28 | web.AddCommand(newWebLoginCmd(env), newWebLogoutCmd(env)) | ||
| 29 | return web | 29 | return web | ||
M internal/usecase/sessions.go +2 -2
| @@ -74,13 +74,13 @@ func NewSessions(ctx context.Context, store ports.SessionStore, clock ports.Cloc | |||
| 74 | 74 | } | |
| 75 | 75 | ||
| 76 | 76 | // requireOwner is the access rule of the session API: root, or a stored token | |
| 77 | - | // with every scope and no repository list. | |
| 77 | + | // with every scope, no repository list and no branch prefixes. | |
| 78 | 78 | func requireOwner(p domain.Principal) error { | |
| 79 | 79 | switch { | |
| 80 | 80 | case p.IsAnonymous(): | |
| 81 | 81 | return domain.ErrUnauthenticated | |
| 82 | 82 | case !p.HasOwnerRights(): | |
| 83 | - | return fmt.Errorf("%w: managing web sessions needs the admin token or a token with every scope and no repository list", domain.ErrForbidden) | |
| 83 | + | return fmt.Errorf("%w: managing web sessions needs the admin token or a token with every scope, no repository list and no branch prefixes", domain.ErrForbidden) | |
| 84 | 84 | } | |
| 85 | 85 | return nil | |
| 86 | 86 | } | |
| @@ -74,13 +74,13 @@ func NewSessions(ctx context.Context, store ports.SessionStore, clock ports.Cloc | |||||
| 74 | } | 74 | } | ||
| 75 | 75 | ||||
| 76 | // requireOwner is the access rule of the session API: root, or a stored token | 76 | // requireOwner is the access rule of the session API: root, or a stored token | ||
| 77 | - | // with every scope and no repository list. | 77 | + | // with every scope, no repository list and no branch prefixes. |
| 78 | func requireOwner(p domain.Principal) error { | 78 | func requireOwner(p domain.Principal) error { | ||
| 79 | switch { | 79 | switch { | ||
| 80 | case p.IsAnonymous(): | 80 | case p.IsAnonymous(): | ||
| 81 | return domain.ErrUnauthenticated | 81 | return domain.ErrUnauthenticated | ||
| 82 | case !p.HasOwnerRights(): | 82 | case !p.HasOwnerRights(): | ||
| 83 | - | return fmt.Errorf("%w: managing web sessions needs the admin token or a token with every scope and no repository list", domain.ErrForbidden) | 83 | + | return fmt.Errorf("%w: managing web sessions needs the admin token or a token with every scope, no repository list and no branch prefixes", domain.ErrForbidden) |
| 84 | } | 84 | } | ||
| 85 | return nil | 85 | return nil | ||
| 86 | } | 86 | } | ||
M skills/ermite/CLI.md +1 -1
| @@ -81,5 +81,5 @@ Merge request JSON: `repo`, `number`, `title`, `description`, `source_branch`, ` | |||
| 81 | 81 | | `serve` | configured by `ERMITE_*` environment variables only (see the project README) | | |
| 82 | 82 | | `version [--json]` | client version; with `ERMITE_URL`, the server version and features | | |
| 83 | 83 | | `signing-key show` | public key and `allowed_signers` line to check squash commits with `git verify-commit` | | |
| 84 | - | | `web login [--json]` | one-time sign-in link (about 60 s); needs the admin token or a token with every scope and no repository list; the link is a secret | | |
| 84 | + | | `web login [--json]` | one-time sign-in link (about 60 s); needs the admin token or a token with every scope, no repository list and no branch prefix; the link is a secret | | |
| 85 | 85 | | `web logout --all` | signs every browser out | | |
| @@ -81,5 +81,5 @@ Merge request JSON: `repo`, `number`, `title`, `description`, `source_branch`, ` | |||||
| 81 | | `serve` | configured by `ERMITE_*` environment variables only (see the project README) | | 81 | | `serve` | configured by `ERMITE_*` environment variables only (see the project README) | | ||
| 82 | | `version [--json]` | client version; with `ERMITE_URL`, the server version and features | | 82 | | `version [--json]` | client version; with `ERMITE_URL`, the server version and features | | ||
| 83 | | `signing-key show` | public key and `allowed_signers` line to check squash commits with `git verify-commit` | | 83 | | `signing-key show` | public key and `allowed_signers` line to check squash commits with `git verify-commit` | | ||
| 84 | - | | `web login [--json]` | one-time sign-in link (about 60 s); needs the admin token or a token with every scope and no repository list; the link is a secret | | 84 | + | | `web login [--json]` | one-time sign-in link (about 60 s); needs the admin token or a token with every scope, no repository list and no branch prefix; the link is a secret | |
| 85 | | `web logout --all` | signs every browser out | | 85 | | `web logout --all` | signs every browser out | | ||