defmodule GitGudWeb.RepoAuth do
@moduledoc """
The read gate for every repository-scoped LiveView.
## Why this is a hook and not a check in each mount
It used to be a check in each mount, in the sense that the router
said so — `live_session :public_repo` carried the comment
"Visibility is enforced inside the LiveView itself". It wasn't.
Eighteen of the nineteen browsing LiveViews called
`get_repository_by_path!/2` and rendered whatever came back, so a
private repository's file tree, blobs, commits, issues, pull
requests, wiki and CI logs were readable by anyone who typed the URL.
The listings correctly hid those repositories, which is exactly what
made it hard to notice.
A rule that has to be remembered in nineteen places is a rule that is
eventually forgotten in one. This runs before any of them mount, so a
new repository page is gated by existing, and the only way to opt out
is to put the route somewhere else on purpose.
## Why a 404 and not a 403
A 403 confirms the repository exists. `Ecto.NoResultsError` is what
`get_repository_by_path!/2` raises for a repository that isn't there,
so a private repository and a nonexistent one produce byte-identical
responses and probing tells an attacker nothing.
## Where the real decision lives
`GitGud.Organizations.can?/3` — the same function the mobile API and
the settings pages already use. This module resolves the repository
and asks; it has no opinion of its own about who may read what.
"""
alias GitGud.Organizations
alias GitGud.Repositories
alias GitGud.Repositories.Repository
@doc """
Halt unless the mounting user may read `owner/name`.
Expects `:current_scope` to be assigned already, which the
`UserAuth` hook listed before it does. An unauthenticated visitor has
no user, and `can?/3` answers for `nil` — public and internal
repositories are readable, private ones are not.
"""
def on_mount(:require_read, %{"owner" => owner, "name" => name}, _session, socket) do
user = socket.assigns[:current_scope] && socket.assigns.current_scope.user
case Repositories.find_by_path(owner, name) do
%Repository{} = repo ->
if Organizations.can?(user, repo, :read),
do: {:cont, socket},
else: not_found()
nil ->
not_found()
end
end
# Live sessions hold more than repository routes — the home page, an
# org profile, a user's syms. Those carry no owner/name to check, and
# have their own rules.
def on_mount(:require_read, _params, _session, socket), do: {:cont, socket}
defp not_found, do: raise(Ecto.NoResultsError, queryable: Repository)
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
2.6 KiB · text
History
b868907