feat: add support for Postgres database provider (#79)

This commit is contained in:
Elias Schneider
2024-12-12 17:21:28 +01:00
committed by GitHub
parent e9d83dd6c3
commit 9d20a98dbb
38 changed files with 433 additions and 81 deletions

View File

@@ -1,6 +1,8 @@
APP_ENV=production
PUBLIC_APP_URL=http://localhost
DB_PATH=data/pocket-id.db
DB_PROVIDER=sqlite
SQLITE_DB_PATH=data/pocket-id.db
POSTGRES_CONNECTION_STRING=postgresql://postgres:postgres@localhost:5432/pocket-id
UPLOAD_PATH=data/uploads
PORT=8080
HOST=localhost

View File

@@ -5,7 +5,6 @@ go 1.23.1
require (
github.com/caarlos0/env/v11 v11.2.2
github.com/fxamacker/cbor/v2 v2.7.0
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/go-co-op/gocron/v2 v2.12.1
github.com/go-playground/validator/v10 v10.22.1
@@ -18,6 +17,7 @@ require (
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1
golang.org/x/crypto v0.27.0
golang.org/x/time v0.6.0
gorm.io/driver/postgres v1.5.11
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.12
)
@@ -36,6 +36,10 @@ require (
github.com/google/go-tpm v0.9.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
@@ -43,6 +47,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.23 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -57,6 +62,7 @@ require (
golang.org/x/arch v0.10.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect

View File

@@ -1,3 +1,7 @@
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@@ -13,18 +17,32 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
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/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0=
github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-co-op/gocron/v2 v2.12.1 h1:dCIIBFbzhWKdgXeEifBjHPzgQ1hoWhjS4289Hjjy1uw=
github.com/go-co-op/gocron/v2 v2.12.1/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -39,6 +57,8 @@ github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0=
github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
@@ -55,6 +75,14 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
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/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -85,16 +113,28 @@ github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNG
github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1 h1:UihPOz+oIJ5X0JsO7wEkL50fheCODsoZ9r86mJWfNMc=
github.com/oschwald/maxminddb-golang/v2 v2.0.0-beta.1/go.mod h1:vPpFrres6g9B5+meBwAd9xnp335KFcLEFW7EqJxBHy0=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@@ -118,6 +158,14 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -130,6 +178,8 @@ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWB
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
@@ -146,6 +196,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=

View File

@@ -2,9 +2,13 @@ package bootstrap
import (
"errors"
"fmt"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/sqlite3"
"github.com/golang-migrate/migrate/v4/database"
postgresMigrate "github.com/golang-migrate/migrate/v4/database/postgres"
sqliteMigrate "github.com/golang-migrate/migrate/v4/database/sqlite3"
"github.com/stonith404/pocket-id/backend/internal/common"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
@@ -19,15 +23,29 @@ func newDatabase() (db *gorm.DB) {
log.Fatalf("failed to connect to database: %v", err)
}
sqlDb, err := db.DB()
sqlDb.SetMaxOpenConns(1)
if err != nil {
log.Fatalf("failed to get sql.DB: %v", err)
}
driver, err := sqlite3.WithInstance(sqlDb, &sqlite3.Config{})
// Choose the correct driver for the database provider
var driver database.Driver
switch common.EnvConfig.DbProvider {
case common.DbProviderSqlite:
driver, err = sqliteMigrate.WithInstance(sqlDb, &sqliteMigrate.Config{})
case common.DbProviderPostgres:
driver, err = postgresMigrate.WithInstance(sqlDb, &postgresMigrate.Config{})
default:
log.Fatalf("unsupported database provider: %s", common.EnvConfig.DbProvider)
}
if err != nil {
log.Fatalf("failed to create migration driver: %v", err)
}
// Run migrations
m, err := migrate.NewWithDatabaseInstance(
"file://migrations",
"postgres", driver)
"file://migrations/"+string(common.EnvConfig.DbProvider),
"pocket-id", driver,
)
if err != nil {
log.Fatalf("failed to create migration instance: %v", err)
}
@@ -41,15 +59,20 @@ func newDatabase() (db *gorm.DB) {
}
func connectDatabase() (db *gorm.DB, err error) {
dbPath := common.EnvConfig.DBPath
var dialector gorm.Dialector
// Use in-memory database for testing
if common.EnvConfig.AppEnv == "test" {
dbPath = "file::memory:?cache=shared"
// Choose the correct database provider
switch common.EnvConfig.DbProvider {
case common.DbProviderSqlite:
dialector = sqlite.Open(common.EnvConfig.SqliteDBPath)
case common.DbProviderPostgres:
dialector = postgres.Open(common.EnvConfig.PostgresConnectionString)
default:
return nil, fmt.Errorf("unsupported database provider: %s", common.EnvConfig.DbProvider)
}
for i := 1; i <= 3; i++ {
db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{
db, err = gorm.Open(dialector, &gorm.Config{
TranslateError: true,
Logger: getLogger(),
})

View File

@@ -6,32 +6,55 @@ import (
"log"
)
type DbProvider string
const (
DbProviderSqlite DbProvider = "sqlite"
DbProviderPostgres DbProvider = "postgres"
)
type EnvConfigSchema struct {
AppEnv string `env:"APP_ENV"`
AppURL string `env:"PUBLIC_APP_URL"`
DBPath string `env:"DB_PATH"`
UploadPath string `env:"UPLOAD_PATH"`
Port string `env:"BACKEND_PORT"`
Host string `env:"HOST"`
EmailTemplatesPath string `env:"EMAIL_TEMPLATES_PATH"`
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
AppEnv string `env:"APP_ENV"`
AppURL string `env:"PUBLIC_APP_URL"`
DbProvider DbProvider `env:"DB_PROVIDER"`
SqliteDBPath string `env:"SQLITE_DB_PATH"`
PostgresConnectionString string `env:"POSTGRES_CONNECTION_STRING"`
UploadPath string `env:"UPLOAD_PATH"`
Port string `env:"BACKEND_PORT"`
Host string `env:"HOST"`
EmailTemplatesPath string `env:"EMAIL_TEMPLATES_PATH"`
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
}
var EnvConfig = &EnvConfigSchema{
AppEnv: "production",
DBPath: "data/pocket-id.db",
UploadPath: "data/uploads",
AppURL: "http://localhost",
Port: "8080",
Host: "localhost",
EmailTemplatesPath: "./email-templates",
MaxMindLicenseKey: "",
GeoLiteDBPath: "data/GeoLite2-City.mmdb",
AppEnv: "production",
DbProvider: "sqlite",
SqliteDBPath: "data/pocket-id.db",
PostgresConnectionString: "",
UploadPath: "data/uploads",
AppURL: "http://localhost",
Port: "8080",
Host: "localhost",
EmailTemplatesPath: "./email-templates",
MaxMindLicenseKey: "",
GeoLiteDBPath: "data/GeoLite2-City.mmdb",
}
func init() {
if err := env.ParseWithOptions(EnvConfig, env.Options{}); err != nil {
log.Fatal(err)
}
// Validate the environment variables
if EnvConfig.DbProvider != DbProviderSqlite && EnvConfig.DbProvider != DbProviderPostgres {
log.Fatal("Invalid DB_PROVIDER value. Must be 'sqlite' or 'postgres'")
}
if EnvConfig.DbProvider == DbProviderPostgres && EnvConfig.PostgresConnectionString == "" {
log.Fatal("Missing POSTGRES_CONNECTION_STRING environment variable")
}
if EnvConfig.DbProvider == DbProviderSqlite && EnvConfig.SqliteDBPath == "" {
log.Fatal("Missing SQLITE_DB_PATH environment variable")
}
}

View File

@@ -91,9 +91,7 @@ func (wc *WebauthnController) verifyLoginHandler(c *gin.Context) {
return
}
userID := c.GetString("userID")
user, token, err := wc.webAuthnService.VerifyLogin(sessionID, userID, credentialAssertionData, c.ClientIP(), c.Request.UserAgent())
user, token, err := wc.webAuthnService.VerifyLogin(sessionID, credentialAssertionData, c.ClientIP(), c.Request.UserAgent())
if err != nil {
c.Error(err)
return

View File

@@ -2,6 +2,7 @@ package datatype
import (
"database/sql/driver"
"github.com/stonith404/pocket-id/backend/internal/common"
"time"
)
@@ -14,7 +15,11 @@ func (date *DateTime) Scan(value interface{}) (err error) {
}
func (date DateTime) Value() (driver.Value, error) {
return time.Time(date).Unix(), nil
if common.EnvConfig.DbProvider == common.DbProviderSqlite {
return time.Time(date).Unix(), nil
} else {
return time.Time(date), nil
}
}
func (date DateTime) UTC() time.Time {

View File

@@ -33,7 +33,7 @@ func (u User) WebAuthnCredentials() []webauthn.Credential {
for i, credential := range u.Credentials {
credentials[i] = webauthn.Credential{
ID: []byte(credential.CredentialID),
ID: credential.CredentialID,
AttestationType: credential.AttestationType,
PublicKey: credential.PublicKey,
Transport: credential.Transport,

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"github.com/go-webauthn/webauthn/protocol"
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
"time"
)
@@ -12,7 +13,7 @@ type WebauthnSession struct {
Base
Challenge string
ExpiresAt time.Time
ExpiresAt datatype.DateTime
UserVerification string
}
@@ -20,7 +21,7 @@ type WebauthnCredential struct {
Base
Name string
CredentialID string
CredentialID []byte
PublicKey []byte
AttestationType string
Transport AuthenticatorTransportList

View File

@@ -60,7 +60,7 @@ func (s *TestService) SeedDatabase() error {
userGroups := []model.UserGroup{
{
Base: model.Base{
ID: "4110f814-56f1-4b28-8998-752b69bc97c0e",
ID: "c7ae7c01-28a3-4f3c-9572-1ee734ea8368",
},
Name: "developers",
FriendlyName: "Developers",
@@ -146,7 +146,7 @@ func (s *TestService) SeedDatabase() error {
webauthnCredentials := []model.WebauthnCredential{
{
Name: "Passkey 1",
CredentialID: "test-credential-1",
CredentialID: []byte("test-credential-1"),
PublicKey: publicKey1,
AttestationType: "none",
Transport: model.AuthenticatorTransportList{protocol.Internal},
@@ -154,7 +154,7 @@ func (s *TestService) SeedDatabase() error {
},
{
Name: "Passkey 2",
CredentialID: "test-credential-2",
CredentialID: []byte("test-credential-2"),
PublicKey: publicKey2,
AttestationType: "none",
Transport: model.AuthenticatorTransportList{protocol.Internal},
@@ -169,7 +169,7 @@ func (s *TestService) SeedDatabase() error {
webauthnSession := model.WebauthnSession{
Challenge: "challenge",
ExpiresAt: time.Now().Add(1 * time.Hour),
ExpiresAt: datatype.DateTime(time.Now().Add(1 * time.Hour)),
UserVerification: "preferred",
}
if err := tx.Create(&webauthnSession).Error; err != nil {
@@ -183,13 +183,29 @@ func (s *TestService) SeedDatabase() error {
func (s *TestService) ResetDatabase() error {
err := s.db.Transaction(func(tx *gorm.DB) error {
var tables []string
if err := tx.Raw("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != 'schema_migrations';").Scan(&tables).Error; err != nil {
return err
switch common.EnvConfig.DbProvider {
case common.DbProviderSqlite:
// Query to get all tables for SQLite
if err := tx.Raw("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != 'schema_migrations';").Scan(&tables).Error; err != nil {
return err
}
case common.DbProviderPostgres:
// Query to get all tables for PostgreSQL
if err := tx.Raw(`
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public' AND tablename != 'schema_migrations';
`).Scan(&tables).Error; err != nil {
return err
}
default:
return fmt.Errorf("unsupported database provider: %s", common.EnvConfig.DbProvider)
}
// Delete all rows from all tables
for _, table := range tables {
if err := tx.Exec("DELETE FROM " + table).Error; err != nil {
if err := tx.Exec(fmt.Sprintf("DELETE FROM %s;", table)).Error; err != nil {
return err
}
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/go-webauthn/webauthn/webauthn"
"github.com/stonith404/pocket-id/backend/internal/common"
"github.com/stonith404/pocket-id/backend/internal/model"
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
"github.com/stonith404/pocket-id/backend/internal/utils"
"gorm.io/gorm"
"net/http"
@@ -55,7 +56,7 @@ func (s *WebAuthnService) BeginRegistration(userID string) (*model.PublicKeyCred
}
sessionToStore := &model.WebauthnSession{
ExpiresAt: session.Expires,
ExpiresAt: datatype.DateTime(session.Expires),
Challenge: session.Challenge,
UserVerification: string(session.UserVerification),
}
@@ -79,7 +80,7 @@ func (s *WebAuthnService) VerifyRegistration(sessionID, userID string, r *http.R
session := webauthn.SessionData{
Challenge: storedSession.Challenge,
Expires: storedSession.ExpiresAt,
Expires: storedSession.ExpiresAt.ToTime(),
UserID: []byte(userID),
}
@@ -95,7 +96,7 @@ func (s *WebAuthnService) VerifyRegistration(sessionID, userID string, r *http.R
credentialToStore := model.WebauthnCredential{
Name: "New Passkey",
CredentialID: string(credential.ID),
CredentialID: credential.ID,
AttestationType: credential.AttestationType,
PublicKey: credential.PublicKey,
Transport: credential.Transport,
@@ -117,7 +118,7 @@ func (s *WebAuthnService) BeginLogin() (*model.PublicKeyCredentialRequestOptions
}
sessionToStore := &model.WebauthnSession{
ExpiresAt: session.Expires,
ExpiresAt: datatype.DateTime(session.Expires),
Challenge: session.Challenge,
UserVerification: string(session.UserVerification),
}
@@ -133,7 +134,7 @@ func (s *WebAuthnService) BeginLogin() (*model.PublicKeyCredentialRequestOptions
}, nil
}
func (s *WebAuthnService) VerifyLogin(sessionID, userID string, credentialAssertionData *protocol.ParsedCredentialAssertionData, ipAddress, userAgent string) (model.User, string, error) {
func (s *WebAuthnService) VerifyLogin(sessionID string, credentialAssertionData *protocol.ParsedCredentialAssertionData, ipAddress, userAgent string) (model.User, string, error) {
var storedSession model.WebauthnSession
if err := s.db.First(&storedSession, "id = ?", sessionID).Error; err != nil {
return model.User{}, "", err
@@ -141,7 +142,7 @@ func (s *WebAuthnService) VerifyLogin(sessionID, userID string, credentialAssert
session := webauthn.SessionData{
Challenge: storedSession.Challenge,
Expires: storedSession.ExpiresAt,
Expires: storedSession.ExpiresAt.ToTime(),
}
var user *model.User
@@ -156,10 +157,6 @@ func (s *WebAuthnService) VerifyLogin(sessionID, userID string, credentialAssert
return model.User{}, "", err
}
if err := s.db.Find(&user, "id = ?", userID).Error; err != nil {
return model.User{}, "", err
}
token, err := s.jwtService.GenerateAccessToken(*user)
if err != nil {
return model.User{}, "", err

View File

@@ -0,0 +1,126 @@
CREATE TABLE app_config_variables
(
key VARCHAR(100) NOT NULL PRIMARY KEY,
value TEXT NOT NULL,
type VARCHAR(20) NOT NULL,
is_public BOOLEAN DEFAULT FALSE NOT NULL,
is_internal BOOLEAN DEFAULT FALSE NOT NULL,
default_value TEXT
);
CREATE TABLE user_groups
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
friendly_name VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE users
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
first_name VARCHAR(100),
last_name VARCHAR(100),
is_admin BOOLEAN DEFAULT FALSE NOT NULL
);
CREATE TABLE audit_logs
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
event VARCHAR(100) NOT NULL,
ip_address INET NOT NULL,
data JSONB NOT NULL,
user_id UUID REFERENCES users ON DELETE SET NULL,
user_agent TEXT,
country VARCHAR(100),
city VARCHAR(100)
);
CREATE TABLE custom_claims
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
key VARCHAR(255) NOT NULL,
value TEXT NOT NULL,
user_id UUID REFERENCES users ON DELETE CASCADE,
user_group_id UUID REFERENCES user_groups ON DELETE CASCADE,
CONSTRAINT custom_claims_unique UNIQUE (key, user_id, user_group_id),
CHECK (user_id IS NOT NULL OR user_group_id IS NOT NULL)
);
CREATE TABLE oidc_authorization_codes
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
code VARCHAR(255) NOT NULL UNIQUE,
scope TEXT NOT NULL,
nonce VARCHAR(255),
expires_at TIMESTAMPTZ NOT NULL,
user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE,
client_id UUID NOT NULL,
code_challenge VARCHAR(255),
code_challenge_method_sha256 BOOLEAN
);
CREATE TABLE oidc_clients
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
name VARCHAR(255),
secret TEXT,
callback_urls JSONB,
image_type VARCHAR(10),
created_by_id UUID REFERENCES users ON DELETE SET NULL,
is_public BOOLEAN DEFAULT FALSE
);
CREATE TABLE one_time_access_tokens
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
token VARCHAR(255) NOT NULL UNIQUE,
expires_at TIMESTAMPTZ NOT NULL,
user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE
);
CREATE TABLE user_authorized_oidc_clients
(
scope VARCHAR(255),
user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE,
client_id UUID NOT NULL REFERENCES oidc_clients ON DELETE CASCADE,
PRIMARY KEY (user_id, client_id)
);
CREATE TABLE user_groups_users
(
user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE,
user_group_id UUID NOT NULL REFERENCES user_groups ON DELETE CASCADE,
PRIMARY KEY (user_id, user_group_id)
);
CREATE TABLE webauthn_credentials
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
name VARCHAR(255) NOT NULL,
credential_id BYTEA NOT NULL UNIQUE,
public_key BYTEA NOT NULL,
attestation_type VARCHAR(20) NOT NULL,
transport JSONB NOT NULL,
user_id UUID REFERENCES users ON DELETE CASCADE,
backup_eligible BOOLEAN DEFAULT FALSE NOT NULL,
backup_state BOOLEAN DEFAULT FALSE NOT NULL
);
CREATE TABLE webauthn_sessions
(
id UUID NOT NULL PRIMARY KEY,
created_at TIMESTAMPTZ,
challenge VARCHAR(255) NOT NULL UNIQUE,
expires_at TIMESTAMPTZ NOT NULL,
user_verification VARCHAR(255) NOT NULL
);