~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
93 lines · 2.8 KB Raw History
import { useState } from 'react';
import { Button } from '#/components/atoms/button.jsx';
import { Notice } from '#/components/atoms/notice.jsx';
import { Panel } from '#/components/atoms/panel.jsx';
import { ErrorNotice } from '#/components/molecules/error-notice.jsx';
import { Section } from '#/components/molecules/section.jsx';
import { TextField } from '#/components/molecules/text-field.jsx';
import { post } from '#/lib/api.js';

/**
 * Change the account password. The current password is required even inside
 * a signed-in session, so a stolen cookie alone cannot rotate the
 * credential. Open sessions and app passwords stay valid.
 */
export function PasswordCard() {
  const [current, setCurrent] = useState('');
  const [next, setNext] = useState('');
  const [confirm, setConfirm] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState('');
  const [done, setDone] = useState(false);

  const submit = async (/** @type {import('react').FormEvent} */ event) => {
    event.preventDefault();
    setError('');
    setDone(false);
    if (next !== confirm) {
      setError('The new passwords do not match.');
      return;
    }
    setBusy(true);
    try {
      await post('/password', { currentPassword: current, newPassword: next });
      setDone(true);
      setCurrent('');
      setNext('');
      setConfirm('');
    } catch (caught) {
      setError(String(caught?.message || caught));
    } finally {
      setBusy(false);
    }
  };

  return (
    <Panel>
      <Section
        title="Password"
        description="Signs in to this server and its account pages."
      >
        <form onSubmit={submit} className="flex flex-col gap-4">
          <ErrorNotice error={error} />
          {done && (
            <Notice variant="info">
              Password changed. Open sessions stay signed in.
            </Notice>
          )}
          <TextField
            label="Current password"
            type="password"
            autoComplete="current-password"
            value={current}
            onChange={(e) => setCurrent(e.target.value)}
            required
          />
          <TextField
            label="New password"
            type="password"
            autoComplete="new-password"
            value={next}
            onChange={(e) => setNext(e.target.value)}
            minLength={8}
            hint="At least 8 characters."
            required
          />
          <TextField
            label="Confirm new password"
            type="password"
            autoComplete="new-password"
            value={confirm}
            onChange={(e) => setConfirm(e.target.value)}
            required
          />
          <div>
            <Button type="submit" disabled={busy}>
              Change password
            </Button>
          </div>
        </form>
      </Section>
    </Panel>
  );
}