Refactor session management improve security checks and error handling
- Increase requirements of a session before attempting to cut through the OAuth login flow - Fix langkey setting for language detection in context - Refactor login requirement middleware to use redirect instead of JSON error - Utilize Echo's built-in Recover middleware for better error recovery - Enable Gzip middleware for static routes for optimized delivery - Create a new Logout route handler to invalidate session and redirect - Implement DB session existence check and invalidation logic
- commit
- 2589b4cd1b412676e6b3c87c08953dc095939b37
- author
- bnema · · 2023-11-04 11:29 UTC
- parent
- 7797a53
- signature
- unsigned
13 files · +160 -69
M .gitignore +2 -1
| @@ -12,4 +12,5 @@ gordon | |||
| 12 | 12 | dist/ | |
| 13 | 13 | data/ | |
| 14 | 14 | setup.sh | |
| 15 | - | .iscontainer⏎̸ | |
| 15 | + | .iscontainer | |
| 16 | + | sqlite.db⏎̸ | |
| @@ -12,4 +12,5 @@ gordon | |||||
| 12 | dist/ | 12 | dist/ | ||
| 13 | data/ | 13 | data/ | ||
| 14 | setup.sh | 14 | setup.sh | ||
| 15 | - | .iscontainer⏎̸ | 15 | + | .iscontainer |
| 16 | + | sqlite.db⏎̸ | |||
M go.mod +4 -2
| @@ -10,7 +10,7 @@ require ( | |||
| 10 | 10 | github.com/gorilla/sessions v1.2.1 | |
| 11 | 11 | github.com/joho/godotenv v1.5.1 | |
| 12 | 12 | github.com/labstack/echo-contrib v0.15.0 | |
| 13 | - | github.com/labstack/echo/v4 v4.11.1 | |
| 13 | + | github.com/labstack/echo/v4 v4.11.2 | |
| 14 | 14 | github.com/microcosm-cc/bluemonday v1.0.25 | |
| 15 | 15 | github.com/spf13/cobra v1.7.0 | |
| 16 | 16 | gopkg.in/yaml.v3 v3.0.1 | |
| @@ -18,6 +18,7 @@ require ( | |||
| 18 | 18 | ) | |
| 19 | 19 | ||
| 20 | 20 | require ( | |
| 21 | + | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | |
| 21 | 22 | github.com/dustin/go-humanize v1.0.1 // indirect | |
| 22 | 23 | github.com/inconshreveable/mousetrap v1.1.0 // indirect | |
| 23 | 24 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | |
| @@ -27,7 +28,7 @@ require ( | |||
| 27 | 28 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | |
| 28 | 29 | github.com/rivo/uniseg v0.4.4 // indirect | |
| 29 | 30 | github.com/spf13/pflag v1.0.5 // indirect | |
| 30 | - | github.com/stretchr/testify v1.8.2 // indirect | |
| 31 | + | github.com/stretchr/testify v1.8.4 // indirect | |
| 31 | 32 | lukechampine.com/uint128 v1.3.0 // indirect | |
| 32 | 33 | modernc.org/cc/v3 v3.41.0 // indirect | |
| 33 | 34 | modernc.org/ccgo/v3 v3.16.15 // indirect | |
| @@ -51,6 +52,7 @@ require ( | |||
| 51 | 52 | github.com/gorilla/context v1.1.1 // indirect | |
| 52 | 53 | github.com/gorilla/css v1.0.0 // indirect | |
| 53 | 54 | github.com/gorilla/securecookie v1.1.1 // indirect | |
| 55 | + | github.com/labstack/echo v3.3.10+incompatible | |
| 54 | 56 | github.com/labstack/gommon v0.4.0 // indirect | |
| 55 | 57 | github.com/mattn/go-colorable v0.1.13 // indirect | |
| 56 | 58 | github.com/mattn/go-isatty v0.0.20 // indirect | |
| @@ -10,7 +10,7 @@ require ( | |||||
| 10 | github.com/gorilla/sessions v1.2.1 | 10 | github.com/gorilla/sessions v1.2.1 | ||
| 11 | github.com/joho/godotenv v1.5.1 | 11 | github.com/joho/godotenv v1.5.1 | ||
| 12 | github.com/labstack/echo-contrib v0.15.0 | 12 | github.com/labstack/echo-contrib v0.15.0 | ||
| 13 | - | github.com/labstack/echo/v4 v4.11.1 | 13 | + | github.com/labstack/echo/v4 v4.11.2 |
| 14 | github.com/microcosm-cc/bluemonday v1.0.25 | 14 | github.com/microcosm-cc/bluemonday v1.0.25 | ||
| 15 | github.com/spf13/cobra v1.7.0 | 15 | github.com/spf13/cobra v1.7.0 | ||
| 16 | gopkg.in/yaml.v3 v3.0.1 | 16 | gopkg.in/yaml.v3 v3.0.1 | ||
| @@ -18,6 +18,7 @@ require ( | |||||
| 18 | ) | 18 | ) | ||
| 19 | 19 | ||||
| 20 | require ( | 20 | require ( | ||
| 21 | + | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | |||
| 21 | github.com/dustin/go-humanize v1.0.1 // indirect | 22 | github.com/dustin/go-humanize v1.0.1 // indirect | ||
| 22 | github.com/inconshreveable/mousetrap v1.1.0 // indirect | 23 | github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
| 23 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | 24 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||
| @@ -27,7 +28,7 @@ require ( | |||||
| 27 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | 28 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | ||
| 28 | github.com/rivo/uniseg v0.4.4 // indirect | 29 | github.com/rivo/uniseg v0.4.4 // indirect | ||
| 29 | github.com/spf13/pflag v1.0.5 // indirect | 30 | github.com/spf13/pflag v1.0.5 // indirect | ||
| 30 | - | github.com/stretchr/testify v1.8.2 // indirect | 31 | + | github.com/stretchr/testify v1.8.4 // indirect |
| 31 | lukechampine.com/uint128 v1.3.0 // indirect | 32 | lukechampine.com/uint128 v1.3.0 // indirect | ||
| 32 | modernc.org/cc/v3 v3.41.0 // indirect | 33 | modernc.org/cc/v3 v3.41.0 // indirect | ||
| 33 | modernc.org/ccgo/v3 v3.16.15 // indirect | 34 | modernc.org/ccgo/v3 v3.16.15 // indirect | ||
| @@ -51,6 +52,7 @@ require ( | |||||
| 51 | github.com/gorilla/context v1.1.1 // indirect | 52 | github.com/gorilla/context v1.1.1 // indirect | ||
| 52 | github.com/gorilla/css v1.0.0 // indirect | 53 | github.com/gorilla/css v1.0.0 // indirect | ||
| 53 | github.com/gorilla/securecookie v1.1.1 // indirect | 54 | github.com/gorilla/securecookie v1.1.1 // indirect | ||
| 55 | + | github.com/labstack/echo v3.3.10+incompatible | |||
| 54 | github.com/labstack/gommon v0.4.0 // indirect | 56 | github.com/labstack/gommon v0.4.0 // indirect | ||
| 55 | github.com/mattn/go-colorable v0.1.13 // indirect | 57 | github.com/mattn/go-colorable v0.1.13 // indirect | ||
| 56 | github.com/mattn/go-isatty v0.0.20 // indirect | 58 | github.com/mattn/go-isatty v0.0.20 // indirect | ||
M go.sum +7 -0
| @@ -15,6 +15,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 | |||
| 15 | 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
| 16 | 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
| 17 | 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
| 18 | + | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | |
| 19 | + | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | |
| 18 | 20 | github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= | |
| 19 | 21 | github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | |
| 20 | 22 | github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= | |
| @@ -57,10 +59,14 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | |||
| 57 | 59 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | |
| 58 | 60 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | |
| 59 | 61 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | |
| 62 | + | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= | |
| 63 | + | github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= | |
| 60 | 64 | github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= | |
| 61 | 65 | github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= | |
| 62 | 66 | github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= | |
| 63 | 67 | github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= | |
| 68 | + | github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE= | |
| 69 | + | github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= | |
| 64 | 70 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= | |
| 65 | 71 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= | |
| 66 | 72 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | |
| @@ -112,6 +118,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ | |||
| 112 | 118 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | |
| 113 | 119 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | |
| 114 | 120 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | |
| 121 | + | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | |
| 115 | 122 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | |
| 116 | 123 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | |
| 117 | 124 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= | |
| @@ -15,6 +15,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 | |||||
| 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
| 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| 18 | + | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | |||
| 19 | + | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | |||
| 18 | github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= | 20 | github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= | ||
| 19 | github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | 21 | github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||
| 20 | github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= | 22 | github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= | ||
| @@ -57,10 +59,14 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | |||||
| 57 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | 59 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||
| 58 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | 60 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||
| 59 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | 61 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||
| 62 | + | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= | |||
| 63 | + | github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= | |||
| 60 | github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= | 64 | github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= | ||
| 61 | github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= | 65 | github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= | ||
| 62 | github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= | 66 | github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= | ||
| 63 | github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= | 67 | github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= | ||
| 68 | + | github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE= | |||
| 69 | + | github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= | |||
| 64 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= | 70 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= | ||
| 65 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= | 71 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= | ||
| 66 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | 72 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||
| @@ -112,6 +118,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ | |||||
| 112 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | 118 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||
| 113 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | 119 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | ||
| 114 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | 120 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||
| 121 | + | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | |||
| 115 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | 122 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||
| 116 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | 123 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||
| 117 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= | 124 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= | ||
M internal/db/queries/sessions.go +21 -0
| @@ -120,3 +120,24 @@ func ExtendSessionExpiration(a *server.App, accountID string, sessionID string, | |||
| 120 | 120 | ||
| 121 | 121 | return nil | |
| 122 | 122 | } | |
| 123 | + | ||
| 124 | + | func CheckDBSessionExists(a *server.App, sessionID string) (bool, error) { | |
| 125 | + | var sessionExists bool | |
| 126 | + | query := "SELECT EXISTS(SELECT 1 FROM sessions WHERE id = ?)" | |
| 127 | + | err := a.DB.QueryRow(query, sessionID).Scan(&sessionExists) | |
| 128 | + | if err != nil { | |
| 129 | + | return false, err | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | return sessionExists, nil | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | func InvalidateDBSession(a *server.App, sessionID string) error { | |
| 136 | + | query := "UPDATE sessions SET is_online = ? WHERE id = ?" | |
| 137 | + | _, err := a.DB.Exec(query, false, sessionID) | |
| 138 | + | if err != nil { | |
| 139 | + | return err | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | return nil | |
| 143 | + | } | |
| @@ -120,3 +120,24 @@ func ExtendSessionExpiration(a *server.App, accountID string, sessionID string, | |||||
| 120 | 120 | ||||
| 121 | return nil | 121 | return nil | ||
| 122 | } | 122 | } | ||
| 123 | + | ||||
| 124 | + | func CheckDBSessionExists(a *server.App, sessionID string) (bool, error) { | |||
| 125 | + | var sessionExists bool | |||
| 126 | + | query := "SELECT EXISTS(SELECT 1 FROM sessions WHERE id = ?)" | |||
| 127 | + | err := a.DB.QueryRow(query, sessionID).Scan(&sessionExists) | |||
| 128 | + | if err != nil { | |||
| 129 | + | return false, err | |||
| 130 | + | } | |||
| 131 | + | ||||
| 132 | + | return sessionExists, nil | |||
| 133 | + | } | |||
| 134 | + | ||||
| 135 | + | func InvalidateDBSession(a *server.App, sessionID string) error { | |||
| 136 | + | query := "UPDATE sessions SET is_online = ? WHERE id = ?" | |||
| 137 | + | _, err := a.DB.Exec(query, false, sessionID) | |||
| 138 | + | if err != nil { | |||
| 139 | + | return err | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | return nil | |||
| 143 | + | } | |||
M internal/httpserve/handler/adminroutes.go +2 -2
| @@ -18,7 +18,6 @@ var ( | |||
| 18 | 18 | // GetLocalizedData returns the data for the localization | |
| 19 | 19 | func GetLocalizedData(c echo.Context, a *server.App) (map[string]interface{}, error) { | |
| 20 | 20 | lang := c.Get("LangKey") | |
| 21 | - | ||
| 22 | 21 | if lang == nil { | |
| 23 | 22 | return nil, fmt.Errorf("LangKey not found in context") | |
| 24 | 23 | } | |
| @@ -28,7 +27,8 @@ func GetLocalizedData(c echo.Context, a *server.App) (map[string]interface{}, er | |||
| 28 | 27 | return nil, fmt.Errorf("failed to get localization: %w", err) | |
| 29 | 28 | } | |
| 30 | 29 | return map[string]interface{}{ | |
| 31 | - | "Lang": yamlData, | |
| 30 | + | "Lang": yamlData, | |
| 31 | + | "LogoutURL": a.Config.Admin.Path + "/logout", | |
| 32 | 32 | }, nil | |
| 33 | 33 | } | |
| 34 | 34 | ||
| @@ -18,7 +18,6 @@ var ( | |||||
| 18 | // GetLocalizedData returns the data for the localization | 18 | // GetLocalizedData returns the data for the localization | ||
| 19 | func GetLocalizedData(c echo.Context, a *server.App) (map[string]interface{}, error) { | 19 | func GetLocalizedData(c echo.Context, a *server.App) (map[string]interface{}, error) { | ||
| 20 | lang := c.Get("LangKey") | 20 | lang := c.Get("LangKey") | ||
| 21 | - | ||||
| 22 | if lang == nil { | 21 | if lang == nil { | ||
| 23 | return nil, fmt.Errorf("LangKey not found in context") | 22 | return nil, fmt.Errorf("LangKey not found in context") | ||
| 24 | } | 23 | } | ||
| @@ -28,7 +27,8 @@ func GetLocalizedData(c echo.Context, a *server.App) (map[string]interface{}, er | |||||
| 28 | return nil, fmt.Errorf("failed to get localization: %w", err) | 27 | return nil, fmt.Errorf("failed to get localization: %w", err) | ||
| 29 | } | 28 | } | ||
| 30 | return map[string]interface{}{ | 29 | return map[string]interface{}{ | ||
| 31 | - | "Lang": yamlData, | 30 | + | "Lang": yamlData, |
| 31 | + | "LogoutURL": a.Config.Admin.Path + "/logout", | |||
| 32 | }, nil | 32 | }, nil | ||
| 33 | } | 33 | } | ||
| 34 | 34 | ||||
A internal/httpserve/handler/logout.go +51 -0
| @@ -0,0 +1,51 @@ | |||
| 1 | + | package handler | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | "net/http" | |
| 6 | + | "time" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/gordon/internal/db/queries" | |
| 9 | + | "github.com/bnema/gordon/internal/server" | |
| 10 | + | "github.com/gorilla/sessions" | |
| 11 | + | "github.com/labstack/echo/v4" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | func Logout(c echo.Context, a *server.App) error { | |
| 15 | + | // Retrieve the session | |
| 16 | + | sess, err := getSession(c) | |
| 17 | + | if err != nil || sess == nil { | |
| 18 | + | // if there is no session, redirect to the login page | |
| 19 | + | return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |
| 20 | + | ||
| 21 | + | } | |
| 22 | + | ||
| 23 | + | // Invalidate the session in the database | |
| 24 | + | if sess != nil { | |
| 25 | + | err := queries.InvalidateDBSession(a, sess.Values["sessionID"].(string)) | |
| 26 | + | if err != nil { | |
| 27 | + | return fmt.Errorf("failed to invalidate session: %w", err) | |
| 28 | + | } | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | // Delete the session | |
| 32 | + | sess.Options = &sessions.Options{ | |
| 33 | + | Path: "/", | |
| 34 | + | MaxAge: -1, | |
| 35 | + | HttpOnly: true, | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | err = sess.Save(c.Request(), c.Response()) | |
| 39 | + | if err != nil { | |
| 40 | + | return fmt.Errorf("failed to save session: %w", err) | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // Drop the cookie | |
| 44 | + | cookie := new(http.Cookie) | |
| 45 | + | cookie.Name = "session" | |
| 46 | + | cookie.Value = "" | |
| 47 | + | cookie.Expires = time.Now().Add(-1 * time.Hour) | |
| 48 | + | c.SetCookie(cookie) | |
| 49 | + | ||
| 50 | + | return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |
| 51 | + | } | |
| @@ -0,0 +1,51 @@ | |||||
| 1 | + | package handler | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | "net/http" | |||
| 6 | + | "time" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/gordon/internal/db/queries" | |||
| 9 | + | "github.com/bnema/gordon/internal/server" | |||
| 10 | + | "github.com/gorilla/sessions" | |||
| 11 | + | "github.com/labstack/echo/v4" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | func Logout(c echo.Context, a *server.App) error { | |||
| 15 | + | // Retrieve the session | |||
| 16 | + | sess, err := getSession(c) | |||
| 17 | + | if err != nil || sess == nil { | |||
| 18 | + | // if there is no session, redirect to the login page | |||
| 19 | + | return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |||
| 20 | + | ||||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | // Invalidate the session in the database | |||
| 24 | + | if sess != nil { | |||
| 25 | + | err := queries.InvalidateDBSession(a, sess.Values["sessionID"].(string)) | |||
| 26 | + | if err != nil { | |||
| 27 | + | return fmt.Errorf("failed to invalidate session: %w", err) | |||
| 28 | + | } | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | // Delete the session | |||
| 32 | + | sess.Options = &sessions.Options{ | |||
| 33 | + | Path: "/", | |||
| 34 | + | MaxAge: -1, | |||
| 35 | + | HttpOnly: true, | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | err = sess.Save(c.Request(), c.Response()) | |||
| 39 | + | if err != nil { | |||
| 40 | + | return fmt.Errorf("failed to save session: %w", err) | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | // Drop the cookie | |||
| 44 | + | cookie := new(http.Cookie) | |||
| 45 | + | cookie.Name = "session" | |||
| 46 | + | cookie.Value = "" | |||
| 47 | + | cookie.Expires = time.Now().Add(-1 * time.Hour) | |||
| 48 | + | c.SetCookie(cookie) | |||
| 49 | + | ||||
| 50 | + | return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |||
| 51 | + | } | |||
M internal/httpserve/handler/oauth.go +53 -22
| @@ -57,38 +57,69 @@ func RenderLoginPage(c echo.Context, a *server.App) error { | |||
| 57 | 57 | return c.HTML(200, renderedHTML) | |
| 58 | 58 | } | |
| 59 | 59 | ||
| 60 | - | // StartOAuthGithub starts the Github OAuth flow | |
| 60 | + | // StartOAuthGithub starts the Github OAuth flow. | |
| 61 | 61 | func StartOAuthGithub(c echo.Context, a *server.App) error { | |
| 62 | - | // Check if the session is already here and valid before going for the oauth flow | |
| 62 | + | fmt.Println("Attempting to get an existing session.") | |
| 63 | 63 | sess, err := getSession(c) | |
| 64 | + | if err != nil || sess == nil || sess.Values == nil { | |
| 65 | + | fmt.Println("Session retrieval failed, initiating new GitHub OAuth flow.") | |
| 66 | + | return initiateGithubOAuthFlow(c, a) | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | fmt.Println("Attempting to assert session values.") | |
| 70 | + | sessionID, ok1 := sess.Values["sessionID"].(string) | |
| 71 | + | accountID, ok2 := sess.Values["accountID"].(string) | |
| 72 | + | authenticated, ok3 := sess.Values["authenticated"].(bool) | |
| 73 | + | ||
| 74 | + | if !ok1 || !ok2 || !ok3 || !authenticated { | |
| 75 | + | fmt.Println("Session assertion failed, or user not authenticated. Initiating new OAuth flow.") | |
| 76 | + | return initiateGithubOAuthFlow(c, a) | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | fmt.Println("Checking if session exists in DB.") | |
| 80 | + | sessionExists, err := queries.CheckDBSessionExists(a, sessionID) | |
| 64 | 81 | if err != nil { | |
| 65 | - | return err | |
| 82 | + | fmt.Printf("Error checking if session exists: %v\n", err) | |
| 83 | + | return fmt.Errorf("could not check if session exists: %w", err) | |
| 66 | 84 | } | |
| 67 | 85 | ||
| 68 | - | // Check if the session values are valid | |
| 69 | - | sessionID, accountID, ok := sess.Values["accountID"].(string), sess.Values["sessionID"].(string), sess.Values["authenticated"].(bool) | |
| 70 | - | if ok && accountID != "" && sessionID != "" { | |
| 71 | - | // Check if the session is expired | |
| 72 | - | sessionExpired, err := IsSessionExpiredInDB(a, accountID, sessionID) | |
| 73 | - | if err != nil { | |
| 74 | - | return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) | |
| 75 | - | } | |
| 76 | - | if !sessionExpired { | |
| 77 | - | sessionExpiryTime := 30 * time.Minute | |
| 78 | - | // Extend session expiration conditionally, e.g., if it's close to expiring | |
| 79 | - | err = queries.ExtendSessionExpiration(a, accountID, sessionID, time.Now().Add(sessionExpiryTime)) | |
| 80 | - | if err != nil { | |
| 81 | - | return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) | |
| 82 | - | } | |
| 83 | - | return c.Redirect(http.StatusFound, a.Config.Admin.Path) | |
| 84 | - | } | |
| 86 | + | if !sessionExists { | |
| 87 | + | fmt.Println("Session does not exist in DB, initiating new OAuth flow.") | |
| 88 | + | return initiateGithubOAuthFlow(c, a) | |
| 85 | 89 | } | |
| 86 | 90 | ||
| 87 | - | //Initiate the Github OAuth flow | |
| 91 | + | fmt.Println("Getting the expiration time of the session.") | |
| 92 | + | currentTime := time.Now() | |
| 93 | + | sessionExpiration, err := queries.GetSessionExpiration(a, accountID, sessionID, currentTime) | |
| 94 | + | if err != nil { | |
| 95 | + | fmt.Printf("Error getting session expiration: %v\n", err) | |
| 96 | + | return fmt.Errorf("could not get session expiration: %w", err) | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | if currentTime.After(sessionExpiration) { | |
| 100 | + | fmt.Println("Current time is after session expiration, initiating new OAuth flow.") | |
| 101 | + | return initiateGithubOAuthFlow(c, a) | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | fmt.Println("Extending session expiration time by 30 minutes.") | |
| 105 | + | newExpirationTime := currentTime.Add(30 * time.Minute) | |
| 106 | + | err = queries.ExtendSessionExpiration(a, accountID, sessionID, newExpirationTime) | |
| 107 | + | if err != nil { | |
| 108 | + | fmt.Printf("Error extending session expiration: %v\n", err) | |
| 109 | + | return fmt.Errorf("could not extend session expiration: %w", err) | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | fmt.Println("Session is valid and extended. Redirecting to admin panel.") | |
| 113 | + | return c.Redirect(http.StatusFound, a.Config.Admin.Path) | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | // initiateGithubOAuthFlow starts a new GitHub OAuth flow | |
| 117 | + | func initiateGithubOAuthFlow(c echo.Context, a *server.App) error { | |
| 88 | 118 | clientID := os.Getenv("GITHUB_APP_ID") | |
| 89 | 119 | redirectDomain := a.GenerateOauthCallbackURL() | |
| 90 | 120 | encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain)) | |
| 91 | - | // Redirect to Gordon's Proxy to grab the oauth access | |
| 121 | + | ||
| 122 | + | // Redirect to GitHub's OAuth endpoint to grab the OAuth access | |
| 92 | 123 | proxyURL := a.Config.Build.ProxyURL | |
| 93 | 124 | oauthURL := fmt.Sprintf( | |
| 94 | 125 | "%s/github/authorize?client_id=%s&redirect_uri=%s&state=%s", | |
| @@ -57,38 +57,69 @@ func RenderLoginPage(c echo.Context, a *server.App) error { | |||||
| 57 | return c.HTML(200, renderedHTML) | 57 | return c.HTML(200, renderedHTML) | ||
| 58 | } | 58 | } | ||
| 59 | 59 | ||||
| 60 | - | // StartOAuthGithub starts the Github OAuth flow | 60 | + | // StartOAuthGithub starts the Github OAuth flow. |
| 61 | func StartOAuthGithub(c echo.Context, a *server.App) error { | 61 | func StartOAuthGithub(c echo.Context, a *server.App) error { | ||
| 62 | - | // Check if the session is already here and valid before going for the oauth flow | 62 | + | fmt.Println("Attempting to get an existing session.") |
| 63 | sess, err := getSession(c) | 63 | sess, err := getSession(c) | ||
| 64 | + | if err != nil || sess == nil || sess.Values == nil { | |||
| 65 | + | fmt.Println("Session retrieval failed, initiating new GitHub OAuth flow.") | |||
| 66 | + | return initiateGithubOAuthFlow(c, a) | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | fmt.Println("Attempting to assert session values.") | |||
| 70 | + | sessionID, ok1 := sess.Values["sessionID"].(string) | |||
| 71 | + | accountID, ok2 := sess.Values["accountID"].(string) | |||
| 72 | + | authenticated, ok3 := sess.Values["authenticated"].(bool) | |||
| 73 | + | ||||
| 74 | + | if !ok1 || !ok2 || !ok3 || !authenticated { | |||
| 75 | + | fmt.Println("Session assertion failed, or user not authenticated. Initiating new OAuth flow.") | |||
| 76 | + | return initiateGithubOAuthFlow(c, a) | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | fmt.Println("Checking if session exists in DB.") | |||
| 80 | + | sessionExists, err := queries.CheckDBSessionExists(a, sessionID) | |||
| 64 | if err != nil { | 81 | if err != nil { | ||
| 65 | - | return err | 82 | + | fmt.Printf("Error checking if session exists: %v\n", err) |
| 83 | + | return fmt.Errorf("could not check if session exists: %w", err) | |||
| 66 | } | 84 | } | ||
| 67 | 85 | ||||
| 68 | - | // Check if the session values are valid | 86 | + | if !sessionExists { |
| 69 | - | sessionID, accountID, ok := sess.Values["accountID"].(string), sess.Values["sessionID"].(string), sess.Values["authenticated"].(bool) | 87 | + | fmt.Println("Session does not exist in DB, initiating new OAuth flow.") |
| 70 | - | if ok && accountID != "" && sessionID != "" { | 88 | + | return initiateGithubOAuthFlow(c, a) |
| 71 | - | // Check if the session is expired | |||
| 72 | - | sessionExpired, err := IsSessionExpiredInDB(a, accountID, sessionID) | |||
| 73 | - | if err != nil { | |||
| 74 | - | return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) | |||
| 75 | - | } | |||
| 76 | - | if !sessionExpired { | |||
| 77 | - | sessionExpiryTime := 30 * time.Minute | |||
| 78 | - | // Extend session expiration conditionally, e.g., if it's close to expiring | |||
| 79 | - | err = queries.ExtendSessionExpiration(a, accountID, sessionID, time.Now().Add(sessionExpiryTime)) | |||
| 80 | - | if err != nil { | |||
| 81 | - | return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) | |||
| 82 | - | } | |||
| 83 | - | return c.Redirect(http.StatusFound, a.Config.Admin.Path) | |||
| 84 | - | } | |||
| 85 | } | 89 | } | ||
| 86 | 90 | ||||
| 87 | - | //Initiate the Github OAuth flow | 91 | + | fmt.Println("Getting the expiration time of the session.") |
| 92 | + | currentTime := time.Now() | |||
| 93 | + | sessionExpiration, err := queries.GetSessionExpiration(a, accountID, sessionID, currentTime) | |||
| 94 | + | if err != nil { | |||
| 95 | + | fmt.Printf("Error getting session expiration: %v\n", err) | |||
| 96 | + | return fmt.Errorf("could not get session expiration: %w", err) | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | if currentTime.After(sessionExpiration) { | |||
| 100 | + | fmt.Println("Current time is after session expiration, initiating new OAuth flow.") | |||
| 101 | + | return initiateGithubOAuthFlow(c, a) | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | fmt.Println("Extending session expiration time by 30 minutes.") | |||
| 105 | + | newExpirationTime := currentTime.Add(30 * time.Minute) | |||
| 106 | + | err = queries.ExtendSessionExpiration(a, accountID, sessionID, newExpirationTime) | |||
| 107 | + | if err != nil { | |||
| 108 | + | fmt.Printf("Error extending session expiration: %v\n", err) | |||
| 109 | + | return fmt.Errorf("could not extend session expiration: %w", err) | |||
| 110 | + | } | |||
| 111 | + | ||||
| 112 | + | fmt.Println("Session is valid and extended. Redirecting to admin panel.") | |||
| 113 | + | return c.Redirect(http.StatusFound, a.Config.Admin.Path) | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | // initiateGithubOAuthFlow starts a new GitHub OAuth flow | |||
| 117 | + | func initiateGithubOAuthFlow(c echo.Context, a *server.App) error { | |||
| 88 | clientID := os.Getenv("GITHUB_APP_ID") | 118 | clientID := os.Getenv("GITHUB_APP_ID") | ||
| 89 | redirectDomain := a.GenerateOauthCallbackURL() | 119 | redirectDomain := a.GenerateOauthCallbackURL() | ||
| 90 | encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain)) | 120 | encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain)) | ||
| 91 | - | // Redirect to Gordon's Proxy to grab the oauth access | 121 | + | |
| 122 | + | // Redirect to GitHub's OAuth endpoint to grab the OAuth access | |||
| 92 | proxyURL := a.Config.Build.ProxyURL | 123 | proxyURL := a.Config.Build.ProxyURL | ||
| 93 | oauthURL := fmt.Sprintf( | 124 | oauthURL := fmt.Sprintf( | ||
| 94 | "%s/github/authorize?client_id=%s&redirect_uri=%s&state=%s", | 125 | "%s/github/authorize?client_id=%s&redirect_uri=%s&state=%s", | ||
M internal/httpserve/handler/sessiondb.go +2 -2
| @@ -47,9 +47,9 @@ func ValidateSessionAndUser(c echo.Context, a *server.App) error { | |||
| 47 | 47 | return nil | |
| 48 | 48 | } | |
| 49 | 49 | ||
| 50 | - | func IsSessionExpiredInDB(a *server.App, userID string, sessionID string) (bool, error) { | |
| 50 | + | func IsSessionExpiredInDB(a *server.App, accountID string, sessionID string) (bool, error) { | |
| 51 | 51 | currentTime := time.Now() | |
| 52 | - | expirationTime, err := queries.GetSessionExpiration(a, userID, sessionID, currentTime) | |
| 52 | + | expirationTime, err := queries.GetSessionExpiration(a, accountID, sessionID, currentTime) | |
| 53 | 53 | if err != nil { | |
| 54 | 54 | return false, err | |
| 55 | 55 | } | |
| @@ -47,9 +47,9 @@ func ValidateSessionAndUser(c echo.Context, a *server.App) error { | |||||
| 47 | return nil | 47 | return nil | ||
| 48 | } | 48 | } | ||
| 49 | 49 | ||||
| 50 | - | func IsSessionExpiredInDB(a *server.App, userID string, sessionID string) (bool, error) { | 50 | + | func IsSessionExpiredInDB(a *server.App, accountID string, sessionID string) (bool, error) { |
| 51 | currentTime := time.Now() | 51 | currentTime := time.Now() | ||
| 52 | - | expirationTime, err := queries.GetSessionExpiration(a, userID, sessionID, currentTime) | 52 | + | expirationTime, err := queries.GetSessionExpiration(a, accountID, sessionID, currentTime) |
| 53 | if err != nil { | 53 | if err != nil { | ||
| 54 | return false, err | 54 | return false, err | ||
| 55 | } | 55 | } | ||
M internal/httpserve/middleware/langdetect.go +1 -16
| @@ -3,34 +3,19 @@ package middleware | |||
| 3 | 3 | import ( | |
| 4 | 4 | "strings" | |
| 5 | 5 | ||
| 6 | - | "github.com/labstack/echo-contrib/session" | |
| 7 | 6 | "github.com/labstack/echo/v4" | |
| 8 | 7 | ) | |
| 9 | 8 | ||
| 10 | - | // LangKey is a key for setting and getting the language from the context | |
| 11 | - | const LangKey = "CurrentLang" | |
| 12 | - | ||
| 13 | 9 | // LanguageDetectiondetects the current language and sets it in the context | |
| 14 | 10 | func LanguageDetection(next echo.HandlerFunc) echo.HandlerFunc { | |
| 15 | 11 | return func(c echo.Context) error { | |
| 16 | 12 | lang := detectCurrentLanguage(c) | |
| 17 | - | c.Set(LangKey, lang) | |
| 13 | + | c.Set("LangKey", lang) | |
| 18 | 14 | return next(c) | |
| 19 | 15 | } | |
| 20 | 16 | } | |
| 21 | 17 | ||
| 22 | 18 | func detectCurrentLanguage(c echo.Context) string { | |
| 23 | - | // Check if the lang key is set in the session storage | |
| 24 | - | sess, err := session.Get("session", c) | |
| 25 | - | if err != nil { | |
| 26 | - | // If session storage is not available, proceed with "Accept-Language" header detection | |
| 27 | - | return "en" | |
| 28 | - | } | |
| 29 | - | ||
| 30 | - | if langValue, ok := sess.Values["lang"]; ok && langValue != nil { | |
| 31 | - | return langValue.(string) // Return the lang value from the session storage | |
| 32 | - | } | |
| 33 | - | ||
| 34 | 19 | // If lang key is not set, proceed with "Accept-Language" header detection | |
| 35 | 20 | header := c.Request().Header.Get("Accept-Language") | |
| 36 | 21 | if strings.Contains(header, "fr") { | |
| @@ -3,34 +3,19 @@ package middleware | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "strings" | 4 | "strings" | ||
| 5 | 5 | ||||
| 6 | - | "github.com/labstack/echo-contrib/session" | |||
| 7 | "github.com/labstack/echo/v4" | 6 | "github.com/labstack/echo/v4" | ||
| 8 | ) | 7 | ) | ||
| 9 | 8 | ||||
| 10 | - | // LangKey is a key for setting and getting the language from the context | |||
| 11 | - | const LangKey = "CurrentLang" | |||
| 12 | - | ||||
| 13 | // LanguageDetectiondetects the current language and sets it in the context | 9 | // LanguageDetectiondetects the current language and sets it in the context | ||
| 14 | func LanguageDetection(next echo.HandlerFunc) echo.HandlerFunc { | 10 | func LanguageDetection(next echo.HandlerFunc) echo.HandlerFunc { | ||
| 15 | return func(c echo.Context) error { | 11 | return func(c echo.Context) error { | ||
| 16 | lang := detectCurrentLanguage(c) | 12 | lang := detectCurrentLanguage(c) | ||
| 17 | - | c.Set(LangKey, lang) | 13 | + | c.Set("LangKey", lang) |
| 18 | return next(c) | 14 | return next(c) | ||
| 19 | } | 15 | } | ||
| 20 | } | 16 | } | ||
| 21 | 17 | ||||
| 22 | func detectCurrentLanguage(c echo.Context) string { | 18 | func detectCurrentLanguage(c echo.Context) string { | ||
| 23 | - | // Check if the lang key is set in the session storage | |||
| 24 | - | sess, err := session.Get("session", c) | |||
| 25 | - | if err != nil { | |||
| 26 | - | // If session storage is not available, proceed with "Accept-Language" header detection | |||
| 27 | - | return "en" | |||
| 28 | - | } | |||
| 29 | - | ||||
| 30 | - | if langValue, ok := sess.Values["lang"]; ok && langValue != nil { | |||
| 31 | - | return langValue.(string) // Return the lang value from the session storage | |||
| 32 | - | } | |||
| 33 | - | ||||
| 34 | // If lang key is not set, proceed with "Accept-Language" header detection | 19 | // If lang key is not set, proceed with "Accept-Language" header detection | ||
| 35 | header := c.Request().Header.Get("Accept-Language") | 20 | header := c.Request().Header.Get("Accept-Language") | ||
| 36 | if strings.Contains(header, "fr") { | 21 | if strings.Contains(header, "fr") { | ||
M internal/httpserve/middleware/requirelogin.go +8 -5
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package middleware | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "net/http" | |
| 4 | 5 | "time" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/bnema/gordon/internal/httpserve/handler" | |
| @@ -9,6 +10,10 @@ import ( | |||
| 9 | 10 | "github.com/labstack/echo/v4" | |
| 10 | 11 | ) | |
| 11 | 12 | ||
| 13 | + | func redirectToLogin(c echo.Context, a *server.App) error { | |
| 14 | + | return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |
| 15 | + | } | |
| 16 | + | ||
| 12 | 17 | func RequireLogin(a *server.App) echo.MiddlewareFunc { | |
| 13 | 18 | return func(next echo.HandlerFunc) echo.HandlerFunc { | |
| 14 | 19 | return func(c echo.Context) error { | |
| @@ -23,15 +28,13 @@ func RequireLogin(a *server.App) echo.MiddlewareFunc { | |||
| 23 | 28 | time.Sleep(time.Duration(i+1) * 500 * time.Millisecond) | |
| 24 | 29 | } | |
| 25 | 30 | } | |
| 26 | - | return c.JSON(401, map[string]string{"error": err.Error()}) | |
| 31 | + | // propose the redirect auth url as httpcode | |
| 32 | + | return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |
| 27 | 33 | } | |
| 34 | + | ||
| 28 | 35 | } | |
| 29 | 36 | } | |
| 30 | 37 | ||
| 31 | - | // func redirectToLogin(c echo.Context, a *server.App) error { | |
| 32 | - | // return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |
| 33 | - | // } | |
| 34 | - | ||
| 35 | 38 | // func DeleteCookieAndRedirectToLogin(c echo.Context, a *server.App) error { | |
| 36 | 39 | // // Delete session cookie | |
| 37 | 40 | // cookie := new(http.Cookie) | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package middleware | 1 | package middleware | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "net/http" | |||
| 4 | "time" | 5 | "time" | ||
| 5 | 6 | ||||
| 6 | "github.com/bnema/gordon/internal/httpserve/handler" | 7 | "github.com/bnema/gordon/internal/httpserve/handler" | ||
| @@ -9,6 +10,10 @@ import ( | |||||
| 9 | "github.com/labstack/echo/v4" | 10 | "github.com/labstack/echo/v4" | ||
| 10 | ) | 11 | ) | ||
| 11 | 12 | ||||
| 13 | + | func redirectToLogin(c echo.Context, a *server.App) error { | |||
| 14 | + | return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |||
| 15 | + | } | |||
| 16 | + | ||||
| 12 | func RequireLogin(a *server.App) echo.MiddlewareFunc { | 17 | func RequireLogin(a *server.App) echo.MiddlewareFunc { | ||
| 13 | return func(next echo.HandlerFunc) echo.HandlerFunc { | 18 | return func(next echo.HandlerFunc) echo.HandlerFunc { | ||
| 14 | return func(c echo.Context) error { | 19 | return func(c echo.Context) error { | ||
| @@ -23,15 +28,13 @@ func RequireLogin(a *server.App) echo.MiddlewareFunc { | |||||
| 23 | time.Sleep(time.Duration(i+1) * 500 * time.Millisecond) | 28 | time.Sleep(time.Duration(i+1) * 500 * time.Millisecond) | ||
| 24 | } | 29 | } | ||
| 25 | } | 30 | } | ||
| 26 | - | return c.JSON(401, map[string]string{"error": err.Error()}) | 31 | + | // propose the redirect auth url as httpcode |
| 32 | + | return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |||
| 27 | } | 33 | } | ||
| 34 | + | ||||
| 28 | } | 35 | } | ||
| 29 | } | 36 | } | ||
| 30 | 37 | ||||
| 31 | - | // func redirectToLogin(c echo.Context, a *server.App) error { | |||
| 32 | - | // return c.Redirect(http.StatusSeeOther, a.Config.Admin.Path+"/login") | |||
| 33 | - | // } | |||
| 34 | - | ||||
| 35 | // func DeleteCookieAndRedirectToLogin(c echo.Context, a *server.App) error { | 38 | // func DeleteCookieAndRedirectToLogin(c echo.Context, a *server.App) error { | ||
| 36 | // // Delete session cookie | 39 | // // Delete session cookie | ||
| 37 | // cookie := new(http.Cookie) | 40 | // cookie := new(http.Cookie) | ||
M internal/httpserve/router.go +5 -2
| @@ -6,10 +6,13 @@ import ( | |||
| 6 | 6 | "github.com/bnema/gordon/internal/server" | |
| 7 | 7 | _ "github.com/joho/godotenv/autoload" | |
| 8 | 8 | "github.com/labstack/echo/v4" | |
| 9 | + | echomid "github.com/labstack/echo/v4/middleware" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| 11 | 12 | // RegisterRoutes registers all routes and middlewares | |
| 12 | 13 | func RegisterRoutes(e *echo.Echo, a *server.App) *echo.Echo { | |
| 14 | + | // e.Use(echomid.Logger()) too verbose | |
| 15 | + | e.Use(echomid.Recover()) | |
| 13 | 16 | // Initiate the session middleware | |
| 14 | 17 | e.Use(middleware.InitSessionMiddleware(a)) | |
| 15 | 18 | ||
| @@ -63,7 +66,7 @@ func bindAPIEndpoints(e *echo.Echo, a *server.App) { | |||
| 63 | 66 | func bindStaticRoute(e *echo.Echo, a *server.App, path string) { | |
| 64 | 67 | e.GET(path, func(c echo.Context) error { | |
| 65 | 68 | return handler.StaticRoute(c, a) | |
| 66 | - | }) | |
| 69 | + | }, echomid.Gzip()) | |
| 67 | 70 | } | |
| 68 | 71 | ||
| 69 | 72 | // bindLoginRoute binds all login routes | |
| @@ -78,7 +81,7 @@ func bindLoginRoute(e *echo.Echo, a *server.App, adminPath string) { | |||
| 78 | 81 | return handler.OAuthCallback(c, a) | |
| 79 | 82 | }) | |
| 80 | 83 | e.GET(adminPath+"/logout", func(c echo.Context) error { | |
| 81 | - | return c.HTML(200, "<h1>Logout</h1>") | |
| 84 | + | return handler.Logout(c, a) | |
| 82 | 85 | }) | |
| 83 | 86 | } | |
| 84 | 87 | ||
| @@ -6,10 +6,13 @@ import ( | |||||
| 6 | "github.com/bnema/gordon/internal/server" | 6 | "github.com/bnema/gordon/internal/server" | ||
| 7 | _ "github.com/joho/godotenv/autoload" | 7 | _ "github.com/joho/godotenv/autoload" | ||
| 8 | "github.com/labstack/echo/v4" | 8 | "github.com/labstack/echo/v4" | ||
| 9 | + | echomid "github.com/labstack/echo/v4/middleware" | |||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| 11 | // RegisterRoutes registers all routes and middlewares | 12 | // RegisterRoutes registers all routes and middlewares | ||
| 12 | func RegisterRoutes(e *echo.Echo, a *server.App) *echo.Echo { | 13 | func RegisterRoutes(e *echo.Echo, a *server.App) *echo.Echo { | ||
| 14 | + | // e.Use(echomid.Logger()) too verbose | |||
| 15 | + | e.Use(echomid.Recover()) | |||
| 13 | // Initiate the session middleware | 16 | // Initiate the session middleware | ||
| 14 | e.Use(middleware.InitSessionMiddleware(a)) | 17 | e.Use(middleware.InitSessionMiddleware(a)) | ||
| 15 | 18 | ||||
| @@ -63,7 +66,7 @@ func bindAPIEndpoints(e *echo.Echo, a *server.App) { | |||||
| 63 | func bindStaticRoute(e *echo.Echo, a *server.App, path string) { | 66 | func bindStaticRoute(e *echo.Echo, a *server.App, path string) { | ||
| 64 | e.GET(path, func(c echo.Context) error { | 67 | e.GET(path, func(c echo.Context) error { | ||
| 65 | return handler.StaticRoute(c, a) | 68 | return handler.StaticRoute(c, a) | ||
| 66 | - | }) | 69 | + | }, echomid.Gzip()) |
| 67 | } | 70 | } | ||
| 68 | 71 | ||||
| 69 | // bindLoginRoute binds all login routes | 72 | // bindLoginRoute binds all login routes | ||
| @@ -78,7 +81,7 @@ func bindLoginRoute(e *echo.Echo, a *server.App, adminPath string) { | |||||
| 78 | return handler.OAuthCallback(c, a) | 81 | return handler.OAuthCallback(c, a) | ||
| 79 | }) | 82 | }) | ||
| 80 | e.GET(adminPath+"/logout", func(c echo.Context) error { | 83 | e.GET(adminPath+"/logout", func(c echo.Context) error { | ||
| 81 | - | return c.HTML(200, "<h1>Logout</h1>") | 84 | + | return handler.Logout(c, a) |
| 82 | }) | 85 | }) | ||
| 83 | } | 86 | } | ||
| 84 | 87 | ||||
M internal/templating/models/html/admin/index.gohtml +2 -15
| @@ -1,25 +1,12 @@ | |||
| 1 | - | <!DOCTYPE html> | |
| 1 | + | <!doctype html> | |
| 2 | 2 | <html lang="en"> | |
| 3 | 3 | {{template "header.gohtml" .}} | |
| 4 | 4 | ||
| 5 | 5 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-0"> | |
| 6 | 6 | {{template "menu.gohtml" .}} | |
| 7 | 7 | <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4"> | |
| 8 | - | <div class="hidden md:flex mb-4 justify-end"> | |
| 9 | - | <div class="w-72 h-10 bg-gray-300 rounded-lg"> | |
| 10 | - | <div class="flex justify-between items-center h-full px-2"> | |
| 11 | - | <input | |
| 12 | - | type="text" | |
| 13 | - | class="w-full h-full bg-transparent outline-none" | |
| 14 | - | placeholder="Search" | |
| 15 | - | /> | |
| 16 | - | <i class="iconoir-search"></i> | |
| 17 | - | </div> | |
| 18 | - | </div> | |
| 19 | - | </div> | |
| 20 | 8 | <div | |
| 21 | - | class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow" | |
| 22 | - | > | |
| 9 | + | class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"> | |
| 23 | 10 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | |
| 24 | 11 | <h2>{{index .Lang "dashboard" "content" "title"}}</h2> | |
| 25 | 12 | </div> | |
| @@ -1,25 +1,12 @@ | |||||
| 1 | - | <!DOCTYPE html> | 1 | + | <!doctype html> |
| 2 | <html lang="en"> | 2 | <html lang="en"> | ||
| 3 | {{template "header.gohtml" .}} | 3 | {{template "header.gohtml" .}} | ||
| 4 | 4 | ||||
| 5 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-0"> | 5 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-0"> | ||
| 6 | {{template "menu.gohtml" .}} | 6 | {{template "menu.gohtml" .}} | ||
| 7 | <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4"> | 7 | <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4"> | ||
| 8 | - | <div class="hidden md:flex mb-4 justify-end"> | |||
| 9 | - | <div class="w-72 h-10 bg-gray-300 rounded-lg"> | |||
| 10 | - | <div class="flex justify-between items-center h-full px-2"> | |||
| 11 | - | <input | |||
| 12 | - | type="text" | |||
| 13 | - | class="w-full h-full bg-transparent outline-none" | |||
| 14 | - | placeholder="Search" | |||
| 15 | - | /> | |||
| 16 | - | <i class="iconoir-search"></i> | |||
| 17 | - | </div> | |||
| 18 | - | </div> | |||
| 19 | - | </div> | |||
| 20 | <div | 8 | <div | ||
| 21 | - | class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow" | 9 | + | class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"> |
| 22 | - | > | |||
| 23 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | 10 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | ||
| 24 | <h2>{{index .Lang "dashboard" "content" "title"}}</h2> | 11 | <h2>{{index .Lang "dashboard" "content" "title"}}</h2> | ||
| 25 | </div> | 12 | </div> | ||
M internal/templating/models/html/fragments/menu.gohtml +2 -2
| @@ -22,13 +22,13 @@ | |||
| 22 | 22 | </div> | |
| 23 | 23 | <div class="w-full flex space-x-4 justify-end"> | |
| 24 | 24 | <a | |
| 25 | - | href="/admin/settings" | |
| 25 | + | href="{{ .UserSettingsURL }}" | |
| 26 | 26 | title="User settings" | |
| 27 | 27 | class="iconf text-gordonmascot-mblue text-lg md:text-xl" | |
| 28 | 28 | ></a | |
| 29 | 29 | > | |
| 30 | 30 | <a | |
| 31 | - | href="/admin/logout" | |
| 31 | + | href="{{ .LogoutURL }}" | |
| 32 | 32 | title="Logout" | |
| 33 | 33 | class="iconf text-gordonmascot-mblue text-lg md:text-xl" | |
| 34 | 34 | ></a | |
| @@ -22,13 +22,13 @@ | |||||
| 22 | </div> | 22 | </div> | ||
| 23 | <div class="w-full flex space-x-4 justify-end"> | 23 | <div class="w-full flex space-x-4 justify-end"> | ||
| 24 | <a | 24 | <a | ||
| 25 | - | href="/admin/settings" | 25 | + | href="{{ .UserSettingsURL }}" |
| 26 | title="User settings" | 26 | title="User settings" | ||
| 27 | class="iconf text-gordonmascot-mblue text-lg md:text-xl" | 27 | class="iconf text-gordonmascot-mblue text-lg md:text-xl" | ||
| 28 | ></a | 28 | ></a | ||
| 29 | > | 29 | > | ||
| 30 | <a | 30 | <a | ||
| 31 | - | href="/admin/logout" | 31 | + | href="{{ .LogoutURL }}" |
| 32 | title="Logout" | 32 | title="Logout" | ||
| 33 | class="iconf text-gordonmascot-mblue text-lg md:text-xl" | 33 | class="iconf text-gordonmascot-mblue text-lg md:text-xl" | ||
| 34 | ></a | 34 | ></a | ||