mirror of
https://github.com/knadh/listmonk.git
synced 2024-11-15 03:55:06 +08:00
Refactor the oidc
package and separate out handlers.
This commit is contained in:
parent
8ca95f6827
commit
f8b3ddb5ee
1 changed files with 75 additions and 73 deletions
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
@ -25,100 +24,103 @@ type Config struct {
|
||||||
Skipper middleware.Skipper
|
Skipper middleware.Skipper
|
||||||
}
|
}
|
||||||
|
|
||||||
func OIDCAuth(config Config) echo.MiddlewareFunc {
|
type OIDC struct {
|
||||||
provider, err := oidc.NewProvider(context.Background(), config.ProviderURL)
|
cfg oauth2.Config
|
||||||
|
verifier *oidc.IDTokenVerifier
|
||||||
|
skipper middleware.Skipper
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg Config) *OIDC {
|
||||||
|
provider, err := oidc.NewProvider(context.Background(), cfg.ProviderURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
verifier := provider.Verifier(&oidc.Config{
|
verifier := provider.Verifier(&oidc.Config{
|
||||||
ClientID: config.ClientID,
|
ClientID: cfg.ClientID,
|
||||||
})
|
})
|
||||||
|
|
||||||
oidcConfig := oauth2.Config{
|
oidcConfig := oauth2.Config{
|
||||||
ClientID: config.ClientID,
|
ClientID: cfg.ClientID,
|
||||||
ClientSecret: config.ClientSecret,
|
ClientSecret: cfg.ClientSecret,
|
||||||
Endpoint: provider.Endpoint(),
|
Endpoint: provider.Endpoint(),
|
||||||
RedirectURL: config.RedirectURL,
|
RedirectURL: cfg.RedirectURL,
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
}
|
}
|
||||||
|
|
||||||
pathURL, err := url.Parse(config.RedirectURL)
|
return &OIDC{
|
||||||
|
verifier: verifier,
|
||||||
|
cfg: oidcConfig,
|
||||||
|
skipper: cfg.Skipper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleCallback is the HTTP handler that handles the post-OIDC provider redirect callback.
|
||||||
|
func (o *OIDC) HandleCallback(c echo.Context) error {
|
||||||
|
tk, err := o.cfg.Exchange(c.Request().Context(), c.Request().URL.Query().Get("code"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("error exchanging token: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Skipper == nil {
|
rawIDTk, ok := tk.Extra("id_token").(string)
|
||||||
config.Skipper = middleware.DefaultSkipper
|
if !ok {
|
||||||
|
return echo.NewHTTPError(http.StatusUnauthorized, "`id_token` missing.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
idTk, err := o.verifier.Verify(c.Request().Context(), rawIDTk)
|
||||||
return func(c echo.Context) error {
|
if err != nil {
|
||||||
if config.Skipper(c) {
|
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("error verifying ID token: %v", err))
|
||||||
return next(c)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if c.Request().URL.Path == pathURL.Path {
|
nonce, err := c.Cookie("nonce")
|
||||||
oauth2Token, err := oidcConfig.Exchange(c.Request().Context(), c.Request().URL.Query().Get("code"))
|
if err != nil {
|
||||||
if err != nil {
|
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("nonce cookie not found: %v", err))
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to exchange token: %v", err))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
if idTk.Nonce != nonce.Value {
|
||||||
if !ok {
|
return echo.NewHTTPError(http.StatusUnauthorized, "nonce did not match")
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "No id_token field in oauth2 token")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
idToken, err := verifier.Verify(c.Request().Context(), rawIDToken)
|
c.SetCookie(&http.Cookie{
|
||||||
if err != nil {
|
Name: "id_token",
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to verify ID Token: %v", err))
|
Value: rawIDTk,
|
||||||
}
|
Secure: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
Path: "/",
|
||||||
|
})
|
||||||
|
|
||||||
nonce, err := c.Cookie("nonce")
|
return c.Redirect(302, c.Request().URL.Query().Get("state"))
|
||||||
if err != nil {
|
}
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("nonce cookie not found: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if idToken.Nonce != nonce.Value {
|
func (o *OIDC) Middleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "nonce did not match")
|
return func(c echo.Context) error {
|
||||||
}
|
if o.skipper != nil && o.skipper(c) {
|
||||||
|
return next(c)
|
||||||
c.SetCookie(&http.Cookie{
|
|
||||||
Name: "id_token",
|
|
||||||
Value: rawIDToken,
|
|
||||||
Secure: true,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
Path: "/",
|
|
||||||
})
|
|
||||||
|
|
||||||
// Login success - redirect back to the intended page
|
|
||||||
return c.Redirect(302, c.Request().URL.Query().Get("state"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if request is authenticated
|
|
||||||
rawIDToken, err := c.Cookie("id_token")
|
|
||||||
if err == nil { // cookie found
|
|
||||||
_, err = verifier.Verify(c.Request().Context(), rawIDToken.Value)
|
|
||||||
if err == nil {
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
} else if err != http.ErrNoCookie {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to login
|
|
||||||
nonce, err := randString(16)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
c.SetCookie(&http.Cookie{
|
|
||||||
Name: "nonce",
|
|
||||||
Value: nonce,
|
|
||||||
Secure: true,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
Path: "/",
|
|
||||||
})
|
|
||||||
return c.Redirect(302, oidcConfig.AuthCodeURL(c.Request().URL.RequestURI(), oidc.Nonce(nonce)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rawIDTk, err := c.Cookie("id_token")
|
||||||
|
if err != http.ErrNoCookie {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the token.
|
||||||
|
_, err = o.verifier.Verify(c.Request().Context(), rawIDTk.Value)
|
||||||
|
if err == nil {
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the verification failed, redirect to the provider for auth.
|
||||||
|
nonce, err := randString(16)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
c.SetCookie(&http.Cookie{
|
||||||
|
Name: "nonce",
|
||||||
|
Value: nonce,
|
||||||
|
Secure: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
Path: "/",
|
||||||
|
})
|
||||||
|
return c.Redirect(302, o.cfg.AuthCodeURL(c.Request().URL.RequestURI(), oidc.Nonce(nonce)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue