752 lines
25 KiB
Go
752 lines
25 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"crypto/rand"
|
||
"crypto/tls"
|
||
"database/sql"
|
||
"encoding/base64"
|
||
"errors"
|
||
"fmt"
|
||
"html/template"
|
||
"log"
|
||
"net/http"
|
||
"os"
|
||
"os/signal"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"syscall"
|
||
"time"
|
||
|
||
"github.com/go-ldap/ldap/v3"
|
||
_ "github.com/go-sql-driver/mysql"
|
||
)
|
||
|
||
func getenv(k, d string) string {
|
||
if v := os.Getenv(k); v != "" {
|
||
return v
|
||
}
|
||
return d
|
||
}
|
||
|
||
func enabled(k string, def bool) bool {
|
||
b, err := strconv.ParseBool(strings.ToLower(os.Getenv(k)))
|
||
if err != nil {
|
||
return def
|
||
}
|
||
return b
|
||
}
|
||
|
||
func cacheControl(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
|
||
/* ################################################################## */
|
||
/* START DER STRUKTUREN */
|
||
/* ################################################################## */
|
||
|
||
type RuleSet struct {
|
||
Id int
|
||
Name string
|
||
Default_contact_read sql.NullInt64
|
||
Default_contact_write sql.NullInt64
|
||
Default_contact_delete sql.NullInt64
|
||
Default_keyword_read sql.NullInt64
|
||
Default_keyword_write sql.NullInt64
|
||
Default_keyword_delete sql.NullInt64
|
||
Default_keyword_attach sql.NullInt64
|
||
Default_keyword_detach sql.NullInt64
|
||
Default_aduser_read sql.NullInt64
|
||
Default_aduser_write sql.NullInt64
|
||
Default_aduser_delete sql.NullInt64
|
||
Default_location_read sql.NullInt64
|
||
Default_location_write sql.NullInt64
|
||
Default_location_delete sql.NullInt64
|
||
Default_department_read sql.NullInt64
|
||
Default_department_write sql.NullInt64
|
||
Default_department_delete sql.NullInt64
|
||
Self_contact_read sql.NullInt64
|
||
Self_contact_write sql.NullInt64
|
||
Self_keyword_attach sql.NullInt64
|
||
Self_keyword_detach sql.NullInt64
|
||
Private_contact_read sql.NullInt64
|
||
Private_contact_write sql.NullInt64
|
||
Private_keyword_add sql.NullInt64
|
||
Private_keyword_delete sql.NullInt64
|
||
Private_keyword_attach sql.NullInt64
|
||
Private_keyword_detach sql.NullInt64
|
||
}
|
||
|
||
type ADUser struct {
|
||
Id int
|
||
SamAccountName string
|
||
Sid string
|
||
RuleSetId sql.NullInt64
|
||
}
|
||
|
||
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 sql.NullInt64
|
||
AdUserId sql.NullInt64
|
||
DisplayName string
|
||
Phone string
|
||
Mobile string
|
||
Homeoffice string
|
||
Email string
|
||
Room string
|
||
DepartmentId sql.NullInt64
|
||
LocationId sql.NullInt64
|
||
}
|
||
|
||
type SimplifiedContact struct {
|
||
Id int
|
||
OwnerId sql.NullInt64
|
||
AdUserId sql.NullInt64
|
||
DisplayName string
|
||
Phone string
|
||
Mobile string
|
||
Homeoffice string
|
||
Email string
|
||
Room string
|
||
DepartmentId string
|
||
LocationId string
|
||
}
|
||
|
||
type ContactKeywordLink struct {
|
||
Contact sql.NullInt64
|
||
Keyword sql.NullInt64
|
||
}
|
||
|
||
type Keyword struct {
|
||
Id int
|
||
Owner sql.NullInt64
|
||
Name string
|
||
}
|
||
|
||
type DataPort struct {
|
||
Contacts []Contact
|
||
Keywords []Keyword
|
||
/*Links []ContactKeywordLink*/
|
||
}
|
||
|
||
type SimplifiedDataPort struct {
|
||
Contacts []SimplifiedContact
|
||
Keywords []Keyword
|
||
/*Links []ContactKeywordLink*/
|
||
}
|
||
|
||
/* ################################################################## */
|
||
/* ENDE DER STRUKTUREN */
|
||
/* ################################################################## */
|
||
|
||
// ----- Example handlers -----
|
||
func (s *Server) publicHello(w http.ResponseWriter, r *http.Request) {
|
||
fmt.Fprintln(w, "Hallo an alle - öffentliche Daten")
|
||
}
|
||
func (s *Server) privateHello(w http.ResponseWriter, r *http.Request) {
|
||
user := r.Context().Value(userKey).(string)
|
||
fmt.Fprintf(w, "Hallo %s - hier deine persönlichen Daten", user)
|
||
}
|
||
|
||
func NullInt64ToString(n sql.NullInt64) string {
|
||
if !n.Valid {
|
||
return ""
|
||
}
|
||
return strconv.FormatInt(n.Int64, 10)
|
||
}
|
||
|
||
func GetDataReturnDataPort(QueryContact, QueryKeyword string) SimplifiedDataPort {
|
||
var contList []SimplifiedContact
|
||
var keywordList []Keyword
|
||
|
||
if QueryContact != "" {
|
||
rowsContact, err := DB.Query(QueryContact)
|
||
if err != nil {
|
||
fmt.Println("a", err)
|
||
}
|
||
for rowsContact.Next() {
|
||
var c SimplifiedContact
|
||
err = rowsContact.Scan(&c.Id, &c.OwnerId, &c.AdUserId, &c.DisplayName, &c.Phone, &c.Mobile, &c.Homeoffice, &c.Email, &c.Room, &c.DepartmentId, &c.LocationId)
|
||
if err != nil {
|
||
fmt.Println("b", err)
|
||
}
|
||
contList = append(contList, c)
|
||
}
|
||
}
|
||
|
||
if QueryKeyword != "" {
|
||
rowsKeyword, err := DB.Query(QueryKeyword)
|
||
if err != nil {
|
||
fmt.Println("c", err)
|
||
}
|
||
for rowsKeyword.Next() {
|
||
var c0 Keyword
|
||
err = rowsKeyword.Scan(&c0.Id, &c0.Owner, &c0.Name)
|
||
if err != nil {
|
||
fmt.Println("d", err)
|
||
}
|
||
keywordList = append(keywordList, c0)
|
||
}
|
||
}
|
||
|
||
return SimplifiedDataPort{Contacts: contList, Keywords: keywordList}
|
||
}
|
||
|
||
func makeContactTableJoin() string {
|
||
return "SELECT c.contact_id, c.contact_owner_id, c.contact_aduser_id, c.contact_displayname, c.contact_phone, c.contact_mobile, c.contact_homeoffice, c.contact_email, c.contact_room, d.department_name, l.location_name FROM contact c JOIN department d ON (c.contact_department_id = d.department_id) JOIN location l ON (c.contact_location_id = l.location_id) "
|
||
}
|
||
|
||
func (s *Server) ListPublic(w http.ResponseWriter, r *http.Request) {
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+"WHERE c.contact_owner_id = -1;", "SELECT * FROM keyword c WHERE c.keyword_owner = -1;")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
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"))
|
||
tplFull.ExecuteTemplate(w, "layout", D)
|
||
/*user := r.Context().Value(userKey).(string)
|
||
fmt.Fprintf(w, "Hallo %s - hier deine persönlichen Daten", user)*/
|
||
}
|
||
|
||
func (s *Server) ListPrivate(w http.ResponseWriter, r *http.Request) {
|
||
user := r.Context().Value(userKey).(string)
|
||
/*fmt.Fprintf(w, "Hallo %s - hier deine persönlichen Daten", user)*/
|
||
fmt.Println("Hallo "+user+" hier deine persönlichen Daten", user)
|
||
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+"WHERE c.contact_owner_id = -1 OR c.contact_owner_id = 1;", "SELECT * FROM keyword c WHERE c.keyword_owner = -1 OR c.keyword_owner = 1;")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
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"))
|
||
tplFull.ExecuteTemplate(w, "layout", D)
|
||
|
||
}
|
||
|
||
func (s *Server) XcontactPublic(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+"WHERE c.contact_owner_id = -1 AND (c.contact_displayname LIKE '%"+sparam+"%' OR c.contact_phone LIKE '%"+sparam+"%' OR c.contact_mobile LIKE '%"+sparam+"%' OR c.contact_homeoffice LIKE '%"+sparam+"%');", "SELECT * FROM keyword c WHERE c.keyword_owner = -1;")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||
layout.ExecuteTemplate(w, "kontakt", D)
|
||
}
|
||
|
||
func (s *Server) XcontactPrivate(w http.ResponseWriter, r *http.Request) {
|
||
|
||
}
|
||
|
||
func (s *Server) XdepartmentPublic(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+"WHERE c.contact_owner_id = -1 AND (d.department_name LIKE '%"+sparam+"%');", "SELECT * FROM keyword c WHERE c.keyword_owner = -1;")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||
layout.ExecuteTemplate(w, "kontakt", D)
|
||
}
|
||
|
||
func (s *Server) XdepartmentPrivate(w http.ResponseWriter, r *http.Request) {
|
||
|
||
}
|
||
|
||
func (s *Server) XroomPublic(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+"WHERE c.contact_owner_id = -1 AND (c.contact_room LIKE '%"+sparam+"%');", "SELECT * FROM keyword c WHERE c.keyword_owner = -1;")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||
layout.ExecuteTemplate(w, "kontakt", D)
|
||
}
|
||
|
||
func (s *Server) XroomPrivate(w http.ResponseWriter, r *http.Request) {
|
||
|
||
}
|
||
|
||
func (s *Server) XlocationPublic(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+"WHERE c.contact_owner_id = -1 AND (l.location_name LIKE '%"+sparam+"%');", "SELECT * FROM keyword c WHERE c.keyword_owner = -1;")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||
layout.ExecuteTemplate(w, "kontakt", D)
|
||
}
|
||
|
||
func (s *Server) XlocationPrivate(w http.ResponseWriter, r *http.Request) {
|
||
|
||
}
|
||
|
||
func (s *Server) XkeywordPublic(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+"WHERE c.contact_owner_id = -1 AND (l.location_name LIKE '%"+sparam+"%');", "SELECT * FROM keyword c WHERE c.keyword_owner = -1 AND c.keyword_name LIKE '%"+sparam+"%';")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("schlagwort").Funcs(funcs).ParseFiles(templatesDir + "/schlagwortliste.html"))
|
||
layout.ExecuteTemplate(w, "schlagwort", D)
|
||
}
|
||
|
||
func (s *Server) XkeywordPrivate(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||
D := GetDataReturnDataPort("", "SELECT * FROM keyword c WHERE (c.keyword_owner = -1 OR c.keyword_owner = 1) AND c.keyword_name LIKE '%"+sparam+"%';")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("schlagwort").Funcs(funcs).ParseFiles(templatesDir + "/schlagwortliste.html"))
|
||
layout.ExecuteTemplate(w, "schlagwort", D)
|
||
}
|
||
|
||
func (s *Server) XkwbctPublic(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("cid"))
|
||
D := GetDataReturnDataPort("", "SELECT c.* FROM keyword c JOIN contactkeyword z ON (z.contactkeyword_keyword = c.keyword_id) WHERE z.contactkeyword_contact = "+sparam+";")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("schlagwort").Funcs(funcs).ParseFiles(templatesDir + "/schlagwortliste.html"))
|
||
layout.ExecuteTemplate(w, "schlagwort", D)
|
||
}
|
||
|
||
func (s *Server) XkwbctPrivate(w http.ResponseWriter, r *http.Request) {
|
||
user := r.Context().Value(userKey).(string)
|
||
fmt.Println(user)
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("cid"))
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+" JOIN contactkeyword z ON (z.contactkeyword_contact = c.contact_id) WHERE c.keyword_owner = -1 OR c.keyword_owner = 1 AND z.contactkeyword_keyword = "+sparam+";", "")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||
layout.ExecuteTemplate(w, "kontakt", D)
|
||
}
|
||
|
||
func (s *Server) XctbkwPublic(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("cid"))
|
||
D := GetDataReturnDataPort(makeContactTableJoin()+" JOIN contactkeyword z ON (z.contactkeyword_contact = c.contact_id) WHERE c.keyword_owner = -1 AND z.contactkeyword_keyword = "+sparam+";", "")
|
||
funcs := template.FuncMap{"now": time.Now}
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
layout := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||
layout.ExecuteTemplate(w, "kontakt", D)
|
||
}
|
||
|
||
func (s *Server) XctbkwPrivate(w http.ResponseWriter, r *http.Request) {
|
||
|
||
}
|
||
|
||
var CFG Config
|
||
var DB *sql.DB
|
||
|
||
type Ranger struct {
|
||
mu sync.RWMutex
|
||
xContacts map[string]struct{}
|
||
}
|
||
|
||
/*func newRanger() *Ranger {
|
||
return &Ranger{
|
||
blocks: make(map[string]map[netip.Prefix]struct{}),
|
||
whites: make(map[netip.Addr]struct{}),
|
||
}
|
||
}*/
|
||
|
||
type XServer struct {
|
||
ranger *Ranger
|
||
}
|
||
|
||
func main() {
|
||
|
||
// Signal-Kanal einrichten
|
||
stop := make(chan os.Signal, 1)
|
||
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
|
||
|
||
// Goroutine, die auf Signale wartet
|
||
go func() {
|
||
<-stop
|
||
fmt.Println("Stop Sign...")
|
||
prepareExit()
|
||
os.Exit(0)
|
||
}()
|
||
|
||
// --- Verzeichnisse konfigurierbar machen -------------------------
|
||
staticDir := getenv("BLOG_STATIC_DIR", "./static")
|
||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||
|
||
CFG = Config{
|
||
DSN: "hikos:hikos@tcp(db-mysql-ubnt-a.stadt-hilden.de:3306)/hikos?parseTime=true",
|
||
LDAPURL: "ldaps://ldaps.example.com:636",
|
||
LDAPBindPattern: "uid=%s,ou=users,dc=example,dc=com",
|
||
SessionTTL: 24 * time.Hour,
|
||
}
|
||
|
||
db, err := sql.Open("mysql", CFG.DSN)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
if err := db.Ping(); err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
DB = db
|
||
|
||
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
|
||
}
|
||
|
||
// 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"))
|
||
|
||
/*tplKontakt := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||
tplSchlagwort := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/schlagwortliste.html"))*/
|
||
|
||
layoutSSO := template.Must(template.New("sso").Funcs(funcs).ParseFiles(templatesDir + "/login.html"))
|
||
|
||
mux := http.NewServeMux()
|
||
|
||
mux.HandleFunc("/login", srv.loginHandler)
|
||
//mux.Handle("/protected", srv.withAuth(http.HandlerFunc(srv.protectedHandler)))
|
||
|
||
mux.HandleFunc("/sso", func(w http.ResponseWriter, r *http.Request) {
|
||
layoutSSO.ExecuteTemplate(w, "sso", nil)
|
||
})
|
||
|
||
mux.Handle("/hello", srv.authAware(true, http.HandlerFunc(srv.publicHello), http.HandlerFunc(srv.privateHello)))
|
||
|
||
// Handler für /
|
||
|
||
mux.Handle("/", srv.authAware(false, http.HandlerFunc(srv.ListPublic), http.HandlerFunc(srv.ListPrivate)))
|
||
mux.Handle("/htmx/contact", srv.authAware(false, http.HandlerFunc(srv.XcontactPublic), http.HandlerFunc(srv.XcontactPrivate)))
|
||
mux.Handle("/htmx/department", srv.authAware(false, http.HandlerFunc(srv.XdepartmentPublic), http.HandlerFunc(srv.XdepartmentPrivate)))
|
||
mux.Handle("/htmx/room", srv.authAware(false, http.HandlerFunc(srv.XroomPublic), http.HandlerFunc(srv.XroomPrivate)))
|
||
mux.Handle("/htmx/location", srv.authAware(false, http.HandlerFunc(srv.XlocationPublic), http.HandlerFunc(srv.XlocationPrivate)))
|
||
mux.Handle("/htmx/keyword", srv.authAware(false, http.HandlerFunc(srv.XkeywordPublic), http.HandlerFunc(srv.XkeywordPrivate)))
|
||
mux.Handle("/htmx/keywordbycontact", srv.authAware(false, http.HandlerFunc(srv.XkwbctPublic), http.HandlerFunc(srv.XkwbctPrivate)))
|
||
mux.Handle("/htmx/contactbykeyword", srv.authAware(false, http.HandlerFunc(srv.XctbkwPublic), http.HandlerFunc(srv.XctbkwPrivate)))
|
||
|
||
/*mux.HandleFunc("/htmx/contact", func(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||
fmt.Println("All_OK", sparam)
|
||
sqlq := "SELECT * FROM contact c WHERE c.contact_displayname LIKE '%" + sparam + "%' OR c.contact_phone LIKE '%" + sparam + "%' OR c.contact_mobile LIKE '%" + sparam + "%' OR c.contact_homeoffice LIKE '%" + sparam + "%';"
|
||
D := GetDataReturnDataPort(sqlq, "")
|
||
tplKontakt.ExecuteTemplate(w, "kontakt", D)
|
||
})
|
||
|
||
mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) {
|
||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||
})
|
||
|
||
mux.HandleFunc("/htmx/department", func(w http.ResponseWriter, r *http.Request) {
|
||
if err := r.ParseForm(); err != nil {
|
||
http.Error(w, "bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
sparam := strings.TrimSpace(r.Form.Get("search"))
|
||
fmt.Println("Department_OK", sparam)
|
||
sqlq := "SELECT c.* FROM contact c JOIN department d ON c.contact_department_id = d.department_id WHERE d.department_name LIKE '%" + sparam + "%';"
|
||
D := GetDataReturnDataPort(sqlq, "")
|
||
tplKontakt.ExecuteTemplate(w, "kontakt", D)
|
||
})
|
||
|
||
mux.HandleFunc("/htmx/room", func(w http.ResponseWriter, r *http.Request) {
|
||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
||
})
|
||
|
||
mux.HandleFunc("/htmx/location", 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)))))
|
||
|
||
mux.HandleFunc("/store", func(w http.ResponseWriter, r *http.Request) {
|
||
|
||
})
|
||
|
||
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://0.0.0.0: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))
|
||
|
||
}
|
||
|
||
func prepareExit() {
|
||
fmt.Println("~", "Running exit tasks...")
|
||
/*if err := SaveTickCatalog(getenv("BLOG_TICKS_DIR", "/ticks") + "/ticks.json"); err != nil {
|
||
fmt.Println("Fehler beim Speichern:", err)
|
||
}
|
||
fmt.Println("Geladener Katalog:", TickCatalog)*/
|
||
fmt.Println("~", "Exit completed.")
|
||
}
|
||
|
||
func StopServer(e error) {
|
||
fmt.Println("~", "Stopping server...")
|
||
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 1 == 2 {
|
||
if err := s.auth.Authenticate(user, pass); err != nil {
|
||
http.Error(w, "invalid credentials", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
} else {
|
||
if user == "admin" && pass == "admin" {
|
||
|
||
} else {
|
||
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: false, HttpOnly: true, SameSite: http.SameSiteStrictMode})
|
||
http.Redirect(w, r, "/", http.StatusMovedPermanently)
|
||
}
|
||
|
||
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, "/sso", 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)
|
||
}
|