diff --git a/internal/lib/config.go b/config/config.go similarity index 95% rename from internal/lib/config.go rename to config/config.go index 0f3ae84..bbdfce0 100644 --- a/internal/lib/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -package lib +package config type Config struct { Env string `env:"ENV" env-default:"prod"` diff --git a/go.mod b/go.mod index 879da1f..3e36aa5 100644 --- a/go.mod +++ b/go.mod @@ -3,25 +3,71 @@ module git.sch9.ru/new_gate/ms-auth go 1.21.3 require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/google/uuid v1.6.0 github.com/ilyakaznacheev/cleanenv v1.5.0 - github.com/valkey-io/valkey-go v1.0.38 - golang.org/x/crypto v0.24.0 - google.golang.org/grpc v1.64.0 + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go/modules/valkey v0.33.0 + github.com/valkey-io/valkey-go v1.0.41 + go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.25.0 + google.golang.org/grpc v1.64.1 google.golang.org/protobuf v1.34.1 ) require ( - github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/containerd/containerd v1.7.19 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/testcontainers/testcontainers-go v0.33.0 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect ) diff --git a/go.sum b/go.sum index 3c1c147..54224d6 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,62 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE= +github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= @@ -29,45 +73,149 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/valkey-io/valkey-go v1.0.38 h1:0g+ozx+WUGmu18h8SVGoIyxdWp820utVLlFkGnQ3h0c= -github.com/valkey-io/valkey-go v1.0.38/go.mod h1:LXqAbjygRuA1YRocojTslAGx2dQB4p8feaseGviWka4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= +github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= +github.com/testcontainers/testcontainers-go/modules/valkey v0.33.0 h1:yLD+3pvvTQiU0h9Aj6/2D+iaoDDl+IaMm0rGEUxQlZs= +github.com/testcontainers/testcontainers-go/modules/valkey v0.33.0/go.mod h1:q4939OS+DBYCAbwALicXW77GPt5TGrBnVLTlWzZBxOY= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/valkey-io/valkey-go v1.0.41 h1:pWgh9MP24Vl0ANZ0KxEMwB/LHvTUKwlm2SPuWIrSlFw= +github.com/valkey-io/valkey-go v1.0.41/go.mod h1:LXqAbjygRuA1YRocojTslAGx2dQB4p8feaseGviWka4= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +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/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -76,5 +224,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= diff --git a/internal/models/role.go b/internal/models/role.go index d12b785..62f2bd2 100644 --- a/internal/models/role.go +++ b/internal/models/role.go @@ -1,6 +1,8 @@ package models -import "git.sch9.ru/new_gate/ms-auth/internal/lib" +import ( + "git.sch9.ru/new_gate/ms-auth/pkg/utils" +) type Role int32 @@ -40,5 +42,5 @@ func (role Role) Valid() error { case RoleSpectator, RoleParticipant, RoleModerator, RoleAdmin: return nil } - return lib.ErrBadRole + return utils.ErrBadRole } diff --git a/internal/models/session.go b/internal/models/session.go index bc58de2..dd709e2 100644 --- a/internal/models/session.go +++ b/internal/models/session.go @@ -1,7 +1,7 @@ package models import ( - "git.sch9.ru/new_gate/ms-auth/internal/lib" + "git.sch9.ru/new_gate/ms-auth/pkg/utils" "github.com/golang-jwt/jwt" "github.com/google/uuid" ) @@ -13,17 +13,17 @@ type Session struct { func NewSession(userId int32) *Session { return &Session{ - Id: lib.AsStringP(uuid.NewString()), + Id: utils.AsStringP(uuid.NewString()), UserId: &userId, } } func (s Session) Valid() error { if s.Id == nil { - return lib.ErrBadSession + return utils.ErrBadSession } if s.UserId == nil { - return lib.ErrBadSession + return utils.ErrBadSession } return nil } @@ -35,7 +35,7 @@ func (s Session) Token(secret string) (string, error) { refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, s) str, err := refreshToken.SignedString([]byte(secret)) if err != nil { - return "", lib.ErrBadSession + return "", utils.ErrBadSession } return str, nil } @@ -45,7 +45,7 @@ func Parse(tkn string, secret string) (*Session, error) { return []byte(secret), nil }) if err != nil { - return nil, lib.ErrBadSession + return nil, utils.ErrBadSession } session := parsedToken.Claims.(*Session) if err = session.Valid(); err != nil { diff --git a/internal/models/user.go b/internal/models/user.go index 33af6ca..69e6ba7 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -1,7 +1,7 @@ package models import ( - "git.sch9.ru/new_gate/ms-auth/internal/lib" + "git.sch9.ru/new_gate/ms-auth/pkg/utils" "golang.org/x/crypto/bcrypt" "time" ) @@ -19,10 +19,10 @@ type User struct { func (user *User) ValidUsername() error { if user.Username == nil { - return lib.ErrBadUsername + return utils.ErrBadUsername } - err := lib.ValidUsername(*user.Username) + err := utils.ValidUsername(*user.Username) if err != nil { return err } @@ -32,10 +32,10 @@ func (user *User) ValidUsername() error { func (user *User) ValidPassword() error { if user.Password == nil { - return lib.ErrBadHandleOrPassword + return utils.ErrBadHandleOrPassword } - err := lib.ValidPassword(*user.Password) + err := utils.ValidPassword(*user.Password) if err != nil { return err } @@ -45,37 +45,37 @@ func (user *User) ValidPassword() error { func (user *User) ValidEmail() error { if user.Email == nil { - return lib.ErrBadEmail + return utils.ErrBadEmail } - return lib.ValidEmail(*user.Email) + return utils.ValidEmail(*user.Email) } func (user *User) ValidRole() error { if user.Role == nil { - return lib.ErrBadRole + return utils.ErrBadRole } return user.Role.Valid() } func (user *User) HashPassword() error { if user.Password == nil { - return lib.ErrBadHandleOrPassword + return utils.ErrBadHandleOrPassword } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*user.Password), bcrypt.DefaultCost) if err != nil { - return lib.ErrInternal + return utils.ErrInternal } - user.Password = lib.AsStringP(string(hashedPassword)) + user.Password = utils.AsStringP(string(hashedPassword)) return nil } func (user *User) ComparePassword(password string) error { if user.Password == nil { - return lib.ErrBadHandleOrPassword + return utils.ErrBadHandleOrPassword } err := bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)) if err != nil { - return lib.ErrBadHandleOrPassword + return utils.ErrBadHandleOrPassword } return nil } diff --git a/internal/services/session.go b/internal/services/session.go deleted file mode 100644 index 70e1127..0000000 --- a/internal/services/session.go +++ /dev/null @@ -1,103 +0,0 @@ -package services - -import ( - "context" - "git.sch9.ru/new_gate/ms-auth/internal/lib" - "git.sch9.ru/new_gate/ms-auth/internal/models" -) - -type SessionProvider interface { - CreateSession(ctx context.Context, userId int32) error - ReadSessionByToken(ctx context.Context, token string) (*models.Session, error) - ReadSessionByUserId(ctx context.Context, userId int32) (*models.Session, error) - UpdateSession(ctx context.Context, session *models.Session) error - DeleteSessionByToken(ctx context.Context, token string) error - DeleteSessionByUserId(ctx context.Context, userId int32) error -} - -type SessionService struct { - sessionProvider SessionProvider - userProvider UserStorage - cfg lib.Config -} - -func NewSessionService(sessionProvider SessionProvider, userProvider UserStorage, cfg lib.Config) *SessionService { - return &SessionService{ - sessionProvider: sessionProvider, - userProvider: userProvider, - cfg: cfg, - } -} - -func (s *SessionService) Create(ctx context.Context, handle, password string) (*string, error) { - var ( - err error - user *models.User - ) - - if lib.ValidUsername(handle) == nil { - user, err = s.userProvider.ReadUserByUsername(ctx, handle) - } else if lib.ValidEmail(handle) == nil { - user, err = s.userProvider.ReadUserByEmail(ctx, handle) - } else { - return nil, lib.ErrBadHandleOrPassword - } - if err != nil { - return nil, err - } - - err = user.ComparePassword(password) - if err != nil { - return nil, err - } - - //err = s.sessionProvider.CreateSession(ctx, *user.Id) - //if err != nil { - // return nil, err - //} - s.sessionProvider.CreateSession(ctx, *user.Id) // FIXME - - session, err := s.sessionProvider.ReadSessionByUserId(ctx, *user.Id) - if err != nil { - return nil, err - } - - token, err := session.Token(s.cfg.JWTSecret) - if err != nil { - return nil, err - } - - return &token, nil -} - -func (s *SessionService) Read(ctx context.Context, token string) (*int32, error) { - session, err := s.sessionProvider.ReadSessionByToken(ctx, token) - if err != nil { - return nil, err - } - return session.UserId, nil -} - -func (s *SessionService) Update(ctx context.Context, token string) error { - session, err := s.sessionProvider.ReadSessionByToken(ctx, token) - if err != nil { - return err - } - err = s.sessionProvider.UpdateSession(ctx, session) - if err != nil { - return err - } - return nil -} - -func (s *SessionService) Delete(ctx context.Context, token string) error { - session, err := s.sessionProvider.ReadSessionByToken(ctx, token) - if err != nil { - return err - } - err = s.sessionProvider.DeleteSessionByUserId(ctx, *session.UserId) - if err != nil { - return err - } - return nil -} diff --git a/internal/services/user.go b/internal/services/user.go deleted file mode 100644 index 1dfd00f..0000000 --- a/internal/services/user.go +++ /dev/null @@ -1,131 +0,0 @@ -package services - -import ( - "context" - "git.sch9.ru/new_gate/ms-auth/internal/lib" - "git.sch9.ru/new_gate/ms-auth/internal/models" -) - -type UserStorage interface { - CreateUser(ctx context.Context, user *models.User) (int32, error) - ReadUserByEmail(ctx context.Context, email string) (*models.User, error) - ReadUserByUsername(ctx context.Context, username string) (*models.User, error) - ReadUserById(ctx context.Context, id int32) (*models.User, error) - UpdateUser(ctx context.Context, user *models.User) error - DeleteUser(ctx context.Context, id int32) error -} - -type UserService struct { - userProvider UserStorage - sessionProvider SessionProvider - cfg lib.Config -} - -func NewUserService( - userProvider UserStorage, - sessionProvider SessionProvider, - cfg lib.Config, -) *UserService { - return &UserService{ - userProvider: userProvider, - sessionProvider: sessionProvider, - cfg: cfg, - } -} - -func (u *UserService) CreateUser(ctx context.Context, user *models.User) (int32, error) { - me := ctx.Value("user").(*models.User) - - switch *me.Role { - case models.RoleAdmin: - break - case models.RoleModerator: - if !user.Role.AtMost(models.RoleParticipant) { - return 0, lib.ErrNoPermission - } - default: - return 0, lib.ErrNoPermission - } - - return u.userProvider.CreateUser(ctx, user) -} - -func (u *UserService) ReadUserBySessionToken(ctx context.Context, token string) (*models.User, error) { - session, err := u.sessionProvider.ReadSessionByToken(ctx, token) - if err != nil { - return nil, err - } - - return u.userProvider.ReadUserById(ctx, *session.UserId) -} - -func (u *UserService) ReadUser(ctx context.Context, id int32) (*models.User, error) { - return u.userProvider.ReadUserById(ctx, id) -} - -func (u *UserService) ReadUserByEmail(ctx context.Context, email string) (*models.User, error) { - return u.userProvider.ReadUserByEmail(ctx, email) -} - -func (u *UserService) ReadUserByUsername(ctx context.Context, username string) (*models.User, error) { - return u.userProvider.ReadUserByUsername(ctx, username) -} - -func (u *UserService) UpdateUser(ctx context.Context, modifiedUser *models.User) error { - me := ctx.Value("user").(*models.User) - - user, err := u.userProvider.ReadUserById(ctx, *modifiedUser.Id) - if err != nil { - return err - } - - hasAccess := func() bool { - if me.Role.IsAdmin() { - return true - } - if me.Role.IsModerator() { - if !user.Role.AtMost(models.RoleParticipant) { - return false - } - return true - } - if me.Role.IsParticipant() { - if me.Id != user.Id { - return false - } - if modifiedUser.Username != nil { - return false - } - if modifiedUser.Email != nil { - return false - } - if modifiedUser.ExpiresAt != nil { - return false - } - if modifiedUser.Role != nil { - return false - } - return true - } - if me.Role.IsSpectator() { - return false - } - return false - }() - - if !hasAccess { - return lib.ErrNoPermission - } - - return u.userProvider.UpdateUser(ctx, user) -} - -func (u *UserService) DeleteUser(ctx context.Context, id int32) error { - me := ctx.Value("user").(*models.User) - - if *me.Id == id || !me.Role.IsAdmin() { - return lib.ErrNoPermission - } - - return u.userProvider.DeleteUser(ctx, id) -} diff --git a/internal/sessions/delivery.go b/internal/sessions/delivery.go new file mode 100644 index 0000000..1788f4d --- /dev/null +++ b/internal/sessions/delivery.go @@ -0,0 +1,14 @@ +package sessions + +import ( + "context" + sessionv1 "git.sch9.ru/new_gate/ms-auth/pkg/go/gen/proto/session/v1" + "google.golang.org/protobuf/types/known/emptypb" +) + +type SessionHandlers interface { + Create(ctx context.Context, req *sessionv1.CreateSessionRequest) (*sessionv1.CreateSessionResponse, error) + Read(ctx context.Context, req *sessionv1.ReadSessionRequest) (*sessionv1.ReadSessionResponse, error) + Update(ctx context.Context, req *sessionv1.UpdateSessionRequest) (*emptypb.Empty, error) + Delete(ctx context.Context, req *sessionv1.DeleteSessionRequest) (*emptypb.Empty, error) +} diff --git a/internal/sessions/delivery/grpc/handlers.go b/internal/sessions/delivery/grpc/handlers.go new file mode 100644 index 0000000..fec2d79 --- /dev/null +++ b/internal/sessions/delivery/grpc/handlers.go @@ -0,0 +1,89 @@ +package grpc + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/internal/models" + "git.sch9.ru/new_gate/ms-auth/internal/sessions" + "git.sch9.ru/new_gate/ms-auth/internal/users" + sessionv1 "git.sch9.ru/new_gate/ms-auth/pkg/go/gen/proto/session/v1" + "git.sch9.ru/new_gate/ms-auth/pkg/utils" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +type sessionHandlers struct { + sessionv1.UnimplementedSessionServiceServer + sessionUC sessions.UseCase + userUC users.UseCase +} + +func NewSessionHandlers(gserver *grpc.Server, sessionUC sessions.UseCase, userUC users.UseCase) { + handlers := &sessionHandlers{ + sessionUC: sessionUC, + userUC: userUC, + } + + sessionv1.RegisterSessionServiceServer(gserver, handlers) +} + +func (s *sessionHandlers) Create(ctx context.Context, req *sessionv1.CreateSessionRequest) (*sessionv1.CreateSessionResponse, error) { + var ( + err error + user *models.User + ) + + handle := req.GetHandle() + password := req.GetPassword() + + if utils.ValidUsername(handle) == nil { + user, err = s.userUC.ReadUserByUsername(ctx, req.GetHandle()) + } else if utils.ValidEmail(handle) == nil { + user, err = s.userUC.ReadUserByEmail(ctx, handle) + } else { + return nil, utils.ErrBadHandleOrPassword + } + if err != nil { + return nil, err + } + + err = user.ComparePassword(password) + if err != nil { + return nil, err + } + + token, err := s.sessionUC.Create(ctx, *user.Id) + if err != nil { + return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME + } + return &sessionv1.CreateSessionResponse{ + Token: *token, + }, nil +} + +func (s *sessionHandlers) Read(ctx context.Context, req *sessionv1.ReadSessionRequest) (*sessionv1.ReadSessionResponse, error) { + id, err := s.sessionUC.Read(ctx, req.GetToken()) + if err != nil { + return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME + } + return &sessionv1.ReadSessionResponse{ + UserId: *id, + }, nil +} + +func (s *sessionHandlers) Update(ctx context.Context, req *sessionv1.UpdateSessionRequest) (*emptypb.Empty, error) { + err := s.sessionUC.Update(ctx, req.GetToken()) + if err != nil { + return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME + } + return &emptypb.Empty{}, nil +} + +func (s *sessionHandlers) Delete(ctx context.Context, req *sessionv1.DeleteSessionRequest) (*emptypb.Empty, error) { + err := s.sessionUC.Delete(ctx, req.GetToken()) + if err != nil { + return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME + } + return &emptypb.Empty{}, nil +} diff --git a/internal/sessions/repository/valkey_repository.go b/internal/sessions/repository/valkey_repository.go new file mode 100644 index 0000000..54dbade --- /dev/null +++ b/internal/sessions/repository/valkey_repository.go @@ -0,0 +1,126 @@ +package repository + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/config" + "git.sch9.ru/new_gate/ms-auth/internal/models" + "git.sch9.ru/new_gate/ms-auth/pkg/utils" + "github.com/valkey-io/valkey-go" + "go.uber.org/zap" + "time" +) + +type valkeyRepository struct { + db valkey.Client + cfg config.Config + logger *zap.Logger +} + +func NewValkeyRepository(db valkey.Client, cfg config.Config, logger *zap.Logger) *valkeyRepository { + return &valkeyRepository{ + db: db, + cfg: cfg, + logger: logger, + } +} + +const sessionLifetime = time.Minute * 40 + +func (r *valkeyRepository) CreateSession(ctx context.Context, userId int32) error { + session := models.NewSession(userId) + + resp := r.db.Do(ctx, r.db. + B().Set(). + Key(string(*session.UserId)). + Value(*session.Id). + Nx(). + Exat(time.Now().Add(sessionLifetime)). + Build(), + ) + + if err := resp.Error(); err != nil { + return utils.ErrInternal + } + + return nil +} + +func (r *valkeyRepository) ReadSessionByToken(ctx context.Context, token string) (*models.Session, error) { + session, err := models.Parse(token, r.cfg.JWTSecret) + if err != nil { + return nil, err + } + + sessionRecord, err := r.ReadSessionByUserId(ctx, *session.UserId) + if err != nil { + return nil, err + } + + if *session.Id != *sessionRecord.Id { + return nil, utils.ErrInternal + } + + return session, err +} + +func (r *valkeyRepository) ReadSessionByUserId(ctx context.Context, userId int32) (*models.Session, error) { + resp := r.db.Do(ctx, r.db.B().Get().Key(string(userId)).Build()) + if err := resp.Error(); err != nil { + return nil, utils.ErrInternal + } + + id, err := resp.ToString() + if err != nil { + return nil, utils.ErrInternal + } + + return &models.Session{ + Id: &id, + UserId: &userId, + }, err +} + +func (r *valkeyRepository) UpdateSession(ctx context.Context, session *models.Session) error { + resp := r.db.Do(ctx, r.db. + B().Set(). + Key(string(*session.UserId)). + Value(*session.Id). + Xx(). + Exat(time.Now().Add(sessionLifetime)). + Build(), + ) + + if err := resp.Error(); err != nil { + return utils.ErrInternal + } + + return nil +} + +func (r *valkeyRepository) DeleteSessionByToken(ctx context.Context, token string) error { + session, err := models.Parse(token, r.cfg.JWTSecret) + if err != nil { + return err + } + + err = r.DeleteSessionByUserId(ctx, *session.UserId) + if err != nil { + return err + } + + return nil +} + +func (r *valkeyRepository) DeleteSessionByUserId(ctx context.Context, userId int32) error { + resp := r.db.Do(ctx, r.db. + B().Del(). + Key(string(userId)). + Build(), + ) + + if err := resp.Error(); err != nil { + return utils.ErrInternal + } + + return nil +} diff --git a/internal/sessions/repository/valkey_repository_test.go b/internal/sessions/repository/valkey_repository_test.go new file mode 100644 index 0000000..fce1006 --- /dev/null +++ b/internal/sessions/repository/valkey_repository_test.go @@ -0,0 +1,64 @@ +package repository + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/config" + "git.sch9.ru/new_gate/ms-auth/internal/sessions" + vk "git.sch9.ru/new_gate/ms-auth/pkg/external/valkey" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/modules/valkey" + "go.uber.org/zap" + "log" + "testing" +) + +func SetupValkey() (sessions.ValkeyRepository, func()) { + ctx := context.Background() + + valkeyContainer, err := valkey.Run(ctx, + "docker.io/valkey/valkey:7.2.5", + //valkey.WithSnapshotting(10, 1), + //valkey.WithLogLevel(valkey.LogLevelVerbose), + //valkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf")), + ) + + if err != nil { + panic(err) + } + + dsn, err := valkeyContainer.ConnectionString(ctx) + if err != nil { + panic(err) + } + + client, err := vk.NewValkeyClient(dsn) + if err != nil { + panic(err) + } + + cfg := config.Config{ + JWTSecret: "secret", + } + + sessionRepo := NewValkeyRepository(client, cfg, zap.NewNop()) + + closeVC := func() { + if err := valkeyContainer.Stop(ctx, nil); err != nil { + log.Printf("failed to terminate container: %s", err) + } + } + + return sessionRepo, closeVC +} + +func TestValkeyRepository_CreateSession(t *testing.T) { + t.Parallel() + + sessionRepo, closeVC := SetupValkey() + defer closeVC() + + t.Run("valid session creation", func(t *testing.T) { + err := sessionRepo.CreateSession(context.Background(), 1) + require.NoError(t, err) + }) +} diff --git a/internal/sessions/usecase.go b/internal/sessions/usecase.go new file mode 100644 index 0000000..43b56ac --- /dev/null +++ b/internal/sessions/usecase.go @@ -0,0 +1,10 @@ +package sessions + +import "context" + +type UseCase interface { + Create(ctx context.Context, userId int32) (*string, error) + Read(ctx context.Context, token string) (*int32, error) + Update(ctx context.Context, token string) error + Delete(ctx context.Context, token string) error +} diff --git a/internal/sessions/usecase/usecase.go b/internal/sessions/usecase/usecase.go new file mode 100644 index 0000000..0f8bb73 --- /dev/null +++ b/internal/sessions/usecase/usecase.go @@ -0,0 +1,71 @@ +package usecase + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/config" + "git.sch9.ru/new_gate/ms-auth/internal/sessions" +) + +type useCase struct { + sessionRepo sessions.ValkeyRepository + cfg config.Config +} + +func NewUseCase(sessionRepo sessions.ValkeyRepository, cfg config.Config) *useCase { + return &useCase{ + sessionRepo: sessionRepo, + cfg: cfg, + } +} + +func (s *useCase) Create(ctx context.Context, userId int32) (*string, error) { + var ( + err error + ) + + s.sessionRepo.CreateSession(ctx, userId) // FIXME + + session, err := s.sessionRepo.ReadSessionByUserId(ctx, userId) + if err != nil { + return nil, err + } + + token, err := session.Token(s.cfg.JWTSecret) + if err != nil { + return nil, err + } + + return &token, nil +} + +func (s *useCase) Read(ctx context.Context, token string) (*int32, error) { + session, err := s.sessionRepo.ReadSessionByToken(ctx, token) + if err != nil { + return nil, err + } + return session.UserId, nil +} + +func (s *useCase) Update(ctx context.Context, token string) error { + session, err := s.sessionRepo.ReadSessionByToken(ctx, token) + if err != nil { + return err + } + err = s.sessionRepo.UpdateSession(ctx, session) + if err != nil { + return err + } + return nil +} + +func (s *useCase) Delete(ctx context.Context, token string) error { + session, err := s.sessionRepo.ReadSessionByToken(ctx, token) + if err != nil { + return err + } + err = s.sessionRepo.DeleteSessionByUserId(ctx, *session.UserId) + if err != nil { + return err + } + return nil +} diff --git a/internal/sessions/valkey_repository.go b/internal/sessions/valkey_repository.go new file mode 100644 index 0000000..88e16f7 --- /dev/null +++ b/internal/sessions/valkey_repository.go @@ -0,0 +1,15 @@ +package sessions + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/internal/models" +) + +type ValkeyRepository interface { + CreateSession(ctx context.Context, userId int32) error + ReadSessionByToken(ctx context.Context, token string) (*models.Session, error) + ReadSessionByUserId(ctx context.Context, userId int32) (*models.Session, error) + UpdateSession(ctx context.Context, session *models.Session) error + DeleteSessionByToken(ctx context.Context, token string) error + DeleteSessionByUserId(ctx context.Context, userId int32) error +} diff --git a/internal/storage/session.go b/internal/storage/session.go deleted file mode 100644 index 4177fe1..0000000 --- a/internal/storage/session.go +++ /dev/null @@ -1,125 +0,0 @@ -package storage - -import ( - "context" - "git.sch9.ru/new_gate/ms-auth/internal/lib" - "git.sch9.ru/new_gate/ms-auth/internal/models" - "github.com/valkey-io/valkey-go" - "go.uber.org/zap" - "time" -) - -type SessionStorage struct { - db valkey.Client - cfg lib.Config - logger *zap.Logger -} - -func NewSessionStorage(db valkey.Client, cfg lib.Config, logger *zap.Logger) *SessionStorage { - return &SessionStorage{ - db: db, - cfg: cfg, - logger: logger, - } -} - -const sessionLifetime = time.Minute * 40 - -func (storage *SessionStorage) CreateSession(ctx context.Context, userId int32) error { - session := models.NewSession(userId) - - resp := storage.db.Do(ctx, storage.db. - B().Set(). - Key(string(*session.UserId)). - Value(*session.Id). - Nx(). - Exat(time.Now().Add(sessionLifetime)). - Build(), - ) - - if err := resp.Error(); err != nil { - return lib.ErrInternal - } - - return nil -} - -func (storage *SessionStorage) ReadSessionByToken(ctx context.Context, token string) (*models.Session, error) { - session, err := models.Parse(token, storage.cfg.JWTSecret) - if err != nil { - return nil, err - } - - sessionRecord, err := storage.ReadSessionByUserId(ctx, *session.UserId) - if err != nil { - return nil, err - } - - if *session.Id != *sessionRecord.Id { - return nil, lib.ErrInternal - } - - return session, err -} - -func (storage *SessionStorage) ReadSessionByUserId(ctx context.Context, userId int32) (*models.Session, error) { - resp := storage.db.Do(ctx, storage.db.B().Get().Key(string(userId)).Build()) - if err := resp.Error(); err != nil { - return nil, lib.ErrInternal - } - - id, err := resp.ToString() - if err != nil { - return nil, lib.ErrInternal - } - - return &models.Session{ - Id: &id, - UserId: &userId, - }, err -} - -func (storage *SessionStorage) UpdateSession(ctx context.Context, session *models.Session) error { - resp := storage.db.Do(ctx, storage.db. - B().Set(). - Key(string(*session.UserId)). - Value(*session.Id). - Xx(). - Exat(time.Now().Add(sessionLifetime)). - Build(), - ) - - if err := resp.Error(); err != nil { - return lib.ErrInternal - } - - return nil -} - -func (storage *SessionStorage) DeleteSessionByToken(ctx context.Context, token string) error { - session, err := models.Parse(token, storage.cfg.JWTSecret) - if err != nil { - return err - } - - err = storage.DeleteSessionByUserId(ctx, *session.UserId) - if err != nil { - return err - } - - return nil -} - -func (storage *SessionStorage) DeleteSessionByUserId(ctx context.Context, userId int32) error { - resp := storage.db.Do(ctx, storage.db. - B().Del(). - Key(string(userId)). - Build(), - ) - - if err := resp.Error(); err != nil { - return lib.ErrInternal - } - - return nil -} diff --git a/internal/transport/interceptors.go b/internal/transport/interceptors.go deleted file mode 100644 index 24a623f..0000000 --- a/internal/transport/interceptors.go +++ /dev/null @@ -1,36 +0,0 @@ -package transport - -import ( - "context" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -type ReqWithToken interface { - GetToken() string -} - -func (s *AuthServer) AuthInterceptor() grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - reqWithToken, ok := req.(ReqWithToken) - if !ok { - return handler(ctx, req) - } - token := reqWithToken.GetToken() - - userId, err := s.sessionService.Read(ctx, token) - if err != nil { - return nil, status.Errorf(codes.Unauthenticated, "") - } - - user, err := s.userService.ReadUser(ctx, *userId) - if err != nil { - return nil, status.Errorf(codes.Unauthenticated, "") - } - - ctx = context.WithValue(ctx, "user", user) - - return handler(ctx, req) - } -} diff --git a/internal/transport/server.go b/internal/transport/server.go deleted file mode 100644 index eb36cef..0000000 --- a/internal/transport/server.go +++ /dev/null @@ -1,93 +0,0 @@ -package transport - -import ( - "context" - "git.sch9.ru/new_gate/ms-auth/internal/models" - sessionv1 "git.sch9.ru/new_gate/ms-auth/pkg/go/gen/proto/session/v1" - userv1 "git.sch9.ru/new_gate/ms-auth/pkg/go/gen/proto/user/v1" - "go.uber.org/zap" - "google.golang.org/protobuf/types/known/timestamppb" - "net" - "time" - - "google.golang.org/grpc" -) - -type SessionService interface { - Create(ctx context.Context, handle, password string) (*string, error) - Read(ctx context.Context, token string) (*int32, error) - Update(ctx context.Context, token string) error - Delete(ctx context.Context, token string) error -} - -type UserService interface { - CreateUser(ctx context.Context, user *models.User) (int32, error) - ReadUser(ctx context.Context, id int32) (*models.User, error) - UpdateUser(ctx context.Context, user *models.User) error - DeleteUser(ctx context.Context, id int32) error -} - -type AuthServer struct { - sessionv1.UnimplementedSessionServiceServer - sessionService SessionService - - userv1.UnimplementedUserServiceServer - userService UserService - - GRPCServer *grpc.Server - logger *zap.Logger - - ln net.Listener -} - -func NewAuthServer( - sessionService SessionService, - userService UserService, - logger *zap.Logger, - ln net.Listener, -) *AuthServer { - server := &AuthServer{ - sessionService: sessionService, - userService: userService, - logger: logger, - ln: ln, - } - - grpcServer := grpc.NewServer( - grpc.UnaryInterceptor(server.AuthInterceptor()), - ) - - server.GRPCServer = grpcServer - - sessionv1.RegisterSessionServiceServer(server.GRPCServer, server) - - return server -} - -func AsTimeP(t *timestamppb.Timestamp) *time.Time { - if t == nil { - return nil - } - tt := t.AsTime() - return &tt -} - -func AsMRoleP(v userv1.Role) *models.Role { - vv := models.Role(v.Number()) - return &vv -} - -func AsTimestampP(t *time.Time) *timestamppb.Timestamp { - if t == nil { - return nil - } - return timestamppb.New(*t) -} - -func AsRoleP(r *models.Role) *userv1.Role { - if r == nil { - return nil - } - rr := userv1.Role(*r) - return &rr -} diff --git a/internal/transport/session.go b/internal/transport/session.go deleted file mode 100644 index 53d6978..0000000 --- a/internal/transport/session.go +++ /dev/null @@ -1,45 +0,0 @@ -package transport - -import ( - "context" - sessionv1 "git.sch9.ru/new_gate/ms-auth/pkg/go/gen/proto/session/v1" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/emptypb" -) - -func (s *AuthServer) Create(ctx context.Context, req *sessionv1.CreateSessionRequest) (*sessionv1.CreateSessionResponse, error) { - token, err := s.sessionService.Create(ctx, req.GetHandle(), req.GetPassword()) - if err != nil { - return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME - } - return &sessionv1.CreateSessionResponse{ - Token: *token, - }, nil -} - -func (s *AuthServer) Read(ctx context.Context, req *sessionv1.ReadSessionRequest) (*sessionv1.ReadSessionResponse, error) { - id, err := s.sessionService.Read(ctx, req.GetToken()) - if err != nil { - return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME - } - return &sessionv1.ReadSessionResponse{ - UserId: *id, - }, nil -} - -func (s *AuthServer) Update(ctx context.Context, req *sessionv1.UpdateSessionRequest) (*emptypb.Empty, error) { - err := s.sessionService.Update(ctx, req.GetToken()) - if err != nil { - return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME - } - return &emptypb.Empty{}, nil -} - -func (s *AuthServer) Delete(ctx context.Context, req *sessionv1.DeleteSessionRequest) (*emptypb.Empty, error) { - err := s.sessionService.Delete(ctx, req.GetToken()) - if err != nil { - return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME - } - return &emptypb.Empty{}, nil -} diff --git a/internal/transport/user.go b/internal/transport/user.go deleted file mode 100644 index 9b1296c..0000000 --- a/internal/transport/user.go +++ /dev/null @@ -1,88 +0,0 @@ -package transport - -import ( - "context" - "git.sch9.ru/new_gate/ms-auth/internal/lib" - "git.sch9.ru/new_gate/ms-auth/internal/models" - userv1 "git.sch9.ru/new_gate/ms-auth/pkg/go/gen/proto/user/v1" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/emptypb" -) - -func (s *AuthServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) { - user := req.GetUser() - if user == nil { - return nil, status.Errorf(codes.Unknown, "") // FIXME - } - id, err := s.userService.CreateUser( - ctx, - &models.User{ - Username: lib.AsStringP(user.GetUsername()), - Password: lib.AsStringP(user.GetPassword()), - Email: nil, - ExpiresAt: AsTimeP(user.ExpiresAt), - Role: AsMRoleP(user.GetRole()), - }, - ) - if err != nil { - return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME - } - - return &userv1.CreateUserResponse{ - Id: id, - }, nil -} - -func (s *AuthServer) ReadUser(ctx context.Context, req *userv1.ReadUserRequest) (*userv1.ReadUserResponse, error) { - user, err := s.userService.ReadUser( - ctx, - req.GetId(), - ) - if err != nil { - return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME - } - - return &userv1.ReadUserResponse{ - User: &userv1.ReadUserResponse_User{ - Id: *user.Id, - Username: *user.Username, - ExpiresAt: AsTimestampP(user.ExpiresAt), - CreatedAt: AsTimestampP(user.CreatedAt), - Role: *AsRoleP(user.Role), - }, - }, nil -} - -func (s *AuthServer) UpdateUser(ctx context.Context, req *userv1.UpdateUserRequest) (*emptypb.Empty, error) { - user := req.GetUser() - if user == nil { - return nil, status.Errorf(codes.Unknown, "") // FIXME - } - err := s.userService.UpdateUser( - ctx, - &models.User{ - Id: lib.AsInt32P(user.GetId()), - Username: lib.AsStringP(user.GetUsername()), - Password: lib.AsStringP(user.GetPassword()), - Email: nil, - ExpiresAt: AsTimeP(user.ExpiresAt), - Role: AsMRoleP(user.GetRole()), - }, - ) - if err != nil { - return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME - } - return &emptypb.Empty{}, nil -} - -func (s *AuthServer) DeleteUser(ctx context.Context, req *userv1.DeleteUserRequest) (*emptypb.Empty, error) { - err := s.userService.DeleteUser( - ctx, - req.GetId(), - ) - if err != nil { - return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME - } - return &emptypb.Empty{}, nil -} diff --git a/internal/users/delivery.go b/internal/users/delivery.go new file mode 100644 index 0000000..e6cfa06 --- /dev/null +++ b/internal/users/delivery.go @@ -0,0 +1,14 @@ +package users + +import ( + "context" + userv1 "git.sch9.ru/new_gate/ms-auth/pkg/go/gen/proto/user/v1" + "google.golang.org/protobuf/types/known/emptypb" +) + +type UserHandlers interface { + CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) + ReadUser(ctx context.Context, req *userv1.ReadUserRequest) (*userv1.ReadUserResponse, error) + UpdateUser(ctx context.Context, req *userv1.UpdateUserRequest) (*emptypb.Empty, error) + DeleteUser(ctx context.Context, req *userv1.DeleteUserRequest) (*emptypb.Empty, error) +} diff --git a/internal/users/delivery/grpc/handlers.go b/internal/users/delivery/grpc/handlers.go new file mode 100644 index 0000000..5a7eee6 --- /dev/null +++ b/internal/users/delivery/grpc/handlers.go @@ -0,0 +1,116 @@ +package grpc + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/internal/models" + "git.sch9.ru/new_gate/ms-auth/internal/users" + userv1 "git.sch9.ru/new_gate/ms-auth/pkg/go/gen/proto/user/v1" + "git.sch9.ru/new_gate/ms-auth/pkg/utils" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +type userHandlers struct { + userv1.UnimplementedUserServiceServer + userUC users.UseCase +} + +func NewUserHandlers(gserver *grpc.Server, userUC users.UseCase) { + handlers := &userHandlers{ + userUC: userUC, + } + + userv1.RegisterUserServiceServer(gserver, handlers) +} + +func (h *userHandlers) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) { + user := req.GetUser() + if user == nil { + return nil, status.Errorf(codes.Unknown, "") // FIXME + } + id, err := h.userUC.CreateUser( + ctx, + &models.User{ + Username: utils.AsStringP(user.GetUsername()), + Password: utils.AsStringP(user.GetPassword()), + Email: nil, + ExpiresAt: utils.TimeP(user.ExpiresAt), + Role: AsMRoleP(user.GetRole()), + }, + ) + if err != nil { + return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME + } + + return &userv1.CreateUserResponse{ + Id: id, + }, nil +} + +func (h *userHandlers) ReadUser(ctx context.Context, req *userv1.ReadUserRequest) (*userv1.ReadUserResponse, error) { + user, err := h.userUC.ReadUser( + ctx, + req.GetId(), + ) + if err != nil { + return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME + } + + return &userv1.ReadUserResponse{ + User: &userv1.ReadUserResponse_User{ + Id: *user.Id, + Username: *user.Username, + ExpiresAt: utils.TimestampP(user.ExpiresAt), + CreatedAt: utils.TimestampP(user.CreatedAt), + Role: *AsRoleP(user.Role), + }, + }, nil +} + +func (h *userHandlers) UpdateUser(ctx context.Context, req *userv1.UpdateUserRequest) (*emptypb.Empty, error) { + user := req.GetUser() + if user == nil { + return nil, status.Errorf(codes.Unknown, "") // FIXME + } + err := h.userUC.UpdateUser( + ctx, + &models.User{ + Id: utils.AsInt32P(user.GetId()), + Username: utils.AsStringP(user.GetUsername()), + Password: utils.AsStringP(user.GetPassword()), + Email: nil, + ExpiresAt: utils.TimeP(user.ExpiresAt), + Role: AsMRoleP(user.GetRole()), + }, + ) + if err != nil { + return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME + } + return &emptypb.Empty{}, nil +} + +func (h *userHandlers) DeleteUser(ctx context.Context, req *userv1.DeleteUserRequest) (*emptypb.Empty, error) { + err := h.userUC.DeleteUser( + ctx, + req.GetId(), + ) + if err != nil { + return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME + } + return &emptypb.Empty{}, nil +} + +func AsMRoleP(v userv1.Role) *models.Role { + vv := models.Role(v.Number()) + return &vv +} + +func AsRoleP(r *models.Role) *userv1.Role { + if r == nil { + return nil + } + rr := userv1.Role(*r) + return &rr +} diff --git a/internal/users/delivery/grpc/token_interceptor.go b/internal/users/delivery/grpc/token_interceptor.go new file mode 100644 index 0000000..9adf82d --- /dev/null +++ b/internal/users/delivery/grpc/token_interceptor.go @@ -0,0 +1,30 @@ +package grpc + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/internal/sessions" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +func TokenInterceptor(s sessions.UseCase) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return handler(ctx, req) + } + auth := md.Get("authorization") + if len(auth) == 0 { + return handler(ctx, req) + } + + userId, err := s.Read(ctx, auth[0]) + if err != nil { + return handler(ctx, req) + } + + ctx = context.WithValue(ctx, "userId", *userId) + + return handler(ctx, req) + } +} diff --git a/internal/users/pg_repository.go b/internal/users/pg_repository.go new file mode 100644 index 0000000..4fe5b2c --- /dev/null +++ b/internal/users/pg_repository.go @@ -0,0 +1,15 @@ +package users + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/internal/models" +) + +type PgRepository interface { + CreateUser(ctx context.Context, user *models.User) (int32, error) + ReadUserByEmail(ctx context.Context, email string) (*models.User, error) + ReadUserByUsername(ctx context.Context, username string) (*models.User, error) + ReadUserById(ctx context.Context, id int32) (*models.User, error) + UpdateUser(ctx context.Context, user *models.User) error + DeleteUser(ctx context.Context, id int32) error +} diff --git a/internal/storage/user.go b/internal/users/repository/pg_repository.go similarity index 57% rename from internal/storage/user.go rename to internal/users/repository/pg_repository.go index 6400525..b3216ce 100644 --- a/internal/storage/user.go +++ b/internal/users/repository/pg_repository.go @@ -1,10 +1,10 @@ -package storage +package repository import ( "context" "errors" - "git.sch9.ru/new_gate/ms-auth/internal/lib" "git.sch9.ru/new_gate/ms-auth/internal/models" + "git.sch9.ru/new_gate/ms-auth/pkg/utils" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" "github.com/jmoiron/sqlx" @@ -12,13 +12,13 @@ import ( "time" ) -type UserStorage struct { +type UsersRepository struct { db *sqlx.DB logger *zap.Logger } -func NewUserStorage(db *sqlx.DB, logger *zap.Logger) *UserStorage { - return &UserStorage{ +func NewUserRepository(db *sqlx.DB, logger *zap.Logger) *UsersRepository { + return &UsersRepository{ db: db, logger: logger, } @@ -26,7 +26,14 @@ func NewUserStorage(db *sqlx.DB, logger *zap.Logger) *UserStorage { const year = time.Hour * 24 * 365 -func (storage *UserStorage) CreateUser(ctx context.Context, user *models.User) (int32, error) { +const createUser = ` +INSERT INTO users + (username, hashed_pwd, email, expires_at, role) +VALUES (?, ?, ?, ?, ?) +RETURNING id +` + +func (storage *UsersRepository) CreateUser(ctx context.Context, user *models.User) (int32, error) { if err := user.ValidUsername(); err != nil { return 0, err } @@ -39,16 +46,13 @@ func (storage *UserStorage) CreateUser(ctx context.Context, user *models.User) ( if err := user.ValidRole(); err != nil { return 0, err } - if err := user.HashPassword(); err != nil { + if err := user.HashPassword(); err != nil { // FIXME: get rid of mutation return 0, err } - query := storage.db.Rebind(` -INSERT INTO users - (username, hashed_pwd, email, expires_at, role) -VALUES (?, ?, ?, ?, ?) -RETURNING id -`) + user.ExpiresAt = utils.AsTimeP(time.Now().Add(100 * year)) // FIXME: get rid of mutation + + query := storage.db.Rebind(createUser) rows, err := storage.db.QueryxContext( ctx, @@ -56,7 +60,7 @@ RETURNING id user.Username, user.Password, user.Email, - time.Now().Add(100*year), + user.ExpiresAt, user.Role, ) if err != nil { @@ -65,7 +69,8 @@ RETURNING id defer rows.Close() var id int32 - err = rows.StructScan(&id) + rows.Next() + err = rows.Scan(&id) if err != nil { return 0, storage.handlePgErr(err) } @@ -73,9 +78,11 @@ RETURNING id return id, nil } -func (storage *UserStorage) ReadUserByEmail(ctx context.Context, email string) (*models.User, error) { +const readUserByEmail = "SELECT * from users WHERE email=? LIMIT 1" + +func (storage *UsersRepository) ReadUserByEmail(ctx context.Context, email string) (*models.User, error) { var user models.User - query := storage.db.Rebind("SELECT * from users WHERE email=? LIMIT 1") + query := storage.db.Rebind(readUserByEmail) err := storage.db.GetContext(ctx, &user, query, email) if err != nil { return nil, storage.handlePgErr(err) @@ -83,9 +90,11 @@ func (storage *UserStorage) ReadUserByEmail(ctx context.Context, email string) ( return &user, nil } -func (storage *UserStorage) ReadUserByUsername(ctx context.Context, username string) (*models.User, error) { +const readUserByUsername = "SELECT * from users WHERE username=? LIMIT 1" + +func (storage *UsersRepository) ReadUserByUsername(ctx context.Context, username string) (*models.User, error) { var user models.User - query := storage.db.Rebind("SELECT * from users WHERE username=? LIMIT 1") + query := storage.db.Rebind(readUserByUsername) err := storage.db.GetContext(ctx, &user, query, username) if err != nil { return nil, storage.handlePgErr(err) @@ -93,9 +102,11 @@ func (storage *UserStorage) ReadUserByUsername(ctx context.Context, username str return &user, nil } -func (storage *UserStorage) ReadUserById(ctx context.Context, id int32) (*models.User, error) { +const readUserById = "SELECT * from users WHERE id=? LIMIT 1" + +func (storage *UsersRepository) ReadUserById(ctx context.Context, id int32) (*models.User, error) { var user models.User - query := storage.db.Rebind("SELECT * from users WHERE id=? LIMIT 1") + query := storage.db.Rebind(readUserById) err := storage.db.GetContext(ctx, &user, query, id) if err != nil { return nil, storage.handlePgErr(err) @@ -103,7 +114,17 @@ func (storage *UserStorage) ReadUserById(ctx context.Context, id int32) (*models return &user, nil } -func (storage *UserStorage) UpdateUser(ctx context.Context, user *models.User) error { +const updateUser = ` +UPDATE users +SET username = COALESCE(?, username), + hashed_pwd = COALESCE(?, hashed_pwd), + email = COALESCE(?, email), + expires_at = COALESCE(?, expires_at), + role = COALESCE(?, role) +WHERE id = ? +` + +func (storage *UsersRepository) UpdateUser(ctx context.Context, user *models.User) error { var err error if user.Username != nil { if err = user.ValidUsername(); err != nil { @@ -119,7 +140,7 @@ func (storage *UserStorage) UpdateUser(ctx context.Context, user *models.User) e } } if user.Email != nil { - if err = lib.ValidEmail(*user.Email); err != nil { + if err = utils.ValidEmail(*user.Email); err != nil { return err } } @@ -129,14 +150,7 @@ func (storage *UserStorage) UpdateUser(ctx context.Context, user *models.User) e } } - query := storage.db.Rebind(` -UPDATE users -SET username = COALESCE(?, username), - hashed_pwd = COALESCE(?, hashed_pwd), - email = COALESCE(?, email), - expires_at = COALESCE(?, expires_at), - role = COALESCE(?, role) -WHERE id = ?`) + query := storage.db.Rebind(updateUser) _, err = storage.db.ExecContext( ctx, @@ -154,8 +168,11 @@ WHERE id = ?`) } return nil } -func (storage *UserStorage) DeleteUser(ctx context.Context, id int32) error { - query := storage.db.Rebind("UPDATE users SET expired_at=now() WHERE id = ?") + +const deleteUser = "UPDATE users SET expired_at=now() WHERE id = ?" + +func (storage *UsersRepository) DeleteUser(ctx context.Context, id int32) error { + query := storage.db.Rebind(deleteUser) _, err := storage.db.ExecContext(ctx, query, id) if err != nil { return storage.handlePgErr(err) @@ -164,15 +181,15 @@ func (storage *UserStorage) DeleteUser(ctx context.Context, id int32) error { return nil } -func (storage *UserStorage) handlePgErr(err error) error { +func (storage *UsersRepository) handlePgErr(err error) error { var pgErr *pgconn.PgError if !errors.As(err, &pgErr) { storage.logger.DPanic("unexpected error from postgres", zap.String("err", err.Error())) - return lib.ErrUnexpected + return utils.ErrUnexpected } if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) { return errors.New("unique key violation") // FIXME } storage.logger.DPanic("unexpected internal error from postgres", zap.String("err", err.Error())) - return lib.ErrInternal + return utils.ErrInternal } diff --git a/internal/users/repository/pg_repository_test.go b/internal/users/repository/pg_repository_test.go new file mode 100644 index 0000000..7721f88 --- /dev/null +++ b/internal/users/repository/pg_repository_test.go @@ -0,0 +1,146 @@ +package repository + +import ( + "context" + "database/sql/driver" + "git.sch9.ru/new_gate/ms-auth/pkg/utils" + "github.com/DATA-DOG/go-sqlmock" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "testing" + "time" + + "git.sch9.ru/new_gate/ms-auth/internal/models" +) + +func TestUsersRepository_CreateUser(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + defer sqlxDB.Close() + + userRepo := NewUserRepository(sqlxDB, zap.NewNop()) + + t.Run("valid user creation", func(t *testing.T) { + rows := sqlmock.NewRows([]string{"id"}).AddRow(1) + + user := &models.User{ + Username: utils.AsStringP("testuser"), + Password: utils.AsStringP("testpassword"), + Email: utils.AsStringP("test@example.com"), + Role: AsRoleP(models.RoleAdmin), + } + + mock.ExpectQuery(sqlxDB.Rebind(createUser)).WithArgs( + user.Username, + AnyString{}, + user.Email, + AnyTime{}, + user.Role, + ).WillReturnRows(rows) + + _, err = userRepo.CreateUser(context.Background(), user) + require.NoError(t, err) + }) + + // TODO: add more tests + // invalid username + // invalid password + // invalid email + // invalid role + // password hashing error + // database query error + // database scan error + // etc +} + +func TestUsersRepository_UpdateUser(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + defer sqlxDB.Close() + + userRepo := NewUserRepository(sqlxDB, zap.NewNop()) + + t.Run("valid user update", func(t *testing.T) { + user := &models.User{ + Id: utils.AsInt32P(1), + Username: utils.AsStringP("testuser"), + Password: utils.AsStringP("testpassword"), + Email: utils.AsStringP("test@example.com"), + ExpiresAt: utils.AsTimeP(time.Now().Add(1 * time.Hour)), + Role: AsRoleP(models.RoleAdmin), + } + + require.NoError(t, err) + + mock.ExpectExec(sqlxDB.Rebind(updateUser)). + WithArgs( + *user.Username, + AnyString{}, + *user.Email, + *user.ExpiresAt, + *user.Role, + *user.Id, + ).WillReturnResult(sqlmock.NewResult(1, 1)) + + err = userRepo.UpdateUser(context.Background(), user) + require.NoError(t, err) + }) + + // TODO: add more tests +} + +func TestUsersRepository_DeleteUser(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + defer sqlxDB.Close() + + userRepo := NewUserRepository(sqlxDB, zap.NewNop()) + + t.Run("valid user deletion", func(t *testing.T) { + user := &models.User{ + Id: utils.AsInt32P(1), + } + + mock.ExpectExec(sqlxDB.Rebind(deleteUser)).WithArgs(*user.Id).WillReturnResult(sqlmock.NewResult(1, 1)) + + err = userRepo.DeleteUser(context.Background(), *user.Id) + require.NoError(t, err) + }) + + // TODO: add more tests +} + +func AsRoleP(r models.Role) *models.Role { + return &r +} + +type AnyTime struct{} + +// Match satisfies sqlmock.Argument interface +func (a AnyTime) Match(v driver.Value) bool { + _, ok := v.(time.Time) + return ok +} + +type AnyString struct{} + +func (a AnyString) Match(v driver.Value) bool { + _, ok := v.(string) + return ok +} diff --git a/internal/users/usecase.go b/internal/users/usecase.go new file mode 100644 index 0000000..d848193 --- /dev/null +++ b/internal/users/usecase.go @@ -0,0 +1,16 @@ +package users + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/internal/models" +) + +type UseCase interface { + CreateUser(ctx context.Context, user *models.User) (int32, error) + ReadUserBySessionToken(ctx context.Context, token string) (*models.User, error) + ReadUser(ctx context.Context, id int32) (*models.User, error) + ReadUserByEmail(ctx context.Context, email string) (*models.User, error) + ReadUserByUsername(ctx context.Context, username string) (*models.User, error) + UpdateUser(ctx context.Context, modifiedUser *models.User) error + DeleteUser(ctx context.Context, id int32) error +} diff --git a/internal/users/usecase/usecase.go b/internal/users/usecase/usecase.go new file mode 100644 index 0000000..d644154 --- /dev/null +++ b/internal/users/usecase/usecase.go @@ -0,0 +1,149 @@ +package usecase + +import ( + "context" + "git.sch9.ru/new_gate/ms-auth/config" + "git.sch9.ru/new_gate/ms-auth/internal/models" + "git.sch9.ru/new_gate/ms-auth/internal/sessions" + "git.sch9.ru/new_gate/ms-auth/internal/users" + "git.sch9.ru/new_gate/ms-auth/pkg/utils" +) + +type useCase struct { + userRepo users.PgRepository + sessionProvider sessions.ValkeyRepository + cfg config.Config +} + +func NewUseCase( + userRepo users.PgRepository, + sessionRepo sessions.ValkeyRepository, + cfg config.Config, +) *useCase { + return &useCase{ + userRepo: userRepo, + sessionProvider: sessionRepo, + cfg: cfg, + } +} + +func (u *useCase) CreateUser(ctx context.Context, user *models.User) (int32, error) { + meId, ok := ctx.Value("userId").(*int32) + if !ok { + return 0, utils.ErrNoPermission + } + + me, err := u.ReadUser(ctx, *meId) + if err != nil { + return 0, err + } + + switch *me.Role { + case models.RoleAdmin: + break + case models.RoleModerator: + if !user.Role.AtMost(models.RoleParticipant) { + return 0, utils.ErrNoPermission + } + default: + return 0, utils.ErrNoPermission + } + + return u.userRepo.CreateUser(ctx, user) +} + +func (u *useCase) ReadUserBySessionToken(ctx context.Context, token string) (*models.User, error) { + session, err := u.sessionProvider.ReadSessionByToken(ctx, token) + if err != nil { + return nil, err + } + + return u.userRepo.ReadUserById(ctx, *session.UserId) +} + +func (u *useCase) ReadUser(ctx context.Context, id int32) (*models.User, error) { + return u.userRepo.ReadUserById(ctx, id) +} + +func (u *useCase) ReadUserByEmail(ctx context.Context, email string) (*models.User, error) { + return u.userRepo.ReadUserByEmail(ctx, email) +} + +func (u *useCase) ReadUserByUsername(ctx context.Context, username string) (*models.User, error) { + return u.userRepo.ReadUserByUsername(ctx, username) +} + +func (u *useCase) UpdateUser(ctx context.Context, modifiedUser *models.User) error { + meId, ok := ctx.Value("userId").(*int32) + if !ok { + return utils.ErrNoPermission + } + + me, err := u.ReadUser(ctx, *meId) + if err != nil { + return err + } + + user, err := u.userRepo.ReadUserById(ctx, *modifiedUser.Id) + if err != nil { + return err + } + + hasAccess := func() bool { + if me.Role.IsAdmin() { + return true + } + if me.Role.IsModerator() { + if !user.Role.AtMost(models.RoleParticipant) { + return false + } + return true + } + if me.Role.IsParticipant() { + if me.Id != user.Id { + return false + } + if modifiedUser.Username != nil { + return false + } + if modifiedUser.Email != nil { + return false + } + if modifiedUser.ExpiresAt != nil { + return false + } + if modifiedUser.Role != nil { + return false + } + return true + } + if me.Role.IsSpectator() { + return false + } + return false + }() + + if !hasAccess { + return utils.ErrNoPermission + } + + return u.userRepo.UpdateUser(ctx, user) +} + +func (u *useCase) DeleteUser(ctx context.Context, id int32) error { + userId, ok := ctx.Value("userId").(*int32) + if !ok { + return utils.ErrNoPermission + } + + me, err := u.ReadUser(ctx, *userId) + if err != nil { + return err + } + + if *me.Id == id || !me.Role.IsAdmin() { + return utils.ErrNoPermission + } + + return u.userRepo.DeleteUser(ctx, id) +} diff --git a/main.go b/main.go index d5a1278..382f35f 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,20 @@ package main import ( "fmt" - "git.sch9.ru/new_gate/ms-auth/internal/lib" - "git.sch9.ru/new_gate/ms-auth/internal/services" - "git.sch9.ru/new_gate/ms-auth/internal/storage" - "git.sch9.ru/new_gate/ms-auth/internal/transport" + "git.sch9.ru/new_gate/ms-auth/config" + sessionsDelivery "git.sch9.ru/new_gate/ms-auth/internal/sessions/delivery/grpc" + sessionsRepository "git.sch9.ru/new_gate/ms-auth/internal/sessions/repository" + sessionsUseCase "git.sch9.ru/new_gate/ms-auth/internal/sessions/usecase" + usersDelivery "git.sch9.ru/new_gate/ms-auth/internal/users/delivery/grpc" + usersRepository "git.sch9.ru/new_gate/ms-auth/internal/users/repository" + usersUseCase "git.sch9.ru/new_gate/ms-auth/internal/users/usecase" + "git.sch9.ru/new_gate/ms-auth/pkg/external/postgres" + "git.sch9.ru/new_gate/ms-auth/pkg/external/valkey" "github.com/ilyakaznacheev/cleanenv" _ "github.com/jackc/pgx/v5/stdlib" - "github.com/jmoiron/sqlx" - "github.com/valkey-io/valkey-go" "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" "net" "os" "os/signal" @@ -18,7 +23,7 @@ import ( ) func main() { - var cfg lib.Config + var cfg config.Config err := cleanenv.ReadConfig(".env", &cfg) if err != nil { panic(fmt.Sprintf("error reading config: %s", err.Error())) @@ -33,39 +38,33 @@ func main() { panic(fmt.Sprintf(`error reading config: env expected "prod" or "dev", got "%s"`, cfg.Env)) } - db, err := sqlx.Connect("pgx", cfg.PostgresDSN) + db, err := postgres.NewPostgresDB(cfg.PostgresDSN) if err != nil { panic(err) } defer db.Close() - userStorage := storage.NewUserStorage(db, logger) + vk, err := valkey.NewValkeyClient(cfg.RedisDSN) - opts, err := valkey.ParseURL(cfg.RedisDSN) - if err != nil { - panic(err) - } + userRepo := usersRepository.NewUserRepository(db, logger) + userUC := usersUseCase.NewUseCase(userRepo, nil, cfg) - vk, err := valkey.NewClient(opts) - if err != nil { - panic(err) - } - defer vk.Close() + sessionRepo := sessionsRepository.NewValkeyRepository(vk, cfg, logger) + sessionUC := sessionsUseCase.NewUseCase(sessionRepo, cfg) - sessionStorage := storage.NewSessionStorage(vk, cfg, logger) + gserver := grpc.NewServer(grpc.UnaryInterceptor(usersDelivery.TokenInterceptor(sessionUC))) - sessionService := services.NewSessionService(sessionStorage, userStorage, cfg) - userService := services.NewUserService(userStorage, sessionStorage, cfg) + usersDelivery.NewUserHandlers(gserver, userUC) + sessionsDelivery.NewSessionHandlers(gserver, sessionUC, userUC) + reflection.Register(gserver) ln, err := net.Listen("tcp", cfg.Address) if err != nil { panic(err) } - server := transport.NewAuthServer(sessionService, userService, logger, ln) - go func() { - if err = server.GRPCServer.Serve(ln); err != nil { + if err = gserver.Serve(ln); err != nil { panic(err) } }() diff --git a/pkg/external/postgres/postgres.go b/pkg/external/postgres/postgres.go new file mode 100644 index 0000000..b83463e --- /dev/null +++ b/pkg/external/postgres/postgres.go @@ -0,0 +1,30 @@ +package postgres + +import ( + "github.com/jmoiron/sqlx" + "time" +) + +const ( + maxOpenConns = 60 + connMaxLifetime = 120 + maxIdleConns = 30 + connMaxIdleTime = 20 +) + +func NewPostgresDB(dsn string) (*sqlx.DB, error) { + db, err := sqlx.Open("pgx", dsn) + if err != nil { + return nil, err + } + + db.SetMaxOpenConns(maxOpenConns) + db.SetConnMaxLifetime(connMaxLifetime * time.Second) + db.SetMaxIdleConns(maxIdleConns) + db.SetConnMaxIdleTime(connMaxIdleTime * time.Second) + if err = db.Ping(); err != nil { + return nil, err + } + + return db, nil +} diff --git a/pkg/external/valkey/valkey.go b/pkg/external/valkey/valkey.go new file mode 100644 index 0000000..c5a4900 --- /dev/null +++ b/pkg/external/valkey/valkey.go @@ -0,0 +1,12 @@ +package valkey + +import "github.com/valkey-io/valkey-go" + +func NewValkeyClient(dsn string) (valkey.Client, error) { + opts, err := valkey.ParseURL(dsn) + if err != nil { + return nil, err + } + + return valkey.NewClient(opts) +} diff --git a/pkg/utils/converter.go b/pkg/utils/converter.go new file mode 100644 index 0000000..4996a5f --- /dev/null +++ b/pkg/utils/converter.go @@ -0,0 +1,33 @@ +package utils + +import ( + "google.golang.org/protobuf/types/known/timestamppb" + "time" +) + +func TimeP(t *timestamppb.Timestamp) *time.Time { + if t == nil { + return nil + } + tt := t.AsTime() + return &tt +} + +func TimestampP(t *time.Time) *timestamppb.Timestamp { + if t == nil { + return nil + } + return timestamppb.New(*t) +} + +func AsTimeP(t time.Time) *time.Time { + return &t +} + +func AsInt32P(v int32) *int32 { + return &v +} + +func AsStringP(str string) *string { + return &str +} diff --git a/internal/lib/errors.go b/pkg/utils/errors.go similarity index 97% rename from internal/lib/errors.go rename to pkg/utils/errors.go index 43c66ab..3382290 100644 --- a/internal/lib/errors.go +++ b/pkg/utils/errors.go @@ -1,4 +1,4 @@ -package lib +package utils import ( "errors" diff --git a/internal/lib/lib.go b/pkg/utils/validation.go similarity index 73% rename from internal/lib/lib.go rename to pkg/utils/validation.go index 7d03b5f..1806b0e 100644 --- a/internal/lib/lib.go +++ b/pkg/utils/validation.go @@ -1,21 +1,6 @@ -package lib +package utils -import ( - "net/mail" - "time" -) - -func AsTimeP(t time.Time) *time.Time { - return &t -} - -func AsInt32P(v int32) *int32 { - return &v -} - -func AsStringP(str string) *string { - return &str -} +import "net/mail" func ValidPassword(str string) error { if len(str) < 5 {