Loads of updates

This commit is contained in:
Lilleman auf Larv 2021-06-22 22:52:48 +02:00
parent 8dc20a4eb0
commit ccafd60923
18 changed files with 1206 additions and 125 deletions

View File

@ -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

View File

@ -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`

34
docker-compose.yml Normal file
View File

@ -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

21
go.mod
View File

@ -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
)

147
go.sum
View File

@ -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=

View File

@ -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

View File

@ -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
}

View File

@ -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
}

366
src/docs/docs.go Normal file
View File

@ -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{})
}

304
src/docs/swagger.json Normal file
View File

@ -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"
}
}
}
}
}

203
src/docs/swagger.yaml Normal file
View File

@ -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"

View File

@ -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})

View File

@ -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]
}

View File

@ -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"}})
}

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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
}