Lightning.Projects.Scope (Lightning v2.19.0-pre)

View Source

What standing does an actor have in a project, right now.

fetch/2 establishes facts, not permission: it reports the actor's role, and refuses outright for a project that does not exist or is scheduled for deletion. Whether that standing is enough for an action is the policy's judgement — see Lightning.Policies.ProjectUsers.permitted?/2.

{:ok, %Scope{role: :editor}}
{:ok, %Scope{role: nil}}                      no membership row
{:error, :no_such_project}
{:error, :project_scheduled_for_deletion}
{:error, :connection_not_for_this_project}    repo-connection actor only

So {:ok, _} is not itself an authorisation result — a non-member of a live project gets role: nil, which support access can still make meaningful.

Two support fields, deliberately. support_user? is the raw account flag. support? additionally requires the project's allow_support_access. Anything granting access to a customer's project wants support?.

A %ProjectRepoConnection{} has no membership row, so its standing is which project it belongs to; fetch/2 checks that, and its role is always nil. Ownership is checked before liveness, so a connection learns nothing about a project that is not its own.

Scheduling deletion removes no membership rows and revokes no token, so this refusal is the whole of the offboarding gate during the purge window.

When not to reach for it

  • ProjectUsers self-actions — they act on a specific membership row and ask whether it is the caller's own, which a scope cannot answer.
  • Sandboxes :cancel_scheduled_deletion — its subject is scheduled by definition, so the liveness gate would make restore impossible.
  • Provisioning's %Project{id: nil} clause — no row to read, no members to consult.

Every other project-scoped decision resolves here; no policy module reads Project.scheduled_deletion directly.

Likewise, no policy module reads requires_mfa/mfa_enabled directly. That rule is written once, privately, below; mfa_satisfied? is the only way to reach it.

Summary

Types

Whoever is asking. A %User{} may hold a role; a %ProjectRepoConnection{} never does.

Anything that identifies a project: a loaded %Project{}, a project id, or any struct or map carrying a :project_id — a %Dataclip{}, a %Collection{}, a %Projects.File{}, a %ProjectUser{}.

t()

Functions

The actor's standing in the project.

Whether the user holds one of roles in the project.

Types

actor()

Whoever is asking. A %User{} may hold a role; a %ProjectRepoConnection{} never does.

error()

@type error() ::
  :no_such_project
  | :project_scheduled_for_deletion
  | :connection_not_for_this_project

subject()

@type subject() ::
  Lightning.Projects.Project.t()
  | Ecto.UUID.t()
  | %{:project_id => Ecto.UUID.t(), optional(any()) => any()}
  | nil

Anything that identifies a project: a loaded %Project{}, a project id, or any struct or map carrying a :project_id — a %Dataclip{}, a %Collection{}, a %Projects.File{}, a %ProjectUser{}.

t()

@type t() :: %Lightning.Projects.Scope{
  actor: actor(),
  mfa_satisfied?: boolean(),
  project: Lightning.Projects.Project.t(),
  project_user: Lightning.Projects.ProjectUser.t() | nil,
  role: :owner | :admin | :editor | :viewer | nil,
  support?: boolean(),
  support_user?: boolean()
}

Functions

fetch(user, subject)

@spec fetch(actor(), subject()) :: {:ok, t()} | {:error, error()}

The actor's standing in the project.

Returns {:error, :project_scheduled_for_deletion} for a project that is winding down, {:error, :no_such_project} when the subject does not identify a project that exists, and — for a %ProjectRepoConnection{} actor — {:error, :connection_not_for_this_project} when the resolved project is not the one the connection belongs to.

role_in?(user, subject, roles)

@spec role_in?(Lightning.Accounts.User.t(), subject(), [atom()]) :: boolean()

Whether the user holds one of roles in the project.

false for a project that does not exist or is scheduled for deletion, false for a user with no membership row, and false for a user who has not met the project's MFA requirement. It never consults support?, so a rule that admits a support user has to decide on the %Scope{} itself.