Skip to content

Commit 60e74d4

Browse files
committed
feat(1password): add 1password integration
1 parent a67085b commit 60e74d4

File tree

10 files changed

+253
-25
lines changed

10 files changed

+253
-25
lines changed

README.md

+10-21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# PassDIY
33

4-
A personal password/token manager TUI for developers to generate various types of hash/salted secrets and store them in their cloud-based Hashicorp Vault
4+
A personal password/token manager TUI for developers to generate various types of hash/salted secrets and store them in different cloud based vaults
55

66
## Why PassDIY?
77

@@ -25,32 +25,20 @@ Because managing tokens, pins used in various dummy/dev apps require them to be
2525

2626
![passdiydemo2 (1)](https://github.com/user-attachments/assets/a69792c6-d24d-4659-b478-9f9aa32e071d)
2727

28-
## Setup
28+
## Hashicorp Setup
2929

30-
To allow PassDIY to store and connect to your Hashicorp vault you must create a [service principle](https://developer.hashicorp.com/hcp/docs/hcp/iam/service-principal) with ```Vault Secrets App Manager``` permission. You would also need to set up following Env variables to successfully connect and store the secrets to specific app in your vault. If you don't care about storing the values you can ignore the below variables and directly run
31-
`go run main.go`
30+
To allow PassDIY to store and connect to your Hashicorp vault you must create a [service principle](https://developer.hashicorp.com/hcp/docs/hcp/iam/service-principal) with ```Vault Secrets App Manager``` permission. Also would need set below envs
3231

33-
`HCP_CLIENT_ID` your sp client ID
32+
`export HCP_CLIENT_ID=<your-hcp-client-id>`
33+
`export HCP_CLIENT_SECRET=<your-hcp-client-secret>`
3434

35-
`HCP_CLIENT_SECRET` your sp secret
35+
more detailed in `./Setup.md`
3636

37-
`HCP_ORG_ID` your HCP org ID
37+
## 1Password Setup
3838

39-
`HCP_PROJECT_ID` your HCP project ID
39+
To allow PassDIY to connect to your 1Password Vault you would need to set [service principle](https://developer.1password.com/docs/sdks) anf the service account token
4040

41-
`HCP_APP_NAME` your HCP app name
42-
43-
`HCP_API_TOKEN` your HCP API token generated from `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` you don't need generate `HCP_API_TOKEN` every time it expires the PassDIY's `hcpvaultconnect` command will handle it by automatically connecting for you
44-
45-
you can now clone the repo and run from source
46-
47-
`go run main.go`
48-
49-
can also build PassDIY with build command and run passdiy
50-
51-
`go build`
52-
53-
`./passdiy`
41+
`export OP_SERVICE_ACCOUNT_TOKEN=<your-service-account-token>`
5442

5543
## Config
5644

@@ -83,6 +71,7 @@ const (
8371
- [Hashicorp Vault](https://developer.hashicorp.com/hcp/api-docs/vault-secrets#overview)
8472
- [English](github.com/gregoryv/english)
8573
- [Clipboard](https://github.com/atotto/clipboard)
74+
- [1Password SDK](https://github.com/1Password/onepassword-sdk-go)
8675

8776
## License
8877

Setup.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Hashicorp Setup
2+
3+
To allow PassDIY to store and connect to your Hashicorp vault you must create a [service principle](https://developer.hashicorp.com/hcp/docs/hcp/iam/service-principal) with ```Vault Secrets App Manager``` permission. You would also need to set up following Env variables to successfully connect and store the secrets to specific app in your vault. If you don't care about storing the values you can ignore the below variables and directly run
4+
`go run main.go`
5+
6+
`HCP_CLIENT_ID` your sp client ID
7+
8+
`HCP_CLIENT_SECRET` your sp secret
9+
10+
`HCP_ORG_ID` your HCP org ID
11+
12+
`HCP_PROJECT_ID` your HCP project ID
13+
14+
`HCP_APP_NAME` your HCP app name
15+
16+
`HCP_API_TOKEN` your HCP API token generated from `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` you don't need generate `HCP_API_TOKEN` every time it expires the PassDIY's `hcpvaultconnect` command will handle it by automatically connecting for you
17+
18+
you can now clone the repo and run from source
19+
20+
`go run main.go`
21+
22+
can also build PassDIY with build command and run passdiy
23+
24+
`go build`
25+
26+
`./passdiy`
27+
28+
## 1Password Setup
29+
30+
To allow PassDIY to connect to your 1Password Vault you would need to set [service principle](https://developer.1password.com/docs/sdks)
31+
32+
`export OP_SERVICE_ACCOUNT_TOKEN=<your-service-account-token>`

cmds/cmd.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
tea "github.com/charmbracelet/bubbletea"
1010
"github.com/jalpp/passdiy/config"
1111
hcp "github.com/jalpp/passdiy/hcpvault"
12+
opass "github.com/jalpp/passdiy/onepassword"
1213
cmd "github.com/jalpp/passdiy/password"
1314
)
1415

@@ -24,10 +25,11 @@ var (
2425
pwpDesc = fmt.Sprintf("strong %d-word passphrase", config.PASSPHRASE_COUNT_NUM)
2526
saltDesc = fmt.Sprintf("password with extra %d-char salt on top", config.SALT_EXTRA_LENGTH)
2627
hashDesc = "hash value of a password with Argon2id"
27-
directDesc = "direct hash running buffer value with Argon2id"
28-
hcpvaultstoreDesc = "Store a new secret to Hashicop Vault"
29-
hcpvaultconnectDesc = "Generate HCP API token and connect to Hashicop Vault"
28+
hcpvaultstoreDesc = "Store a new secret to Hashicorp Vault"
29+
hcpvaultconnectDesc = "Generate HCP API token and connect to Hashicorp Vault"
3030
hcpvaultlistDesc = "List HCP Vault secrets log details"
31+
opassstoreDesc = "Store a new secret to 1Password in password format"
32+
opasslistDesc = "List 1Password Vault item names"
3133
)
3234

3335
func (i CommandItem) Title() string { return i.title }
@@ -84,6 +86,8 @@ func CreateCommandItems() []list.Item {
8486
CommandItem{title: "hcpvaultstore", desc: hcpvaultstoreDesc},
8587
CommandItem{title: "hcpvaultconnect", desc: hcpvaultconnectDesc},
8688
CommandItem{title: "hcpvaultlist", desc: hcpvaultlistDesc},
89+
CommandItem{title: "1passstore", desc: opassstoreDesc},
90+
CommandItem{title: "1passlist", desc: opasslistDesc},
8791
}
8892
}
8993

@@ -152,6 +156,17 @@ func HandleCommand(input, userInput string) string {
152156
return "Please connect to Hashicorp vault via hcpvaultconnect"
153157
}
154158
return list
159+
case "1passstore":
160+
parts := strings.SplitN(userInput, "|", 3)
161+
if len(parts) == 3 {
162+
user := parts[0]
163+
pass := parts[1]
164+
url := parts[2]
165+
return opass.Create(user, pass, url)
166+
}
167+
return "Invalid format. use 'user|value|url'."
168+
case "1passlist":
169+
return opass.List()
155170
default:
156171
return fmt.Sprintf("Unknown command: %s", input)
157172
}

go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ require (
1212
)
1313

1414
require (
15+
github.com/1password/onepassword-sdk-go v0.1.3 // indirect
1516
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
1617
github.com/charmbracelet/x/ansi v0.2.3 // indirect
1718
github.com/charmbracelet/x/term v0.2.0 // indirect
1819
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
20+
github.com/extism/go-sdk v1.3.1 // indirect
21+
github.com/gobwas/glob v0.2.3 // indirect
1922
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
2023
github.com/mattn/go-isatty v0.0.20 // indirect
2124
github.com/mattn/go-localereader v0.0.1 // indirect
@@ -25,6 +28,7 @@ require (
2528
github.com/muesli/termenv v0.15.2 // indirect
2629
github.com/rivo/uniseg v0.4.7 // indirect
2730
github.com/sahilm/fuzzy v0.1.1 // indirect
31+
github.com/tetratelabs/wazero v1.8.1 // indirect
2832
golang.org/x/crypto v0.17.0 // indirect
2933
golang.org/x/sync v0.8.0 // indirect
3034
golang.org/x/sys v0.24.0 // indirect

go.sum

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/1password/onepassword-sdk-go v0.1.3 h1:PP8+pydBt40Uh21tXP9bmPCPTlBc23JW5iVpOjzssw4=
2+
github.com/1password/onepassword-sdk-go v0.1.3/go.mod h1:nZEOzWFvodClltx8G0xtcNGqzNrrcfW589Rb9T82hE8=
13
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
24
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
35
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
@@ -16,6 +18,10 @@ github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4h
1618
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
1719
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
1820
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
21+
github.com/extism/go-sdk v1.3.1 h1:eVpuv36b67Km/tAb7Cq6msHEW8kkdFgpZO/7fCwjuoE=
22+
github.com/extism/go-sdk v1.3.1/go.mod h1:tPMWfCSOThie3LSTSZKbrQjRm2oAXxUUjSE4HJWjYQM=
23+
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
24+
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
1925
github.com/gregoryv/english v0.6.0 h1:NHGV9r1M20ifPacmPG41ZQGEbH9cTlxvwUuAcXm1v0w=
2026
github.com/gregoryv/english v0.6.0/go.mod h1:F5O7gDq0yWDJUEOXuNv+ebChEjK7N9eBFHsxGyFui/0=
2127
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
@@ -39,6 +45,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
3945
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
4046
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
4147
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
48+
github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550=
49+
github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
4250
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
4351
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4452
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=

main.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
101101
if m.inputMode == "hash" && inputValue != "" {
102102
return m, tea.Batch(cmd.ExecuteCommand("hash", inputValue), m.spinner.Tick)
103103
}
104+
if m.inputMode == "1passstore" {
105+
if inputValue != "" && strings.Contains(inputValue, "|") {
106+
return m, tea.Batch(cmd.ExecuteCommand("1passstore", inputValue), m.spinner.Tick)
107+
}
108+
m.output = "Please provide input in 'user|value|url' format."
109+
return m, nil
110+
}
104111
if m.inputMode == "hcpvaultstore" {
105112
if inputValue != "" && strings.Contains(inputValue, "=") {
106113
return m, tea.Batch(cmd.ExecuteCommand("hcpvaultstore", inputValue), m.spinner.Tick)
@@ -110,6 +117,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
110117
}
111118
m.output = "Please provide valid input."
112119
return m, nil
120+
113121
}
114122

115123
selectedItem := m.list.SelectedItem().(cmd.CommandItem).Title()
@@ -134,7 +142,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
134142
return m, tea.Batch(cmd.ExecuteCommand(selectedCommand, m.prevOutput), m.spinner.Tick)
135143
}
136144

137-
if selectedItem == "hash" || selectedItem == "hcpvaultstore" {
145+
if selectedItem == "hash" || selectedItem == "hcpvaultstore" || selectedItem == "1passstore" {
138146
m.inputMode = selectedItem
139147
m.textInput.SetValue("")
140148
m.prevOutput = ""
@@ -195,6 +203,15 @@ func (m model) View() string {
195203
maskedInput,
196204
))
197205
}
206+
207+
if m.inputMode == "1passstore" {
208+
return style.GreenStyle.Render(
209+
fmt.Sprintf(
210+
"Enter the password/token in 'name|value|url' format and press Enter: \n\n%s\n\n",
211+
maskedInput,
212+
))
213+
}
214+
198215
return style.GreenStyle.Render(fmt.Sprintf(
199216
"Enter the token/password for hashing with Argon2id and press Enter:\n\n%s\n\n",
200217
maskedInput,
@@ -218,6 +235,10 @@ func (m model) View() string {
218235
return style.VaultStyle.Render(fmt.Sprintf("%s\n\n ⛛ Vault: %s", m.list.View(), m.output))
219236
}
220237

238+
if strings.Contains(strings.ToLower(m.output), "1password") {
239+
return style.OPassStyle.Render(fmt.Sprintf("%s\n\n1Password Vault: %s", m.list.View(), m.output))
240+
}
241+
221242
return style.GreenStyle.Render(fmt.Sprintf("%s\n\n 🔑 [c] Copy [esc] Exist Sublist [x] Clear \n 🔑 Buffer: %s%s", m.list.View(), cmd.CoverUp(m.output), copyMessage))
222243
}
223244

onepassword/connect.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package onepassword
2+
3+
import (
4+
"context"
5+
"os"
6+
7+
"github.com/1password/onepassword-sdk-go"
8+
)
9+
10+
func Connect() *onepassword.Client {
11+
12+
token := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN")
13+
14+
client, err := onepassword.NewClient(context.Background(),
15+
onepassword.WithServiceAccountToken(token),
16+
17+
onepassword.WithIntegrationInfo("passdiy", "v1.0.0"),
18+
)
19+
if err != nil {
20+
return nil
21+
}
22+
23+
return client
24+
}

onepassword/create.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package onepassword
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/1password/onepassword-sdk-go"
9+
)
10+
11+
func GetVaultId(client *onepassword.Client) string {
12+
vaults, err := client.Vaults.ListAll(context.Background())
13+
var valId string
14+
if err != nil {
15+
panic(err)
16+
}
17+
for {
18+
vault, err := vaults.Next()
19+
if errors.Is(err, onepassword.ErrorIteratorDone) {
20+
break
21+
} else if err != nil {
22+
panic(err)
23+
}
24+
25+
valId = vault.ID
26+
}
27+
return valId
28+
}
29+
30+
func createItem(client *onepassword.Client, vaultID string, usernameVal string, passVal string, URL string) string {
31+
sectionID := "passDetails"
32+
itemParams := onepassword.ItemCreateParams{
33+
Title: fmt.Sprintf("PassDIY Gen: %s", URL),
34+
Category: onepassword.ItemCategoryLogin,
35+
VaultID: vaultID,
36+
Fields: []onepassword.ItemField{
37+
{
38+
ID: "username",
39+
Title: "username",
40+
Value: usernameVal,
41+
FieldType: onepassword.ItemFieldTypeText,
42+
},
43+
{
44+
ID: "password",
45+
Title: "password",
46+
Value: passVal,
47+
FieldType: onepassword.ItemFieldTypeConcealed,
48+
},
49+
{
50+
ID: "onetimepassword",
51+
Title: "one-time password",
52+
Value: "otpauth://totp/my-example-otp?secret=jncrjgbdjnrncbjsr&issuer=1Password",
53+
SectionID: &sectionID,
54+
FieldType: onepassword.ItemFieldTypeTOTP,
55+
},
56+
},
57+
Sections: []onepassword.ItemSection{
58+
{
59+
ID: sectionID,
60+
Title: "Extra Details",
61+
},
62+
},
63+
Tags: []string{"passdiy generated", fmt.Sprintf("web: %s", URL)},
64+
Websites: []onepassword.Website{
65+
{
66+
URL: URL,
67+
AutofillBehavior: onepassword.AutofillBehaviorAnywhereOnWebsite,
68+
Label: URL,
69+
},
70+
},
71+
}
72+
73+
createdItem, err := client.Items.Create(context.Background(), itemParams)
74+
if err != nil {
75+
return fmt.Sprintf("Error %s", err.Error())
76+
}
77+
78+
return fmt.Sprintf("Successfully created password item ID %s in 1Password Vault", createdItem.Title)
79+
}
80+
81+
func Create(user string, pass string, URL string) string {
82+
83+
var client = Connect()
84+
85+
if client == nil {
86+
return "Error! Please check 1password config"
87+
}
88+
89+
var valId = GetVaultId(client)
90+
91+
return createItem(client, valId, user, pass, URL)
92+
93+
}

0 commit comments

Comments
 (0)