testAppPasswordsSignInWithoutABrowser

tcmts.atproto.test.testAppPasswordsSignInWithoutABrowser · #fjvnn3yw · term

With an app password in TCM_ATPROTO_APP_PASSWORD, a sign-in needs no browser and covers any scope; it's kept with the password, so later sign-ins, for any scope and with no password given, need nothing, and when its tokens are gone it signs in again by itself. A wrong password is refused, and nothing kept.

import { APP_PASSWORD_ENV, atprotoSession, pdsFetch, readSessions } from "#codebase/tcmts/atproto";
import { startFakeAtproto } from "#codebase/tcmts/cli/test/e2e/examples";
import { tempDir, withChildren } from "#codebase/tcmts/cli/test/e2e/harness";
import { withEnv } from "#codebase/tcmts/registry/test";
import assert from "node:assert/strict";

export const testAppPasswordsSignInWithoutABrowser = () =>
  withChildren(async (keep) => {
    const alice = {
      did: "did:plc:alice000000000000000000",
      handle: "alice.test",
      password: "a-pass",
    };
    const net = await startFakeAtproto(keep, [alice]);
    const write = async (s: Awaited<ReturnType<typeof atprotoSession>>, rkey: string) =>
      (
        await pdsFetch(s, "/xrpc/com.atproto.repo.createRecord", {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({
            repo: s.did,
            collection: "codes.tcm.codebase",
            rkey,
            record: { $type: "codes.tcm.codebase", name: rkey },
          }),
        })
      ).status;
    await withEnv(
      {
        TCM_ATPROTO_DIRECTORY: net.url,
        TCM_ATPROTO_LOCAL: "1",
        XDG_CONFIG_HOME: tempDir("app password sign-ins"),
        BROWSER: "false",
      },
      async () => {
        const ask = (scope: string) =>
          atprotoSession({ who: "alice.test", scope, why: "testing", tell: () => {} });
        // a wrong password: refused, nothing kept
        await withEnv({ [APP_PASSWORD_ENV]: "wrong" }, async () => {
          await assert.rejects(ask("atproto repo:codes.tcm.codebase"), /app password: Invalid/);
        });
        assert.deepEqual(readSessions().sessions, {});
        // the right one: signed in, no browser, and it writes
        const s = await withEnv({ [APP_PASSWORD_ENV]: "a-pass" }, () =>
          ask("atproto repo:codes.tcm.codebase"),
        );
        assert.equal(s.did, alice.did);
        assert.equal(s.dpopKey, undefined);
        assert.equal(net.oauth.signIns, 0);
        assert.equal(await write(s, "one"), 200);
        assert.ok(net.records.get(`at://${alice.did}/codes.tcm.codebase/one`));
        // kept: asked again, for another scope and with no password given, it's the same sign-in
        const again = await ask("atproto repo:codes.tcm.release");
        assert.equal(again.appPassword, "a-pass");
        // its tokens gone: it signs in again with the password, by itself
        again.accessToken = "expired";
        again.refreshToken = "gone";
        assert.equal(await write(again, "two"), 200);
        assert.equal(readSessions().sessions[alice.did]!.accessToken, `acc-${alice.did}`);
        assert.equal(net.oauth.signIns, 0);
      },
    );
  })

uses