unified code verification service
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/billing/documents
|
module github.com/tech/sendico/billing/documents
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -60,11 +60,11 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -221,16 +221,16 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -249,8 +249,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -258,8 +258,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/billing/fees
|
module github.com/tech/sendico/billing/fees
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -45,11 +45,11 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -172,15 +172,15 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -199,8 +199,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -208,8 +208,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/discovery
|
module github.com/tech/sendico/discovery
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../pkg
|
replace github.com/tech/sendico/pkg => ../pkg
|
||||||
|
|
||||||
@@ -38,12 +38,12 @@ require (
|
|||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
google.golang.org/grpc v1.78.0 // indirect
|
google.golang.org/grpc v1.78.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -172,15 +172,15 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -199,8 +199,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -208,8 +208,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/fx/ingestor
|
module github.com/tech/sendico/fx/ingestor
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ require (
|
|||||||
github.com/tech/sendico/fx/storage v0.0.0
|
github.com/tech/sendico/fx/storage v0.0.0
|
||||||
github.com/tech/sendico/pkg v0.1.0
|
github.com/tech/sendico/pkg v0.1.0
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.27.1
|
||||||
golang.org/x/net v0.49.0
|
golang.org/x/net v0.50.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,11 +43,11 @@ require (
|
|||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
google.golang.org/grpc v1.78.0 // indirect
|
google.golang.org/grpc v1.78.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -172,15 +172,15 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -199,8 +199,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -208,8 +208,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/fx/oracle
|
module github.com/tech/sendico/fx/oracle
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -43,10 +43,10 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -172,15 +172,15 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -199,8 +199,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -208,8 +208,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/fx/storage
|
module github.com/tech/sendico/fx/storage
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ require (
|
|||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
|||||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -150,16 +150,16 @@ 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-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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
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.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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/gateway/chain
|
module github.com/tech/sendico/gateway/chain
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -77,12 +77,12 @@ require (
|
|||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -318,8 +318,8 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
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/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=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
@@ -327,8 +327,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -349,8 +349,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -360,8 +360,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/gateway/mntx
|
module github.com/tech/sendico/gateway/mntx
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -43,10 +43,10 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -174,15 +174,15 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -201,8 +201,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -210,8 +210,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/gateway/tgsettle
|
module github.com/tech/sendico/gateway/tgsettle
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -40,10 +40,10 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -172,15 +172,15 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -199,8 +199,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -208,8 +208,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/gateway/tron
|
module github.com/tech/sendico/gateway/tron
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -85,13 +85,13 @@ require (
|
|||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -334,8 +334,8 @@ 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
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/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=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
@@ -344,8 +344,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -368,8 +368,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -379,10 +379,10 @@ 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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -174,15 +174,15 @@ 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=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -201,8 +201,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -210,8 +210,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func (s *Service) blockAccountResponder(_ context.Context, req *ledgerv1.BlockAc
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := s.logger.With(mzap.ObjRef("account_ref", accountRef))
|
logger := s.logger.With(mzap.AccRef(accountRef))
|
||||||
|
|
||||||
account, err := s.storage.Accounts().Get(ctx, accountRef)
|
account, err := s.storage.Accounts().Get(ctx, accountRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -61,7 +61,7 @@ func (s *Service) blockAccountResponder(_ context.Context, req *ledgerv1.BlockAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if account.Status == pmodel.LedgerAccountStatusFrozen {
|
if account.Status == pmodel.LedgerAccountStatusFrozen {
|
||||||
logger.Debug("account already frozen", mzap.ObjRef("account_ref", accountRef))
|
logger.Debug("account already frozen", mzap.AccRef(accountRef))
|
||||||
return &ledgerv1.BlockAccountResponse{Account: toProtoAccount(account)}, nil
|
return &ledgerv1.BlockAccountResponse{Account: toProtoAccount(account)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ func (s *Service) blockAccountResponder(_ context.Context, req *ledgerv1.BlockAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
account.Status = pmodel.LedgerAccountStatusFrozen
|
account.Status = pmodel.LedgerAccountStatusFrozen
|
||||||
logger.Info("account blocked (frozen)", mzap.ObjRef("account_ref", accountRef))
|
logger.Info("account blocked (frozen)", mzap.AccRef(accountRef))
|
||||||
return &ledgerv1.BlockAccountResponse{Account: toProtoAccount(account)}, nil
|
return &ledgerv1.BlockAccountResponse{Account: toProtoAccount(account)}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ func (s *Service) unblockAccountResponder(_ context.Context, req *ledgerv1.Unblo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := s.logger.With(mzap.ObjRef("account_ref", accountRef))
|
logger := s.logger.With(mzap.AccRef(accountRef))
|
||||||
|
|
||||||
account, err := s.storage.Accounts().Get(ctx, accountRef)
|
account, err := s.storage.Accounts().Get(ctx, accountRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,7 +124,7 @@ func (s *Service) unblockAccountResponder(_ context.Context, req *ledgerv1.Unblo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if account.Status == pmodel.LedgerAccountStatusActive {
|
if account.Status == pmodel.LedgerAccountStatusActive {
|
||||||
logger.Debug("account already active", mzap.ObjRef("account_ref", accountRef))
|
logger.Debug("account already active", mzap.AccRef(accountRef))
|
||||||
return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil
|
return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ func (s *Service) unblockAccountResponder(_ context.Context, req *ledgerv1.Unblo
|
|||||||
}
|
}
|
||||||
|
|
||||||
account.Status = pmodel.LedgerAccountStatusActive
|
account.Status = pmodel.LedgerAccountStatusActive
|
||||||
logger.Info("account unblocked (active)", mzap.ObjRef("account_ref", accountRef))
|
logger.Info("account unblocked (active)", mzap.AccRef(accountRef))
|
||||||
return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil
|
return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ func (s *Service) upsertBalances(ctx context.Context, lines []*model.PostingLine
|
|||||||
for accountRef, delta := range balanceDeltas {
|
for accountRef, delta := range balanceDeltas {
|
||||||
account := accounts[accountRef]
|
account := accounts[accountRef]
|
||||||
if account == nil {
|
if account == nil {
|
||||||
s.logger.Warn("account cache missing for balance update", mzap.ObjRef("account_ref", accountRef))
|
s.logger.Warn("account cache missing for balance update", mzap.AccRef(accountRef))
|
||||||
return merrors.Internal("account cache missing for balance update")
|
return merrors.Internal("account cache missing for balance update")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ func (s *Service) upsertBalances(ctx context.Context, lines []*model.PostingLine
|
|||||||
if err != nil && !errors.Is(err, storage.ErrBalanceNotFound) {
|
if err != nil && !errors.Is(err, storage.ErrBalanceNotFound) {
|
||||||
s.logger.Warn("failed to fetch account balance",
|
s.logger.Warn("failed to fetch account balance",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef))
|
mzap.AccRef(accountRef))
|
||||||
return merrors.Internal("failed to update balance")
|
return merrors.Internal("failed to update balance")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ func (s *Service) upsertBalances(ctx context.Context, lines []*model.PostingLine
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := balancesStore.Upsert(ctx, newBalance); err != nil {
|
if err := balancesStore.Upsert(ctx, newBalance); err != nil {
|
||||||
s.logger.Warn("failed to upsert account balance", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
s.logger.Warn("failed to upsert account balance", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return merrors.Internal("failed to update balance")
|
return merrors.Internal("failed to update balance")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,14 +124,14 @@ func (a *accountsStore) Get(ctx context.Context, accountRef bson.ObjectID) (*pkm
|
|||||||
result := &pkm.LedgerAccount{}
|
result := &pkm.LedgerAccount{}
|
||||||
if err := a.repo.Get(ctx, accountRef, result); err != nil {
|
if err := a.repo.Get(ctx, accountRef, result); err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
a.logger.Debug("Account not found", mzap.ObjRef("account_ref", accountRef))
|
a.logger.Debug("Account not found", mzap.AccRef(accountRef))
|
||||||
return nil, storage.ErrAccountNotFound
|
return nil, storage.ErrAccountNotFound
|
||||||
}
|
}
|
||||||
a.logger.Warn("Failed to get account", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
a.logger.Warn("Failed to get account", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
a.logger.Debug("Account loaded", mzap.ObjRef("account_ref", accountRef), zap.String("account_code", result.AccountCode))
|
a.logger.Debug("Account loaded", mzap.AccRef(accountRef), zap.String("account_code", result.AccountCode))
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,10 +325,10 @@ func (a *accountsStore) UpdateStatus(ctx context.Context, accountRef bson.Object
|
|||||||
|
|
||||||
patch := repository.Patch().Set(repository.Field("status"), status)
|
patch := repository.Patch().Set(repository.Field("status"), status)
|
||||||
if err := a.repo.Patch(ctx, accountRef, patch); err != nil {
|
if err := a.repo.Patch(ctx, accountRef, patch); err != nil {
|
||||||
a.logger.Warn("Failed to update account status", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
a.logger.Warn("Failed to update account status", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a.logger.Debug("Account status updated", mzap.ObjRef("account_ref", accountRef), zap.String("status", string(status)))
|
a.logger.Debug("Account status updated", mzap.AccRef(accountRef), zap.String("status", string(status)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ func (b *balancesStore) Get(ctx context.Context, accountRef bson.ObjectID) (*mod
|
|||||||
result := &model.AccountBalance{}
|
result := &model.AccountBalance{}
|
||||||
if err := b.repo.FindOneByFilter(ctx, query, result); err != nil {
|
if err := b.repo.FindOneByFilter(ctx, query, result); err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
b.logger.Debug("balance not found", mzap.ObjRef("account_ref", accountRef))
|
b.logger.Debug("balance not found", mzap.AccRef(accountRef))
|
||||||
return nil, storage.ErrBalanceNotFound
|
return nil, storage.ErrBalanceNotFound
|
||||||
}
|
}
|
||||||
b.logger.Warn("failed to get balance", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
b.logger.Warn("failed to get balance", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logger.Debug("balance loaded", mzap.ObjRef("account_ref", accountRef),
|
b.logger.Debug("balance loaded", mzap.AccRef(accountRef),
|
||||||
zap.String("balance", result.Balance))
|
zap.String("balance", result.Balance))
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,10 +130,10 @@ func (p *postingLinesStore) ListByAccount(ctx context.Context, accountRef bson.O
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Warn("failed to list posting lines by account", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
p.logger.Warn("failed to list posting lines by account", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.logger.Debug("listed posting lines by account", zap.Int("count", len(lines)), mzap.ObjRef("account_ref", accountRef))
|
p.logger.Debug("listed posting lines by account", zap.Int("count", len(lines)), mzap.AccRef(accountRef))
|
||||||
return lines, nil
|
return lines, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/notification
|
module github.com/tech/sendico/notification
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../pkg
|
replace github.com/tech/sendico/pkg => ../pkg
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ require (
|
|||||||
github.com/xhit/go-simple-mail/v2 v2.16.0
|
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.27.1
|
||||||
golang.org/x/text v0.33.0
|
golang.org/x/text v0.34.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,11 +46,11 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
google.golang.org/grpc v1.78.0 // indirect
|
google.golang.org/grpc v1.78.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -189,15 +189,15 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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-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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -216,8 +216,8 @@ 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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -225,8 +225,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ func (a *NotificationAPI) onAccount(context context.Context, account *model.Acco
|
|||||||
var link string
|
var link string
|
||||||
var err error
|
var err error
|
||||||
if link, err = a.dp.GetFullLink("verify", token); err != nil {
|
if link, err = a.dp.GetFullLink("verify", token); err != nil {
|
||||||
a.logger.Warn("Failed to generate verification link", zap.Error(err), zap.String("login", account.Login))
|
a.logger.Warn("Failed to generate verification link", zap.Error(err), mzap.Login(account))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mr := a.client.MailBuilder().
|
mr := a.client.MailBuilder().
|
||||||
@@ -21,9 +22,9 @@ func (a *NotificationAPI) onAccount(context context.Context, account *model.Acco
|
|||||||
AddButton(link).
|
AddButton(link).
|
||||||
SetTemplateID("welcome")
|
SetTemplateID("welcome")
|
||||||
if err := a.client.Send(mr); err != nil {
|
if err := a.client.Send(mr); err != nil {
|
||||||
a.logger.Warn("Failed to send verification email", zap.Error(err), zap.String("login", account.Login))
|
a.logger.Warn("Failed to send verification email", zap.Error(err), mzap.Login(account))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.logger.Info("Verification email sent", zap.String("login", account.Login))
|
a.logger.Info("Verification email sent", mzap.Login(account))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,24 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *NotificationAPI) onConfirmationCode(ctx context.Context, account *model.Account, destination string, target model.ConfirmationTarget, code string) error {
|
func (a *NotificationAPI) onConfirmationCode(ctx context.Context, account *model.Account, target string, purpose model.VerificationPurpose, code string) error {
|
||||||
builder := a.client.MailBuilder().
|
builder := a.client.MailBuilder().
|
||||||
AddRecipient(account.Name, strings.TrimSpace(destination)).
|
AddRecipient(account.Name, strings.TrimSpace(target)).
|
||||||
SetAccountID(account.ID.Hex()).
|
SetAccountID(account.ID.Hex()).
|
||||||
SetLocale(account.Locale).
|
SetLocale(account.Locale).
|
||||||
SetTemplateID("confirmation-code").
|
SetTemplateID("confirmation-code").
|
||||||
AddData("Name", account.Name).
|
AddData("Name", account.Name).
|
||||||
AddData("Code", code).
|
AddData("Code", code).
|
||||||
AddData("Target", string(target))
|
AddData("Target", model.VPToString(purpose))
|
||||||
|
|
||||||
if err := a.client.Send(builder); err != nil {
|
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))
|
a.logger.Warn("Failed to send confirmation code email", zap.Error(err), mzap.Login(account))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.logger.Info("Confirmation code email sent", zap.String("login", account.Login), zap.String("destination", destination), zap.String("target", string(target)))
|
a.logger.Info("Confirmation code email sent", mzap.Login(account), zap.String("destination", target), zap.String("target", string(purpose)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ func (a *NotificationAPI) onPasswordReset(context context.Context, account *mode
|
|||||||
var link string
|
var link string
|
||||||
var err error
|
var err error
|
||||||
if link, err = a.dp.GetFullLink("password", "reset", account.ID.Hex(), resetToken); err != nil {
|
if link, err = a.dp.GetFullLink("password", "reset", account.ID.Hex(), resetToken); err != nil {
|
||||||
a.logger.Warn("Failed to generate password reset link", zap.Error(err), zap.String("login", account.Login))
|
a.logger.Warn("Failed to generate password reset link", zap.Error(err), mzap.Login(account))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mr := a.client.MailBuilder().
|
mr := a.client.MailBuilder().
|
||||||
@@ -22,9 +23,9 @@ func (a *NotificationAPI) onPasswordReset(context context.Context, account *mode
|
|||||||
AddData("URL", link).
|
AddData("URL", link).
|
||||||
SetTemplateID("reset-password")
|
SetTemplateID("reset-password")
|
||||||
if err := a.client.Send(mr); err != nil {
|
if err := a.client.Send(mr); err != nil {
|
||||||
a.logger.Warn("Failed to send password reset email", zap.Error(err), zap.String("login", account.Login))
|
a.logger.Warn("Failed to send password reset email", zap.Error(err), mzap.Login(account))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.logger.Info("Password reset email sent", zap.String("login", account.Login))
|
a.logger.Info("Password reset email sent", mzap.Login(account))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/payments/orchestrator
|
module github.com/tech/sendico/payments/orchestrator
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -101,10 +101,10 @@ require (
|
|||||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -205,8 +205,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
@@ -217,8 +217,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -242,14 +242,14 @@ golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
|||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -266,8 +266,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -192,6 +192,10 @@ func Forbidden(logger mlogger.Logger, source mservice.Type, errType, hint string
|
|||||||
return Error(logger, source, http.StatusForbidden, errType, hint)
|
return Error(logger, source, http.StatusForbidden, errType, hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TooManyRequests(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
|
||||||
|
return Error(logger, source, http.StatusTooManyRequests, "too_many_requests", hint)
|
||||||
|
}
|
||||||
|
|
||||||
func LicenseRequired(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
|
func LicenseRequired(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
errorf(logger, w, r, source, http.StatusPaymentRequired, "license_required", hint)
|
errorf(logger, w, r, source, http.StatusPaymentRequired, "license_required", hint)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (db *ArchivableDBImp[T]) SetArchived(ctx context.Context, accountRef, objec
|
|||||||
// Check permissions using enforceObject helper
|
// Check permissions using enforceObject helper
|
||||||
if err := enforceObjectByRef(ctx, db.dbImp, db.enforcer, model.ActionUpdate, accountRef, objectRef); err != nil {
|
if err := enforceObjectByRef(ctx, db.dbImp, db.enforcer, model.ActionUpdate, accountRef, objectRef); err != nil {
|
||||||
db.logger.Warn("Failed to enforce object permission", zap.Error(err),
|
db.logger.Warn("Failed to enforce object permission", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ func (db *ArchivableDBImp[T]) SetArchived(ctx context.Context, accountRef, objec
|
|||||||
obj := db.createEmpty()
|
obj := db.createEmpty()
|
||||||
if err := db.dbImp.Get(ctx, objectRef, obj); err != nil {
|
if err := db.dbImp.Get(ctx, objectRef, obj); err != nil {
|
||||||
db.logger.Warn("Failed to get object for setting archived status", zap.Error(err),
|
db.logger.Warn("Failed to get object for setting archived status", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ func (db *ArchivableDBImp[T]) SetArchived(ctx context.Context, accountRef, objec
|
|||||||
archivable := db.getArchivable(obj)
|
archivable := db.getArchivable(obj)
|
||||||
currentArchived := archivable.IsArchived()
|
currentArchived := archivable.IsArchived()
|
||||||
if currentArchived == archived {
|
if currentArchived == archived {
|
||||||
db.logger.Debug("No change needed - same archived status", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("No change needed - same archived status", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
||||||
return nil // No change needed
|
return nil // No change needed
|
||||||
}
|
}
|
||||||
@@ -69,11 +69,11 @@ func (db *ArchivableDBImp[T]) SetArchived(ctx context.Context, accountRef, objec
|
|||||||
patch := repository.Patch().Set(repository.IsArchivedField(), archived)
|
patch := repository.Patch().Set(repository.IsArchivedField(), archived)
|
||||||
if err := db.dbImp.Patch(ctx, objectRef, patch); err != nil {
|
if err := db.dbImp.Patch(ctx, objectRef, patch); err != nil {
|
||||||
db.logger.Warn("Failed to set archived status on object", zap.Error(err),
|
db.logger.Warn("Failed to set archived status on object", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.logger.Debug("Successfully set archived status on object", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("Successfully set archived status on object", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -82,14 +82,14 @@ func (db *ArchivableDBImp[T]) SetArchived(ctx context.Context, accountRef, objec
|
|||||||
func (db *ArchivableDBImp[T]) IsArchived(ctx context.Context, accountRef, objectRef bson.ObjectID) (bool, error) {
|
func (db *ArchivableDBImp[T]) IsArchived(ctx context.Context, accountRef, objectRef bson.ObjectID) (bool, error) {
|
||||||
// // Check permissions using single Enforce
|
// // Check permissions using single Enforce
|
||||||
if err := enforceObjectByRef(ctx, db.dbImp, db.enforcer, model.ActionRead, accountRef, objectRef); err != nil {
|
if err := enforceObjectByRef(ctx, db.dbImp, db.enforcer, model.ActionRead, accountRef, objectRef); err != nil {
|
||||||
db.logger.Debug("Permission denied for checking archived status", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("Permission denied for checking archived status", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(model.ActionRead)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(model.ActionRead)))
|
||||||
return false, merrors.AccessDenied("read", "object", objectRef)
|
return false, merrors.AccessDenied("read", "object", objectRef)
|
||||||
}
|
}
|
||||||
obj := db.createEmpty()
|
obj := db.createEmpty()
|
||||||
if err := db.dbImp.Get(ctx, objectRef, obj); err != nil {
|
if err := db.dbImp.Get(ctx, objectRef, obj); err != nil {
|
||||||
db.logger.Warn("Failed to get object for checking archived status", zap.Error(err),
|
db.logger.Warn("Failed to get object for checking archived status", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
archivable := db.getArchivable(obj)
|
archivable := db.getArchivable(obj)
|
||||||
|
|||||||
@@ -33,13 +33,13 @@ func (db *ProtectedDBImp[T]) enforce(ctx context.Context, action model.Action, o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to enforce permission",
|
db.DBImp.Logger.Warn("Failed to enforce permission",
|
||||||
zap.Error(err), mzap.ObjRef("permission_ref", object.GetPermissionRef()),
|
zap.Error(err), mzap.ObjRef("permission_ref", object.GetPermissionRef()),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", object.GetOrganizationRef()),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", object.GetOrganizationRef()),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !res {
|
if !res {
|
||||||
db.DBImp.Logger.Debug("Access denied", mzap.ObjRef("permission_ref", object.GetPermissionRef()),
|
db.DBImp.Logger.Debug("Access denied", mzap.ObjRef("permission_ref", object.GetPermissionRef()),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", object.GetOrganizationRef()),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", object.GetOrganizationRef()),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
||||||
return merrors.AccessDenied(db.Collection, string(action), objectRef)
|
return merrors.AccessDenied(db.Collection, string(action), objectRef)
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ func (db *ProtectedDBImp[T]) enforce(ctx context.Context, action model.Action, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *ProtectedDBImp[T]) Create(ctx context.Context, accountRef, organizationRef bson.ObjectID, object T) error {
|
func (db *ProtectedDBImp[T]) Create(ctx context.Context, accountRef, organizationRef bson.ObjectID, object T) error {
|
||||||
db.DBImp.Logger.Debug("Attempting to create object", mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Debug("Attempting to create object", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)))
|
||||||
|
|
||||||
if object.GetPermissionRef() == bson.NilObjectID {
|
if object.GetPermissionRef() == bson.NilObjectID {
|
||||||
@@ -60,12 +60,12 @@ func (db *ProtectedDBImp[T]) Create(ctx context.Context, accountRef, organizatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DBImp.Create(ctx, object); err != nil {
|
if err := db.DBImp.Create(ctx, object); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to create object", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Warn("Failed to create object", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Successfully created object", mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Debug("Successfully created object", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ func (db *ProtectedDBImp[T]) InsertMany(ctx context.Context, accountRef, organiz
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Attempting to insert many objects", mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Debug("Attempting to insert many objects", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)),
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)),
|
||||||
zap.Int("count", len(objects)))
|
zap.Int("count", len(objects)))
|
||||||
|
|
||||||
@@ -92,13 +92,13 @@ func (db *ProtectedDBImp[T]) InsertMany(ctx context.Context, accountRef, organiz
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DBImp.InsertMany(ctx, objects); err != nil {
|
if err := db.DBImp.InsertMany(ctx, objects); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to insert many objects", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Warn("Failed to insert many objects", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)),
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)),
|
||||||
zap.Int("count", len(objects)))
|
zap.Int("count", len(objects)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Successfully inserted many objects", mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Debug("Successfully inserted many objects", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)),
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("collection", string(db.Collection)),
|
||||||
zap.Int("count", len(objects)))
|
zap.Int("count", len(objects)))
|
||||||
return nil
|
return nil
|
||||||
@@ -108,57 +108,57 @@ func (db *ProtectedDBImp[T]) enforceObject(ctx context.Context, action model.Act
|
|||||||
l, err := db.ListIDs(ctx, action, accountRef, repository.IDFilter(objectRef))
|
l, err := db.ListIDs(ctx, action, accountRef, repository.IDFilter(objectRef))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.DBImp.Logger.Warn("Error occured while checking access rights", zap.Error(err),
|
db.DBImp.Logger.Warn("Error occured while checking access rights", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(l) == 0 {
|
if len(l) == 0 {
|
||||||
db.DBImp.Logger.Debug("Access denied", zap.String("action", string(action)), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
db.DBImp.Logger.Debug("Access denied", zap.String("action", string(action)), mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return merrors.AccessDenied(db.Collection, string(action), objectRef)
|
return merrors.AccessDenied(db.Collection, string(action), objectRef)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *ProtectedDBImp[T]) Get(ctx context.Context, accountRef, objectRef bson.ObjectID, result T) error {
|
func (db *ProtectedDBImp[T]) Get(ctx context.Context, accountRef, objectRef bson.ObjectID, result T) error {
|
||||||
db.DBImp.Logger.Debug("Attempting to get object", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
db.DBImp.Logger.Debug("Attempting to get object", mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
|
|
||||||
if err := db.enforceObject(ctx, model.ActionRead, accountRef, objectRef); err != nil {
|
if err := db.enforceObject(ctx, model.ActionRead, accountRef, objectRef); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DBImp.Get(ctx, objectRef, result); err != nil {
|
if err := db.DBImp.Get(ctx, objectRef, result); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to get object", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Warn("Failed to get object", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("collection", string(db.Collection)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Successfully retrieved object",
|
db.DBImp.Logger.Debug("Successfully retrieved object",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", result.GetOrganizationRef()),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", result.GetOrganizationRef()),
|
||||||
mzap.StorableRef(result), mzap.ObjRef("permission_ref", result.GetPermissionRef()))
|
mzap.StorableRef(result), mzap.ObjRef("permission_ref", result.GetPermissionRef()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *ProtectedDBImp[T]) Update(ctx context.Context, accountRef bson.ObjectID, object T) error {
|
func (db *ProtectedDBImp[T]) Update(ctx context.Context, accountRef bson.ObjectID, object T) error {
|
||||||
db.DBImp.Logger.Debug("Attempting to update object", mzap.ObjRef("account_ref", accountRef), mzap.StorableRef(object))
|
db.DBImp.Logger.Debug("Attempting to update object", mzap.AccRef(accountRef), mzap.StorableRef(object))
|
||||||
|
|
||||||
if err := db.enforceObject(ctx, model.ActionUpdate, accountRef, *object.GetID()); err != nil {
|
if err := db.enforceObject(ctx, model.ActionUpdate, accountRef, *object.GetID()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DBImp.Update(ctx, object); err != nil {
|
if err := db.DBImp.Update(ctx, object); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to update object", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Warn("Failed to update object", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", object.GetOrganizationRef()), mzap.StorableRef(object))
|
mzap.ObjRef("organization_ref", object.GetOrganizationRef()), mzap.StorableRef(object))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Successfully updated object",
|
db.DBImp.Logger.Debug("Successfully updated object",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", object.GetOrganizationRef()),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", object.GetOrganizationRef()),
|
||||||
mzap.StorableRef(object), mzap.ObjRef("permission_ref", object.GetPermissionRef()))
|
mzap.StorableRef(object), mzap.ObjRef("permission_ref", object.GetPermissionRef()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *ProtectedDBImp[T]) Delete(ctx context.Context, accountRef, objectRef bson.ObjectID) error {
|
func (db *ProtectedDBImp[T]) Delete(ctx context.Context, accountRef, objectRef bson.ObjectID) error {
|
||||||
db.DBImp.Logger.Debug("Attempting to delete object",
|
db.DBImp.Logger.Debug("Attempting to delete object",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
|
|
||||||
if err := db.enforceObject(ctx, model.ActionDelete, accountRef, objectRef); err != nil {
|
if err := db.enforceObject(ctx, model.ActionDelete, accountRef, objectRef); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -166,12 +166,12 @@ func (db *ProtectedDBImp[T]) Delete(ctx context.Context, accountRef, objectRef b
|
|||||||
|
|
||||||
if err := db.DBImp.Delete(ctx, objectRef); err != nil {
|
if err := db.DBImp.Delete(ctx, objectRef); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to delete object", zap.Error(err),
|
db.DBImp.Logger.Warn("Failed to delete object", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Successfully deleted object",
|
db.DBImp.Logger.Debug("Successfully deleted object",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,17 +182,17 @@ func (db *ProtectedDBImp[T]) ListIDs(
|
|||||||
query builder.Query,
|
query builder.Query,
|
||||||
) ([]bson.ObjectID, error) {
|
) ([]bson.ObjectID, error) {
|
||||||
db.DBImp.Logger.Debug("Attempting to list object IDs",
|
db.DBImp.Logger.Debug("Attempting to list object IDs",
|
||||||
mzap.ObjRef("account_ref", accountRef), zap.String("collection", string(db.Collection)), zap.Any("filter", query.BuildQuery()))
|
mzap.AccRef(accountRef), zap.String("collection", string(db.Collection)), zap.Any("filter", query.BuildQuery()))
|
||||||
|
|
||||||
// 1. Fetch all candidate IDs from the underlying DB
|
// 1. Fetch all candidate IDs from the underlying DB
|
||||||
allIDs, err := db.DBImp.ListPermissionBound(ctx, query)
|
allIDs, err := db.DBImp.ListPermissionBound(ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to list object IDs", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Warn("Failed to list object IDs", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
zap.String("collection", string(db.Collection)), zap.String("action", string(action)))
|
zap.String("collection", string(db.Collection)), zap.String("action", string(action)))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(allIDs) == 0 {
|
if len(allIDs) == 0 {
|
||||||
db.DBImp.Logger.Debug("No objects found matching filter", mzap.ObjRef("account_ref", accountRef),
|
db.DBImp.Logger.Debug("No objects found matching filter", mzap.AccRef(accountRef),
|
||||||
zap.String("collection", string(db.Collection)), zap.Any("filter", query.BuildQuery()))
|
zap.String("collection", string(db.Collection)), zap.Any("filter", query.BuildQuery()))
|
||||||
return []bson.ObjectID{}, merrors.NoData(fmt.Sprintf("no %s found", db.Collection))
|
return []bson.ObjectID{}, merrors.NoData(fmt.Sprintf("no %s found", db.Collection))
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ func (db *ProtectedDBImp[T]) ListIDs(
|
|||||||
// If the error is something other than AccessDenied, we want to fail
|
// If the error is something other than AccessDenied, we want to fail
|
||||||
db.DBImp.Logger.Warn("Error while enforcing read permission", zap.Error(enforceErr),
|
db.DBImp.Logger.Warn("Error while enforcing read permission", zap.Error(enforceErr),
|
||||||
mzap.ObjRef("permission_ref", desc.GetPermissionRef()), zap.String("action", string(action)),
|
mzap.ObjRef("permission_ref", desc.GetPermissionRef()), zap.String("action", string(action)),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", desc.GetOrganizationRef()),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", desc.GetOrganizationRef()),
|
||||||
mzap.ObjRef("object_ref", *desc.GetID()), zap.String("collection", string(db.Collection)),
|
mzap.ObjRef("object_ref", *desc.GetID()), zap.String("collection", string(db.Collection)),
|
||||||
)
|
)
|
||||||
return nil, enforceErr
|
return nil, enforceErr
|
||||||
@@ -216,7 +216,7 @@ func (db *ProtectedDBImp[T]) ListIDs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Successfully enforced read permission on IDs", zap.Int("fetched_count", len(allIDs)),
|
db.DBImp.Logger.Debug("Successfully enforced read permission on IDs", zap.Int("fetched_count", len(allIDs)),
|
||||||
zap.Int("allowed_count", len(allowedIDs)), mzap.ObjRef("account_ref", accountRef),
|
zap.Int("allowed_count", len(allowedIDs)), mzap.AccRef(accountRef),
|
||||||
zap.String("collection", string(db.Collection)), zap.String("action", string(action)))
|
zap.String("collection", string(db.Collection)), zap.String("action", string(action)))
|
||||||
|
|
||||||
// 3. Return only the IDs that passed permission checks
|
// 3. Return only the IDs that passed permission checks
|
||||||
@@ -270,7 +270,7 @@ func CreateDBImp[T model.PermissionBoundStorable](
|
|||||||
|
|
||||||
func (db *ProtectedDBImp[T]) Patch(ctx context.Context, accountRef, objectRef bson.ObjectID, patch builder.Patch) error {
|
func (db *ProtectedDBImp[T]) Patch(ctx context.Context, accountRef, objectRef bson.ObjectID, patch builder.Patch) error {
|
||||||
db.DBImp.Logger.Debug("Attempting to patch object",
|
db.DBImp.Logger.Debug("Attempting to patch object",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
|
|
||||||
if err := db.enforceObject(ctx, model.ActionUpdate, accountRef, objectRef); err != nil {
|
if err := db.enforceObject(ctx, model.ActionUpdate, accountRef, objectRef); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -278,18 +278,18 @@ func (db *ProtectedDBImp[T]) Patch(ctx context.Context, accountRef, objectRef bs
|
|||||||
|
|
||||||
if err := db.DBImp.Repository.Patch(ctx, objectRef, patch); err != nil {
|
if err := db.DBImp.Repository.Patch(ctx, objectRef, patch); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to patch object", zap.Error(err),
|
db.DBImp.Logger.Warn("Failed to patch object", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Successfully patched object",
|
db.DBImp.Logger.Debug("Successfully patched object",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *ProtectedDBImp[T]) PatchMany(ctx context.Context, accountRef bson.ObjectID, query builder.Query, patch builder.Patch) (int, error) {
|
func (db *ProtectedDBImp[T]) PatchMany(ctx context.Context, accountRef bson.ObjectID, query builder.Query, patch builder.Patch) (int, error) {
|
||||||
db.DBImp.Logger.Debug("Attempting to patch many objects",
|
db.DBImp.Logger.Debug("Attempting to patch many objects",
|
||||||
mzap.ObjRef("account_ref", accountRef), zap.Any("filter", query.BuildQuery()))
|
mzap.AccRef(accountRef), zap.Any("filter", query.BuildQuery()))
|
||||||
|
|
||||||
ids, err := db.ListIDs(ctx, model.ActionUpdate, accountRef, query)
|
ids, err := db.ListIDs(ctx, model.ActionUpdate, accountRef, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -309,11 +309,11 @@ func (db *ProtectedDBImp[T]) PatchMany(ctx context.Context, accountRef bson.Obje
|
|||||||
modified, err := db.DBImp.Repository.PatchMany(ctx, finalQuery, patch)
|
modified, err := db.DBImp.Repository.PatchMany(ctx, finalQuery, patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to patch many objects", zap.Error(err),
|
db.DBImp.Logger.Warn("Failed to patch many objects", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef))
|
mzap.AccRef(accountRef))
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DBImp.Logger.Debug("Successfully patched many objects",
|
db.DBImp.Logger.Debug("Successfully patched many objects",
|
||||||
mzap.ObjRef("account_ref", accountRef), zap.Int("modified_count", modified))
|
mzap.AccRef(accountRef), zap.Int("modified_count", modified))
|
||||||
return modified, nil
|
return modified, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ func (db *AccountBoundDBImp[T]) enforce(ctx context.Context, action model.Action
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to enforce permission",
|
db.Logger.Warn("Failed to enforce permission",
|
||||||
zap.Error(err), mzap.ObjRef("permission_ref", db.PermissionRef),
|
zap.Error(err), mzap.ObjRef("permission_ref", db.PermissionRef),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
||||||
zap.String("action", string(action)))
|
zap.String("action", string(action)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !res {
|
if !res {
|
||||||
db.Logger.Debug("Access denied", mzap.ObjRef("permission_ref", db.PermissionRef),
|
db.Logger.Debug("Access denied", mzap.ObjRef("permission_ref", db.PermissionRef),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
||||||
zap.String("action", string(action)))
|
zap.String("action", string(action)))
|
||||||
return merrors.AccessDenied(db.Collection, string(action), bson.NilObjectID)
|
return merrors.AccessDenied(db.Collection, string(action), bson.NilObjectID)
|
||||||
}
|
}
|
||||||
@@ -73,13 +73,13 @@ func (db *AccountBoundDBImp[T]) enforceInterface(ctx context.Context, action mod
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to enforce permission",
|
db.Logger.Warn("Failed to enforce permission",
|
||||||
zap.Error(err), mzap.ObjRef("permission_ref", db.PermissionRef),
|
zap.Error(err), mzap.ObjRef("permission_ref", db.PermissionRef),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
||||||
zap.String("action", string(action)))
|
zap.String("action", string(action)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !res {
|
if !res {
|
||||||
db.Logger.Debug("Access denied", mzap.ObjRef("permission_ref", db.PermissionRef),
|
db.Logger.Debug("Access denied", mzap.ObjRef("permission_ref", db.PermissionRef),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
||||||
zap.String("action", string(action)))
|
zap.String("action", string(action)))
|
||||||
return merrors.AccessDenied(db.Collection, string(action), bson.NilObjectID)
|
return merrors.AccessDenied(db.Collection, string(action), bson.NilObjectID)
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ func (db *AccountBoundDBImp[T]) enforceInterface(ctx context.Context, action mod
|
|||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) Create(ctx context.Context, accountRef bson.ObjectID, object T) error {
|
func (db *AccountBoundDBImp[T]) Create(ctx context.Context, accountRef bson.ObjectID, object T) error {
|
||||||
orgRef := object.GetOrganizationRef()
|
orgRef := object.GetOrganizationRef()
|
||||||
db.Logger.Debug("Attempting to create object", mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Debug("Attempting to create object", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", orgRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("organization_ref", orgRef), zap.String("collection", string(db.Collection)))
|
||||||
|
|
||||||
// Check organization update permission for create operations
|
// Check organization update permission for create operations
|
||||||
@@ -97,22 +97,22 @@ func (db *AccountBoundDBImp[T]) Create(ctx context.Context, accountRef bson.Obje
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DBImp.Create(ctx, object); err != nil {
|
if err := db.DBImp.Create(ctx, object); err != nil {
|
||||||
db.Logger.Warn("Failed to create object", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Warn("Failed to create object", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", orgRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("organization_ref", orgRef), zap.String("collection", string(db.Collection)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully created object", mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Debug("Successfully created object", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", orgRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("organization_ref", orgRef), zap.String("collection", string(db.Collection)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) Get(ctx context.Context, accountRef, objectRef bson.ObjectID, result T) error {
|
func (db *AccountBoundDBImp[T]) Get(ctx context.Context, accountRef, objectRef bson.ObjectID, result T) error {
|
||||||
db.Logger.Debug("Attempting to get object", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
db.Logger.Debug("Attempting to get object", mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
|
|
||||||
// First get the object to check its organization
|
// First get the object to check its organization
|
||||||
if err := db.DBImp.Get(ctx, objectRef, result); err != nil {
|
if err := db.DBImp.Get(ctx, objectRef, result); err != nil {
|
||||||
db.Logger.Warn("Failed to get object", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Warn("Failed to get object", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("collection", string(db.Collection)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -122,13 +122,13 @@ func (db *AccountBoundDBImp[T]) Get(ctx context.Context, accountRef, objectRef b
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully retrieved object", mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Debug("Successfully retrieved object", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", result.GetOrganizationRef()), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("organization_ref", result.GetOrganizationRef()), zap.String("collection", string(db.Collection)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) Update(ctx context.Context, accountRef bson.ObjectID, object T) error {
|
func (db *AccountBoundDBImp[T]) Update(ctx context.Context, accountRef bson.ObjectID, object T) error {
|
||||||
db.Logger.Debug("Attempting to update object", mzap.ObjRef("account_ref", accountRef), mzap.StorableRef(object))
|
db.Logger.Debug("Attempting to update object", mzap.AccRef(accountRef), mzap.StorableRef(object))
|
||||||
|
|
||||||
// Check organization update permission
|
// Check organization update permission
|
||||||
if err := db.enforce(ctx, model.ActionUpdate, object, accountRef); err != nil {
|
if err := db.enforce(ctx, model.ActionUpdate, object, accountRef); err != nil {
|
||||||
@@ -136,18 +136,18 @@ func (db *AccountBoundDBImp[T]) Update(ctx context.Context, accountRef bson.Obje
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DBImp.Update(ctx, object); err != nil {
|
if err := db.DBImp.Update(ctx, object); err != nil {
|
||||||
db.Logger.Warn("Failed to update object", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Warn("Failed to update object", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", object.GetOrganizationRef()), mzap.StorableRef(object))
|
mzap.ObjRef("organization_ref", object.GetOrganizationRef()), mzap.StorableRef(object))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully updated object", mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Debug("Successfully updated object", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", object.GetOrganizationRef()), mzap.StorableRef(object))
|
mzap.ObjRef("organization_ref", object.GetOrganizationRef()), mzap.StorableRef(object))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) Patch(ctx context.Context, accountRef, objectRef bson.ObjectID, patch builder.Patch) error {
|
func (db *AccountBoundDBImp[T]) Patch(ctx context.Context, accountRef, objectRef bson.ObjectID, patch builder.Patch) error {
|
||||||
db.Logger.Debug("Attempting to patch object", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
db.Logger.Debug("Attempting to patch object", mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
|
|
||||||
// First get the object to check its organization
|
// First get the object to check its organization
|
||||||
objs, err := db.DBImp.Repository.ListAccountBound(ctx, repository.IDFilter(objectRef))
|
objs, err := db.DBImp.Repository.ListAccountBound(ctx, repository.IDFilter(objectRef))
|
||||||
@@ -156,7 +156,7 @@ func (db *AccountBoundDBImp[T]) Patch(ctx context.Context, accountRef, objectRef
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(objs) == 0 {
|
if len(objs) == 0 {
|
||||||
db.Logger.Debug("Permission denied for deletion", mzap.ObjRef("object_ref", objectRef), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Debug("Permission denied for deletion", mzap.ObjRef("object_ref", objectRef), mzap.AccRef(accountRef))
|
||||||
return merrors.AccessDenied(db.Collection, string(model.ActionDelete), objectRef)
|
return merrors.AccessDenied(db.Collection, string(model.ActionDelete), objectRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,17 +166,17 @@ func (db *AccountBoundDBImp[T]) Patch(ctx context.Context, accountRef, objectRef
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DBImp.Patch(ctx, objectRef, patch); err != nil {
|
if err := db.DBImp.Patch(ctx, objectRef, patch); err != nil {
|
||||||
db.Logger.Warn("Failed to patch object", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Warn("Failed to patch object", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("collection", string(db.Collection)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully patched object", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
db.Logger.Debug("Successfully patched object", mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) Delete(ctx context.Context, accountRef, objectRef bson.ObjectID) error {
|
func (db *AccountBoundDBImp[T]) Delete(ctx context.Context, accountRef, objectRef bson.ObjectID) error {
|
||||||
db.Logger.Debug("Attempting to delete object", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
db.Logger.Debug("Attempting to delete object", mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
|
|
||||||
// First get the object to check its organization
|
// First get the object to check its organization
|
||||||
objs, err := db.DBImp.Repository.ListAccountBound(ctx, repository.IDFilter(objectRef))
|
objs, err := db.DBImp.Repository.ListAccountBound(ctx, repository.IDFilter(objectRef))
|
||||||
@@ -185,7 +185,7 @@ func (db *AccountBoundDBImp[T]) Delete(ctx context.Context, accountRef, objectRe
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(objs) == 0 {
|
if len(objs) == 0 {
|
||||||
db.Logger.Debug("Permission denied for deletion", mzap.ObjRef("object_ref", objectRef), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Debug("Permission denied for deletion", mzap.ObjRef("object_ref", objectRef), mzap.AccRef(accountRef))
|
||||||
return merrors.AccessDenied(db.Collection, string(model.ActionDelete), objectRef)
|
return merrors.AccessDenied(db.Collection, string(model.ActionDelete), objectRef)
|
||||||
}
|
}
|
||||||
// Check organization update permission for delete operations
|
// Check organization update permission for delete operations
|
||||||
@@ -194,29 +194,29 @@ func (db *AccountBoundDBImp[T]) Delete(ctx context.Context, accountRef, objectRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DBImp.Delete(ctx, objectRef); err != nil {
|
if err := db.DBImp.Delete(ctx, objectRef); err != nil {
|
||||||
db.Logger.Warn("Failed to delete object", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Warn("Failed to delete object", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("collection", string(db.Collection)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("collection", string(db.Collection)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully deleted object", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
db.Logger.Debug("Successfully deleted object", mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) DeleteMany(ctx context.Context, accountRef bson.ObjectID, query builder.Query) error {
|
func (db *AccountBoundDBImp[T]) DeleteMany(ctx context.Context, accountRef bson.ObjectID, query builder.Query) error {
|
||||||
db.Logger.Debug("Attempting to delete many objects", mzap.ObjRef("account_ref", accountRef), zap.String("collection", string(db.Collection)))
|
db.Logger.Debug("Attempting to delete many objects", mzap.AccRef(accountRef), zap.String("collection", string(db.Collection)))
|
||||||
|
|
||||||
// Get all candidate objects for batch permission checking
|
// Get all candidate objects for batch permission checking
|
||||||
allObjects, err := db.DBImp.Repository.ListPermissionBound(ctx, query)
|
allObjects, err := db.DBImp.Repository.ListPermissionBound(ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to list objects for delete many", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to list objects for delete many", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use batch enforcement for efficiency
|
// Use batch enforcement for efficiency
|
||||||
allowedResults, err := db.Enforcer.EnforceBatch(ctx, allObjects, accountRef, model.ActionUpdate)
|
allowedResults, err := db.Enforcer.EnforceBatch(ctx, allObjects, accountRef, model.ActionUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to enforce batch permissions for delete many", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to enforce batch permissions for delete many", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,27 +229,27 @@ func (db *AccountBoundDBImp[T]) DeleteMany(ctx context.Context, accountRef bson.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(allowedIDs) == 0 {
|
if len(allowedIDs) == 0 {
|
||||||
db.Logger.Debug("No objects allowed for deletion", mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Debug("No objects allowed for deletion", mzap.AccRef(accountRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete only the allowed objects
|
// Delete only the allowed objects
|
||||||
allowedQuery := query.And(repository.Query().In(repository.IDField(), allowedIDs))
|
allowedQuery := query.And(repository.Query().In(repository.IDField(), allowedIDs))
|
||||||
if err := db.DBImp.DeleteMany(ctx, allowedQuery); err != nil {
|
if err := db.DBImp.DeleteMany(ctx, allowedQuery); err != nil {
|
||||||
db.Logger.Warn("Failed to delete many objects", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to delete many objects", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully deleted many objects", mzap.ObjRef("account_ref", accountRef), zap.Int("count", len(allowedIDs)))
|
db.Logger.Debug("Successfully deleted many objects", mzap.AccRef(accountRef), zap.Int("count", len(allowedIDs)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) FindOne(ctx context.Context, accountRef bson.ObjectID, query builder.Query, result T) error {
|
func (db *AccountBoundDBImp[T]) FindOne(ctx context.Context, accountRef bson.ObjectID, query builder.Query, result T) error {
|
||||||
db.Logger.Debug("Attempting to find one object", mzap.ObjRef("account_ref", accountRef), zap.String("collection", string(db.Collection)))
|
db.Logger.Debug("Attempting to find one object", mzap.AccRef(accountRef), zap.String("collection", string(db.Collection)))
|
||||||
|
|
||||||
// For FindOne, we need to check read permission after finding the object
|
// For FindOne, we need to check read permission after finding the object
|
||||||
if err := db.DBImp.FindOne(ctx, query, result); err != nil {
|
if err := db.DBImp.FindOne(ctx, query, result); err != nil {
|
||||||
db.Logger.Warn("Failed to find one object", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to find one object", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,25 +258,25 @@ func (db *AccountBoundDBImp[T]) FindOne(ctx context.Context, accountRef bson.Obj
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully found one object", mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Debug("Successfully found one object", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", result.GetOrganizationRef()))
|
mzap.ObjRef("organization_ref", result.GetOrganizationRef()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) ListIDs(ctx context.Context, accountRef bson.ObjectID, query builder.Query) ([]bson.ObjectID, error) {
|
func (db *AccountBoundDBImp[T]) ListIDs(ctx context.Context, accountRef bson.ObjectID, query builder.Query) ([]bson.ObjectID, error) {
|
||||||
db.Logger.Debug("Attempting to list object IDs", mzap.ObjRef("account_ref", accountRef), zap.String("collection", string(db.Collection)))
|
db.Logger.Debug("Attempting to list object IDs", mzap.AccRef(accountRef), zap.String("collection", string(db.Collection)))
|
||||||
|
|
||||||
// Get all candidate objects for batch permission checking
|
// Get all candidate objects for batch permission checking
|
||||||
allObjects, err := db.DBImp.Repository.ListPermissionBound(ctx, query)
|
allObjects, err := db.DBImp.Repository.ListPermissionBound(ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to list objects for ID filtering", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to list objects for ID filtering", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use batch enforcement for efficiency
|
// Use batch enforcement for efficiency
|
||||||
allowedResults, err := db.Enforcer.EnforceBatch(ctx, allObjects, accountRef, model.ActionRead)
|
allowedResults, err := db.Enforcer.EnforceBatch(ctx, allObjects, accountRef, model.ActionRead)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to enforce batch permissions for ID listing", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to enforce batch permissions for ID listing", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,12 +289,12 @@ func (db *AccountBoundDBImp[T]) ListIDs(ctx context.Context, accountRef bson.Obj
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully filtered object IDs", zap.Int("total_count", len(allObjects)),
|
db.Logger.Debug("Successfully filtered object IDs", zap.Int("total_count", len(allObjects)),
|
||||||
zap.Int("allowed_count", len(allowedIDs)), mzap.ObjRef("account_ref", accountRef))
|
zap.Int("allowed_count", len(allowedIDs)), mzap.AccRef(accountRef))
|
||||||
return allowedIDs, nil
|
return allowedIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) ListAccountBound(ctx context.Context, accountRef, organizationRef bson.ObjectID, query builder.Query) ([]model.AccountBoundStorable, error) {
|
func (db *AccountBoundDBImp[T]) ListAccountBound(ctx context.Context, accountRef, organizationRef bson.ObjectID, query builder.Query) ([]model.AccountBoundStorable, error) {
|
||||||
db.Logger.Debug("Attempting to list account bound objects", mzap.ObjRef("account_ref", accountRef), zap.String("collection", string(db.Collection)))
|
db.Logger.Debug("Attempting to list account bound objects", mzap.AccRef(accountRef), zap.String("collection", string(db.Collection)))
|
||||||
|
|
||||||
// Build query to find objects where accountRef matches OR is null/absent
|
// Build query to find objects where accountRef matches OR is null/absent
|
||||||
accountQuery := repository.WithOrg(accountRef, organizationRef)
|
accountQuery := repository.WithOrg(accountRef, organizationRef)
|
||||||
@@ -305,7 +305,7 @@ func (db *AccountBoundDBImp[T]) ListAccountBound(ctx context.Context, accountRef
|
|||||||
// Get all candidate objects
|
// Get all candidate objects
|
||||||
allObjects, err := db.DBImp.Repository.ListAccountBound(ctx, finalQuery)
|
allObjects, err := db.DBImp.Repository.ListAccountBound(ctx, finalQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to list account bound objects", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to list account bound objects", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,18 +323,18 @@ func (db *AccountBoundDBImp[T]) ListAccountBound(ctx context.Context, accountRef
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully filtered account bound objects", zap.Int("total_count", len(allObjects)),
|
db.Logger.Debug("Successfully filtered account bound objects", zap.Int("total_count", len(allObjects)),
|
||||||
zap.Int("allowed_count", len(allowedObjects)), mzap.ObjRef("account_ref", accountRef))
|
zap.Int("allowed_count", len(allowedObjects)), mzap.AccRef(accountRef))
|
||||||
return allowedObjects, nil
|
return allowedObjects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) GetByAccountRef(ctx context.Context, accountRef bson.ObjectID, result T) error {
|
func (db *AccountBoundDBImp[T]) GetByAccountRef(ctx context.Context, accountRef bson.ObjectID, result T) error {
|
||||||
db.Logger.Debug("Attempting to get object by account ref", mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Debug("Attempting to get object by account ref", mzap.AccRef(accountRef))
|
||||||
|
|
||||||
// Build query to find objects where accountRef matches OR is null/absent
|
// Build query to find objects where accountRef matches OR is null/absent
|
||||||
query := repository.WithoutOrg(accountRef)
|
query := repository.WithoutOrg(accountRef)
|
||||||
|
|
||||||
if err := db.DBImp.FindOne(ctx, query, result); err != nil {
|
if err := db.DBImp.FindOne(ctx, query, result); err != nil {
|
||||||
db.Logger.Warn("Failed to get object by account ref", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to get object by account ref", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,13 +343,13 @@ func (db *AccountBoundDBImp[T]) GetByAccountRef(ctx context.Context, accountRef
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully retrieved object by account ref", mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Debug("Successfully retrieved object by account ref", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", result.GetOrganizationRef()))
|
mzap.ObjRef("organization_ref", result.GetOrganizationRef()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *AccountBoundDBImp[T]) DeleteByAccountRef(ctx context.Context, accountRef bson.ObjectID) error {
|
func (db *AccountBoundDBImp[T]) DeleteByAccountRef(ctx context.Context, accountRef bson.ObjectID) error {
|
||||||
db.Logger.Debug("Attempting to delete objects by account ref", mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Debug("Attempting to delete objects by account ref", mzap.AccRef(accountRef))
|
||||||
|
|
||||||
// Build query to find objects where accountRef matches OR is null/absent
|
// Build query to find objects where accountRef matches OR is null/absent
|
||||||
query := repository.WithoutOrg(accountRef)
|
query := repository.WithoutOrg(accountRef)
|
||||||
@@ -357,7 +357,7 @@ func (db *AccountBoundDBImp[T]) DeleteByAccountRef(ctx context.Context, accountR
|
|||||||
// Get all candidate objects for individual permission checking
|
// Get all candidate objects for individual permission checking
|
||||||
allObjects, err := db.DBImp.Repository.ListAccountBound(ctx, query)
|
allObjects, err := db.DBImp.Repository.ListAccountBound(ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to list objects for delete by account ref", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to list objects for delete by account ref", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,18 +375,18 @@ func (db *AccountBoundDBImp[T]) DeleteByAccountRef(ctx context.Context, accountR
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(allowedIDs) == 0 {
|
if len(allowedIDs) == 0 {
|
||||||
db.Logger.Debug("No objects allowed for deletion by account ref", mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Debug("No objects allowed for deletion by account ref", mzap.AccRef(accountRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete only the allowed objects
|
// Delete only the allowed objects
|
||||||
allowedQuery := query.And(repository.Query().In(repository.IDField(), allowedIDs))
|
allowedQuery := query.And(repository.Query().In(repository.IDField(), allowedIDs))
|
||||||
if err := db.DBImp.DeleteMany(ctx, allowedQuery); err != nil {
|
if err := db.DBImp.DeleteMany(ctx, allowedQuery); err != nil {
|
||||||
db.Logger.Warn("Failed to delete objects by account ref", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
db.Logger.Warn("Failed to delete objects by account ref", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Logger.Debug("Successfully deleted objects by account ref", mzap.ObjRef("account_ref", accountRef), zap.Int("count", len(allowedIDs)))
|
db.Logger.Debug("Successfully deleted objects by account ref", mzap.AccRef(accountRef), zap.Int("count", len(allowedIDs)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ func enforceObject[T model.PermissionBoundStorable](ctx context.Context, db *tem
|
|||||||
l, err := db.ListPermissionBound(ctx, query)
|
l, err := db.ListPermissionBound(ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Error occured while checking access rights", zap.Error(err),
|
db.Logger.Warn("Error occured while checking access rights", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), zap.String("action", string(action)))
|
mzap.AccRef(accountRef), zap.String("action", string(action)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(l) == 0 {
|
if len(l) == 0 {
|
||||||
db.Logger.Debug("Access denied", mzap.ObjRef("account_ref", accountRef), zap.String("action", string(action)))
|
db.Logger.Debug("Access denied", mzap.AccRef(accountRef), zap.String("action", string(action)))
|
||||||
return merrors.AccessDenied(db.Repository.Collection(), string(action), bson.NilObjectID)
|
return merrors.AccessDenied(db.Repository.Collection(), string(action), bson.NilObjectID)
|
||||||
}
|
}
|
||||||
for _, item := range l {
|
for _, item := range l {
|
||||||
@@ -34,11 +34,11 @@ func enforceObject[T model.PermissionBoundStorable](ctx context.Context, db *tem
|
|||||||
res, err := enforcer.EnforceBatch(ctx, l, accountRef, action)
|
res, err := enforcer.EnforceBatch(ctx, l, accountRef, action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to enforce permission", zap.Error(err),
|
db.Logger.Warn("Failed to enforce permission", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), zap.String("action", string(action)))
|
mzap.AccRef(accountRef), zap.String("action", string(action)))
|
||||||
}
|
}
|
||||||
for objectRef, hasPermission := range res {
|
for objectRef, hasPermission := range res {
|
||||||
if !hasPermission {
|
if !hasPermission {
|
||||||
db.Logger.Info("Permission denied for object during reordering", mzap.ObjRef("account_ref", accountRef),
|
db.Logger.Info("Permission denied for object during reordering", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(model.ActionUpdate)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(model.ActionUpdate)))
|
||||||
return merrors.AccessDenied(db.Repository.Collection(), string(action), objectRef)
|
return merrors.AccessDenied(db.Repository.Collection(), string(action), objectRef)
|
||||||
}
|
}
|
||||||
@@ -50,11 +50,11 @@ func enforceObjectByRef[T model.PermissionBoundStorable](ctx context.Context, db
|
|||||||
err := enforceObject(ctx, db, enforcer, action, accountRef, repository.IDFilter(objectRef))
|
err := enforceObject(ctx, db, enforcer, action, accountRef, repository.IDFilter(objectRef))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, merrors.ErrAccessDenied) {
|
if errors.Is(err, merrors.ErrAccessDenied) {
|
||||||
db.Logger.Debug("Access denied", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
db.Logger.Debug("Access denied", mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
||||||
return merrors.AccessDenied(db.Repository.Collection(), string(action), objectRef)
|
return merrors.AccessDenied(db.Repository.Collection(), string(action), objectRef)
|
||||||
} else {
|
} else {
|
||||||
db.Logger.Warn("Error occurred while checking permissions", zap.Error(err),
|
db.Logger.Warn("Error occurred while checking permissions", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef
|
|||||||
obj := db.createEmpty()
|
obj := db.createEmpty()
|
||||||
if err := db.repo.Get(ctx, objectRef, obj); err != nil {
|
if err := db.repo.Get(ctx, objectRef, obj); err != nil {
|
||||||
db.logger.Warn("Failed to get object for reordering", zap.Error(err), zap.Int("new_index", newIndex),
|
db.logger.Warn("Failed to get object for reordering", zap.Error(err), zap.Int("new_index", newIndex),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef
|
|||||||
indexable := db.getIndexable(obj)
|
indexable := db.getIndexable(obj)
|
||||||
currentIndex := indexable.Index
|
currentIndex := indexable.Index
|
||||||
if currentIndex == newIndex {
|
if currentIndex == newIndex {
|
||||||
db.logger.Debug("No reordering needed - same index", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("No reordering needed - same index", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
mzap.ObjRef("object_ref", objectRef), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
||||||
return nil // No change needed
|
return nil // No change needed
|
||||||
}
|
}
|
||||||
@@ -72,13 +72,13 @@ func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef
|
|||||||
objects, err := db.repo.ListPermissionBound(ctx, reorderFilter)
|
objects, err := db.repo.ListPermissionBound(ctx, reorderFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Warn("Failed to get affected objects for reordering (moving down)",
|
db.logger.Warn("Failed to get affected objects for reordering (moving down)",
|
||||||
zap.Error(err), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef),
|
zap.Error(err), mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef),
|
||||||
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
affectedObjects = append(affectedObjects, objects...)
|
affectedObjects = append(affectedObjects, objects...)
|
||||||
db.logger.Debug("Found affected objects for moving down",
|
db.logger.Debug("Found affected objects for moving down",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(objects)))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(objects)))
|
||||||
} else {
|
} else {
|
||||||
// Moving up: items between newIndex and currentIndex-1 will be shifted down by +1
|
// Moving up: items between newIndex and currentIndex-1 will be shifted down by +1
|
||||||
reorderFilter := filter.
|
reorderFilter := filter.
|
||||||
@@ -89,12 +89,12 @@ func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef
|
|||||||
objects, err := db.repo.ListPermissionBound(ctx, reorderFilter)
|
objects, err := db.repo.ListPermissionBound(ctx, reorderFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Warn("Failed to get affected objects for reordering (moving up)", zap.Error(err),
|
db.logger.Warn("Failed to get affected objects for reordering (moving up)", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef),
|
||||||
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
affectedObjects = append(affectedObjects, objects...)
|
affectedObjects = append(affectedObjects, objects...)
|
||||||
db.logger.Debug("Found affected objects for moving up", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("Found affected objects for moving up", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(objects)))
|
mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(objects)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef
|
|||||||
targetObjects, err := db.repo.ListPermissionBound(ctx, repository.IDFilter(objectRef))
|
targetObjects, err := db.repo.ListPermissionBound(ctx, repository.IDFilter(objectRef))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Warn("Failed to get target object for permission checking", zap.Error(err),
|
db.logger.Warn("Failed to get target object for permission checking", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(targetObjects) > 0 {
|
if len(targetObjects) > 0 {
|
||||||
@@ -110,27 +110,27 @@ func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions for all affected objects using EnforceBatch
|
// Check permissions for all affected objects using EnforceBatch
|
||||||
db.logger.Debug("Checking permissions for reordering", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("Checking permissions for reordering", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(affectedObjects)),
|
mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(affectedObjects)),
|
||||||
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
||||||
|
|
||||||
permissions, err := db.enforcer.EnforceBatch(ctx, affectedObjects, accountRef, model.ActionUpdate)
|
permissions, err := db.enforcer.EnforceBatch(ctx, affectedObjects, accountRef, model.ActionUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Warn("Failed to check permissions for reordering", zap.Error(err),
|
db.logger.Warn("Failed to check permissions for reordering", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(affectedObjects)))
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(affectedObjects)))
|
||||||
return merrors.Internal("failed to check permissions for reordering")
|
return merrors.Internal("failed to check permissions for reordering")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify all objects have update permission
|
// Verify all objects have update permission
|
||||||
for resObjectRef, hasPermission := range permissions {
|
for resObjectRef, hasPermission := range permissions {
|
||||||
if !hasPermission {
|
if !hasPermission {
|
||||||
db.logger.Info("Permission denied for object during reordering", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Info("Permission denied for object during reordering", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(model.ActionUpdate)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(model.ActionUpdate)))
|
||||||
return merrors.AccessDenied(db.repo.Collection(), string(model.ActionUpdate), resObjectRef)
|
return merrors.AccessDenied(db.repo.Collection(), string(model.ActionUpdate), resObjectRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db.logger.Debug("All permissions granted, proceeding with reordering", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("All permissions granted, proceeding with reordering", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Int("permission_count", len(permissions)))
|
mzap.ObjRef("object_ref", objectRef), zap.Int("permission_count", len(permissions)))
|
||||||
|
|
||||||
// All permissions checked, proceed with reordering
|
// All permissions checked, proceed with reordering
|
||||||
@@ -144,11 +144,11 @@ func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef
|
|||||||
updatedCount, err := db.repo.PatchMany(ctx, reorderFilter, patch)
|
updatedCount, err := db.repo.PatchMany(ctx, reorderFilter, patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Warn("Failed to shift objects during reordering (moving down)", zap.Error(err),
|
db.logger.Warn("Failed to shift objects during reordering (moving down)", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef),
|
||||||
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex), zap.Int("updated_count", updatedCount))
|
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex), zap.Int("updated_count", updatedCount))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
db.logger.Debug("Successfully shifted objects (moving down)", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("Successfully shifted objects (moving down)", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Int("updated_count", updatedCount))
|
mzap.ObjRef("object_ref", objectRef), zap.Int("updated_count", updatedCount))
|
||||||
} else {
|
} else {
|
||||||
// Moving up: shift items between newIndex and currentIndex-1 down by +1
|
// Moving up: shift items between newIndex and currentIndex-1 down by +1
|
||||||
@@ -160,23 +160,23 @@ func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef
|
|||||||
updatedCount, err := db.repo.PatchMany(ctx, reorderFilter, patch)
|
updatedCount, err := db.repo.PatchMany(ctx, reorderFilter, patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.logger.Warn("Failed to shift objects during reordering (moving up)", zap.Error(err),
|
db.logger.Warn("Failed to shift objects during reordering (moving up)", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef),
|
||||||
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex), zap.Int("updated_count", updatedCount))
|
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex), zap.Int("updated_count", updatedCount))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
db.logger.Debug("Successfully shifted objects (moving up)", mzap.ObjRef("account_ref", accountRef),
|
db.logger.Debug("Successfully shifted objects (moving up)", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Int("updated_count", updatedCount))
|
mzap.ObjRef("object_ref", objectRef), zap.Int("updated_count", updatedCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the target object to new index
|
// Update the target object to new index
|
||||||
if err := db.repo.Patch(ctx, objectRef, repository.Patch().Set(repository.IndexField(), newIndex)); err != nil {
|
if err := db.repo.Patch(ctx, objectRef, repository.Patch().Set(repository.IndexField(), newIndex)); err != nil {
|
||||||
db.logger.Warn("Failed to update target object index", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
db.logger.Warn("Failed to update target object index", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
mzap.ObjRef("object_ref", objectRef), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.logger.Debug("Successfully reordered object with permission checking",
|
db.logger.Debug("Successfully reordered object with permission checking",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("old_index", currentIndex),
|
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("old_index", currentIndex),
|
||||||
zap.Int("new_index", newIndex), zap.Int("affected_count", len(affectedObjects)))
|
zap.Int("new_index", newIndex), zap.Int("affected_count", len(affectedObjects)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ func (c *CasbinEnforcer) EnforceBatch(
|
|||||||
ok, err := c.Enforce(ctx, desc.GetPermissionRef(), accountRef, desc.GetOrganizationRef(), *desc.GetID(), action)
|
ok, err := c.Enforce(ctx, desc.GetPermissionRef(), accountRef, desc.GetOrganizationRef(), *desc.GetID(), action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn("Failed to enforce", zap.Error(err), mzap.ObjRef("permission_ref", desc.GetPermissionRef()),
|
c.logger.Warn("Failed to enforce", zap.Error(err), mzap.ObjRef("permission_ref", desc.GetPermissionRef()),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", desc.GetOrganizationRef()),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", desc.GetOrganizationRef()),
|
||||||
mzap.ObjRef("object_ref", *desc.GetID()), zap.String("action", string(action)))
|
mzap.ObjRef("object_ref", *desc.GetID()), zap.String("action", string(action)))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ func (c *CasbinEnforcer) GetRoles(ctx context.Context, accountRef, orgRef bson.O
|
|||||||
|
|
||||||
// GetPermissions retrieves all effective policies for the user within the domain.
|
// GetPermissions retrieves all effective policies for the user within the domain.
|
||||||
func (c *CasbinEnforcer) GetPermissions(ctx context.Context, accountRef, orgRef bson.ObjectID) ([]model.Role, []model.Permission, error) {
|
func (c *CasbinEnforcer) GetPermissions(ctx context.Context, accountRef, orgRef bson.ObjectID) ([]model.Role, []model.Permission, error) {
|
||||||
c.logger.Debug("Fetching policies for user", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", orgRef))
|
c.logger.Debug("Fetching policies for user", mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", orgRef))
|
||||||
|
|
||||||
// Step 1: Retrieve all roles assigned to the user within the domain
|
// Step 1: Retrieve all roles assigned to the user within the domain
|
||||||
roles, err := c.GetRoles(ctx, accountRef, orgRef)
|
roles, err := c.GetRoles(ctx, accountRef, orgRef)
|
||||||
|
|||||||
@@ -182,14 +182,14 @@ func (rm *RoleManager) Revoke(ctx context.Context, roleRef, accountRef, orgRef b
|
|||||||
// logPolicyResult logs results for Assign and Revoke.
|
// logPolicyResult logs results for Assign and Revoke.
|
||||||
func (rm *RoleManager) logPolicyResult(action string, result bool, err error, roleRef, accountRef, orgRef bson.ObjectID) error {
|
func (rm *RoleManager) logPolicyResult(action string, result bool, err error, roleRef, accountRef, orgRef bson.ObjectID) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rm.logger.Warn("Failed to "+action+" role", zap.Error(err), mzap.ObjRef("role_ref", roleRef), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", orgRef))
|
rm.logger.Warn("Failed to "+action+" role", zap.Error(err), mzap.ObjRef("role_ref", roleRef), mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", orgRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
msg := "Role " + action + "ed successfully"
|
msg := "Role " + action + "ed successfully"
|
||||||
if !result {
|
if !result {
|
||||||
msg = "Role already " + action + "ed"
|
msg = "Role already " + action + "ed"
|
||||||
}
|
}
|
||||||
rm.logger.Info(msg, mzap.ObjRef("role_ref", roleRef), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", orgRef))
|
rm.logger.Info(msg, mzap.ObjRef("role_ref", roleRef), mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", orgRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,24 +48,24 @@ func (n *Enforcer) Enforce(
|
|||||||
action model.Action,
|
action model.Action,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
if organizationRef.IsZero() {
|
if organizationRef.IsZero() {
|
||||||
n.logger.Warn("Missing organization context", mzap.ObjRef("account_ref", accountRef),
|
n.logger.Warn("Missing organization context", mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
||||||
mzap.ObjRef("object", objectRef), zap.String("action", string(action)))
|
mzap.ObjRef("object", objectRef), zap.String("action", string(action)))
|
||||||
return false, merrors.InvalidArgument("organization context missing", "organizationRef")
|
return false, merrors.InvalidArgument("organization context missing", "organizationRef")
|
||||||
}
|
}
|
||||||
roleAssignments, err := n.rdb.Roles(ctx, accountRef, organizationRef)
|
roleAssignments, err := n.rdb.Roles(ctx, accountRef, organizationRef)
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
n.logger.Debug("No roles defined for account", mzap.ObjRef("account_ref", accountRef))
|
n.logger.Debug("No roles defined for account", mzap.AccRef(accountRef))
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.logger.Warn("Failed to fetch roles while checking permissions", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
n.logger.Warn("Failed to fetch roles while checking permissions", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
||||||
mzap.ObjRef("object", objectRef), zap.String("action", string(action)))
|
mzap.ObjRef("object", objectRef), zap.String("action", string(action)))
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if len(roleAssignments) == 0 {
|
if len(roleAssignments) == 0 {
|
||||||
n.logger.Warn("No roles found for account", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
n.logger.Warn("No roles found for account", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
||||||
return false, merrors.Internal("No roles found for account " + accountRef.Hex())
|
return false, merrors.Internal("No roles found for account " + accountRef.Hex())
|
||||||
@@ -75,7 +75,7 @@ func (n *Enforcer) Enforce(
|
|||||||
for _, roleAssignment := range roleAssignments {
|
for _, roleAssignment := range roleAssignments {
|
||||||
policies, err := n.pdb.PoliciesForPermissionAction(ctx, roleAssignment.DescriptionRef, permissionRef, action)
|
policies, err := n.pdb.PoliciesForPermissionAction(ctx, roleAssignment.DescriptionRef, permissionRef, action)
|
||||||
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||||
n.logger.Warn("Failed to fetch permissions", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
n.logger.Warn("Failed to fetch permissions", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(action)))
|
||||||
return false, err
|
return false, err
|
||||||
@@ -137,7 +137,7 @@ func (n *Enforcer) EnforceBatch(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
n.logger.Debug("No roles defined for account", zap.Error(err),
|
n.logger.Debug("No roles defined for account", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
// With no roles, mark all objects in this venue as denied.
|
// With no roles, mark all objects in this venue as denied.
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
results[*obj.GetID()] = false
|
results[*obj.GetID()] = false
|
||||||
@@ -146,7 +146,7 @@ func (n *Enforcer) EnforceBatch(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
n.logger.Warn("Failed to fetch roles", zap.Error(err),
|
n.logger.Warn("Failed to fetch roles", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,14 +198,14 @@ func (n *Enforcer) EnforceBatch(
|
|||||||
|
|
||||||
// GetRoles retrieves all roles assigned to the user within the domain.
|
// GetRoles retrieves all roles assigned to the user within the domain.
|
||||||
func (n *Enforcer) GetRoles(ctx context.Context, accountRef, organizationRef bson.ObjectID) ([]model.Role, error) {
|
func (n *Enforcer) GetRoles(ctx context.Context, accountRef, organizationRef bson.ObjectID) ([]model.Role, error) {
|
||||||
n.logger.Debug("Fetching roles for user", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
n.logger.Debug("Fetching roles for user", mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
ra, err := n.rdb.Roles(ctx, accountRef, organizationRef)
|
ra, err := n.rdb.Roles(ctx, accountRef, organizationRef)
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
n.logger.Debug("No roles assigned to user", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
n.logger.Debug("No roles assigned to user", mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
return []model.Role{}, nil
|
return []model.Role{}, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.logger.Warn("Failed to fetch roles", zap.Error(err), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
n.logger.Warn("Failed to fetch roles", zap.Error(err), mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ func (n *Enforcer) Reload() error {
|
|||||||
|
|
||||||
// GetPermissions retrieves all effective policies for the user within the domain.
|
// GetPermissions retrieves all effective policies for the user within the domain.
|
||||||
func (n *Enforcer) GetPermissions(ctx context.Context, accountRef, organizationRef bson.ObjectID) ([]model.Role, []model.Permission, error) {
|
func (n *Enforcer) GetPermissions(ctx context.Context, accountRef, organizationRef bson.ObjectID) ([]model.Role, []model.Permission, error) {
|
||||||
n.logger.Debug("Fetching policies for user", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
n.logger.Debug("Fetching policies for user", mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
|
|
||||||
roles, err := n.GetRoles(ctx, accountRef, organizationRef)
|
roles, err := n.GetRoles(ctx, accountRef, organizationRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -117,14 +117,14 @@ func (rm *RoleManager) Revoke(ctx context.Context, roleRef, accountRef, organiza
|
|||||||
// logPolicyResult logs results for Assign and Revoke.
|
// logPolicyResult logs results for Assign and Revoke.
|
||||||
func (rm *RoleManager) logPolicyResult(action string, result bool, err error, roleRef, accountRef, organizationRef bson.ObjectID) error {
|
func (rm *RoleManager) logPolicyResult(action string, result bool, err error, roleRef, accountRef, organizationRef bson.ObjectID) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rm.logger.Warn("Failed to "+action+" role", zap.Error(err), mzap.ObjRef("role_ref", roleRef), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
rm.logger.Warn("Failed to "+action+" role", zap.Error(err), mzap.ObjRef("role_ref", roleRef), mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
msg := "Role " + action + "ed successfully"
|
msg := "Role " + action + "ed successfully"
|
||||||
if !result {
|
if !result {
|
||||||
msg = "Role already " + action + "ed"
|
msg = "Role already " + action + "ed"
|
||||||
}
|
}
|
||||||
rm.logger.Info(msg, mzap.ObjRef("role_ref", roleRef), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
rm.logger.Info(msg, mzap.ObjRef("role_ref", roleRef), mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package confirmation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/db/template"
|
|
||||||
"github.com/tech/sendico/pkg/model"
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DB interface {
|
|
||||||
template.DB[*model.ConfirmationCode]
|
|
||||||
|
|
||||||
FindActive(ctx context.Context, accountRef bson.ObjectID, destination string, target model.ConfirmationTarget, now int64) (*model.ConfirmationCode, error)
|
|
||||||
DeleteTuple(ctx context.Context, accountRef bson.ObjectID, destination string, target model.ConfirmationTarget) error
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"github.com/tech/sendico/pkg/auth"
|
"github.com/tech/sendico/pkg/auth"
|
||||||
"github.com/tech/sendico/pkg/db/account"
|
"github.com/tech/sendico/pkg/db/account"
|
||||||
"github.com/tech/sendico/pkg/db/chainassets"
|
"github.com/tech/sendico/pkg/db/chainassets"
|
||||||
"github.com/tech/sendico/pkg/db/confirmation"
|
|
||||||
mongoimpl "github.com/tech/sendico/pkg/db/internal/mongo"
|
mongoimpl "github.com/tech/sendico/pkg/db/internal/mongo"
|
||||||
"github.com/tech/sendico/pkg/db/invitation"
|
"github.com/tech/sendico/pkg/db/invitation"
|
||||||
"github.com/tech/sendico/pkg/db/organization"
|
"github.com/tech/sendico/pkg/db/organization"
|
||||||
@@ -22,7 +21,6 @@ import (
|
|||||||
// Factory exposes high-level repositories used by application services.
|
// Factory exposes high-level repositories used by application services.
|
||||||
type Factory interface {
|
type Factory interface {
|
||||||
NewRefreshTokensDB() (refreshtokens.DB, error)
|
NewRefreshTokensDB() (refreshtokens.DB, error)
|
||||||
NewConfirmationsDB() (confirmation.DB, error)
|
|
||||||
|
|
||||||
NewChainAsstesDB() (chainassets.DB, error)
|
NewChainAsstesDB() (chainassets.DB, error)
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
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/v2/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
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package confirmationdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/db/repository"
|
|
||||||
"github.com/tech/sendico/pkg/model"
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (db *ConfirmationDB) DeleteTuple(ctx context.Context, accountRef bson.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)
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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/v2/bson"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (db *ConfirmationDB) FindActive(ctx context.Context, accountRef bson.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
|
|
||||||
}
|
|
||||||
@@ -11,10 +11,8 @@ import (
|
|||||||
"github.com/tech/sendico/pkg/auth"
|
"github.com/tech/sendico/pkg/auth"
|
||||||
"github.com/tech/sendico/pkg/db/account"
|
"github.com/tech/sendico/pkg/db/account"
|
||||||
"github.com/tech/sendico/pkg/db/chainassets"
|
"github.com/tech/sendico/pkg/db/chainassets"
|
||||||
"github.com/tech/sendico/pkg/db/confirmation"
|
|
||||||
"github.com/tech/sendico/pkg/db/internal/mongo/accountdb"
|
"github.com/tech/sendico/pkg/db/internal/mongo/accountdb"
|
||||||
"github.com/tech/sendico/pkg/db/internal/mongo/chainassetsdb"
|
"github.com/tech/sendico/pkg/db/internal/mongo/chainassetsdb"
|
||||||
"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/invitationdb"
|
||||||
"github.com/tech/sendico/pkg/db/internal/mongo/organizationdb"
|
"github.com/tech/sendico/pkg/db/internal/mongo/organizationdb"
|
||||||
"github.com/tech/sendico/pkg/db/internal/mongo/paymethoddb"
|
"github.com/tech/sendico/pkg/db/internal/mongo/paymethoddb"
|
||||||
@@ -188,10 +186,6 @@ func (db *DB) NewAccountDB() (account.DB, error) {
|
|||||||
return accountdb.Create(db.logger, db.db())
|
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) {
|
func (db *DB) NewOrganizationDB() (organization.DB, error) {
|
||||||
pdb, err := db.NewPoliciesDB()
|
pdb, err := db.NewPoliciesDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func (db *PaymentMethodsDB) SetArchived(ctx context.Context, accountRef, organiz
|
|||||||
// Use the ArchivableDB for the main archiving logic
|
// Use the ArchivableDB for the main archiving logic
|
||||||
if err := db.ArchivableDB.SetArchived(ctx, accountRef, objectRef, isArchived); err != nil {
|
if err := db.ArchivableDB.SetArchived(ctx, accountRef, objectRef, isArchived); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to chnage object archive status", zap.Error(err),
|
db.DBImp.Logger.Warn("Failed to chnage object archive status", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
||||||
mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", isArchived), zap.Bool("cascade", cascade))
|
mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", isArchived), zap.Bool("cascade", cascade))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func (db *RecipientDB) SetArchived(ctx context.Context, accountRef, organization
|
|||||||
// Use the ArchivableDB for the main archiving logic
|
// Use the ArchivableDB for the main archiving logic
|
||||||
if err := db.ArchivableDB.SetArchived(ctx, accountRef, objectRef, isArchived); err != nil {
|
if err := db.ArchivableDB.SetArchived(ctx, accountRef, objectRef, isArchived); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to change recipient archive status", zap.Error(err),
|
db.DBImp.Logger.Warn("Failed to change recipient archive status", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
||||||
mzap.ObjRef("recipient_ref", objectRef), zap.Bool("archived", isArchived), zap.Bool("cascade", cascade))
|
mzap.ObjRef("recipient_ref", objectRef), zap.Bool("archived", isArchived), zap.Bool("cascade", cascade))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ func (db *RecipientDB) SetArchived(ctx context.Context, accountRef, organization
|
|||||||
if cascade {
|
if cascade {
|
||||||
if err := db.setArchivedPaymentMethods(ctx, accountRef, organizationRef, objectRef, isArchived); err != nil {
|
if err := db.setArchivedPaymentMethods(ctx, accountRef, organizationRef, objectRef, isArchived); err != nil {
|
||||||
db.DBImp.Logger.Warn("Failed to update payment methods archive status", zap.Error(err),
|
db.DBImp.Logger.Warn("Failed to update payment methods archive status", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef),
|
||||||
mzap.ObjRef("recipient_ref", objectRef), zap.Bool("archived", isArchived), zap.Bool("cascade", cascade))
|
mzap.ObjRef("recipient_ref", objectRef), zap.Bool("archived", isArchived), zap.Bool("cascade", cascade))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func (db *RefreshTokenDB) Revoke(ctx context.Context, accountRef bson.ObjectID,
|
|||||||
if err := db.Repository.FindOneByFilter(ctx, f, &rt); err != nil {
|
if err := db.Repository.FindOneByFilter(ctx, f, &rt); err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
db.Logger.Warn("Failed to find refresh token", zap.Error(err),
|
db.Logger.Warn("Failed to find refresh token", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), zap.String("client_id", session.ClientID), zap.String("device_id", session.DeviceID))
|
mzap.AccRef(accountRef), zap.String("client_id", session.ClientID), zap.String("device_id", session.DeviceID))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -6,68 +6,146 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/db/repository"
|
"github.com/tech/sendico/pkg/db/repository"
|
||||||
|
"github.com/tech/sendico/pkg/db/repository/builder"
|
||||||
"github.com/tech/sendico/pkg/db/verification"
|
"github.com/tech/sendico/pkg/db/verification"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
mutil "github.com/tech/sendico/pkg/mutil/db"
|
||||||
|
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *verificationDB) Consume(
|
func (db *verificationDB) Consume(
|
||||||
ct context.Context,
|
ct context.Context,
|
||||||
|
accountRef bson.ObjectID,
|
||||||
|
purpose model.VerificationPurpose,
|
||||||
rawToken string,
|
rawToken string,
|
||||||
) (*model.VerificationToken, error) {
|
) (*model.VerificationToken, error) {
|
||||||
|
|
||||||
hash := tokenHash(rawToken)
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
// 1) Find token by hash (do NOT filter by usedAt/expiresAt here),
|
|
||||||
// otherwise you can't distinguish "used/expired" from "not found".
|
|
||||||
filter := repository.Query().And(
|
|
||||||
repository.Filter("verifyTokenHash", hash),
|
|
||||||
)
|
|
||||||
|
|
||||||
t, e := db.tf.CreateTransaction().Execute(
|
t, e := db.tf.CreateTransaction().Execute(
|
||||||
ct,
|
ct,
|
||||||
func(ctx context.Context) (any, error) {
|
func(ctx context.Context) (any, error) {
|
||||||
var existing model.VerificationToken
|
|
||||||
if err := db.DBImp.FindOne(ctx, filter, &existing); err != nil {
|
// 1) Load active tokens for this context
|
||||||
|
activeFilter := repository.Query().And(
|
||||||
|
repository.Filter("accountRef", accountRef),
|
||||||
|
repository.Filter("purpose", purpose),
|
||||||
|
repository.Filter("usedAt", nil),
|
||||||
|
repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now),
|
||||||
|
)
|
||||||
|
|
||||||
|
tokens, err := mutil.GetObjects[model.VerificationToken](
|
||||||
|
ctx, db.Logger, activeFilter, nil, db.DBImp.Repository,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
db.Logger.Debug("Token hash not found", zap.Error(err), zap.String("hash", hash))
|
db.Logger.Debug("No tokens found", zap.Error(err), mzap.AccRef(accountRef), zap.String("purpose", string(purpose)))
|
||||||
return nil, verification.ErorrTokenNotFound()
|
return nil, verification.ErorrTokenNotFound()
|
||||||
}
|
}
|
||||||
db.Logger.Warn("Failed to check token", zap.Error(err), zap.String("hash", hash))
|
db.Logger.Warn("Failed to load active tokens", zap.Error(err), mzap.AccRef(accountRef), zap.String("purpose", string(purpose)))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Semantic checks
|
if len(tokens) == 0 {
|
||||||
if existing.UsedAt != nil {
|
db.Logger.Debug("No tokens found", zap.Error(err), mzap.AccRef(accountRef), zap.String("purpose", string(purpose)))
|
||||||
db.Logger.Debug(
|
return nil, verification.ErorrTokenNotFound()
|
||||||
"Token has already been used",
|
}
|
||||||
zap.String("hash", hash),
|
|
||||||
zap.Time("used_at", *existing.UsedAt),
|
// 2) Find matching token via hasher (OTP or Magic — doesn't matter)
|
||||||
)
|
var token *model.VerificationToken
|
||||||
|
|
||||||
|
for i := range tokens {
|
||||||
|
t := &tokens[i]
|
||||||
|
hash := hasherFor(t).Hash(rawToken, t)
|
||||||
|
|
||||||
|
if hash == t.VerifyTokenHash {
|
||||||
|
token = t
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if token == nil {
|
||||||
|
// wrong code/token → increment attempts
|
||||||
|
for _, t := range tokens {
|
||||||
|
_, _ = db.DBImp.PatchMany(
|
||||||
|
ctx,
|
||||||
|
repository.IDFilter(t.ID),
|
||||||
|
repository.Patch().Inc(repository.Field("attempts"), 1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil, verification.ErorrTokenNotFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Static checks
|
||||||
|
if token.UsedAt != nil {
|
||||||
return nil, verification.ErorrTokenAlreadyUsed()
|
return nil, verification.ErorrTokenAlreadyUsed()
|
||||||
}
|
}
|
||||||
|
if !token.ExpiresAt.After(now) {
|
||||||
if !existing.ExpiresAt.After(now) { // includes equal time edge-case
|
|
||||||
db.Logger.Debug(
|
|
||||||
"Token has already expired",
|
|
||||||
zap.String("hash", hash),
|
|
||||||
zap.Time("expired_at", existing.ExpiresAt),
|
|
||||||
)
|
|
||||||
return nil, verification.ErorrTokenExpired()
|
return nil, verification.ErorrTokenExpired()
|
||||||
}
|
}
|
||||||
|
if token.MaxRetries != nil && token.Attempts >= *token.MaxRetries {
|
||||||
|
return nil, verification.ErrorTokenAttemptsExceeded()
|
||||||
|
}
|
||||||
|
|
||||||
// 3) Mark as used
|
// 4) Atomic consume
|
||||||
existing.UsedAt = &now
|
consumeFilter := repository.Query().And(
|
||||||
if err := db.DBImp.Update(ctx, &existing); err != nil {
|
repository.IDFilter(token.ID),
|
||||||
db.Logger.Warn("Failed to consume token", zap.Error(err), zap.String("hash", hash))
|
repository.Filter("accountRef", accountRef),
|
||||||
|
repository.Filter("purpose", purpose),
|
||||||
|
repository.Filter("usedAt", nil),
|
||||||
|
repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now),
|
||||||
|
)
|
||||||
|
|
||||||
|
if token.MaxRetries != nil {
|
||||||
|
consumeFilter = consumeFilter.And(
|
||||||
|
repository.Query().Comparison(repository.Field("attempts"), builder.Lt, *token.MaxRetries),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := db.DBImp.PatchMany(
|
||||||
|
ctx,
|
||||||
|
consumeFilter,
|
||||||
|
repository.Patch().Set(repository.Field("usedAt"), now),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &existing, nil
|
if updated == 1 {
|
||||||
|
token.UsedAt = &now
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) Consume failed → increment attempts
|
||||||
|
_, _ = db.DBImp.PatchMany(
|
||||||
|
ctx,
|
||||||
|
repository.IDFilter(token.ID),
|
||||||
|
repository.Patch().Inc(repository.Field("attempts"), 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 6) Re-check state
|
||||||
|
var fresh model.VerificationToken
|
||||||
|
if err := db.DBImp.FindOne(ctx, repository.IDFilter(token.ID), &fresh); err != nil {
|
||||||
|
return nil, merrors.Internal("failed to re-check token state")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fresh.UsedAt != nil {
|
||||||
|
return nil, verification.ErorrTokenAlreadyUsed()
|
||||||
|
}
|
||||||
|
if !fresh.ExpiresAt.After(now) {
|
||||||
|
return nil, verification.ErorrTokenExpired()
|
||||||
|
}
|
||||||
|
if fresh.MaxRetries != nil && fresh.Attempts >= *fresh.MaxRetries {
|
||||||
|
return nil, verification.ErrorTokenAttemptsExceeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, verification.ErorrTokenNotFound()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
@@ -76,6 +154,5 @@ func (db *verificationDB) Consume(
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, merrors.Internal("unexpected token type")
|
return nil, merrors.Internal("unexpected token type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,95 +2,224 @@ package verificationimp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"errors"
|
||||||
"encoding/base64"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/db/repository"
|
"github.com/tech/sendico/pkg/db/repository"
|
||||||
"github.com/tech/sendico/pkg/db/repository/builder"
|
"github.com/tech/sendico/pkg/db/repository/builder"
|
||||||
|
"github.com/tech/sendico/pkg/db/verification"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
"github.com/tech/sendico/pkg/mutil/mzap"
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const verificationTokenBytes = 32
|
func normalizedIdempotencyKey(value *string) (string, bool) {
|
||||||
|
if value == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(*value)
|
||||||
|
if key == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return key, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func idempotencyFilter(
|
||||||
|
request *verification.Request,
|
||||||
|
idempotencyKey string,
|
||||||
|
) builder.Query {
|
||||||
|
return repository.Query().And(
|
||||||
|
repository.Filter("accountRef", request.AccountRef),
|
||||||
|
repository.Filter("purpose", request.Purpose),
|
||||||
|
repository.Filter("target", request.Target),
|
||||||
|
repository.Filter("idempotencyKey", idempotencyKey),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashFilter(hash string) builder.Query {
|
||||||
|
return repository.Filter("verifyTokenHash", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func idempotencySeed(request *verification.Request, idempotencyKey string) string {
|
||||||
|
return strings.Join([]string{
|
||||||
|
request.AccountRef.Hex(),
|
||||||
|
string(request.Purpose),
|
||||||
|
request.Target,
|
||||||
|
request.Kind,
|
||||||
|
idempotencyKey,
|
||||||
|
}, "|")
|
||||||
|
}
|
||||||
|
|
||||||
func newVerificationToken(
|
func newVerificationToken(
|
||||||
accountRef bson.ObjectID,
|
request *verification.Request,
|
||||||
purpose model.VerificationPurpose,
|
idempotencyKey string,
|
||||||
target string,
|
hasIdempotency bool,
|
||||||
ttl time.Duration,
|
|
||||||
) (*model.VerificationToken, string, error) {
|
) (*model.VerificationToken, string, error) {
|
||||||
|
|
||||||
raw := make([]byte, verificationTokenBytes)
|
|
||||||
if _, err := rand.Read(raw); err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawToken := base64.RawURLEncoding.EncodeToString(raw)
|
|
||||||
|
|
||||||
hashStr := tokenHash(rawToken)
|
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
token := &model.VerificationToken{
|
var (
|
||||||
AccountRef: accountRef,
|
raw string
|
||||||
Purpose: purpose,
|
hash string
|
||||||
Target: target,
|
salt *string
|
||||||
VerifyTokenHash: hashStr,
|
err error
|
||||||
UsedAt: nil,
|
)
|
||||||
ExpiresAt: now.Add(ttl),
|
|
||||||
|
switch request.Kind {
|
||||||
|
case verification.TokenKindOTP:
|
||||||
|
if hasIdempotency {
|
||||||
|
var saltValue string
|
||||||
|
raw, saltValue, hash = generateDeterministicOTP(idempotencySeed(request, idempotencyKey))
|
||||||
|
salt = &saltValue
|
||||||
|
} else {
|
||||||
|
var s string
|
||||||
|
raw, s, hash, err = generateOTP()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
salt = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
default: // Magic token
|
||||||
|
if hasIdempotency {
|
||||||
|
raw, hash = generateDeterministicMagic(idempotencySeed(request, idempotencyKey))
|
||||||
|
} else {
|
||||||
|
raw, hash, err = generateMagic()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return token, rawToken, nil
|
token := &model.VerificationToken{
|
||||||
|
AccountRef: request.AccountRef,
|
||||||
|
Purpose: request.Purpose,
|
||||||
|
Target: request.Target,
|
||||||
|
IdempotencyKey: nil,
|
||||||
|
VerifyTokenHash: hash,
|
||||||
|
Salt: salt,
|
||||||
|
UsedAt: nil,
|
||||||
|
ExpiresAt: now.Add(request.Ttl),
|
||||||
|
MaxRetries: request.MaxRetries,
|
||||||
|
}
|
||||||
|
if hasIdempotency {
|
||||||
|
token.IdempotencyKey = &idempotencyKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *verificationDB) Create(
|
func (db *verificationDB) Create(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
accountRef bson.ObjectID,
|
request *verification.Request,
|
||||||
purpose model.VerificationPurpose,
|
|
||||||
target string,
|
|
||||||
ttl time.Duration,
|
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|
||||||
logFields := []zap.Field{
|
if request == nil {
|
||||||
zap.String("purpose", string(purpose)), zap.Duration("ttl", ttl),
|
return "", merrors.Internal("nil request")
|
||||||
mzap.AccRef(accountRef), zap.String("target", target),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
token, raw, err := newVerificationToken(accountRef, purpose, target, ttl)
|
idempotencyKey, hasIdempotency := normalizedIdempotencyKey(request.IdempotencyKey)
|
||||||
|
|
||||||
|
token, raw, err := newVerificationToken(request, idempotencyKey, hasIdempotency)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to generate verification token", append(logFields, zap.Error(err))...)
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate any active tokens for the same (accountRef, purpose, target).
|
_, err = db.tf.CreateTransaction().Execute(ctx, func(tx context.Context) (any, error) {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
invalidated, err := db.DBImp.PatchMany(ctx,
|
|
||||||
repository.Query().And(
|
baseFilter := repository.Query().And(
|
||||||
repository.Filter("accountRef", accountRef),
|
repository.Filter("accountRef", request.AccountRef),
|
||||||
repository.Filter("purpose", purpose),
|
repository.Filter("purpose", request.Purpose),
|
||||||
repository.Filter("target", target),
|
repository.Filter("target", request.Target),
|
||||||
repository.Filter("usedAt", nil),
|
repository.Filter("usedAt", nil),
|
||||||
repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now),
|
repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now),
|
||||||
),
|
)
|
||||||
repository.Patch().Set(repository.Field("usedAt"), now),
|
|
||||||
)
|
// Optional idempotency key support for safe retries.
|
||||||
|
if hasIdempotency {
|
||||||
|
var sameToken model.VerificationToken
|
||||||
|
err := db.DBImp.FindOne(tx, hashFilter(token.VerifyTokenHash), &sameToken)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
// Same hash means the same Create operation already succeeded.
|
||||||
|
return nil, nil
|
||||||
|
case errors.Is(err, merrors.ErrNoData):
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing model.VerificationToken
|
||||||
|
err = db.DBImp.FindOne(tx, idempotencyFilter(request, idempotencyKey), &existing)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
// Existing request with the same idempotency scope has already succeeded.
|
||||||
|
return nil, nil
|
||||||
|
case errors.Is(err, merrors.ErrNoData):
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Cooldown: if there exists ANY active token created after cutoff → block
|
||||||
|
if request.Cooldown != nil {
|
||||||
|
cutoff := now.Add(-*request.Cooldown)
|
||||||
|
|
||||||
|
cooldownFilter := baseFilter.And(
|
||||||
|
repository.Query().Comparison(repository.Field("createdAt"), builder.Gt, cutoff),
|
||||||
|
)
|
||||||
|
|
||||||
|
var recent model.VerificationToken
|
||||||
|
err := db.DBImp.FindOne(tx, cooldownFilter, &recent)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return nil, verification.ErrorCooldownActive()
|
||||||
|
case errors.Is(err, merrors.ErrNoData):
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Invalidate active tokens for this context
|
||||||
|
if _, err := db.DBImp.PatchMany(
|
||||||
|
tx,
|
||||||
|
baseFilter,
|
||||||
|
repository.Patch().Set(repository.Field("usedAt"), now),
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Create new token only after cooldown/idempotency checks pass.
|
||||||
|
if err := db.DBImp.Create(tx, token); err != nil {
|
||||||
|
if hasIdempotency && errors.Is(err, merrors.ErrDataConflict) {
|
||||||
|
var sameToken model.VerificationToken
|
||||||
|
findErr := db.DBImp.FindOne(tx, hashFilter(token.VerifyTokenHash), &sameToken)
|
||||||
|
switch {
|
||||||
|
case findErr == nil:
|
||||||
|
return nil, nil
|
||||||
|
case errors.Is(findErr, merrors.ErrNoData):
|
||||||
|
default:
|
||||||
|
return nil, findErr
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing model.VerificationToken
|
||||||
|
findErr = db.DBImp.FindOne(tx, idempotencyFilter(request, idempotencyKey), &existing)
|
||||||
|
switch {
|
||||||
|
case findErr == nil:
|
||||||
|
return nil, nil
|
||||||
|
case errors.Is(findErr, merrors.ErrNoData):
|
||||||
|
default:
|
||||||
|
return nil, findErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.Logger.Warn("Failed to invalidate previous tokens", append(logFields, zap.Error(err))...)
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if invalidated > 0 {
|
|
||||||
db.Logger.Debug("Invalidated previous tokens", append(logFields, zap.Int("count", invalidated))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.DBImp.Create(ctx, token); err != nil {
|
|
||||||
db.Logger.Warn("Failed to persist verification token", append(logFields, zap.Error(err))...)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
db.Logger.Debug("Verification token created", append(logFields, zap.String("hash", token.VerifyTokenHash))...)
|
|
||||||
|
|
||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,21 @@ func Create(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := p.Repository.CreateIndex(&ri.Definition{
|
||||||
|
Keys: []ri.Key{
|
||||||
|
{Field: "accountRef", Sort: ri.Asc},
|
||||||
|
{Field: "purpose", Sort: ri.Asc},
|
||||||
|
{Field: "target", Sort: ri.Asc},
|
||||||
|
{Field: "idempotencyKey", Sort: ri.Asc},
|
||||||
|
},
|
||||||
|
Unique: true,
|
||||||
|
Sparse: true,
|
||||||
|
Name: "uniq_verification_context_idempotency",
|
||||||
|
}); err != nil {
|
||||||
|
p.Logger.Error("Failed to create unique idempotency index on verification context", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
ttl := int32(2678400) // 30 days
|
ttl := int32(2678400) // 30 days
|
||||||
if err := p.Repository.CreateIndex(&ri.Definition{
|
if err := p.Repository.CreateIndex(&ri.Definition{
|
||||||
Keys: []ri.Key{{Field: "expiresAt", Sort: ri.Asc}},
|
Keys: []ri.Key{{Field: "expiresAt", Sort: ri.Asc}},
|
||||||
|
|||||||
@@ -1,10 +1,105 @@
|
|||||||
package verificationimp
|
package verificationimp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TokenHasher interface {
|
||||||
|
Hash(raw string, token *model.VerificationToken) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type magicHasher struct{}
|
||||||
|
|
||||||
|
func (magicHasher) Hash(raw string, _ *model.VerificationToken) string {
|
||||||
|
return tokenHash(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
type otpHasher struct{}
|
||||||
|
|
||||||
|
func (otpHasher) Hash(raw string, t *model.VerificationToken) string {
|
||||||
|
return otpHash(raw, *t.Salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasherFor(t *model.VerificationToken) TokenHasher {
|
||||||
|
if t.Salt != nil {
|
||||||
|
return otpHasher{}
|
||||||
|
}
|
||||||
|
return magicHasher{}
|
||||||
|
}
|
||||||
|
|
||||||
|
const verificationTokenBytes = 32
|
||||||
|
const otpDigits = 6
|
||||||
|
|
||||||
|
func generateMagic() (raw, hash string, err error) {
|
||||||
|
rawBytes := make([]byte, verificationTokenBytes)
|
||||||
|
if _, err = rand.Read(rawBytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raw = base64.RawURLEncoding.EncodeToString(rawBytes)
|
||||||
|
hash = tokenHash(raw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateDeterministicMagic(seed string) (raw, hash string) {
|
||||||
|
sum := sha256.Sum256([]byte("magic:" + seed))
|
||||||
|
raw = base64.RawURLEncoding.EncodeToString(sum[:])
|
||||||
|
hash = tokenHash(raw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateOTP() (code, salt, hash string, err error) {
|
||||||
|
// otpDigits-digit code
|
||||||
|
n := make([]byte, 4)
|
||||||
|
if _, err = rand.Read(n); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
num := int(n[0])<<24 | int(n[1])<<16 | int(n[2])<<8 | int(n[3])
|
||||||
|
|
||||||
|
mod := 1
|
||||||
|
for i := 0; i < otpDigits; i++ {
|
||||||
|
mod *= 10
|
||||||
|
}
|
||||||
|
|
||||||
|
code = fmt.Sprintf("%0*d", otpDigits, num%mod)
|
||||||
|
|
||||||
|
// per-token salt
|
||||||
|
saltBytes := make([]byte, 16)
|
||||||
|
if _, err = rand.Read(saltBytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
salt = base64.RawURLEncoding.EncodeToString(saltBytes)
|
||||||
|
|
||||||
|
hash = otpHash(code, salt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateDeterministicOTP(seed string) (code, salt, hash string) {
|
||||||
|
sum := sha256.Sum256([]byte("otp:" + seed))
|
||||||
|
|
||||||
|
num := int(sum[0])<<24 | int(sum[1])<<16 | int(sum[2])<<8 | int(sum[3])
|
||||||
|
mod := 1
|
||||||
|
for i := 0; i < otpDigits; i++ {
|
||||||
|
mod *= 10
|
||||||
|
}
|
||||||
|
code = fmt.Sprintf("%0*d", otpDigits, num%mod)
|
||||||
|
|
||||||
|
salt = base64.RawURLEncoding.EncodeToString(sum[4:20])
|
||||||
|
hash = otpHash(code, salt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We store only the resulting hash (+salt) in DB, never the OTP itself.
|
||||||
|
func otpHash(code, salt string) string {
|
||||||
|
sum := sha256.Sum256([]byte(salt + ":" + code))
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
|
||||||
func tokenHash(rawToken string) string {
|
func tokenHash(rawToken string) string {
|
||||||
hash := sha256.Sum256([]byte(rawToken))
|
hash := sha256.Sum256([]byte(rawToken))
|
||||||
return base64.RawURLEncoding.EncodeToString(hash[:])
|
return base64.RawURLEncoding.EncodeToString(hash[:])
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +29,10 @@ import (
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func newTestVerificationDB(t *testing.T) *verificationDB {
|
func newTestVerificationDB(t *testing.T) *verificationDB {
|
||||||
|
return newTestVerificationDBWithFactory(t, &passthroughTxFactory{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestVerificationDBWithFactory(t *testing.T, tf transaction.Factory) *verificationDB {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
repo := newMemoryTokenRepository()
|
repo := newMemoryTokenRepository()
|
||||||
logger := zap.NewNop()
|
logger := zap.NewNop()
|
||||||
@@ -36,7 +41,7 @@ func newTestVerificationDB(t *testing.T) *verificationDB {
|
|||||||
Logger: logger,
|
Logger: logger,
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
},
|
},
|
||||||
tf: &passthroughTxFactory{},
|
tf: tf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +56,20 @@ func (*passthroughTx) Execute(ctx context.Context, cb transaction.Callback) (any
|
|||||||
return cb(ctx)
|
return cb(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// retryingTxFactory simulates transaction callbacks being executed more than once.
|
||||||
|
type retryingTxFactory struct{}
|
||||||
|
|
||||||
|
func (*retryingTxFactory) CreateTransaction() transaction.Transaction { return &retryingTx{} }
|
||||||
|
|
||||||
|
type retryingTx struct{}
|
||||||
|
|
||||||
|
func (*retryingTx) Execute(ctx context.Context, cb transaction.Callback) (any, error) {
|
||||||
|
if _, err := cb(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cb(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// in-memory repository for VerificationToken
|
// in-memory repository for VerificationToken
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -156,8 +175,34 @@ func (m *memoryTokenRepository) InsertMany(ctx context.Context, objs []storable.
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (m *memoryTokenRepository) FindManyByFilter(context.Context, builder.Query, rd.DecodingFunc) error {
|
func (m *memoryTokenRepository) FindManyByFilter(_ context.Context, query builder.Query, decoder rd.DecodingFunc) error {
|
||||||
return merrors.NotImplemented("not needed")
|
m.mu.Lock()
|
||||||
|
var matches []interface{}
|
||||||
|
for _, id := range m.order {
|
||||||
|
tok := m.data[id]
|
||||||
|
if tok != nil && matchToken(query, tok) {
|
||||||
|
raw, err := bson.Marshal(cloneToken(tok))
|
||||||
|
if err != nil {
|
||||||
|
m.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
matches = append(matches, bson.Raw(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
cur, err := mongo.NewCursorFromDocuments(matches, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cur.Close(context.Background())
|
||||||
|
|
||||||
|
for cur.Next(context.Background()) {
|
||||||
|
if err := decoder(cur); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
func (m *memoryTokenRepository) Patch(context.Context, bson.ObjectID, builder.Patch) error {
|
func (m *memoryTokenRepository) Patch(context.Context, bson.ObjectID, builder.Patch) error {
|
||||||
return merrors.NotImplemented("not needed")
|
return merrors.NotImplemented("not needed")
|
||||||
@@ -190,8 +235,14 @@ func (m *memoryTokenRepository) Collection() string { return mservice.Verificati
|
|||||||
// tokenFieldValue returns the stored value for a given BSON field name.
|
// tokenFieldValue returns the stored value for a given BSON field name.
|
||||||
func tokenFieldValue(tok *model.VerificationToken, field string) any {
|
func tokenFieldValue(tok *model.VerificationToken, field string) any {
|
||||||
switch field {
|
switch field {
|
||||||
|
case "_id":
|
||||||
|
return tok.ID
|
||||||
|
case "createdAt":
|
||||||
|
return tok.CreatedAt
|
||||||
case "verifyTokenHash":
|
case "verifyTokenHash":
|
||||||
return tok.VerifyTokenHash
|
return tok.VerifyTokenHash
|
||||||
|
case "salt":
|
||||||
|
return tok.Salt
|
||||||
case "usedAt":
|
case "usedAt":
|
||||||
return tok.UsedAt
|
return tok.UsedAt
|
||||||
case "expiresAt":
|
case "expiresAt":
|
||||||
@@ -202,6 +253,15 @@ func tokenFieldValue(tok *model.VerificationToken, field string) any {
|
|||||||
return tok.Purpose
|
return tok.Purpose
|
||||||
case "target":
|
case "target":
|
||||||
return tok.Target
|
return tok.Target
|
||||||
|
case "idempotencyKey":
|
||||||
|
if tok.IdempotencyKey == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return *tok.IdempotencyKey
|
||||||
|
case "maxRetries":
|
||||||
|
return tok.MaxRetries
|
||||||
|
case "attempts":
|
||||||
|
return tok.Attempts
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -261,11 +321,11 @@ func matchOperator(stored any, ops bson.M) bool {
|
|||||||
for op, cmpVal := range ops {
|
for op, cmpVal := range ops {
|
||||||
switch op {
|
switch op {
|
||||||
case "$gt":
|
case "$gt":
|
||||||
if !timeGt(stored, cmpVal) {
|
if !cmpGt(stored, cmpVal) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case "$lt":
|
case "$lt":
|
||||||
if !timeLt(stored, cmpVal) {
|
if !cmpLt(stored, cmpVal) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,6 +333,36 @@ func matchOperator(stored any, ops bson.M) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cmpGt(stored, cmpVal any) bool {
|
||||||
|
if si, ok := toInt(stored); ok {
|
||||||
|
if ci, ok := toInt(cmpVal); ok {
|
||||||
|
return si > ci
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timeGt(stored, cmpVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmpLt(stored, cmpVal any) bool {
|
||||||
|
if si, ok := toInt(stored); ok {
|
||||||
|
if ci, ok := toInt(cmpVal); ok {
|
||||||
|
return si < ci
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timeLt(stored, cmpVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInt(v any) (int, bool) {
|
||||||
|
switch iv := v.(type) {
|
||||||
|
case int:
|
||||||
|
return iv, true
|
||||||
|
case int64:
|
||||||
|
return int(iv), true
|
||||||
|
case int32:
|
||||||
|
return int(iv), true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
func valuesEqual(a, b any) bool {
|
func valuesEqual(a, b any) bool {
|
||||||
// nil checks: usedAt == nil
|
// nil checks: usedAt == nil
|
||||||
if b == nil {
|
if b == nil {
|
||||||
@@ -343,21 +433,34 @@ func toTime(v any) (time.Time, bool) {
|
|||||||
return time.Time{}, false
|
return time.Time{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyPatch applies $set operations from a patch bson.D to a token.
|
// applyPatch applies $set and $inc operations from a patch bson.D to a token.
|
||||||
func applyPatch(tok *model.VerificationToken, patchDoc bson.D) {
|
func applyPatch(tok *model.VerificationToken, patchDoc bson.D) {
|
||||||
for _, op := range patchDoc {
|
for _, op := range patchDoc {
|
||||||
if op.Key != "$set" {
|
switch op.Key {
|
||||||
continue
|
case "$set":
|
||||||
}
|
fields, ok := op.Value.(bson.D)
|
||||||
fields, ok := op.Value.(bson.D)
|
if !ok {
|
||||||
if !ok {
|
continue
|
||||||
continue
|
}
|
||||||
}
|
for _, f := range fields {
|
||||||
for _, f := range fields {
|
switch f.Key {
|
||||||
switch f.Key {
|
case "usedAt":
|
||||||
case "usedAt":
|
if t, ok := f.Value.(time.Time); ok {
|
||||||
if t, ok := f.Value.(time.Time); ok {
|
tok.UsedAt = &t
|
||||||
tok.UsedAt = &t
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "$inc":
|
||||||
|
fields, ok := op.Value.(bson.D)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, f := range fields {
|
||||||
|
switch f.Key {
|
||||||
|
case "attempts":
|
||||||
|
if v, ok := f.Value.(int); ok {
|
||||||
|
tok.Attempts += v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,20 +473,27 @@ func cloneToken(src *model.VerificationToken) *model.VerificationToken {
|
|||||||
t := *src.UsedAt
|
t := *src.UsedAt
|
||||||
dst.UsedAt = &t
|
dst.UsedAt = &t
|
||||||
}
|
}
|
||||||
|
if src.MaxRetries != nil {
|
||||||
|
v := *src.MaxRetries
|
||||||
|
dst.MaxRetries = &v
|
||||||
|
}
|
||||||
|
if src.Salt != nil {
|
||||||
|
s := *src.Salt
|
||||||
|
dst.Salt = &s
|
||||||
|
}
|
||||||
|
if src.IdempotencyKey != nil {
|
||||||
|
k := *src.IdempotencyKey
|
||||||
|
dst.IdempotencyKey = &k
|
||||||
|
}
|
||||||
return &dst
|
return &dst
|
||||||
}
|
}
|
||||||
|
|
||||||
// allTokens returns every stored token for inspection in tests.
|
// ---------------------------------------------------------------------------
|
||||||
func (m *memoryTokenRepository) allTokens() []*model.VerificationToken {
|
// helpers – request builder
|
||||||
m.mu.Lock()
|
// ---------------------------------------------------------------------------
|
||||||
defer m.mu.Unlock()
|
|
||||||
out := make([]*model.VerificationToken, 0, len(m.data))
|
func req(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string, ttl time.Duration) *verification.Request {
|
||||||
for _, id := range m.order {
|
return verification.NewLinkRequest(accountRef, purpose, target).WithTTL(ttl)
|
||||||
if tok, ok := m.data[id]; ok {
|
|
||||||
out = append(out, cloneToken(tok))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -395,7 +505,7 @@ func TestCreate_ReturnsRawToken(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
raw, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotEmpty(t, raw)
|
assert.NotEmpty(t, raw)
|
||||||
}
|
}
|
||||||
@@ -405,10 +515,10 @@ func TestCreate_TokenCanBeConsumed(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
raw, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tok, err := db.Consume(ctx, raw)
|
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, raw)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, accountRef, tok.AccountRef)
|
assert.Equal(t, accountRef, tok.AccountRef)
|
||||||
assert.Equal(t, model.PurposePasswordReset, tok.Purpose)
|
assert.Equal(t, model.PurposePasswordReset, tok.Purpose)
|
||||||
@@ -420,10 +530,10 @@ func TestConsume_ReturnsCorrectFields(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
raw, err := db.Create(ctx, accountRef, model.PurposeEmailChange, "new@example.com", time.Hour)
|
raw, err := db.Create(ctx, req(accountRef, model.PurposeEmailChange, "new@example.com", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tok, err := db.Consume(ctx, raw)
|
tok, err := db.Consume(ctx, accountRef, model.PurposeEmailChange, raw)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, accountRef, tok.AccountRef)
|
assert.Equal(t, accountRef, tok.AccountRef)
|
||||||
assert.Equal(t, model.PurposeEmailChange, tok.Purpose)
|
assert.Equal(t, model.PurposeEmailChange, tok.Purpose)
|
||||||
@@ -435,16 +545,16 @@ func TestConsume_SecondConsumeFailsAlreadyUsed(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
raw, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = db.Consume(ctx, raw)
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, raw)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = db.Consume(ctx, raw)
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, raw)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.True(t, errors.Is(err, verification.ErrTokenAlreadyUsed),
|
assert.True(t, errors.Is(err, verification.ErrTokenNotFound),
|
||||||
"second consume should fail because usedAt is set")
|
"second consume should fail — used tokens are excluded from active filter")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConsume_ExpiredTokenFails(t *testing.T) {
|
func TestConsume_ExpiredTokenFails(t *testing.T) {
|
||||||
@@ -453,20 +563,20 @@ func TestConsume_ExpiredTokenFails(t *testing.T) {
|
|||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
// Create with a TTL that is already in the past.
|
// Create with a TTL that is already in the past.
|
||||||
raw, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", -time.Hour)
|
raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", -time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = db.Consume(ctx, raw)
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, raw)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.True(t, errors.Is(err, verification.ErrTokenExpired),
|
assert.True(t, errors.Is(err, verification.ErrTokenNotFound),
|
||||||
"expired token should not be consumable")
|
"expired token is excluded from active filter")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConsume_UnknownTokenFails(t *testing.T) {
|
func TestConsume_UnknownTokenFails(t *testing.T) {
|
||||||
db := newTestVerificationDB(t)
|
db := newTestVerificationDB(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
_, err := db.Consume(ctx, "nonexistent-token-value")
|
_, err := db.Consume(ctx, bson.NilObjectID, "", "nonexistent-token-value")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.True(t, errors.Is(err, verification.ErrTokenNotFound))
|
assert.True(t, errors.Is(err, verification.ErrTokenNotFound))
|
||||||
}
|
}
|
||||||
@@ -476,21 +586,21 @@ func TestCreate_InvalidatesPreviousToken(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
oldRaw, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
oldRaw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
newRaw, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
newRaw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotEqual(t, oldRaw, newRaw, "new token should differ from old one")
|
assert.NotEqual(t, oldRaw, newRaw, "new token should differ from old one")
|
||||||
|
|
||||||
// Old token is no longer consumable.
|
// Old token is no longer consumable — invalidated (usedAt set) by the second Create.
|
||||||
_, err = db.Consume(ctx, oldRaw)
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, oldRaw)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.True(t, errors.Is(err, verification.ErrTokenAlreadyUsed),
|
assert.True(t, errors.Is(err, verification.ErrTokenNotFound),
|
||||||
"old token should be invalidated (usedAt set) after new token creation")
|
"old token should be invalidated after new token creation")
|
||||||
|
|
||||||
// New token works fine.
|
// New token works fine.
|
||||||
tok, err := db.Consume(ctx, newRaw)
|
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, newRaw)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, accountRef, tok.AccountRef)
|
assert.Equal(t, accountRef, tok.AccountRef)
|
||||||
}
|
}
|
||||||
@@ -500,19 +610,19 @@ func TestCreate_InvalidatesMultiplePreviousTokens(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
first, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
first, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
second, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
second, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
third, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
third, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = db.Consume(ctx, first)
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, first)
|
||||||
assert.True(t, errors.Is(err, verification.ErrTokenAlreadyUsed), "first should be invalidated")
|
assert.True(t, errors.Is(err, verification.ErrTokenNotFound), "first should be invalidated")
|
||||||
_, err = db.Consume(ctx, second)
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, second)
|
||||||
assert.True(t, errors.Is(err, verification.ErrTokenAlreadyUsed), "second should be invalidated")
|
assert.True(t, errors.Is(err, verification.ErrTokenNotFound), "second should be invalidated")
|
||||||
|
|
||||||
tok, err := db.Consume(ctx, third)
|
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, third)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, accountRef, tok.AccountRef)
|
assert.Equal(t, accountRef, tok.AccountRef)
|
||||||
}
|
}
|
||||||
@@ -522,14 +632,14 @@ func TestCreate_DifferentPurposeNotInvalidated(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
resetRaw, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
resetRaw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Creating an activation token should NOT invalidate the password-reset token.
|
// Creating an activation token should NOT invalidate the password-reset token.
|
||||||
_, err = db.Create(ctx, accountRef, model.PurposeAccountActivation, "", time.Hour)
|
_, err = db.Create(ctx, req(accountRef, model.PurposeAccountActivation, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tok, err := db.Consume(ctx, resetRaw)
|
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, resetRaw)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, model.PurposePasswordReset, tok.Purpose)
|
assert.Equal(t, model.PurposePasswordReset, tok.Purpose)
|
||||||
}
|
}
|
||||||
@@ -539,14 +649,14 @@ func TestCreate_DifferentTargetNotInvalidated(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
firstRaw, err := db.Create(ctx, accountRef, model.PurposeEmailChange, "a@example.com", time.Hour)
|
firstRaw, err := db.Create(ctx, req(accountRef, model.PurposeEmailChange, "a@example.com", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Creating a token for a different target email should NOT invalidate the first.
|
// Creating a token for a different target email should NOT invalidate the first.
|
||||||
_, err = db.Create(ctx, accountRef, model.PurposeEmailChange, "b@example.com", time.Hour)
|
_, err = db.Create(ctx, req(accountRef, model.PurposeEmailChange, "b@example.com", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tok, err := db.Consume(ctx, firstRaw)
|
tok, err := db.Consume(ctx, accountRef, model.PurposeEmailChange, firstRaw)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "a@example.com", tok.Target)
|
assert.Equal(t, "a@example.com", tok.Target)
|
||||||
}
|
}
|
||||||
@@ -557,13 +667,13 @@ func TestCreate_DifferentAccountNotInvalidated(t *testing.T) {
|
|||||||
account1 := bson.NewObjectID()
|
account1 := bson.NewObjectID()
|
||||||
account2 := bson.NewObjectID()
|
account2 := bson.NewObjectID()
|
||||||
|
|
||||||
raw1, err := db.Create(ctx, account1, model.PurposePasswordReset, "", time.Hour)
|
raw1, err := db.Create(ctx, req(account1, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = db.Create(ctx, account2, model.PurposePasswordReset, "", time.Hour)
|
_, err = db.Create(ctx, req(account2, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tok, err := db.Consume(ctx, raw1)
|
tok, err := db.Consume(ctx, account1, model.PurposePasswordReset, raw1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, account1, tok.AccountRef)
|
assert.Equal(t, account1, tok.AccountRef)
|
||||||
}
|
}
|
||||||
@@ -574,18 +684,18 @@ func TestCreate_AlreadyUsedTokenNotInvalidatedAgain(t *testing.T) {
|
|||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
// Create and consume first token.
|
// Create and consume first token.
|
||||||
raw1, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
raw1, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = db.Consume(ctx, raw1)
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, raw1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create second — the already-consumed token should have usedAt set,
|
// Create second — the already-consumed token should have usedAt set,
|
||||||
// so the invalidation query (usedAt == nil) should skip it.
|
// so the invalidation query (usedAt == nil) should skip it.
|
||||||
// This tests that the PatchMany filter correctly excludes already-used tokens.
|
// This tests that the PatchMany filter correctly excludes already-used tokens.
|
||||||
raw2, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
raw2, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tok, err := db.Consume(ctx, raw2)
|
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, raw2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, accountRef, tok.AccountRef)
|
assert.Equal(t, accountRef, tok.AccountRef)
|
||||||
}
|
}
|
||||||
@@ -596,14 +706,14 @@ func TestCreate_ExpiredTokenNotInvalidated(t *testing.T) {
|
|||||||
accountRef := bson.NewObjectID()
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
// Create a token that is already expired.
|
// Create a token that is already expired.
|
||||||
_, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", -time.Hour)
|
_, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", -time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create a fresh one — invalidation should skip the expired token (expiresAt > now filter).
|
// Create a fresh one — invalidation should skip the expired token (expiresAt > now filter).
|
||||||
raw2, err := db.Create(ctx, accountRef, model.PurposePasswordReset, "", time.Hour)
|
raw2, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tok, err := db.Consume(ctx, raw2)
|
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, raw2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, accountRef, tok.AccountRef)
|
assert.Equal(t, accountRef, tok.AccountRef)
|
||||||
}
|
}
|
||||||
@@ -619,3 +729,228 @@ func TestTokenHash_DifferentInputs(t *testing.T) {
|
|||||||
h2 := tokenHash("input-b")
|
h2 := tokenHash("input-b")
|
||||||
assert.NotEqual(t, h1, h2)
|
assert.NotEqual(t, h1, h2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// cooldown tests
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestCreate_CooldownBlocksCreation(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
// First creation without cooldown.
|
||||||
|
_, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Immediate re-create with cooldown should be blocked — token is too recent to invalidate.
|
||||||
|
r2 := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithCooldown(time.Minute)
|
||||||
|
_, err = db.Create(ctx, r2)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.True(t, errors.Is(err, verification.ErrCooldownActive))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate_CooldownExpiresAllowsCreation(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
// First creation without cooldown.
|
||||||
|
_, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
|
||||||
|
// Re-create with short cooldown — the prior token is old enough to be invalidated.
|
||||||
|
r2 := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithCooldown(time.Millisecond)
|
||||||
|
_, err = db.Create(ctx, r2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate_CooldownNilIgnored(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
_, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// No cooldown set — immediate re-create should succeed.
|
||||||
|
_, err = db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate_IdempotencyKeyReplayReturnsSameToken(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
firstReq := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithIdempotencyKey("same-key")
|
||||||
|
firstRaw, err := db.Create(ctx, firstReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, firstRaw)
|
||||||
|
|
||||||
|
// Replay with the same idempotency key should return success and same token.
|
||||||
|
secondReq := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithIdempotencyKey("same-key")
|
||||||
|
secondRaw, err := db.Create(ctx, secondReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, firstRaw, secondRaw)
|
||||||
|
|
||||||
|
repo := db.Repository.(*memoryTokenRepository)
|
||||||
|
repo.mu.Lock()
|
||||||
|
assert.Len(t, repo.data, 1)
|
||||||
|
repo.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate_IdempotencyScopeIncludesTarget(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
r1 := req(accountRef, model.PurposeEmailChange, "a@example.com", time.Hour).WithIdempotencyKey("same-key")
|
||||||
|
raw1, err := db.Create(ctx, r1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, raw1)
|
||||||
|
|
||||||
|
// Same account/purpose/key but different target should be treated as a different idempotency scope.
|
||||||
|
r2 := req(accountRef, model.PurposeEmailChange, "b@example.com", time.Hour).WithIdempotencyKey("same-key")
|
||||||
|
raw2, err := db.Create(ctx, r2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, raw2)
|
||||||
|
assert.NotEqual(t, raw1, raw2)
|
||||||
|
|
||||||
|
t1, err := db.Consume(ctx, accountRef, model.PurposeEmailChange, raw1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "a@example.com", t1.Target)
|
||||||
|
|
||||||
|
t2, err := db.Consume(ctx, accountRef, model.PurposeEmailChange, raw2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "b@example.com", t2.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate_IdempotencySurvivesCallbackRetry(t *testing.T) {
|
||||||
|
db := newTestVerificationDBWithFactory(t, &retryingTxFactory{})
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
// Cooldown would block the second callback execution if idempotency wasn't handled.
|
||||||
|
r := req(accountRef, model.PurposePasswordReset, "", time.Hour).
|
||||||
|
WithCooldown(time.Minute).
|
||||||
|
WithIdempotencyKey("retry-safe")
|
||||||
|
|
||||||
|
raw, err := db.Create(ctx, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, raw)
|
||||||
|
|
||||||
|
repo := db.Repository.(*memoryTokenRepository)
|
||||||
|
repo.mu.Lock()
|
||||||
|
require.Len(t, repo.data, 1)
|
||||||
|
for _, tok := range repo.data {
|
||||||
|
require.NotNil(t, tok.IdempotencyKey)
|
||||||
|
assert.Equal(t, "retry-safe", *tok.IdempotencyKey)
|
||||||
|
assert.Nil(t, tok.UsedAt)
|
||||||
|
assert.Equal(t, tok.VerifyTokenHash, tokenHash(raw))
|
||||||
|
}
|
||||||
|
repo.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// max retries / attempts tests
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestConsume_MaxRetriesExceeded(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
r := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithMaxRetries(2)
|
||||||
|
raw, err := db.Create(ctx, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Simulate 2 prior failed attempts by setting Attempts directly.
|
||||||
|
repo := db.Repository.(*memoryTokenRepository)
|
||||||
|
repo.mu.Lock()
|
||||||
|
for _, tok := range repo.data {
|
||||||
|
tok.Attempts = 2
|
||||||
|
}
|
||||||
|
repo.mu.Unlock()
|
||||||
|
|
||||||
|
// Consume with correct token should fail — attempts already at max.
|
||||||
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, raw)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.True(t, errors.Is(err, verification.ErrTokenAttemptsExceeded))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConsume_UnderMaxRetriesSucceeds(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
r := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithMaxRetries(3)
|
||||||
|
raw, err := db.Create(ctx, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Simulate 2 prior failed attempts (under maxRetries=3).
|
||||||
|
repo := db.Repository.(*memoryTokenRepository)
|
||||||
|
repo.mu.Lock()
|
||||||
|
for _, tok := range repo.data {
|
||||||
|
tok.Attempts = 2
|
||||||
|
}
|
||||||
|
repo.mu.Unlock()
|
||||||
|
|
||||||
|
// Consume with correct token should succeed.
|
||||||
|
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, raw)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, accountRef, tok.AccountRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConsume_NoMaxRetriesIgnoresAttempts(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
// Create without MaxRetries.
|
||||||
|
raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Simulate high attempt count — should be ignored since MaxRetries is nil.
|
||||||
|
repo := db.Repository.(*memoryTokenRepository)
|
||||||
|
repo.mu.Lock()
|
||||||
|
for _, tok := range repo.data {
|
||||||
|
tok.Attempts = 100
|
||||||
|
}
|
||||||
|
repo.mu.Unlock()
|
||||||
|
|
||||||
|
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, raw)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, accountRef, tok.AccountRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConsume_WrongHashReturnsNotFound(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
|
||||||
|
_, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Wrong code — hash won't match any token.
|
||||||
|
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "wrong-code")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.True(t, errors.Is(err, verification.ErrTokenNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConsume_ContextMismatchReturnsNotFound(t *testing.T) {
|
||||||
|
db := newTestVerificationDB(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
accountRef := bson.NewObjectID()
|
||||||
|
otherAccount := bson.NewObjectID()
|
||||||
|
|
||||||
|
raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Correct token but wrong accountRef — context mismatch.
|
||||||
|
_, err = db.Consume(ctx, otherAccount, model.PurposePasswordReset, raw)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.True(t, errors.Is(err, verification.ErrTokenNotFound))
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrTokenNotFound = errors.New("vtNotFound")
|
ErrTokenNotFound = errors.New("vtNotFound")
|
||||||
ErrTokenAlreadyUsed = errors.New("vtAlreadyUsed")
|
ErrTokenAlreadyUsed = errors.New("vtAlreadyUsed")
|
||||||
ErrTokenExpired = errors.New("vtExpired")
|
ErrTokenExpired = errors.New("vtExpired")
|
||||||
|
ErrTokenAttemptsExceeded = errors.New("vtAttemptsExceeded")
|
||||||
|
ErrCooldownActive = errors.New("vtCooldownActive")
|
||||||
|
ErrIdempotencyConflict = errors.New("vtIdempotencyConflict")
|
||||||
)
|
)
|
||||||
|
|
||||||
func wrap(err error, msg string) error {
|
func wrap(err error, msg string) error {
|
||||||
@@ -26,3 +29,15 @@ func ErorrTokenAlreadyUsed() error {
|
|||||||
func ErorrTokenExpired() error {
|
func ErorrTokenExpired() error {
|
||||||
return wrap(ErrTokenExpired, "verification token expired")
|
return wrap(ErrTokenExpired, "verification token expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrorCooldownActive() error {
|
||||||
|
return wrap(ErrCooldownActive, "token creation cooldown is active")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorTokenAttemptsExceeded() error {
|
||||||
|
return wrap(ErrTokenAttemptsExceeded, "verification token max attempts exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorIdempotencyConflict() error {
|
||||||
|
return wrap(ErrIdempotencyConflict, "verification token request idempotency key has already been used")
|
||||||
|
}
|
||||||
|
|||||||
70
api/pkg/db/verification/request.go
Normal file
70
api/pkg/db/verification/request.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package verification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenKind = string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenKindOTP TokenKind = "otp"
|
||||||
|
TokenKindLink TokenKind = "link"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
AccountRef bson.ObjectID
|
||||||
|
Purpose model.VerificationPurpose
|
||||||
|
Target string
|
||||||
|
Ttl time.Duration
|
||||||
|
Kind TokenKind
|
||||||
|
MaxRetries *int
|
||||||
|
Cooldown *time.Duration
|
||||||
|
IdempotencyKey *string // Optional key to make Create idempotent for retries.
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string, kind TokenKind) *Request {
|
||||||
|
return &Request{
|
||||||
|
AccountRef: accountRef,
|
||||||
|
Purpose: purpose,
|
||||||
|
Target: target,
|
||||||
|
Kind: kind,
|
||||||
|
Ttl: 15 * time.Minute, // default TTL for verification tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinkRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string) *Request {
|
||||||
|
return newRequest(accountRef, purpose, target, TokenKindLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOTPRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string) *Request {
|
||||||
|
return newRequest(accountRef, purpose, target, TokenKindOTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) WithTTL(ttl time.Duration) *Request {
|
||||||
|
r.Ttl = ttl
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) WithMaxRetries(maxRetries int) *Request {
|
||||||
|
r.MaxRetries = &maxRetries
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) WithCooldown(cooldown time.Duration) *Request {
|
||||||
|
r.Cooldown = &cooldown
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) WithIdempotencyKey(key string) *Request {
|
||||||
|
normalized := strings.TrimSpace(key)
|
||||||
|
if normalized == "" {
|
||||||
|
r.IdempotencyKey = nil
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
r.IdempotencyKey = &normalized
|
||||||
|
return r
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package verification
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
@@ -10,12 +9,6 @@ import (
|
|||||||
|
|
||||||
type DB interface {
|
type DB interface {
|
||||||
// template.DB[*model.VerificationToken]
|
// template.DB[*model.VerificationToken]
|
||||||
Create(
|
Create(ctx context.Context, request *Request) (verificationToken string, err error)
|
||||||
ctx context.Context,
|
Consume(ctx context.Context, accountRef bson.ObjectID, purpose model.VerificationPurpose, verificationToken string) (*model.VerificationToken, error)
|
||||||
accountRef bson.ObjectID,
|
|
||||||
purpose model.VerificationPurpose,
|
|
||||||
target string,
|
|
||||||
ttl time.Duration,
|
|
||||||
) (rawToken string, err error)
|
|
||||||
Consume(ctx context.Context, rawToken string) (*model.VerificationToken, error)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ require (
|
|||||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
|
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.27.1
|
||||||
golang.org/x/crypto v0.47.0
|
golang.org/x/crypto v0.48.0
|
||||||
google.golang.org/grpc v1.78.0
|
google.golang.org/grpc v1.78.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
)
|
)
|
||||||
@@ -87,11 +87,11 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -204,8 +204,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -218,8 +218,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -246,14 +246,14 @@ golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
|||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -271,8 +271,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ func (ccn *ConfirmationCodeNotification) Serialize() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newConfirmationEvent(action nm.NotificationAction) model.NotificationEvent {
|
func newConfirmationEvent(action nm.NotificationAction) model.NotificationEvent {
|
||||||
return model.NewNotification(mservice.Confirmations, action)
|
return model.NewNotification(mservice.Verification, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfirmationCodeEnvelope(sender string, accountRef bson.ObjectID, destination string, target model.ConfirmationTarget, code string) messaging.Envelope {
|
func NewConfirmationCodeEnvelope(sender string, accountRef bson.ObjectID, destination string, target model.VerificationPurpose, code string) messaging.Envelope {
|
||||||
return &ConfirmationCodeNotification{
|
return &ConfirmationCodeNotification{
|
||||||
Envelope: messaging.CreateEnvelope(sender, newConfirmationEvent(nm.NAPending)),
|
Envelope: messaging.CreateEnvelope(sender, newConfirmationEvent(nm.NAPending)),
|
||||||
payload: confirmationCodePayload{
|
payload: confirmationCodePayload{
|
||||||
|
|||||||
@@ -42,10 +42,12 @@ func (ccp *ConfirmationCodeProcessor) Process(ctx context.Context, envelope me.E
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
target := model.ConfirmationTarget(msg.Target)
|
target, err := model.VPFromString(msg.Target)
|
||||||
if target != model.ConfirmationTargetLogin && target != model.ConfirmationTargetPayout {
|
if err != nil {
|
||||||
return merrors.InvalidArgument("invalid confirmation target", "target")
|
ccp.logger.Warn("Failed to parse confirmation target from envelope", zap.Error(err), zap.String("topic", ccp.event.ToString()), zap.String("target", msg.Target))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.Code == "" {
|
if msg.Code == "" {
|
||||||
return merrors.InvalidArgument("empty confirmation code", "code")
|
return merrors.InvalidArgument("empty confirmation code", "code")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func confirmationResultEvent(sourceService, rail string) model.NotificationEvent
|
|||||||
rail = "default"
|
rail = "default"
|
||||||
}
|
}
|
||||||
rail = strings.ToLower(rail)
|
rail = strings.ToLower(rail)
|
||||||
return model.NewNotification(mservice.Confirmations, nm.NotificationAction(action+"."+rail))
|
return model.NewNotification(mservice.Verification, nm.NotificationAction(action+"."+rail))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfirmationRequestEnvelope(sender string, request *model.ConfirmationRequest) messaging.Envelope {
|
func NewConfirmationRequestEnvelope(sender string, request *model.ConfirmationRequest) messaging.Envelope {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Code(sender string, accountRef bson.ObjectID, destination string, target model.ConfirmationTarget, code string) messaging.Envelope {
|
func Code(sender string, accountRef bson.ObjectID, destination string, target model.VerificationPurpose, code string) messaging.Envelope {
|
||||||
return cinternal.NewConfirmationCodeEnvelope(sender, accountRef, destination, target, code)
|
return cinternal.NewConfirmationCodeEnvelope(sender, accountRef, destination, target, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ import (
|
|||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfirmationCodeHandler = func(context.Context, *model.Account, string, model.ConfirmationTarget, string) error
|
type ConfirmationCodeHandler = func(context.Context, *model.Account, string, model.VerificationPurpose, string) error
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/db/storable"
|
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfirmationTarget string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ConfirmationTargetLogin ConfirmationTarget = "login"
|
|
||||||
ConfirmationTargetPayout ConfirmationTarget = "payout"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfirmationCode struct {
|
|
||||||
storable.Base `bson:",inline" json:",inline"`
|
|
||||||
AccountRef bson.ObjectID `bson:"accountRef" json:"accountRef"`
|
|
||||||
Destination string `bson:"destination" json:"destination"`
|
|
||||||
Target ConfirmationTarget `bson:"target" json:"target"`
|
|
||||||
CodeHash []byte `bson:"codeHash" json:"codeHash,omitempty"`
|
|
||||||
Salt []byte `bson:"salt" json:"salt,omitempty"`
|
|
||||||
ExpiresAt time.Time `bson:"expiresAt" json:"expiresAt"`
|
|
||||||
MaxAttempts int `bson:"maxAttempts" json:"maxAttempts"`
|
|
||||||
ResendLimit int `bson:"resendLimit" json:"resendLimit"`
|
|
||||||
CooldownUntil time.Time `bson:"cooldownUntil" json:"cooldownUntil"`
|
|
||||||
Used bool `bson:"used" json:"used"`
|
|
||||||
Attempts int `bson:"attempts" json:"attempts"`
|
|
||||||
ResendCount int `bson:"resendCount" json:"resendCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ConfirmationCode) Collection() string {
|
|
||||||
return mservice.Confirmations
|
|
||||||
}
|
|
||||||
@@ -106,7 +106,7 @@ func StringToNotificationEvent(eventType, eventAction string) (NotificationEvent
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return NewNotification(et, ea), nil
|
return NewNotification(et, ea), nil
|
||||||
}
|
}
|
||||||
if et == mservice.Confirmations {
|
if et == mservice.Verification {
|
||||||
action := strings.TrimSpace(eventAction)
|
action := strings.TrimSpace(eventAction)
|
||||||
if action == "" {
|
if action == "" {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/db/storable"
|
"github.com/tech/sendico/pkg/db/storable"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,18 +15,45 @@ const (
|
|||||||
PurposeAccountActivation VerificationPurpose = "account_activation"
|
PurposeAccountActivation VerificationPurpose = "account_activation"
|
||||||
PurposeEmailChange VerificationPurpose = "email_change"
|
PurposeEmailChange VerificationPurpose = "email_change"
|
||||||
PurposePasswordReset VerificationPurpose = "password_reset"
|
PurposePasswordReset VerificationPurpose = "password_reset"
|
||||||
PurposeSensitiveAction VerificationPurpose = "sensitive_action"
|
|
||||||
PurposeMagicLogin VerificationPurpose = "magic_login"
|
PurposeMagicLogin VerificationPurpose = "magic_login"
|
||||||
|
PurposeLogin VerificationPurpose = "login"
|
||||||
|
PurposePayout VerificationPurpose = "payout"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func VPToString(code VerificationPurpose) string {
|
||||||
|
return string(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func VPFromString(s string) (VerificationPurpose, error) {
|
||||||
|
switch s {
|
||||||
|
case string(PurposeAccountActivation):
|
||||||
|
return PurposeAccountActivation, nil
|
||||||
|
case string(PurposeEmailChange):
|
||||||
|
return PurposeEmailChange, nil
|
||||||
|
case string(PurposePasswordReset):
|
||||||
|
return PurposePasswordReset, nil
|
||||||
|
case string(PurposeMagicLogin):
|
||||||
|
return PurposeMagicLogin, nil
|
||||||
|
case string(PurposeLogin):
|
||||||
|
return PurposeLogin, nil
|
||||||
|
case string(PurposePayout):
|
||||||
|
return PurposePayout, nil
|
||||||
|
default:
|
||||||
|
return "", merrors.InvalidArgument(fmt.Sprintf("invalid verification purpose: %s", s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type VerificationToken struct {
|
type VerificationToken struct {
|
||||||
storable.Base `bson:",inline" json:",inline"`
|
storable.Base `bson:",inline" json:",inline"`
|
||||||
ArchivableBase `bson:",inline" json:",inline"`
|
|
||||||
|
|
||||||
Target string `bson:"target,omitempty" json:"target"`
|
Target string `bson:"target,omitempty" json:"target"`
|
||||||
AccountRef bson.ObjectID `bson:"accountRef" json:"accountRef"`
|
AccountRef bson.ObjectID `bson:"accountRef" json:"accountRef"`
|
||||||
Purpose VerificationPurpose `bson:"purpose" json:"purpose"`
|
Purpose VerificationPurpose `bson:"purpose" json:"purpose"`
|
||||||
|
IdempotencyKey *string `bson:"idempotencyKey,omitempty" json:"-"`
|
||||||
VerifyTokenHash string `bson:"verifyTokenHash" json:"-"`
|
VerifyTokenHash string `bson:"verifyTokenHash" json:"-"`
|
||||||
|
Salt *string `bson:"salt,omitempty" json:"-"`
|
||||||
UsedAt *time.Time `bson:"usedAt,omitempty" json:"-"`
|
UsedAt *time.Time `bson:"usedAt,omitempty" json:"-"`
|
||||||
ExpiresAt time.Time `bson:"expiresAt" json:"-"`
|
ExpiresAt time.Time `bson:"expiresAt" json:"-"`
|
||||||
|
MaxRetries *int `bson:"maxRetries,omitempty" json:"-"`
|
||||||
|
Attempts int `bson:"attempts" json:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ type Type = string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
Accounts Type = "accounts" // Represents user accounts in the system
|
Accounts Type = "accounts" // Represents user accounts in the system
|
||||||
Confirmations Type = "confirmations" // Represents confirmation code flows
|
Verification Type = "verification" // Represents verification code flows
|
||||||
Amplitude Type = "amplitude" // Represents analytics integration with Amplitude
|
Amplitude Type = "amplitude" // Represents analytics integration with Amplitude
|
||||||
Discovery Type = "discovery" // Represents service discovery registry
|
Discovery Type = "discovery" // Represents service discovery registry
|
||||||
Site Type = "site" // Represents public site endpoints
|
Site Type = "site" // Represents public site endpoints
|
||||||
@@ -57,7 +57,7 @@ const (
|
|||||||
|
|
||||||
func StringToSType(s string) (Type, error) {
|
func StringToSType(s string) (Type, error) {
|
||||||
switch Type(s) {
|
switch Type(s) {
|
||||||
case Accounts, Confirmations, Amplitude, Site, Changes, Clients, ChainGateway, ChainWallets, ChainWalletBalances,
|
case Accounts, Verification, Amplitude, Site, Changes, Clients, ChainGateway, ChainWallets, ChainWalletBalances,
|
||||||
ChainTransfers, ChainDeposits, MntxGateway, PaymentGateway, FXOracle, FeePlans, BillingDocuments, FilterProjects, Invitations, Invoices, Logo, Ledger,
|
ChainTransfers, ChainDeposits, MntxGateway, PaymentGateway, FXOracle, FeePlans, BillingDocuments, FilterProjects, Invitations, Invoices, Logo, Ledger,
|
||||||
LedgerAccounts, LedgerBalances, LedgerEntries, LedgerOutbox, LedgerParties, LedgerPlines, Notifications,
|
LedgerAccounts, LedgerBalances, LedgerEntries, LedgerOutbox, LedgerParties, LedgerPlines, Notifications,
|
||||||
Organizations, Payments, PaymentRoutes, PaymentPlanTemplates, PaymentOrchestrator, Permissions, Policies, PolicyAssignements,
|
Organizations, Payments, PaymentRoutes, PaymentPlanTemplates, PaymentOrchestrator, Permissions, Policies, PolicyAssignements,
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ func GetAccountBoundObjects[T any](
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, merrors.ErrNoData) {
|
if !errors.Is(err, merrors.ErrNoData) {
|
||||||
logger.Warn("Failed to fetch account bound objects", zap.Error(err),
|
logger.Warn("Failed to fetch account bound objects", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef),
|
mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef),
|
mzap.ObjRef("organization_ref", organizationRef),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("No matching account bound objects found", zap.Error(err),
|
logger.Debug("No matching account bound objects found", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef),
|
mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef),
|
mzap.ObjRef("organization_ref", organizationRef),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func GetAccountBoundObjects[T any](
|
|||||||
|
|
||||||
logger.Debug("Successfully retrieved account bound objects",
|
logger.Debug("Successfully retrieved account bound objects",
|
||||||
zap.Int("total_count", len(allObjects)),
|
zap.Int("total_count", len(allObjects)),
|
||||||
mzap.ObjRef("account_ref", accountRef),
|
mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef),
|
mzap.ObjRef("organization_ref", organizationRef),
|
||||||
zap.Any("objs", allObjects),
|
zap.Any("objs", allObjects),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,17 +29,17 @@ func GetProtectedObjects[T any](
|
|||||||
refs, err := repo.ListPermissionBound(ctx, repository.ApplyCursor(filter, cursor))
|
refs, err := repo.ListPermissionBound(ctx, repository.ApplyCursor(filter, cursor))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, merrors.ErrNoData) {
|
if !errors.Is(err, merrors.ErrNoData) {
|
||||||
logger.Warn("Failed to fetch object IDs", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
logger.Warn("Failed to fetch object IDs", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("action", string(action)))
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("action", string(action)))
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("No matching IDs found", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
logger.Debug("No matching IDs found", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("action", string(action)))
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("action", string(action)))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res, err := enforcer.EnforceBatch(ctx, refs, accountRef, action)
|
res, err := enforcer.EnforceBatch(ctx, refs, accountRef, action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to enforce object IDs", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
logger.Warn("Failed to enforce object IDs", zap.Error(err), mzap.AccRef(accountRef),
|
||||||
mzap.ObjRef("organization_ref", organizationRef), zap.String("action", string(action)))
|
mzap.ObjRef("organization_ref", organizationRef), zap.String("action", string(action)))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,22 +73,22 @@ func (m *AccountManager) DeleteOrganization(ctx context.Context, orgRef bson.Obj
|
|||||||
// DeleteAccount deletes an account and all its associated data
|
// DeleteAccount deletes an account and all its associated data
|
||||||
// The caller is responsible for wrapping this in a transaction
|
// The caller is responsible for wrapping this in a transaction
|
||||||
func (m *AccountManager) DeleteAccount(ctx context.Context, accountRef bson.ObjectID) error {
|
func (m *AccountManager) DeleteAccount(ctx context.Context, accountRef bson.ObjectID) error {
|
||||||
m.logger.Debug("Deleting account", mzap.ObjRef("account_ref", accountRef))
|
m.logger.Debug("Deleting account", mzap.AccRef(accountRef))
|
||||||
|
|
||||||
// Delete the account
|
// Delete the account
|
||||||
if err := m.accountDB.Delete(ctx, accountRef); err != nil {
|
if err := m.accountDB.Delete(ctx, accountRef); err != nil {
|
||||||
m.logger.Warn("Failed to delete account", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
m.logger.Warn("Failed to delete account", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.logger.Info("Successfully deleted account", mzap.ObjRef("account_ref", accountRef))
|
m.logger.Info("Successfully deleted account", mzap.AccRef(accountRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAll deletes all data for a given account and organization
|
// DeleteAll deletes all data for a given account and organization
|
||||||
// The caller is responsible for wrapping this in a transaction
|
// The caller is responsible for wrapping this in a transaction
|
||||||
func (m *AccountManager) DeleteAll(ctx context.Context, accountRef, organizationRef bson.ObjectID) error {
|
func (m *AccountManager) DeleteAll(ctx context.Context, accountRef, organizationRef bson.ObjectID) error {
|
||||||
m.logger.Debug("Deleting all data", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
m.logger.Debug("Deleting all data", mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
|
|
||||||
// Delete organization first (which will cascade delete all related data)
|
// Delete organization first (which will cascade delete all related data)
|
||||||
if err := m.DeleteOrganization(ctx, organizationRef); err != nil {
|
if err := m.DeleteOrganization(ctx, organizationRef); err != nil {
|
||||||
@@ -98,11 +98,11 @@ func (m *AccountManager) DeleteAll(ctx context.Context, accountRef, organization
|
|||||||
|
|
||||||
// Delete account
|
// Delete account
|
||||||
if err := m.DeleteAccount(ctx, accountRef); err != nil {
|
if err := m.DeleteAccount(ctx, accountRef); err != nil {
|
||||||
m.logger.Warn("Failed to delete account", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
m.logger.Warn("Failed to delete account", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.logger.Info("Successfully deleted all data", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
m.logger.Info("Successfully deleted all data", mzap.AccRef(accountRef), mzap.ObjRef("organization_ref", organizationRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
api/pkg/mutil/mask/mask.go
Normal file
17
api/pkg/mutil/mask/mask.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package mask
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func Email(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]
|
||||||
|
}
|
||||||
20
api/pkg/mutil/mzap/account.go
Normal file
20
api/pkg/mutil/mzap/account.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package mzap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mutil/mask"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AccRef(accountRef bson.ObjectID) zap.Field {
|
||||||
|
return ObjRef("account_ref", accountRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Email(email string) zap.Field {
|
||||||
|
return zap.String("email", mask.Email(email))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Login(account *model.Account) zap.Field {
|
||||||
|
return Email(account.Login)
|
||||||
|
}
|
||||||
@@ -13,7 +13,3 @@ func ObjRef(name string, objRef bson.ObjectID) zap.Field {
|
|||||||
func StorableRef(obj storable.Storable) zap.Field {
|
func StorableRef(obj storable.Storable) zap.Field {
|
||||||
return ObjRef(obj.Collection()+"_ref", *obj.GetID())
|
return ObjRef(obj.Collection()+"_ref", *obj.GetID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func AccRef(accountRef bson.ObjectID) zap.Field {
|
|
||||||
return ObjRef("account_ref", accountRef)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/server
|
module github.com/tech/sendico/server
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.7
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../pkg
|
replace github.com/tech/sendico/pkg => ../pkg
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ require (
|
|||||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
|
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.27.1
|
||||||
golang.org/x/net v0.49.0
|
golang.org/x/net v0.50.0
|
||||||
google.golang.org/grpc v1.78.0
|
google.golang.org/grpc v1.78.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@@ -134,9 +134,9 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -292,8 +292,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -308,8 +308,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -336,14 +336,14 @@ golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
|||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -361,10 +361,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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -155,14 +155,18 @@ func (s *service) CreateAccount(
|
|||||||
func (s *service) VerifyAccount(
|
func (s *service) VerifyAccount(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
acct *model.Account,
|
acct *model.Account,
|
||||||
) (verificationToken string, err error) {
|
) (string, error) {
|
||||||
verificationToken, err = s.vdb.Create(ctx, *acct.GetID(), model.PurposeAccountActivation, "", time.Duration(time.Hour*24))
|
token, err := s.vdb.Create(
|
||||||
|
ctx,
|
||||||
|
verification.NewLinkRequest(*acct.GetID(), model.PurposeAccountActivation, "").
|
||||||
|
WithTTL(time.Duration(time.Hour*24)),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to create verification token for new account", zap.Error(err), mzap.StorableRef(acct))
|
s.logger.Warn("Failed to create verification token for new account", zap.Error(err), mzap.StorableRef(acct))
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return verificationToken, nil
|
return token, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,19 +178,19 @@ func (s *service) DeleteAccount(
|
|||||||
// Check if this is the only member in the organization
|
// Check if this is the only member in the organization
|
||||||
if len(org.Members) <= 1 {
|
if len(org.Members) <= 1 {
|
||||||
s.logger.Warn("Cannot delete account - it's the only member in the organization",
|
s.logger.Warn("Cannot delete account - it's the only member in the organization",
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.StorableRef(org))
|
mzap.AccRef(accountRef), mzap.StorableRef(org))
|
||||||
return merrors.InvalidArgument("Cannot delete the only member of an organization")
|
return merrors.InvalidArgument("Cannot delete the only member of an organization")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1) Remove from organization
|
// 1) Remove from organization
|
||||||
if err := s.RemoveAccountFromOrganization(ctx, org, accountRef); err != nil {
|
if err := s.RemoveAccountFromOrganization(ctx, org, accountRef); err != nil {
|
||||||
s.logger.Warn("Failed to revoke account role", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
s.logger.Warn("Failed to revoke account role", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Delete the account document
|
// 2) Delete the account document
|
||||||
if err := s.accountDB.Delete(ctx, accountRef); err != nil {
|
if err := s.accountDB.Delete(ctx, accountRef); err != nil {
|
||||||
s.logger.Warn("Failed to delete account", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
s.logger.Warn("Failed to delete account", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -203,13 +207,13 @@ func (s *service) RemoveAccountFromOrganization(
|
|||||||
roles, err := s.enforcer.GetRoles(ctx, accountRef, org.ID)
|
roles, err := s.enforcer.GetRoles(ctx, accountRef, org.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to fetch account permissions", zap.Error(err), mzap.StorableRef(org),
|
s.logger.Warn("Failed to fetch account permissions", zap.Error(err), mzap.StorableRef(org),
|
||||||
mzap.ObjRef("account_ref", accountRef))
|
mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
if err := s.roleManager.Revoke(ctx, role.DescriptionRef, accountRef, org.ID); err != nil {
|
if err := s.roleManager.Revoke(ctx, role.DescriptionRef, accountRef, org.ID); err != nil {
|
||||||
s.logger.Warn("Failed to revoke account role", zap.Error(err),
|
s.logger.Warn("Failed to revoke account role", zap.Error(err),
|
||||||
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("role_ref", role.DescriptionRef))
|
mzap.AccRef(accountRef), mzap.ObjRef("role_ref", role.DescriptionRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,7 +222,7 @@ func (s *service) RemoveAccountFromOrganization(
|
|||||||
// Remove the member by slicing it out
|
// Remove the member by slicing it out
|
||||||
org.Members = append(org.Members[:i], org.Members[i+1:]...)
|
org.Members = append(org.Members[:i], org.Members[i+1:]...)
|
||||||
if err := s.orgDB.Update(ctx, accountRef, org); err != nil {
|
if err := s.orgDB.Update(ctx, accountRef, org); err != nil {
|
||||||
s.logger.Warn("Failed to remove member from organization", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
s.logger.Warn("Failed to remove member from organization", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -231,7 +235,11 @@ func (s *service) ResetPassword(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
acct *model.Account,
|
acct *model.Account,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
return s.vdb.Create(ctx, *acct.GetID(), model.PurposePasswordReset, "", time.Duration(time.Hour*1))
|
return s.vdb.Create(
|
||||||
|
ctx,
|
||||||
|
verification.NewOTPRequest(*acct.GetID(), model.PurposePasswordReset, "").
|
||||||
|
WithTTL(time.Duration(time.Hour*1)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) UpdateLogin(
|
func (s *service) UpdateLogin(
|
||||||
@@ -239,7 +247,11 @@ func (s *service) UpdateLogin(
|
|||||||
acct *model.Account,
|
acct *model.Account,
|
||||||
newLogin string,
|
newLogin string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
return s.vdb.Create(ctx, *acct.GetID(), model.PurposeEmailChange, newLogin, time.Duration(time.Hour*1))
|
return s.vdb.Create(
|
||||||
|
ctx,
|
||||||
|
verification.NewOTPRequest(*acct.GetID(), model.PurposeEmailChange, newLogin).
|
||||||
|
WithTTL(time.Duration(time.Hour*1)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) JoinOrganization(
|
func (s *service) JoinOrganization(
|
||||||
@@ -350,7 +362,7 @@ func (s *service) DeleteAll(
|
|||||||
accountRef bson.ObjectID,
|
accountRef bson.ObjectID,
|
||||||
) error {
|
) error {
|
||||||
s.logger.Info("Starting complete deletion (organization + account)",
|
s.logger.Info("Starting complete deletion (organization + account)",
|
||||||
mzap.StorableRef(org), mzap.ObjRef("account_ref", accountRef))
|
mzap.StorableRef(org), mzap.AccRef(accountRef))
|
||||||
|
|
||||||
// 1. First delete the organization and all its data
|
// 1. First delete the organization and all its data
|
||||||
if err := s.DeleteOrganization(ctx, org); err != nil {
|
if err := s.DeleteOrganization(ctx, org); err != nil {
|
||||||
@@ -359,11 +371,11 @@ func (s *service) DeleteAll(
|
|||||||
|
|
||||||
// 2. Then delete the account
|
// 2. Then delete the account
|
||||||
if err := s.accountDB.Delete(ctx, accountRef); err != nil {
|
if err := s.accountDB.Delete(ctx, accountRef); err != nil {
|
||||||
s.logger.Warn("Failed to delete account", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
|
s.logger.Warn("Failed to delete account", zap.Error(err), mzap.AccRef(accountRef))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("Complete deletion successful", mzap.StorableRef(org), mzap.ObjRef("account_ref", accountRef))
|
s.logger.Info("Complete deletion successful", mzap.StorableRef(org), mzap.AccRef(accountRef))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ type pendingLoginResponse struct {
|
|||||||
Account accountResponse `json:"account"`
|
Account accountResponse `json:"account"`
|
||||||
PendingToken TokenData `json:"pendingToken"`
|
PendingToken TokenData `json:"pendingToken"`
|
||||||
Destination string `json:"destination"`
|
Destination string `json:"destination"`
|
||||||
TTLSeconds int `json:"ttlSeconds"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *TokenData, destination string, ttlSeconds int) http.HandlerFunc {
|
func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *TokenData, destination string) http.HandlerFunc {
|
||||||
return response.Accepted(
|
return response.Accepted(
|
||||||
logger,
|
logger,
|
||||||
&pendingLoginResponse{
|
&pendingLoginResponse{
|
||||||
@@ -25,7 +24,6 @@ func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *T
|
|||||||
},
|
},
|
||||||
PendingToken: *pendingToken,
|
PendingToken: *pendingToken,
|
||||||
Destination: destination,
|
Destination: destination,
|
||||||
TTLSeconds: ttlSeconds,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package confirmation
|
package verification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
"github.com/tech/sendico/server/interface/api"
|
"github.com/tech/sendico/server/interface/api"
|
||||||
"github.com/tech/sendico/server/internal/server/confirmationimp"
|
"github.com/tech/sendico/server/internal/server/verificationimp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Create(a api.API) (mservice.MicroService, error) {
|
func Create(a api.API) (mservice.MicroService, error) {
|
||||||
return confirmationimp.CreateAPI(a)
|
return verificationimp.CreateAPI(a)
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
"github.com/tech/sendico/server/interface/api"
|
"github.com/tech/sendico/server/interface/api"
|
||||||
"github.com/tech/sendico/server/interface/services/account"
|
"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/invitation"
|
||||||
"github.com/tech/sendico/server/interface/services/ledger"
|
"github.com/tech/sendico/server/interface/services/ledger"
|
||||||
"github.com/tech/sendico/server/interface/services/logo"
|
"github.com/tech/sendico/server/interface/services/logo"
|
||||||
@@ -22,6 +21,7 @@ import (
|
|||||||
"github.com/tech/sendico/server/interface/services/permission"
|
"github.com/tech/sendico/server/interface/services/permission"
|
||||||
"github.com/tech/sendico/server/interface/services/recipient"
|
"github.com/tech/sendico/server/interface/services/recipient"
|
||||||
"github.com/tech/sendico/server/interface/services/site"
|
"github.com/tech/sendico/server/interface/services/site"
|
||||||
|
"github.com/tech/sendico/server/interface/services/verification"
|
||||||
"github.com/tech/sendico/server/interface/services/wallet"
|
"github.com/tech/sendico/server/interface/services/wallet"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -82,7 +82,7 @@ func (a *APIImp) installServices() error {
|
|||||||
srvf := make([]api.MicroServiceFactoryT, 0)
|
srvf := make([]api.MicroServiceFactoryT, 0)
|
||||||
|
|
||||||
srvf = append(srvf, account.Create)
|
srvf = append(srvf, account.Create)
|
||||||
srvf = append(srvf, confirmation.Create)
|
srvf = append(srvf, verification.Create)
|
||||||
srvf = append(srvf, organization.Create)
|
srvf = append(srvf, organization.Create)
|
||||||
srvf = append(srvf, invitation.Create)
|
srvf = append(srvf, invitation.Create)
|
||||||
srvf = append(srvf, logo.Create)
|
srvf = append(srvf, logo.Create)
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func CreateMiddleware(logger mlogger.Logger, db db.Factory, enforcer auth.Enforc
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cdb, err := db.NewConfirmationsDB()
|
cdb, err := db.NewVerificationsDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Error("Failed to create confirmations database", zap.Error(err))
|
p.logger.Error("Failed to create confirmations database", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ func (ar *AuthorizedRouter) tokenHandler(service mservice.Type, endpoint string,
|
|||||||
func (ar *AuthorizedRouter) AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc) {
|
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 {
|
hndlr := func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc {
|
||||||
if t.Pending {
|
if t.Pending {
|
||||||
return response.Forbidden(ar.logger, ar.service, "confirmation_required", "pending token requires confirmation")
|
return response.Unauthorized(ar.logger, ar.service, "additional verification required")
|
||||||
}
|
}
|
||||||
var a model.Account
|
var a model.Account
|
||||||
if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
|
if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.ObjRef("account_ref", t.AccountRef))
|
ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.AccRef(t.AccountRef))
|
||||||
return response.NotFound(ar.logger, ar.service, err.Error())
|
return response.NotFound(ar.logger, ar.service, err.Error())
|
||||||
}
|
}
|
||||||
return response.Internal(ar.logger, ar.service, err)
|
return response.Internal(ar.logger, ar.service, err)
|
||||||
@@ -63,7 +63,7 @@ func (ar *AuthorizedRouter) PendingAccountHandler(service mservice.Type, endpoin
|
|||||||
var a model.Account
|
var a model.Account
|
||||||
if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
|
if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.ObjRef("account_ref", t.AccountRef))
|
ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.AccRef(t.AccountRef))
|
||||||
return response.NotFound(ar.logger, ar.service, err.Error())
|
return response.NotFound(ar.logger, ar.service, err.Error())
|
||||||
}
|
}
|
||||||
return response.Internal(ar.logger, ar.service, err)
|
return response.Internal(ar.logger, ar.service, err)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
api "github.com/tech/sendico/pkg/api/http"
|
api "github.com/tech/sendico/pkg/api/http"
|
||||||
"github.com/tech/sendico/pkg/auth"
|
"github.com/tech/sendico/pkg/auth"
|
||||||
"github.com/tech/sendico/pkg/db/account"
|
"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/db/refreshtokens"
|
||||||
|
"github.com/tech/sendico/pkg/db/verification"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||||
@@ -36,7 +36,7 @@ func (d *Dispatcher) PendingAccountHandler(service mservice.Type, endpoint strin
|
|||||||
d.protected.PendingAccountHandler(service, endpoint, method, handler)
|
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 {
|
func NewDispatcher(logger mlogger.Logger, router chi.Router, db account.DB, vdb verification.DB, rtdb refreshtokens.DB, enforcer auth.Enforcer, config *middleware.Config) *Dispatcher {
|
||||||
d := &Dispatcher{
|
d := &Dispatcher{
|
||||||
logger: logger.Named("api_dispatcher"),
|
logger: logger.Named("api_dispatcher"),
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ func NewDispatcher(logger mlogger.Logger, router chi.Router, db account.DB, cdb
|
|||||||
endpoint := os.Getenv(config.EndPointEnv)
|
endpoint := os.Getenv(config.EndPointEnv)
|
||||||
signature := middleware.SignatureConf(config)
|
signature := middleware.SignatureConf(config)
|
||||||
router.Group(func(r chi.Router) {
|
router.Group(func(r chi.Router) {
|
||||||
d.public = rpublic.NewRouter(d.logger, endpoint, db, cdb, rtdb, r, &config.Token, &signature)
|
d.public = rpublic.NewRouter(d.logger, endpoint, db, vdb, rtdb, r, &config.Token, &signature)
|
||||||
})
|
})
|
||||||
router.Group(func(r chi.Router) {
|
router.Group(func(r chi.Router) {
|
||||||
d.protected = rauthorized.NewRouter(d.logger, endpoint, r, db, enforcer, &config.Token, &signature)
|
d.protected = rauthorized.NewRouter(d.logger, endpoint, r, db, enforcer, &config.Token, &signature)
|
||||||
|
|||||||
@@ -6,15 +6,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/api/http/response"
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/model"
|
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
"github.com/tech/sendico/pkg/mutil/mask"
|
||||||
"github.com/tech/sendico/server/interface/api/srequest"
|
"github.com/tech/sendico/server/interface/api/srequest"
|
||||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||||
"github.com/tech/sendico/server/internal/server/confirmationimp"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,15 +45,7 @@ func (pr *PublicRouter) logUserIn(ctx context.Context, _ *http.Request, req *sre
|
|||||||
return response.Internal(pr.logger, pr.service, err)
|
return response.Internal(pr.logger, pr.service, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := confirmationimp.DefaultConfig()
|
return sresponse.LoginPending(pr.logger, account, &pendingToken, mask.Email(account.Login))
|
||||||
_, 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)))
|
|
||||||
|
|
||||||
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 {
|
func (a *PublicRouter) login(r *http.Request) http.HandlerFunc {
|
||||||
|
|||||||
@@ -1,27 +1,21 @@
|
|||||||
package routers
|
package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
api "github.com/tech/sendico/pkg/api/http"
|
api "github.com/tech/sendico/pkg/api/http"
|
||||||
"github.com/tech/sendico/pkg/db/account"
|
"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/db/refreshtokens"
|
||||||
|
"github.com/tech/sendico/pkg/db/verification"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||||
"github.com/tech/sendico/server/interface/middleware"
|
"github.com/tech/sendico/server/interface/middleware"
|
||||||
re "github.com/tech/sendico/server/internal/api/routers/endpoint"
|
re "github.com/tech/sendico/server/internal/api/routers/endpoint"
|
||||||
"github.com/tech/sendico/server/internal/server/confirmationimp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PublicRouter struct {
|
type PublicRouter struct {
|
||||||
logger mlogger.Logger
|
logger mlogger.Logger
|
||||||
db account.DB
|
db account.DB
|
||||||
cdb confirmation.DB
|
|
||||||
cstore *confirmationimp.ConfirmationStore
|
|
||||||
imp *re.HttpEndpointRouter
|
imp *re.HttpEndpointRouter
|
||||||
rtdb refreshtokens.DB
|
rtdb refreshtokens.DB
|
||||||
config middleware.TokenConfig
|
config middleware.TokenConfig
|
||||||
@@ -33,39 +27,11 @@ func (pr *PublicRouter) InstallHandler(service mservice.Type, endpoint string, m
|
|||||||
pr.imp.InstallHandler(service, endpoint, method, handler)
|
pr.imp.InstallHandler(service, endpoint, method, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *PublicRouter) generateCode() (string, error) {
|
func NewRouter(logger mlogger.Logger, apiEndpoint string, db account.DB, vdb verification.DB, rtdb refreshtokens.DB, router chi.Router, config *middleware.TokenConfig, signature *middleware.Signature) *PublicRouter {
|
||||||
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")
|
l := logger.Named("public")
|
||||||
hr := PublicRouter{
|
hr := PublicRouter{
|
||||||
logger: l,
|
logger: l,
|
||||||
db: db,
|
db: db,
|
||||||
cdb: cdb,
|
|
||||||
cstore: confirmationimp.NewStore(cdb),
|
|
||||||
rtdb: rtdb,
|
rtdb: rtdb,
|
||||||
config: *config,
|
config: *config,
|
||||||
signature: *signature,
|
signature: *signature,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func (pr *PublicRouter) validateRefreshToken(ctx context.Context, _ *http.Reques
|
|||||||
|
|
||||||
var account model.Account
|
var account model.Account
|
||||||
if err := pr.db.Get(ctx, *rt.AccountRef, &account); errors.Is(err, merrors.ErrNoData) {
|
if err := pr.db.Get(ctx, *rt.AccountRef, &account); errors.Is(err, merrors.ErrNoData) {
|
||||||
pr.logger.Info("User not found while rotating refresh token", zap.Error(err), mzap.ObjRef("account_ref", *rt.AccountRef))
|
pr.logger.Info("User not found while rotating refresh token", zap.Error(err), mzap.AccRef(*rt.AccountRef))
|
||||||
return nil, nil, merrors.Unauthorized("user not found")
|
return nil, nil, merrors.Unauthorized("user not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
45
api/server/internal/mutil/verification/verificatoin.go
Normal file
45
api/server/internal/mutil/verification/verificatoin.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package mutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/db/verification"
|
||||||
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MapTokenErrorToResponse(logger mlogger.Logger, service mservice.Type, err error) http.HandlerFunc {
|
||||||
|
if errors.Is(err, verification.ErrTokenNotFound) {
|
||||||
|
logger.Debug("Verification token not found during consume", zap.Error(err))
|
||||||
|
return response.NotFound(logger, service, "No account found associated with given verifcation token")
|
||||||
|
}
|
||||||
|
if errors.Is(err, verification.ErrTokenExpired) {
|
||||||
|
logger.Debug("Verification token expired during consume", zap.Error(err))
|
||||||
|
return response.Gone(logger, service, "token_expired", "verification token has expired")
|
||||||
|
}
|
||||||
|
if errors.Is(err, verification.ErrTokenAlreadyUsed) {
|
||||||
|
logger.Debug("Verification token already used during consume", zap.Error(err))
|
||||||
|
return response.DataConflict(logger, service, "verification token has already been used")
|
||||||
|
}
|
||||||
|
if errors.Is(err, verification.ErrTokenAttemptsExceeded) {
|
||||||
|
logger.Debug("Verification token attempts exceeded", zap.Error(err))
|
||||||
|
return response.Forbidden(logger, service, "code_attempts_exceeded", "verification token has already been used")
|
||||||
|
}
|
||||||
|
if errors.Is(err, verification.ErrCooldownActive) {
|
||||||
|
logger.Debug("Cooldown is still active", zap.Error(err))
|
||||||
|
return response.TooManyRequests(logger, service, "verification token can't be generated yet, cooldown is still active")
|
||||||
|
}
|
||||||
|
if errors.Is(err, verification.ErrIdempotencyConflict) {
|
||||||
|
logger.Debug("Verification idempotency key conflict", zap.Error(err))
|
||||||
|
return response.DataConflict(logger, service, "verification request was already processed")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Unexpected error during token verification", zap.Error(err))
|
||||||
|
return response.Auto(logger, service, err)
|
||||||
|
}
|
||||||
|
logger.Debug("No token verification error found")
|
||||||
|
return response.Success(logger)
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ func (a *AccountAPI) verify(r *http.Request) http.HandlerFunc {
|
|||||||
// Get user
|
// Get user
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
// Delete verification token to confirm account
|
// Delete verification token to confirm account
|
||||||
t, err := a.vdb.Consume(ctx, token)
|
t, err := a.vdb.Consume(ctx, bson.NilObjectID, model.PurposeAccountActivation, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Debug("Failed to consume verification token", zap.Error(err))
|
a.logger.Debug("Failed to consume verification token", zap.Error(err))
|
||||||
return a.mapTokenErrorToResponse(err)
|
return a.mapTokenErrorToResponse(err)
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ func (a *AccountAPI) resetPassword(r *http.Request) http.HandlerFunc {
|
|||||||
return response.Auto(a.logger, a.Name(), err)
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := a.vdb.Consume(ctx, token)
|
t, err := a.vdb.Consume(ctx, accountRef, model.PurposePasswordReset, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to consume password reset token", zap.Error(err), zap.String("token", token))
|
a.logger.Warn("Failed to consume password reset token", zap.Error(err), zap.String("token", token))
|
||||||
return a.mapTokenErrorToResponse(err)
|
return a.mapTokenErrorToResponse(err)
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
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(), "/", api.Post, p.requestCode)
|
|
||||||
a.Register().PendingAccountHandler(p.Name(), "/resend", api.Post, p.resendCode)
|
|
||||||
a.Register().PendingAccountHandler(p.Name(), "/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", 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
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
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/v2/bson"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errConfirmationNotFound confirmationError = "confirmation not found or expired"
|
|
||||||
errConfirmationUsed confirmationError = "confirmation already used"
|
|
||||||
errConfirmationMismatch confirmationError = "confirmation code mismatch"
|
|
||||||
errConfirmationAttemptsExceeded confirmationError = "confirmation attempts exceeded"
|
|
||||||
errConfirmationCooldown confirmationError = "confirmation cooldown active"
|
|
||||||
errConfirmationResendLimit confirmationError = "confirmation resend limit reached"
|
|
||||||
)
|
|
||||||
|
|
||||||
type confirmationError string
|
|
||||||
|
|
||||||
func (e confirmationError) Error() string {
|
|
||||||
return string(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfirmationStore struct {
|
|
||||||
db confirmation.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStore(db confirmation.DB) *ConfirmationStore {
|
|
||||||
return &ConfirmationStore{db: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConfirmationStore) Create(
|
|
||||||
ctx context.Context,
|
|
||||||
accountRef bson.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 bson.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 bson.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 bson.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.ConfirmationCode{
|
|
||||||
AccountRef: accountRef,
|
|
||||||
Destination: destination,
|
|
||||||
Target: target,
|
|
||||||
CodeHash: hashCode(salt, code),
|
|
||||||
Salt: salt,
|
|
||||||
ExpiresAt: now.Add(cfg.TTL),
|
|
||||||
MaxAttempts: cfg.MaxAttempts,
|
|
||||||
ResendLimit: cfg.ResendLimit,
|
|
||||||
CooldownUntil: now.Add(cfg.Cooldown),
|
|
||||||
Used: false,
|
|
||||||
Attempts: 0,
|
|
||||||
ResendCount: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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"`
|
|
||||||
}
|
|
||||||
@@ -33,19 +33,19 @@ func (a *PermissionsAPI) changeRole(r *http.Request, account *model.Account, _ *
|
|||||||
res, err := a.enforcer.Enforce(ctx, a.rolesPermissionRef, account.ID, orgRef, req.AccountRef, model.ActionUpdate)
|
res, err := a.enforcer.Enforce(ctx, a.rolesPermissionRef, account.ID, orgRef, req.AccountRef, model.ActionUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to check permissions while assigning new role", zap.Error(err),
|
a.logger.Warn("Failed to check permissions while assigning new role", zap.Error(err),
|
||||||
mzap.ObjRef("requesting_account_ref", account.ID), mzap.ObjRef("account_ref", req.AccountRef),
|
mzap.ObjRef("requesting_account_ref", account.ID), mzap.AccRef(req.AccountRef),
|
||||||
mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
|
mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
|
||||||
}
|
}
|
||||||
if !res {
|
if !res {
|
||||||
a.logger.Debug("Permission denied to set new role", mzap.ObjRef("requesting_account_ref", account.ID),
|
a.logger.Debug("Permission denied to set new role", mzap.ObjRef("requesting_account_ref", account.ID),
|
||||||
mzap.ObjRef("account_ref", req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
|
mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
|
||||||
return response.AccessDenied(a.logger, a.Name(), "no permission to change user roles")
|
return response.AccessDenied(a.logger, a.Name(), "no permission to change user roles")
|
||||||
}
|
}
|
||||||
|
|
||||||
var roleDescription model.RoleDescription
|
var roleDescription model.RoleDescription
|
||||||
if err := a.rdb.Get(ctx, req.NewRoleDescriptionRef, &roleDescription); err != nil {
|
if err := a.rdb.Get(ctx, req.NewRoleDescriptionRef, &roleDescription); err != nil {
|
||||||
a.logger.Warn("Failed to fetch and validate role description", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
|
a.logger.Warn("Failed to fetch and validate role description", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
|
||||||
mzap.ObjRef("account_ref", req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
|
mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,13 +57,13 @@ func (a *PermissionsAPI) changeRoleImp(ctx context.Context, req *srequest.Change
|
|||||||
// TODO: add check that role revocation won't leave venue without the owner
|
// TODO: add check that role revocation won't leave venue without the owner
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to fetch account roles", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
|
a.logger.Warn("Failed to fetch account roles", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
|
||||||
mzap.ObjRef("account_ref", req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
|
mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
if err := a.manager.Role().Revoke(ctx, role.DescriptionRef, req.AccountRef, organizationRef); err != nil {
|
if err := a.manager.Role().Revoke(ctx, role.DescriptionRef, req.AccountRef, organizationRef); err != nil {
|
||||||
a.logger.Warn("Failed to revoke old role", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
|
a.logger.Warn("Failed to revoke old role", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
|
||||||
mzap.ObjRef("account_ref", req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef),
|
mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef),
|
||||||
mzap.ObjRef("role_ref", role.DescriptionRef))
|
mzap.ObjRef("role_ref", role.DescriptionRef))
|
||||||
// continue...
|
// continue...
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func (a *PermissionsAPI) changeRoleImp(ctx context.Context, req *srequest.Change
|
|||||||
}
|
}
|
||||||
if err := a.manager.Role().Assign(ctx, &role); err != nil {
|
if err := a.manager.Role().Assign(ctx, &role); err != nil {
|
||||||
a.logger.Warn("Failed to assign new role", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
|
a.logger.Warn("Failed to assign new role", zap.Error(err), mzap.ObjRef("requesting_account_ref", account.ID),
|
||||||
mzap.ObjRef("account_ref", req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef),
|
mzap.AccRef(req.AccountRef), mzap.ObjRef("role_description_ref", req.NewRoleDescriptionRef),
|
||||||
mzap.ObjRef("role_ref", req.NewRoleDescriptionRef))
|
mzap.ObjRef("role_ref", req.NewRoleDescriptionRef))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|||||||
57
api/server/internal/server/verificationimp/request.go
Normal file
57
api/server/internal/server/verificationimp/request.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package verificationimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/db/verification"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mutil/mask"
|
||||||
|
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||||
|
emodel "github.com/tech/sendico/server/interface/model"
|
||||||
|
mutil "github.com/tech/sendico/server/internal/mutil/verification"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *VerificationAPI) requestCode(r *http.Request, account *model.Account, token *emodel.AccountToken) http.HandlerFunc {
|
||||||
|
var req verificationCodeRequest
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
purpose, err := model.VPFromString(req.Purpose)
|
||||||
|
if err != nil {
|
||||||
|
return response.BadRequest(a.logger, a.Name(), "invalid_target", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if purpose == model.PurposeLogin && (token == nil || !token.Pending) {
|
||||||
|
return response.Forbidden(a.logger, a.Name(), "pending_token_required", "login confirmation requires pending token")
|
||||||
|
}
|
||||||
|
|
||||||
|
target := a.resolveTarget(req.Destination, account)
|
||||||
|
if target == "" {
|
||||||
|
return response.BadRequest(a.logger, a.Name(), "missing_destination", "email destination is required")
|
||||||
|
}
|
||||||
|
vReq := verification.NewOTPRequest(account.ID, purpose, target).
|
||||||
|
WithTTL(a.config.TTL).
|
||||||
|
WithCooldown(a.config.Cooldown).
|
||||||
|
WithMaxRetries(a.config.ResendLimit).
|
||||||
|
WithIdempotencyKey(req.IdempotencyKey)
|
||||||
|
|
||||||
|
otp, err := a.store.Create(r.Context(), vReq)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to create confirmation code for resend", zap.Error(err), mzap.AccRef(account.ID))
|
||||||
|
return mutil.MapTokenErrorToResponse(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.sendCode(account, purpose, target, otp)
|
||||||
|
|
||||||
|
return response.Accepted(a.logger, verificationResponse{
|
||||||
|
TTLSeconds: int(vReq.Ttl.Seconds()),
|
||||||
|
CooldownSeconds: int(a.config.Cooldown.Seconds()),
|
||||||
|
Destination: mask.Email(target),
|
||||||
|
IdempotencyKey: req.IdempotencyKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user