oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1package memoria
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7 "fmt"
8
9 "amadan.net/rastrillo/rastrillo/sessions"
10)
11
12var errHomeIdentity = errors.New("memoria: Home identity unavailable")
13
14type homeIdentityQueryer interface {
15 QueryRowContext(context.Context, string, ...any) *sql.Row
16}
17
18const homeAddressFingerprintPurpose = "home.local-address.lookup.v1"
19
20func homeAddressPurpose(id int64) string {
21 return fmt.Sprintf("home.local-address.v1:%d", id)
22}
23
24// ensureHomeLocalIdentity allocates only after independent local verification. The
25// row is an identity binding, not a membership or content ownership record.
26func (a *app) ensureHomeLocalIdentity(ctx context.Context, session sessions.Session) (int64, string, error) {
27 if session.Method != "magiclink" && session.Method != "keymail" {
28 return 0, "", errHomeIdentity
29 }
30 address := normaliseAddress(session.Subject)
31 if address == "" || !a.Admit(address) {
32 return 0, "", errHomeIdentity
33 }
34 fingerprint, err := a.store.sealer.Fingerprint(homeAddressFingerprintPurpose, []byte(address))
35 if err != nil {
36 return 0, "", errHomeIdentity
37 }
38 tx, err := a.db.Writer().BeginTx(ctx, nil)
39 if err != nil {
40 return 0, "", errHomeIdentity
41 }
42 defer tx.Rollback()
43 var id int64
44 err = tx.QueryRowContext(ctx, "SELECT id FROM memoria_home_local_identities WHERE address_lookup=?", fingerprint).Scan(&id)
45 if errors.Is(err, sql.ErrNoRows) {
46 result, insertErr := tx.ExecContext(ctx, "INSERT INTO memoria_home_local_identities(address_lookup,address_sealed) VALUES(?,?)", fingerprint, []byte{})
47 if insertErr != nil {
48 return 0, "", errHomeIdentity
49 }
50 id, err = result.LastInsertId()
51 if err != nil || id <= 0 {
52 return 0, "", errHomeIdentity
53 }
54 sealed, sealErr := a.store.sealer.Seal(homeAddressPurpose(id), []byte(address))
55 if sealErr != nil {
56 return 0, "", errHomeIdentity
57 }
58 if _, err = tx.ExecContext(ctx, "UPDATE memoria_home_local_identities SET address_sealed=? WHERE id=?", sealed, id); err != nil {
59 return 0, "", errHomeIdentity
60 }
61 } else if err != nil {
62 return 0, "", errHomeIdentity
63 }
64 label, err := a.lookupHomeLocalIdentity(ctx, tx, id)
65 if err != nil || label != address || tx.Commit() != nil {
66 return 0, "", errHomeIdentity
67 }
68 return id, label, nil
69}
70
71func (a *app) lookupHomeLocalIdentity(ctx context.Context, q homeIdentityQueryer, id int64) (string, error) {
72 if id <= 0 {
73 return "", sql.ErrNoRows
74 }
75 var fingerprint string
76 var sealed []byte
77 if err := q.QueryRowContext(ctx, "SELECT address_lookup,address_sealed FROM memoria_home_local_identities WHERE id=?", id).Scan(&fingerprint, &sealed); err != nil {
78 return "", err
79 }
80 plain, err := a.store.sealer.Open(homeAddressPurpose(id), sealed)
81 if err != nil {
82 return "", errHomeIdentity
83 }
84 address := string(plain)
85 lookup, err := a.store.sealer.Fingerprint(homeAddressFingerprintPurpose, plain)
86 if err != nil || lookup != fingerprint || address != normaliseAddress(address) {
87 return "", errHomeIdentity
88 }
89 if !a.Admit(address) {
90 return "", sql.ErrNoRows
91 }
92 return address, nil
93}
94
95// localHomeIdentity is read-only: resolving a browser session during a Home
96// exchange cannot allocate an identity or change the account being linked.
97func (a *app) localHomeIdentity(ctx context.Context, session sessions.Session) (int64, string, error) {
98 if session.Method != "magiclink" && session.Method != "keymail" {
99 return 0, "", errHomeIdentity
100 }
101 address := normaliseAddress(session.Subject)
102 if address == "" || !a.Admit(address) {
103 return 0, "", errHomeIdentity
104 }
105 fingerprint, err := a.store.sealer.Fingerprint(homeAddressFingerprintPurpose, []byte(address))
106 if err != nil {
107 return 0, "", errHomeIdentity
108 }
109 var id int64
110 if err = a.db.Writer().QueryRowContext(ctx, "SELECT id FROM memoria_home_local_identities WHERE address_lookup=?", fingerprint).Scan(&id); err != nil {
111 return 0, "", errHomeIdentity
112 }
113 label, err := a.lookupHomeLocalIdentity(ctx, a.db.Writer(), id)
114 if err != nil || label != address {
115 return 0, "", errHomeIdentity
116 }
117 return id, label, nil
118}
119