Skip to content

Commit e31d3c5

Browse files
authored
fix(cli): fixes CLI compatibility with WSL (#111)
1 parent 61003a9 commit e31d3c5

File tree

6 files changed

+112
-39
lines changed

6 files changed

+112
-39
lines changed

cli/go.mod

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/nitrictech/suga/cli
33
go 1.24.3
44

55
require (
6+
github.com/99designs/keyring v1.2.2
67
github.com/charmbracelet/huh v0.7.1-0.20250714122654-40d2b68703eb
78
github.com/charmbracelet/lipgloss v1.1.0
89
github.com/fsnotify/fsnotify v1.9.0
@@ -13,7 +14,6 @@ require (
1314
github.com/mitchellh/mapstructure v1.5.0
1415
github.com/nitrictech/suga/engines v0.0.0-20250822044031-c54426614b80
1516
github.com/nitrictech/suga/proto v0.0.0-20250822044031-c54426614b80
16-
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1717
github.com/pkg/errors v0.9.1
1818
github.com/robfig/cron/v3 v3.0.1
1919
github.com/samber/do/v2 v2.0.0-beta.7
@@ -23,14 +23,12 @@ require (
2323
github.com/spf13/viper v1.20.1
2424
github.com/stretchr/testify v1.10.0
2525
github.com/xeipuuv/gojsonschema v1.2.0
26-
github.com/zalando/go-keyring v0.2.6
2726
golang.org/x/net v0.41.0
2827
google.golang.org/grpc v1.72.0
2928
gopkg.in/yaml.v3 v3.0.1
3029
)
3130

3231
require (
33-
al.essio.dev/pkg/shellescape v1.5.1 // indirect
3432
cel.dev/expr v0.20.0 // indirect
3533
cloud.google.com/go v0.116.0 // indirect
3634
cloud.google.com/go/auth v0.15.0 // indirect
@@ -39,6 +37,7 @@ require (
3937
cloud.google.com/go/iam v1.2.2 // indirect
4038
cloud.google.com/go/monitoring v1.21.2 // indirect
4139
cloud.google.com/go/storage v1.49.0 // indirect
40+
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
4241
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect
4342
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
4443
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
@@ -67,6 +66,7 @@ require (
6766
github.com/davecgh/go-spew v1.1.1 // indirect
6867
github.com/dnephin/pflag v1.0.7 // indirect
6968
github.com/dustin/go-humanize v1.0.1 // indirect
69+
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
7070
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
7171
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
7272
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
@@ -76,12 +76,13 @@ require (
7676
github.com/go-logr/logr v1.4.2 // indirect
7777
github.com/go-logr/stdr v1.2.2 // indirect
7878
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
79-
github.com/godbus/dbus/v5 v5.1.0 // indirect
79+
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
8080
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
8181
github.com/google/s2a-go v0.1.9 // indirect
8282
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
8383
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
8484
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
85+
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
8586
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
8687
github.com/hashicorp/go-safetemp v1.0.0 // indirect
8788
github.com/hashicorp/go-version v1.7.0 // indirect
@@ -98,6 +99,7 @@ require (
9899
github.com/mitchellh/go-homedir v1.1.0 // indirect
99100
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
100101
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
102+
github.com/mtibben/percent v0.2.1 // indirect
101103
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
102104
github.com/muesli/cancelreader v0.2.2 // indirect
103105
github.com/muesli/termenv v0.16.0 // indirect

cli/go.sum

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
2-
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
31
cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
42
cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
53
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
@@ -619,6 +617,10 @@ cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcP
619617
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
620618
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
621619
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
620+
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
621+
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
622+
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
623+
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
622624
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
623625
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
624626
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw=
@@ -742,6 +744,8 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3
742744
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
743745
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
744746
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
747+
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
748+
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
745749
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
746750
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
747751
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -801,8 +805,8 @@ github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO
801805
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
802806
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
803807
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
804-
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
805-
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
808+
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
809+
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
806810
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
807811
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
808812
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
@@ -924,6 +928,8 @@ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8
924928
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
925929
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
926930
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
931+
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
932+
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
927933
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
928934
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
929935
github.com/hashicorp/go-getter v1.7.8 h1:mshVHx1Fto0/MydBekWan5zUipGq7jO0novchgMmSiY=
@@ -1001,12 +1007,15 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4
10011007
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
10021008
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
10031009
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
1010+
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
1011+
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
10041012
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
10051013
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
10061014
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
10071015
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
10081016
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
10091017
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
1018+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
10101019
github.com/nitrictech/suga/engines v0.0.0-20250822044031-c54426614b80 h1:xlkysFs8BxYx8k4Qn0wO0xVU0osy46Y005IB+ld9J24=
10111020
github.com/nitrictech/suga/engines v0.0.0-20250822044031-c54426614b80/go.mod h1:m3hmoLQLAkNRQkvjcNcX0R71qqDyLg+lDGFKIA2dRs8=
10121021
github.com/nitrictech/suga/proto v0.0.0-20250822044031-c54426614b80 h1:8h+GOlmYuXjzr7h1wdEwJafE7YStah+VGq1c8fM3jcI=
@@ -1019,8 +1028,6 @@ github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2
10191028
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
10201029
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
10211030
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
1022-
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
1023-
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
10241031
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
10251032
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
10261033
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -1117,8 +1124,6 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
11171124
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
11181125
github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU=
11191126
github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
1120-
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
1121-
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
11221127
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
11231128
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
11241129
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
@@ -1428,7 +1433,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
14281433
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
14291434
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
14301435
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1431-
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
14321436
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
14331437
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
14341438
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1839,6 +1843,7 @@ google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9x
18391843
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
18401844
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18411845
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1846+
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18421847
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
18431848
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
18441849
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=

cli/internal/workos/device.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import (
77
"strings"
88
"time"
99

10+
"github.com/nitrictech/suga/cli/internal/browser"
1011
"github.com/nitrictech/suga/cli/internal/style"
1112
"github.com/nitrictech/suga/cli/internal/style/icons"
1213
"github.com/nitrictech/suga/cli/internal/workos/http"
13-
"github.com/pkg/browser"
1414
)
1515

1616
// DeviceAuthResponse represents the device authorization response from backend
@@ -40,7 +40,7 @@ func (a *WorkOSAuth) performDeviceAuth() error {
4040

4141
fmt.Printf("\nYour code is: %s\n", style.Bold(style.Yellow(deviceResp.UserCode)))
4242

43-
err = browser.OpenURL(deviceResp.VerificationURIComplete)
43+
err = browser.Open(deviceResp.VerificationURIComplete)
4444
if err != nil {
4545
fmt.Printf("\nPlease visit: %s\n", style.Cyan(deviceResp.VerificationURI))
4646
fmt.Println("and enter the code above to login")

cli/internal/workos/keyring.go

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
package workos
22

33
import (
4+
"crypto/rand"
45
"crypto/sha256"
6+
"encoding/base64"
57
"encoding/json"
68
"fmt"
9+
"os"
10+
"path/filepath"
711

8-
"github.com/zalando/go-keyring"
12+
"github.com/99designs/keyring"
913
)
1014

11-
func hashTokenKey(tokenKey string) string {
12-
// Hash the token key for a consistent length.
13-
hash := sha256.Sum256([]byte(tokenKey))
14-
return fmt.Sprintf("%x", hash)
15-
}
16-
1715
type KeyringTokenStore struct {
18-
service string
19-
tokenKey string
16+
ring keyring.Keyring
17+
key string
2018
}
2119

20+
// NewKeyringTokenStore tries system keyring first, falls back to encrypted file
2221
func NewKeyringTokenStore(serviceName, tokenKey string) (*KeyringTokenStore, error) {
2322
if serviceName == "" {
2423
return nil, fmt.Errorf("service name is required")
@@ -28,22 +27,53 @@ func NewKeyringTokenStore(serviceName, tokenKey string) (*KeyringTokenStore, err
2827
return nil, fmt.Errorf("token key is required")
2928
}
3029

31-
hashedTokenKey := hashTokenKey(tokenKey)
30+
hash := sha256.Sum256([]byte(tokenKey))
31+
hashedKey := fmt.Sprintf("%x", hash)
32+
33+
homeDir, err := os.UserHomeDir()
34+
if err != nil {
35+
return nil, fmt.Errorf("failed to resolve home directory: %w", err)
36+
}
37+
38+
fileDir := filepath.Join(homeDir, ".suga")
3239

33-
return &KeyringTokenStore{service: serviceName, tokenKey: hashedTokenKey}, nil
40+
passphrase, err := getOrCreateFilePassphrase(fileDir)
41+
if err != nil {
42+
return nil, fmt.Errorf("failed to get file passphrase: %w", err)
43+
}
44+
45+
ring, err := keyring.Open(keyring.Config{
46+
ServiceName: serviceName,
47+
FileDir: fileDir,
48+
FilePasswordFunc: keyring.FixedStringPrompt(passphrase),
49+
AllowedBackends: []keyring.BackendType{
50+
keyring.SecretServiceBackend,
51+
keyring.KeychainBackend,
52+
keyring.WinCredBackend,
53+
keyring.FileBackend,
54+
},
55+
})
56+
if err != nil {
57+
return nil, fmt.Errorf("failed to open keyring: %w", err)
58+
}
59+
60+
return &KeyringTokenStore{
61+
ring: ring,
62+
key: hashedKey,
63+
}, nil
3464
}
3565

3666
func (s *KeyringTokenStore) GetTokens() (*Tokens, error) {
37-
token, err := keyring.Get(s.service, s.tokenKey)
67+
item, err := s.ring.Get(s.key)
3868
if err != nil {
39-
if err == keyring.ErrNotFound {
69+
if err == keyring.ErrKeyNotFound {
4070
return nil, ErrNotFound
4171
}
4272
return nil, fmt.Errorf("failed to retrieve token from keyring: %w", err)
4373
}
4474

4575
var tokens Tokens
46-
err = json.Unmarshal([]byte(token), &tokens)
76+
err = json.Unmarshal(item.Data, &tokens)
4777
if err != nil {
4878
return nil, fmt.Errorf("failed to unmarshal token: %w", err)
4979
}
@@ -52,21 +82,61 @@ func (s *KeyringTokenStore) GetTokens() (*Tokens, error) {
5282
}
5383

5484
func (s *KeyringTokenStore) SaveTokens(tokens *Tokens) error {
55-
json, err := json.Marshal(tokens)
85+
data, err := json.Marshal(tokens)
5686
if err != nil {
5787
return fmt.Errorf("failed to marshal token: %w", err)
5888
}
5989

60-
return keyring.Set(s.service, s.tokenKey, string(json))
90+
err = s.ring.Set(keyring.Item{
91+
Key: s.key,
92+
Data: data,
93+
})
94+
if err != nil {
95+
return fmt.Errorf("failed to save token to keyring: %w", err)
96+
}
97+
98+
return nil
6199
}
62100

63101
func (s *KeyringTokenStore) Clear() error {
64-
err := keyring.Delete(s.service, s.tokenKey)
102+
err := s.ring.Remove(s.key)
65103
if err != nil {
66-
if err == keyring.ErrNotFound {
104+
if err == keyring.ErrKeyNotFound {
67105
return ErrNotFound
68106
}
69107
return fmt.Errorf("failed to delete token from keyring: %w", err)
70108
}
71109
return nil
72110
}
111+
112+
// getOrCreateFilePassphrase uses a per-user random passphrase instead of hardcoded constant
113+
// to prevent mass decryption and follow security best practices (defense in depth)
114+
func getOrCreateFilePassphrase(fileDir string) (string, error) {
115+
passphrasePath := filepath.Join(fileDir, ".keyring-passphrase")
116+
117+
data, err := os.ReadFile(passphrasePath)
118+
if err == nil {
119+
return string(data), nil
120+
}
121+
122+
if !os.IsNotExist(err) {
123+
return "", fmt.Errorf("failed to read passphrase file: %w", err)
124+
}
125+
126+
if err := os.MkdirAll(fileDir, 0700); err != nil {
127+
return "", fmt.Errorf("failed to create directory: %w", err)
128+
}
129+
130+
passphraseBytes := make([]byte, 32)
131+
if _, err := rand.Read(passphraseBytes); err != nil {
132+
return "", fmt.Errorf("failed to generate random passphrase: %w", err)
133+
}
134+
135+
passphrase := base64.StdEncoding.EncodeToString(passphraseBytes)
136+
137+
if err := os.WriteFile(passphrasePath, []byte(passphrase), 0600); err != nil {
138+
return "", fmt.Errorf("failed to write passphrase file: %w", err)
139+
}
140+
141+
return passphrase, nil
142+
}

cli/internal/workos/workos.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func NewWorkOSAuth(inj do.Injector) (*WorkOSAuth, error) {
5454
}
5555
httpClient := http.NewHttpClient("", opts...)
5656

57-
tokenStore := do.MustInvokeAs[TokenStore](inj)
57+
tokenStore := do.MustInvoke[*KeyringTokenStore](inj)
5858

5959
return &WorkOSAuth{tokenStore: tokenStore, httpClient: httpClient}, nil
6060
}

cli/main.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ func createTokenStore(inj do.Injector) (*workos.KeyringTokenStore, error) {
1717
config := do.MustInvoke[*config.Config](inj)
1818
apiUrl := config.GetSugaServerUrl()
1919

20-
tokenStore, err := workos.NewKeyringTokenStore("suga.cli", apiUrl.String())
21-
if err != nil {
22-
return nil, err
23-
}
24-
return tokenStore, nil
20+
return workos.NewKeyringTokenStore("suga.cli", apiUrl.String())
2521
}
2622

2723
func main() {

0 commit comments

Comments
 (0)