mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 15:26:40 +00:00
refactor: move grpc and http APIs to separate packages
This commit is contained in:
96
management/server/http/handler/callback.go
Normal file
96
management/server/http/handler/callback.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/gorilla/sessions"
|
||||
middleware2 "github.com/wiretrustee/wiretrustee/management/server/http/middleware"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Callback handler used to receive a callback from the identity provider
|
||||
type Callback struct {
|
||||
authenticator *middleware2.Authenticator
|
||||
sessionStore sessions.Store
|
||||
}
|
||||
|
||||
func NewCallback(authenticator *middleware2.Authenticator, sessionStore sessions.Store) *Callback {
|
||||
return &Callback{
|
||||
authenticator: authenticator,
|
||||
sessionStore: sessionStore,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP checks the user session, verifies the state, verifies the token, stores user profile in a session,
|
||||
// and in case of the successful auth redirects user to the main page
|
||||
func (h *Callback) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := h.sessionStore.Get(r, "auth-session")
|
||||
if err != nil {
|
||||
//todo redirect to the error page stating: "error occurred plz try again later and a link to login"
|
||||
//http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Query().Get("state") != session.Values["state"] {
|
||||
//todo redirect to the error page stating: "error authenticating plz try to login once again"
|
||||
//http.Error(w, "invalid state parameter", http.StatusBadRequest)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.authenticator.Config.Exchange(context.TODO(), r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
log.Printf("no token found: %v", err)
|
||||
//todo redirect to the error page stating: "error authenticating plz try to login once again"
|
||||
//w.WriteHeader(http.StatusUnauthorized)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
rawIDToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
//todo redirect to the error page stating: "error occurred plz try again later and a link to login"
|
||||
//http.Error(w, "no id_token field in oauth2 token.", http.StatusInternalServerError)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
oidcConfig := &oidc.Config{
|
||||
ClientID: h.authenticator.Config.ClientID,
|
||||
}
|
||||
|
||||
idToken, err := h.authenticator.Provider.Verifier(oidcConfig).Verify(context.TODO(), rawIDToken)
|
||||
|
||||
if err != nil {
|
||||
//todo redirect to the error page stating: "error occurred plz try again later and a link to login"
|
||||
//http.Error(w, "failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// get the userInfo from the token
|
||||
var profile map[string]interface{}
|
||||
if err := idToken.Claims(&profile); err != nil {
|
||||
//todo redirect to the error page stating: "error occurred plz try again later and a link to login"
|
||||
//http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
session.Values["id_token"] = rawIDToken
|
||||
session.Values["access_token"] = token.AccessToken
|
||||
session.Values["profile"] = profile
|
||||
|
||||
err = session.Save(r, w)
|
||||
if err != nil {
|
||||
//todo redirect to the error page stating: "error occurred plz try again later and a link to login"
|
||||
//http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// redirect to logged in page
|
||||
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
|
||||
}
|
||||
44
management/server/http/handler/dashboard.go
Normal file
44
management/server/http/handler/dashboard.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/sessions"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dashboard is a handler of the main page of the app (dashboard)
|
||||
type Dashboard struct {
|
||||
sessionStore sessions.Store
|
||||
}
|
||||
|
||||
func NewDashboard(sessionStore sessions.Store) *Dashboard {
|
||||
return &Dashboard{
|
||||
sessionStore: sessionStore,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP verifies if user is authenticated and returns a user dashboard
|
||||
func (h *Dashboard) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
session, err := h.sessionStore.Get(r, "auth-session")
|
||||
if err != nil {
|
||||
//todo redirect to the error page stating: "error occurred plz try again later and a link to login"
|
||||
//http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
//todo get user account and relevant data to show
|
||||
profile := session.Values["profile"].(map[string]interface{})
|
||||
name := profile["name"]
|
||||
w.WriteHeader(200)
|
||||
_, err = io.Copy(w, strings.NewReader("hello "+fmt.Sprintf("%v", name)))
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
//template.RenderTemplate(w, "dashboard", session.Values["profile"])
|
||||
}
|
||||
58
management/server/http/handler/login.go
Normal file
58
management/server/http/handler/login.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"github.com/gorilla/sessions"
|
||||
middleware2 "github.com/wiretrustee/wiretrustee/management/server/http/middleware"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Login handler used to login a user
|
||||
type Login struct {
|
||||
authenticator *middleware2.Authenticator
|
||||
sessionStore sessions.Store
|
||||
}
|
||||
|
||||
func NewLogin(authenticator *middleware2.Authenticator, sessionStore sessions.Store) *Login {
|
||||
return &Login{
|
||||
authenticator: authenticator,
|
||||
sessionStore: sessionStore,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP generates a new session state for a user and redirects the user to the auth URL
|
||||
func (h *Login) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Generate random state
|
||||
b := make([]byte, 32)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
//todo redirect to the error page stating: "error occurred plz try again later and a link to login"
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
state := base64.StdEncoding.EncodeToString(b)
|
||||
|
||||
session, err := h.sessionStore.Get(r, "auth-session")
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *fs.PathError:
|
||||
// a case when session doesn't exist in the store but was sent by the client in the cookie -> create new session ID
|
||||
// it appears that in this case session is always non empty object
|
||||
session.ID = "" //nolint
|
||||
default:
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
session.Values["state"] = state //nolint
|
||||
err = session.Save(r, w) //nolint
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
url := h.authenticator.Config.AuthCodeURL(state)
|
||||
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||
}
|
||||
48
management/server/http/handler/logout.go
Normal file
48
management/server/http/handler/logout.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Logout logs out a user
|
||||
type Logout struct {
|
||||
authDomain string
|
||||
authClientId string
|
||||
}
|
||||
|
||||
func NewLogout(authDomain string, authClientId string) *Logout {
|
||||
return &Logout{authDomain: authDomain, authClientId: authClientId}
|
||||
}
|
||||
|
||||
// ServeHTTP redirects user to teh auth identity provider logout URL
|
||||
func (h *Logout) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
logoutUrl, err := url.Parse("https://" + h.authDomain)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
logoutUrl.Path += "/v2/logout"
|
||||
parameters := url.Values{}
|
||||
|
||||
var scheme string
|
||||
if r.TLS == nil {
|
||||
scheme = "http"
|
||||
} else {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
returnTo, err := url.Parse(scheme + "://" + r.Host + "/login")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
parameters.Add("returnTo", returnTo.String())
|
||||
parameters.Add("client_id", h.authClientId)
|
||||
logoutUrl.RawQuery = parameters.Encode()
|
||||
|
||||
http.Redirect(w, r, logoutUrl.String(), http.StatusTemporaryRedirect)
|
||||
}
|
||||
39
management/server/http/middleware/auth.go
Normal file
39
management/server/http/middleware/auth.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"golang.org/x/oauth2"
|
||||
"log"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
)
|
||||
|
||||
type Authenticator struct {
|
||||
Provider *oidc.Provider
|
||||
Config oauth2.Config
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
func NewAuthenticator(authDomain string, authClientId string, authClientSecret string, authCallback string) (*Authenticator, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, "https://"+authDomain+"/")
|
||||
if err != nil {
|
||||
log.Printf("failed to get provider: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := oauth2.Config{
|
||||
ClientID: authClientId,
|
||||
ClientSecret: authClientSecret,
|
||||
RedirectURL: authCallback,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile"},
|
||||
}
|
||||
|
||||
return &Authenticator{
|
||||
Provider: provider,
|
||||
Config: conf,
|
||||
Ctx: ctx,
|
||||
}, nil
|
||||
}
|
||||
31
management/server/http/middleware/authenticated.go
Normal file
31
management/server/http/middleware/authenticated.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gorilla/sessions"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type AuthMiddleware struct {
|
||||
sessionStore sessions.Store
|
||||
}
|
||||
|
||||
func NewAuth(sessionStore sessions.Store) *AuthMiddleware {
|
||||
return &AuthMiddleware{sessionStore: sessionStore}
|
||||
}
|
||||
|
||||
func (am *AuthMiddleware) IsAuthenticated(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
|
||||
session, err := am.sessionStore.Get(r, "auth-session")
|
||||
if err != nil {
|
||||
//todo redirect to the error page stating: "error occurred plz try again later and a link to login"
|
||||
//http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := session.Values["profile"]; !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
} else {
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
91
management/server/http/server.go
Normal file
91
management/server/http/server.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
log "github.com/sirupsen/logrus"
|
||||
s "github.com/wiretrustee/wiretrustee/management/server"
|
||||
handler2 "github.com/wiretrustee/wiretrustee/management/server/http/handler"
|
||||
middleware2 "github.com/wiretrustee/wiretrustee/management/server/http/middleware"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
server *http.Server
|
||||
config *s.HttpServerConfig
|
||||
certManager *autocert.Manager
|
||||
}
|
||||
|
||||
// NewHttpsServer creates a new HTTPs server (with HTTPS support)
|
||||
// The listening address will be :443 no matter what was specified in s.HttpServerConfig.Address
|
||||
func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager) *Server {
|
||||
server := &http.Server{
|
||||
Addr: config.Address,
|
||||
WriteTimeout: time.Second * 15,
|
||||
ReadTimeout: time.Second * 15,
|
||||
IdleTimeout: time.Second * 60,
|
||||
}
|
||||
return &Server{server: server, config: config, certManager: certManager}
|
||||
}
|
||||
|
||||
// NewHttpServer creates a new HTTP server (without HTTPS)
|
||||
func NewHttpServer(config *s.HttpServerConfig) *Server {
|
||||
return NewHttpsServer(config, nil)
|
||||
}
|
||||
|
||||
// Stop stops the http server
|
||||
func (s *Server) Stop(ctx context.Context) error {
|
||||
err := s.server.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start defines http handlers and starts the http server. Blocks until server is shutdown.
|
||||
func (s *Server) Start() error {
|
||||
|
||||
sessionStore := sessions.NewFilesystemStore("", []byte("something-very-secret"))
|
||||
authenticator, err := middleware2.NewAuthenticator(s.config.AuthDomain, s.config.AuthClientId, s.config.AuthClientSecret, s.config.AuthCallback)
|
||||
if err != nil {
|
||||
log.Errorf("failed cerating authentication middleware %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
gob.Register(map[string]interface{}{})
|
||||
|
||||
r := http.NewServeMux()
|
||||
s.server.Handler = r
|
||||
|
||||
r.Handle("/login", handler2.NewLogin(authenticator, sessionStore))
|
||||
r.Handle("/logout", handler2.NewLogout(s.config.AuthDomain, s.config.AuthClientId))
|
||||
r.Handle("/callback", handler2.NewCallback(authenticator, sessionStore))
|
||||
r.Handle("/dashboard", negroni.New(
|
||||
negroni.HandlerFunc(middleware2.NewAuth(sessionStore).IsAuthenticated),
|
||||
negroni.Wrap(handler2.NewDashboard(sessionStore))),
|
||||
)
|
||||
http.Handle("/", r)
|
||||
|
||||
if s.certManager != nil {
|
||||
// if HTTPS is enabled we reuse the listener from the cert manager
|
||||
listener := s.certManager.Listener()
|
||||
log.Infof("http server listening on %s", listener.Addr())
|
||||
if err = http.Serve(listener, s.certManager.HTTPHandler(r)); err != nil {
|
||||
log.Errorf("failed to serve https server: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Infof("http server listening on %s", s.server.Addr)
|
||||
if err = s.server.ListenAndServe(); err != nil {
|
||||
log.Errorf("failed to serve http server: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
21
management/server/http/template/templates.go
Normal file
21
management/server/http/template/templates.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func RenderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
|
||||
cwd, _ := os.Getwd()
|
||||
t, err := template.ParseFiles(filepath.Join(cwd, "./routes/"+tmpl+"/"+tmpl+".html"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = t.Execute(w, data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user