daemon/quarantine.bend
cswap-bend-cli@0.1.0.0 · 140 lines · 22 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/act.bend as Act
4import cswap-bend-cli@0.1.0.0/store/io.bend as SIo
5import cswap-bend-cli@0.1.0.0/daemon/events.bend as Ev
6import cswap-bend-cli@0.1.0.0/daemon/state.bend as St
7import cswap-bend-cli@0.1.0.0/daemon/freshen.bend as Fr
8
9
10
11
12
13type Rel is Data:
14 Rel{slot: String, email: String, reason: String}
15
16def ActM(-A: Type) -> Type:
17 Act.of(A)
18
19def ActM.pure(-A: Type, x: A) -> Act.of(A):
20 Act.pure(A, x)
21
22def ActM.bind(-A: Type, -B: Type, m: Act.of(A), f: A -> Act.of(B)) -> Act.of(B):
23 Act.bind(A, B, m, f)
24
25def fp_opt2(+empty: Bool, +fp: String) -> Maybe<&2, String>:
26 match empty:
27 case True{}:
28 None{}
29 case False{}:
30 Some{fp}
31
32def fp_opt(+fp: String) -> Maybe<&2, String>:
33 fp_opt2(String.is_empty(fp), fp)
34
35def write_q(+root: String, +slot: String, +email: String, +reason: String, +ts: String, +fp: String) -> Act.of(Unit):
36 St.with_state_lock(Unit, root,
37 do ActM<Unit>:
38 st : Json.Json <- Act.lift(Json.Json, St.read_state(root))
39 St.write_state(root, St.quarantine(st, slot, email, reason, ts, fp_opt(fp))))
40
41
42
43def quarantine(+root: String, +slot: String, +email: String, +reason: String, +ts: String) -> Act.of(Json.Json):
44 do ActM<Json.Json>:
45 creds : String <- SIo.read_cred(root, slot, email)
46 fp : String <- Act.lift(String, Fr.fingerprint(creds))
47 write_q(root, slot, email, reason, ts, fp)
48 return Ev.quarantined(ts, slot, email, reason)
49
50
51
52def keep(+m: Maybe<&2, Rel>, +rest: List<&2, Rel>) -> List<&2, Rel>:
53 match m:
54 case Some{r}:
55 r <> rest
56 case None{}:
57 rest
58
59def replaced(+slot: String, +entry_email: String) -> Maybe<&2, Rel>:
60 Some{Rel{slot, entry_email, "account-replaced"}}
61
62def changed(+differs: Bool, +slot: String, +email: String) -> Maybe<&2, Rel>:
63 match differs:
64 case True{}:
65 Some{Rel{slot, email, "credentials-replaced"}}
66 case False{}:
67 None{}
68
69def opt_eq(+a: Maybe<&2, String>, +b: Maybe<&2, String>) -> Bool:
70 match a b:
71 case None{} None{}:
72 True{}
73 case Some{x} Some{y}:
74 String.eq(x, y)
75 case _ _:
76 False{}
77
78def fp_differs(+entry: Maybe<&2, String>, +fp: String) -> Bool:
79 Bool.not(opt_eq(entry, fp_opt(fp)))
80
81def gone(+email_now: String, +entry_email: String) -> Bool:
82 String.is_empty(email_now) || Bool.not(String.eq(email_now, entry_email))
83
84def compare_fp(+root: String, +st: Json.Json, +slot: String, +email_now: String) -> Act.of(Maybe<&2, Rel>):
85 do ActM<Maybe<&2, Rel>>:
86 creds : String <- SIo.read_cred(root, slot, email_now)
87 fp : String <- Act.lift(String, Fr.fingerprint(creds))
88 return changed(fp_differs(St.entry_fp(st, slot), fp), slot, email_now)
89
90def check_slot(+root: String, +seq: Json.Json, +st: Json.Json, +slot: String) -> Act.of(Maybe<&2, Rel>):
91 Act.cond(Maybe<&2, Rel>, gone(SIo.slot_email(seq, slot), St.entry_email(st, slot)),
92 Act.pure(Maybe<&2, Rel>, replaced(slot, St.entry_email(st, slot))),
93 compare_fp(root, st, slot, SIo.slot_email(seq, slot)))
94
95def rels(+root: String, +seq: Json.Json, +st: Json.Json, +slots: List<&2, String>) -> Act.of(List<&2, Rel>):
96 match slots:
97 case Nil{}:
98 Act.pure(List<&2, Rel>, [])
99 case Con{s, t}:
100 do ActM<List<&2, Rel>>:
101 m : Maybe<&2, Rel> <- check_slot(root, seq, st, s)
102 rest : List<&2, Rel> <- rels(root, seq, st, t)
103 return keep(m, rest)
104
105def drop_all(+rs: List<&2, Rel>, +st: Json.Json) -> Json.Json:
106 match rs:
107 case Nil{}:
108 st
109 case Con{Rel{s, e, r}, t}:
110 drop_all(t, St.release(st, s))
111
112def events_of(+ts: String, +rs: List<&2, Rel>) -> List<&2, Json.Json>:
113 match rs:
114 case Nil{}:
115 []
116 case Con{Rel{s, e, r}, t}:
117 Ev.unquarantined(ts, s, e, r) <> events_of(ts, t)
118
119def write_released(+root: String, +rs: List<&2, Rel>) -> Act.of(Unit):
120 St.with_state_lock(Unit, root,
121 do ActM<Unit>:
122 st : Json.Json <- Act.lift(Json.Json, St.read_state(root))
123 St.write_state(root, drop_all(rs, st)))
124
125def release_events(+root: String, +rs: List<&2, Rel>, +ts: String) -> Act.of(List<&2, Json.Json>):
126 match rs:
127 case Nil{}:
128 Act.pure(List<&2, Json.Json>, [])
129 case Con{r, t}:
130 do ActM<List<&2, Json.Json>>:
131 write_released(root, Con{r, t})
132 return events_of(ts, Con{r, t})
133
134
135
136def release_recovered(+root: String, +seq: Json.Json, +ts: String) -> Act.of(List<&2, Json.Json>):
137 do ActM<List<&2, Json.Json>>:
138 +st : Json.Json <- Act.lift(Json.Json, St.read_state(root))
139 rs : List<&2, Rel> <- rels(root, seq, st, St.quarantined_slots(st))
140 release_events(root, rs, ts)