@@ -56,14 +56,27 @@ module Prompt = struct
56
56
| "Y" | "y" -> Lwt. return (TTY true )
57
57
| _ -> Lwt. return (TTY false ))
58
58
59
- let input_help_if_user_input () =
59
+ let input_help_if_user_input ?( msg = " Please type the secret and then do Ctrl+d twice to terminate input " ) () =
60
60
match is_TTY with
61
- | true -> Lwt_io. printl " I: reading from stdin. Please type the secret and then do Ctrl+d twice to terminate input "
61
+ | true -> Lwt_io. printl @@ sprintf " I: reading from stdin. %s " msg
62
62
| false -> Lwt. return_unit
63
63
64
64
let read_input_from_stdin ?initial :_ () = Lwt_io. (read stdin)
65
65
66
- let rec input_and_validate_loop ?(transform = fun x -> x) ?initial get_secret_input =
66
+ let validate_secret secret =
67
+ match Secret.Validation. validate secret with
68
+ | Error (e , _typ ) -> Error e
69
+ | _ -> Ok ()
70
+
71
+ let validate_comments comments =
72
+ let comment_has_empty_lines =
73
+ String. trim comments |> String. split_on_char '\n' |> List. map String. trim |> List. mem " "
74
+ in
75
+ match comment_has_empty_lines with
76
+ | true -> Error " secrets cannot have empty lines in the middle of the comments"
77
+ | false -> Ok ()
78
+
79
+ let rec input_and_validate_loop ~validate ?initial get_input =
67
80
let remove_trailing_newlines s =
68
81
(* reverse the string and count leading newlines instead of traversing the string
69
82
multiple times to remove trailing newlines *)
@@ -81,29 +94,29 @@ module Prompt = struct
81
94
let trailing_newlines = count_leading_newlines rev_s in
82
95
String. sub s 0 (String. length s - trailing_newlines)
83
96
in
84
- let % lwt input = get_secret_input ?initial () in
85
- let input = transform input in
97
+ let % lwt input = get_input ?initial () in
86
98
(* Remove bash commented lines from the secret and any trailing newlines *)
87
99
let secret =
88
100
String. split_on_char '\n' input
89
101
|> List. filter (fun line -> not (String. starts_with ~prefix: " #" line))
90
102
|> String. concat " \n "
91
103
|> remove_trailing_newlines
92
104
in
93
- match Secret.Validation. validate secret with
94
- | Error ( e , _typ ) ->
105
+ match validate input with
106
+ | Error e ->
95
107
if is_TTY = false then Shell. die " This secret is in an invalid format: %s" e
96
108
else (
97
109
let % lwt () = Lwt_io. printlf " \n This secret is in an invalid format: %s" e in
98
- if % lwt yesno " Edit again?" then input_and_validate_loop ~initial: input get_secret_input else Lwt. return_error e)
110
+ if % lwt yesno " Edit again?" then input_and_validate_loop ~validate ~initial: input get_input
111
+ else Lwt. return_error e)
99
112
| _ -> Lwt. return_ok secret
100
113
101
114
(* * Gets and validates user input reading from stdin. If the input has the wrong format, the user
102
115
is prompted to reinput the secret with the correct format. Allows passing in a function for input
103
116
transformation. Throws an error if the transformed input doesn't comply with the format and the
104
117
user doesn't want to fix the input format. *)
105
- let get_valid_input_from_stdin_exn ?transform () =
106
- match % lwt input_and_validate_loop ?transform read_input_from_stdin with
118
+ let get_valid_input_from_stdin_exn ?( validate = validate_secret) () =
119
+ match % lwt input_and_validate_loop ~validate read_input_from_stdin with
107
120
| Error e -> Shell. die " This secret is in an invalid format: %s" e
108
121
| Ok secret -> Lwt. return_ok secret
109
122
end
@@ -427,7 +440,8 @@ module Edit_cmd = struct
427
440
Invariant .run_if_recipient ~op_string: "edit secret" ~path: (path_of_secret_name secret_name ) ~f: (fun () ->
428
441
Edit .edit_secret secret_name ~allow_retry: true ~get_updated_secret: (fun initial ->
429
442
Prompt .input_and_validate_loop
430
- (* when we are editing a secret, we know `initial` is Some and we add the format explainer to it *)
443
+ ~validate: Prompt .validate_secret
444
+ (* when we are editing a secret, we know `initial` is Some and we add the format explainer to it *)
431
445
?initial:(Option .map (fun i -> i ^ Secret .format_explainer ) initial )
432
446
(Edit .new_text_from_editor ~name: (show_name secret_name ))))
433
447
@@ -802,7 +816,7 @@ module New = struct
802
816
let create_new_secret secret_name =
803
817
let % lwt () =
804
818
Edit. edit_secret ~self_fallback: true secret_name ~allow_retry: true ~get_updated_secret: (fun initial ->
805
- Prompt. input_and_validate_loop
819
+ Prompt. input_and_validate_loop ~validate: Prompt. validate_secret
806
820
~initial: (Option. value ~default: Secret. format_explainer initial)
807
821
(Edit. new_text_from_editor ~name: (show_name secret_name)))
808
822
in
@@ -901,35 +915,24 @@ module Replace = struct
901
915
| false -> " \n\n " ^ new_secret_plaintext)
902
916
| true ->
903
917
(* if there is already a secret, recreate or replace it *)
904
- let % lwt original_secret' =
905
- (* Get the original secret if we are in the recipient list, otherwise fully replace it *)
906
- try % lwt Storage.Secrets. decrypt_exn ~silence_stderr: true secret_name with _ -> Lwt. return " "
907
- in
908
- let original_secret =
909
- try Ok (Secret.Validation. parse_exn original_secret')
910
- with _e -> Error " failed to parse original secret"
911
- in
912
- let extract_comments ~f ~default secret =
913
- Result. map (fun ({ comments; _ } : Secret.t ) -> Option. map f comments |> Option. value ~default ) secret
914
- |> Result. value ~default
915
- in
916
- (* if the input doesn't have a newline char at the end we need to add one *)
917
- let new_secret_plaintext =
918
- match String. ends_with ~suffix: " \n " new_secret_plaintext with
919
- | true -> new_secret_plaintext
920
- | false -> new_secret_plaintext ^ " \n "
918
+ let % lwt original_secret =
919
+ try % lwt
920
+ let % lwt secret_plaintext = Storage.Secrets. decrypt_exn ~silence_stderr: true secret_name in
921
+ Lwt. return @@ Secret.Validation. parse_exn secret_plaintext
922
+ with _e ->
923
+ Shell. die
924
+ " E: unable to parse secret %s's format. If we proceed, the comments will be lost. Aborting. Please \
925
+ use the edit command to replace and fix this secret."
926
+ (show_name secret_name)
921
927
in
922
928
Lwt. return
923
929
(match is_singleline_secret with
924
930
| true ->
925
- new_secret_plaintext
926
- ^ extract_comments ~f: ( fun comments -> " \n " ^ comments) ~default: " " original_secret
931
+ Secret. singleline_from_text_description new_secret_plaintext
932
+ ( Option. value ~default: " " original_secret.comments)
927
933
| false ->
928
- (* add an empty line before comments and before the secret,
929
- or just an empty line if there are no comments *)
930
- extract_comments ~f: (fun comments -> " \n " ^ comments ^ " \n " ) ~default: " \n " original_secret
931
- ^ " \n "
932
- ^ new_secret_plaintext)
934
+ Secret. multiline_from_text_description new_secret_plaintext
935
+ (Option. value ~default: " " original_secret.comments))
933
936
in
934
937
try % lwt Encrypt. encrypt_exn ~plaintext: updated_secret ~secret_name recipients
935
938
with exn -> Shell. die ~exn " E: encrypting %s failed" (show_name secret_name))
@@ -944,6 +947,71 @@ module Replace = struct
944
947
Cmd. v info term
945
948
end
946
949
950
+ module Replace_comments = struct
951
+ let replace_comment secret_name =
952
+ let recipients = Storage.Secrets. (get_recipients_from_path_exn @@ to_path secret_name) in
953
+ let secret_name_str = show_name secret_name in
954
+ match recipients with
955
+ | [] ->
956
+ Shell. die
957
+ {| E : No recipients found (use " passage {create,new} folder/new_secret_name" to use recipients associated with $ PASSAGE_IDENTITY instead)| }
958
+ secret_name_str
959
+ | _ ->
960
+ Invariant. run_if_recipient ~op_string: " replace comments" ~path: (path_of_secret_name secret_name) ~f: (fun () ->
961
+ let % lwt updated_secret =
962
+ match Storage.Secrets. secret_exists secret_name with
963
+ | false -> Shell. die " E: no such secret: %s" secret_name_str
964
+ | true ->
965
+ let % lwt original_secret =
966
+ try % lwt
967
+ let % lwt original_secret_plaintext = Storage.Secrets. decrypt_exn ~silence_stderr: true secret_name in
968
+ Lwt. return @@ Secret.Validation. parse_exn original_secret_plaintext
969
+ with _e ->
970
+ Shell. die
971
+ " E: unable to parse secret %s's format. Please fix it before replacing the comments,or use the \
972
+ edit command"
973
+ secret_name_str
974
+ in
975
+ let get_comments_from_stdin () =
976
+ let % lwt () =
977
+ Prompt. input_help_if_user_input
978
+ ~msg: " Please type the new comments and then do Ctrl+d twice to terminate input" ()
979
+ in
980
+ let % lwt new_comments = Prompt. read_input_from_stdin () in
981
+ match Prompt. validate_comments new_comments with
982
+ | Error e -> Shell. die " The comments are in an invalid format: %s" e
983
+ | _ -> Lwt. return new_comments
984
+ in
985
+ let get_comments_from_editor () =
986
+ match % lwt
987
+ Prompt. input_and_validate_loop ~validate: Prompt. validate_comments ?initial:original_secret.comments
988
+ (Edit. new_text_from_editor ~name: (show_name secret_name))
989
+ with
990
+ | Error e -> Shell. die " The comments are in an invalid format: %s" e
991
+ | Ok secret -> Lwt. return secret
992
+ in
993
+ let % lwt new_comments =
994
+ match Prompt. is_TTY with
995
+ | false -> get_comments_from_stdin ()
996
+ | true -> get_comments_from_editor ()
997
+ in
998
+ let updated_secret =
999
+ match original_secret.kind with
1000
+ | Secret. Singleline -> Secret. singleline_from_text_description original_secret.text new_comments
1001
+ | Secret. Multiline -> Secret. multiline_from_text_description original_secret.text new_comments
1002
+ in
1003
+ Lwt. return updated_secret
1004
+ in
1005
+ try % lwt Encrypt. encrypt_exn ~plaintext: updated_secret ~secret_name recipients
1006
+ with exn -> Shell. die ~exn " E: encrypting %s failed" secret_name_str)
1007
+
1008
+ let replace_comments =
1009
+ let doc = " replaces the comments of the specified secret, keeping the secret." in
1010
+ let info = Cmd. info " replace-comment" ~doc in
1011
+ let term = main_run Term. (const replace_comment $ Flags. secret_name) in
1012
+ Cmd. v info term
1013
+ end
1014
+
947
1015
module Rm = struct
948
1016
let force =
949
1017
let doc = " Delete secrets and folders without asking for confirmation" in
@@ -1268,6 +1336,7 @@ let () =
1268
1336
Realpath. realpath;
1269
1337
Refresh. refresh;
1270
1338
Replace. replace;
1339
+ Replace_comments. replace_comments;
1271
1340
Rm. rm;
1272
1341
Search. search;
1273
1342
Get. secret;
0 commit comments