feat: share env files as encrypted links #54
No files matched your search
@@ -40,6 +40,7 @@ type option struct {
|
||||
group string // the source row it's in (search)
|
||||
kind provider.Kind // the kind of provider it's from (search)
|
||||
hits []int // characters of the label the filter matched (Go to)
|
||||
plain bool // a key in a plain env file: no key to encrypt with (Go to)
|
||||
run func(m *Model) tea.Cmd
|
||||
// header makes this a section title: not selectable, and hidden
|
||||
// while a filter narrows the list.
|
||||
|
||||
@@ -61,13 +61,14 @@ func (m *Model) searchEntries() (sources, items []option) {
|
||||
var es []entry
|
||||
// key is the store key, so ctrl+f stars the entry; mark shows how
|
||||
// much is loaded, reload (ctrl+r) loads it again.
|
||||
add := func(depth int, row string, kind provider.Kind, path []string, account, key, mark string, run, reload func(*Model) tea.Cmd) {
|
||||
add := func(depth int, row string, kind provider.Kind, path []string, account, key, mark string, run, reload func(*Model) tea.Cmd) *option {
|
||||
n := m.store.Node(key)
|
||||
sort := strings.ToLower(strings.Join(path, "/")) + " " + account
|
||||
es = append(es, entry{depth, sort,
|
||||
option{label: m.pathLabel(kind, path, account), note: n.Note, star: key,
|
||||
starAccount: providerOf(row), starred: n.StarredFor(providerOf(row)),
|
||||
mark: mark, run: run, reload: reload, id: sort, group: row, kind: kind}})
|
||||
return &es[len(es)-1].opt
|
||||
}
|
||||
for _, p := range m.providers {
|
||||
for _, src := range m.sourceResults[p.ID()].sources {
|
||||
@@ -98,10 +99,11 @@ func (m *Model) searchEntries() (sources, items []option) {
|
||||
typ := strings.TrimSuffix(strings.ToLower(pluralLabels[t]), "s") // "key"
|
||||
suffix = strings.Trim(typ+" · "+suffix, " ·")
|
||||
}
|
||||
add(2, row, c.Source.Kind, []string{src.Name, c.Name, o.Name}, suffix, objectKey(o), " ", func(m *Model) tea.Cmd {
|
||||
e := add(2, row, c.Source.Kind, []string{src.Name, c.Name, o.Name}, suffix, objectKey(o), " ", func(m *Model) tea.Cmd {
|
||||
m.startNav(r, actGo, row)
|
||||
return nil
|
||||
}, reload)
|
||||
e.plain = c.Detail == provider.DetailPlainEnv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,9 @@ type searchView struct {
|
||||
// pick, if set, makes Go to a picker of secrets, for title: it lists
|
||||
// secrets only, and enter gives the selected one's link to pick
|
||||
// instead of going there.
|
||||
pick func(m *Model, r ref.Ref) tea.Cmd
|
||||
pickTitle, pickNote string
|
||||
pick func(m *Model, r ref.Ref) tea.Cmd
|
||||
pickTitle string
|
||||
pickLines []string // what picking does, above the input
|
||||
|
||||
// loaded are the scopes a search has loaded since the popup opened:
|
||||
// typing loads the scope once, so lists that fail aren't asked for
|
||||
@@ -749,7 +750,14 @@ func (m Model) searchViewBox() string {
|
||||
lists = panes.BoxCounted(m.st, title(resultsTitle, true), "", counter(v.res, len(v.shown)), right, resW, listH, focused)
|
||||
}
|
||||
|
||||
body := []string{" " + v.input.View(), ""}
|
||||
var body []string
|
||||
if v.pick != nil {
|
||||
for _, l := range v.pickLines {
|
||||
body = append(body, " "+l)
|
||||
}
|
||||
body = append(body, "")
|
||||
}
|
||||
body = append(body, " "+v.input.View(), "")
|
||||
for _, l := range strings.Split(lists, "\n") {
|
||||
body = append(body, " "+l)
|
||||
}
|
||||
@@ -761,9 +769,6 @@ func (m Model) searchViewBox() string {
|
||||
} else {
|
||||
body = append(body, "") // the same height on every tab
|
||||
}
|
||||
if v.pick != nil && v.pickNote != "" {
|
||||
body = append(body, " "+m.st.Muted.Render(v.pickNote))
|
||||
}
|
||||
km := m.keys
|
||||
var foot string
|
||||
switch {
|
||||
@@ -808,10 +813,10 @@ func (v *searchView) selectLabel() string {
|
||||
}
|
||||
|
||||
// pickable is the link of a Go to entry that a picker can pick: a
|
||||
// secret (not a key or certificate).
|
||||
// secret (not a key or certificate, nor a key in a plain env file).
|
||||
func pickable(o option) (ref.Ref, bool) {
|
||||
r, err := ref.Parse(o.star)
|
||||
if err != nil || r.IsShare() || r.IsSource() || r.IsContainer() || (r.Type != "" && r.Type != provider.TypeSecret) {
|
||||
if err != nil || o.plain || r.IsShare() || r.IsSource() || r.IsContainer() || (r.Type != "" && r.Type != provider.TypeSecret) {
|
||||
return ref.Ref{}, false
|
||||
}
|
||||
return r, true
|
||||
|
||||
@@ -131,7 +131,15 @@ func (m *Model) pickShareKey(s sharing) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
m.search.pickTitle = "Encrypt with which secret?"
|
||||
m.search.pickNote = "Whoever can read it can open the link. share.key sets a default."
|
||||
what := s.files[0].Path
|
||||
if len(s.files) > 1 {
|
||||
what = plural(len(s.files), "env file")
|
||||
}
|
||||
m.search.pickLines = []string{
|
||||
"Sharing " + what + " from " + s.repo.Name + " as an encrypted link.",
|
||||
"Pick the secret to encrypt with: whoever can read it can open the link.",
|
||||
m.st.Muted.Render("Its value isn't shown or put in the link. share.key sets a default."),
|
||||
}
|
||||
m.fillSearch(false)
|
||||
return tea.Batch(cmd, m.searchLoads())
|
||||
}
|
||||
|
||||
@@ -47,6 +47,14 @@ func shareFrom(t *testing.T, root string, paste bool) string {
|
||||
if v == nil || v.pick == nil {
|
||||
t.Fatalf("no secret picker: %s", d.m.flash.text)
|
||||
}
|
||||
for _, o := range v.shown {
|
||||
if o.plain || strings.Contains(o.label, ".env") {
|
||||
t.Errorf("a plain env value offered as the key: %s", o.label)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(d.screen(), "as an encrypted link") {
|
||||
t.Errorf("doesn't say what picking does:\n%s", d.screen())
|
||||
}
|
||||
if paste {
|
||||
// A reference pasted is picked as it is.
|
||||
d.send(tea.PasteMsg{Content: "https://" + kv + "/secrets/db-password"})
|
||||
|
||||
Reference in new issue
Block a user