diff --git a/Dockerfile b/Dockerfile index 2910300..c29d125 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.15.6-alpine AS builder +FROM golang:1.16.5-alpine3.13 AS builder # Install missing pkgs RUN apk add --no-cache git diff --git a/README.md b/README.md index 67fc8a7..8ef6fcc 100644 --- a/README.md +++ b/README.md @@ -29,5 +29,7 @@ The account field "role" is a bit special, in that if it contains "admin" as one ## Some useful cURLs Obtain an admin GWT: `curl -d '"api-key-goes-here"' -H "Content-Type: application/json" -i http://localhost:4000/auth/api-key` + Use a bearer token to make a call: `curl -H "Content-Type: application/json" -H "Authorization: bearer your-JWT-token-goes-here" -i http://localhost:4000/account/{accountID}` + Create account: `curl -d '{"name": "Bosse", "password":"Hemligt", "fields": [{ "name":"role", "values":["user"]}]}' -H "Content-Type: application/json" -H "Authorization: bearer your-JWT-token-goes-here" -i http://localhost:4000/account` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ab92cc9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +version: '3.9' + +services: + postgres: + image: postgres:13.3-alpine + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=auth + ports: + - 5432:5432 + + db-migrations: + image: amacneil/dbmate:v1.12.0 + environment: + - DATABASE_URL=postgres://postgres:postgres@postgres:5432/auth?sslmode=disable + volumes: + - "./db:/db:ro" + command: up + profiles: ["migrations"] + depends_on: + - postgres + + auth: + build: . + environment: + - ADMIN_API_KEY=hihi + - DATABASE_URL=postgres://postgres:postgres@postgres:5432/auth?sslmode=disable + - JWT_SHARED_SECRET=hihi + - WEB_BIND_HOST=:4000 + depends_on: + - postgres + ports: + - 4000:4000 \ No newline at end of file diff --git a/go.mod b/go.mod index d6a6638..0911b8d 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,24 @@ module gitlab.larvit.se/power-plan/auth go 1.15 require ( + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 + github.com/arsmn/fiber-swagger/v2 v2.11.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/gofiber/fiber/v2 v2.3.2 - github.com/google/uuid v1.1.2 + github.com/gofiber/fiber/v2 v2.13.0 + github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/google/uuid v1.2.0 + github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd // indirect + github.com/jackc/pgproto3/v2 v2.0.7 // indirect github.com/jackc/pgx/v4 v4.10.1 github.com/joho/godotenv v1.3.0 - github.com/sirupsen/logrus v1.7.0 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.9.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/swaggo/swag v1.7.0 + go.uber.org/atomic v1.8.0 // indirect + go.uber.org/multierr v1.7.0 // indirect + go.uber.org/zap v1.17.0 + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index d15449d..0919f1f 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,50 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= -github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/arsmn/fiber-swagger/v2 v2.11.0 h1:hsifJD38zcarxmgyfBs/OrbAQ3lesqVi/zvsvhcY8Bg= +github.com/arsmn/fiber-swagger/v2 v2.11.0/go.mod h1:GL/gL5i5yq47tfRYg3DKo4fBVbQ7Z2bAvAEiL1AyN1U= 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/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +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/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= +github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/spec v0.19.14 h1:r4fbYFo6N4ZelmSX8G6p+cv/hZRXzcuqQIADGT1iNKM= +github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc= +github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofiber/fiber/v2 v2.3.2 h1:8ecrfzlfTUsboMybK6TQIfPoObmPR1hEoKU7Ni1pElg= -github.com/gofiber/fiber/v2 v2.3.2/go.mod h1:f8BRRIMjMdRyt2qmJ/0Sea3j3rwwfufPrh9WNBRiVZ0= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gofiber/fiber/v2 v2.11.0 h1:97PoVZI3JLlJyfMHFhKZoEHQEfTwOXvhQs2+YoLr9jk= +github.com/gofiber/fiber/v2 v2.11.0/go.mod h1:oZTLWqYnqpMMuF922SjGbsYZsdpE1MCfh416HNdweIM= +github.com/gofiber/fiber/v2 v2.13.0 h1:jJBCPwq+hlsfHRDVsmfu6pbgW85Y8jL9dE+VmTzfE6I= +github.com/gofiber/fiber/v2 v2.13.0/go.mod h1:oZTLWqYnqpMMuF922SjGbsYZsdpE1MCfh416HNdweIM= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -35,8 +60,9 @@ github.com/jackc/pgconn v1.8.0 h1:FmjZ0rOyXTr1wfWs45i4a9vjnjWUAGpMuQLD9OSs+lw= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd h1:eDErF6V/JPJON/B7s68BxwHgfmyOntHJQ8IOaz0x4R8= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= @@ -46,8 +72,9 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.0.6 h1:b1105ZGEMFe7aCvrT1Cca3VoVb4ZFMaFJLJcg/3zD+8= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.7 h1:6Pwi1b3QdY65cuv6SyVO0FgPd5J3Bl7wf/nQQjinHMA= +github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= @@ -76,21 +103,27 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= -github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= +github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -98,64 +131,91 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= +github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E= +github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.18.0 h1:IV0DdMlatq9QO1Cr6wGJPVW1sV1Q8HvZXAIcjorylyM= -github.com/valyala/fasthttp v1.18.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/valyala/fasthttp v1.26.0 h1:k5Tooi31zPG/g8yS6o2RffRO2C9B9Kah9SY8j/S7058= +github.com/valyala/fasthttp v1.26.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.8.0 h1:CUhrE4N1rqSE6FM9ecihEjRkLQu8cDfgDyoOs83mEY4= +go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -164,17 +224,22 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201210223839-7e3030f88018 h1:XKi8B/gRBuTZN1vU9gFsLMm6zVz5FSCDzm8JYACnjy8= -golang.org/x/sys v0.0.0-20201210223839-7e3030f88018/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -182,16 +247,28 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0= +golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 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/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/src/db/accounts.go b/src/db/accounts.go index 8a25c30..7e82907 100644 --- a/src/db/accounts.go +++ b/src/db/accounts.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/google/uuid" - log "github.com/sirupsen/logrus" ) // AccountCreate writes a user to database @@ -15,38 +14,31 @@ func (d Db) AccountCreate(input AccountCreateInput) (CreatedAccount, error) { _, err := d.DbPool.Exec(context.Background(), accountSQL, input.ID, input.Name, input.APIKey, input.Password) if err != nil { if strings.HasPrefix(err.Error(), "ERROR: duplicate key") { - log.WithFields(log.Fields{"name": input.Name}).Debug("Duplicate name in accounts database") + d.Log.Debug("Duplicate name in accounts database", "name", input.Name) } else { - log.Error("Database error when trying to add account: " + err.Error()) + d.Log.Error("Database error when trying to add account", "err", err.Error()) } return CreatedAccount{}, err } - log.WithFields(log.Fields{ - "id": input.ID, - "name": input.Name, - }).Info("Added account to database") + d.Log.Info("Added account to database", "id", input.ID, "name", input.Name) accountFieldsSQL := "INSERT INTO \"accountsFields\" (id, \"accountId\", name, value) VALUES($1,$2,$3,$4);" for _, field := range input.Fields { newFieldID, uuidErr := uuid.NewRandom() if uuidErr != nil { - log.Fatal("Could not create new Uuid, err: " + uuidErr.Error()) + d.Log.Fatal("Could not create new Uuid", "err", uuidErr.Error()) } _, err := d.DbPool.Exec(context.Background(), accountFieldsSQL, newFieldID, input.ID, field.Name, field.Values) if err != nil { if strings.HasPrefix(err.Error(), "ERROR: duplicate key") { - log.Error("Database error when trying to account field: " + err.Error()) + d.Log.Error("Database error when trying to account field", "err", err.Error()) } } - log.WithFields(log.Fields{ - "accountId": input.ID, - "fieldName": field.Name, - "fieldValues": field.Values, - }).Debug("Added account field") + d.Log.Debug("Added account field", "accountId", input.ID, "fieldName", field.Name, "fieldValues", field.Values) } return CreatedAccount{ @@ -58,12 +50,7 @@ func (d Db) AccountCreate(input AccountCreateInput) (CreatedAccount, error) { // AccountGet fetches an account from the database func (d Db) AccountGet(accountID string, APIKey string, Name string) (Account, error) { - logContext := log.WithFields(log.Fields{ - "accountID": accountID, - "APIKey": len(APIKey), - }) - - logContext.Debug("Trying to get account") + d.Log.Debug("Trying to get account", "accountID", accountID, "len(APIKey)", len(APIKey)) var account Account var searchParam string @@ -82,18 +69,18 @@ func (d Db) AccountGet(accountID string, APIKey string, Name string) (Account, e accountErr := d.DbPool.QueryRow(context.Background(), accountSQL, searchParam).Scan(&account.ID, &account.Created, &account.Name, &account.Password) if accountErr != nil { if accountErr.Error() == "no rows in result set" { - logContext.Debug("No account found") + d.Log.Debug("No account found", "accountID", accountID, "APIKey", len(APIKey)) return Account{}, accountErr } - logContext.Error("Database error when fetching account, err: " + accountErr.Error()) + d.Log.Error("Database error when fetching account", "err", accountErr.Error(), "accountID", accountID, "APIKey", len(APIKey)) return Account{}, accountErr } fieldsSQL := "SELECT name, value FROM \"accountsFields\" WHERE \"accountId\" = $1" rows, fieldsErr := d.DbPool.Query(context.Background(), fieldsSQL, account.ID) if fieldsErr != nil { - logContext.Error("Database error when fetching account fields, err: " + accountErr.Error()) + d.Log.Error("Database error when fetching account fields", "err", accountErr.Error(), "accountID", accountID, "APIKey", len(APIKey)) return Account{}, fieldsErr } @@ -103,7 +90,7 @@ func (d Db) AccountGet(accountID string, APIKey string, Name string) (Account, e var value []string err := rows.Scan(&name, &value) if err != nil { - logContext.Error("Could not get name or value from database row, err: " + err.Error()) + d.Log.Error("Could not get name or value from database row", "err", err.Error(), "accountID", accountID, "APIKey", len(APIKey)) return Account{}, err } account.Fields[name] = value diff --git a/src/db/renewalTokens.go b/src/db/renewalTokens.go index 1aa7e02..28778fd 100644 --- a/src/db/renewalTokens.go +++ b/src/db/renewalTokens.go @@ -3,22 +3,19 @@ package db import ( "context" - log "github.com/sirupsen/logrus" "gitlab.larvit.se/power-plan/auth/src/utils" ) // RenewalTokenCreate obtain a new renewal token func (d Db) RenewalTokenCreate(accountID string) (string, error) { - logContext := log.WithFields(log.Fields{"accountID": accountID}) - - logContext.Debug("Creating new renewal token") + d.Log.Debug("Creating new renewal token", "accountID", accountID) newToken := utils.RandString(60) insertSQL := "INSERT INTO \"renewalTokens\" (\"accountId\",token) VALUES($1,$2);" _, insertErr := d.DbPool.Exec(context.Background(), insertSQL, accountID, newToken) if insertErr != nil { - logContext.Error("Could not insert into database table \"renewalTokens\", err: " + insertErr.Error()) + d.Log.Error("Could not insert into database table \"renewalTokens\"", "err", insertErr.Error(), "accountID", accountID) return "", insertErr } @@ -27,7 +24,7 @@ func (d Db) RenewalTokenCreate(accountID string) (string, error) { // RenewalTokenGet checks if a valid renewal token exists in database func (d Db) RenewalTokenGet(token string) (string, error) { - log.Debug("Trying to get a renewal token") + d.Log.Debug("Trying to get a renewal token") sql := "SELECT \"accountId\" FROM \"renewalTokens\" WHERE exp >= now() AND token = $1" @@ -38,7 +35,7 @@ func (d Db) RenewalTokenGet(token string) (string, error) { return "", nil } - log.Error("Database error when fetching renewal token, err: " + err.Error()) + d.Log.Error("Database error when fetching renewal token", "err", err.Error()) return "", err } @@ -47,12 +44,12 @@ func (d Db) RenewalTokenGet(token string) (string, error) { // RenewalTokenRm removes a renewal token from the database func (d Db) RenewalTokenRm(token string) error { - log.Debug("Trying to remove a renewal token") + d.Log.Debug("Trying to remove a renewal token") sql := "DELETE FROM \"renewalTokens\" WHERE token = $1" _, err := d.DbPool.Exec(context.Background(), sql, token) if err != nil { - log.Error("Database error when trying to remove token, err: " + err.Error()) + d.Log.Error("Database error when trying to remove token", "err", err.Error()) return err } diff --git a/src/db/types.go b/src/db/types.go index 848f095..a4b0e9e 100644 --- a/src/db/types.go +++ b/src/db/types.go @@ -5,6 +5,7 @@ import ( "github.com/google/uuid" "github.com/jackc/pgx/v4/pgxpool" + "go.uber.org/zap" ) // Account is an account as represented in the database @@ -41,4 +42,5 @@ type AccountCreateInput struct { // Db struct type Db struct { DbPool *pgxpool.Pool + Log *zap.SugaredLogger } diff --git a/src/docs/docs.go b/src/docs/docs.go new file mode 100644 index 0000000..b6e7e27 --- /dev/null +++ b/src/docs/docs.go @@ -0,0 +1,366 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Power Plan", + "url": "https://http://pwrpln.com/", + "email": "lilleman@larvit.se" + }, + "license": { + "name": "MIT" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/account": { + "post": { + "description": "Create an account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create an account", + "operationId": "account-create", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.CreatedAccount" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + }, + "/account/{id}": { + "get": { + "description": "Get account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get account", + "operationId": "get-account-by-id", + "parameters": [ + { + "type": "string", + "description": "Account ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.Account" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + }, + "/auth/api-key": { + "post": { + "description": "Authenticate account by API Key", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Authenticate account by API Key", + "operationId": "auth-account-by-api-key", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.Account" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + }, + "/auth/password": { + "post": { + "description": "Authenticate account by Password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Authenticate account by Password", + "operationId": "auth-account-by-password", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.Account" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + }, + "/renew-token": { + "post": { + "description": "Renew token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Renew token", + "operationId": "renew-token", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.Account" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + } + }, + "definitions": { + "db.Account": { + "type": "object", + "properties": { + "created": { + "type": "string" + }, + "fields": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "db.CreatedAccount": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "handlers.ResJSONError": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "field": { + "type": "string" + } + } + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "0.1", + Host: "localhost:3000", + BasePath: "/", + Schemes: []string{}, + Title: "JWT Auth API", + Description: "This is a tiny http API for auth. Register accounts, auth with api-key or name/password, renew JWT tokens...", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/src/docs/swagger.json b/src/docs/swagger.json new file mode 100644 index 0000000..c4b76c9 --- /dev/null +++ b/src/docs/swagger.json @@ -0,0 +1,304 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a tiny http API for auth. Register accounts, auth with api-key or name/password, renew JWT tokens...", + "title": "JWT Auth API", + "contact": { + "name": "Power Plan", + "url": "https://http://pwrpln.com/", + "email": "lilleman@larvit.se" + }, + "license": { + "name": "MIT" + }, + "version": "0.1" + }, + "host": "localhost:3000", + "basePath": "/", + "paths": { + "/account": { + "post": { + "description": "Create an account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create an account", + "operationId": "account-create", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.CreatedAccount" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + }, + "/account/{id}": { + "get": { + "description": "Get account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get account", + "operationId": "get-account-by-id", + "parameters": [ + { + "type": "string", + "description": "Account ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.Account" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + }, + "/auth/api-key": { + "post": { + "description": "Authenticate account by API Key", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Authenticate account by API Key", + "operationId": "auth-account-by-api-key", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.Account" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + }, + "/auth/password": { + "post": { + "description": "Authenticate account by Password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Authenticate account by Password", + "operationId": "auth-account-by-password", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.Account" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + }, + "/renew-token": { + "post": { + "description": "Renew token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Renew token", + "operationId": "renew-token", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/db.Account" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "415": { + "description": "Unsupported Media Type", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handlers.ResJSONError" + } + } + } + } + } + }, + "definitions": { + "db.Account": { + "type": "object", + "properties": { + "created": { + "type": "string" + }, + "fields": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "db.CreatedAccount": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "handlers.ResJSONError": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "field": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml new file mode 100644 index 0000000..96cc56e --- /dev/null +++ b/src/docs/swagger.yaml @@ -0,0 +1,203 @@ +basePath: / +definitions: + db.Account: + properties: + created: + type: string + fields: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + name: + type: string + type: object + db.CreatedAccount: + properties: + apiKey: + type: string + id: + type: string + name: + type: string + type: object + handlers.ResJSONError: + properties: + error: + type: string + field: + type: string + type: object +host: localhost:3000 +info: + contact: + email: lilleman@larvit.se + name: Power Plan + url: https://http://pwrpln.com/ + description: This is a tiny http API for auth. Register accounts, auth with api-key + or name/password, renew JWT tokens... + license: + name: MIT + title: JWT Auth API + version: "0.1" +paths: + /account: + post: + consumes: + - application/json + description: Create an account + operationId: account-create + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/db.CreatedAccount' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/handlers.ResJSONError' + "403": + description: Forbidden + schema: + $ref: '#/definitions/handlers.ResJSONError' + "415": + description: Unsupported Media Type + schema: + $ref: '#/definitions/handlers.ResJSONError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handlers.ResJSONError' + summary: Create an account + /account/{id}: + get: + consumes: + - application/json + description: Get account + operationId: get-account-by-id + parameters: + - description: Account ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/db.Account' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/handlers.ResJSONError' + "403": + description: Forbidden + schema: + $ref: '#/definitions/handlers.ResJSONError' + "415": + description: Unsupported Media Type + schema: + $ref: '#/definitions/handlers.ResJSONError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handlers.ResJSONError' + summary: Get account + /auth/api-key: + post: + consumes: + - application/json + description: Authenticate account by API Key + operationId: auth-account-by-api-key + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/db.Account' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/handlers.ResJSONError' + "403": + description: Forbidden + schema: + $ref: '#/definitions/handlers.ResJSONError' + "415": + description: Unsupported Media Type + schema: + $ref: '#/definitions/handlers.ResJSONError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handlers.ResJSONError' + summary: Authenticate account by API Key + /auth/password: + post: + consumes: + - application/json + description: Authenticate account by Password + operationId: auth-account-by-password + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/db.Account' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/handlers.ResJSONError' + "403": + description: Forbidden + schema: + $ref: '#/definitions/handlers.ResJSONError' + "415": + description: Unsupported Media Type + schema: + $ref: '#/definitions/handlers.ResJSONError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handlers.ResJSONError' + summary: Authenticate account by Password + /renew-token: + post: + consumes: + - application/json + description: Renew token + operationId: renew-token + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/db.Account' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/handlers.ResJSONError' + "403": + description: Forbidden + schema: + $ref: '#/definitions/handlers.ResJSONError' + "415": + description: Unsupported Media Type + schema: + $ref: '#/definitions/handlers.ResJSONError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handlers.ResJSONError' + summary: Renew token +swagger: "2.0" diff --git a/src/handlers/get.go b/src/handlers/get.go index 44fc0bb..1c4cefe 100644 --- a/src/handlers/get.go +++ b/src/handlers/get.go @@ -4,12 +4,19 @@ import ( "github.com/gofiber/fiber/v2" ) -// Hello handler -func (h Handlers) Hello(c *fiber.Ctx) error { - return c.SendString("Hello, World!") -} - -// AccountGet handler +// AccountGet godoc +// @Summary Get account +// @Description Get account +// @ID get-account-by-id +// @Accept json +// @Produce json +// @Param id path string true "Account ID" +// @Success 200 {object} db.Account +// @Failure 401 {object} ResJSONError +// @Failure 403 {object} ResJSONError +// @Failure 415 {object} ResJSONError +// @Failure 500 {object} ResJSONError +// @Router /account/{id} [get] func (h Handlers) AccountGet(c *fiber.Ctx) error { accountID := c.Params("accountID") // logContext := log.WithFields(log.Fields{"accountID": accountID}) diff --git a/src/handlers/helpers.go b/src/handlers/helpers.go index 5fb609a..7a593ac 100644 --- a/src/handlers/helpers.go +++ b/src/handlers/helpers.go @@ -7,7 +7,6 @@ import ( jwt "github.com/dgrijalva/jwt-go" "github.com/gofiber/fiber/v2" - log "github.com/sirupsen/logrus" "gitlab.larvit.se/power-plan/auth/src/db" ) @@ -26,13 +25,13 @@ func (h Handlers) returnTokens(account db.Account, c *fiber.Ctx) error { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(h.JwtKey) if err != nil { - log.Error("Could not create token string, err: " + err.Error()) + h.Log.Error("Could not create token string", "err", err.Error()) return c.Status(500).JSON([]ResJSONError{{Error: "Could not create JWT token string"}}) } renewalToken, renewalTokenErr := h.Db.RenewalTokenCreate(account.ID.String()) if renewalTokenErr != nil { - log.Error("Could not create renewal token, err: " + renewalTokenErr.Error()) + h.Log.Error("Could not create renewal token", "err", renewalTokenErr.Error()) return c.Status(500).JSON([]ResJSONError{{Error: "Could not create renewal token"}}) } @@ -43,15 +42,14 @@ func (h Handlers) returnTokens(account db.Account, c *fiber.Ctx) error { } func (h Handlers) parseJWT(JWT string) (Claims, error) { - logContext := log.WithFields(log.Fields{"JWT": JWT}) - logContext.Trace("Parsing JWT") + h.Log.Debug("Parsing JWT", "JWT", JWT) - JWT = strings.TrimPrefix(JWT, "bearer ") // Since the Authorization header should always start with "bearer " - logContext.WithFields(log.Fields{"TrimmedJWT": JWT}).Trace("JWT trimmed") + trimmedJWT := strings.TrimPrefix(JWT, "bearer ") // Since the Authorization header should always start with "bearer " + h.Log.Debug("JWT trimmed", "JWT", JWT, "trimmedJWT", trimmedJWT) claims := &Claims{} - token, err := jwt.ParseWithClaims(JWT, claims, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(trimmedJWT, claims, func(token *jwt.Token) (interface{}, error) { return h.JwtKey, nil }) if err != nil { @@ -75,7 +73,7 @@ func (h Handlers) parseHeaders(c *fiber.Ctx) map[string]string { lineParts := strings.Split(line, ": ") if len(lineParts) == 1 { - log.WithFields(log.Fields{"line": line}).Trace("Ignoring header line") + h.Log.Debug("Ignoring header line", "line", line) } else { headersMap[lineParts[0]] = lineParts[1] } diff --git a/src/handlers/middlewares.go b/src/handlers/middlewares.go index 0091c3c..43db5c3 100644 --- a/src/handlers/middlewares.go +++ b/src/handlers/middlewares.go @@ -2,15 +2,11 @@ package handlers import ( "github.com/gofiber/fiber/v2" - log "github.com/sirupsen/logrus" ) // Log all requests -func (h Handlers) Log(c *fiber.Ctx) error { - log.WithFields(log.Fields{ - "method": c.Method(), - "url": c.OriginalURL(), - }).Debug("http request") +func (h Handlers) LogReq(c *fiber.Ctx) error { + h.Log.Debug("http request", "method", c.Method(), "url", c.OriginalURL()) c.Next() return nil @@ -22,7 +18,7 @@ func (h Handlers) RequireJSON(c *fiber.Ctx) error { contentType := string(c.Request().Header.ContentType()) if contentType != "application/json" && contentType != "" { - log.WithFields(log.Fields{"content-type": contentType}).Debug("Invalid content-type in request") + h.Log.Debug("Invalid content-type in request", "content-type", contentType) return c.Status(415).JSON([]ResJSONError{{Error: "Invalid content-type"}}) } diff --git a/src/handlers/post.go b/src/handlers/post.go index 26627ca..b6b33fd 100644 --- a/src/handlers/post.go +++ b/src/handlers/post.go @@ -3,15 +3,24 @@ package handlers import ( "strings" - log "github.com/sirupsen/logrus" - "github.com/gofiber/fiber/v2" "github.com/google/uuid" "gitlab.larvit.se/power-plan/auth/src/db" "gitlab.larvit.se/power-plan/auth/src/utils" ) -// AccountCreate creates a new account +// AccountCreate godoc +// @Summary Create an account +// @Description Create an account +// @ID account-create +// @Accept json +// @Produce json +// @Success 200 {object} db.CreatedAccount +// @Failure 401 {object} ResJSONError +// @Failure 403 {object} ResJSONError +// @Failure 415 {object} ResJSONError +// @Failure 500 {object} ResJSONError +// @Router /account [post] func (h Handlers) AccountCreate(c *fiber.Ctx) error { authErr := h.RequireAdminRole(c) if authErr != nil { @@ -44,12 +53,12 @@ func (h Handlers) AccountCreate(c *fiber.Ctx) error { newAccountID, uuidErr := uuid.NewRandom() if uuidErr != nil { - log.Fatal("Could not create new Uuid, err: " + uuidErr.Error()) + h.Log.Fatal("Could not create new Uuid", "err", uuidErr.Error()) } hashedPwd, pwdErr := utils.HashPassword(accountInput.Password) if pwdErr != nil { - log.Fatal("Could not hash password, err: " + pwdErr.Error()) + h.Log.Fatal("Could not hash password", "err", pwdErr.Error()) } createdAccount, err := h.Db.AccountCreate(db.AccountCreateInput{ @@ -70,7 +79,18 @@ func (h Handlers) AccountCreate(c *fiber.Ctx) error { return c.Status(201).JSON(createdAccount) } -// AccountAuthAPIKey auths an APIKey +// AccountAuthAPIKey godoc +// @Summary Authenticate account by API Key +// @Description Authenticate account by API Key +// @ID auth-account-by-api-key +// @Accept json +// @Produce json +// @Success 200 {object} db.Account +// @Failure 401 {object} ResJSONError +// @Failure 403 {object} ResJSONError +// @Failure 415 {object} ResJSONError +// @Failure 500 {object} ResJSONError +// @Router /auth/api-key [post] func (h Handlers) AccountAuthAPIKey(c *fiber.Ctx) error { inputAPIKey := string(c.Request().Body()) inputAPIKey = inputAPIKey[1 : len(inputAPIKey)-1] @@ -80,14 +100,25 @@ func (h Handlers) AccountAuthAPIKey(c *fiber.Ctx) error { if accountErr.Error() == "no rows in result set" { return c.Status(403).JSON([]ResJSONError{{Error: "Invalid credentials"}}) } - log.Error("Something went wrong when trying to fetch account") + h.Log.Error("Something went wrong when trying to fetch account", "err", accountErr.Error()) return c.Status(500).JSON([]ResJSONError{{Error: "Something went wrong when trying to fetch account"}}) } return h.returnTokens(resolvedAccount, c) } -// AccountAuthPassword auths a name/password pair +// AccountAuthPassword godoc +// @Summary Authenticate account by Password +// @Description Authenticate account by Password +// @ID auth-account-by-password +// @Accept json +// @Produce json +// @Success 200 {object} db.Account +// @Failure 401 {object} ResJSONError +// @Failure 403 {object} ResJSONError +// @Failure 415 {object} ResJSONError +// @Failure 500 {object} ResJSONError +// @Router /auth/password [post] func (h Handlers) AccountAuthPassword(c *fiber.Ctx) error { type AuthInput struct { Name string `json:"name"` @@ -115,8 +146,19 @@ func (h Handlers) AccountAuthPassword(c *fiber.Ctx) error { return h.returnTokens(resolvedAccount, c) } -// TokenRenew creates a new renewal token and JWT from an old renewal token -func (h Handlers) TokenRenew(c *fiber.Ctx) error { +// RenewToken godoc +// @Summary Renew token +// @Description Renew token +// @ID renew-token +// @Accept json +// @Produce json +// @Success 200 {object} db.Account +// @Failure 401 {object} ResJSONError +// @Failure 403 {object} ResJSONError +// @Failure 415 {object} ResJSONError +// @Failure 500 {object} ResJSONError +// @Router /renew-token [post] +func (h Handlers) RenewToken(c *fiber.Ctx) error { inputToken := string(c.Request().Body()) inputToken = inputToken[1 : len(inputToken)-1] @@ -132,13 +174,14 @@ func (h Handlers) TokenRenew(c *fiber.Ctx) error { if accountErr.Error() == "no rows in result set" { return c.Status(500).JSON([]ResJSONError{{Error: "Database missmatch. Token found, but account is missing."}}) } - log.Error("Something went wrong when trying to fetch account") + h.Log.Error("Something went wrong when trying to fetch account", "err", accountErr.Error()) return c.Status(500).JSON([]ResJSONError{{Error: "Something went wrong when trying to fetch account"}}) } rmErr := h.Db.RenewalTokenRm(inputToken) if rmErr != nil { - return c.Status(500).JSON([]ResJSONError{{Error: "Could not remove old token, err: " + rmErr.Error()}}) + h.Log.Error("Something went wrong when trying to fetch account", "err", rmErr.Error()) + return c.Status(500).JSON([]ResJSONError{{Error: "Could not remove old token"}}) } return h.returnTokens(resolvedAccount, c) diff --git a/src/handlers/types.go b/src/handlers/types.go index f3839fb..c3c7810 100644 --- a/src/handlers/types.go +++ b/src/handlers/types.go @@ -3,6 +3,7 @@ package handlers import ( jwt "github.com/dgrijalva/jwt-go" "gitlab.larvit.se/power-plan/auth/src/db" + "go.uber.org/zap" ) // Claims is the JWT struct @@ -17,6 +18,7 @@ type Claims struct { type Handlers struct { Db db.Db JwtKey []byte + Log *zap.SugaredLogger } // ResJSONError is an error field that is used in JSON error responses diff --git a/src/main.go b/src/main.go index 05b4052..d558a33 100644 --- a/src/main.go +++ b/src/main.go @@ -5,19 +5,25 @@ import ( "os" "strings" + swagger "github.com/arsmn/fiber-swagger/v2" "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/jackc/pgx/v4/pgxpool" "github.com/joho/godotenv" - log "github.com/sirupsen/logrus" "gitlab.larvit.se/power-plan/auth/src/db" h "gitlab.larvit.se/power-plan/auth/src/handlers" + "gitlab.larvit.se/power-plan/auth/src/utils" + "go.uber.org/zap" + + // docs are generated by Swag CLI, you have to import them. + _ "gitlab.larvit.se/power-plan/auth/src/docs" ) -func createAdminAccount(Db db.Db) { +// Don't put in utils, because it creates import cycle with db... just left it here for now +func createAdminAccount(Db db.Db, log *zap.SugaredLogger) { adminAccountID, uuidErr := uuid.NewRandom() if uuidErr != nil { - log.Fatal("Could not create new Uuid, err: " + uuidErr.Error()) + log.Fatal("Could not create new Uuid", "err", uuidErr.Error()) } _, adminAccountErr := Db.AccountCreate(db.AccountCreateInput{ ID: adminAccountID, @@ -29,20 +35,30 @@ func createAdminAccount(Db db.Db) { if adminAccountErr != nil && strings.HasPrefix(adminAccountErr.Error(), "ERROR: duplicate key") { log.Info("Admin account already created, nothing written to database") } else if adminAccountErr != nil { - log.Fatal("Could not create admin account, err: " + adminAccountErr.Error()) + log.Fatal("Could not create admin account", "err", adminAccountErr.Error()) } } +// @title JWT Auth API +// @version 0.1 +// @description This is a tiny http API for auth. Register accounts, auth with api-key or name/password, renew JWT tokens... + +// @contact.name Power Plan +// @contact.url https://http://pwrpln.com/ +// @contact.email lilleman@larvit.se + +// @license.name MIT + +// @host localhost:3000 +// @BasePath / func main() { + log := utils.GetLog() + err := godotenv.Load() if err != nil { - log.Warn("Error loading .env file, this could be ok if the env file does not exist") + log.Warn("Error loading .env file, this could be ok if the env file does not exist", "err", err.Error()) } - // Add this line for logging filename and line number! - // log.SetReportCaller(true) - log.SetLevel(log.DebugLevel) - if os.Getenv("JWT_SHARED_SECRET") == "changeMe" { log.Fatal("You must change JWT_SHARED_SECRET in .env") } @@ -53,7 +69,7 @@ func main() { dbPool, err := pgxpool.Connect(context.Background(), os.Getenv("DATABASE_URL")) if err != nil { - log.Fatal("Failed to open DB connection: ", err) + log.Fatal("Failed to open DB connection", "err", err.Error()) } else { log.Info("Connected to PostgreSQL database") } @@ -61,28 +77,31 @@ func main() { app := fiber.New() - Db := db.Db{DbPool: dbPool} - handlers := h.Handlers{Db: Db, JwtKey: jwtKey} + Db := db.Db{DbPool: dbPool, Log: log} + handlers := h.Handlers{Db: Db, JwtKey: jwtKey, Log: log} - createAdminAccount(Db) + createAdminAccount(Db, log) // Log all requests - app.Use(handlers.Log) + app.Use(handlers.LogReq) // Always require application/json app.Use(handlers.RequireJSON) - app.Get("/", handlers.Hello) + app.Get("/", func(c *fiber.Ctx) error { return c.Redirect("/swagger/index.html") }) + app.Get("/swagger", func(c *fiber.Ctx) error { return c.Redirect("/swagger/index.html") }) + app.Get("/swagger/*", swagger.Handler) + app.Get("/account/:accountID", handlers.AccountGet) app.Post("/account", handlers.AccountCreate) app.Post("/auth/api-key", handlers.AccountAuthAPIKey) app.Post("/auth/password", handlers.AccountAuthPassword) - app.Post("/renew-token", handlers.TokenRenew) + app.Post("/renew-token", handlers.RenewToken) - log.WithFields(log.Fields{"WEB_BIND_HOST": os.Getenv("WEB_BIND_HOST")}).Info("Trying to start web server") + log.Info("Trying to start web server", "WEB_BIND_HOST", os.Getenv("WEB_BIND_HOST")) if err := app.Listen(os.Getenv("WEB_BIND_HOST")); err != nil { - log.Fatal(err) + log.Fatal("Could not start web server", "err", err.Error()) } log.Info("Webb server closed, shutting down") diff --git a/src/utils/utils.go b/src/utils/utils.go index cfdb116..2196418 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -1,10 +1,13 @@ package utils import ( + "log" "math/rand" "strings" "time" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" "golang.org/x/crypto/bcrypt" ) @@ -48,3 +51,31 @@ func RandString(n int) string { return sb.String() } + +func SyslogTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + loc, _ := time.LoadLocation("") // "" == UTC + t = t.In(loc) + + enc.AppendString(t.Format("2006-01-02 15:04:05")) +} + +func GetLog() *zap.SugaredLogger { + cfg := zap.NewProductionConfig() + cfg.Development = true + cfg.DisableCaller = false + cfg.DisableStacktrace = false + cfg.Encoding = "console" // "console" or "json" + cfg.EncoderConfig.EncodeTime = SyslogTimeEncoder + cfg.OutputPaths = []string{"stdout"} + cfg.ErrorOutputPaths = []string{"stderr"} + cfg.Level.SetLevel(zap.DebugLevel) + + logger, err := cfg.Build() + if err != nil { + log.Panicf("Could not build logger, err: %v", err) + } + defer logger.Sync() // flushes buffer, if any + log := logger.Sugar() + + return log +}