tenseleyflow/shithub / 6cb16b5

Browse files

C4: document HTTPS-git suspended-credential gate in permissions.md

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
6cb16b541c29dd95a3aa9bb8e50ffdda783a28e6
Parents
2fad8e4
Tree
1099f9a

1 changed file

StatusFile+-
M docs/internal/permissions.md 39 0
docs/internal/permissions.mdmodified
@@ -124,6 +124,45 @@ If a handler mutates collaborator state mid-request and re-checks
124124
 policy in the same flight, call `policy.InvalidateRepo(ctx, repoID)`
125125
 between the mutation and the re-check.
126126
 
127
+## Suspended actors and the auth surfaces
128
+
129
+The `IsSuspended` flag on `Actor` is the canonical input the policy
130
+package uses to deny writes by suspended accounts. Each entrypoint
131
+that constructs an actor must source it correctly:
132
+
133
+* **Web (session)** — `middleware.OptionalUser` populates
134
+  `CurrentUser.IsSuspended` from `users.suspended_at`. Handlers pass
135
+  `viewer.IsSuspended` straight into `policy.UserActor`. The lookup
136
+  is run on every request (no cookie-baked state), so an admin
137
+  suspending an account takes effect on the user's next click.
138
+* **Web (PAT)** — `middleware.PATAuthMiddleware` rejects requests
139
+  whose owning user has `suspended_at IS NOT NULL` with a 401 before
140
+  the handler runs. Code paths under PAT auth construct
141
+  `policy.UserActor(..., IsSuspended: false, ...)` because the gate
142
+  is upstream; the field is still passed for honesty and is correct
143
+  by construction.
144
+* **git over HTTPS (`internal/web/handlers/githttp`)** — the basic-
145
+  auth resolver (`auth.go::resolveViaPAT`/`resolveViaPassword`)
146
+  rejects suspended owners with `errBadCredentials` *before* the
147
+  policy check runs, so the `policy.UserActor(..., false, ...)` call
148
+  in `handler.go` never sees a suspended actor. Suspension on the
149
+  HTTPS git path is enforced at credential resolution, not at policy
150
+  evaluation. If the credential resolver is ever reorganised to
151
+  return a populated user even for suspended accounts, propagate the
152
+  flag here.
153
+* **git over SSH (`internal/git/protocol/ssh_dispatch.go`)** — the
154
+  dispatcher loads the user row before constructing the actor and
155
+  passes `user.SuspendedAt.Valid` directly into `policy.UserActor`.
156
+  The `authorized_keys` invocation also rejects up-front (see
157
+  `docs/internal/git-ssh.md`), but the policy call is the
158
+  defence-in-depth layer.
159
+* **post-receive hook (`cmd/shithubd/hook.go`)** — same shape as
160
+  SSH dispatch: load user, pass `SuspendedAt.Valid` into the actor.
161
+
162
+When adding a new auth entrypoint (e.g. an OAuth-bearing webhook
163
+ingest), the rule is: load the user record, source `IsSuspended`
164
+from `users.suspended_at`, and *never* hard-code `false`.
165
+
127166
 ## Site-admin scope
128167
 
129168
 `actor.IsSiteAdmin = true` short-circuits to allow on read actions