···11+type request = {delete_after: string option [@key "deleteAfter"] [@default None]}
22+[@@deriving yojson {strict= false}]
33+44+let handler =
55+ Xrpc.handler ~auth:Authorization (fun {req; auth; db; _} ->
66+ let did = Auth.get_authed_did_exn auth in
77+ (* TODO: handle delete_after *)
88+ let%lwt _req = Xrpc.parse_body req request_of_yojson in
99+ let%lwt () = Data_store.deactivate_actor did db in
1010+ let%lwt _ =
1111+ Sequencer.sequence_account db ~did ~active:false ~status:`Deactivated ()
1212+ in
1313+ Dream.empty `OK )
+62
pegasus/lib/api/server/deleteAccount.ml
···11+type request = {did: string; password: string; token: string}
22+[@@deriving yojson {strict= false}]
33+44+let rec rm_rf path =
55+ if Sys.is_directory path then (
66+ Sys.readdir path
77+ |> Array.iter (fun name -> rm_rf (Filename.concat path name)) ;
88+ Sys.rmdir path )
99+ else Sys.remove path
1010+1111+let handler =
1212+ Xrpc.handler (fun {req; db; _} ->
1313+ let%lwt {did; password; token} = Xrpc.parse_body req request_of_yojson in
1414+ match%lwt Data_store.get_actor_by_identifier did db with
1515+ | None ->
1616+ Errors.invalid_request "account not found"
1717+ | Some actor -> (
1818+ let password_hash = actor.password_hash |> Bcrypt.hash_of_string in
1919+ if not (Bcrypt.verify password password_hash) then
2020+ Errors.auth_required "invalid did or password" ;
2121+ match (actor.auth_code, actor.auth_code_expires_at) with
2222+ | Some auth_code, Some auth_expires_at
2323+ when String.starts_with ~prefix:"del-" auth_code
2424+ && token = auth_code
2525+ && Util.now_ms () < auth_expires_at ->
2626+ let%lwt () =
2727+ try%lwt
2828+ Util.use_pool db (fun conn ->
2929+ Util.transact conn (fun () ->
3030+ let open Util.Syntax in
3131+ let$! () =
3232+ Data_store.Queries.delete_reserved_keys_by_did ~did
3333+ conn
3434+ in
3535+ let$! () =
3636+ Data_store.Queries.delete_actor ~did conn
3737+ in
3838+ let user_db_file =
3939+ Util.Constants.user_db_filepath did
4040+ in
4141+ let user_blobs_dir =
4242+ Util.Constants.user_blobs_location did
4343+ in
4444+ ( if Sys.file_exists user_db_file then
4545+ try Sys.remove user_db_file with _ -> () ) ;
4646+ ( if Sys.file_exists user_blobs_dir then
4747+ try rm_rf user_blobs_dir with _ -> () ) ;
4848+ Lwt.return_ok () ) )
4949+ with e ->
5050+ Errors.(
5151+ log_exn e ;
5252+ internal_error ~msg:"failed to delete account" () )
5353+ in
5454+ let%lwt _ =
5555+ Sequencer.sequence_account db ~did ~active:false
5656+ ~status:`Deleted ()
5757+ in
5858+ Dream.empty `OK
5959+ | None, _ | _, None ->
6060+ Errors.invalid_request ~name:"InvalidToken" "token is invalid"
6161+ | _ ->
6262+ Errors.invalid_request ~name:"ExpiredToken" "token is expired" ) )
+19
pegasus/lib/api/server/requestAccountDelete.ml
···11+let handler =
22+ Xrpc.handler ~auth:Authorization (fun {auth; db; _} ->
33+ let did = Auth.get_authed_did_exn auth in
44+ match%lwt Data_store.get_actor_by_identifier did db with
55+ | None ->
66+ Errors.internal_error ~msg:"actor not found" ()
77+ | Some _actor ->
88+ let code =
99+ "del-"
1010+ ^ String.sub
1111+ Digestif.SHA256.(
1212+ digest_string (did ^ Int.to_string @@ Util.now_ms ()) |> to_hex
1313+ )
1414+ 0 8
1515+ in
1616+ let expires_at = Util.now_ms () + (15 * 60 * 1000) in
1717+ let%lwt () = Data_store.set_auth_code ~did ~code ~expires_at db in
1818+ Dream.log "account deletion code for %s: %s" did code ;
1919+ Dream.empty `OK )
+28
pegasus/lib/api/server/reserveSigningKey.ml
···11+type request = {did: string option [@default None]}
22+[@@deriving yojson {strict= false}]
33+44+type response = {signing_key: string [@key "signingKey"]} [@@deriving yojson]
55+66+let handler =
77+ Xrpc.handler (fun {req; db; _} ->
88+ let%lwt {did} = Xrpc.parse_body req request_of_yojson in
99+ let%lwt existing =
1010+ match did with
1111+ | Some did when did <> "" ->
1212+ Data_store.get_reserved_key_by_did ~did db
1313+ | _ ->
1414+ Lwt.return_none
1515+ in
1616+ match existing with
1717+ | Some key ->
1818+ Dream.json @@ Yojson.Safe.to_string
1919+ @@ response_to_yojson {signing_key= key.key_did}
2020+ | None ->
2121+ let privkey, pubkey = Kleidos.K256.generate_keypair () in
2222+ let key_did = Kleidos.K256.pubkey_to_did_key pubkey in
2323+ let private_key = Kleidos.K256.privkey_to_multikey privkey in
2424+ let%lwt () =
2525+ Data_store.create_reserved_key ~key_did ~did ~private_key db
2626+ in
2727+ Dream.json @@ Yojson.Safe.to_string
2828+ @@ response_to_yojson {signing_key= key_did} )
+80
pegasus/lib/data_store.ml
···1818 type invite_code = {code: string; did: string; remaining: int}
19192020 type firehose_event = {seq: int; time: int; t: string; data: bytes}
2121+2222+ type reserved_key =
2323+ { key_did: string
2424+ ; did: string option
2525+ ; private_key: string
2626+ ; created_at: int }
2127end
22282329open Types
···6167 {sql| UPDATE actors SET deactivated_at = NULL WHERE did = %string{did}
6268 |sql}]
63697070+ let deactivate_actor =
7171+ [%rapper
7272+ execute
7373+ {sql| UPDATE actors SET deactivated_at = %int{deactivated_at} WHERE did = %string{did}
7474+ |sql}]
7575+7676+ let delete_actor =
7777+ [%rapper
7878+ execute
7979+ {sql| DELETE FROM actors WHERE did = %string{did}
8080+ |sql}]
8181+6482 let update_actor_handle =
6583 [%rapper
6684 execute
···108126 RETURNING @int{remaining}
109127 |sql}]
110128129129+ (* reserved keys *)
130130+ let create_reserved_key =
131131+ [%rapper
132132+ execute
133133+ {sql| INSERT INTO reserved_keys (key_did, did, private_key, created_at)
134134+ VALUES (%string{key_did}, %string?{did}, %string{private_key}, %int{created_at})
135135+ |sql}]
136136+137137+ let get_reserved_key_by_did did =
138138+ [%rapper
139139+ get_opt
140140+ {sql| SELECT @string{key_did}, @string?{did}, @string{private_key}, @int{created_at}
141141+ FROM reserved_keys WHERE did = %string{did}
142142+ |sql}
143143+ record_out]
144144+ did
145145+146146+ let get_reserved_key key_did =
147147+ [%rapper
148148+ get_opt
149149+ {sql| SELECT @string{key_did}, @string?{did}, @string{private_key}, @int{created_at}
150150+ FROM reserved_keys WHERE key_did = %string{key_did}
151151+ |sql}
152152+ record_out]
153153+ key_did
154154+155155+ let delete_reserved_key =
156156+ [%rapper
157157+ execute
158158+ {sql| DELETE FROM reserved_keys WHERE key_did = %string{key_did}
159159+ |sql}]
160160+161161+ let delete_reserved_keys_by_did =
162162+ [%rapper
163163+ execute
164164+ {sql| DELETE FROM reserved_keys WHERE did = %string{did}
165165+ |sql}]
166166+111167 (* 2fa *)
112168 let set_auth_code =
113169 [%rapper
···218274219275let activate_actor did conn = Util.use_pool conn @@ Queries.activate_actor ~did
220276277277+let deactivate_actor did conn =
278278+ let deactivated_at = Util.now_ms () in
279279+ Util.use_pool conn @@ Queries.deactivate_actor ~did ~deactivated_at
280280+281281+let delete_actor did conn = Util.use_pool conn @@ Queries.delete_actor ~did
282282+221283let update_actor_handle ~did ~handle conn =
222284 Util.use_pool conn @@ Queries.update_actor_handle ~did ~handle
223285···246308let get_invite ~code conn = Util.use_pool conn @@ Queries.get_invite ~code
247309248310let use_invite ~code conn = Util.use_pool conn @@ Queries.use_invite ~code
311311+312312+(* reserved keys *)
313313+let create_reserved_key ~key_did ~did ~private_key conn =
314314+ let created_at = Util.now_ms () in
315315+ Util.use_pool conn
316316+ @@ Queries.create_reserved_key ~key_did ~did ~private_key ~created_at
317317+318318+let get_reserved_key_by_did ~did conn =
319319+ Util.use_pool conn @@ Queries.get_reserved_key_by_did ~did
320320+321321+let get_reserved_key ~key_did conn =
322322+ Util.use_pool conn @@ Queries.get_reserved_key ~key_did
323323+324324+let delete_reserved_key ~key_did conn =
325325+ Util.use_pool conn @@ Queries.delete_reserved_key ~key_did
326326+327327+let delete_reserved_keys_by_did ~did conn =
328328+ Util.use_pool conn @@ Queries.delete_reserved_keys_by_did ~did
249329250330(* 2fa *)
251331let set_auth_code ~did ~code ~expires_at conn =
+8
pegasus/lib/migrations/004_reserved_keys.sql
···11+CREATE TABLE IF NOT EXISTS reserved_keys (
22+ key_did TEXT PRIMARY KEY,
33+ did TEXT,
44+ private_key TEXT NOT NULL,
55+ created_at INTEGER NOT NULL
66+);
77+88+CREATE INDEX IF NOT EXISTS reserved_keys_did_idx ON reserved_keys(did);
+22-8
pegasus/lib/util.ml
···243243 let module C = (val conn : Caqti_lwt.CONNECTION) in
244244 match%lwt C.start () with
245245 | Ok () -> (
246246- match%lwt fn () with
247247- | Ok _ -> (
248248- match%lwt C.commit () with
249249- | Ok () ->
250250- Lwt.return_ok ()
246246+ try%lwt
247247+ match%lwt fn () with
248248+ | Ok _ -> (
249249+ match%lwt C.commit () with
250250+ | Ok () ->
251251+ Lwt.return_ok ()
252252+ | Error e -> (
253253+ match%lwt C.rollback () with
254254+ | Ok () ->
255255+ Lwt.return_error e
256256+ | Error e ->
257257+ Lwt.return_error e ) )
251258 | Error e -> (
252259 match%lwt C.rollback () with
253260 | Ok () ->
254261 Lwt.return_error e
255262 | Error e ->
256256- Lwt.return_error e ) )
257257- | Error e -> (
263263+ Lwt.return_error e )
264264+ with e -> (
258265 match%lwt C.rollback () with
259266 | Ok () ->
260260- Lwt.return_error e
267267+ Lwt.return_error
268268+ ( match e with
269269+ | Caqti_error.Exn e ->
270270+ e
271271+ | e ->
272272+ Caqti_error.request_failed ~query:"unknown"
273273+ ~uri:(Uri.of_string "//unknown")
274274+ (Caqti_error.Msg (Printexc.to_string e)) )
261275 | Error e ->
262276 Lwt.return_error e ) )
263277 | Error e ->