initial commit

This commit is contained in:
Sergey Chubaryan 2024-07-20 19:55:18 +03:00
commit ce482cc998
13 changed files with 637 additions and 0 deletions

14
docker-compose.yaml Normal file
View File

@ -0,0 +1,14 @@
services:
postgres:
image: postgres
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- 5432:5432
volumes:
- postgres-volume:/var/lib/postgresql
volumes:
postgres-volume:

46
go.mod Normal file
View File

@ -0,0 +1,46 @@
module backend
go 1.22.5
require (
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/jackc/pgx v3.6.2+incompatible
golang.org/x/crypto v0.25.0
)
require (
github.com/bytedance/sonic v1.11.9 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cockroachdb/apd v1.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

119
go.sum Normal file
View File

@ -0,0 +1,119 @@
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
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/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.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/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
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=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

56
main.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"backend/src"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx"
"github.com/jackc/pgx/stdlib"
)
func main() {
key, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
panic(err)
}
keyBytes, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
panic(err)
}
connConf, err := pgx.ParseConnectionString("postgres://postgres:postgres@localhost:5432/postgres")
if err != nil {
panic(err)
}
sqlDb := stdlib.OpenDB(connConf)
if err := sqlDb.Ping(); err != nil {
panic(err)
}
jwtUtil := src.NewJwtUtil(string(keyBytes))
bcryptUtil := src.NewBcrypt()
db := src.NewDB(sqlDb)
userService := src.NewUserService(src.UserServiceDeps{
Jwt: jwtUtil,
Bcrypt: bcryptUtil,
Db: db,
})
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
userGroup := r.Group("/user")
userGroup.POST("/create", src.NewUserCreateHandler(userService))
userGroup.POST("/login", src.NewUserCreateHandler(userService))
dummyGroup := r.Group("/dummy")
dummyGroup.Use(src.NewAuthMiddleware(userService))
dummyGroup.GET("/", src.NewDummyHandler())
r.Run(":8080")
}

26
src/auth_middleware.go Normal file
View File

@ -0,0 +1,26 @@
package src
import (
"fmt"
"github.com/gin-gonic/gin"
)
func NewAuthMiddleware(userService UserService) gin.HandlerFunc {
return func(ctx *gin.Context) {
token := ctx.GetHeader("X-Auth")
if token == "" {
ctx.AbortWithError(403, fmt.Errorf("authorization required"))
return
}
user, err := userService.ValidateToken(ctx, token)
if err == ErrUserWrongToken || err == ErrUserNotExists {
ctx.AbortWithError(403, err)
return
}
ctx.Set("user", user)
ctx.Next()
}
}

23
src/bcrypt.go Normal file
View File

@ -0,0 +1,23 @@
package src
import "golang.org/x/crypto/bcrypt"
type BCryptUtil interface {
HashPassword(password string) (string, error)
IsPasswordsEqual(password, hash string) bool
}
func NewBcrypt() BCryptUtil {
return &bcryptImpl{}
}
type bcryptImpl struct{}
func (b *bcryptImpl) HashPassword(password string) (string, error) {
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), nil
}
func (b *bcryptImpl) IsPasswordsEqual(password, hash string) bool {
return nil == bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}

70
src/db.go Normal file
View File

@ -0,0 +1,70 @@
package src
import (
"context"
"database/sql"
"errors"
)
type DB interface {
CreateUser(ctx context.Context, dto UserDTO) (*UserDTO, error)
GetUserById(ctx context.Context, id string) (*UserDTO, error)
GetUserByLogin(ctx context.Context, login string) (*UserDTO, error)
}
func NewDB(db *sql.DB) DB {
return &dbImpl{db}
}
type dbImpl struct {
db *sql.DB
}
func (d *dbImpl) CreateUser(ctx context.Context, dto UserDTO) (*UserDTO, error) {
query := `insert into users (login, secret, name) values (?, ?, ?) returning id;`
row := d.db.QueryRowContext(ctx, query, dto.Login, dto.Secret, dto.Name)
id := ""
if err := row.Scan(&id); err != nil {
return nil, err
}
return &UserDTO{
Id: id,
Login: dto.Login,
Secret: dto.Secret,
Name: dto.Name,
}, nil
}
func (d *dbImpl) GetUserById(ctx context.Context, id string) (*UserDTO, error) {
query := `select (id, login, secret, name) from users where id = ?;`
row := d.db.QueryRowContext(ctx, query, id)
dto := &UserDTO{}
err := row.Scan(dto)
if err == nil {
return dto, nil
}
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}
func (d *dbImpl) GetUserByLogin(ctx context.Context, login string) (*UserDTO, error) {
query := `select (id, login, secret, name) from users where login = ?;`
row := d.db.QueryRowContext(ctx, query, login)
dto := &UserDTO{}
err := row.Scan(dto)
if err == nil {
return dto, nil
}
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}

9
src/dummy_handler.go Normal file
View File

@ -0,0 +1,9 @@
package src
import "github.com/gin-gonic/gin"
func NewDummyHandler() gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Status(200)
}
}

52
src/jwt.go Normal file
View File

@ -0,0 +1,52 @@
package src
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
type JwtPayload struct {
jwt.RegisteredClaims
UserId string `json:"userId"`
}
type JwtUtil interface {
Create(user UserDTO) (string, error)
Parse(tokenStr string) (JwtPayload, error)
}
func NewJwtUtil(privateKey string) JwtUtil {
return &jwtUtil{
privateKey: privateKey,
}
}
type jwtUtil struct {
privateKey string
}
func (j *jwtUtil) Create(user UserDTO) (string, error) {
payload := JwtPayload{UserId: user.Id}
token := jwt.NewWithClaims(&jwt.SigningMethodHMAC{}, payload)
tokenStr, err := token.SignedString(j.privateKey)
if err != nil {
return "", err
}
return tokenStr, nil
}
func (j *jwtUtil) Parse(tokenStr string) (JwtPayload, error) {
token, err := jwt.ParseWithClaims(tokenStr, JwtPayload{}, func(t *jwt.Token) (interface{}, error) {
return j.privateKey, nil
})
if err != nil {
return JwtPayload{}, err
}
if payload, ok := token.Claims.(JwtPayload); ok {
return payload, nil
}
return JwtPayload{}, fmt.Errorf("cant get payload")
}

15
src/model.go Normal file
View File

@ -0,0 +1,15 @@
package src
type UserDTO struct {
Id string
Login string
Secret string
Name string
}
type UserDAO struct {
Id string `json:"id"`
Login string `json:"login"`
Secret string `json:"secret"`
Name string `json:"name"`
}

106
src/service.go Normal file
View File

@ -0,0 +1,106 @@
package src
import (
"context"
"fmt"
)
var (
ErrUserNotExists = fmt.Errorf("no such user")
ErrUserExists = fmt.Errorf("user with this login already exists")
ErrUserWrongPassword = fmt.Errorf("wrong password")
ErrUserWrongToken = fmt.Errorf("bad user token")
// ErrUserInternal = fmt.Errorf("unexpected error. contact tech support")
)
type UserService interface {
CreateUser(ctx context.Context, params UserCreateParams) (*UserDTO, error)
AuthenticateUser(ctx context.Context, login, password string) (string, error)
ValidateToken(ctx context.Context, tokenStr string) (*UserDTO, error)
}
func NewUserService(deps UserServiceDeps) UserService {
return &userService{deps}
}
type UserServiceDeps struct {
Db DB
Jwt JwtUtil
Bcrypt BCryptUtil
}
type userService struct {
deps UserServiceDeps
}
type UserCreateParams struct {
Login string
Password string
Name string
}
func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (*UserDTO, error) {
exisitngUser, err := u.deps.Db.GetUserByLogin(ctx, params.Login)
if err != nil {
return nil, err
}
if exisitngUser != nil {
return nil, ErrUserExists
}
secret, err := u.deps.Bcrypt.HashPassword(params.Password)
if err != nil {
return nil, err
}
user := UserDTO{
Login: params.Login,
Secret: string(secret),
Name: params.Name,
}
result, err := u.deps.Db.CreateUser(ctx, user)
if err != nil {
return nil, err
}
return result, nil
}
func (u *userService) AuthenticateUser(ctx context.Context, login, password string) (string, error) {
user, err := u.deps.Db.GetUserByLogin(ctx, login)
if err != nil {
return "", err
}
if user == nil {
return "", ErrUserNotExists
}
if !u.deps.Bcrypt.IsPasswordsEqual(password, user.Secret) {
return "", ErrUserWrongPassword
}
jwt, err := u.deps.Jwt.Create(*user)
if err != nil {
return "", err
}
return jwt, nil
}
func (u *userService) ValidateToken(ctx context.Context, tokenStr string) (*UserDTO, error) {
payload, err := u.deps.Jwt.Parse(tokenStr)
if err != nil {
return nil, ErrUserWrongToken
}
user, err := u.deps.Db.GetUserById(ctx, payload.UserId)
if err != nil {
return nil, err
}
if user == nil {
return nil, ErrUserNotExists
}
return user, nil
}

View File

@ -0,0 +1,55 @@
package src
import (
"encoding/json"
"github.com/gin-gonic/gin"
)
type createUserInput struct {
Login string
Password string
Name string
}
type createUserOutput struct {
Id string `json:"id"`
Login string `json:"login"`
Name string `json:"name"`
}
func NewUserCreateHandler(userService UserService) gin.HandlerFunc {
return func(ctx *gin.Context) {
params := createUserInput{}
if err := ctx.ShouldBindJSON(&params); err != nil {
ctx.AbortWithError(400, err)
return
}
dto, err := userService.CreateUser(ctx, UserCreateParams{
Login: params.Login,
Password: params.Password,
Name: params.Name,
})
if err == ErrUserExists {
ctx.AbortWithError(400, err)
return
}
if err != nil {
ctx.AbortWithError(500, err)
return
}
resultBody, err := json.Marshal(createUserOutput{
Id: dto.Id,
Login: dto.Login,
Name: dto.Name,
})
if err != nil {
ctx.AbortWithError(500, err)
return
}
ctx.Data(200, "application/json", resultBody)
}
}

46
src/user_login_handler.go Normal file
View File

@ -0,0 +1,46 @@
package src
import (
"encoding/json"
"github.com/gin-gonic/gin"
)
type loginUserInput struct {
Login string
Password string
}
type loginUserOutput struct {
Token string `json:"token"`
}
func NewUserLoginHandler(userService UserService) gin.HandlerFunc {
return func(ctx *gin.Context) {
params := loginUserInput{}
if err := ctx.ShouldBindJSON(&params); err != nil {
ctx.AbortWithError(400, err)
return
}
token, err := userService.AuthenticateUser(ctx, params.Login, params.Password)
if err == ErrUserNotExists || err == ErrUserWrongPassword {
ctx.AbortWithError(400, err)
return
}
if err != nil {
ctx.AbortWithError(500, err)
return
}
resultBody, err := json.Marshal(loginUserOutput{
Token: token,
})
if err != nil {
ctx.AbortWithError(500, err)
return
}
ctx.Data(200, "application/json", resultBody)
}
}