changes
This commit is contained in:
363
main.go
363
main.go
@@ -1,8 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -10,6 +17,9 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func getenv(k, d string) string {
|
||||
@@ -34,6 +44,85 @@ func cacheControl(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
/* ################################################################## */
|
||||
/* START DER STRUKTUREN */
|
||||
/* ################################################################## */
|
||||
|
||||
type RuleSet struct {
|
||||
Id int
|
||||
Name string
|
||||
Default_contact_read int
|
||||
Default_contact_write int
|
||||
Default_contact_delete int
|
||||
Default_keyword_read int
|
||||
Default_keyword_write int
|
||||
Default_keyword_delete int
|
||||
Default_keyword_attach int
|
||||
Default_keyword_detach int
|
||||
Default_aduser_read int
|
||||
Default_aduser_write int
|
||||
Default_aduser_delete int
|
||||
Default_location_read int
|
||||
Default_location_write int
|
||||
Default_location_delete int
|
||||
Default_department_read int
|
||||
Default_department_write int
|
||||
Default_department_delete int
|
||||
Self_contact_read int
|
||||
Self_contact_write int
|
||||
Self_keyword_attach int
|
||||
Self_keyword_detach int
|
||||
Private_contact_read int
|
||||
Private_contact_write int
|
||||
Private_keyword_add int
|
||||
Private_keyword_delete int
|
||||
Private_keyword_attach int
|
||||
Private_keyword_detach int
|
||||
}
|
||||
|
||||
type ADUser struct {
|
||||
Id int
|
||||
SamAccountName string
|
||||
Sid string
|
||||
RuleSetId RuleSet
|
||||
}
|
||||
|
||||
type Department struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
Id int
|
||||
Name string
|
||||
Address string
|
||||
Zip string
|
||||
City string
|
||||
}
|
||||
|
||||
type Contact struct {
|
||||
Id int
|
||||
OwnerId int
|
||||
AdUserId ADUser
|
||||
DisplayName string
|
||||
Phone string
|
||||
Mobile string
|
||||
Homeoffice string
|
||||
Email string
|
||||
Room string
|
||||
DepartmentId Department
|
||||
LocationId Location
|
||||
}
|
||||
|
||||
type ContactKeywordLink struct {
|
||||
Contact int
|
||||
Keyword int
|
||||
}
|
||||
|
||||
/* ################################################################## */
|
||||
/* ENDE DER STRUKTUREN */
|
||||
/* ################################################################## */
|
||||
|
||||
func main() {
|
||||
|
||||
// Signal-Kanal einrichten
|
||||
@@ -51,16 +140,36 @@ func main() {
|
||||
// --- Verzeichnisse konfigurierbar machen -------------------------
|
||||
staticDir := getenv("BLOG_STATIC_DIR", "./static")
|
||||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||||
/*storeEnabled := enabled("STORE_ENABLE", false)*/
|
||||
|
||||
/*TickCatalog = nil
|
||||
if err := LoadTickCatalog(ticksDir + "/ticks.json"); err != nil {
|
||||
fmt.Println("Fehler beim Laden:", err)
|
||||
cfg := Config{
|
||||
DSN: "hikos:hikos@tcp(10.10.5.31:3306)/hikos?parseTime=true",
|
||||
LDAPURL: "ldaps://ldaps.example.com:636",
|
||||
LDAPBindPattern: "uid=%s,ou=users,dc=example,dc=com",
|
||||
SessionTTL: 24 * time.Hour,
|
||||
}
|
||||
|
||||
fmt.Println("Geladener Katalog:", TickCatalog)
|
||||
db, err := sql.Open("mysql", cfg.DSN)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := db.Ping(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cloneRepo(gitRepo, gitBranch, gitDir)*/
|
||||
store := &SessionStore{DB: db}
|
||||
auth := &LDAPAuthenticator{
|
||||
URL: cfg.LDAPURL,
|
||||
BindPattern: cfg.LDAPBindPattern,
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
}
|
||||
|
||||
srv := &Server{
|
||||
cfg: cfg,
|
||||
sessions: store,
|
||||
auth: auth,
|
||||
}
|
||||
|
||||
funcs := template.FuncMap{
|
||||
"now": time.Now, // jetzt‑Zeit bereitstellen
|
||||
@@ -68,28 +177,48 @@ func main() {
|
||||
|
||||
// Basislayout zuerst parsen
|
||||
layout := template.Must(template.New("base").Funcs(funcs).ParseFiles(templatesDir + "/base.html"))
|
||||
tplFull := template.Must(layout.Clone())
|
||||
template.Must(tplFull.Funcs(funcs).ParseFiles(templatesDir+"/kontaktliste.html", templatesDir+"/schlagwortliste.html"))
|
||||
|
||||
// LIST‑Seite: base + list.html
|
||||
/*tplList = template.Must(layout.Clone())
|
||||
template.Must(tplList.Funcs(funcs).ParseFiles(templatesDir + "/list.html"))
|
||||
tplArticle = template.Must(layout.Clone())
|
||||
template.Must(tplArticle.Funcs(funcs).ParseFiles(templatesDir + "/article.html"))
|
||||
tplPage := template.Must(layout.Clone())
|
||||
template.Must(tplPage.ParseFiles(templatesDir + "/page.html"))*/
|
||||
tplKontakt := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||||
tplSchlagwort := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/schlagwortliste.html"))
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/login", srv.loginHandler)
|
||||
mux.Handle("/protected", srv.withAuth(http.HandlerFunc(srv.protectedHandler)))
|
||||
|
||||
// Handler für /
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
layout.ExecuteTemplate(w, "layout", nil)
|
||||
tplFull.ExecuteTemplate(w, "layout", nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/post/", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
mux.HandleFunc("/htmx/kontakt", func(w http.ResponseWriter, r *http.Request) {
|
||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/page/", func(w http.ResponseWriter, r *http.Request) {
|
||||
mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) {
|
||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/htmx/amt", func(w http.ResponseWriter, r *http.Request) {
|
||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/htmx/raum", func(w http.ResponseWriter, r *http.Request) {
|
||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/htmx/gebaeude", func(w http.ResponseWriter, r *http.Request) {
|
||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/htmx/schlagwort", func(w http.ResponseWriter, r *http.Request) {
|
||||
tplSchlagwort.ExecuteTemplate(w, "schlagwort", nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/htmx/schlagwortbykontakt", func(w http.ResponseWriter, r *http.Request) {
|
||||
tplSchlagwort.ExecuteTemplate(w, "schlagwort", nil)
|
||||
})
|
||||
|
||||
mux.Handle("/static/", cacheControl(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))))
|
||||
@@ -98,7 +227,23 @@ func main() {
|
||||
|
||||
})
|
||||
|
||||
StopServer(http.ListenAndServe(":8080", mux))
|
||||
server := &http.Server{
|
||||
/*Addr: ":8443",*/
|
||||
Addr: ":8080",
|
||||
Handler: mux,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
log.Println("Listening on https://localhost:8080 …")
|
||||
/*if err := server.ListenAndServeTLS("cert.pem", "key.pem"); err != nil {
|
||||
StopServer(err)
|
||||
}*/
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
StopServer(err)
|
||||
}
|
||||
//StopServer(http.ListenAndServe(":8080", mux))
|
||||
|
||||
}
|
||||
|
||||
@@ -116,3 +261,185 @@ func StopServer(e error) {
|
||||
prepareExit()
|
||||
fmt.Println("~", "Server stopped!")
|
||||
}
|
||||
|
||||
/* ################################################################## */
|
||||
/* START LDAP UND GESICHERTE SEITEN */
|
||||
/* ################################################################## */
|
||||
|
||||
// Config bundles all runtime‑adjustable settings.
|
||||
type Config struct {
|
||||
DSN string // MySQL DSN → "user:pass@tcp(127.0.0.1:3306)/app?parseTime=true"
|
||||
LDAPURL string // full URL → "ldaps://ldap.example.com:636"
|
||||
LDAPBindPattern string // DN pattern → "uid=%s,ou=users,dc=example,dc=com"
|
||||
SessionTTL time.Duration // session validity
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
const userKey contextKey = "username"
|
||||
|
||||
// ---- SESSION STORE ----
|
||||
|
||||
type SessionStore struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
// generateRandomToken returns a URL‑safe base64 random string.
|
||||
func generateRandomToken(n int) (string, error) {
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func (s *SessionStore) Create(username string, ttl time.Duration) (string, error) {
|
||||
token, err := generateRandomToken(32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
expires := time.Now().Add(ttl)
|
||||
_, err = s.DB.Exec(`INSERT INTO sessions (username, token, expires_at) VALUES (?, ?, ?)`, username, token, expires)
|
||||
return token, err
|
||||
}
|
||||
|
||||
func (s *SessionStore) GetUsername(token string) (string, error) {
|
||||
var username string
|
||||
var expires time.Time
|
||||
err := s.DB.QueryRow(`SELECT username, expires_at FROM sessions WHERE token = ?`, token).Scan(&username, &expires)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if time.Now().After(expires) {
|
||||
s.Delete(token)
|
||||
return "", errors.New("session expired")
|
||||
}
|
||||
return username, nil
|
||||
}
|
||||
|
||||
func (s *SessionStore) Delete(token string) {
|
||||
s.DB.Exec(`DELETE FROM sessions WHERE token = ?`, token)
|
||||
}
|
||||
|
||||
// ---- LDAP AUTHENTICATOR ----
|
||||
|
||||
type LDAPAuthenticator struct {
|
||||
URL string
|
||||
BindPattern string
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
func (a *LDAPAuthenticator) Authenticate(username, password string) error {
|
||||
if password == "" {
|
||||
return errors.New("leer Passwort")
|
||||
}
|
||||
conn, err := ldap.DialURL(a.URL, ldap.DialWithTLSConfig(a.TLSConfig))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("ldap dial: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
dn := fmt.Sprintf(a.BindPattern, ldap.EscapeFilter(username))
|
||||
if err := conn.Bind(dn, password); err != nil {
|
||||
return fmt.Errorf("ldap bind: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---- HTTP SERVER ----
|
||||
|
||||
type Server struct {
|
||||
cfg Config
|
||||
sessions *SessionStore
|
||||
auth *LDAPAuthenticator
|
||||
}
|
||||
|
||||
func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
user := strings.TrimSpace(r.Form.Get("username"))
|
||||
pass := r.Form.Get("password")
|
||||
|
||||
if err := s.auth.Authenticate(user, pass); err != nil {
|
||||
http.Error(w, "invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := s.sessions.Create(user, s.cfg.SessionTTL)
|
||||
if err != nil {
|
||||
log.Println("cannot create session:", err)
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session_token",
|
||||
Value: token,
|
||||
Expires: time.Now().Add(s.cfg.SessionTTL),
|
||||
Path: "/",
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
})
|
||||
fmt.Fprintln(w, "ok")
|
||||
}
|
||||
|
||||
func (s *Server) withAuth(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := r.Cookie("session_token")
|
||||
if err != nil {
|
||||
http.Error(w, "unauthenticated", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
user, err := s.sessions.GetUsername(c.Value)
|
||||
if err != nil {
|
||||
http.Error(w, "unauthenticated", http.StatusUnauthorized)
|
||||
http.Redirect(w, r, "/login", http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), userKey, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// authAware builds a handler that can treat auth as optional or mandatory.
|
||||
//
|
||||
// required == true → redirect to /login if not authenticated.
|
||||
// required == false → choose between unauth (A) and auth (B) branch.
|
||||
func (s *Server) authAware(required bool, unauth, auth http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var username string
|
||||
if c, err := r.Cookie("session_token"); err == nil {
|
||||
if u, err := s.sessions.GetUsername(c.Value); err == nil {
|
||||
username = u
|
||||
}
|
||||
}
|
||||
isAuth := username != ""
|
||||
if isAuth {
|
||||
r = r.WithContext(context.WithValue(r.Context(), userKey, username))
|
||||
}
|
||||
|
||||
if required {
|
||||
if !isAuth {
|
||||
http.Redirect(w, r, "/login", http.StatusFound)
|
||||
return
|
||||
}
|
||||
auth.ServeHTTP(w, r) // C
|
||||
return
|
||||
}
|
||||
|
||||
if isAuth {
|
||||
auth.ServeHTTP(w, r) // B
|
||||
} else {
|
||||
unauth.ServeHTTP(w, r) // A
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) protectedHandler(w http.ResponseWriter, r *http.Request) {
|
||||
user := r.Context().Value(userKey).(string)
|
||||
fmt.Fprintf(w, "Hallo %s, du bist authentifiziert!", user)
|
||||
}
|
||||
|
Reference in New Issue
Block a user