paste-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 01fdb2b1f66e
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 6eca480d6447d873e2e5eed9
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/Admin.tsx
// A paste's private admin page (/<id>/admin#k=<owner key>, plus &e=<key> when encrypted with a key in the link):
// edit or delete it. Reading here never counts as a view, so a burn-after-reading paste stays unread.
import { useEffect, useMemo, useRef, useState, type FormEvent } from "react";
import { navigate } from "./lib/router";
import { openPaste, updatePaste, deletePaste, type Paste } from "./lib/store";
import { open as unseal, seal, openWithPassword, sealWithPassword, type Sealed } from "./lib/crypto";
import { savePaste, savedPaste, forgetPaste } from "./lib/mine";
import { HOST, shareUrl, adminLink, expiryText, explain } from "./lib/format";
import { copy, share, canShare, toast } from "./ui";
import { AdminSkeleton } from "./Skeletons";
import { LANGUAGES, MAX_BYTES, checkContent, checkTitle, byteLength, formatBytes, isPasswordLocked } from "../functions/lib/paste";

type State =
  | { kind: "opening" }
  | { kind: "nokey" }
  | { kind: "gone"; forgotten: boolean }
  | { kind: "error"; message: string }
  | { kind: "deleted" }
  | { kind: "ready"; paste: Paste; open: Sealed | null; key: string; secret?: string };

export default function AdminPage({ id }: { id: string }) {
  const [state, setState] = useState<State>({ kind: "opening" });
  // Pasting the full admin link into a tab already on this page changes only the hash, so read the key again.
  const [hash, setHash] = useState(location.hash);
  useEffect(() => {
    const onHash = () => setHash(location.hash);
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);

  useEffect(() => {
    setState({ kind: "opening" });
    const params = new URLSearchParams(hash.slice(1));
    const saved = savedPaste(id);
    const key = params.get("k") || saved?.key || "";
    const secret = params.get("e") || saved?.secret || undefined;
    if (!key) { setState({ kind: "nokey" }); return; }
    let live = true;
    (async () => {
      const paste = await openPaste(id, key);
      if (!live) return;
      if (!paste) {
        // This browser made it with this key, so it is gone (expired, deleted, or opened once).
        const forgotten = saved?.key === key;
        if (forgotten) forgetPaste(id);
        return setState({ kind: "gone", forgotten });
      }
      const password = isPasswordLocked(paste);
      let open: Sealed | null = paste.encrypted ? null : paste;
      if (paste.encrypted && !password && secret) open = await unseal(paste.content, secret).catch(() => null);
      if (!live) return;
      savePaste({ id, key, title: open?.title ?? saved?.title ?? "", language: open?.language ?? saved?.language ?? "text", createdAt: paste.createdAt, ttl: paste.ttl, burn: paste.burn, secret: open ? secret : undefined, password: password || undefined });
      setState({ kind: "ready", paste, open, key, secret: open ? secret : undefined });
    })().catch((err) => { if (live) setState({ kind: "error", message: explain(err) }); });
    return () => { live = false; };
  }, [id, hash]);

  useEffect(() => { document.title = "Manage paste | Paste"; }, []);

  if (state.kind === "opening") return <AdminSkeleton />;
  if (state.kind === "error") return (
    <div className="wrap page-pad narrow message enter">
      <h1 className="h1">This paste could not be opened.</h1>
      <p className="lede">{state.message}</p>
      <button className="btn" type="button" onClick={() => location.reload()}>Try again</button>
    </div>
  );
  if (state.kind === "ready") return <AdminView id={id} paste={state.paste} initial={state.open} ownerKey={state.key} secret={state.secret} onDeleted={() => setState({ kind: "deleted" })} />;
  const [title, text] =
    state.kind === "nokey" ? ["This admin link is missing its key.", "Open the full admin link you saved when you made the paste. It ends with #k= and a long code."]
    : state.kind === "deleted" ? ["The paste is deleted.", "Its link no longer works, for anyone."]
    : state.forgotten ? ["This paste is gone.", "It expired, was deleted, or was opened once after being set to delete after the first view. It is off your list now."]
    : ["This admin link does not work.", "The paste may be gone, or the admin link was cut short. Check that you copied all of it."];
  return (
    <div className="wrap page-pad narrow message enter">
      <h1 className="h1">{title}</h1>
      <p className="lede">{text}</p>
      <a className="btn" href="/" onClick={(e) => { e.preventDefault(); navigate("/"); }}>Make a paste</a>
    </div>
  );
}

function AdminView({ id, paste, initial, ownerKey, secret, onDeleted }: {
  id: string; paste: Paste; initial: Sealed | null; ownerKey: string; secret?: string; onDeleted: () => void;
}) {
  const password = isPasswordLocked(paste);
  const empty = { title: "", language: "text", content: "" };
  const [saved, setSaved] = useState<Sealed>(initial ? { ...initial, language: initial.language || "text" } : empty);
  const [draft, setDraft] = useState(saved);
  // A password paste opens for editing once its password is typed here; it is never stored.
  const [unlocked, setUnlocked] = useState<string | null>(null);
  const locked = paste.encrypted && !initial && !unlocked;
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [confirming, setConfirming] = useState(false);
  const link = shareUrl(id, secret);
  const bytes = useMemo(() => byteLength(draft.content), [draft.content]);
  const changed = draft.title !== saved.title || draft.language !== saved.language || draft.content !== saved.content;

  // Leaving with unsaved edits asks first.
  useEffect(() => {
    if (!changed) return;
    const warn = (e: BeforeUnloadEvent) => { e.preventDefault(); e.returnValue = ""; };
    window.addEventListener("beforeunload", warn);
    return () => window.removeEventListener("beforeunload", warn);
  }, [changed]);

  const save = async (e: FormEvent) => {
    e.preventDefault();
    const c = checkContent(draft.content);
    if ("error" in c) return setError(c.error);
    const t = checkTitle(draft.title);
    if ("error" in t) return setError(t.error);
    setBusy(true);
    setError(null);
    try {
      const next = { title: t.title, language: draft.language, content: c.content };
      const content = unlocked ? await sealWithPassword(next, unlocked) : secret ? await seal(next, secret) : null;
      await updatePaste(id, content ? { content } : next);
      setSaved(next);
      setDraft(next);
      savePaste({ id, key: ownerKey, title: next.title, language: next.language, createdAt: paste.createdAt, ttl: paste.ttl, burn: paste.burn, secret, password: password || undefined });
      toast("Saved. The link shows the new version now.");
    } catch (err) {
      setError(explain(err));
    } finally {
      setBusy(false);
    }
  };

  const remove = async () => {
    setBusy(true);
    try {
      await deletePaste(id);
      forgetPaste(id);
      onDeleted();
    } catch (err) {
      setError(explain(err));
      setBusy(false);
    }
  };

  const view = `/${id}${secret ? `#${secret}` : ""}`;
  return (
    <div className="admin enter">
      <header className="edit-head">
        <div className="wrap edit-head-inner">
          <div className="edit-head-text">
            <p className="eyebrow">Manage your paste</p>
            <h1 className="edit-title">{saved.title || (locked ? "Locked paste" : "Untitled paste")}</h1>
            <p className="muted small edit-link">{HOST}/{id}</p>
          </div>
          <div className="row edit-head-actions">
            <button className="btn" type="button" onClick={() => share(link, saved.title || "Paste", "Link copied")} disabled={paste.encrypted && !password && !secret}>
              {canShare() ? "Share link" : "Copy link"}
            </button>
            {!paste.burn && !(paste.encrypted && !password && !secret) && (
              <a className="btn btn-line" href={view} onClick={(e) => { e.preventDefault(); if (!changed || confirm("Leave without saving your changes?")) navigate(view); }}>Open</a>
            )}
          </div>
        </div>
      </header>

      <div className="wrap edit-body">
        {locked && password ? (
          <UnlockForEdit paste={paste} onOpen={(open, pw) => { const next = { ...open, language: open.language || "text" }; setSaved(next); setDraft(next); setUnlocked(pw); }} />
        ) : (
          <form className="card pad stack" onSubmit={save} noValidate aria-label="Edit the paste">
            {locked && <p className="notice">This paste is encrypted, and this admin link does not carry its key. Open the admin link that ends with &amp;e= and a long code to edit it. You can still delete it.</p>}
            <div className="admin-fields">
              <label className="field">
                <span>Title</span>
                <input className="input" value={draft.title} onChange={(e) => setDraft({ ...draft, title: e.target.value })} maxLength={200} placeholder="Untitled paste" disabled={locked} autoComplete="off" />
              </label>
              <label className="field">
                <span>Language</span>
                <select className="select" value={draft.language} onChange={(e) => setDraft({ ...draft, language: e.target.value })} disabled={locked}>
                  {LANGUAGES.map((l) => <option key={l.id} value={l.id}>{l.name}</option>)}
                </select>
              </label>
            </div>
            <label className="field">
              <span>Text</span>
              <textarea className="textarea admin-text" value={draft.content} onChange={(e) => setDraft({ ...draft, content: e.target.value })}
                spellCheck={false} autoCapitalize="off" autoComplete="off" autoCorrect="off" disabled={locked} placeholder={locked ? "Encrypted" : ""} />
              {!locked && <small className={bytes > MAX_BYTES ? "warn" : ""}>{formatBytes(bytes)} of 512 KB</small>}
            </label>
            {error && <p className="notice notice-error" role="alert">{error}</p>}
            <div className="row">
              <button className="btn" type="submit" disabled={locked || !changed || bytes > MAX_BYTES} aria-busy={busy || undefined}><span>Save changes</span></button>
              {changed && <button className="btn btn-quiet" type="button" onClick={() => { setDraft(saved); setError(null); }}>Discard changes</button>}
            </div>
          </form>
        )}

        <aside className="card pad admin-side">
          <h2 className="h3">Details</h2>
          <dl className="details">
            <div><dt>Made</dt><dd>{new Date(paste.createdAt * 1000).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" })}</dd></div>
            <div><dt>Expiry</dt><dd>{expiryText(paste)}{paste.burn ? ". Not opened yet" : ""}</dd></div>
            <div><dt>Who can open it</dt><dd>{password ? "Anyone with the link and the password." : "Anyone with the link."} Nobody can list pastes.</dd></div>
            {paste.encrypted && <div><dt>Encryption</dt><dd>{password ? "Locked with a password in the browser." : "Encrypted in the browser, with the key in the link."} The server holds only scrambled text.</dd></div>}
          </dl>
          <div className="danger-zone">
            {confirming ? (
              <div className="stack enter" style={{ "--gap": "10px" } as React.CSSProperties}>
                <p>Delete this paste for everyone? This cannot be undone.</p>
                <div className="row">
                  <button className="btn btn-sm btn-danger btn-line" type="button" onClick={remove} aria-busy={busy || undefined}><span>Delete for good</span></button>
                  <button className="btn btn-sm btn-quiet" type="button" onClick={() => setConfirming(false)}>Keep it</button>
                </div>
              </div>
            ) : (
              <button className="btn btn-sm btn-line btn-danger" type="button" onClick={() => setConfirming(true)}>Delete paste</button>
            )}
          </div>
        </aside>

        <div className="notice edit-key">
          <p><strong>This page's address is your private admin link.</strong> Bookmark it, or keep it somewhere safe. It works on any device, and anyone with it can change or delete the paste.</p>
          <button className="btn btn-sm btn-line" type="button" onClick={() => copy(adminLink({ id, key: ownerKey, secret }), "Admin link copied")}>Copy admin link</button>
        </div>
      </div>
    </div>
  );
}

function UnlockForEdit({ paste, onOpen }: { paste: Paste; onOpen: (open: Sealed, password: string) => void }) {
  const input = useRef<HTMLInputElement>(null);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const submit = async (e: FormEvent) => {
    e.preventDefault();
    const pw = input.current?.value ?? "";
    if (!pw) return setError("Type the paste's password.");
    setBusy(true);
    setError(null);
    try {
      onOpen(await openWithPassword(paste.content, pw), pw);
    } catch {
      setError("That password does not open this paste.");
      setBusy(false);
    }
  };
  return (
    <form className="card pad stack" onSubmit={submit} noValidate aria-label="Unlock to edit">
      <h2 className="h3">Locked with a password</h2>
      <p className="muted">Type the password to edit the text. You can delete the paste without it.</p>
      <label className="field">
        <span>Password</span>
        <input ref={input} className="input" type="password" autoComplete="off" autoCapitalize="off" spellCheck={false} enterKeyHint="go"
          aria-invalid={error ? true : undefined} aria-describedby={error ? "admin-unlock-error" : undefined} onChange={() => setError(null)} />
        {error && <span className="field-error" id="admin-unlock-error" role="alert">{error}</span>}
      </label>
      <div className="row"><button className="btn" type="submit" aria-busy={busy || undefined}><span>Unlock to edit</span></button></div>
    </form>
  );
}

Change history

Quality pass: password lock, year expiry, line links, text/plain API
01fdb2b1f6 · 4 hours ago
Bounded source sync
fe5788c2d2 · 1 day ago
Bounded source sync
ac6f7dbc95 · 1 day ago
Bounded source sync
0a45926293 · 1 day ago