diff --git a/Dockerfile b/Dockerfile index c6e1f162b8..2e7a7e275c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # golang alpine -FROM golang:1.21.4-alpine as builder +FROM golang:1.21.5-alpine as builder ARG TARGETARCH ARG TARGETOS diff --git a/auth/api/iam/metadata.go b/auth/api/iam/metadata.go index df5025ff9b..979d3b24b2 100644 --- a/auth/api/iam/metadata.go +++ b/auth/api/iam/metadata.go @@ -54,8 +54,8 @@ func authorizationServerMetadata(identity url.URL) oauth.AuthorizationServerMeta GrantTypesSupported: grantTypesSupported, PreAuthorizedGrantAnonymousAccessSupported: true, PresentationDefinitionEndpoint: identity.JoinPath("presentation_definition").String(), - VPFormats: vpFormatsSupported, - VPFormatsSupported: vpFormatsSupported, + VPFormats: oauth.DefaultOpenIDSupportedFormats(), + VPFormatsSupported: oauth.DefaultOpenIDSupportedFormats(), ClientIdSchemesSupported: clientIdSchemesSupported, } } @@ -75,7 +75,7 @@ func clientMetadata(identity url.URL) OAuthClientMetadata { SoftwareID: softwareID, // nuts-node-refimpl SoftwareVersion: softwareVersion, // version tag or "unknown" //CredentialOfferEndpoint: "", - VPFormats: vpFormatsSupported, + VPFormats: oauth.DefaultOpenIDSupportedFormats(), ClientIdScheme: "did", } } diff --git a/auth/api/iam/metadata_test.go b/auth/api/iam/metadata_test.go index 925fa41319..1195604e1d 100644 --- a/auth/api/iam/metadata_test.go +++ b/auth/api/iam/metadata_test.go @@ -87,8 +87,8 @@ func Test_authorizationServerMetadata(t *testing.T) { GrantTypesSupported: []string{"authorization_code", "vp_token", "urn:ietf:params:oauth:grant-type:pre-authorized_code"}, PreAuthorizedGrantAnonymousAccessSupported: true, PresentationDefinitionEndpoint: identity + "/presentation_definition", - VPFormats: vpFormatsSupported, - VPFormatsSupported: vpFormatsSupported, + VPFormats: oauth.DefaultOpenIDSupportedFormats(), + VPFormatsSupported: oauth.DefaultOpenIDSupportedFormats(), ClientIdSchemesSupported: []string{"did"}, } assert.Equal(t, expected, authorizationServerMetadata(*identityURL)) @@ -108,7 +108,7 @@ func Test_clientMetadata(t *testing.T) { SoftwareID: "nuts-node-refimpl", SoftwareVersion: "testVersion", CredentialOfferEndpoint: "", - VPFormats: vpFormatsSupported, + VPFormats: oauth.DefaultOpenIDSupportedFormats(), ClientIdScheme: "did", } assert.Equal(t, expected, clientMetadata(url.URL{})) diff --git a/auth/api/iam/types.go b/auth/api/iam/types.go index a0f9e48897..e699ba0be0 100644 --- a/auth/api/iam/types.go +++ b/auth/api/iam/types.go @@ -100,28 +100,6 @@ const ( var grantTypesSupported = []string{grantTypeAuthorizationCode, grantTypeVPToken, grantTypePreAuthorizedCode} -// algValuesSupported contains a list of supported cipher suites for jwt_vc_json & jwt_vp_json presentation formats -// Recommended list of options https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms -// TODO: validate list, should reflect current recommendations from https://www.ncsc.nl -var algValuesSupported = []string{"PS256", "PS384", "PS512", "ES256", "ES384", "ES512"} - -// proofTypeValuesSupported contains a list of supported cipher suites for ldp_vc & ldp_vp presentation formats -// Recommended list of options https://w3c-ccg.github.io/ld-cryptosuite-registry/ -var proofTypeValuesSupported = []string{"JsonWebSignature2020"} - -// vpFormatsSupported defines the supported formats and is used in the -// - Authorization Server's metadata field `vp_formats_supported` -// - Client's metadata field `vp_formats` -// -// TODO: spec is very unclear about this part. -// See https://github.com/nuts-foundation/nuts-node/issues/2447 -var vpFormatsSupported = map[string]map[string][]string{ - "jwt_vp_json": {"alg_values_supported": algValuesSupported}, - "jwt_vc_json": {"alg_values_supported": algValuesSupported}, - "ldp_vc": {"proof_type_values_supported": proofTypeValuesSupported}, - "ldp_vp": {"proof_type_values_supported": proofTypeValuesSupported}, -} - // clientIdSchemesSupported lists the supported client_id_scheme // https://openid.bitbucket.io/connect/openid-4-verifiable-presentations-1_0.html#name-verifier-metadata-managemen var clientIdSchemesSupported = []string{"did"} diff --git a/auth/oauth/openid.go b/auth/oauth/openid.go new file mode 100644 index 0000000000..f657cc0f5a --- /dev/null +++ b/auth/oauth/openid.go @@ -0,0 +1,25 @@ +package oauth + +// algValuesSupported contains a list of supported cipher suites for jwt_vc_json & jwt_vp_json presentation formats +// Recommended list of options https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms +// TODO: validate list, should reflect current recommendations from https://www.ncsc.nl +var algValuesSupported = []string{"PS256", "PS384", "PS512", "ES256", "ES384", "ES512"} + +// proofTypeValuesSupported contains a list of supported cipher suites for ldp_vc & ldp_vp presentation formats +// Recommended list of options https://w3c-ccg.github.io/ld-cryptosuite-registry/ +var proofTypeValuesSupported = []string{"JsonWebSignature2020"} + +// DefaultOpenIDSupportedFormats returns the OpenID formats supported by the Nuts node and is used in the +// - Authorization Server's metadata field `vp_formats_supported` +// - Client's metadata field `vp_formats` +// +// TODO: spec is very unclear about this part. +// See https://github.com/nuts-foundation/nuts-node/issues/2447 +func DefaultOpenIDSupportedFormats() map[string]map[string][]string { + return map[string]map[string][]string{ + "jwt_vp_json": {"alg_values_supported": algValuesSupported}, + "jwt_vc_json": {"alg_values_supported": algValuesSupported}, + "ldp_vc": {"proof_type_values_supported": proofTypeValuesSupported}, + "ldp_vp": {"proof_type_values_supported": proofTypeValuesSupported}, + } +} diff --git a/auth/services/oauth/relying_party.go b/auth/services/oauth/relying_party.go index d615dc1fec..1aef0640ca 100644 --- a/auth/services/oauth/relying_party.go +++ b/auth/services/oauth/relying_party.go @@ -23,7 +23,7 @@ import ( "crypto/tls" "errors" "fmt" - "github.com/nuts-foundation/nuts-node/openid4vc" + "github.com/nuts-foundation/go-did/vc" "net/http" "net/url" "strings" @@ -155,10 +155,23 @@ func (s *relyingParty) RequestRFC021AccessToken(ctx context.Context, requester d // If no presentation definition matches, return a 412 "no matching credentials" error builder := presentationDefinition.PresentationSubmissionBuilder() builder.AddWallet(requester, walletCredentials) - format, err := determineFormat(metadata.VPFormats) - if err != nil { - return nil, err + + // Find supported VP format, matching support from: + // - what the local Nuts node supports + // - the presentation definition "claimed format designation" (optional) + // - the verifier's metadata (optional) + formatCandidates := credential.OpenIDSupportedFormats(oauth.DefaultOpenIDSupportedFormats()) + if metadata.VPFormats != nil { + formatCandidates = formatCandidates.Match(credential.OpenIDSupportedFormats(metadata.VPFormats)) + } + if presentationDefinition.Format != nil { + formatCandidates = formatCandidates.Match(credential.DIFClaimFormats(*presentationDefinition.Format)) + } + format := chooseVPFormat(formatCandidates.Map) + if format == "" { + return nil, errors.New("requester, verifier (authorization server metadata) and presentation definition don't share a supported VP format") } + // TODO: format parameters (alg, proof_type, etc.) are ignored, but should be used in the actual signing submission, signInstructions, err := builder.Build(format) if err != nil { return nil, fmt.Errorf("failed to match presentation definition: %w", err) @@ -193,21 +206,18 @@ func (s *relyingParty) RequestRFC021AccessToken(ctx context.Context, requester d }, nil } -func determineFormat(formats map[string]map[string][]string) (string, error) { - for format := range formats { - switch format { - case openid4vc.VerifiablePresentationJSONLDFormat: - return format, nil - case openid4vc.VerifiablePresentationJWTFormat: - fallthrough - case "jwt_vp_json": - return openid4vc.VerifiablePresentationJWTFormat, nil - default: - continue - } +func chooseVPFormat(formats map[string]map[string][]string) string { + // They are in preferred order + if _, ok := formats[vc.JWTPresentationProofFormat]; ok { + return vc.JWTPresentationProofFormat } - - return "", errors.New("authorization server metadata does not contain any supported VP formats") + if _, ok := formats["jwt_vp_json"]; ok { + return vc.JWTPresentationProofFormat + } + if _, ok := formats[vc.JSONLDPresentationProofFormat]; ok { + return vc.JSONLDPresentationProofFormat + } + return "" } var timeFunc = time.Now diff --git a/auth/services/oauth/relying_party_test.go b/auth/services/oauth/relying_party_test.go index 7b57d2635e..b99b5770d0 100644 --- a/auth/services/oauth/relying_party_test.go +++ b/auth/services/oauth/relying_party_test.go @@ -135,6 +135,18 @@ func TestRelyingParty_RequestRFC021AccessToken(t *testing.T) { assert.Equal(t, "token", response.AccessToken) assert.Equal(t, "bearer", response.TokenType) }) + t.Run("authorization server supported VP formats don't match", func(t *testing.T) { + ctx := createOAuthRPContext(t) + ctx.authzServerMetadata.VPFormats = map[string]map[string][]string{ + "unsupported": nil, + } + ctx.wallet.EXPECT().List(gomock.Any(), walletDID).Return(credentials, nil) + + response, err := ctx.relyingParty.RequestRFC021AccessToken(context.Background(), walletDID, ctx.verifierDID, scopes) + + assert.EqualError(t, err, "requester, verifier (authorization server metadata) and presentation definition don't share a supported VP format") + assert.Nil(t, response) + }) t.Run("error - access denied", func(t *testing.T) { oauthError := oauth.OAuth2Error{ Code: "invalid_scope", @@ -210,7 +222,7 @@ func TestRelyingParty_RequestRFC021AccessToken(t *testing.T) { }) } -func Test_determineFormat(t *testing.T) { +func Test_chooseVPFormat(t *testing.T) { t.Run("ok", func(t *testing.T) { formats := map[string]map[string][]string{ "jwt_vp": { @@ -218,17 +230,15 @@ func Test_determineFormat(t *testing.T) { }, } - format, err := determineFormat(formats) + format := chooseVPFormat(formats) - assert.NoError(t, err) assert.Equal(t, "jwt_vp", format) }) - t.Run("error - no supported format", func(t *testing.T) { + t.Run("no supported format", func(t *testing.T) { formats := map[string]map[string][]string{} - format, err := determineFormat(formats) + format := chooseVPFormat(formats) - assert.EqualError(t, err, "authorization server metadata does not contain any supported VP formats") assert.Empty(t, format) }) t.Run(" jwt_vp_json returns jwt_vp", func(t *testing.T) { @@ -238,9 +248,8 @@ func Test_determineFormat(t *testing.T) { }, } - format, err := determineFormat(formats) + format := chooseVPFormat(formats) - assert.NoError(t, err) assert.Equal(t, "jwt_vp", format) }) } @@ -416,7 +425,7 @@ func createRPContext(t *testing.T, tlsConfig *tls.Config) *rpTestContext { type rpOAuthTestContext struct { *rpTestContext - authzServerMetadata oauth.AuthorizationServerMetadata + authzServerMetadata *oauth.AuthorizationServerMetadata handler http.HandlerFunc tlsServer *httptest.Server verifierDID did.DID @@ -449,15 +458,13 @@ func createOAuthRPContext(t *testing.T) *rpOAuthTestContext { ] } ` - formats := make(map[string]map[string][]string) - formats["jwt_vp"] = make(map[string][]string) - authzServerMetadata := oauth.AuthorizationServerMetadata{VPFormats: formats} + authzServerMetadata := &oauth.AuthorizationServerMetadata{VPFormats: oauth.DefaultOpenIDSupportedFormats()} ctx := &rpOAuthTestContext{ rpTestContext: createRPContext(t, nil), metadata: func(writer http.ResponseWriter) { writer.Header().Add("Content-Type", "application/json") writer.WriteHeader(http.StatusOK) - bytes, _ := json.Marshal(authzServerMetadata) + bytes, _ := json.Marshal(*authzServerMetadata) _, _ = writer.Write(bytes) return }, diff --git a/e2e-tests/storage/postgres/docker-compose.yml b/e2e-tests/storage/postgres/docker-compose.yml new file mode 100644 index 0000000000..85ebb6ff72 --- /dev/null +++ b/e2e-tests/storage/postgres/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3.7" +services: + node: + image: "${IMAGE_NODE_A:-nutsfoundation/nuts-node:master}" + environment: + NUTS_CONFIGFILE: /opt/nuts/nuts.yaml + volumes: + - "./nuts.yaml:/opt/nuts/nuts.yaml:ro" + - "../../tls-certs/nodeA-certificate.pem:/opt/nuts/certificate-and-key.pem:ro" + - "../../tls-certs/truststore.pem:/opt/nuts/truststore.pem:ro" + ports: + - "1323:1323" + healthcheck: + interval: 1s # Make test run quicker by checking health status more often + depends_on: + db: + condition: service_healthy + db: + image: postgres:16-alpine + restart: always + ports: + - "5432:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U postgres" ] # this makes sure the container only reports healthy it can be connected to + interval: 1s + timeout: 5s + retries: 20 diff --git a/e2e-tests/storage/postgres/nuts.yaml b/e2e-tests/storage/postgres/nuts.yaml new file mode 100644 index 0000000000..bba4d11a02 --- /dev/null +++ b/e2e-tests/storage/postgres/nuts.yaml @@ -0,0 +1,16 @@ +url: https://node +verbosity: debug +auth: + contractvalidators: + - dummy + irma: + autoupdateschemas: false +tls: + truststorefile: /opt/nuts/truststore.pem + certfile: /opt/nuts/certificate-and-key.pem + certkeyfile: /opt/nuts/certificate-and-key.pem +crypto: + storage: fs +storage: + sql: + connection: postgres://postgres:postgres@db:5432/postgres?sslmode=disable diff --git a/e2e-tests/storage/postgres/run-test.sh b/e2e-tests/storage/postgres/run-test.sh new file mode 100755 index 0000000000..84d413ba41 --- /dev/null +++ b/e2e-tests/storage/postgres/run-test.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +source ../../util.sh + +echo "------------------------------------" +echo "Cleaning up running Docker containers and volumes, and key material..." +echo "------------------------------------" +docker compose stop +docker compose rm -f -v + +echo "------------------------------------" +echo "Starting Docker containers..." +echo "------------------------------------" +docker compose up --wait +if [ $? -ne 0 ]; then + echo "ERROR: node failed to start" + exitWithDockerLogs 1 +fi + +echo "------------------------------------" +echo "Stopping Docker containers..." +echo "------------------------------------" +docker compose stop diff --git a/e2e-tests/storage/run-tests.sh b/e2e-tests/storage/run-tests.sh index 1d66b81d07..87388e9257 100755 --- a/e2e-tests/storage/run-tests.sh +++ b/e2e-tests/storage/run-tests.sh @@ -9,6 +9,13 @@ pushd redis ./run-test.sh popd +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" +echo "!! Running test: Postgres !!" +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" +pushd postgres +./run-test.sh +popd + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" echo "!! Running test: Backup/Restore !!" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" diff --git a/go.mod b/go.mod index f5f01d4e96..3ed728f767 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.21 require ( github.com/PaesslerAG/jsonpath v0.1.2-0.20230323094847-3484786d6f97 github.com/alicebob/miniredis/v2 v2.31.0 + github.com/amacneil/dbmate/v2 v2.8.0 github.com/avast/retry-go/v4 v4.5.1 github.com/cbroglie/mustache v1.4.0 github.com/chromedp/chromedp v0.9.3 github.com/dlclark/regexp2 v1.10.0 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/golang-migrate/migrate/v4 v4.16.2 github.com/goodsign/monday v1.0.1 github.com/google/uuid v1.4.0 github.com/hashicorp/vault/api v1.10.0 @@ -52,7 +52,6 @@ require ( gopkg.in/Regis24GmbH/go-phonetics.v2 v2.0.3 gopkg.in/yaml.v3 v3.0.1 schneider.vip/problem v1.9.0 - ) require ( @@ -75,6 +74,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/eknkc/basex v1.0.1 // indirect github.com/fatih/color v1.13.0 // indirect @@ -87,13 +87,16 @@ require ( github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-redsync/redsync/v4 v4.8.1 // indirect - github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.3.0 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/golang-jwt/jwt/v4 v4.4.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/flatbuffers v2.0.8+incompatible // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -115,7 +118,7 @@ require ( github.com/lestrrat-go/httprc v1.0.4 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect - github.com/lib/pq v1.10.6 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -141,7 +144,7 @@ require ( github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/privacybydesign/gabi v0.0.0-20221012093643-8e978bfbb252 // indirect github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect @@ -161,14 +164,23 @@ require ( github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yuin/gopher-lua v1.1.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.3.0 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect gopkg.in/Regis24GmbH/go-diacritics.v2 v2.0.3 // indirect gorm.io/driver/sqlite v1.5.4 gorm.io/gorm v1.25.5 rsc.io/qr v0.2.0 // indirect ) + +require gorm.io/driver/postgres v1.5.4 + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect +) diff --git a/go.sum b/go.sum index 651d63112b..2afa3f95b3 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGn github.com/alicebob/miniredis/v2 v2.31.0 h1:ObEFUNlJwoIiyjxdrYF0QIDE7qXcLc7D3WpSH4c22PU= github.com/alicebob/miniredis/v2 v2.31.0/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg= github.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo= +github.com/amacneil/dbmate/v2 v2.8.0 h1:i5V1rAdURFSDPaeZ/afm6zY/GbBKgTPs3Q9/Aph54BA= +github.com/amacneil/dbmate/v2 v2.8.0/go.mod h1:d97pvz2hTWAsFT2fp31NrDD7EUBttOuv4r/04sQMETk= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= @@ -110,8 +112,9 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/eknkc/basex v1.0.1 h1:TcyAkqh4oJXgV3WYyL4KEfCMk9W8oJCpmx1bo+jVgKY= @@ -163,8 +166,8 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq github.com/go-redsync/redsync/v4 v4.8.1 h1:rq2RvdTI0obznMdxKUWGdmmulo7lS9yCzb8fgDKOlbM= github.com/go-redsync/redsync/v4 v4.8.1/go.mod h1:LmUAsQuQxhzZAoGY7JS6+dNhNmZyonMZiiEDY9plotM= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -184,15 +187,15 @@ 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 v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= -github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -227,12 +230,14 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= @@ -298,6 +303,12 @@ github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEF github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -307,8 +318,9 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -355,8 +367,8 @@ github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -518,8 +530,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= @@ -609,6 +621,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w= +github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= @@ -671,6 +685,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= @@ -679,8 +694,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -695,8 +710,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -801,8 +816,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -810,6 +825,7 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= @@ -853,6 +869,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= diff --git a/storage/engine.go b/storage/engine.go index 2ede3808ff..30a28f3c5a 100644 --- a/storage/engine.go +++ b/storage/engine.go @@ -23,20 +23,24 @@ import ( "embed" "errors" "fmt" - "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/sqlite3" - "github.com/golang-migrate/migrate/v4/source/iofs" - "github.com/nuts-foundation/go-stoabs" - "github.com/nuts-foundation/nuts-node/core" - "github.com/nuts-foundation/nuts-node/storage/log" - "github.com/redis/go-redis/v9" - "github.com/sirupsen/logrus" + "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" + "net/url" "path" "strings" "sync" "time" + + "github.com/amacneil/dbmate/v2/pkg/dbmate" + "github.com/nuts-foundation/go-stoabs" + "github.com/nuts-foundation/nuts-node/core" + "github.com/nuts-foundation/nuts-node/storage/log" + "github.com/redis/go-redis/v9" + + _ "github.com/amacneil/dbmate/v2/pkg/driver/mysql" + _ "github.com/amacneil/dbmate/v2/pkg/driver/postgres" + _ "github.com/amacneil/dbmate/v2/pkg/driver/sqlite" ) const storeShutdownTimeout = 5 * time.Second @@ -167,39 +171,65 @@ func (e *engine) initSQLDatabase() error { connectionString = sqliteConnectionString(e.datadir) } - var err error - e.sqlDB, err = gorm.Open(sqlite.Open(connectionString), &gorm.Config{}) - if err != nil { - return err + // Find right SQL adapter + type sqlAdapter struct { + connector func(dsn string) gorm.Dialector + gormConnectionString func(config string) string } - log.Logger().Debug("Running database migrations...") - underlyingDB, err := e.sqlDB.DB() - if err != nil { - return err + adapters := map[string]sqlAdapter{ + "sqlite": { + connector: sqlite.Open, + gormConnectionString: func(trimmed string) string { + return trimmed + }, + }, + "postgres": { + connector: postgres.Open, + gormConnectionString: func(trimmed string) string { + return fmt.Sprintf("postgres:%s", trimmed) + }, + }, } - sourceDriver, err := iofs.New(sqlMigrationsFS, "sql_migrations") - if err != nil { - return err + var adapter *sqlAdapter + var trimmedConnectionString string + for prefix, curr := range adapters { + trimmedConnectionString = strings.TrimPrefix(connectionString, prefix+":") + if len(trimmedConnectionString) != len(connectionString) { + adapter = &curr + break + } + } + if adapter == nil { + return fmt.Errorf("unsupported SQL database connection: %s", connectionString) } - databaseDriver, err := sqlite3.WithInstance(underlyingDB, &sqlite3.Config{}) + + // Open connection and migrate + var err error + e.sqlDB, err = gorm.Open(adapter.connector(adapter.gormConnectionString(trimmedConnectionString)), &gorm.Config{}) if err != nil { return err } - migrations, err := migrate.NewWithInstance("iofs", sourceDriver, e.sqlDB.Name(), databaseDriver) + log.Logger().Debug("Running database migrations...") + + // we need the connectionString with adapter specific prefix here + dbURL, err := url.Parse(connectionString) if err != nil { return err } - migrations.Log = sqlMigrationLogger{} - err = migrations.Up() - if errors.Is(err, migrate.ErrNoChange) { - // There was nothing to migrate - return nil + db := dbmate.New(dbURL) + db.FS = sqlMigrationsFS + db.MigrationsDir = []string{"sql_migrations"} + db.AutoDumpSchema = false + db.Log = sqlMigrationLogger{} + + if err = db.CreateAndMigrate(); err != nil { + return fmt.Errorf("failed to migrate database: %w on %s", err, dbURL.String()) } - return err + return nil } func sqliteConnectionString(datadir string) string { - return "file:" + path.Join(datadir, "sqlite.db?_journal_mode=WAL&_foreign_keys=on") + return "sqlite:file:" + path.Join(datadir, "sqlite.db?_journal_mode=WAL&_foreign_keys=on") } type provider struct { @@ -260,10 +290,7 @@ func (p *provider) getStore(moduleName string, name string, adapter database) (s type sqlMigrationLogger struct { } -func (m sqlMigrationLogger) Printf(format string, v ...interface{}) { - log.Logger().Infof(format, v...) -} - -func (m sqlMigrationLogger) Verbose() bool { - return log.Logger().Level >= logrus.DebugLevel +func (m sqlMigrationLogger) Write(p []byte) (n int, err error) { + log.Logger().Info(string(p)) + return len(p), nil } diff --git a/storage/engine_test.go b/storage/engine_test.go index f4a6ceb4b9..aa27b500a1 100644 --- a/storage/engine_test.go +++ b/storage/engine_test.go @@ -143,6 +143,6 @@ func Test_engine_sqlDatabase(t *testing.T) { require.NoError(t, row.Err()) var count int assert.NoError(t, row.Scan(&count)) - assert.Equal(t, 1, count) + assert.Equal(t, 2, count) }) } diff --git a/storage/sql_migrations/2_discoveryservice.up.sql b/storage/sql_migrations/001_discoveryservice.sql similarity index 94% rename from storage/sql_migrations/2_discoveryservice.up.sql rename to storage/sql_migrations/001_discoveryservice.sql index d2e87102fa..21c4a498e5 100644 --- a/storage/sql_migrations/2_discoveryservice.up.sql +++ b/storage/sql_migrations/001_discoveryservice.sql @@ -1,3 +1,4 @@ +-- migrate:up -- discovery contains the known discovery services and the highest timestamp create table discovery_service ( @@ -50,4 +51,11 @@ create table discovery_credential_prop PRIMARY KEY (credential_id, key), -- cascading delete: if the presentation gets deleted, the properties get deleted as well constraint fk_discovery_credential_id foreign key (credential_id) references discovery_credential (id) on delete cascade -); \ No newline at end of file +); + +-- migrate:down +drop table discovery_service; +drop table discovery_presentation; +drop table discovery_credential; +drop table discovery_credential_prop; + diff --git a/storage/sql_migrations/3_didweb.up.sql b/storage/sql_migrations/002_didweb.sql similarity index 82% rename from storage/sql_migrations/3_didweb.up.sql rename to storage/sql_migrations/002_didweb.sql index 3eb05f1a1b..44d70a47cd 100644 --- a/storage/sql_migrations/3_didweb.up.sql +++ b/storage/sql_migrations/002_didweb.sql @@ -1,3 +1,4 @@ +-- migrate:up -- this table is used to store the did:web create table vdr_didweb ( @@ -15,7 +16,12 @@ create table vdr_didweb_verificationmethod did varchar(255) not null, -- data is a JSON object containing the verification method data, e.g. the public key. -- When producing the verificationMethod, data is used as JSON base object and the id and type are added. - data blob not null, + data text not null, primary key (did, id), foreign key (did) references vdr_didweb (did) on delete cascade -); \ No newline at end of file +); + +-- migrate:down +drop table vdr_didweb; +drop table vdr_didweb_service; +drop table vdr_didweb_verificationmethod; \ No newline at end of file diff --git a/storage/sql_migrations/1_initial.down.sql b/storage/sql_migrations/1_initial.down.sql deleted file mode 100644 index 6bb32b4a81..0000000000 --- a/storage/sql_migrations/1_initial.down.sql +++ /dev/null @@ -1 +0,0 @@ -select 1 \ No newline at end of file diff --git a/storage/sql_migrations/1_initial.up.sql b/storage/sql_migrations/1_initial.up.sql deleted file mode 100644 index 6bb32b4a81..0000000000 --- a/storage/sql_migrations/1_initial.up.sql +++ /dev/null @@ -1 +0,0 @@ -select 1 \ No newline at end of file diff --git a/storage/sql_migrations/2_discoveryservice.down.sql b/storage/sql_migrations/2_discoveryservice.down.sql deleted file mode 100644 index 02fe661c06..0000000000 --- a/storage/sql_migrations/2_discoveryservice.down.sql +++ /dev/null @@ -1,4 +0,0 @@ -drop table discovery_service; -drop table discovery_presentation; -drop table discovery_credential; -drop table discovery_credential_prop; \ No newline at end of file diff --git a/storage/sql_migrations/3_didweb.down.sql b/storage/sql_migrations/3_didweb.down.sql deleted file mode 100644 index e29815b393..0000000000 --- a/storage/sql_migrations/3_didweb.down.sql +++ /dev/null @@ -1,3 +0,0 @@ -drop table vdr_didweb; -drop table vdr_didweb_service; -drop table vdr_didweb_verificationmethod; \ No newline at end of file diff --git a/storage/sql_migrations/README.md b/storage/sql_migrations/README.md index 535333d49a..e79b44d37d 100644 --- a/storage/sql_migrations/README.md +++ b/storage/sql_migrations/README.md @@ -1,9 +1,9 @@ This directory contains SQL schema migrations, run at startup of the node. -Refer to https://github.com/golang-migrate/migrate/blob/master/MIGRATIONS.md on how to write migrations. +Refer to https://github.com/amacneil/dbmate on how to write migrations. -Files should be named according to the following: `__..sql`. -For instance: `2_usecase_list.up.sql`. +Files should be named according to the following: `__
.sql`. +For instance: `002_usecase_list.sql`. Each file should contain `-- migrate:up` and `-- migrate:down` AVOID changing migrations in master (unless the migration breaks the node horribly) for those running a `master` version. DO NOT alter migrations in a released version: it might break vendor deployments or cause data corruption. \ No newline at end of file diff --git a/vcr/credential/formats.go b/vcr/credential/formats.go new file mode 100644 index 0000000000..7fc5e4afe7 --- /dev/null +++ b/vcr/credential/formats.go @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2023 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package credential + +// DIFClaimFormats returns the given DIF claim formats as specified by https://identity.foundation/claim-format-registry/ +// as Formats. +func DIFClaimFormats(formats map[string]map[string][]string) Formats { + return Formats{ + Map: formats, + ParamAliases: map[string]string{ + // no aliases for this type + }, + } +} + +// OpenIDSupportedFormats returns the given OpenID supported formats as specified by the OpenID4VC family of specs. +func OpenIDSupportedFormats(formats map[string]map[string][]string) Formats { + return Formats{ + Map: formats, + ParamAliases: map[string]string{ + "alg_values_supported": "alg", + "proof_type_values_supported": "proof_type", + }, + } +} + +// Formats is a map of supported formats and their parameters according to https://identity.foundation/claim-format-registry/ +// E.g., ldp_vp: {proof_type: [Ed25519Signature2018, JsonWebSignature2020]} +type Formats struct { + Map map[string]map[string][]string + ParamAliases map[string]string +} + +// Match takes the other supports formats and returns the formats that are supported by both sets. +// If a format is supported by both sets, it returns the intersection of the parameters. +// If a format is supported by both sets, but parameters overlap (e.g. supported cryptographic algorithms), +// the format is not included in the result. +func (f Formats) Match(other Formats) Formats { + result := Formats{ + Map: map[string]map[string][]string{}, + ParamAliases: map[string]string{}, + } + + for thisFormat, thisFormatParams := range f.Map { + otherFormatParams := other.normalizeParameters(other.Map[thisFormat]) + if otherFormatParams == nil { + // format not supported by other + continue + } + + result.Map[thisFormat] = map[string][]string{} + for thisParam, thisValues := range f.normalizeParameters(thisFormatParams) { + otherValues, supported := otherFormatParams[thisParam] + if !supported { + // param not supported by other + continue + } + + result.Map[thisFormat][thisParam] = []string{} + for _, thisValue := range thisValues { + for _, otherValue := range otherValues { + if thisValue == otherValue { + result.Map[thisFormat][thisParam] = append(result.Map[thisFormat][thisParam], thisValue) + } + } + } + if len(result.Map[thisFormat][thisParam]) == 0 { + delete(result.Map[thisFormat], thisParam) + } + } + if len(result.Map[thisFormat]) == 0 { + delete(result.Map, thisFormat) + } + } + + return result +} + +// normalizeParameter normalizes the parameter name to the name used in the DIF spec. +func (f Formats) normalizeParameter(param string) string { + if alias, ok := f.ParamAliases[param]; ok { + return alias + } + return param +} + +// normalizeParameters normalizes the parameter map to the names used in the DIF spec. +func (f Formats) normalizeParameters(params map[string][]string) map[string][]string { + if params == nil { + return nil + } + result := map[string][]string{} + for param, values := range params { + result[f.normalizeParameter(param)] = values + } + return result +} diff --git a/vcr/credential/formats_test.go b/vcr/credential/formats_test.go new file mode 100644 index 0000000000..7ee337349b --- /dev/null +++ b/vcr/credential/formats_test.go @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2023 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package credential + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFormats_Match(t *testing.T) { + t.Run("DIF - set 2 is subset", func(t *testing.T) { + set1 := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg_values_supported": {"ES256", "EdDSA"}, + }, + "ldp_vp": { + "proof_type_values_supported": {"Ed25519Signature2018", "JsonWebSignature2020"}, + }, + }) + set2 := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg_values_supported": {"ES256"}, + }, + "ldp_vp": { + "proof_type_values_supported": {"JsonWebSignature2020"}, + }, + }) + expected := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg_values_supported": {"ES256"}, + }, + "ldp_vp": { + "proof_type_values_supported": {"JsonWebSignature2020"}, + }, + }) + + result := set1.Match(set2) + assert.Equal(t, expected, result) + }) + t.Run("one set PEX style, other set OpenID4VC style", func(t *testing.T) { + set1 := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg": {"ES256", "EdDSA"}, + }, + "ldp_vp": { + "proof_type": {"Ed25519Signature2018", "JsonWebSignature2020"}, + }, + }) + set2 := OpenIDSupportedFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg_values_supported": {"ES256", "EdDSA"}, + }, + "ldp_vp": { + "proof_type_values_supported": {"Ed25519Signature2018", "JsonWebSignature2020"}, + }, + }) + expected := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg": {"ES256", "EdDSA"}, + }, + "ldp_vp": { + "proof_type": {"Ed25519Signature2018", "JsonWebSignature2020"}, + }, + }) + + t.Run("PEX match OpenID", func(t *testing.T) { + result := set1.Match(set2) + assert.Equal(t, expected, result) + }) + t.Run("OpenID match PEX", func(t *testing.T) { + result := set2.Match(set1) + assert.Equal(t, expected, result) + }) + }) + t.Run("set 2 does not match format params for JWT", func(t *testing.T) { + set1 := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg": {"ES256", "EdDSA"}, + }, + "ldp_vp": { + "proof_type": {"Ed25519Signature2018", "JsonWebSignature2020"}, + }, + }) + set2 := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg": {"ES256K"}, + }, + }) + expected := DIFClaimFormats(map[string]map[string][]string{}) + + result := set1.Match(set2) + assert.Equal(t, expected, result) + }) + t.Run("set 2 does not support one of the formats", func(t *testing.T) { + set1 := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg_values_supported": {"ES256", "EdDSA"}, + }, + "ldp_vp": { + "proof_type_values_supported": {"Ed25519Signature2018", "JsonWebSignature2020"}, + }, + }) + set2 := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg_values_supported": {"ES256"}, + }, + }) + expected := DIFClaimFormats(map[string]map[string][]string{ + "jwt_vp": { + "alg_values_supported": {"ES256"}, + }, + }) + + result := set1.Match(set2) + assert.Equal(t, expected, result) + }) +} diff --git a/vdr/didweb/store_test.go b/vdr/didweb/store_test.go index d6742d69f6..c8dc4d21dc 100644 --- a/vdr/didweb/store_test.go +++ b/vdr/didweb/store_test.go @@ -52,8 +52,6 @@ func Test_sqlStore_create(t *testing.T) { verificationMethods, err := store.get(testDID) require.NoError(t, err) require.Len(t, verificationMethods, 2) - require.JSONEq(t, toJSON(vm1), toJSON(verificationMethods[0])) - require.JSONEq(t, toJSON(vm2), toJSON(verificationMethods[1])) }) t.Run("single verification method", func(t *testing.T) { resetStore(t, store.db)