Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Check RFDs
run: make check-rfds

- name: Setup Elixir
uses: erlef/setup-beam@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ rs-test:
.PHONY: lint
lint: ex-lint rs-lint

.PHONY: check-rfds
check-rfds:
@./scripts/check-rfd-status.sh
@./scripts/check-rfd-status-test.sh

.PHONY: ex-lint
ex-lint:
mix credo
Expand Down
16 changes: 16 additions & 0 deletions rfd/0001/IMPLEMENTATION.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#+TITLE: RFD 0001 implementation checklist

Implements [[file:README.adoc][RFD 1: Administration]].

- [ ] A non-admin cannot mount an admin route or obtain admin data by calling a
context function directly.
- [ ] Organization owners and admins have no platform authority unless separately
granted it.
- [ ] An operator can grant the first platform administrator from a release without
manipulating the database manually.
- [ ] Every privilege change, suspension, restoration, and administrative deletion
is audited.
- [ ] Concurrent attempts cannot remove the final platform administrator.
- [ ] Admin list queries are paginated, scoped in SQL, and do not load paste bodies.
- [ ] Authorization, final-admin concurrency, reauthentication, and audit behavior
have context and LiveView tests.
94 changes: 94 additions & 0 deletions rfd/0001/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
:authors: Darwin D Wu <darwin67@users.noreply.github.com>
:state: discussion
:discussion: https://github.com/chaba-dev/textbin/pull/54
:labels: process, software, security

= RFD 1 Administration

== Goal

Give trusted operators a narrowly authorized panel for operating the Textbin
installation without conflating platform authority with organization or
workspace roles.

== Roles and authority

Textbin adds a platform-level `admin` role. It is independent of organization
`owner` and `admin` memberships:

* Organization roles grant authority only inside that organization.
* Platform administrators can operate the installation across organizations.
* A platform administrator receives no implicit organization membership and
must not appear as a workspace collaborator.
* Ordinary users have no platform role. Absence of the role is the default.

Platform authority is stored on the user account or in a dedicated platform
role relation with a database constraint over supported values. Authorization
must not depend on email addresses, configuration allowlists, or UI state.

== Authentication and authorization

The admin LiveViews belong inside the existing
`live_session :require_authenticated_user` and
`[:browser, :require_authenticated_user]` pipeline because every admin route
requires a current user. A platform-admin `on_mount` hook then rejects
non-admins before mounting the page.

Context modules repeat the platform-admin check for every read and mutation so
they remain safe when called by a controller, release task, or future API. A
denied browser request behaves as not found unless showing an explicit forbidden
response is operationally useful; APIs return `403` after authentication.

Destructive and privilege-changing actions require recent reauthentication.
Administrative sessions use the normal session lifetime; there is no separate
permanent admin session or impersonation feature.

== Initial administrator bootstrap

The release exposes an idempotent RPC-compatible function that grants the
platform-admin role to an existing, confirmed user identified by normalized
email. Operators invoke it through the release binary. It must:

. fail if the user does not exist or is not confirmed;
. lock and update the selected account transactionally;
. report whether authority was granted or already present; and
. append an immutable platform audit event naming the actor as the bootstrap
mechanism.

The function does not accept or print passwords. Registration and confirmation
remain the normal account-creation path. Revoking the final platform admin is
rejected unless a replacement is granted in the same operation.

== Panel scope

The first admin panel provides:

* installation totals and recent operational failures;
* user lookup by exact email or ID and account status;
* organization and workspace lookup with membership summaries;
* recent and largest paste metadata, without rendering content by default;
* abuse reports and their resolution state;
* administrative paste deletion;
* account suspension and restoration; and
* platform administrator grant and revocation.

Viewing paste content, impersonating users, editing user content, changing
organization ownership, and reading bearer tokens are not part of the first
version.

== Audit requirements

Every administrative mutation records an append-only platform audit event with
the actor, action, target type and ID, timestamp, request ID when available, and
non-secret structured metadata. Bootstrap events identify the release command
rather than inventing a user actor. Audit records never contain paste content,
passwords, session tokens, API tokens, or storage credentials.

Only platform administrators can read platform audit events. Organization audit
events remain governed by organization authorization and are not a substitute
for the platform log.

== Implementation

Progress against this proposal is tracked in the
link:IMPLEMENTATION.org[RFD 1 implementation checklist].
13 changes: 13 additions & 0 deletions rfd/0002/IMPLEMENTATION.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#+TITLE: RFD 0002 implementation checklist

Implements [[file:README.adoc][RFD 2: Self-hosting documentation]].

- [ ] A fresh operator can identify every required external dependency and durable
path from the guide alone.
- [ ] Local-storage and S3-compatible deployments each have a complete configuration
example and verification procedure.
- [ ] The documented migration, admin-bootstrap, backup, restore, and upgrade
commands execute against the published release image.
- [ ] Documentation clearly separates supported runtime contracts from illustrative
orchestration examples.
- [ ] A restore drill verifies both metadata and external paste content.
51 changes: 51 additions & 0 deletions rfd/0002/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
:authors: Darwin D Wu <darwin67@users.noreply.github.com>
:state: discussion
:discussion: https://github.com/chaba-dev/textbin/pull/54
:labels: process, software, storage

= RFD 2 Self-hosting documentation

== Goal

Document the portable production image well enough that an operator can deploy,
upgrade, back up, restore, and troubleshoot Textbin on their chosen container
platform without requiring Phoenix knowledge.

== Product boundary

Textbin publishes an OCI image and documents its runtime contract. The project
does not maintain production Docker Compose, Kubernetes, Terraform, Helm, or
cloud-provider deployment artifacts. Any snippets are illustrative and must not
be represented as production-ready stacks.

== Required documentation

The self-hosting guide covers:

* supported image tags, architectures, digest pinning, and non-root UID/GID;
* required and optional environment variables with secure example generation;
* PostgreSQL version expectations, connection sizing, migrations, and health;
* local and S3-compatible object storage, permissions, persistence, and
connectivity verification;
* temporary upload space sizing and lifecycle;
* reverse-proxy and direct-TLS topologies, forwarded headers, and health checks;
* first-user registration and platform-admin bootstrap;
* rolling and single-node upgrades, rollback constraints, and migration order;
* coordinated PostgreSQL and blob backup/restore order;
* a restore drill with integrity checks rather than backup creation alone; and
* common startup failures and diagnostics that do not expose secrets.

== Examples

Examples use placeholders, least-privilege credentials, exact image versions,
and explicit persistent mounts. They must not contain reusable passwords or
suggest exposing PostgreSQL or object storage publicly.

A minimal example may show one application process and its dependencies, but it
must state that availability, TLS, secret management, monitoring, and backup
scheduling are operator responsibilities.

== Implementation

Progress against this proposal is tracked in the
link:IMPLEMENTATION.org[RFD 2 implementation checklist].
13 changes: 13 additions & 0 deletions rfd/0003/IMPLEMENTATION.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#+TITLE: RFD 0003 implementation checklist

Implements [[file:README.adoc][RFD 3: Paste discovery and API contract]].

- [ ] Viewer metadata, readable sizes, and line anchors work for anonymous shared
and authenticated private views without weakening authorization.
- [ ] Dashboard and API filters can be combined and retain stable cursor ordering.
- [ ] Search cannot reveal the existence or metadata of an unauthorized paste.
- [ ] Every CLI command's `--json` output is machine-readable and covered by
compatibility tests.
- [ ] API create/show responses contain canonical and raw URLs.
- [ ] Oversized JSON is rejected before full decoding.
- [ ] OpenAPI validation and server contract tests run in CI.
71 changes: 71 additions & 0 deletions rfd/0003/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
:authors: Darwin D Wu <darwin67@users.noreply.github.com>
:state: discussion
:discussion: https://github.com/chaba-dev/textbin/pull/54
:labels: software, api

= RFD 3 Paste discovery and API contract

== Goal

Complete the everyday paste workflow and define one stable API contract used by
the web application, CLI, and generated clients.

== Paste metadata and viewer

Pastes gain optional `title` and `filename` metadata with bounded lengths and no
path semantics. The browser viewer displays title or filename, human-readable
size, and stable line anchors. Anchors update the URL fragment without changing
the paste and remain usable when syntax highlighting is disabled.

Binary pastes remain downloads and are never interpreted as HTML. Metadata in
headers and download filenames is escaped according to the relevant protocol.

== Dashboard filters, list, recent, and search

Owner-visible lists support cursor pagination and filters for syntax,
visibility, expiration state, creator, and workspace. `recent` is the newest
active subset of `list`, not a separate persistence model.

Search is introduced in this order:

. exact/prefix metadata search over title and filename;
. PostgreSQL full-text search for eligible text paste bodies;
. tags and advanced filters only after their data model is specified.

Every query is restricted to workspaces and pastes the current scope may read.
Private content never enters a public search index. Public discovery, if added,
is a separate feature and indexes only public pastes.

== CLI JSON and canonical URLs

Create, show, delete, list, recent, and search support `--json`. JSON mode writes
one documented JSON value to stdout and diagnostics to stderr. It disables
color, browser opening, and human formatting.

Paste representations include canonical `url` and `raw_url` values generated
from endpoint configuration. Dates use RFC 3339 UTC strings, IDs remain strings,
and absent optional values are consistently `null` or omitted as defined by the
OpenAPI schema.

== Request limits

The Plug JSON parser limit is no greater than the application paste limit plus a
small documented metadata allowance. Oversized requests are rejected before
the complete JSON body is decoded. Raw and multipart paths enforce the same
content limit while streaming.

== OpenAPI and clients

OpenAPI covers authentication, token management, paste CRUD, list/recent/search,
raw content, organizations, workspaces, memberships, and invitations. The
document is versioned with the server and checked for drift in CI.

The CLI consumes the stable v1 behavior rather than private endpoints. A
generated TypeScript client is the first supported SDK. Go and Python clients
are deferred until there is demonstrated demand; generation alone does not make
an SDK supported.

== Implementation

Progress against this proposal is tracked in the
link:IMPLEMENTATION.org[RFD 3 implementation checklist].
13 changes: 13 additions & 0 deletions rfd/0004/IMPLEMENTATION.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#+TITLE: RFD 0004 implementation checklist

Implements [[file:README.adoc][RFD 4: Hosted safety and operations]].

- [ ] Limits are enforced consistently across browser, raw API, JSON API, and CLI
traffic.
- [ ] Tests cover proxy trust, distributed-limit behavior, retry headers, and key
isolation.
- [ ] Reports can be submitted without exposing reporter identity publicly and can
be resolved only by platform admins.
- [ ] Logs and traces pass tests that reject known secret and content fields.
- [ ] Metrics avoid unbounded labels and expose background cleanup failures.
- [ ] A documented hosted restore drill proves both database and blob recovery.
72 changes: 72 additions & 0 deletions rfd/0004/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
:authors: Darwin D Wu <darwin67@users.noreply.github.com>
:state: discussion
:discussion: https://github.com/chaba-dev/textbin/pull/54
:labels: software, security, metrics

= RFD 4 Hosted safety and operations

== Goal

Protect a public Textbin installation from accidental overload and abuse while
providing enough telemetry and recovery procedures to operate it safely.

== Upload limits

Limits are evaluated for the authenticated user, API token, source address, and
workspace as applicable. The effective paste-size and expiration limits come
from the most specific entitled scope. A request cannot bypass a workspace
limit by selecting a different client or content encoding.

Guest, free authenticated, and paid limits are configuration or entitlement
data rather than conditionals spread across controllers.

== Rate limiting

Initial rate limits cover authentication attempts, token creation, paste
creation, raw reads, and expensive search operations. Keys include normalized
client address, user ID, token ID, and workspace ID where available.

Client addresses are trusted only through explicitly configured proxy hops.
Production deployments with multiple application replicas use a shared limiter;
an in-memory limiter is acceptable only for documented single-node operation.
Responses use `429`, include `Retry-After`, and do not disclose whether an
account exists.

== Abuse controls and reports

Users can report public or unlisted pastes with a reason category and optional
notes. Reports enter an admin queue with open, actioned, and dismissed states.
Platform admins can remove a paste or suspend an account; both actions require a
reason and create platform audit events.

Private content is not proactively inspected. Automated blocked-content rules,
if introduced, operate on documented signals and retain only the minimum data
needed for enforcement. Shared unlisted pages use `noindex`; private pages are
never indexable.

== Structured logs, metrics, and tracing

Structured request and job logs include request ID, route, status, duration,
error class, storage backend, and relevant non-secret entity IDs. They never
include paste bodies, credentials, authorization headers, or raw session data.

Metrics cover request and upload counts, size and duration histograms, storage
and database errors, rate-limit decisions, report actions, and expiration-job
outcomes. Labels must have bounded cardinality; user, paste, token, request, and
IP values belong in logs, not metric labels.

Tracing is optional and disabled by default. When enabled, trace propagation and
sampling are configurable and spans follow the same content-redaction rules.

== Hosted backups

Hosted environments automate encrypted PostgreSQL and blob backups, record
their completion, and alert on missed schedules. Restore drills run on an
isolated environment and verify metadata-to-blob checksums. Recovery point and
recovery time objectives must be selected before the schedule and retention
policy can be finalized.

== Implementation

Progress against this proposal is tracked in the
link:IMPLEMENTATION.org[RFD 4 implementation checklist].
14 changes: 14 additions & 0 deletions rfd/0005/IMPLEMENTATION.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#+TITLE: RFD 0005 implementation checklist

Implements [[file:README.adoc][RFD 5: Workspace collaboration]].

- [ ] Invitation token theft alone is insufficient when the authenticated email
does not match the invitation.
- [ ] Concurrent acceptance, revocation, and role changes preserve one membership
and current authorization policy.
- [ ] Workspace defaults are enforced identically for browser, user-token, and
workspace-token creation.
- [ ] Workspace tokens cannot cross workspace boundaries or exercise ungranted
scopes.
- [ ] CLI profile selection never redirects a command to another workspace without
explicit user action.
Loading
Loading