daemon/freshen.bend
cswap-bend-cli@0.1.0.0 · 169 lines · 27 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/store/base.bend as SBase
6import cswap-bend-cli@0.1.0.0/usage/cred.bend as UCred
7import cswap-bend-cli@0.1.0.0/usage/gate.bend as UGate
8import cswap-bend-cli@0.1.0.0/daemon/sess.bend as Sess
9import cswap-bend-cli@0.1.0.0/daemon/identity.bend as Id
10
11
12
13
14def ActM(-A: Type) -> Type:
15 Act.of(A)
16
17def ActM.pure(-A: Type, x: A) -> Act.of(A):
18 Act.pure(A, x)
19
20def ActM.bind(-A: Type, -B: Type, m: Act.of(A), f: A -> Act.of(B)) -> Act.of(B):
21 Act.bind(A, B, m, f)
22
23
24
25def hex_of(r: Result<&1, &1, U32 & String, U32 & (String & String)>) -> String:
26 match r:
27 case Fail{e}:
28 ""
29 case Done{(st, (out, err))}:
30 Maybe.default(&2, String, List.head(&2, String, String.split(String.trim(out), ' ')), "")
31
32
33def sha256(+text: String) -> IO(String):
34 IO.bind(Result<&1, &1, U32 & String, U32 & (String & String)>, String, Process.run("shasum", ["-a", "256"], text, 4096, 5000), r => IO.pure(String, hex_of(r)))
35
36def tagged(+tag: String, +h: String) -> String:
37 tag ++ h
38
39def hashed(+tag: String, +text: String) -> IO(String):
40 IO.bind(String, String, sha256(text), h => IO.pure(String, tagged(tag, h)))
41
42def fp_pick(+rt: Maybe<&2, String>, +creds: String) -> IO(String):
43 match rt:
44 case Some{t}:
45 hashed("sha256:", t)
46 case None{}:
47 hashed("sha256-full:", creds)
48
49def fp_nonempty(+empty: Bool, +creds: String) -> IO(String):
50 match empty:
51 case True{}:
52 IO.pure(String, "")
53 case False{}:
54 fp_pick(UCred.refresh_token(UCred.oauth.of(creds)), creds)
55
56
57
58
59
60def fingerprint(+creds: String) -> IO(String):
61 fp_nonempty(String.is_empty(creds), creds)
62
63def has_oauth(+o: Json.Json) -> Bool:
64 match o:
65 case Json.JObj{f}:
66 True{}
67 case _:
68 False{}
69
70def rank2(+a: Bool, +b: Bool, +c: Bool, +d: Bool) -> U32:
71 match a b c d:
72 case True{} _ _ _:
73 1
74 case False{} True{} _ _:
75 2
76 case False{} False{} True{} _:
77 3
78 case False{} False{} False{} True{}:
79 4
80 case _ _ _ _:
81 0
82
83
84
85def systemic_rank(+s: String) -> U32:
86 rank2(String.eq(s, "store-unmirrored"), String.eq(s, "invalid_client"), String.eq(s, "stash-unreadable"), String.eq(s, "consume-busy"))
87
88def transient_or_systemic(+plain: Bool, +e: String) -> String:
89 match plain:
90 case True{}:
91 "transient"
92 case False{}:
93 e
94
95def kind_of(+dead: Bool, +e: String) -> String:
96 match dead:
97 case True{}:
98 "invalid_grant"
99 case False{}:
100 transient_or_systemic(U32.is_eq(systemic_rank(e), 0), e)
101
102def refresh_status(+error: Maybe<&2, String>) -> String:
103 match error:
104 case None{}:
105 "ok"
106 case Some{e}:
107 kind_of(String.eq(e, "invalid_grant") || String.eq(e, "no_refresh_token"), e)
108
109def judged_grant(+error: Maybe<&2, String>, +root: String, +slot: String, +acct: Json.Json) -> Act.of(String):
110 match error:
111 case None{}:
112 Id.check(root, slot, acct)
113 case Some{e}:
114 Act.pure(String, refresh_status(Some{e}))
115
116
117def after_refresh(g: UGate.Gated, +root: String, +slot: String) -> Act.of(String):
118 match g:
119 case UGate.Gated{cred, error, consumed, acct}:
120 judged_grant(error, root, slot, acct)
121
122def refresh_now(+root: String, +slot: String, +email: String, +creds: String) -> Act.of(String):
123 do ActM<String>:
124 g : UGate.Gated <- Act.lift(UGate.Gated, UGate.refresh_gated(slot, email, creds, root))
125 after_refresh(g, root, slot)
126
127def check2(+oauth: Bool, +near: Bool, +root: String, +slot: String, +email: String, +creds: String) -> Act.of(String):
128 match oauth near:
129 case False{} _:
130 Act.pure(String, "invalid_grant")
131 case True{} True{}:
132 refresh_now(root, slot, email, creds)
133 case True{} False{}:
134 Act.pure(String, "ok")
135
136def check(+root: String, +slot: String, +email: String, +creds: String, +now: U32) -> Act.of(String):
137 check2(has_oauth(UCred.oauth.of(creds)), UCred.expired(UCred.oauth.of(creds), (now + 300 : U32)), root, slot, email, creds)
138
139def judge2(+empty: Bool, +root: String, +slot: String, +email: String, +creds: String, +now: U32) -> Act.of(String):
140 match empty:
141 case True{}:
142 Act.pure(String, "transient")
143 case False{}:
144 check(root, slot, email, creds, now)
145
146def judge(+root: String, +slot: String, +email: String, +creds: String, +now: U32) -> Act.of(String):
147 judge2(String.is_empty(creds), root, slot, email, creds, now)
148
149
150
151def judge4(+live: Bool, +root: String, +slot: String, +email: String, +creds: String, +now: U32) -> Act.of(String):
152 Act.cond(String, live, Act.pure(String, "skip-live-session"), judge(root, slot, email, creds, now))
153
154
155def judge5(+root: String, +slot: String, +email: String, +creds: String, +now: U32) -> Act.of(String):
156 do ActM<String>:
157 live : Bool <- Act.lift(Bool, Sess.live(root, slot, email))
158 judge4(live, root, slot, email, creds, now)
159
160def judge3(+api: Bool, +root: String, +slot: String, +email: String, +creds: String, +now: U32) -> Act.of(String):
161 Act.cond(String, api, Act.pure(String, "ok"), judge5(root, slot, email, creds, now))
162
163
164
165
166def status(+root: String, +slot: String, +email: String, +now: U32) -> Act.of(String):
167 do ActM<String>:
168 +creds : String <- SIo.read_cred(root, slot, email)
169 judge3(SBase.looks_like_api_key(creds), root, slot, email, creds, now)