commit ce482cc998cb1a0f6f33de5a5518297bb29bbc0a Author: Sergey Chubaryan Date: Sat Jul 20 19:55:18 2024 +0300 initial commit diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..efa922d --- /dev/null +++ b/docker-compose.yaml @@ -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: \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..077aeee --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7b5667d --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..7c5da9c --- /dev/null +++ b/main.go @@ -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") +} diff --git a/src/auth_middleware.go b/src/auth_middleware.go new file mode 100644 index 0000000..a08f530 --- /dev/null +++ b/src/auth_middleware.go @@ -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() + } +} diff --git a/src/bcrypt.go b/src/bcrypt.go new file mode 100644 index 0000000..232d78b --- /dev/null +++ b/src/bcrypt.go @@ -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)) +} diff --git a/src/db.go b/src/db.go new file mode 100644 index 0000000..56aa320 --- /dev/null +++ b/src/db.go @@ -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 +} diff --git a/src/dummy_handler.go b/src/dummy_handler.go new file mode 100644 index 0000000..9ad39f0 --- /dev/null +++ b/src/dummy_handler.go @@ -0,0 +1,9 @@ +package src + +import "github.com/gin-gonic/gin" + +func NewDummyHandler() gin.HandlerFunc { + return func(ctx *gin.Context) { + ctx.Status(200) + } +} diff --git a/src/jwt.go b/src/jwt.go new file mode 100644 index 0000000..dfeb3e9 --- /dev/null +++ b/src/jwt.go @@ -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") +} diff --git a/src/model.go b/src/model.go new file mode 100644 index 0000000..b5c1e9c --- /dev/null +++ b/src/model.go @@ -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"` +} diff --git a/src/service.go b/src/service.go new file mode 100644 index 0000000..0abf417 --- /dev/null +++ b/src/service.go @@ -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 +} diff --git a/src/user_create_handler.go b/src/user_create_handler.go new file mode 100644 index 0000000..72b8ad9 --- /dev/null +++ b/src/user_create_handler.go @@ -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(¶ms); 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) + } +} diff --git a/src/user_login_handler.go b/src/user_login_handler.go new file mode 100644 index 0000000..819d990 --- /dev/null +++ b/src/user_login_handler.go @@ -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(¶ms); 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) + } +}