aboutsummaryrefslogtreecommitdiffstats
path: root/lib/changeid.ts
blob: 1c6a668837804875a5a06ab3077f5612afaba99f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { db, g, s, ga, sa, archive as arc } from "$lib/db";
import { err, l } from "$lib/logging";
import { randomUUID } from "crypto";

export default async function changeid(un: string) {
  un = un.toLowerCase();
  const id = await g(`user:${un}`);
  if (!id) throw new Error(`user ${un} not found`);

  l(`SECURITY: rekeying ${un}, old id ${id}`);

  const nid = randomUUID();
  const user = await g(`user:${id}`);
  if (!user) throw new Error(`user record ${id} not found`);

  user.id = nid;
  user.password = "reset";

  if (user.pubkey) {
    await s(user.pubkey, nid);
    await sa(user.pubkey, nid);
  }
  await s(`user:${un}`, nid);
  await sa(`user:${un}`, nid);
  await s(`user:${nid}`, user);
  await sa(`user:${nid}`, user);

  const keys = [
    `credit:lightning:${id}`,
    `credit:liquid:${id}`,
    `${id}:invoices`,
    `credit:bitcoin:${id}`,
    `pending:${id}`,
    `${id}:lastlen`,
    `${id}:contacts`,
    `${id}:accounts`,
    `${id}:payments`,
    `account:${id}`,
    `balance:${id}`,
    `user:${id}`,
  ];

  async function migrateKeys(client, get, set, label) {
    for (const k of keys) {
      const nk = k.replace(id, nid);
      try {
        if (
          k.includes("credit:") ||
          k.includes("balance:") ||
          k.includes("pending:")
        ) {
          const a = await get(k);
          await client.incrBy(nk, parseInt(a) || 0);
          await client.del(k);
        } else if (k.includes(":invoices") || k.includes(":payments")) {
          const arr = await client.lRange(k, 0, -1);
          const narr = [];
          for (const pid of arr) {
            const ok = `${k.split(":")[1].slice(0, -1)}:${pid}`;
            const o = await get(ok);
            if (!o) continue;
            o.id = pid;
            o.uid = nid;
            if (o.aid === id) o.aid = nid;
            await set(ok, o);
            narr.push(o);
          }

          await client.del(k);

          for (const o of narr.sort((a, b) => b?.created - a?.created)) {
            if (o) await client.rPush(nk, o.id);
          }
        } else if (k.includes("account:")) {
          const acc = await get(k);
          if (!acc) continue;
          acc.id = nid;
          await set(nk, acc);
          await client.del(k);
        } else if (k.includes("user:")) {
          await client.del(k);
        } else {
          if (await client.exists(k)) await client.rename(k, nk);
        }
      } catch (e: any) {
        err(`changeid [${label}]`, k, e.message);
      }
    }

    await client.lRem(`${nid}:accounts`, 0, id);
    await client.rPush(`${nid}:accounts`, nid);
  }

  await migrateKeys(db, g, s, "main");
  await migrateKeys(arc, ga, sa, "arc");

  const apps = await db.sMembers(`${id}:apps`);
  for (const aid of apps) {
    await db.del(`app:${aid}`);
  }
  await db.del(`${id}:apps`);

  // Update uid/aid in invoice objects and their offer pointers
  const invoiceIds = await db.lRange(`${nid}:invoices`, 0, -1);
  for (const iid of invoiceIds) {
    try {
      const inv = await g(`invoice:${iid}`);
      if (!inv || typeof inv === "string") continue;
      let changed = false;
      if (inv.uid === id) { inv.uid = nid; changed = true; }
      if (inv.aid === id) { inv.aid = nid; changed = true; }
      if (changed) {
        await s(`invoice:${iid}`, inv);
        l("changeid: updated invoice", iid);
      }
    } catch (e) {}
  }

  l(`SECURITY: rekeyed ${un} from ${id} to ${nid}`);
  return nid;
}