Use gorilla sessions
This commit is contained in:
parent
0aafb4b9a7
commit
dc2b5e8fb5
10 changed files with 119 additions and 116 deletions
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
|
@ -15,11 +15,9 @@
|
||||||
"PUBLIC_URL": "http://localhost:4000",
|
"PUBLIC_URL": "http://localhost:4000",
|
||||||
"WEATHER_KEY": "3722ce75e9330aaefde1cb3eb1b8b030",
|
"WEATHER_KEY": "3722ce75e9330aaefde1cb3eb1b8b030",
|
||||||
"APP_VERSION": "v0.0.1-DEV",
|
"APP_VERSION": "v0.0.1-DEV",
|
||||||
"OIDC_CLIENT_ID": "home",
|
"AUTH_CLIENT_ID": "home",
|
||||||
"OIDC_CLIENT_SECRET": "PkfS5S7BkiEeqX3Km7BGxsBrmH6MOzjqcpODTz2akxMCMFHv8TAvIfyWgTlKof85",
|
"AUTH_CLIENT_SECRET": "PkfS5S7BkiEeqX3Km7BGxsBrmH6MOzjqcpODTz2akxMCMFHv8TAvIfyWgTlKof85",
|
||||||
"OIDC_REDIRECT_URI": "http://localhost:4000/auth/callback",
|
"AUTH_ISSUER": "https://sso.unjx.de/auth/v1",
|
||||||
"OIDC_ISSUER": "https://sso.unjx.de/auth/v1",
|
|
||||||
"SESSION_KEY": "49cda749cb5eaa6c38f371c530808ca8",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,10 +2,12 @@ package components
|
||||||
|
|
||||||
import "gitlab.unjx.de/flohoss/godash/services"
|
import "gitlab.unjx.de/flohoss/godash/services"
|
||||||
|
|
||||||
templ User(user services.User) {
|
templ User(user *services.User) {
|
||||||
<div class="avatar">
|
<div class="tooltip tooltip-left" data-tip="Logout">
|
||||||
<div class="ring-primary ring-offset-base-100 w-16 rounded-full ring-2 ring-offset-2">
|
<a class="avatar" href="/logout">
|
||||||
|
<div class="ring-primary ring-offset-base-100 w-12 rounded-full ring-2 ring-offset-2">
|
||||||
<img src={ string(templ.SafeURL(user.Gravatar)) }/>
|
<img src={ string(templ.SafeURL(user.Gravatar)) }/>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -4,10 +4,11 @@ go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/a-h/templ v0.2.778
|
github.com/a-h/templ v0.2.778
|
||||||
github.com/alexedwards/scs/v2 v2.8.0
|
|
||||||
github.com/caarlos0/env/v10 v10.0.0
|
github.com/caarlos0/env/v10 v10.0.0
|
||||||
github.com/coreos/go-oidc/v3 v3.11.0
|
github.com/coreos/go-oidc/v3 v3.11.0
|
||||||
github.com/go-playground/validator/v10 v10.22.1
|
github.com/go-playground/validator/v10 v10.22.1
|
||||||
|
github.com/gorilla/securecookie v1.1.2
|
||||||
|
github.com/gorilla/sessions v1.4.0
|
||||||
github.com/r3labs/sse/v2 v2.10.0
|
github.com/r3labs/sse/v2 v2.10.0
|
||||||
github.com/shirou/gopsutil/v4 v4.24.8
|
github.com/shirou/gopsutil/v4 v4.24.8
|
||||||
github.com/thanhpk/randstr v1.0.6
|
github.com/thanhpk/randstr v1.0.6
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -1,7 +1,5 @@
|
||||||
github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
|
github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
|
||||||
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
||||||
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
|
|
||||||
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
|
||||||
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
|
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/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
|
||||||
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
|
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
|
||||||
|
@ -27,6 +25,12 @@ github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27
|
||||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
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/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/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||||
|
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
|
|
@ -3,6 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
"gitlab.unjx.de/flohoss/godash/internal/env"
|
"gitlab.unjx.de/flohoss/godash/internal/env"
|
||||||
"gitlab.unjx.de/flohoss/godash/services"
|
"gitlab.unjx.de/flohoss/godash/services"
|
||||||
"gitlab.unjx.de/flohoss/godash/views/home"
|
"gitlab.unjx.de/flohoss/godash/views/home"
|
||||||
|
@ -21,9 +22,10 @@ type WeatherService interface {
|
||||||
GetCurrentWeather() *services.OpenWeather
|
GetCurrentWeather() *services.OpenWeather
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAppHandler(env *env.Config, s SystemService, w WeatherService, b BookmarkService) *AppHandler {
|
func NewAppHandler(env *env.Config, store *sessions.CookieStore, s SystemService, w WeatherService, b BookmarkService) *AppHandler {
|
||||||
return &AppHandler{
|
return &AppHandler{
|
||||||
env: env,
|
env: env,
|
||||||
|
store: store,
|
||||||
systemService: s,
|
systemService: s,
|
||||||
weatherService: w,
|
weatherService: w,
|
||||||
bookmarkService: b,
|
bookmarkService: b,
|
||||||
|
@ -32,6 +34,7 @@ func NewAppHandler(env *env.Config, s SystemService, w WeatherService, b Bookmar
|
||||||
|
|
||||||
type AppHandler struct {
|
type AppHandler struct {
|
||||||
env *env.Config
|
env *env.Config
|
||||||
|
store *sessions.CookieStore
|
||||||
systemService SystemService
|
systemService SystemService
|
||||||
weatherService WeatherService
|
weatherService WeatherService
|
||||||
bookmarkService BookmarkService
|
bookmarkService BookmarkService
|
||||||
|
@ -45,12 +48,12 @@ func (bh *AppHandler) appHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
titlePage := bh.env.Title
|
titlePage := bh.env.Title
|
||||||
|
|
||||||
user := services.User{
|
session, _ := bh.store.Get(r, StoreSessionKey)
|
||||||
Name: w.Header().Get("X-User-Name"),
|
user := &services.User{
|
||||||
Email: w.Header().Get("X-User-Email"),
|
Name: session.Values[string(NameKey)].(string),
|
||||||
|
Email: session.Values[string(EmailKey)].(string),
|
||||||
|
Gravatar: session.Values[string(GravatarKey)].(string),
|
||||||
}
|
}
|
||||||
gravatar := services.NewGravatarFromEmail(user.Email)
|
|
||||||
user.Gravatar = gravatar.GetURL()
|
|
||||||
|
|
||||||
home.HomeIndex(titlePage, bh.env.Version, home.Home(titlePage, user, bookmarks, staticSystem, liveSystem, weather)).Render(r.Context(), w)
|
home.HomeIndex(titlePage, bh.env.Version, home.Home(titlePage, user, bookmarks, staticSystem, liveSystem, weather)).Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,9 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alexedwards/scs/v2"
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
"github.com/thanhpk/randstr"
|
"github.com/thanhpk/randstr"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
@ -20,16 +19,15 @@ import (
|
||||||
"gitlab.unjx.de/flohoss/godash/services"
|
"gitlab.unjx.de/flohoss/godash/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
|
type contextKey string
|
||||||
c := &http.Cookie{
|
|
||||||
Name: name,
|
const (
|
||||||
Value: value,
|
NameKey contextKey = "name"
|
||||||
MaxAge: int(time.Hour.Seconds()),
|
EmailKey contextKey = "email"
|
||||||
Secure: r.TLS != nil,
|
GravatarKey contextKey = "gravatar"
|
||||||
HttpOnly: true,
|
|
||||||
}
|
StoreSessionKey = "godash_session"
|
||||||
http.SetCookie(w, c)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
func generateCodeVerifier() (string, error) {
|
func generateCodeVerifier() (string, error) {
|
||||||
verifierLength := 64
|
verifierLength := 64
|
||||||
|
@ -49,38 +47,20 @@ func generateCodeChallenge(verifier string) string {
|
||||||
return base64.RawURLEncoding.EncodeToString(sha)
|
return base64.RawURLEncoding.EncodeToString(sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *AuthHandler) saveTokenToSession(r *http.Request, oauth2Token *oauth2.Token) {
|
func NewAuthHandler(env *env.Config, store *sessions.CookieStore) *AuthHandler {
|
||||||
ah.SessionManager.Put(r.Context(), "access_token", oauth2Token.AccessToken)
|
|
||||||
ah.SessionManager.Put(r.Context(), "refresh_token", oauth2Token.RefreshToken)
|
|
||||||
ah.SessionManager.Put(r.Context(), "token_type", oauth2Token.TokenType)
|
|
||||||
ah.SessionManager.Put(r.Context(), "expiry", oauth2Token.Expiry.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *AuthHandler) loadTokenFromSession(r *http.Request) *oauth2.Token {
|
|
||||||
ex := ah.SessionManager.GetInt64(r.Context(), "expiry")
|
|
||||||
return &oauth2.Token{
|
|
||||||
AccessToken: ah.SessionManager.GetString(r.Context(), "access_token"),
|
|
||||||
RefreshToken: ah.SessionManager.GetString(r.Context(), "refresh_token"),
|
|
||||||
TokenType: ah.SessionManager.GetString(r.Context(), "token_type"),
|
|
||||||
Expiry: time.Unix(ex, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAuthHandler(env *env.Config) *AuthHandler {
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
provider, err := oidc.NewProvider(ctx, env.AuthIssuer)
|
||||||
oidcProvider, err := oidc.NewProvider(ctx, env.OIDCIssuer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to get oidc provider", "err", err.Error())
|
slog.Error("Failed to get oidc provider", "err", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
oauth2Config := &oauth2.Config{
|
config := &oauth2.Config{
|
||||||
ClientID: env.OIDCClientID,
|
ClientID: env.AuthClientID,
|
||||||
ClientSecret: env.OIDCClientSecret,
|
ClientSecret: env.AuthClientSecret,
|
||||||
Endpoint: oidcProvider.Endpoint(),
|
RedirectURL: env.PublicUrl + "/callback",
|
||||||
RedirectURL: env.OIDCRedirectURI,
|
Scopes: env.AuthScopes,
|
||||||
Scopes: env.OIDCScopes,
|
Endpoint: provider.Endpoint(),
|
||||||
}
|
}
|
||||||
|
|
||||||
codeVerifier, err := generateCodeVerifier()
|
codeVerifier, err := generateCodeVerifier()
|
||||||
|
@ -90,87 +70,91 @@ func NewAuthHandler(env *env.Config) *AuthHandler {
|
||||||
}
|
}
|
||||||
codeChallenge := generateCodeChallenge(codeVerifier)
|
codeChallenge := generateCodeChallenge(codeVerifier)
|
||||||
authCodeOptions := []oauth2.AuthCodeOption{
|
authCodeOptions := []oauth2.AuthCodeOption{
|
||||||
oauth2.SetAuthURLParam("redirect_uri", env.OIDCRedirectURI),
|
|
||||||
oauth2.SetAuthURLParam("code_challenge", codeChallenge),
|
oauth2.SetAuthURLParam("code_challenge", codeChallenge),
|
||||||
oauth2.SetAuthURLParam("code_challenge_method", "S256"),
|
oauth2.SetAuthURLParam("code_challenge_method", "S256"),
|
||||||
oauth2.SetAuthURLParam("code_verifier", codeVerifier),
|
oauth2.SetAuthURLParam("code_verifier", codeVerifier),
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionManager := scs.New()
|
|
||||||
sessionManager.Lifetime = 24 * 31 * time.Hour
|
|
||||||
|
|
||||||
return &AuthHandler{
|
return &AuthHandler{
|
||||||
ctx: ctx,
|
provider: provider,
|
||||||
oidcProvider: oidcProvider,
|
config: config,
|
||||||
oauth2Config: oauth2Config,
|
|
||||||
authCodeOptions: authCodeOptions,
|
authCodeOptions: authCodeOptions,
|
||||||
SessionManager: sessionManager,
|
store: store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthHandler struct {
|
type AuthHandler struct {
|
||||||
ctx context.Context
|
provider *oidc.Provider
|
||||||
oidcProvider *oidc.Provider
|
config *oauth2.Config
|
||||||
oauth2Config *oauth2.Config
|
|
||||||
authCodeOptions []oauth2.AuthCodeOption
|
authCodeOptions []oauth2.AuthCodeOption
|
||||||
SessionManager *scs.SessionManager
|
store *sessions.CookieStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *AuthHandler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
func (ah *AuthHandler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
state, err := r.Cookie("state")
|
session, _ := ah.store.Get(r, StoreSessionKey)
|
||||||
if err != nil {
|
state, ok := session.Values["state"].(string)
|
||||||
|
if !ok || state == "" {
|
||||||
http.Error(w, "state not found", http.StatusBadRequest)
|
http.Error(w, "state not found", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.URL.Query().Get("state") != state.Value {
|
if r.URL.Query().Get("state") != state {
|
||||||
http.Error(w, "state did not match", http.StatusBadRequest)
|
http.Error(w, "state did not match", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oauth2Token, err := ah.oauth2Config.Exchange(ah.ctx, r.URL.Query().Get("code"), ah.authCodeOptions...)
|
oauth2Token, err := ah.config.Exchange(r.Context(), r.URL.Query().Get("code"), ah.authCodeOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ah.saveTokenToSession(r, oauth2Token)
|
userInfo, err := ah.provider.UserInfo(r.Context(), oauth2.StaticTokenSource(oauth2Token))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &services.User{}
|
||||||
|
userInfo.Claims(user)
|
||||||
|
|
||||||
|
session.Values[string(NameKey)] = user.Name
|
||||||
|
session.Values[string(EmailKey)] = user.Email
|
||||||
|
session.Values[string(GravatarKey)] = services.NewGravatarFromEmail(user.Email).GetURL()
|
||||||
|
err = session.Save(r, w)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *AuthHandler) handleLogout(w http.ResponseWriter, r *http.Request) {
|
func (ah *AuthHandler) handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
ah.SessionManager.Clear(r.Context())
|
session, _ := ah.store.Get(r, StoreSessionKey)
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
session.Values = make(map[interface{}]interface{})
|
||||||
|
err := session.Save(r, w)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
func (ah *AuthHandler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
|
||||||
state := randstr.String(16)
|
|
||||||
setCallbackCookie(w, r, "state", state)
|
|
||||||
http.Redirect(w, r, ah.oauth2Config.AuthCodeURL(state, ah.authCodeOptions...), http.StatusFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *AuthHandler) AuthMiddleware(next http.Handler) http.Handler {
|
func (ah *AuthHandler) AuthMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
exists := ah.SessionManager.Exists(r.Context(), "access_token")
|
session, _ := ah.store.Get(r, StoreSessionKey)
|
||||||
if !exists {
|
name, ok := session.Values[string(NameKey)].(string)
|
||||||
ah.handleLogin(w, r)
|
if !ok || name == "" {
|
||||||
return
|
state := randstr.String(16)
|
||||||
}
|
session.Values["state"] = state
|
||||||
|
err := session.Save(r, w)
|
||||||
token := ah.loadTokenFromSession(r)
|
|
||||||
ah.oauth2Config.Client(ah.ctx, token)
|
|
||||||
|
|
||||||
tokenInfo, err := ah.oidcProvider.Verifier(&oidc.Config{ClientID: ah.oauth2Config.ClientID}).Verify(ah.ctx, token.AccessToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ah.handleLogin(w, r)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, ah.config.AuthCodeURL(state, ah.authCodeOptions...), http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ah.saveTokenToSession(r, token)
|
|
||||||
|
|
||||||
var userClaims services.User
|
|
||||||
tokenInfo.Claims(&userClaims)
|
|
||||||
w.Header().Set("X-User-Name", userClaims.Name)
|
|
||||||
w.Header().Set("X-User-Email", userClaims.Email)
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,13 +10,13 @@ func SetupRoutes(router *http.ServeMux, sse *sse.Server, appHandler *AppHandler,
|
||||||
router.Handle("GET /sse", authHandler.AuthMiddleware(http.HandlerFunc(sse.ServeHTTP)))
|
router.Handle("GET /sse", authHandler.AuthMiddleware(http.HandlerFunc(sse.ServeHTTP)))
|
||||||
|
|
||||||
fsAssets := http.FileServer(http.Dir("assets"))
|
fsAssets := http.FileServer(http.Dir("assets"))
|
||||||
router.Handle("GET /assets/", authHandler.AuthMiddleware((http.StripPrefix("/assets/", fsAssets))))
|
router.Handle("GET /assets/", authHandler.AuthMiddleware(http.StripPrefix("/assets/", fsAssets)))
|
||||||
|
|
||||||
icons := http.FileServer(http.Dir("storage/icons"))
|
icons := http.FileServer(http.Dir("storage/icons"))
|
||||||
router.Handle("GET /icons/", authHandler.AuthMiddleware(http.StripPrefix("/icons/", icons)))
|
router.Handle("GET /icons/", authHandler.AuthMiddleware(http.StripPrefix("/icons/", icons)))
|
||||||
|
|
||||||
router.HandleFunc("GET /auth/logout", http.HandlerFunc(authHandler.handleLogout))
|
router.HandleFunc("GET /logout", authHandler.handleLogout)
|
||||||
router.HandleFunc("GET /auth/callback", authHandler.handleCallback)
|
router.HandleFunc("GET /callback", authHandler.handleCallback)
|
||||||
|
|
||||||
router.Handle("GET /", authHandler.AuthMiddleware(http.HandlerFunc(appHandler.appHandler)))
|
router.Handle("GET /", authHandler.AuthMiddleware(http.HandlerFunc(appHandler.appHandler)))
|
||||||
}
|
}
|
||||||
|
|
12
internal/env/env.go
vendored
12
internal/env/env.go
vendored
|
@ -10,6 +10,7 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
TimeZone string `env:"TZ" envDefault:"Etc/UTC" validate:"timezone"`
|
TimeZone string `env:"TZ" envDefault:"Etc/UTC" validate:"timezone"`
|
||||||
Title string `env:"TITLE" envDefault:"goDash"`
|
Title string `env:"TITLE" envDefault:"goDash"`
|
||||||
|
PublicUrl string `env:"PUBLIC_URL" validate:"required,url"`
|
||||||
Port int `env:"PORT" envDefault:"4000" validate:"min=1024,max=49151"`
|
Port int `env:"PORT" envDefault:"4000" validate:"min=1024,max=49151"`
|
||||||
Version string `env:"APP_VERSION"`
|
Version string `env:"APP_VERSION"`
|
||||||
LocationLatitude float32 `env:"LOCATION_LATITUDE" envDefault:"48.780331609463815" validate:"latitude"`
|
LocationLatitude float32 `env:"LOCATION_LATITUDE" envDefault:"48.780331609463815" validate:"latitude"`
|
||||||
|
@ -18,13 +19,10 @@ type Config struct {
|
||||||
WeatherUnits string `env:"WEATHER_UNITS" envDefault:"metric"`
|
WeatherUnits string `env:"WEATHER_UNITS" envDefault:"metric"`
|
||||||
WeatherLanguage string `env:"WEATHER_LANG" envDefault:"en" validate:"bcp47_language_tag"`
|
WeatherLanguage string `env:"WEATHER_LANG" envDefault:"en" validate:"bcp47_language_tag"`
|
||||||
WeatherDigits bool `env:"WEATHER_DIGITS" envDefault:"false"`
|
WeatherDigits bool `env:"WEATHER_DIGITS" envDefault:"false"`
|
||||||
OIDCClientID string `env:"OIDC_CLIENT_ID"`
|
AuthClientID string `env:"AUTH_CLIENT_ID"`
|
||||||
OIDCClientSecret string `env:"OIDC_CLIENT_SECRET"`
|
AuthClientSecret string `env:"AUTH_CLIENT_SECRET"`
|
||||||
OIDCRedirectURI string `env:"OIDC_REDIRECT_URI"`
|
AuthScopes []string `env:"AUTH_SCOPES" envSeparator:"," envDefault:"openid,email,profile"`
|
||||||
OIDCScopes []string `env:"OIDC_SCOPES" envSeparator:"," envDefault:"openid,email,profile"`
|
AuthIssuer string `env:"AUTH_ISSUER"`
|
||||||
OIDCIssuer string `env:"OIDC_ISSUER"`
|
|
||||||
OIDCResponseMode string `env:"OIDC_RESPONSE_MODE"`
|
|
||||||
SessionKey string `env:"SESSION_KEY,unset"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var errParse = errors.New("error parsing environment variables")
|
var errParse = errors.New("error parsing environment variables")
|
||||||
|
|
23
main.go
23
main.go
|
@ -5,8 +5,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
"github.com/r3labs/sse/v2"
|
"github.com/r3labs/sse/v2"
|
||||||
|
|
||||||
"gitlab.unjx.de/flohoss/godash/handlers"
|
"gitlab.unjx.de/flohoss/godash/handlers"
|
||||||
|
@ -29,13 +32,23 @@ func main() {
|
||||||
w := services.NewWeatherService(sse, env)
|
w := services.NewWeatherService(sse, env)
|
||||||
b := services.NewBookmarkService()
|
b := services.NewBookmarkService()
|
||||||
|
|
||||||
appHandler := handlers.NewAppHandler(env, s, w, b)
|
parsedUrl, _ := url.Parse(env.PublicUrl)
|
||||||
authHandler := handlers.NewAuthHandler(env)
|
store := sessions.NewCookieStore(securecookie.GenerateRandomKey(32))
|
||||||
|
store.Options = &sessions.Options{
|
||||||
|
Domain: parsedUrl.Hostname(),
|
||||||
|
MaxAge: 86400 * 30,
|
||||||
|
Secure: parsedUrl.Scheme == "https",
|
||||||
|
HttpOnly: true,
|
||||||
|
Partitioned: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
authHandler := handlers.NewAuthHandler(env, store)
|
||||||
|
appHandler := handlers.NewAppHandler(env, store, s, w, b)
|
||||||
handlers.SetupRoutes(router, sse, appHandler, authHandler)
|
handlers.SetupRoutes(router, sse, appHandler, authHandler)
|
||||||
|
|
||||||
lis := fmt.Sprintf(":%d", env.Port)
|
slog.Info("server listening, press ctrl+c to stop", "addr", env.PublicUrl)
|
||||||
slog.Info("server listening, press ctrl+c to stop", "addr", "http://localhost"+lis)
|
err = http.ListenAndServe(fmt.Sprintf(":%d", env.Port), router)
|
||||||
err = http.ListenAndServe(lis, authHandler.SessionManager.LoadAndSave(router))
|
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
slog.Error("server terminated", "error", err)
|
slog.Error("server terminated", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"gitlab.unjx.de/flohoss/godash/views/layout"
|
"gitlab.unjx.de/flohoss/godash/views/layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ Home(title string, user services.User, bookmarks *services.Bookmarks, static *services.StaticInformation, live *services.LiveInformation, weather *services.OpenWeather) {
|
templ Home(title string, user *services.User, bookmarks *services.Bookmarks, static *services.StaticInformation, live *services.LiveInformation, weather *services.OpenWeather) {
|
||||||
<section class="grid gap-10">
|
<section class="grid gap-10">
|
||||||
<div class="flex w-full justify-between items-center">
|
<div class="flex w-full justify-between items-center">
|
||||||
@components.Weather(weather)
|
@components.Weather(weather)
|
||||||
|
|
Loading…
Reference in a new issue