From f71d7c18605f85dc1ba93a46e883e769c1359582 Mon Sep 17 00:00:00 2001 From: Florian Hoss Date: Mon, 18 Mar 2024 21:54:35 +0100 Subject: [PATCH] Add logto authentication --- components/weather.templ | 2 +- compose.yml | 32 ++++++++++++--- go.mod | 23 +++++++---- go.sum | 52 ++++++++++++++++++------- handlers/auth.handlers.go | 78 +++++++++++++++++++++++++++++++++++++ handlers/routes.go | 15 +++++-- handlers/session.storage.go | 30 ++++++++++++++ internal/env/env.go | 5 +++ main.go | 16 ++++---- 9 files changed, 213 insertions(+), 40 deletions(-) create mode 100644 handlers/auth.handlers.go create mode 100644 handlers/session.storage.go diff --git a/components/weather.templ b/components/weather.templ index fd40fb5..f634f1c 100644 --- a/components/weather.templ +++ b/components/weather.templ @@ -36,7 +36,7 @@ func getIcon(icon string) string { templ Weather(weather *services.OpenWeather) {
- +
{ fmt.Sprintf("%0.2f",weather.Temp) } { weather.Units }
diff --git a/compose.yml b/compose.yml index 2415f06..0801c6b 100644 --- a/compose.yml +++ b/compose.yml @@ -11,6 +11,31 @@ services: - V_ALPINE=${V_ALPINE} - APP_VERSION=${CI_COMMIT_TAG} + logto: + depends_on: + logto-db: + condition: service_healthy + image: svhd/logto:latest + entrypoint: ['sh', '-c', 'npm run cli db seed -- --swe && npm start'] + ports: + - 3001:3001 + - 3002:3002 + environment: + - TRUST_PROXY_HEADER=1 + - DB_URL=postgres://postgres:p0stgr3s@logto-db:5432/logto + + logto-db: + image: postgres:14-alpine + user: postgres + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: p0stgr3s + healthcheck: + test: ['CMD-SHELL', 'pg_isready'] + interval: 10s + timeout: 5s + retries: 5 + backend: build: context: . @@ -31,12 +56,7 @@ services: args: - V_GOLANG=${V_GOLANG} command: air -c .air.toml - environment: - - TZ=Europe/Berlin - - LOG_LEVEL=debug - - TITLE=DEV - - APP_VERSION=v0.0.1-dev - - WEATHER_KEY=${WEATHER_KEY} + env_file: .env volumes: - .:/app/ ports: diff --git a/go.mod b/go.mod index e93b60c..f0dd08c 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,15 @@ module gitlab.unjx.de/flohoss/godash go 1.22.0 require ( - github.com/a-h/templ v0.2.598 + github.com/a-h/templ v0.2.639 github.com/caarlos0/env/v10 v10.0.0 github.com/dariubs/percent v1.0.0 github.com/go-playground/validator/v10 v10.19.0 + github.com/gorilla/sessions v1.2.2 + github.com/labstack/echo-contrib v0.15.0 github.com/labstack/echo/v4 v4.11.4 + github.com/logto-io/go/client v0.1.0 + github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b github.com/r3labs/sse/v2 v2.10.0 github.com/shirou/gopsutil v3.21.11+incompatible gopkg.in/yaml.v3 v3.0.1 @@ -15,23 +19,28 @@ require ( require ( github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/logto-io/go/core v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tklauser/go-sysconf v0.3.13 // indirect + github.com/tklauser/numcpus v0.7.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect + gopkg.in/square/go-jose.v2 v2.6.0 // indirect ) diff --git a/go.sum b/go.sum index f81abfa..9822674 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/a-h/templ v0.2.598 h1:6jMIHv6wQZvdPxTuv87erW4RqN/FPU0wk7ZHN5wVuuo= -github.com/a-h/templ v0.2.598/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8= +github.com/a-h/templ v0.2.639 h1:iNyjh6gllEshVDcj3taqtz7dltPKBtncvP+M8HNGdGQ= +github.com/a-h/templ v0.2.639/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/dariubs/percent v1.0.0 h1:fY8q40FRYaCiFZ0gTOa73Cmp21hS32w+tSSmqbGnUzc= @@ -9,8 +9,9 @@ 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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 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= @@ -23,12 +24,28 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc= +github.com/jarcoal/httpmock v1.2.0/go.mod h1:oCoTsnAz4+UoOUIf5lJOWV2QQIW5UoeUI6aM2YnWAZk= +github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= +github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 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/logto-io/go/client v0.1.0 h1:v9UryStPJMSKWOphQt8sYe8i7aUXhJyVZI2QG/hfI/w= +github.com/logto-io/go/client v0.1.0/go.mod h1:5Lghh/MbCbNd/bAj0Ker23uwFveaopi1JBVyy7Arv+k= +github.com/logto-io/go/core v0.1.0 h1:If6if7tRZ48gNFRIs/HuzTRSEnWTBPRwXh900rKLJJs= +github.com/logto-io/go/core v0.1.0/go.mod h1:xvljMVJl5HkeBRQMmVudalroD4k0bexyK5ERl6i/gtU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -36,6 +53,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc= +github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= @@ -44,10 +63,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -55,19 +74,20 @@ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -77,6 +97,8 @@ gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handlers/auth.handlers.go b/handlers/auth.handlers.go new file mode 100644 index 0000000..eb5e442 --- /dev/null +++ b/handlers/auth.handlers.go @@ -0,0 +1,78 @@ +package handlers + +import ( + "log/slog" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/logto-io/go/client" + "gitlab.unjx.de/flohoss/godash/internal/env" +) + +func NewAuthHandler(env *env.Config) *AuthHandler { + return &AuthHandler{ + env: env, + logtoConfig: &client.LogtoConfig{ + Endpoint: env.LogtoEndpoint, + AppId: env.LogtoAppId, + AppSecret: env.LogtoAppSecret, + }, + } +} + +type AuthHandler struct { + env *env.Config + logtoConfig *client.LogtoConfig +} + +func (authHandler *AuthHandler) logtoMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + logtoClient := client.NewLogtoClient( + authHandler.logtoConfig, + NewSessionStorage(c), + ) + if !logtoClient.IsAuthenticated() { + return c.Redirect(http.StatusTemporaryRedirect, "/sign-in") + } + return next(c) + } +} + +func (authHandler *AuthHandler) signInHandler(c echo.Context) error { + logtoClient := client.NewLogtoClient( + authHandler.logtoConfig, + NewSessionStorage(c), + ) + signInUri, err := logtoClient.SignIn(authHandler.env.PublicUrl + "/sign-in-callback") + if err != nil { + slog.Error(err.Error()) + return echo.ErrInternalServerError + } + return c.Redirect(http.StatusTemporaryRedirect, signInUri) +} + +func (authHandler *AuthHandler) signInCallbackHandler(c echo.Context) error { + logtoClient := client.NewLogtoClient( + authHandler.logtoConfig, + NewSessionStorage(c), + ) + err := logtoClient.HandleSignInCallback(c.Request()) + if err != nil { + slog.Error(err.Error()) + return echo.ErrInternalServerError + } + return c.Redirect(http.StatusTemporaryRedirect, "/") +} + +func (authHandler *AuthHandler) signOutCallbackHandler(c echo.Context) error { + logtoClient := client.NewLogtoClient( + authHandler.logtoConfig, + NewSessionStorage(c), + ) + signOutUri, err := logtoClient.SignOut(authHandler.env.PublicUrl) + if err != nil { + slog.Error(err.Error()) + return echo.ErrInternalServerError + } + return c.Redirect(http.StatusTemporaryRedirect, signOutUri) +} diff --git a/handlers/routes.go b/handlers/routes.go index 1ef6934..ed8b6ef 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -8,9 +8,18 @@ import ( "github.com/r3labs/sse/v2" ) -func SetupRoutes(e *echo.Echo, sse *sse.Server, bh *AppHandler) { - e.GET("/", bh.appHandler) - e.GET("/sse", echo.WrapHandler(http.HandlerFunc(sse.ServeHTTP))) +func SetupRoutes(e *echo.Echo, sse *sse.Server, appHandler *AppHandler, authHandler *AuthHandler) { + e.GET("/sign-in", authHandler.signInHandler) + e.GET("/sign-in-callback", authHandler.signInCallbackHandler) + + secure := e.Group("/", authHandler.logtoMiddleware) + secure.GET("sign-out", authHandler.signOutCallbackHandler) + + secure.GET("", appHandler.appHandler) + secure.GET("sse", echo.WrapHandler(http.HandlerFunc(sse.ServeHTTP))) + + secure.Static("", "assets") + secure.Static("storage/icons", "storage/icons") } func renderView(c echo.Context, cmp templ.Component) error { diff --git a/handlers/session.storage.go b/handlers/session.storage.go new file mode 100644 index 0000000..59fdd7b --- /dev/null +++ b/handlers/session.storage.go @@ -0,0 +1,30 @@ +package handlers + +import ( + "github.com/gorilla/sessions" + "github.com/labstack/echo-contrib/session" + "github.com/labstack/echo/v4" +) + +type SessionStorage struct { + session *sessions.Session + context echo.Context +} + +func NewSessionStorage(c echo.Context) *SessionStorage { + session, _ := session.Get("session", c) + return &SessionStorage{session: session, context: c} +} + +func (storage *SessionStorage) GetItem(key string) string { + value := storage.session.Values[key] + if value == nil { + return "" + } + return value.(string) +} + +func (storage *SessionStorage) SetItem(key, value string) { + storage.session.Values[key] = value + storage.session.Save(storage.context.Request(), storage.context.Response()) +} diff --git a/internal/env/env.go b/internal/env/env.go index 9e34732..3942029 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -9,6 +9,7 @@ import ( type Config struct { TimeZone string `env:"TZ" envDefault:"Etc/UTC" validate:"timezone"` + PublicUrl string `env:"PUBLIC_URL" validate:"url"` Title string `env:"TITLE" envDefault:"goDash"` Port int `env:"PORT" envDefault:"4000" validate:"min=1024,max=49151"` Version string `env:"APP_VERSION"` @@ -18,6 +19,10 @@ type Config struct { WeatherUnits string `env:"WEATHER_UNITS" envDefault:"metric"` WeatherLanguage string `env:"WEATHER_LANG" envDefault:"en"` WeatherDigits bool `env:"WEATHER_DIGITS" envDefault:"true"` + LogtoEndpoint string `env:"LOGTO_ENDPOINT" validate:"url"` + LogtoAppId string `env:"LOGTO_APP_ID,unset"` + LogtoAppSecret string `env:"LOGTO_APP_SECRET,unset"` + SessionKey string `env:"SESSION_KEY,unset"` } var errParse = errors.New("error parsing environment variables") diff --git a/main.go b/main.go index 1a42913..88eb8e3 100644 --- a/main.go +++ b/main.go @@ -7,8 +7,10 @@ import ( "os" "strings" + "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "github.com/quasoft/memstore" "github.com/r3labs/sse/v2" "gitlab.unjx.de/flohoss/godash/handlers" "gitlab.unjx.de/flohoss/godash/internal/env" @@ -25,18 +27,15 @@ func main() { e := echo.New() e.HideBanner = true e.HidePort = true - e.Debug = true e.Pre(middleware.RemoveTrailingSlash()) e.Use(middleware.Recover()) e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ Skipper: func(c echo.Context) bool { - return strings.Contains(c.Path(), "sse") + return strings.Contains(c.Path(), "sse") || strings.Contains(c.Path(), "sign") }, })) - - e.Static("/", "assets") - e.Static("/storage/icons", "storage/icons") + e.Use(session.Middleware(memstore.NewMemStore([]byte(env.SessionKey)))) sse := sse.New() sse.AutoReplay = false @@ -45,10 +44,11 @@ func main() { w := services.NewWeatherService(sse, env) b := services.NewBookmarkService() - ah := handlers.NewAppHandler(env, s, w, b) - handlers.SetupRoutes(e, sse, ah) + appHandler := handlers.NewAppHandler(env, s, w, b) + authHandler := handlers.NewAuthHandler(env) + handlers.SetupRoutes(e, sse, appHandler, authHandler) - slog.Info("starting server", "url", fmt.Sprintf("http://localhost:%d", env.Port)) + slog.Info("starting server", "url", env.PublicUrl) if err := e.Start(fmt.Sprintf(":%d", env.Port)); err != http.ErrServerClosed { slog.Error("cannot start server", "err", err) os.Exit(1)