diff --git a/api/billing/fees/go.mod b/api/billing/fees/go.mod
index 10bae4f..91255a8 100644
--- a/api/billing/fees/go.mod
+++ b/api/billing/fees/go.mod
@@ -10,7 +10,7 @@ require (
github.com/tech/sendico/fx/oracle v0.0.0
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
google.golang.org/grpc v1.77.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -36,7 +36,7 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
@@ -44,7 +44,7 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
diff --git a/api/billing/fees/go.sum b/api/billing/fees/go.sum
index 13a4a5d..91ffdb4 100644
--- a/api/billing/fees/go.sum
+++ b/api/billing/fees/go.sum
@@ -9,8 +9,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -117,10 +115,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
-github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -156,32 +152,32 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -218,8 +214,6 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
diff --git a/api/chain/gateway/go.mod b/api/chain/gateway/go.mod
index 3f79cec..be00771 100644
--- a/api/chain/gateway/go.mod
+++ b/api/chain/gateway/go.mod
@@ -14,7 +14,7 @@ require (
github.com/stretchr/testify v1.11.1
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
gopkg.in/yaml.v3 v3.0.1
@@ -65,7 +65,7 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
@@ -79,7 +79,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
diff --git a/api/chain/gateway/go.sum b/api/chain/gateway/go.sum
index 704ef47..d7bb493 100644
--- a/api/chain/gateway/go.sum
+++ b/api/chain/gateway/go.sum
@@ -6,25 +6,17 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251110112254-48a6e677648f h1:B/TfTw73mVqWKDzJZhU9Qi9wQyYfmiCz9FnmpQsyv5M=
-github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251110112254-48a6e677648f/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
-github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251117160429-c598d23eddcf h1:aZI2VRIP0LAI6Rw934WEAxxL0SNYSVt9vR9h/cP5Pbo=
-github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251117160429-c598d23eddcf/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251119083800-2aa1d4cc79d7 h1:uups37roJCTtR/BrJa0WoMrxt3rzgV+Qrj+TxYyJoAo=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251119083800-2aa1d4cc79d7/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bits-and-blooms/bitset v1.24.3 h1:Bte86SlO3lwPQqww+7BE9ZuUCKIjfqnG5jtEyqA9y9Y=
-github.com/bits-and-blooms/bitset v1.24.3/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -247,10 +239,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
-github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@@ -285,12 +275,8 @@ github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0 h1:iXVA84s5hKMS5gn01GWOYHE3ymy/2b+0YkpFeTxB2XY=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0/go.mod h1:R6tMjTojRiaoo89fh/hf7tOmfzohdqSU17R9DwSVSog=
-github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
-github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
-github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
-github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
@@ -310,32 +296,32 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -378,8 +364,6 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
diff --git a/api/fx/ingestor/go.mod b/api/fx/ingestor/go.mod
index c856265..50ced22 100644
--- a/api/fx/ingestor/go.mod
+++ b/api/fx/ingestor/go.mod
@@ -12,7 +12,7 @@ require (
github.com/prometheus/client_golang v1.23.2
github.com/tech/sendico/fx/storage v0.0.0
github.com/tech/sendico/pkg v0.1.0
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
gopkg.in/yaml.v3 v3.0.1
)
@@ -35,7 +35,7 @@ require (
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
@@ -44,7 +44,7 @@ require (
go.mongodb.org/mongo-driver v1.17.6 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
diff --git a/api/fx/ingestor/go.sum b/api/fx/ingestor/go.sum
index 53148e5..91ffdb4 100644
--- a/api/fx/ingestor/go.sum
+++ b/api/fx/ingestor/go.sum
@@ -9,8 +9,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -117,10 +115,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
-github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -156,47 +152,41 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
-golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -205,8 +195,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -215,8 +203,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -226,12 +212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
diff --git a/api/fx/oracle/go.mod b/api/fx/oracle/go.mod
index 1659166..b1b2718 100644
--- a/api/fx/oracle/go.mod
+++ b/api/fx/oracle/go.mod
@@ -12,7 +12,7 @@ require (
github.com/tech/sendico/fx/storage v0.0.0
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
gopkg.in/yaml.v3 v3.0.1
@@ -37,7 +37,7 @@ require (
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
@@ -45,7 +45,7 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
diff --git a/api/fx/oracle/go.sum b/api/fx/oracle/go.sum
index 13a4a5d..91ffdb4 100644
--- a/api/fx/oracle/go.sum
+++ b/api/fx/oracle/go.sum
@@ -9,8 +9,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -117,10 +115,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
-github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -156,32 +152,32 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -218,8 +214,6 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
diff --git a/api/fx/storage/go.mod b/api/fx/storage/go.mod
index 8dd247c..7b1bf9b 100644
--- a/api/fx/storage/go.mod
+++ b/api/fx/storage/go.mod
@@ -7,7 +7,7 @@ replace github.com/tech/sendico/pkg => ../../pkg
require (
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
)
require (
@@ -25,7 +25,7 @@ require (
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
diff --git a/api/fx/storage/go.sum b/api/fx/storage/go.sum
index bab4975..170b262 100644
--- a/api/fx/storage/go.sum
+++ b/api/fx/storage/go.sum
@@ -7,8 +7,6 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -122,28 +120,26 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -151,8 +147,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -160,16 +154,14 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/api/ledger/go.mod b/api/ledger/go.mod
index 12767dd..9bf4afe 100644
--- a/api/ledger/go.mod
+++ b/api/ledger/go.mod
@@ -10,7 +10,7 @@ require (
github.com/stretchr/testify v1.11.1
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
gopkg.in/yaml.v3 v3.0.1
@@ -38,7 +38,7 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
@@ -46,7 +46,7 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
diff --git a/api/ledger/go.sum b/api/ledger/go.sum
index 4ae6767..63f1b50 100644
--- a/api/ledger/go.sum
+++ b/api/ledger/go.sum
@@ -9,8 +9,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -117,10 +115,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
-github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -158,32 +154,32 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -220,8 +216,6 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
diff --git a/api/notification/go.mod b/api/notification/go.mod
index 4f18fef..7d313cb 100644
--- a/api/notification/go.mod
+++ b/api/notification/go.mod
@@ -13,7 +13,7 @@ require (
github.com/tech/sendico/pkg v0.1.0
github.com/xhit/go-simple-mail/v2 v2.16.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
golang.org/x/text v0.31.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -38,7 +38,7 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817 // indirect
@@ -48,7 +48,7 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
diff --git a/api/notification/go.sum b/api/notification/go.sum
index acaab3e..5ae62e8 100644
--- a/api/notification/go.sum
+++ b/api/notification/go.sum
@@ -13,8 +13,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -123,10 +121,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
-github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -171,32 +167,32 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -233,8 +229,6 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
diff --git a/api/notification/i18n/en.json b/api/notification/i18n/en.json
index 0cc34ad..24afb54 100644
--- a/api/notification/i18n/en.json
+++ b/api/notification/i18n/en.json
@@ -20,6 +20,10 @@
"mail.reset-password.greeting": "Hi, {{.Name}}",
"mail.reset-password.body": "It looks like you requested a new password.
If that sounds right, you can enter a new password by clicking the followinglink<\/a>.
If you have not requested a passowrd change please contact us under {{.SupportMail}}<\/a>.",
+ "mail.confirmation-code.subj": "{{.ServiceName}}: your confirmation code",
+ "mail.confirmation-code.greeting": "Hi, {{.Name}}",
+ "mail.confirmation-code.body": "Use this code to complete your {{.Target}} request: {{.Code}}<\/b>. This code expires soon.",
+
"mail.email-verification.subj": "{{.ServiceName}}: verify your email address",
"mail.email-verification.greeting": "Hi, {{.Name}}",
"mail.email-verification.body": "It looks like you have changed your email address.
If that sounds right, verify your email address by clicking the following link<\/a>.
If you have not changed your email address please contact us under {{.SupportMail}}<\/a>.",
diff --git a/api/notification/i18n/ru.json b/api/notification/i18n/ru.json
index b7f28a6..0d42683 100644
--- a/api/notification/i18n/ru.json
+++ b/api/notification/i18n/ru.json
@@ -21,6 +21,10 @@
"mail.reset-password.greeting": "День добрый, {{.Name}}",
"mail.reset-password.body": "От вашего имени пришел запрос на сброс пароля.
Если этот запрос отправили вы, то подтвердите сброс переходом по ссылке<\/a>.
Если этот запрос отправили не вы, то дайте нам знать по адресу электронной почты {{.SupportMail}}<\/a>",
+ "mail.confirmation-code.subj": "{{.ServiceName}}: ваш код подтверждения",
+ "mail.confirmation-code.greeting": "День добрый, {{.Name}}",
+ "mail.confirmation-code.body": "Используйте этот код, чтобы завершить запрос {{.Target}}: {{.Code}}<\/b>. Код скоро истекает.",
+
"mail.email-verification.subj": "{{.ServiceName}}: подтвердите ваш адрес электронной почты",
"mail.email-verification.greeting": "День добрый, {{.Name}}",
"mail.email-verification.body": "От вашего имени пришел запрос на смену адреса электронной почты.
Если этот запрос отправили вы, то подтвердите смену переходом по ссылке <\/a>.
Если этот запрос отправили не вы, то дайте нам знать по адресу электронной почты {{.SupportMail}}<\/a>",
@@ -58,4 +62,4 @@
"___file_trailer": ""
-}
\ No newline at end of file
+}
diff --git a/api/notification/i18n/uk.json b/api/notification/i18n/uk.json
index 8771068..e19651c 100644
--- a/api/notification/i18n/uk.json
+++ b/api/notification/i18n/uk.json
@@ -20,6 +20,10 @@
"mail.reset-password.greeting": "День добрий, {{.Name}}",
"mail.reset-password.body": "Від вашого імені прийшов запит на скидання пароля.
Якщо цей запит відправили ви, то підтвердіть скидання переходом по посилання<\/a>.
>
Якщо цей запит надіслали не ви, дайте нам знати за адресою електронної пошти {{.SupportMail}}<\/a>",
+ "mail.confirmation-code.subj": "{{.ServiceName}}: ваш код підтвердження",
+ "mail.confirmation-code.greeting": "День добрий, {{.Name}}",
+ "mail.confirmation-code.body": "Використайте цей код, щоб завершити запит {{.Target}}: {{.Code}}<\/b>. Код скоро спливає.",
+
"mail.email-verification.subj": "{{.ServiceName}}: Перевірте свою адресу електронної пошти",
"mail.email-verification.greeting": "День добрий, {{.Name}}",
"mail.email-verification.body": "Від вашого імені надійшов запит на адресу електронної пошти.
Якщо цей запит надіслали, то підтвердьте зміну переходом за адресою<\/a>.
Якщо цей запит відправили не ви, то дайте нам знати на адресу електронної пошти {{.SupportMail}}<\/a>",
diff --git a/api/notification/internal/server/notificationimp/confcode.go b/api/notification/internal/server/notificationimp/confcode.go
new file mode 100644
index 0000000..bccf5ee
--- /dev/null
+++ b/api/notification/internal/server/notificationimp/confcode.go
@@ -0,0 +1,27 @@
+package notificationimp
+
+import (
+ "context"
+ "strings"
+
+ "github.com/tech/sendico/pkg/model"
+ "go.uber.org/zap"
+)
+
+func (a *NotificationAPI) onConfirmationCode(ctx context.Context, account *model.Account, destination string, target model.ConfirmationTarget, code string) error {
+ builder := a.client.MailBuilder().
+ AddRecipient(account.Name, strings.TrimSpace(destination)).
+ SetAccountID(account.ID.Hex()).
+ SetLocale(account.Locale).
+ SetTemplateID("confirmation-code").
+ AddData("Name", account.Name).
+ AddData("Code", code).
+ AddData("Target", string(target))
+
+ if err := a.client.Send(builder); err != nil {
+ a.logger.Warn("Failed to send confirmation code email", zap.Error(err), zap.String("login", account.Login))
+ return err
+ }
+ a.logger.Info("Confirmation code email sent", zap.String("login", account.Login), zap.String("destination", destination), zap.String("target", string(target)))
+ return nil
+}
diff --git a/api/notification/internal/server/notificationimp/notification.go b/api/notification/internal/server/notificationimp/notification.go
index 2805652..4f81929 100644
--- a/api/notification/internal/server/notificationimp/notification.go
+++ b/api/notification/internal/server/notificationimp/notification.go
@@ -9,6 +9,7 @@ import (
"github.com/tech/sendico/pkg/domainprovider"
"github.com/tech/sendico/pkg/merrors"
na "github.com/tech/sendico/pkg/messaging/notifications/account"
+ cnotifications "github.com/tech/sendico/pkg/messaging/notifications/confirmation"
ni "github.com/tech/sendico/pkg/messaging/notifications/invitation"
snotifications "github.com/tech/sendico/pkg/messaging/notifications/site"
"github.com/tech/sendico/pkg/mlogger"
@@ -70,6 +71,11 @@ func CreateAPI(a api.API) (*NotificationAPI, error) {
return nil, err
}
+ if err := a.Register().Consumer(cnotifications.NewConfirmationCodeProcessor(p.logger, db, p.onConfirmationCode)); err != nil {
+ p.logger.Error("Failed to create confirmation code handler", zap.Error(err))
+ return nil, err
+ }
+
idb, err := a.DBFactory().NewInvitationsDB()
if err != nil {
p.logger.Error("Failed to create invitation db connection", zap.Error(err))
diff --git a/api/payments/orchestrator/go.mod b/api/payments/orchestrator/go.mod
index 5973b59..f87cb3f 100644
--- a/api/payments/orchestrator/go.mod
+++ b/api/payments/orchestrator/go.mod
@@ -20,7 +20,7 @@ require (
github.com/tech/sendico/ledger v0.0.0-00010101000000-000000000000
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
gopkg.in/yaml.v3 v3.0.1
@@ -46,7 +46,7 @@ require (
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
@@ -54,7 +54,7 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
diff --git a/api/payments/orchestrator/go.sum b/api/payments/orchestrator/go.sum
index 4babef9..44a7f0b 100644
--- a/api/payments/orchestrator/go.sum
+++ b/api/payments/orchestrator/go.sum
@@ -9,8 +9,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -117,10 +115,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
-github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -142,10 +138,10 @@ github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0 h1:iXVA84s5hKMS5gn01GWOYHE3ymy/2b+0YkpFeTxB2XY=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0/go.mod h1:R6tMjTojRiaoo89fh/hf7tOmfzohdqSU17R9DwSVSog=
-github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
-github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
-github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
-github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
+github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
+github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
+github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
+github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@@ -159,32 +155,32 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -221,8 +217,6 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
diff --git a/api/pkg/db/confirmation/confirmation.go b/api/pkg/db/confirmation/confirmation.go
new file mode 100644
index 0000000..7f5d510
--- /dev/null
+++ b/api/pkg/db/confirmation/confirmation.go
@@ -0,0 +1,16 @@
+package confirmation
+
+import (
+ "context"
+
+ "github.com/tech/sendico/pkg/db/template"
+ "github.com/tech/sendico/pkg/model"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type DB interface {
+ template.DB[*model.ConfirmationCode]
+
+ FindActive(ctx context.Context, accountRef primitive.ObjectID, destination string, target model.ConfirmationTarget, now int64) (*model.ConfirmationCode, error)
+ DeleteTuple(ctx context.Context, accountRef primitive.ObjectID, destination string, target model.ConfirmationTarget) error
+}
diff --git a/api/pkg/db/factory.go b/api/pkg/db/factory.go
index dc8c7e8..3c37aac 100644
--- a/api/pkg/db/factory.go
+++ b/api/pkg/db/factory.go
@@ -3,6 +3,7 @@ package db
import (
"github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/db/account"
+ "github.com/tech/sendico/pkg/db/confirmation"
mongoimpl "github.com/tech/sendico/pkg/db/internal/mongo"
"github.com/tech/sendico/pkg/db/invitation"
"github.com/tech/sendico/pkg/db/organization"
@@ -17,6 +18,7 @@ import (
// Factory exposes high-level repositories used by application services.
type Factory interface {
NewRefreshTokensDB() (refreshtokens.DB, error)
+ NewConfirmationsDB() (confirmation.DB, error)
NewAccountDB() (account.DB, error)
NewOrganizationDB() (organization.DB, error)
diff --git a/api/pkg/db/internal/mongo/confirmationdb/db.go b/api/pkg/db/internal/mongo/confirmationdb/db.go
new file mode 100644
index 0000000..27cf571
--- /dev/null
+++ b/api/pkg/db/internal/mongo/confirmationdb/db.go
@@ -0,0 +1,67 @@
+package confirmationdb
+
+import (
+ "github.com/tech/sendico/pkg/db/confirmation"
+ ri "github.com/tech/sendico/pkg/db/repository/index"
+ "github.com/tech/sendico/pkg/db/template"
+ "github.com/tech/sendico/pkg/mlogger"
+ "github.com/tech/sendico/pkg/model"
+ "github.com/tech/sendico/pkg/mservice"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.uber.org/zap"
+)
+
+const (
+ fieldAccountRef = "accountRef"
+ fieldDestination = "destination"
+ fieldTarget = "target"
+ fieldExpiresAt = "expiresAt"
+ fieldUsed = "used"
+)
+
+type ConfirmationDB struct {
+ template.DBImp[*model.ConfirmationCode]
+}
+
+func Create(logger mlogger.Logger, db *mongo.Database) (confirmation.DB, error) {
+ p := &ConfirmationDB{
+ DBImp: *template.Create[*model.ConfirmationCode](logger, mservice.Confirmations, db),
+ }
+
+ // Ensure one active code per account/destination/target.
+ if err := p.Repository.CreateIndex(&ri.Definition{
+ Keys: []ri.Key{
+ {Field: fieldAccountRef, Sort: ri.Asc},
+ {Field: fieldDestination, Sort: ri.Asc},
+ {Field: fieldTarget, Sort: ri.Asc},
+ },
+ Unique: true,
+ }); err != nil {
+ p.Logger.Error("Failed to create confirmation unique index", zap.Error(err))
+ return nil, err
+ }
+
+ // TTL on expiry.
+ ttl := int32(0)
+ if err := p.Repository.CreateIndex(&ri.Definition{
+ Keys: []ri.Key{
+ {Field: fieldExpiresAt, Sort: ri.Asc},
+ },
+ TTL: &ttl,
+ }); err != nil {
+ p.Logger.Error("Failed to create confirmation TTL index", zap.Error(err))
+ return nil, err
+ }
+
+ // Query helper indexes.
+ if err := p.Repository.CreateIndex(&ri.Definition{
+ Keys: []ri.Key{
+ {Field: fieldUsed, Sort: ri.Asc},
+ },
+ }); err != nil {
+ p.Logger.Error("Failed to create confirmation used index", zap.Error(err))
+ return nil, err
+ }
+
+ return p, nil
+}
diff --git a/api/pkg/db/internal/mongo/confirmationdb/delete.go b/api/pkg/db/internal/mongo/confirmationdb/delete.go
new file mode 100644
index 0000000..c174435
--- /dev/null
+++ b/api/pkg/db/internal/mongo/confirmationdb/delete.go
@@ -0,0 +1,17 @@
+package confirmationdb
+
+import (
+ "context"
+
+ "github.com/tech/sendico/pkg/db/repository"
+ "github.com/tech/sendico/pkg/model"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+func (db *ConfirmationDB) DeleteTuple(ctx context.Context, accountRef primitive.ObjectID, destination string, target model.ConfirmationTarget) error {
+ query := repository.Query().
+ Filter(repository.Field(fieldAccountRef), accountRef).
+ Filter(repository.Field(fieldDestination), destination).
+ Filter(repository.Field(fieldTarget), target)
+ return db.DeleteMany(ctx, query)
+}
diff --git a/api/pkg/db/internal/mongo/confirmationdb/find.go b/api/pkg/db/internal/mongo/confirmationdb/find.go
new file mode 100644
index 0000000..3be2f63
--- /dev/null
+++ b/api/pkg/db/internal/mongo/confirmationdb/find.go
@@ -0,0 +1,26 @@
+package confirmationdb
+
+import (
+ "context"
+ "time"
+
+ "github.com/tech/sendico/pkg/db/repository"
+ "github.com/tech/sendico/pkg/db/repository/builder"
+ "github.com/tech/sendico/pkg/model"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+func (db *ConfirmationDB) FindActive(ctx context.Context, accountRef primitive.ObjectID, destination string, target model.ConfirmationTarget, now int64) (*model.ConfirmationCode, error) {
+ var res model.ConfirmationCode
+ query := repository.Query().
+ Filter(repository.Field(fieldAccountRef), accountRef).
+ Filter(repository.Field(fieldDestination), destination).
+ Filter(repository.Field(fieldTarget), target).
+ Filter(repository.Field(fieldUsed), false).
+ Comparison(repository.Field(fieldExpiresAt), builder.Gt, time.Unix(now, 0))
+
+ if err := db.FindOne(ctx, query, &res); err != nil {
+ return nil, err
+ }
+ return &res, nil
+}
diff --git a/api/pkg/db/internal/mongo/db.go b/api/pkg/db/internal/mongo/db.go
index 6083f15..02cc9ff 100755
--- a/api/pkg/db/internal/mongo/db.go
+++ b/api/pkg/db/internal/mongo/db.go
@@ -7,7 +7,9 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/db/account"
+ "github.com/tech/sendico/pkg/db/confirmation"
"github.com/tech/sendico/pkg/db/internal/mongo/accountdb"
+ "github.com/tech/sendico/pkg/db/internal/mongo/confirmationdb"
"github.com/tech/sendico/pkg/db/internal/mongo/invitationdb"
"github.com/tech/sendico/pkg/db/internal/mongo/organizationdb"
"github.com/tech/sendico/pkg/db/internal/mongo/policiesdb"
@@ -156,6 +158,10 @@ func (db *DB) NewAccountDB() (account.DB, error) {
return accountdb.Create(db.logger, db.db())
}
+func (db *DB) NewConfirmationsDB() (confirmation.DB, error) {
+ return confirmationdb.Create(db.logger, db.db())
+}
+
func (db *DB) NewOrganizationDB() (organization.DB, error) {
pdb, err := db.NewPoliciesDB()
if err != nil {
diff --git a/api/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go b/api/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go
index 750b0e7..129f34a 100644
--- a/api/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go
+++ b/api/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go
@@ -3,9 +3,9 @@ package builderimp
import (
"testing"
+ "github.com/stretchr/testify/assert"
"github.com/tech/sendico/pkg/db/repository/builder"
"github.com/tech/sendico/pkg/mservice"
- "github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
@@ -45,7 +45,7 @@ func TestPipelineImp_Lookup(t *testing.T) {
mockForeignField := &MockField{build: "foreignField"}
mockAsField := &MockField{build: "asField"}
- result := pipeline.Lookup(mservice.Projects, mockLocalField, mockForeignField, mockAsField)
+ result := pipeline.Lookup(mservice.Site, mockLocalField, mockForeignField, mockAsField)
// Should return self for chaining
assert.Same(t, pipeline, result)
@@ -54,7 +54,7 @@ func TestPipelineImp_Lookup(t *testing.T) {
assert.Len(t, built, 1)
expected := bson.D{{Key: string(builder.Lookup), Value: bson.D{
- {Key: string(builder.MKFrom), Value: mservice.Projects},
+ {Key: string(builder.MKFrom), Value: mservice.Site},
{Key: string(builder.MKLocalField), Value: "localField"},
{Key: string(builder.MKForeignField), Value: "foreignField"},
{Key: string(builder.MKAs), Value: "asField"},
@@ -70,7 +70,7 @@ func TestPipelineImp_LookupWithPipeline_WithoutLet(t *testing.T) {
}
mockAsField := &MockField{build: "asField"}
- result := pipeline.LookupWithPipeline(mservice.Tasks, mockNestedPipeline, mockAsField, nil)
+ result := pipeline.LookupWithPipeline(mservice.Site, mockNestedPipeline, mockAsField, nil)
// Should return self for chaining
assert.Same(t, pipeline, result)
@@ -79,7 +79,7 @@ func TestPipelineImp_LookupWithPipeline_WithoutLet(t *testing.T) {
assert.Len(t, built, 1)
expected := bson.D{{Key: string(builder.Lookup), Value: bson.D{
- {Key: string(builder.MKFrom), Value: mservice.Tasks},
+ {Key: string(builder.MKFrom), Value: mservice.Site},
{Key: string(builder.MKPipeline), Value: mockNestedPipeline.build},
{Key: string(builder.MKAs), Value: "asField"},
}}}
@@ -99,7 +99,7 @@ func TestPipelineImp_LookupWithPipeline_WithLet(t *testing.T) {
"projRef": mockLetField,
}
- result := pipeline.LookupWithPipeline(mservice.Tasks, mockNestedPipeline, mockAsField, &letVars)
+ result := pipeline.LookupWithPipeline(mservice.Site, mockNestedPipeline, mockAsField, &letVars)
// Should return self for chaining
assert.Same(t, pipeline, result)
@@ -108,7 +108,7 @@ func TestPipelineImp_LookupWithPipeline_WithLet(t *testing.T) {
assert.Len(t, built, 1)
expected := bson.D{{Key: string(builder.Lookup), Value: bson.D{
- {Key: string(builder.MKFrom), Value: mservice.Tasks},
+ {Key: string(builder.MKFrom), Value: mservice.Site},
{Key: string(builder.MKPipeline), Value: mockNestedPipeline.build},
{Key: string(builder.MKAs), Value: "asField"},
{Key: string(builder.MKLet), Value: bson.D{{Key: "projRef", Value: "$_id"}}},
@@ -126,14 +126,14 @@ func TestPipelineImp_LookupWithPipeline_WithEmptyLet(t *testing.T) {
emptyLetVars := map[string]builder.Field{}
- pipeline.LookupWithPipeline(mservice.Tasks, mockNestedPipeline, mockAsField, &emptyLetVars)
+ pipeline.LookupWithPipeline(mservice.Site, mockNestedPipeline, mockAsField, &emptyLetVars)
built := pipeline.Build()
assert.Len(t, built, 1)
// Should not include let field when empty
expected := bson.D{{Key: string(builder.Lookup), Value: bson.D{
- {Key: string(builder.MKFrom), Value: mservice.Tasks},
+ {Key: string(builder.MKFrom), Value: mservice.Site},
{Key: string(builder.MKPipeline), Value: mockNestedPipeline.build},
{Key: string(builder.MKAs), Value: "asField"},
}}}
diff --git a/api/pkg/go.mod b/api/pkg/go.mod
index e96d7fa..be42262 100644
--- a/api/pkg/go.mod
+++ b/api/pkg/go.mod
@@ -15,8 +15,8 @@ require (
github.com/testcontainers/testcontainers-go v0.33.0
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
- golang.org/x/crypto v0.44.0
+ go.uber.org/zap v1.27.1
+ golang.org/x/crypto v0.45.0
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
)
@@ -67,7 +67,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
diff --git a/api/pkg/go.sum b/api/pkg/go.sum
index d3150a6..6c0ebad 100644
--- a/api/pkg/go.sum
+++ b/api/pkg/go.sum
@@ -11,8 +11,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/casbin/casbin/v2 v2.132.0 h1:73hGmOszGSL3hTVquwkAi98XLl3gPJ+BxB6D7G9Fxtk=
-github.com/casbin/casbin/v2 v2.132.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/casbin/v2 v2.134.0 h1:wyO3hZb487GzlGVAI2hUoHQT0ehFD+9B5P+HVG9BVTM=
github.com/casbin/casbin/v2 v2.134.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
@@ -128,14 +126,12 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
-github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
-github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
@@ -174,27 +170,23 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
-go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
@@ -202,16 +194,16 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -275,12 +267,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
-google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
+google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
+google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
diff --git a/api/pkg/messaging/internal/notifications/confirmation/notification.go b/api/pkg/messaging/internal/notifications/confirmation/notification.go
new file mode 100644
index 0000000..c45f5ae
--- /dev/null
+++ b/api/pkg/messaging/internal/notifications/confirmation/notification.go
@@ -0,0 +1,47 @@
+package notifications
+
+import (
+ "encoding/json"
+
+ messaging "github.com/tech/sendico/pkg/messaging/envelope"
+ "github.com/tech/sendico/pkg/model"
+ nm "github.com/tech/sendico/pkg/model/notification"
+ "github.com/tech/sendico/pkg/mservice"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type confirmationCodePayload struct {
+ AccountRef string `json:"accountRef"`
+ Destination string `json:"destination"`
+ Target string `json:"target"`
+ Code string `json:"code"`
+}
+
+type ConfirmationCodeNotification struct {
+ messaging.Envelope
+ payload confirmationCodePayload
+}
+
+func (ccn *ConfirmationCodeNotification) Serialize() ([]byte, error) {
+ data, err := json.Marshal(ccn.payload)
+ if err != nil {
+ return nil, err
+ }
+ return ccn.Envelope.Wrap(data)
+}
+
+func newConfirmationEvent(action nm.NotificationAction) model.NotificationEvent {
+ return model.NewNotification(mservice.Confirmations, action)
+}
+
+func NewConfirmationCodeEnvelope(sender string, accountRef primitive.ObjectID, destination string, target model.ConfirmationTarget, code string) messaging.Envelope {
+ return &ConfirmationCodeNotification{
+ Envelope: messaging.CreateEnvelope(sender, newConfirmationEvent(nm.NAPending)),
+ payload: confirmationCodePayload{
+ AccountRef: accountRef.Hex(),
+ Destination: destination,
+ Target: string(target),
+ Code: code,
+ },
+ }
+}
diff --git a/api/pkg/messaging/internal/notifications/confirmation/processor.go b/api/pkg/messaging/internal/notifications/confirmation/processor.go
new file mode 100644
index 0000000..c085712
--- /dev/null
+++ b/api/pkg/messaging/internal/notifications/confirmation/processor.go
@@ -0,0 +1,70 @@
+package notifications
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/tech/sendico/pkg/db/account"
+ "github.com/tech/sendico/pkg/merrors"
+ me "github.com/tech/sendico/pkg/messaging/envelope"
+ ch "github.com/tech/sendico/pkg/messaging/notifications/confirmation/handler"
+ np "github.com/tech/sendico/pkg/messaging/notifications/processor"
+ "github.com/tech/sendico/pkg/mlogger"
+ "github.com/tech/sendico/pkg/model"
+ nm "github.com/tech/sendico/pkg/model/notification"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "go.uber.org/zap"
+)
+
+type ConfirmationCodeProcessor struct {
+ logger mlogger.Logger
+ db account.DB
+ handler ch.ConfirmationCodeHandler
+ event model.NotificationEvent
+}
+
+func (ccp *ConfirmationCodeProcessor) Process(ctx context.Context, envelope me.Envelope) error {
+ var msg confirmationCodePayload
+ if err := json.Unmarshal(envelope.GetData(), &msg); err != nil {
+ ccp.logger.Warn("Failed to unmarshal confirmation code envelope", zap.Error(err), zap.String("topic", ccp.event.ToString()))
+ return err
+ }
+
+ accountRef, err := primitive.ObjectIDFromHex(msg.AccountRef)
+ if err != nil {
+ ccp.logger.Warn("Failed to restore account id from envelope", zap.Error(err), zap.String("topic", ccp.event.ToString()), zap.String("account_ref", msg.AccountRef))
+ return err
+ }
+
+ var account model.Account
+ if err := ccp.db.Get(ctx, accountRef, &account); err != nil {
+ ccp.logger.Warn("Failed to fetch account for confirmation code", zap.Error(err), zap.String("topic", ccp.event.ToString()), zap.String("account_ref", msg.AccountRef))
+ return err
+ }
+
+ target := model.ConfirmationTarget(msg.Target)
+ if target != model.ConfirmationTargetLogin && target != model.ConfirmationTargetPayout {
+ return merrors.InvalidArgument("invalid confirmation target", "target")
+ }
+ if msg.Code == "" {
+ return merrors.InvalidArgument("empty confirmation code", "code")
+ }
+ if msg.Destination == "" {
+ return merrors.InvalidArgument("empty destination", "destination")
+ }
+
+ return ccp.handler(ctx, &account, msg.Destination, target, msg.Code)
+}
+
+func (ccp *ConfirmationCodeProcessor) GetSubject() model.NotificationEvent {
+ return ccp.event
+}
+
+func NewConfirmationCodeProcessor(logger mlogger.Logger, db account.DB, handler ch.ConfirmationCodeHandler) np.EnvelopeProcessor {
+ return &ConfirmationCodeProcessor{
+ logger: logger.Named("confirmation_code_processor"),
+ db: db,
+ handler: handler,
+ event: newConfirmationEvent(nm.NAPending),
+ }
+}
diff --git a/api/pkg/messaging/notifications/confirmation/confirmation.go b/api/pkg/messaging/notifications/confirmation/confirmation.go
new file mode 100644
index 0000000..23ec6db
--- /dev/null
+++ b/api/pkg/messaging/notifications/confirmation/confirmation.go
@@ -0,0 +1,20 @@
+package notifications
+
+import (
+ "github.com/tech/sendico/pkg/db/account"
+ messaging "github.com/tech/sendico/pkg/messaging/envelope"
+ cinternal "github.com/tech/sendico/pkg/messaging/internal/notifications/confirmation"
+ ch "github.com/tech/sendico/pkg/messaging/notifications/confirmation/handler"
+ np "github.com/tech/sendico/pkg/messaging/notifications/processor"
+ "github.com/tech/sendico/pkg/mlogger"
+ "github.com/tech/sendico/pkg/model"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+func Code(sender string, accountRef primitive.ObjectID, destination string, target model.ConfirmationTarget, code string) messaging.Envelope {
+ return cinternal.NewConfirmationCodeEnvelope(sender, accountRef, destination, target, code)
+}
+
+func NewConfirmationCodeProcessor(logger mlogger.Logger, db account.DB, handler ch.ConfirmationCodeHandler) np.EnvelopeProcessor {
+ return cinternal.NewConfirmationCodeProcessor(logger, db, handler)
+}
diff --git a/api/pkg/messaging/notifications/confirmation/handler/interface.go b/api/pkg/messaging/notifications/confirmation/handler/interface.go
new file mode 100644
index 0000000..a7c2477
--- /dev/null
+++ b/api/pkg/messaging/notifications/confirmation/handler/interface.go
@@ -0,0 +1,9 @@
+package handler
+
+import (
+ "context"
+
+ "github.com/tech/sendico/pkg/model"
+)
+
+type ConfirmationCodeHandler = func(context.Context, *model.Account, string, model.ConfirmationTarget, string) error
diff --git a/api/pkg/model/confirmation.go b/api/pkg/model/confirmation.go
new file mode 100644
index 0000000..1fdcfc6
--- /dev/null
+++ b/api/pkg/model/confirmation.go
@@ -0,0 +1,43 @@
+package model
+
+import (
+ "time"
+
+ "github.com/tech/sendico/pkg/mservice"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type ConfirmationTarget string
+
+const (
+ ConfirmationTargetLogin ConfirmationTarget = "login"
+ ConfirmationTargetPayout ConfirmationTarget = "payout"
+)
+
+// ConfirmationCode stores verification codes for operations like login or payouts.
+type ConfirmationCode struct {
+ AccountBoundBase `bson:",inline" json:",inline"`
+
+ Destination string `bson:"destination" json:"destination"`
+ Target ConfirmationTarget `bson:"target" json:"target"`
+ CodeHash []byte `bson:"codeHash" json:"-"`
+ Salt []byte `bson:"salt" json:"-"`
+ ExpiresAt time.Time `bson:"expiresAt" json:"expiresAt"`
+ Attempts int `bson:"attempts" json:"attempts"`
+ MaxAttempts int `bson:"maxAttempts" json:"maxAttempts"`
+ ResendCount int `bson:"resendCount" json:"resendCount"`
+ ResendLimit int `bson:"resendLimit" json:"resendLimit"`
+ CooldownUntil time.Time `bson:"cooldownUntil" json:"cooldownUntil"`
+ Used bool `bson:"used" json:"used"`
+}
+
+func (c *ConfirmationCode) Collection() string {
+ return mservice.Confirmations
+}
+
+func NewConfirmationCode(accountRef primitive.ObjectID) *ConfirmationCode {
+ cc := &ConfirmationCode{}
+ cc.SetID(primitive.NewObjectID())
+ cc.AccountRef = &accountRef
+ return cc
+}
diff --git a/api/pkg/mservice/services.go b/api/pkg/mservice/services.go
index be4e30f..51b3b47 100644
--- a/api/pkg/mservice/services.go
+++ b/api/pkg/mservice/services.go
@@ -6,12 +6,11 @@ type Type = string
const (
Accounts Type = "accounts" // Represents user accounts in the system
+ Confirmations Type = "confirmations" // Represents confirmation code flows
Amplitude Type = "amplitude" // Represents analytics integration with Amplitude
Site Type = "site" // Represents public site endpoints
- Automations Type = "automation" // Represents automation workflows
Changes Type = "changes" // Tracks changes made to resources
Clients Type = "clients" // Represents client information
- Comments Type = "comments" // Represents comments on tasks or other resources
ChainGateway Type = "chain_gateway" // Represents chain gateway microservice
FXOracle Type = "fx_oracle" // Represents FX oracle microservice
FeePlans Type = "fee_plans" // Represents fee plans microservice
@@ -37,37 +36,22 @@ const (
Permissions Type = "permissions" // Represents permissiosns service
Policies Type = "policies" // Represents access control policies
PolicyAssignements Type = "policy_assignments" // Represents policy assignments database
- Priorities Type = "priorities" // Represents object properties
- PriorityGroups Type = "priority_groups" // Represents task or project priorities
- Projects Type = "projects" // Represents projects managed in the system
- PropertyBindings Type = "property_bindings" // Represents properties bindings of resources
- PropertySchemas Type = "property_schemas" // Represents properties or attributes of resources
- Properties Type = "properties" // Represents property values of the propertites of specific objects
- Reactions Type = "reactions" // Represents comment reactions
RefreshTokens Type = "refresh_tokens" // Represents refresh tokens for authentication
Roles Type = "roles" // Represents roles in access control
- Statuses Type = "statuses" // Represents statuses of tasks or projects
- StatusGroups Type = "status_groups" // Represents status groups
- Steps Type = "steps" // Represents steps in workflows or processes
Storage Type = "storage" // Represents statuses of tasks or projects
- Tags Type = "tags" // Represents tags managed in the system
- Tasks Type = "tasks" // Represents tasks managed in the system
- Teams Type = "teams" // Represents teams managed in the system
Tenants Type = "tenants" // Represents tenants managed in the system
Workflows Type = "workflows" // Represents workflows for tasks or projects
- Workspaces Type = "workspaces" // Represents workspaces containing projects and teams
)
func StringToSType(s string) (Type, error) {
switch Type(s) {
- case Accounts, Amplitude, Site, Automations, Changes, Clients, Comments, ChainGateway, ChainWallets, ChainWalletBalances,
+ case Accounts, Confirmations, Amplitude, Site, Changes, Clients, ChainGateway, ChainWallets, ChainWalletBalances,
ChainTransfers, ChainDeposits, FXOracle, FeePlans, FilterProjects, Invitations, Invoices, Logo, Ledger,
LedgerAccounts, LedgerBalances, LedgerEntries, LedgerOutbox, LedgerParties, LedgerPlines, Notifications,
- Organizations, Payments, PaymentOrchestrator, Permissions, Policies, PolicyAssignements, Priorities,
- PriorityGroups, Projects, PropertyBindings, PropertySchemas, Properties, Reactions, RefreshTokens, Roles,
- Statuses, StatusGroups, Steps, Storage, Tags, Tasks, Teams, Tenants, Workflows, Workspaces:
+ Organizations, Payments, PaymentOrchestrator, Permissions, Policies, PolicyAssignements,
+ RefreshTokens, Roles, Storage, Tenants, Workflows:
return Type(s), nil
default:
- return "", merrors.DataConflict("invalid service type: " + s)
+ return "", merrors.InvalidArgument("invalid service type", s)
}
}
diff --git a/api/server/go.mod b/api/server/go.mod
index 4618b51..27cea8c 100644
--- a/api/server/go.mod
+++ b/api/server/go.mod
@@ -7,10 +7,10 @@ replace github.com/tech/sendico/pkg => ../pkg
replace github.com/tech/sendico/chain/gateway => ../chain/gateway
require (
- github.com/aws/aws-sdk-go-v2 v1.39.6
- github.com/aws/aws-sdk-go-v2/config v1.31.20
- github.com/aws/aws-sdk-go-v2/credentials v1.18.24
- github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2
+ github.com/aws/aws-sdk-go-v2 v1.40.0
+ github.com/aws/aws-sdk-go-v2/config v1.32.0
+ github.com/aws/aws-sdk-go-v2/credentials v1.19.0
+ github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/cors v1.2.2
github.com/go-chi/jwtauth/v5 v5.3.3
@@ -23,7 +23,7 @@ require (
github.com/testcontainers/testcontainers-go v0.33.0
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
go.mongodb.org/mongo-driver v1.17.6
- go.uber.org/zap v1.27.0
+ go.uber.org/zap v1.27.1
golang.org/x/net v0.47.0
gopkg.in/yaml.v3 v3.0.1
moul.io/chizap v1.0.3
@@ -40,18 +40,19 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
- github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 // indirect
- github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 // indirect
+ github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 // indirect
github.com/aws/smithy-go v1.23.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/casbin/mongodb-adapter/v3 v3.7.0 // indirect
@@ -104,7 +105,7 @@ require (
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.3 // indirect
+ github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
@@ -126,7 +127,7 @@ require (
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
- golang.org/x/crypto v0.44.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
diff --git a/api/server/go.sum b/api/server/go.sum
index 6c9922f..e2d491e 100644
--- a/api/server/go.sum
+++ b/api/server/go.sum
@@ -6,40 +6,42 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk=
-github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
+github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc=
+github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y=
-github.com/aws/aws-sdk-go-v2/config v1.31.20 h1:/jWF4Wu90EhKCgjTdy1DGxcbcbNrjfBHvksEL79tfQc=
-github.com/aws/aws-sdk-go-v2/config v1.31.20/go.mod h1:95Hh1Tc5VYKL9NJ7tAkDcqeKt+MCXQB1hQZaRdJIZE0=
-github.com/aws/aws-sdk-go-v2/credentials v1.18.24 h1:iJ2FmPT35EaIB0+kMa6TnQ+PwG5A1prEdAw+PsMzfHg=
-github.com/aws/aws-sdk-go-v2/credentials v1.18.24/go.mod h1:U91+DrfjAiXPDEGYhh/x29o4p0qHX5HDqG7y5VViv64=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M=
+github.com/aws/aws-sdk-go-v2/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA=
+github.com/aws/aws-sdk-go-v2/config v1.32.0/go.mod h1:pSRm/+D3TxBixGMXlgtX4+MPO9VNtEEtiFmNpxksoxw=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.0 h1:7zm+ez+qEqLaNsCSRaistkvJRJv8sByDOVuCnyHbP7M=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.0/go.mod h1:pHKPblrT7hqFGkNLxqoS3FlGoPrQg4hMIa+4asZzBfs=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13/go.mod h1:/FDdxWhz1486obGrKKC1HONd7krpk38LBt+dutLcN9k=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 h1:ITi7qiDSv/mSGDSWNpZ4k4Ve0DQR6Ug2SJQ8zEHoDXg=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14/go.mod h1:k1xtME53H1b6YpZt74YmwlONMWf4ecM+lut1WQLAF/U=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4=
-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4/go.mod h1:455WPHSwaGj2waRSpQp7TsnpOnBfw8iDfPfbwl7KPJE=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 h1:zhBJXdhWIFZ1acfDYIhu4+LCzdUS2Vbcum7D01dXlHQ=
-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13/go.mod h1:JaaOeCE368qn2Hzi3sEzY6FgAZVCIYcC2nwbro2QCh8=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2 h1:DhdbtDl4FdNlj31+xiRXANxEE+eC7n8JQz+/ilwQ8Uc=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw=
-github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 h1:NjShtS1t8r5LUfFVtFeI8xLAHQNTa7UI0VawXlrBMFQ=
-github.com/aws/aws-sdk-go-v2/service/sso v1.30.3/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 h1:gTsnx0xXNQ6SBbymoDvcoRHL+q4l/dAFsQuKfDWSaGc=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo=
-github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 h1:HK5ON3KmQV2HcAunnx4sKLB9aPf3gKGwVAf7xnx0QT0=
-github.com/aws/aws-sdk-go-v2/service/sts v1.40.2/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 h1:Hjkh7kE6D81PgrHlE/m9gx+4TyyeLHuY8xJs7yXN5C4=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5/go.mod h1:nPRXgyCfAurhyaTMoBMwRBYBhaHI4lNPAnJmjM0Tslc=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0 h1:8FshVvnV2sr9kOSAbOnc/vwVmmAwMjOedKH6JW2ddPM=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw=
+github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g=
+github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 h1:MvlNs/f+9eM0mOjD9JzBUbf5jghyTk3p+O9yHMXX94Y=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
+github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA=
+github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@@ -196,8 +198,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q=
-github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
+github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
+github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -278,16 +280,16 @@ go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95a
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
-golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
diff --git a/api/server/interface/api/register.go b/api/server/interface/api/register.go
index 4a46514..63683bd 100644
--- a/api/server/interface/api/register.go
+++ b/api/server/interface/api/register.go
@@ -11,6 +11,7 @@ import (
type Register interface {
Handler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.HandlerFunc)
AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc)
+ PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc)
WSHandler(messageType string, handler ws.HandlerFunc)
Messaging() messaging.Register
diff --git a/api/server/interface/api/sresponse/login_pending.go b/api/server/interface/api/sresponse/login_pending.go
new file mode 100644
index 0000000..5ef2d4a
--- /dev/null
+++ b/api/server/interface/api/sresponse/login_pending.go
@@ -0,0 +1,31 @@
+package sresponse
+
+import (
+ "net/http"
+
+ "github.com/tech/sendico/pkg/api/http/response"
+ "github.com/tech/sendico/pkg/mlogger"
+ "github.com/tech/sendico/pkg/model"
+)
+
+type pendingLoginResponse struct {
+ Account accountResponse `json:"account"`
+ PendingToken TokenData `json:"pendingToken"`
+ Destination string `json:"destination"`
+ TTLSeconds int `json:"ttlSeconds"`
+}
+
+func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *TokenData, destination string, ttlSeconds int) http.HandlerFunc {
+ return response.Accepted(
+ logger,
+ &pendingLoginResponse{
+ Account: accountResponse{
+ Account: *_createAccount(account, false),
+ authResponse: authResponse{},
+ },
+ PendingToken: *pendingToken,
+ Destination: destination,
+ TTLSeconds: ttlSeconds,
+ },
+ )
+}
diff --git a/api/server/interface/api/sresponse/response.go b/api/server/interface/api/sresponse/response.go
index 04a6cf8..8bd7211 100644
--- a/api/server/interface/api/sresponse/response.go
+++ b/api/server/interface/api/sresponse/response.go
@@ -4,9 +4,11 @@ import (
"net/http"
"github.com/tech/sendico/pkg/model"
+ emodel "github.com/tech/sendico/server/interface/model"
)
type (
- HandlerFunc = func(r *http.Request) http.HandlerFunc
- AccountHandlerFunc = func(r *http.Request, account *model.Account, accessToken *TokenData) http.HandlerFunc
+ HandlerFunc = func(r *http.Request) http.HandlerFunc
+ AccountHandlerFunc = func(r *http.Request, account *model.Account, accessToken *TokenData) http.HandlerFunc
+ PendingAccountHandlerFunc = func(r *http.Request, account *model.Account, token *emodel.AccountToken) http.HandlerFunc
)
diff --git a/api/server/interface/model/token.go b/api/server/interface/model/token.go
index 7f51ba6..9390ab6 100644
--- a/api/server/interface/model/token.go
+++ b/api/server/interface/model/token.go
@@ -17,6 +17,7 @@ type AccountToken struct {
Name string
Locale string
Expiration time.Time
+ Pending bool
}
func createAccountToken(a *model.Account, expiration int) AccountToken {
@@ -26,6 +27,7 @@ func createAccountToken(a *model.Account, expiration int) AccountToken {
Name: a.Name,
Locale: a.Locale,
Expiration: time.Now().Add(mduration.Param2Duration(expiration, time.Hour)),
+ Pending: false,
}
}
@@ -44,6 +46,7 @@ const (
paramNameLocale = "locale"
paramNameLogin = "login"
paramNameExpiration = "exp"
+ paramNamePending = "pending"
)
func Claims2Token(claims middleware.MapClaims) (*AccountToken, error) {
@@ -65,6 +68,11 @@ func Claims2Token(claims middleware.MapClaims) (*AccountToken, error) {
if at.Locale, err = getTokenParam(claims, paramNameLocale); err != nil {
return nil, err
}
+ if pending, ok := claims[paramNamePending]; ok {
+ if pbool, ok := pending.(bool); ok {
+ at.Pending = pbool
+ }
+ }
if expValue, ok := claims[paramNameExpiration]; ok {
switch exp := expValue.(type) {
case time.Time:
@@ -90,5 +98,20 @@ func Account2Claims(a *model.Account, expiration int) middleware.MapClaims {
paramNameName: t.Name,
paramNameLocale: t.Locale,
paramNameExpiration: int64(t.Expiration.Unix()),
+ paramNamePending: t.Pending,
+ }
+}
+
+func PendingAccount2Claims(a *model.Account, expirationMinutes int) middleware.MapClaims {
+ t := createAccountToken(a, expirationMinutes/60)
+ t.Expiration = time.Now().Add(time.Duration(expirationMinutes) * time.Minute)
+ t.Pending = true
+ return middleware.MapClaims{
+ paramNameID: t.AccountRef.Hex(),
+ paramNameLogin: t.Login,
+ paramNameName: t.Name,
+ paramNameLocale: t.Locale,
+ paramNameExpiration: t.Expiration.Unix(),
+ paramNamePending: t.Pending,
}
}
diff --git a/api/server/interface/services/confirmation/confirmation.go b/api/server/interface/services/confirmation/confirmation.go
new file mode 100644
index 0000000..39cc975
--- /dev/null
+++ b/api/server/interface/services/confirmation/confirmation.go
@@ -0,0 +1,11 @@
+package confirmation
+
+import (
+ "github.com/tech/sendico/pkg/mservice"
+ "github.com/tech/sendico/server/interface/api"
+ "github.com/tech/sendico/server/internal/server/confirmationimp"
+)
+
+func Create(a api.API) (mservice.MicroService, error) {
+ return confirmationimp.CreateAPI(a)
+}
diff --git a/api/server/internal/api/api.go b/api/server/internal/api/api.go
index 822e514..d37506f 100644
--- a/api/server/internal/api/api.go
+++ b/api/server/internal/api/api.go
@@ -12,6 +12,7 @@ import (
"github.com/tech/sendico/pkg/mservice"
"github.com/tech/sendico/server/interface/api"
"github.com/tech/sendico/server/interface/services/account"
+ "github.com/tech/sendico/server/interface/services/confirmation"
"github.com/tech/sendico/server/interface/services/invitation"
"github.com/tech/sendico/server/interface/services/logo"
"github.com/tech/sendico/server/interface/services/organization"
@@ -76,6 +77,7 @@ func (a *APIImp) installServices() error {
srvf := make([]api.MicroServiceFactoryT, 0)
srvf = append(srvf, account.Create)
+ srvf = append(srvf, confirmation.Create)
srvf = append(srvf, organization.Create)
srvf = append(srvf, invitation.Create)
srvf = append(srvf, logo.Create)
diff --git a/api/server/internal/api/middleware.go b/api/server/internal/api/middleware.go
index 800c800..c024582 100644
--- a/api/server/internal/api/middleware.go
+++ b/api/server/internal/api/middleware.go
@@ -45,6 +45,10 @@ func (mw *Middleware) AccountHandler(service mservice.Type, endpoint string, met
mw.epdispatcher.AccountHandler(service, endpoint, method, handler)
}
+func (mw *Middleware) PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc) {
+ mw.epdispatcher.PendingAccountHandler(service, endpoint, method, handler)
+}
+
func (mw *Middleware) WSHandler(messageType string, handler wsh.HandlerFunc) {
mw.wshandler.InstallHandler(messageType, handler)
}
@@ -133,7 +137,13 @@ func CreateMiddleware(logger mlogger.Logger, db db.Factory, enforcer auth.Enforc
return nil, err
}
- p.epdispatcher = routers.NewDispatcher(p.logger, p.router, adb, rtdb, enforcer, config)
+ cdb, err := db.NewConfirmationsDB()
+ if err != nil {
+ p.logger.Error("Failed to create confirmations database", zap.Error(err))
+ return nil, err
+ }
+
+ p.epdispatcher = routers.NewDispatcher(p.logger, p.router, adb, cdb, rtdb, enforcer, config)
p.wshandler = ws.NewRouter(p.logger, p.router, &config.WebSocket, p.apiEndpoint)
return p, nil
}
diff --git a/api/server/internal/api/routers/authorized/handler.go b/api/server/internal/api/routers/authorized/handler.go
index b0a3580..29118ce 100644
--- a/api/server/internal/api/routers/authorized/handler.go
+++ b/api/server/internal/api/routers/authorized/handler.go
@@ -37,6 +37,9 @@ func (ar *AuthorizedRouter) tokenHandler(service mservice.Type, endpoint string,
func (ar *AuthorizedRouter) AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc) {
hndlr := func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc {
+ if t.Pending {
+ return response.Forbidden(ar.logger, ar.service, "confirmation_required", "pending token requires confirmation")
+ }
var a model.Account
if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
if errors.Is(err, merrors.ErrNoData) {
@@ -54,3 +57,18 @@ func (ar *AuthorizedRouter) AccountHandler(service mservice.Type, endpoint strin
}
ar.tokenHandler(service, endpoint, method, hndlr)
}
+
+func (ar *AuthorizedRouter) PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc) {
+ hndlr := func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc {
+ var a model.Account
+ if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
+ if errors.Is(err, merrors.ErrNoData) {
+ ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.ObjRef("account_ref", t.AccountRef))
+ return response.NotFound(ar.logger, ar.service, err.Error())
+ }
+ return response.Internal(ar.logger, ar.service, err)
+ }
+ return handler(r, &a, t)
+ }
+ ar.tokenHandler(service, endpoint, method, hndlr)
+}
diff --git a/api/server/internal/api/routers/dispatcher.go b/api/server/internal/api/routers/dispatcher.go
index 6f6ebb0..3ec2929 100644
--- a/api/server/internal/api/routers/dispatcher.go
+++ b/api/server/internal/api/routers/dispatcher.go
@@ -8,6 +8,7 @@ import (
api "github.com/tech/sendico/pkg/api/http"
"github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/db/account"
+ "github.com/tech/sendico/pkg/db/confirmation"
"github.com/tech/sendico/pkg/db/refreshtokens"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
@@ -31,7 +32,11 @@ func (d *Dispatcher) AccountHandler(service mservice.Type, endpoint string, meth
d.protected.AccountHandler(service, endpoint, method, handler)
}
-func NewDispatcher(logger mlogger.Logger, router chi.Router, db account.DB, rtdb refreshtokens.DB, enforcer auth.Enforcer, config *middleware.Config) *Dispatcher {
+func (d *Dispatcher) PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc) {
+ d.protected.PendingAccountHandler(service, endpoint, method, handler)
+}
+
+func NewDispatcher(logger mlogger.Logger, router chi.Router, db account.DB, cdb confirmation.DB, rtdb refreshtokens.DB, enforcer auth.Enforcer, config *middleware.Config) *Dispatcher {
d := &Dispatcher{
logger: logger.Named("api_dispatcher"),
}
@@ -40,7 +45,7 @@ func NewDispatcher(logger mlogger.Logger, router chi.Router, db account.DB, rtdb
endpoint := os.Getenv(config.EndPointEnv)
signature := middleware.SignatureConf(config)
router.Group(func(r chi.Router) {
- d.public = rpublic.NewRouter(d.logger, endpoint, db, rtdb, r, &config.Token, &signature)
+ d.public = rpublic.NewRouter(d.logger, endpoint, db, cdb, rtdb, r, &config.Token, &signature)
})
router.Group(func(r chi.Router) {
d.protected = rauthorized.NewRouter(d.logger, endpoint, r, db, enforcer, &config.Token, &signature)
diff --git a/api/server/internal/api/routers/endpoint/token.go b/api/server/internal/api/routers/endpoint/token.go
index bb01eb2..be85a2a 100644
--- a/api/server/internal/api/routers/endpoint/token.go
+++ b/api/server/internal/api/routers/endpoint/token.go
@@ -18,3 +18,13 @@ func (er *HttpEndpointRouter) CreateAccessToken(user *model.Account) (sresponse.
}
return token, err
}
+
+func (er *HttpEndpointRouter) CreatePendingToken(user *model.Account, ttlMinutes int) (sresponse.TokenData, error) {
+ ja := jwtauth.New(er.signature.Algorithm, er.signature.PrivateKey, er.signature.PublicKey)
+ _, res, err := ja.Encode(emodel.PendingAccount2Claims(user, ttlMinutes))
+ token := sresponse.TokenData{
+ Token: res,
+ Expiration: time.Now().Add(time.Duration(ttlMinutes) * time.Minute),
+ }
+ return token, err
+}
diff --git a/api/server/internal/api/routers/public/login.go b/api/server/internal/api/routers/public/login.go
index b668b9e..c8c90c1 100644
--- a/api/server/internal/api/routers/public/login.go
+++ b/api/server/internal/api/routers/public/login.go
@@ -6,14 +6,20 @@ import (
"errors"
"net/http"
"strings"
+ "time"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/merrors"
+ "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice"
"github.com/tech/sendico/server/interface/api/srequest"
+ "github.com/tech/sendico/server/interface/api/sresponse"
+ "github.com/tech/sendico/server/internal/server/confirmationimp"
"go.uber.org/zap"
)
+const pendingLoginTTLMinutes = 10
+
func (pr *PublicRouter) logUserIn(ctx context.Context, r *http.Request, req *srequest.Login) http.HandlerFunc {
// Get the account database entry
trimmedLogin := strings.TrimSpace(req.Login)
@@ -35,13 +41,23 @@ func (pr *PublicRouter) logUserIn(ctx context.Context, r *http.Request, req *sre
return response.Unauthorized(pr.logger, pr.service, "password does not match")
}
- accessToken, err := pr.imp.CreateAccessToken(account)
+ pendingToken, err := pr.imp.CreatePendingToken(account, pendingLoginTTLMinutes)
if err != nil {
- pr.logger.Warn("Failed to generate access token", zap.Error(err))
+ pr.logger.Warn("Failed to generate pending token", zap.Error(err))
return response.Internal(pr.logger, pr.service, err)
}
- return pr.refreshAndRespondLogin(ctx, r, &req.SessionIdentifier, account, &accessToken)
+ cfg := confirmationimp.DefaultConfig()
+ _, rec, err := pr.cstore.Create(ctx, account.ID, account.Login, model.ConfirmationTargetLogin, cfg, pr.generateCode)
+ if err != nil {
+ pr.logger.Warn("Failed to create login confirmation code", zap.Error(err))
+ return response.Internal(pr.logger, pr.service, err)
+ }
+ pr.logger.Info("Login confirmation code issued",
+ zap.String("destination", pr.maskEmail(account.Login)),
+ zap.String("account", account.Login))
+
+ return sresponse.LoginPending(pr.logger, account, &pendingToken, pr.maskEmail(account.Login), int(time.Until(rec.ExpiresAt).Seconds()))
}
func (a *PublicRouter) login(r *http.Request) http.HandlerFunc {
diff --git a/api/server/internal/api/routers/public/respond.go b/api/server/internal/api/routers/public/respond.go
index 002c51f..71dff09 100644
--- a/api/server/internal/api/routers/public/respond.go
+++ b/api/server/internal/api/routers/public/respond.go
@@ -2,59 +2,16 @@ package routers
import (
"context"
- "crypto/rand"
- "encoding/base64"
- "io"
"net/http"
- "time"
"github.com/tech/sendico/pkg/api/http/response"
- "github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mutil/mzap"
"github.com/tech/sendico/server/interface/api/sresponse"
+ rtokens "github.com/tech/sendico/server/internal/api/routers/tokens"
"go.uber.org/zap"
)
-func generateRefreshTokenData(length int) (string, error) {
- randomBytes := make([]byte, length)
- if _, err := io.ReadFull(rand.Reader, randomBytes); err != nil {
- return "", merrors.Internal("failed to generate secure random bytes: " + err.Error())
- }
-
- return base64.URLEncoding.EncodeToString(randomBytes), nil
-}
-
-func (er *PublicRouter) prepareRefreshToken(ctx context.Context, r *http.Request, session *model.SessionIdentifier, account *model.Account) (*model.RefreshToken, error) {
- refreshToken, err := generateRefreshTokenData(er.config.Length)
- if err != nil {
- er.logger.Warn("Failed to generate refresh token", zap.Error(err), mzap.StorableRef(account))
- return nil, err
- }
-
- token := &model.RefreshToken{
- AccountBoundBase: model.AccountBoundBase{
- AccountRef: account.GetID(),
- },
- ClientRefreshToken: model.ClientRefreshToken{
- SessionIdentifier: *session,
- RefreshToken: refreshToken,
- },
- ExpiresAt: time.Now().Add(time.Duration(er.config.Expiration.Refresh) * time.Hour),
- IsRevoked: false,
- UserAgent: r.UserAgent(),
- IPAddress: r.RemoteAddr,
- }
-
- if err = er.rtdb.Create(ctx, token); err != nil {
- er.logger.Warn("Failed to store a refresh token", zap.Error(err), mzap.StorableRef(account),
- zap.String("client_id", token.ClientID), zap.String("device_id", token.DeviceID))
- return nil, err
- }
-
- return token, nil
-}
-
func (pr *PublicRouter) refreshAndRespondLogin(
ctx context.Context,
r *http.Request,
@@ -62,7 +19,16 @@ func (pr *PublicRouter) refreshAndRespondLogin(
account *model.Account,
accessToken *sresponse.TokenData,
) http.HandlerFunc {
- refreshToken, err := pr.prepareRefreshToken(ctx, r, session, account)
+ refreshToken, err := rtokens.PrepareRefreshToken(
+ ctx,
+ r,
+ pr.rtdb,
+ pr.config.Length,
+ pr.config.Expiration.Refresh,
+ session,
+ account,
+ pr.logger,
+ )
if err != nil {
pr.logger.Warn("Failed to create refresh token", zap.Error(err), mzap.StorableRef(account),
zap.String("client_id", session.ClientID), zap.String("device_id", session.DeviceID))
diff --git a/api/server/internal/api/routers/public/router.go b/api/server/internal/api/routers/public/router.go
index a2fcae3..2d5ab0f 100644
--- a/api/server/internal/api/routers/public/router.go
+++ b/api/server/internal/api/routers/public/router.go
@@ -1,20 +1,27 @@
package routers
import (
+ "crypto/rand"
+ "strings"
+
"github.com/go-chi/chi/v5"
api "github.com/tech/sendico/pkg/api/http"
"github.com/tech/sendico/pkg/db/account"
+ "github.com/tech/sendico/pkg/db/confirmation"
"github.com/tech/sendico/pkg/db/refreshtokens"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
"github.com/tech/sendico/server/interface/api/sresponse"
"github.com/tech/sendico/server/interface/middleware"
re "github.com/tech/sendico/server/internal/api/routers/endpoint"
+ "github.com/tech/sendico/server/internal/server/confirmationimp"
)
type PublicRouter struct {
logger mlogger.Logger
db account.DB
+ cdb confirmation.DB
+ cstore *confirmationimp.ConfirmationStore
imp *re.HttpEndpointRouter
rtdb refreshtokens.DB
config middleware.TokenConfig
@@ -26,11 +33,39 @@ func (pr *PublicRouter) InstallHandler(service mservice.Type, endpoint string, m
pr.imp.InstallHandler(service, endpoint, method, handler)
}
-func NewRouter(logger mlogger.Logger, apiEndpoint string, db account.DB, rtdb refreshtokens.DB, router chi.Router, config *middleware.TokenConfig, signature *middleware.Signature) *PublicRouter {
+func (pr *PublicRouter) generateCode() (string, error) {
+ const digits = "0123456789"
+ b := make([]byte, confirmationimp.DefaultConfig().CodeLength)
+ if _, err := rand.Read(b); err != nil {
+ return "", err
+ }
+ for i := range b {
+ b[i] = digits[int(b[i])%len(digits)]
+ }
+ return string(b), nil
+}
+
+func (pr *PublicRouter) maskEmail(email string) string {
+ parts := strings.Split(email, "@")
+ if len(parts) != 2 {
+ return email
+ }
+ local := parts[0]
+ if len(local) > 2 {
+ local = local[:1] + "***" + local[len(local)-1:]
+ } else {
+ local = local[:1] + "***"
+ }
+ return local + "@" + parts[1]
+}
+
+func NewRouter(logger mlogger.Logger, apiEndpoint string, db account.DB, cdb confirmation.DB, rtdb refreshtokens.DB, router chi.Router, config *middleware.TokenConfig, signature *middleware.Signature) *PublicRouter {
l := logger.Named("public")
hr := PublicRouter{
logger: l,
db: db,
+ cdb: cdb,
+ cstore: confirmationimp.NewStore(cdb),
rtdb: rtdb,
config: *config,
signature: *signature,
diff --git a/api/server/internal/api/routers/router.go b/api/server/internal/api/routers/router.go
index ef95937..90ea96f 100644
--- a/api/server/internal/api/routers/router.go
+++ b/api/server/internal/api/routers/router.go
@@ -12,4 +12,5 @@ type APIRouter interface {
type ProtectedAPIRouter interface {
AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc)
+ PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc)
}
diff --git a/api/server/internal/api/routers/tokens/tokens.go b/api/server/internal/api/routers/tokens/tokens.go
new file mode 100644
index 0000000..e768532
--- /dev/null
+++ b/api/server/internal/api/routers/tokens/tokens.go
@@ -0,0 +1,65 @@
+package tokens
+
+import (
+ "context"
+ "crypto/rand"
+ "encoding/base64"
+ "io"
+ "net/http"
+ "time"
+
+ "github.com/tech/sendico/pkg/db/refreshtokens"
+ "github.com/tech/sendico/pkg/merrors"
+ "github.com/tech/sendico/pkg/mlogger"
+ "github.com/tech/sendico/pkg/model"
+ "github.com/tech/sendico/pkg/mutil/mzap"
+ "go.uber.org/zap"
+)
+
+func generateRefreshTokenData(length int) (string, error) {
+ randomBytes := make([]byte, length)
+ if _, err := io.ReadFull(rand.Reader, randomBytes); err != nil {
+ return "", merrors.Internal("failed to generate secure random bytes: " + err.Error())
+ }
+
+ return base64.URLEncoding.EncodeToString(randomBytes), nil
+}
+
+func PrepareRefreshToken(
+ ctx context.Context,
+ r *http.Request,
+ rtdb refreshtokens.DB,
+ length int,
+ refreshExpiration int,
+ session *model.SessionIdentifier,
+ account *model.Account,
+ logger mlogger.Logger,
+) (*model.RefreshToken, error) {
+ refreshToken, err := generateRefreshTokenData(length)
+ if err != nil {
+ logger.Warn("Failed to generate refresh token", zap.Error(err), mzap.StorableRef(account))
+ return nil, err
+ }
+
+ token := &model.RefreshToken{
+ AccountBoundBase: model.AccountBoundBase{
+ AccountRef: account.GetID(),
+ },
+ ClientRefreshToken: model.ClientRefreshToken{
+ SessionIdentifier: *session,
+ RefreshToken: refreshToken,
+ },
+ ExpiresAt: time.Now().Add(time.Duration(refreshExpiration) * time.Hour),
+ IsRevoked: false,
+ UserAgent: r.UserAgent(),
+ IPAddress: r.RemoteAddr,
+ }
+
+ if err = rtdb.Create(ctx, token); err != nil {
+ logger.Warn("Failed to store a refresh token", zap.Error(err), mzap.StorableRef(account),
+ zap.String("client_id", token.ClientID), zap.String("device_id", token.DeviceID))
+ return nil, err
+ }
+
+ return token, nil
+}
diff --git a/api/server/internal/server/confirmationimp/request.go b/api/server/internal/server/confirmationimp/request.go
new file mode 100644
index 0000000..68e61f3
--- /dev/null
+++ b/api/server/internal/server/confirmationimp/request.go
@@ -0,0 +1,48 @@
+package confirmationimp
+
+import (
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "github.com/tech/sendico/pkg/api/http/response"
+ "github.com/tech/sendico/pkg/model"
+ "github.com/tech/sendico/pkg/mutil/mzap"
+ emodel "github.com/tech/sendico/server/interface/model"
+ "go.uber.org/zap"
+)
+
+func (a *ConfirmationAPI) requestCode(r *http.Request, account *model.Account, token *emodel.AccountToken) http.HandlerFunc {
+ var req confirmationRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ a.logger.Warn("Failed to decode confirmation request", zap.Error(err))
+ return response.BadPayload(a.logger, a.Name(), err)
+ }
+
+ target, err := a.parseTarget(req.Target)
+ if err != nil {
+ return response.BadRequest(a.logger, a.Name(), "invalid_target", err.Error())
+ }
+
+ if target == model.ConfirmationTargetLogin && (token == nil || !token.Pending) {
+ return response.Forbidden(a.logger, a.Name(), "pending_token_required", "login confirmation requires pending token")
+ }
+
+ destination := a.resolveDestination(req.Destination, account)
+ if destination == "" {
+ return response.BadRequest(a.logger, a.Name(), "missing_destination", "email destination is required")
+ }
+ code, rec, err := a.store.Create(r.Context(), account.ID, destination, target, a.config, a.generateCode)
+ if err != nil {
+ a.logger.Warn("Failed to create confirmation code", zap.Error(err), mzap.ObjRef("account_ref", account.ID))
+ return response.Internal(a.logger, a.Name(), err)
+ }
+
+ a.sendCode(account, target, destination, code)
+
+ return response.Accepted(a.logger, confirmationResponse{
+ TTLSeconds: int(time.Until(rec.ExpiresAt).Seconds()),
+ CooldownSeconds: int(a.config.Cooldown.Seconds()),
+ Destination: maskEmail(destination),
+ })
+}
diff --git a/api/server/internal/server/confirmationimp/resend.go b/api/server/internal/server/confirmationimp/resend.go
new file mode 100644
index 0000000..4e7e1d2
--- /dev/null
+++ b/api/server/internal/server/confirmationimp/resend.go
@@ -0,0 +1,56 @@
+package confirmationimp
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "time"
+
+ "github.com/tech/sendico/pkg/api/http/response"
+ "github.com/tech/sendico/pkg/model"
+ "github.com/tech/sendico/pkg/mutil/mzap"
+ emodel "github.com/tech/sendico/server/interface/model"
+ "go.uber.org/zap"
+)
+
+func (a *ConfirmationAPI) resendCode(r *http.Request, account *model.Account, token *emodel.AccountToken) http.HandlerFunc {
+ var req confirmationRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ a.logger.Warn("Failed to decode confirmation resend request", zap.Error(err))
+ return response.BadPayload(a.logger, a.Name(), err)
+ }
+
+ target, err := a.parseTarget(req.Target)
+ if err != nil {
+ return response.BadRequest(a.logger, a.Name(), "invalid_target", err.Error())
+ }
+
+ if target == model.ConfirmationTargetLogin && (token == nil || !token.Pending) {
+ return response.Forbidden(a.logger, a.Name(), "pending_token_required", "login confirmation requires pending token")
+ }
+
+ destination := a.resolveDestination(req.Destination, account)
+ if destination == "" {
+ return response.BadRequest(a.logger, a.Name(), "missing_destination", "email destination is required")
+ }
+ code, rec, err := a.store.Resend(r.Context(), account.ID, destination, target, a.config, a.generateCode)
+ switch {
+ case errors.Is(err, errConfirmationNotFound):
+ return response.NotFound(a.logger, a.Name(), "no_active_code_for_resend")
+ case errors.Is(err, errConfirmationCooldown):
+ return response.Forbidden(a.logger, a.Name(), "cooldown_active", "please wait before requesting another code")
+ case errors.Is(err, errConfirmationResendLimit):
+ return response.Forbidden(a.logger, a.Name(), "resend_limit_reached", "too many resend attempts")
+ case err != nil:
+ a.logger.Warn("Failed to resend confirmation code", zap.Error(err), mzap.ObjRef("account_ref", account.ID))
+ return response.Internal(a.logger, a.Name(), err)
+ }
+
+ a.sendCode(account, target, destination, code)
+
+ return response.Accepted(a.logger, confirmationResponse{
+ TTLSeconds: int(time.Until(rec.ExpiresAt).Seconds()),
+ CooldownSeconds: int(a.config.Cooldown.Seconds()),
+ Destination: maskEmail(destination),
+ })
+}
diff --git a/api/server/internal/server/confirmationimp/service.go b/api/server/internal/server/confirmationimp/service.go
new file mode 100644
index 0000000..737e8c8
--- /dev/null
+++ b/api/server/internal/server/confirmationimp/service.go
@@ -0,0 +1,158 @@
+package confirmationimp
+
+import (
+ "context"
+ "crypto/rand"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/go-chi/jwtauth/v5"
+ api "github.com/tech/sendico/pkg/api/http"
+ "github.com/tech/sendico/pkg/db/refreshtokens"
+ "github.com/tech/sendico/pkg/merrors"
+ "github.com/tech/sendico/pkg/messaging"
+ cnotifications "github.com/tech/sendico/pkg/messaging/notifications/confirmation"
+ "github.com/tech/sendico/pkg/mlogger"
+ "github.com/tech/sendico/pkg/model"
+ "github.com/tech/sendico/pkg/mservice"
+ "github.com/tech/sendico/pkg/mutil/mzap"
+ eapi "github.com/tech/sendico/server/interface/api"
+ "github.com/tech/sendico/server/interface/api/sresponse"
+ "github.com/tech/sendico/server/interface/middleware"
+ emodel "github.com/tech/sendico/server/interface/model"
+ "go.uber.org/zap"
+)
+
+type Config struct {
+ CodeLength int
+ TTL time.Duration
+ MaxAttempts int
+ Cooldown time.Duration
+ ResendLimit int
+}
+
+func defaultConfig() Config {
+ return Config{
+ CodeLength: 6,
+ TTL: 10 * time.Minute,
+ MaxAttempts: 5,
+ Cooldown: time.Minute,
+ ResendLimit: 5,
+ }
+}
+
+func DefaultConfig() Config {
+ return defaultConfig()
+}
+
+type ConfirmationAPI struct {
+ logger mlogger.Logger
+ config Config
+ store *ConfirmationStore
+ rtdb refreshtokens.DB
+ producer messaging.Producer
+ tokenConfig middleware.TokenConfig
+ signature middleware.Signature
+}
+
+func (a *ConfirmationAPI) Name() mservice.Type {
+ return mservice.Confirmations
+}
+
+func (a *ConfirmationAPI) Finish(_ context.Context) error {
+ return nil
+}
+
+func CreateAPI(a eapi.API) (*ConfirmationAPI, error) {
+ cdb, err := a.DBFactory().NewConfirmationsDB()
+ if err != nil {
+ return nil, err
+ }
+ rtdb, err := a.DBFactory().NewRefreshTokensDB()
+ if err != nil {
+ return nil, err
+ }
+
+ p := &ConfirmationAPI{
+ logger: a.Logger().Named(mservice.Confirmations),
+ config: defaultConfig(),
+ store: NewStore(cdb),
+ rtdb: rtdb,
+ producer: a.Register().Messaging().Producer(),
+ tokenConfig: a.Config().Mw.Token,
+ signature: middleware.SignatureConf(a.Config().Mw),
+ }
+
+ a.Register().PendingAccountHandler(p.Name(), "/confirmations", api.Post, p.requestCode)
+ a.Register().PendingAccountHandler(p.Name(), "/confirmations/resend", api.Post, p.resendCode)
+ a.Register().PendingAccountHandler(p.Name(), "/confirmations/verify", api.Post, p.verifyCode)
+ return p, nil
+}
+
+func (a *ConfirmationAPI) generateCode() (string, error) {
+ const digits = "0123456789"
+ b := make([]byte, a.config.CodeLength)
+ _, err := rand.Read(b)
+ if err != nil {
+ return "", err
+ }
+ for i := range b {
+ b[i] = digits[int(b[i])%len(digits)]
+ }
+ return string(b), nil
+}
+
+func (a *ConfirmationAPI) parseTarget(raw string) (model.ConfirmationTarget, error) {
+ switch strings.ToLower(strings.TrimSpace(raw)) {
+ case string(model.ConfirmationTargetLogin):
+ return model.ConfirmationTargetLogin, nil
+ case string(model.ConfirmationTargetPayout):
+ return model.ConfirmationTargetPayout, nil
+ default:
+ return "", merrors.InvalidArgument(fmt.Sprintf("unsupported target '%s'", raw), "target")
+ }
+}
+
+func (a *ConfirmationAPI) resolveDestination(reqDest string, account *model.Account) string {
+ destination := strings.ToLower(strings.TrimSpace(reqDest))
+ if destination == "" && account != nil {
+ destination = strings.ToLower(strings.TrimSpace(account.Login))
+ }
+ return destination
+}
+
+func (a *ConfirmationAPI) sendCode(account *model.Account, target model.ConfirmationTarget, destination, code string) {
+ a.logger.Info("Confirmation code generated",
+ zap.String("target", string(target)),
+ zap.String("destination", maskEmail(destination)),
+ mzap.ObjRef("account_ref", account.ID))
+ if err := a.producer.SendMessage(cnotifications.Code(a.Name(), account.ID, destination, target, code)); err != nil {
+ a.logger.Warn("Failed to send confirmation code notification", zap.Error(err), mzap.ObjRef("account_ref", account.ID))
+ }
+ a.logger.Debug("Confirmation code debug dump (do not log in production)", zap.String("code", code))
+}
+
+func maskEmail(email string) string {
+ parts := strings.Split(email, "@")
+ if len(parts) != 2 {
+ return email
+ }
+ local := parts[0]
+ if len(local) > 2 {
+ local = local[:1] + "***" + local[len(local)-1:]
+ } else {
+ local = local[:1] + "***"
+ }
+ return local + "@" + parts[1]
+}
+
+func (a *ConfirmationAPI) createAccessToken(account *model.Account) (sresponse.TokenData, error) {
+ ja := jwtauth.New(a.signature.Algorithm, a.signature.PrivateKey, a.signature.PublicKey)
+ _, res, err := ja.Encode(emodel.Account2Claims(account, a.tokenConfig.Expiration.Account))
+ token := sresponse.TokenData{
+ Token: res,
+ Expiration: time.Now().Add(time.Duration(a.tokenConfig.Expiration.Account) * time.Hour),
+ }
+ return token, err
+}
diff --git a/api/server/internal/server/confirmationimp/store.go b/api/server/internal/server/confirmationimp/store.go
new file mode 100644
index 0000000..a3e83fe
--- /dev/null
+++ b/api/server/internal/server/confirmationimp/store.go
@@ -0,0 +1,181 @@
+package confirmationimp
+
+import (
+ "context"
+ "crypto/rand"
+ "crypto/sha256"
+ "crypto/subtle"
+ "errors"
+ "time"
+
+ "github.com/tech/sendico/pkg/db/confirmation"
+ "github.com/tech/sendico/pkg/merrors"
+ "github.com/tech/sendico/pkg/model"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+var (
+ errConfirmationNotFound = errors.New("confirmation not found or expired")
+ errConfirmationUsed = errors.New("confirmation already used")
+ errConfirmationMismatch = errors.New("confirmation code mismatch")
+ errConfirmationAttemptsExceeded = errors.New("confirmation attempts exceeded")
+ errConfirmationCooldown = errors.New("confirmation cooldown active")
+ errConfirmationResendLimit = errors.New("confirmation resend limit reached")
+)
+
+type ConfirmationStore struct {
+ db confirmation.DB
+}
+
+func NewStore(db confirmation.DB) *ConfirmationStore {
+ return &ConfirmationStore{db: db}
+}
+
+func (s *ConfirmationStore) Create(
+ ctx context.Context,
+ accountRef primitive.ObjectID,
+ destination string,
+ target model.ConfirmationTarget,
+ cfg Config,
+ generator func() (string, error),
+) (string, *model.ConfirmationCode, error) {
+ if err := s.db.DeleteTuple(ctx, accountRef, destination, target); err != nil && !errors.Is(err, merrors.ErrNoData) {
+ return "", nil, err
+ }
+
+ code, _, rec, err := s.buildRecord(accountRef, destination, target, cfg, generator)
+ if err != nil {
+ return "", nil, err
+ }
+
+ if err := s.db.Create(ctx, rec); err != nil {
+ return "", nil, err
+ }
+
+ return code, rec, nil
+}
+
+func (s *ConfirmationStore) Resend(
+ ctx context.Context,
+ accountRef primitive.ObjectID,
+ destination string,
+ target model.ConfirmationTarget,
+ cfg Config,
+ generator func() (string, error),
+) (string, *model.ConfirmationCode, error) {
+ now := time.Now().UTC()
+ active, err := s.db.FindActive(ctx, accountRef, destination, target, now.Unix())
+ if errors.Is(err, merrors.ErrNoData) {
+ return s.Create(ctx, accountRef, destination, target, cfg, generator)
+ }
+ if err != nil {
+ return "", nil, err
+ }
+ if active.ResendCount >= active.ResendLimit {
+ return "", nil, errConfirmationResendLimit
+ }
+ if now.Before(active.CooldownUntil) {
+ return "", nil, errConfirmationCooldown
+ }
+
+ code, salt, updated, err := s.buildRecord(accountRef, destination, target, cfg, generator)
+ if err != nil {
+ return "", nil, err
+ }
+ // Preserve attempt counters but bump resend count.
+ updated.ID = active.ID
+ updated.CreatedAt = active.CreatedAt
+ updated.Attempts = active.Attempts
+ updated.ResendCount = active.ResendCount + 1
+ updated.Salt = salt
+
+ if err := s.db.Update(ctx, updated); err != nil {
+ return "", nil, err
+ }
+ return code, updated, nil
+}
+
+func (s *ConfirmationStore) Verify(
+ ctx context.Context,
+ accountRef primitive.ObjectID,
+ destination string,
+ target model.ConfirmationTarget,
+ code string,
+) error {
+ now := time.Now().UTC()
+ rec, err := s.db.FindActive(ctx, accountRef, destination, target, now.Unix())
+ if errors.Is(err, merrors.ErrNoData) {
+ return errConfirmationNotFound
+ }
+ if err != nil {
+ return err
+ }
+ if rec.Used {
+ return errConfirmationUsed
+ }
+
+ rec.Attempts++
+ if rec.Attempts > rec.MaxAttempts {
+ rec.Used = true
+ _ = s.db.Update(ctx, rec)
+ return errConfirmationAttemptsExceeded
+ }
+
+ if subtle.ConstantTimeCompare(rec.CodeHash, hashCode(rec.Salt, code)) != 1 {
+ _ = s.db.Update(ctx, rec)
+ return errConfirmationMismatch
+ }
+
+ rec.Used = true
+ return s.db.Update(ctx, rec)
+}
+
+func (s *ConfirmationStore) buildRecord(
+ accountRef primitive.ObjectID,
+ destination string,
+ target model.ConfirmationTarget,
+ cfg Config,
+ generator func() (string, error),
+) (string, []byte, *model.ConfirmationCode, error) {
+ code, err := generator()
+ if err != nil {
+ return "", nil, nil, err
+ }
+ salt, err := newSalt()
+ if err != nil {
+ return "", nil, nil, err
+ }
+
+ now := time.Now().UTC()
+ rec := model.NewConfirmationCode(accountRef)
+ rec.Destination = destination
+ rec.Target = target
+ rec.CodeHash = hashCode(salt, code)
+ rec.Salt = salt
+ rec.ExpiresAt = now.Add(cfg.TTL)
+ rec.MaxAttempts = cfg.MaxAttempts
+ rec.ResendLimit = cfg.ResendLimit
+ rec.CooldownUntil = now.Add(cfg.Cooldown)
+ rec.Used = false
+ rec.Attempts = 0
+ rec.ResendCount = 0
+ rec.CreatedAt = now
+ rec.UpdatedAt = now
+
+ return code, salt, rec, nil
+}
+
+func hashCode(salt []byte, code string) []byte {
+ h := sha256.New()
+ h.Write(salt)
+ h.Write([]byte(code))
+ return h.Sum(nil)
+}
+
+func newSalt() ([]byte, error) {
+ buf := make([]byte, 16)
+ if _, err := rand.Read(buf); err != nil {
+ return nil, err
+ }
+ return buf, nil
+}
diff --git a/api/server/internal/server/confirmationimp/types.go b/api/server/internal/server/confirmationimp/types.go
new file mode 100644
index 0000000..743c0b6
--- /dev/null
+++ b/api/server/internal/server/confirmationimp/types.go
@@ -0,0 +1,23 @@
+package confirmationimp
+
+import (
+ "github.com/tech/sendico/pkg/model"
+)
+
+type confirmationRequest struct {
+ Target string `json:"target"`
+ Destination string `json:"destination,omitempty"`
+}
+
+type confirmationVerifyRequest struct {
+ Target string `json:"target"`
+ Code string `json:"code"`
+ Destination string `json:"destination,omitempty"`
+ SessionIdentifier model.SessionIdentifier `json:"sessionIdentifier"`
+}
+
+type confirmationResponse struct {
+ TTLSeconds int `json:"ttl_seconds"`
+ CooldownSeconds int `json:"cooldown_seconds"`
+ Destination string `json:"destination"`
+}
diff --git a/api/server/internal/server/confirmationimp/verify.go b/api/server/internal/server/confirmationimp/verify.go
new file mode 100644
index 0000000..4ff4434
--- /dev/null
+++ b/api/server/internal/server/confirmationimp/verify.go
@@ -0,0 +1,88 @@
+package confirmationimp
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "strings"
+
+ "github.com/tech/sendico/pkg/api/http/response"
+ "github.com/tech/sendico/pkg/model"
+ "github.com/tech/sendico/pkg/mutil/mzap"
+ "github.com/tech/sendico/server/interface/api/sresponse"
+ emodel "github.com/tech/sendico/server/interface/model"
+ rtokens "github.com/tech/sendico/server/internal/api/routers/tokens"
+ "go.uber.org/zap"
+)
+
+func (a *ConfirmationAPI) verifyCode(r *http.Request, account *model.Account, token *emodel.AccountToken) http.HandlerFunc {
+ var req confirmationVerifyRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ a.logger.Warn("Failed to decode confirmation verification request", zap.Error(err))
+ return response.BadPayload(a.logger, a.Name(), err)
+ }
+
+ target, err := a.parseTarget(req.Target)
+ if err != nil {
+ return response.BadRequest(a.logger, a.Name(), "invalid_target", err.Error())
+ }
+
+ if target == model.ConfirmationTargetLogin && (token == nil || !token.Pending) {
+ return response.Forbidden(a.logger, a.Name(), "pending_token_required", "login confirmation requires pending token")
+ }
+
+ if strings.TrimSpace(req.Code) == "" {
+ return response.BadRequest(a.logger, a.Name(), "missing_code", "confirmation code is required")
+ }
+
+ destination := a.resolveDestination(req.Destination, account)
+ if destination == "" {
+ return response.BadRequest(a.logger, a.Name(), "missing_destination", "email destination is required")
+ }
+ err = a.store.Verify(r.Context(), account.ID, destination, target, strings.TrimSpace(req.Code))
+ switch {
+ case errors.Is(err, errConfirmationNotFound):
+ return response.NotFound(a.logger, a.Name(), "code_not_found_or_expired")
+ case errors.Is(err, errConfirmationUsed):
+ return response.Forbidden(a.logger, a.Name(), "code_used", "code has already been used")
+ case errors.Is(err, errConfirmationAttemptsExceeded):
+ return response.Forbidden(a.logger, a.Name(), "attempt_limit_reached", "too many failed attempts")
+ case errors.Is(err, errConfirmationMismatch):
+ return response.Forbidden(a.logger, a.Name(), "invalid_code", "code does not match")
+ case err != nil:
+ a.logger.Warn("Failed to verify confirmation code", zap.Error(err), mzap.ObjRef("account_ref", account.ID))
+ return response.Internal(a.logger, a.Name(), err)
+ }
+
+ a.logger.Info("Confirmation code verified", zap.String("target", string(target)), mzap.ObjRef("account_ref", account.ID))
+ if target == model.ConfirmationTargetLogin {
+ if req.SessionIdentifier.ClientID == "" || req.SessionIdentifier.DeviceID == "" {
+ return response.BadRequest(a.logger, a.Name(), "missing_session", "session identifier is required")
+ }
+ accessToken, err := a.createAccessToken(account)
+ if err != nil {
+ a.logger.Warn("Failed to generate access token", zap.Error(err))
+ return response.Internal(a.logger, a.Name(), err)
+ }
+ refreshToken, err := rtokens.PrepareRefreshToken(
+ r.Context(),
+ r,
+ a.rtdb,
+ a.tokenConfig.Length,
+ a.tokenConfig.Expiration.Refresh,
+ &req.SessionIdentifier,
+ account,
+ a.logger,
+ )
+ if err != nil {
+ a.logger.Warn("Failed to generate refresh token", zap.Error(err))
+ return response.Internal(a.logger, a.Name(), err)
+ }
+ rt := sresponse.TokenData{
+ Token: refreshToken.RefreshToken,
+ Expiration: refreshToken.ExpiresAt,
+ }
+ return sresponse.Login(a.logger, account, &accessToken, &rt)
+ }
+ return response.Success(a.logger)
+}
diff --git a/frontend/pshared/lib/api/responses/login_pending.dart b/frontend/pshared/lib/api/responses/login_pending.dart
new file mode 100644
index 0000000..91e5241
--- /dev/null
+++ b/frontend/pshared/lib/api/responses/login_pending.dart
@@ -0,0 +1,26 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'package:pshared/api/responses/account.dart';
+import 'package:pshared/api/responses/token.dart';
+
+part 'login_pending.g.dart';
+
+
+@JsonSerializable(explicitToJson: true)
+class PendingLoginResponse {
+ final AccountResponse account;
+ final TokenData pendingToken;
+ final String destination;
+ final int ttlSeconds;
+
+ const PendingLoginResponse({
+ required this.account,
+ required this.pendingToken,
+ required this.destination,
+ required this.ttlSeconds,
+ });
+
+ factory PendingLoginResponse.fromJson(Map json) => _$PendingLoginResponseFromJson(json);
+
+ Map toJson() => _$PendingLoginResponseToJson(this);
+}
diff --git a/frontend/pshared/lib/models/auth/login_outcome.dart b/frontend/pshared/lib/models/auth/login_outcome.dart
new file mode 100644
index 0000000..24df29e
--- /dev/null
+++ b/frontend/pshared/lib/models/auth/login_outcome.dart
@@ -0,0 +1,17 @@
+import 'package:pshared/models/account/account.dart';
+import 'package:pshared/models/auth/pending_login.dart';
+
+
+class LoginOutcome {
+ final Account? account;
+ final PendingLogin? pending;
+
+ const LoginOutcome._({this.account, this.pending});
+
+ factory LoginOutcome.completed(Account account) => LoginOutcome._(account: account);
+
+ factory LoginOutcome.pending(PendingLogin pending) => LoginOutcome._(pending: pending);
+
+ bool get isPending => pending != null;
+ bool get isCompleted => account != null;
+}
diff --git a/frontend/pshared/lib/models/auth/pending_login.dart b/frontend/pshared/lib/models/auth/pending_login.dart
new file mode 100644
index 0000000..3585bcc
--- /dev/null
+++ b/frontend/pshared/lib/models/auth/pending_login.dart
@@ -0,0 +1,33 @@
+import 'package:pshared/api/responses/login_pending.dart';
+import 'package:pshared/api/responses/token.dart';
+import 'package:pshared/data/mapper/account/account.dart';
+import 'package:pshared/models/account/account.dart';
+import 'package:pshared/models/session_identifier.dart';
+
+
+class PendingLogin {
+ final Account account;
+ final TokenData pendingToken;
+ final String destination;
+ final int ttlSeconds;
+ final SessionIdentifier session;
+
+ const PendingLogin({
+ required this.account,
+ required this.pendingToken,
+ required this.destination,
+ required this.ttlSeconds,
+ required this.session,
+ });
+
+ factory PendingLogin.fromResponse(
+ PendingLoginResponse response, {
+ required SessionIdentifier session,
+ }) => PendingLogin(
+ account: response.account.account.toDomain(),
+ pendingToken: response.pendingToken,
+ destination: response.destination,
+ ttlSeconds: response.ttlSeconds,
+ session: session,
+ );
+}
diff --git a/frontend/pshared/lib/models/session_identifier.dart b/frontend/pshared/lib/models/session_identifier.dart
new file mode 100644
index 0000000..106f8f8
--- /dev/null
+++ b/frontend/pshared/lib/models/session_identifier.dart
@@ -0,0 +1,18 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'session_identifier.g.dart';
+
+@JsonSerializable()
+class SessionIdentifier {
+ final String clientId;
+ final String deviceId;
+
+ const SessionIdentifier({
+ required this.clientId,
+ required this.deviceId,
+ });
+
+ factory SessionIdentifier.fromJson(Map json) => _$SessionIdentifierFromJson(json);
+
+ Map toJson() => _$SessionIdentifierToJson(this);
+}
diff --git a/frontend/pshared/lib/provider/account.dart b/frontend/pshared/lib/provider/account.dart
index f44906d..fea9b02 100644
--- a/frontend/pshared/lib/provider/account.dart
+++ b/frontend/pshared/lib/provider/account.dart
@@ -7,6 +7,8 @@ import 'package:pshared/api/requests/signup.dart';
import 'package:pshared/api/requests/login_data.dart';
import 'package:pshared/config/constants.dart';
import 'package:pshared/models/account/account.dart';
+import 'package:pshared/models/auth/login_outcome.dart';
+import 'package:pshared/models/auth/pending_login.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/storable.dart';
import 'package:pshared/provider/locale.dart';
@@ -23,8 +25,10 @@ class AccountProvider extends ChangeNotifier {
Resource _resource = Resource(data: null);
Resource get resource => _resource;
late LocaleProvider _localeProvider;
+ PendingLogin? _pendingLogin;
Account? get account => _resource.data;
+ PendingLogin? get pendingLogin => _pendingLogin;
bool get isLoggedIn => account != null;
bool get isLoading => _resource.isLoading;
Object? get error => _resource.error;
@@ -57,27 +61,38 @@ class AccountProvider extends ChangeNotifier {
void _pickupLocale(String locale) => _localeProvider.setLocale(Locale(locale));
- Future login({
+ Future login({
required String email,
required String password,
required String locale,
}) async {
_setResource(_resource.copyWith(isLoading: true, error: null));
try {
- final acc = await AccountService.login(LoginData.build(
+ final outcome = await AccountService.login(LoginData.build(
login: email,
password: password,
locale: locale,
));
- _setResource(Resource(data: acc, isLoading: false));
- _pickupLocale(acc.locale);
- return acc;
+ if (outcome.account != null) {
+ _setResource(Resource(data: outcome.account, isLoading: false));
+ _pickupLocale(outcome.account!.locale);
+ } else {
+ _pendingLogin = outcome.pending;
+ _setResource(_resource.copyWith(isLoading: false));
+ }
+ return outcome;
} catch (e) {
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
rethrow;
}
}
+ void completePendingLogin(Account account) {
+ _pendingLogin = null;
+ _setResource(Resource(data: account, isLoading: false, error: null));
+ _pickupLocale(account.locale);
+ }
+
Future isAuthorizationStored() async => AuthorizationService.isAuthorizationStored();
Future restore() async {
diff --git a/frontend/pshared/lib/service/account.dart b/frontend/pshared/lib/service/account.dart
index a935dc2..facb9a1 100644
--- a/frontend/pshared/lib/service/account.dart
+++ b/frontend/pshared/lib/service/account.dart
@@ -8,9 +8,13 @@ import 'package:pshared/api/requests/login_data.dart';
import 'package:pshared/api/requests/password/change.dart';
import 'package:pshared/api/requests/password/forgot.dart';
import 'package:pshared/api/requests/password/reset.dart';
+import 'package:pshared/api/responses/login.dart';
import 'package:pshared/data/mapper/account/account.dart';
import 'package:pshared/models/account/account.dart';
+import 'package:pshared/models/auth/login_outcome.dart';
+import 'package:pshared/models/auth/pending_login.dart';
import 'package:pshared/service/authorization/service.dart';
+import 'package:pshared/service/authorization/storage.dart';
import 'package:pshared/service/files.dart';
import 'package:pshared/service/services.dart';
import 'package:pshared/utils/http/requests.dart';
@@ -20,11 +24,46 @@ class AccountService {
static final _logger = Logger('service.account');
static const String _objectType = Services.account;
- static Future login(LoginData login) async {
+ static Future login(LoginData login) async {
_logger.fine('Logging in');
return AuthorizationService.login(_objectType, login);
}
+ static Future resendLoginCode(PendingLogin pending, {String? destination}) async {
+ await getPOSTResponse(
+ _objectType,
+ 'confirmations/resend',
+ {
+ 'target': 'login',
+ if (destination != null) 'destination': destination,
+ },
+ authToken: pending.pendingToken.token,
+ );
+ }
+
+ static Future confirmLoginCode({
+ required PendingLogin pending,
+ required String code,
+ String? destination,
+ }) async {
+ final response = await getPOSTResponse(
+ _objectType,
+ 'confirmations/verify',
+ {
+ 'target': 'login',
+ 'code': code,
+ if (destination != null) 'destination': destination,
+ 'sessionIdentifier': pending.session.toJson(),
+ },
+ authToken: pending.pendingToken.token,
+ );
+
+ final loginResponse = LoginResponse.fromJson(response);
+ await AuthorizationStorage.updateToken(loginResponse.accessToken);
+ await AuthorizationStorage.updateRefreshToken(loginResponse.refreshToken);
+ return loginResponse.account.toDomain();
+ }
+
static Future restore() async {
return AuthorizationService.restore();
}
diff --git a/frontend/pshared/lib/service/authorization/service.dart b/frontend/pshared/lib/service/authorization/service.dart
index 57e615f..a73a752 100644
--- a/frontend/pshared/lib/service/authorization/service.dart
+++ b/frontend/pshared/lib/service/authorization/service.dart
@@ -5,9 +5,13 @@ import 'package:pshared/api/requests/login.dart';
import 'package:pshared/api/requests/login_data.dart';
import 'package:pshared/api/responses/account.dart';
import 'package:pshared/api/responses/login.dart';
+import 'package:pshared/api/responses/login_pending.dart';
import 'package:pshared/config/web.dart';
import 'package:pshared/data/mapper/account/account.dart';
import 'package:pshared/models/account/account.dart';
+import 'package:pshared/models/auth/login_outcome.dart';
+import 'package:pshared/models/auth/pending_login.dart';
+import 'package:pshared/models/session_identifier.dart';
import 'package:pshared/service/authorization/circuit_breaker.dart';
import 'package:pshared/service/authorization/retry_helper.dart';
import 'package:pshared/service/authorization/storage.dart';
@@ -22,7 +26,7 @@ import 'package:pshared/utils/http/requests.dart' as httpr;
class AuthorizationService {
static final _logger = Logger('service.authorization.auth_service');
- static Future login(String service, LoginData login) async {
+ static Future login(String service, LoginData login) async {
_logger.fine('Logging in ${login.login} with ${login.locale} locale');
final deviceId = await DeviceIdManager.getDeviceId();
final response = await httpr.getPOSTResponse(
@@ -31,7 +35,17 @@ class AuthorizationService {
LoginRequest(login: login, deviceId: deviceId, clientId: Constants.clientId).toJson(),
);
- return (await _completeLogin(response)).account.toDomain();
+ if (response.containsKey('refreshToken')) {
+ return LoginOutcome.completed((await completeLogin(response)).account.toDomain());
+ }
+ if (response.containsKey('pendingToken')) {
+ final pending = PendingLogin.fromResponse(
+ PendingLoginResponse.fromJson(response),
+ session: SessionIdentifier(clientId: Constants.clientId, deviceId: deviceId),
+ );
+ return LoginOutcome.pending(pending);
+ }
+ throw AuthenticationFailedException('Unexpected login response', Exception(response.toString()));
}
static Future _updateAccessToken(AccountResponse response) async {
@@ -49,6 +63,8 @@ class AuthorizationService {
return lr;
}
+ static Future completeLogin(Map response) => _completeLogin(response);
+
static Future restore() async {
return (await TokenService.refreshAccessToken()).account.toDomain();
}
diff --git a/frontend/pweb/lib/main.dart b/frontend/pweb/lib/main.dart
index 3eaca66..a5bf863 100644
--- a/frontend/pweb/lib/main.dart
+++ b/frontend/pweb/lib/main.dart
@@ -24,7 +24,6 @@ import 'package:pweb/providers/two_factor.dart';
import 'package:pweb/providers/upload_history.dart';
import 'package:pweb/providers/wallets.dart';
import 'package:pweb/services/amplitude.dart';
-import 'package:pweb/services/auth.dart';
import 'package:pweb/services/balance.dart';
import 'package:pweb/services/payments/payment_methods.dart';
import 'package:pweb/services/payments/upload_history.dart';
@@ -53,17 +52,16 @@ void main() async {
runApp(
MultiProvider(
providers: [
- Provider(
- create: (_) => AuthenticationService(),
- ),
- ChangeNotifierProxyProvider(
- create: (context) => TwoFactorProvider(
- context.read(),
- ),
- update: (context, authService, previous) => TwoFactorProvider(authService),
- ),
ChangeNotifierProvider(create: (_) => LocaleProvider(null)),
ChangeNotifierProvider(create: (_) => AccountProvider()),
+ ChangeNotifierProxyProvider(
+ create: (context) => TwoFactorProvider(
+ accountProvider: context.read(),
+ ),
+ update: (context, accountProvider, previous) => TwoFactorProvider(
+ accountProvider: accountProvider,
+ ),
+ ),
ChangeNotifierProvider(create: (_) => OrganizationsProvider()),
ChangeNotifierProvider(create: (_) => AccountProvider()),
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
diff --git a/frontend/pweb/lib/pages/2fa/resend.dart b/frontend/pweb/lib/pages/2fa/resend.dart
index 57bde0d..13eb971 100644
--- a/frontend/pweb/lib/pages/2fa/resend.dart
+++ b/frontend/pweb/lib/pages/2fa/resend.dart
@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
import 'package:pweb/generated/i18n/app_localizations.dart';
+import 'package:pweb/providers/two_factor.dart';
class ResendCodeButton extends StatelessWidget {
@@ -12,9 +15,7 @@ class ResendCodeButton extends StatelessWidget {
final localizations = AppLocalizations.of(context)!;
return TextButton(
- onPressed: () {
- // TODO: Add resend logic
- },
+ onPressed: () => context.read().resendCode(),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(0, 0),
@@ -28,4 +29,4 @@ class ResendCodeButton extends StatelessWidget {
child: Text(localizations.twoFactorResend),
);
}
-}
\ No newline at end of file
+}
diff --git a/frontend/pweb/lib/pages/login/form.dart b/frontend/pweb/lib/pages/login/form.dart
index a145cb6..bf230b1 100644
--- a/frontend/pweb/lib/pages/login/form.dart
+++ b/frontend/pweb/lib/pages/login/form.dart
@@ -38,13 +38,16 @@ class _LoginFormState extends State {
final provider = Provider.of(context, listen: false);
try {
- //final account =
- await provider.login(
+ final outcome = await provider.login(
email: _usernameController.text,
password: _passwordController.text,
locale: context.read().locale.languageCode,
);
- onLogin();
+ if (outcome.isPending) {
+ navigateAndReplace(context, Pages.sfactor);
+ } else {
+ onLogin();
+ }
return 'ok';
} catch (e) {
onError(provider.error ?? e);
@@ -92,7 +95,7 @@ class _LoginFormState extends State {
onSignUp: () => navigate(context, Pages.signup),
login: () => _login(
context,
- () => navigateAndReplace(context, Pages.sfactor),
+ () => navigateAndReplace(context, Pages.dashboard),
(e) => postNotifyUserOfErrorX(
context: context,
errorSituation: AppLocalizations.of(context)!.errorLogin,
diff --git a/frontend/pweb/lib/providers/two_factor.dart b/frontend/pweb/lib/providers/two_factor.dart
index c5a74e4..f23bb52 100644
--- a/frontend/pweb/lib/providers/two_factor.dart
+++ b/frontend/pweb/lib/providers/two_factor.dart
@@ -1,38 +1,69 @@
import 'package:flutter/material.dart';
+import 'package:logging/logging.dart';
-import 'package:pweb/services/auth.dart';
-
+import 'package:pshared/models/auth/pending_login.dart';
+import 'package:pshared/provider/account.dart';
+import 'package:pshared/service/account.dart';
class TwoFactorProvider extends ChangeNotifier {
- final AuthenticationService _authService;
+ static final _logger = Logger('provider.two_factor');
+ final AccountProvider _accountProvider;
- TwoFactorProvider(this._authService);
+ TwoFactorProvider({required AccountProvider accountProvider}) : _accountProvider = accountProvider;
bool _isSubmitting = false;
bool _hasError = false;
bool _verificationSuccess = false;
+ String? _errorMessage;
bool get isSubmitting => _isSubmitting;
bool get hasError => _hasError;
bool get verificationSuccess => _verificationSuccess;
+ String? get errorMessage => _errorMessage;
+ PendingLogin? get pendingLogin => _accountProvider.pendingLogin;
Future submitCode(String code) async {
_isSubmitting = true;
_hasError = false;
+ _errorMessage = null;
_verificationSuccess = false;
notifyListeners();
try {
- final success = await _authService.verifyTwoFactorCode(code);
- if (success) {
- _verificationSuccess = true;
+ final pending = _accountProvider.pendingLogin;
+ if (pending == null) {
+ throw Exception('No pending login available');
}
+ final account = await AccountService.confirmLoginCode(
+ pending: pending,
+ code: code,
+ );
+ _accountProvider.completePendingLogin(account);
+ _verificationSuccess = true;
} catch (e) {
_hasError = true;
+ _errorMessage = e.toString();
+ _logger.warning('Failed to verify code', e);
} finally {
_isSubmitting = false;
notifyListeners();
}
}
-}
\ No newline at end of file
+
+ Future resendCode() async {
+ final pending = _accountProvider.pendingLogin;
+ if (pending == null) {
+ _logger.warning('No pending login to resend code for');
+ return;
+ }
+ try {
+ await AccountService.resendLoginCode(pending);
+ } catch (e) {
+ _logger.warning('Failed to resend login code', e);
+ _hasError = true;
+ _errorMessage = e.toString();
+ notifyListeners();
+ }
+ }
+}
diff --git a/frontend/pweb/lib/services/auth.dart b/frontend/pweb/lib/services/auth.dart
deleted file mode 100644
index 95a75e7..0000000
--- a/frontend/pweb/lib/services/auth.dart
+++ /dev/null
@@ -1,12 +0,0 @@
-
-class AuthenticationService {
- Future verifyTwoFactorCode(String code) async {
- await Future.delayed(const Duration(seconds: 2));
-
- if (code == '000000') {
- return true;
- } else {
- throw Exception('Wrong Code'); //TODO Localize
- }
- }
-}
diff --git a/frontend/pweb/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/pweb/macos/Flutter/GeneratedPluginRegistrant.swift
index 79f5652..33de092 100644
--- a/frontend/pweb/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/frontend/pweb/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -8,7 +8,6 @@ import Foundation
import amplitude_flutter
import file_selector_macos
import flutter_timezone
-import path_provider_foundation
import share_plus
import shared_preferences_foundation
import sqflite_darwin
@@ -18,7 +17,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AmplitudeFlutterPlugin.register(with: registry.registrar(forPlugin: "AmplitudeFlutterPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
- PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))