diff --git a/go.mod b/go.mod index e5f8b91..19df527 100644 --- a/go.mod +++ b/go.mod @@ -3,28 +3,34 @@ module attune-heart-therapy go 1.25.1 require ( - github.com/gin-gonic/gin v1.9.1 + github.com/didip/tollbooth/v7 v7.0.2 + github.com/gin-contrib/cors v1.7.6 + github.com/gin-gonic/gin v1.10.1 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/joho/godotenv v1.5.1 github.com/spf13/cobra v1.8.0 github.com/stripe/stripe-go/v76 v76.25.0 - golang.org/x/crypto v0.31.0 + golang.org/x/crypto v0.39.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.31.1 ) -require gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect +require ( + github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect +) require ( - github.com/bytedance/sonic v1.9.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect + github.com/bytedance/sonic v1.13.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 - github.com/goccy/go-json v0.10.2 // indirect + github.com/go-playground/validator/v10 v10.26.0 + github.com/goccy/go-json v0.10.5 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -33,23 +39,23 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sync v0.10.0 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + golang.org/x/arch v0.18.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/term v0.36.0 - golang.org/x/text v0.21.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + golang.org/x/text v0.26.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d69105a..0056783 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,45 @@ -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0= +github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= +github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/didip/tollbooth/v7 v7.0.2 h1:WYEfusYI6g64cN0qbZgekDrYfuYBZjUZd5+RlWi69p4= +github.com/didip/tollbooth/v7 v7.0.2/go.mod h1:RtRYfEmFGX70+ike5kSndSvLtQ3+F2EAmTI4Un/VXNc= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= +github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw= +github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 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= @@ -50,23 +59,24 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -84,28 +94,25 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stripe/stripe-go/v76 v76.25.0 h1:kmDoOTvdQSTQssQzWZQQkgbAR2Q8eXdMWbN/ylNalWA= github.com/stripe/stripe-go/v76 v76.25.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc= +golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= @@ -113,14 +120,11 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -135,4 +139,4 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/internal/middleware/cors.go b/internal/middleware/cors.go index 1f020e7..d6b76ab 100644 --- a/internal/middleware/cors.go +++ b/internal/middleware/cors.go @@ -1,22 +1,46 @@ package middleware import ( + "time" + + "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) -// CORSMiddleware handles Cross-Origin Resource Sharing +// CORSMiddleware handles Cross-Origin Resource Sharing for frontend integration func CORSMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - // Will be implemented in task 13 - c.Header("Access-Control-Allow-Origin", "*") - c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization") - - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(204) - return - } - - c.Next() - } + return cors.New(cors.Config{ + AllowOrigins: []string{ + "http://localhost:3000", // Next.js development server + "http://localhost:3001", // Alternative frontend port + "https://localhost:3000", // HTTPS development + "https://localhost:3001", // HTTPS alternative + // Add production domains here when deploying + }, + AllowMethods: []string{ + "GET", + "POST", + "PUT", + "DELETE", + "OPTIONS", + "PATCH", + }, + AllowHeaders: []string{ + "Origin", + "Content-Type", + "Content-Length", + "Accept-Encoding", + "X-CSRF-Token", + "Authorization", + "Accept", + "Cache-Control", + "X-Requested-With", + }, + ExposeHeaders: []string{ + "Content-Length", + "Content-Type", + }, + AllowCredentials: true, + MaxAge: 12 * time.Hour, // Preflight cache duration + }) } diff --git a/internal/middleware/logging.go b/internal/middleware/logging.go new file mode 100644 index 0000000..2b9515e --- /dev/null +++ b/internal/middleware/logging.go @@ -0,0 +1,97 @@ +package middleware + +import ( + "fmt" + "log" + "time" + + "github.com/gin-gonic/gin" +) + +// LoggingMiddleware creates a custom logging middleware with detailed request information +func LoggingMiddleware() gin.HandlerFunc { + return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + // Custom log format with more details + return fmt.Sprintf("[%s] %s %s %s %d %s \"%s\" %s \"%s\" %s\n", + param.TimeStamp.Format("2006-01-02 15:04:05"), + param.ClientIP, + param.Method, + param.Path, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + param.Request.Referer(), + formatBodySize(param.BodySize), + ) + }) +} + +// StructuredLoggingMiddleware creates a structured logging middleware for better log parsing +func StructuredLoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + path := c.Request.URL.Path + raw := c.Request.URL.RawQuery + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(start) + + // Get client IP + clientIP := c.ClientIP() + + // Get method + method := c.Request.Method + + // Get status code + statusCode := c.Writer.Status() + + // Get body size + bodySize := c.Writer.Size() + + // Build full path with query string + if raw != "" { + path = path + "?" + raw + } + + // Log structured information + log.Printf("REQUEST: method=%s path=%s status=%d latency=%v client_ip=%s body_size=%d user_agent=\"%s\"", + method, + path, + statusCode, + latency, + clientIP, + bodySize, + c.Request.UserAgent(), + ) + + // Log errors if any + if len(c.Errors) > 0 { + log.Printf("ERRORS: %v", c.Errors.String()) + } + } +} + +// formatBodySize formats body size in human readable format +func formatBodySize(size int) string { + if size == 0 { + return "0B" + } + + const unit = 1024 + if size < unit { + return fmt.Sprintf("%dB", size) + } + + div, exp := int64(unit), 0 + for n := size / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + + return fmt.Sprintf("%.1f%cB", float64(size)/float64(div), "KMGTPE"[exp]) +} diff --git a/internal/middleware/rate_limit.go b/internal/middleware/rate_limit.go new file mode 100644 index 0000000..a81022e --- /dev/null +++ b/internal/middleware/rate_limit.go @@ -0,0 +1,82 @@ +package middleware + +import ( + "net/http" + "time" + + "github.com/didip/tollbooth/v7" + "github.com/didip/tollbooth/v7/limiter" + "github.com/gin-gonic/gin" +) + +// RateLimitMiddleware creates a rate limiting middleware for API protection +func RateLimitMiddleware() gin.HandlerFunc { + // Create a rate limiter: 100 requests per minute per IP + lmt := tollbooth.NewLimiter(100, &limiter.ExpirableOptions{ + DefaultExpirationTTL: time.Hour, + }) + + // Configure the limiter + lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}) + lmt.SetMethods([]string{"GET", "POST", "PUT", "DELETE", "PATCH"}) + + // Configure message for rate limit exceeded + lmt.SetMessage("Rate limit exceeded. Please try again later.") + + return func(c *gin.Context) { + // Add rate limit headers + c.Header("X-Rate-Limit-Limit", "100") + c.Header("X-Rate-Limit-Window", "60s") + + // Check rate limit + httpError := tollbooth.LimitByRequest(lmt, c.Writer, c.Request) + if httpError != nil { + c.Header("Retry-After", "60") + c.JSON(http.StatusTooManyRequests, gin.H{ + "error": "Rate limit exceeded", + "message": "Too many requests. Please try again later.", + "retry_after": "60s", + }) + c.Abort() + return + } + + c.Next() + } +} + +// StrictRateLimitMiddleware creates a stricter rate limiting middleware for sensitive endpoints +func StrictRateLimitMiddleware() gin.HandlerFunc { + // Create a stricter rate limiter: 10 requests per minute per IP + lmt := tollbooth.NewLimiter(10, &limiter.ExpirableOptions{ + DefaultExpirationTTL: time.Hour, + }) + + // Configure the limiter + lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}) + lmt.SetMethods([]string{"POST", "PUT", "DELETE"}) + + // Configure message for rate limit exceeded + lmt.SetMessage("Rate limit exceeded for sensitive endpoint. Please try again later.") + + return func(c *gin.Context) { + // Add rate limit headers + c.Header("X-Rate-Limit-Limit", "10") + c.Header("X-Rate-Limit-Window", "60s") + + // Check rate limit + httpError := tollbooth.LimitByRequest(lmt, c.Writer, c.Request) + if httpError != nil { + c.Header("Retry-After", "60") + c.JSON(http.StatusTooManyRequests, gin.H{ + "error": "Rate limit exceeded", + "message": "Too many requests for this sensitive endpoint. Please try again later.", + "retry_after": "60s", + }) + c.Abort() + return + } + + c.Next() + } +} diff --git a/internal/middleware/security.go b/internal/middleware/security.go new file mode 100644 index 0000000..abb786b --- /dev/null +++ b/internal/middleware/security.go @@ -0,0 +1,30 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" +) + +// SecurityMiddleware adds security headers to responses +func SecurityMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Security headers + c.Header("X-Content-Type-Options", "nosniff") + c.Header("X-Frame-Options", "DENY") + c.Header("X-XSS-Protection", "1; mode=block") + c.Header("Referrer-Policy", "strict-origin-when-cross-origin") + c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; child-src 'none'; worker-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; manifest-src 'self'") + + // Remove server information + c.Header("Server", "") + + c.Next() + } +} + +// NoIndexMiddleware adds no-index header for non-production environments +func NoIndexMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Header("X-Robots-Tag", "noindex, nofollow, noarchive, nosnippet") + c.Next() + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 88e305a..eb50fec 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -7,15 +7,38 @@ import ( "attune-heart-therapy/internal/config" "attune-heart-therapy/internal/database" "attune-heart-therapy/internal/handlers" + "attune-heart-therapy/internal/middleware" "attune-heart-therapy/internal/services" "github.com/gin-gonic/gin" ) +// MiddlewareContainer holds all middleware functions +type MiddlewareContainer struct { + jwtService services.JWTService +} + +// Auth returns the authentication middleware +func (m *MiddlewareContainer) Auth() gin.HandlerFunc { + return middleware.AuthMiddleware(m.jwtService) +} + +// RequireAdmin returns the admin authentication middleware +func (m *MiddlewareContainer) RequireAdmin() gin.HandlerFunc { + return middleware.RequireAdmin(m.jwtService) +} + +// StrictRateLimit returns the strict rate limiting middleware +func (m *MiddlewareContainer) StrictRateLimit() gin.HandlerFunc { + return middleware.StrictRateLimitMiddleware() +} + type Server struct { config *config.Config db *database.DB router *gin.Engine + middleware *MiddlewareContainer + authHandler *handlers.AuthHandler paymentHandler *handlers.PaymentHandler bookingHandler *handlers.BookingHandler adminHandler *handlers.AdminHandler @@ -27,9 +50,8 @@ func New(cfg *config.Config) *Server { router := gin.New() - // Add basic middleware - router.Use(gin.Logger()) - router.Use(gin.Recovery()) + // Configure middleware stack + setupMiddlewareStack(router) return &Server{ config: cfg, @@ -37,6 +59,24 @@ func New(cfg *config.Config) *Server { } } +// setupMiddlewareStack configures the Gin middleware stack +func setupMiddlewareStack(router *gin.Engine) { + // Security middleware - should be first + router.Use(middleware.SecurityMiddleware()) + + // CORS middleware for frontend integration + router.Use(middleware.CORSMiddleware()) + + // Request logging middleware + router.Use(middleware.StructuredLoggingMiddleware()) + + // Recovery middleware to handle panics + router.Use(gin.Recovery()) + + // Rate limiting middleware for API protection + router.Use(middleware.RateLimitMiddleware()) +} + // Initialize sets up the database connection and runs migrations func (s *Server) Initialize() error { // Initialize database connection @@ -89,47 +129,63 @@ func (s *Server) Shutdown() error { } func (s *Server) setupRoutes() { - // Health check endpoint + // Health check endpoint - no middleware needed s.router.GET("/health", s.healthCheck) - // API v1 routes group + // API v1 routes group with base middleware v1 := s.router.Group("/api") { - // Auth routes (will be implemented in later tasks) - auth := v1.Group("/auth") + // Public routes group - no authentication required + public := v1.Group("/") { - auth.POST("/register", func(c *gin.Context) { - c.JSON(501, gin.H{"message": "Not implemented yet"}) - }) - auth.POST("/login", func(c *gin.Context) { - c.JSON(501, gin.H{"message": "Not implemented yet"}) - }) + // Auth routes - public endpoints for registration and login + auth := public.Group("/auth") + { + // Apply strict rate limiting to auth endpoints + auth.Use(s.middleware.StrictRateLimit()) + auth.POST("/register", s.authHandler.Register) + auth.POST("/login", s.authHandler.Login) + } + + // Schedule routes - public endpoint for getting available slots + public.GET("/schedules", s.bookingHandler.GetAvailableSlots) + + // Payment webhook - public endpoint for Stripe webhooks (no auth needed) + public.POST("/payments/webhook", s.paymentHandler.HandleWebhook) } - // Schedule routes - public endpoint for getting available slots - v1.GET("/schedules", s.bookingHandler.GetAvailableSlots) - - // Booking routes - require authentication - bookings := v1.Group("/bookings") - // Note: Authentication middleware will be added in task 13 + // Authenticated routes group - require JWT authentication + authenticated := v1.Group("/") + authenticated.Use(s.middleware.Auth()) { - bookings.GET("/", s.bookingHandler.GetUserBookings) - bookings.POST("/", s.bookingHandler.CreateBooking) - bookings.PUT("/:id/cancel", s.bookingHandler.CancelBooking) - bookings.PUT("/:id/reschedule", s.bookingHandler.RescheduleBooking) - } + // Auth profile routes - require authentication + authProfile := authenticated.Group("/auth") + { + authProfile.GET("/profile", s.authHandler.GetProfile) + authProfile.PUT("/profile", s.authHandler.UpdateProfile) + authProfile.POST("/logout", s.authHandler.Logout) + } - // Payment routes - payments := v1.Group("/payments") - { - payments.POST("/intent", s.paymentHandler.CreatePaymentIntent) - payments.POST("/confirm", s.paymentHandler.ConfirmPayment) - payments.POST("/webhook", s.paymentHandler.HandleWebhook) + // Booking routes - require authentication + bookings := authenticated.Group("/bookings") + { + bookings.GET("/", s.bookingHandler.GetUserBookings) + bookings.POST("/", s.bookingHandler.CreateBooking) + bookings.PUT("/:id/cancel", s.bookingHandler.CancelBooking) + bookings.PUT("/:id/reschedule", s.bookingHandler.RescheduleBooking) + } + + // Payment routes - require authentication (except webhook) + payments := authenticated.Group("/payments") + { + payments.POST("/intent", s.paymentHandler.CreatePaymentIntent) + payments.POST("/confirm", s.paymentHandler.ConfirmPayment) + } } // Admin routes - require admin authentication admin := v1.Group("/admin") - // Note: Admin authentication middleware will be added in task 13 + admin.Use(s.middleware.RequireAdmin()) { admin.GET("/dashboard", s.adminHandler.GetDashboard) admin.POST("/schedules", s.adminHandler.CreateSchedule) @@ -152,11 +208,16 @@ func (s *Server) initializeServices() { // Initialize notification service notificationService := services.NewNotificationService(repos.Notification, s.config) - // Initialize JWT service (needed for user service) + // Initialize JWT service (needed for user service and middleware) jwtService := services.NewJWTService(s.config.JWT.Secret, s.config.JWT.Expiration) + // Initialize middleware container with JWT service + s.middleware = &MiddlewareContainer{ + jwtService: jwtService, + } + // Initialize user service with notification integration - _ = services.NewUserService(repos.User, jwtService, notificationService) // Ready for auth handlers + userService := services.NewUserService(repos.User, jwtService, notificationService) // Initialize payment service with notification integration paymentService := services.NewPaymentService(s.config, repos.Booking, repos.User, notificationService) @@ -175,6 +236,7 @@ func (s *Server) initializeServices() { adminService := services.NewAdminService(repos.User, repos.Booking, repos.Schedule) // Initialize handlers + s.authHandler = handlers.NewAuthHandler(userService) s.paymentHandler = handlers.NewPaymentHandler(paymentService) s.bookingHandler = handlers.NewBookingHandler(bookingService) s.adminHandler = handlers.NewAdminHandler(adminService)