This commit is contained in:
2025-05-21 06:45:19 +02:00
parent 49d8d547c2
commit ea37419860
4 changed files with 298 additions and 58 deletions

181
main.go
View File

@@ -51,40 +51,40 @@ func cacheControl(next http.Handler) http.Handler {
type RuleSet struct { type RuleSet struct {
Id int Id int
Name string Name string
Default_contact_read int Default_contact_read sql.NullInt64
Default_contact_write int Default_contact_write sql.NullInt64
Default_contact_delete int Default_contact_delete sql.NullInt64
Default_keyword_read int Default_keyword_read sql.NullInt64
Default_keyword_write int Default_keyword_write sql.NullInt64
Default_keyword_delete int Default_keyword_delete sql.NullInt64
Default_keyword_attach int Default_keyword_attach sql.NullInt64
Default_keyword_detach int Default_keyword_detach sql.NullInt64
Default_aduser_read int Default_aduser_read sql.NullInt64
Default_aduser_write int Default_aduser_write sql.NullInt64
Default_aduser_delete int Default_aduser_delete sql.NullInt64
Default_location_read int Default_location_read sql.NullInt64
Default_location_write int Default_location_write sql.NullInt64
Default_location_delete int Default_location_delete sql.NullInt64
Default_department_read int Default_department_read sql.NullInt64
Default_department_write int Default_department_write sql.NullInt64
Default_department_delete int Default_department_delete sql.NullInt64
Self_contact_read int Self_contact_read sql.NullInt64
Self_contact_write int Self_contact_write sql.NullInt64
Self_keyword_attach int Self_keyword_attach sql.NullInt64
Self_keyword_detach int Self_keyword_detach sql.NullInt64
Private_contact_read int Private_contact_read sql.NullInt64
Private_contact_write int Private_contact_write sql.NullInt64
Private_keyword_add int Private_keyword_add sql.NullInt64
Private_keyword_delete int Private_keyword_delete sql.NullInt64
Private_keyword_attach int Private_keyword_attach sql.NullInt64
Private_keyword_detach int Private_keyword_detach sql.NullInt64
} }
type ADUser struct { type ADUser struct {
Id int Id int
SamAccountName string SamAccountName string
Sid string Sid string
RuleSetId RuleSet RuleSetId sql.NullInt64
} }
type Department struct { type Department struct {
@@ -102,21 +102,32 @@ type Location struct {
type Contact struct { type Contact struct {
Id int Id int
OwnerId int OwnerId sql.NullInt64
AdUserId ADUser AdUserId sql.NullInt64
DisplayName string DisplayName string
Phone string Phone string
Mobile string Mobile string
Homeoffice string Homeoffice string
Email string Email string
Room string Room string
DepartmentId Department DepartmentId sql.NullInt64
LocationId Location LocationId sql.NullInt64
} }
type ContactKeywordLink struct { type ContactKeywordLink struct {
Contact int Contact sql.NullInt64
Keyword int Keyword sql.NullInt64
}
type Keyword struct {
Id int
Owner sql.NullInt64
Name string
}
type DataPort struct {
Contacts []Contact
Keywords []Keyword
} }
/* ################################################################## */ /* ################################################################## */
@@ -125,13 +136,71 @@ type ContactKeywordLink struct {
// ----- Example handlers ----- // ----- Example handlers -----
func (s *Server) publicHello(w http.ResponseWriter, r *http.Request) { func (s *Server) publicHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hallo an alle öffentliche Daten") fmt.Fprintln(w, "Hallo an alle - öffentliche Daten")
} }
func (s *Server) privateHello(w http.ResponseWriter, r *http.Request) { func (s *Server) privateHello(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(userKey).(string) user := r.Context().Value(userKey).(string)
fmt.Fprintf(w, "Hallo %s hier deine persönlichen Daten", user) fmt.Fprintf(w, "Hallo %s - hier deine persönlichen Daten", user)
} }
func GetDataReturnDataPort(QueryContact, QueryKeyword string) DataPort {
rows, err := DB.Query(QueryContact)
if err != nil {
fmt.Println("a", err)
}
var contList []Contact
for rows.Next() {
var c Contact
err = rows.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)
}
rows1, err := DB.Query(QueryKeyword)
if err != nil {
fmt.Println("c", err)
}
var keywordList []Keyword
for rows1.Next() {
var c0 Keyword
err = rows1.Scan(&c0.Id, &c0.Owner, &c0.Name)
if err != nil {
fmt.Println("d", err)
}
keywordList = append(keywordList, c0)
}
return DataPort{Contacts: contList, Keywords: keywordList}
}
func (s *Server) ListPublic(w http.ResponseWriter, r *http.Request) {
D := GetDataReturnDataPort("SELECT * FROM contact c 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) {
D := GetDataReturnDataPort("SELECT * FROM contact c 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)
/*user := r.Context().Value(userKey).(string)
fmt.Fprintf(w, "Hallo %s - hier deine persönlichen Daten", user)*/
}
var CFG Config
var DB *sql.DB
func main() { func main() {
// Signal-Kanal einrichten // Signal-Kanal einrichten
@@ -150,32 +219,33 @@ func main() {
staticDir := getenv("BLOG_STATIC_DIR", "./static") staticDir := getenv("BLOG_STATIC_DIR", "./static")
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates") templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
cfg := Config{ CFG = Config{
DSN: "hikos:hikos@tcp(10.10.5.31:3306)/hikos?parseTime=true", DSN: "hikos:hikos@tcp(10.10.5.31:3306)/hikos?parseTime=true",
LDAPURL: "ldaps://ldaps.example.com:636", LDAPURL: "ldaps://ldaps.example.com:636",
LDAPBindPattern: "uid=%s,ou=users,dc=example,dc=com", LDAPBindPattern: "uid=%s,ou=users,dc=example,dc=com",
SessionTTL: 24 * time.Hour, SessionTTL: 24 * time.Hour,
} }
db, err := sql.Open("mysql", cfg.DSN) db, err := sql.Open("mysql", CFG.DSN)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := db.Ping(); err != nil { if err := db.Ping(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
DB = db
store := &SessionStore{DB: db} store := &SessionStore{DB: db}
auth := &LDAPAuthenticator{ auth := &LDAPAuthenticator{
URL: cfg.LDAPURL, URL: CFG.LDAPURL,
BindPattern: cfg.LDAPBindPattern, BindPattern: CFG.LDAPBindPattern,
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
}, },
} }
srv := &Server{ srv := &Server{
cfg: cfg, cfg: CFG,
sessions: store, sessions: store,
auth: auth, auth: auth,
} }
@@ -192,17 +262,26 @@ func main() {
tplKontakt := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.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")) 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 := http.NewServeMux()
mux.HandleFunc("/login", srv.loginHandler) mux.HandleFunc("/login", srv.loginHandler)
//mux.Handle("/protected", srv.withAuth(http.HandlerFunc(srv.protectedHandler))) //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))) mux.Handle("/hello", srv.authAware(true, http.HandlerFunc(srv.publicHello), http.HandlerFunc(srv.privateHello)))
// Handler für / // Handler für /
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
mux.Handle("/", srv.authAware(false, http.HandlerFunc(srv.ListPublic), http.HandlerFunc(srv.ListPrivate)))
/*mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tplFull.ExecuteTemplate(w, "layout", nil) tplFull.ExecuteTemplate(w, "layout", nil)
}) })*/
mux.HandleFunc("/htmx/kontakt", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/htmx/kontakt", func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
@@ -223,9 +302,10 @@ func main() {
for rows.Next() { for rows.Next() {
var c Contact var c Contact
err = rows.Scan(&c.Id) err = rows.Scan(&c.Id)
contList = append(contList, c)
} }
tplKontakt.ExecuteTemplate(w, "kontakt", nil) tplKontakt.ExecuteTemplate(w, "kontakt", contList)
}) })
mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) {
@@ -393,10 +473,21 @@ func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) {
} }
user := strings.TrimSpace(r.Form.Get("username")) user := strings.TrimSpace(r.Form.Get("username"))
pass := r.Form.Get("password") pass := r.Form.Get("password")
if 1 == 2 {
if err := s.auth.Authenticate(user, pass); err != nil { if err := s.auth.Authenticate(user, pass); err != nil {
http.Error(w, "invalid credentials", http.StatusUnauthorized) http.Error(w, "invalid credentials", http.StatusUnauthorized)
return 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) token, err := s.sessions.Create(user, s.cfg.SessionTTL)
if err != nil { if err != nil {
log.Println("cannot create session:", err) log.Println("cannot create session:", err)
@@ -404,7 +495,7 @@ func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) {
return 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}) 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") http.Redirect(w, r, "/", http.StatusMovedPermanently)
} }
func (s *Server) withAuth(next http.Handler) http.Handler { func (s *Server) withAuth(next http.Handler) http.Handler {

View File

@@ -12,7 +12,7 @@
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name <a href="/sso">(Anmelden)</a></th>
<th>Telefon</th> <th>Telefon</th>
<th>Mobil</th> <th>Mobil</th>
<th>Homeoffice</th> <th>Homeoffice</th>
@@ -22,15 +22,15 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{ range .Kontakte }} {{ range .Contacts }}
<tr> <tr>
<td>{{ .Name }}</td> <td>{{ if .DisplayName }}{{ .DisplayName }}{{ end }}</td>
<td>{{ .Telefon }}</td> <td>{{ if .Phone }}{{ .Phone }}{{ end }}</td>
<td>{{ .Mobil }}</td> <td>{{ if .Mobile }}{{ .Mobile }}{{ end }}</td>
<td>{{ .Homeoffice }}</td> <td>{{ if .Homeoffice }}{{ .Homeoffice }}{{ end }}</td>
<td>{{ .Amt }}</td> <td>{{ if .DepartmentId.Valid }}{{ .DepartmentId.Int64 }}{{ end }}</td>
<td>{{ .Raum }}</td> <td>{{ if .Room }}{{ .Room }}{{ end }}</td>
<td>{{ .Gebaeude }}</td> <td>{{ if .LocationId.Valid }}{{ .LocationId.Int64 }}{{ end }}</td>
</tr> </tr>
{{ end }} {{ end }}
</tbody> </tbody>

149
static/templates/login.html Normal file
View File

@@ -0,0 +1,149 @@
{{ define "sso" }}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Login</title>
<!-- Dein zentrales styles.css mit den Variablen einbinden -->
<link rel="stylesheet" href="/static/css/main.css" />
<!-- ▸ Zusätzliche Regeln NUR für das Log-In-Formular -->
<style>
/* Vollflächig zentrieren */
.login-wrapper{
display:flex;
align-items:center;
justify-content:center;
height:100vh; /* immer Bildschirmhöhe */
width:100%;
}
/* Die Karte selbst */
.login-card{
background:var(--card-bg);
padding:2.5rem 2rem;
border-radius:calc(var(--radius)*2);
box-shadow:var(--shadow);
width:100%;
max-width:400px;
}
.login-card h1{
font-size:1.5rem;
font-weight:600;
margin-bottom:1.5rem;
text-align:center;
}
.InputBox {
width:100%;
}
/* Formular-Layout */
.login-card form{
display:grid;
gap:1rem;
justify-items:center; /* horizontale Zentrierung für alle Kinder */
}
.login-card label{
font-size:0.875rem;
text-align:center;
color:var(--text-muted);
margin-bottom:0.25rem;
text-align:center; /* Label-Text mittig */
width:100%;
}
.login-card input{
padding:0.75rem 1rem;
border:1px solid var(--code-border);
border-radius:var(--radius);
background:var(--code-bg);
color: var(--text);
font-family:"Inter",system-ui,sans-serif;
transition:border-color .2s ease;
width:100%; /* auf kleineren Screens vollbreit … */
/*max-width:280px;*/ /* … aber nie breiter als 280 px */
margin:0 auto; /* Input-Box selbst zentrieren */
}
.login-card input:focus{
border-color:var(--accent);
outline:none;
color: var(--text);
box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 40%, transparent);
}
/* Primär-Button */
.login-card button{
padding:0.75rem 1rem;
border:none;
border-radius:var(--radius);
background:var(--accent);
color:#fff;
font-weight:600;
cursor:pointer;
transition:background .2s ease, transform .1s ease;
max-width:280px; /* Button bündelt sich an der Input-Breite */
width:100%;
margin:0.5rem auto 0;
}
.login-card button:hover{
background:var(--accent-light);
}
.login-card button:active{
transform:translateY(1px);
}
/* kleine Links unten */
.login-card .links{
margin-top:1rem;
text-align:center;
}
.login-card .links a{
color:var(--accent);
font-size:0.875rem;
text-decoration:none;
}
.login-card .links a:hover{
text-decoration:underline;
}
</style>
</head>
<body>
<div class="login-wrapper">
<div class="login-card">
<h1>Anmelden</h1>
<form action="/login" method="post">
<div class="InputBox">
<label for="username">Benutzername</label>
<input type="text"
id="email"
name="username"
placeholder="Benutzername"
autocomplete="Benutzername"
required />
</div>
<div class="InputBox">
<label for="password">Passwort</label>
<input type="password"
id="password"
name="password"
placeholder="••••••••"
autocomplete="current-password"
required />
</div>
<button type="submit">Einloggen</button>
</form>
<div class="links">
<a href="/forgot-password">Passwort vergessen?</a>
</div>
</div>
</div>
</body>
</html>
{{ end }}

View File

@@ -10,7 +10,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{ range .Kontakte }} {{ range .Keywords }}
<tr><td>{{ .Name }}</td></tr> <tr><td>{{ .Name }}</td></tr>
{{ end }} {{ end }}
</tbody> </tbody>