diff --git a/Backend/endpoints/time.go b/Backend/endpoints/time.go index ce65cde..2db1489 100644 --- a/Backend/endpoints/time.go +++ b/Backend/endpoints/time.go @@ -28,27 +28,26 @@ func TimeHandler(w http.ResponseWriter, r *http.Request) { } } -func parseTimestamp(r *http.Request , get_key string, fallback string) (time.Time, error) { +func parseTimestamp(r *http.Request, get_key string, fallback string) (time.Time, error) { _timestamp_get := r.URL.Query().Get(get_key) - if(_timestamp_get == "") { + if _timestamp_get == "" { _timestamp_get = fallback } timestamp_get, err := time.Parse("2006-01-02", _timestamp_get) - if(err != nil){ + if err != nil { return time.Now(), err } return timestamp_get, nil } - // Returns bookings from DB with similar card uid -> checks for card uid in http query params func getBookings(w http.ResponseWriter, r *http.Request) { var user models.User var err error - if(helper.GetEnv("GO_ENV", "production") == "debug"){ + if helper.GetEnv("GO_ENV", "production") == "debug" { user, err = (*models.User).GetByPersonalNummer(nil, 123) - }else{ - if(!Session.Exists(r.Context(), "user")){ + } else { + if !Session.Exists(r.Context(), "user") { log.Println("No user in session storage!") http.Error(w, "Not logged in!", http.StatusForbidden) return @@ -56,7 +55,7 @@ func getBookings(w http.ResponseWriter, r *http.Request) { user, err = (*models.User).GetByPersonalNummer(nil, Session.GetInt(r.Context(), "user")) } - if(err != nil){ + if err != nil { log.Println("No user found with the given personal number!") http.Error(w, "No user found", http.StatusNotFound) return @@ -64,18 +63,18 @@ func getBookings(w http.ResponseWriter, r *http.Request) { // TODO add config for timeoffset tsFrom, err := parseTimestamp(r, "time_from", time.Now().AddDate(0, -1, 0).Format("2006-01-02")) - if(err != nil ){ + if err != nil { log.Println("Error parsing 'from' time", err) http.Error(w, "Timestamp 'from' cannot be parsed!", http.StatusBadRequest) return } tsTo, err := parseTimestamp(r, "time_to", time.Now().Format("2006-01-02")) - if(err != nil ){ + if err != nil { log.Println("Error parsing 'to' time", err) http.Error(w, "Timestamp 'to' cannot be parsed!", http.StatusBadRequest) return } - tsTo = tsTo.AddDate(0,0,1) // so that today is inside + tsTo = tsTo.AddDate(0, 0, 1) // so that today is inside bookings, err := (*models.Booking).GetBookingsGrouped(nil, user.CardUID, tsFrom, tsTo) if err != nil { @@ -87,22 +86,22 @@ func getBookings(w http.ResponseWriter, r *http.Request) { templates.TimeDashboard(bookings).Render(ctx, w) } -func updateBooking(w http.ResponseWriter, r *http.Request){ +func updateBooking(w http.ResponseWriter, r *http.Request) { r.ParseForm() - for index, possibleBooking := range r.PostForm{ - if(index[:7] == "booking"){ + for index, possibleBooking := range r.PostForm { + if index[:7] == "booking" { booking_id, err := strconv.Atoi(index[8:]) - if(err != nil){ + if err != nil { log.Println("Error parsing bookingId", err) continue } booking, err := (*models.Booking).GetBookingById(nil, booking_id) - if(err != nil){ + if err != nil { log.Println("Error getting booking!", err) continue } parsedTime, err := time.ParseInLocation("15:04", possibleBooking[0], time.Local) - if(err != nil){ + if err != nil { log.Println("Error parsing time!", err) continue } @@ -113,17 +112,17 @@ func updateBooking(w http.ResponseWriter, r *http.Request){ getBookings(w, r) } -func getBookingsAPI(w http.ResponseWriter, r *http.Request){ +func getBookingsAPI(w http.ResponseWriter, r *http.Request) { _user_pn := r.URL.Query().Get("personal_nummer") user_pn, err := strconv.Atoi(_user_pn) - if(err != nil){ + if err != nil { log.Println("No personal numver found!") http.Error(w, "No personal number found", http.StatusBadRequest) return } user, err := (*models.User).GetByPersonalNummer(nil, user_pn) - if(err != nil){ + if err != nil { log.Println("No user found with the given personal number!") http.Error(w, "No user found", http.StatusNotFound) return @@ -131,18 +130,18 @@ func getBookingsAPI(w http.ResponseWriter, r *http.Request){ // TODO add config for timeoffset tsFrom, err := parseTimestamp(r, "time_from", time.Now().AddDate(0, -1, 0).Format("2006-01-02")) - if(err != nil ){ + if err != nil { log.Println("Error parsing 'from' time", err) http.Error(w, "Timestamp 'from' cannot be parsed!", http.StatusBadRequest) return } tsTo, err := parseTimestamp(r, "time_to", time.Now().Format("2006-01-02")) - if(err != nil ){ + if err != nil { log.Println("Error parsing 'to' time", err) http.Error(w, "Timestamp 'to' cannot be parsed!", http.StatusBadRequest) return } - tsTo = tsTo.AddDate(0,0,1) // so that today is inside + tsTo = tsTo.AddDate(0, 0, 1) // so that today is inside bookings, err := (*models.Booking).GetBookingsGrouped(nil, user.CardUID, tsFrom, tsTo) if err != nil { diff --git a/Backend/endpoints/time_create.go b/Backend/endpoints/time_create.go index c59b7f9..3c13053 100644 --- a/Backend/endpoints/time_create.go +++ b/Backend/endpoints/time_create.go @@ -29,7 +29,7 @@ func TimeCreateHandler(w http.ResponseWriter, r *http.Request) { // Creates a booking from the http query params -> no body needed // after that entry wi'll be written to database and the booking is returned as json func createBooking(w http.ResponseWriter, r *http.Request) { - if(!checkPassword(r)){ + if !checkPassword(r) { log.Println("Wrong or no API key provided!") http.Error(w, "Wrong or no API key provided", http.StatusUnauthorized) return diff --git a/Backend/endpoints/user.go b/Backend/endpoints/user.go index 218bb14..f821178 100644 --- a/Backend/endpoints/user.go +++ b/Backend/endpoints/user.go @@ -18,24 +18,42 @@ func CreateSessionManager(lifetime time.Duration) *scs.SessionManager { Session.Lifetime = lifetime return Session } - -func LoginHandler(w http.ResponseWriter, r *http.Request){ - switch r.Method{ - case http.MethodGet: showForm(w, r, false) +func LoginHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + showLoginForm(w, r, false) break - case http.MethodPost: loginUser(w, r) + case http.MethodPost: + loginUser(w, r) break - default: - showForm(w, r, false) + default: + showLoginForm(w, r, false) break } } -func showForm(w http.ResponseWriter, r *http.Request, failed bool){ +func UserHandler(w http.ResponseWriter, r *http.Request) { + if !Session.Exists(r.Context(), "user") { + http.Redirect(w, r, "/user/login", http.StatusTemporaryRedirect) + } + switch r.Method { + case http.MethodGet: + showPWForm(w, r, 0) + break + case http.MethodPost: + changePassword(w, r) + break + default: + http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed) + break + } +} + +func showLoginForm(w http.ResponseWriter, r *http.Request, failed bool) { templates.LoginForm(failed).Render(r.Context(), w) } -func loginUser(w http.ResponseWriter, r *http.Request){ +func loginUser(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { log.Println("Error parsing form!", err) @@ -43,32 +61,64 @@ func loginUser(w http.ResponseWriter, r *http.Request){ return } _personal_nummer := r.FormValue("personal_nummer") - if(_personal_nummer == ""){ + if _personal_nummer == "" { log.Println("No personal_nummer provided!") http.Error(w, "No personal_nummer provided", http.StatusBadRequest) return } personal_nummer, err := strconv.Atoi(_personal_nummer) - if(err != nil){ + if err != nil { log.Println("Cannot parse personal nubmer!") http.Error(w, "Cannot parse number", http.StatusBadRequest) return } user, err := (*models.User).GetByPersonalNummer(nil, personal_nummer) - if(err != nil){ + if err != nil { log.Println("No user found under this personal number!") http.Error(w, "No user found!", http.StatusNotFound) } password := r.FormValue("password") - if(user.Login(password)){ + if user.Login(password) { log.Printf("New succesfull user login from %s %s!\n", user.Vorname, user.Name) Session.Put(r.Context(), "user", user.PersonalNummer) http.Redirect(w, r, "/time", http.StatusSeeOther) //with this browser always uses GET - }else{ - showForm(w, r, true) + } else { + showLoginForm(w, r, true) } - -showForm(w, r, false) + showLoginForm(w, r, false) +} + +// change user password and store salted hash in db +func changePassword(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + log.Println("Error parsing form!", err) + http.Error(w, "Error parsing form error", http.StatusBadRequest) + return + } + password := r.FormValue("password") + newPassword := r.FormValue("new_password") + if password == "" || newPassword == "" || newPassword != r.FormValue("new_password_repeat") { + showPWForm(w, r, http.StatusBadRequest) + } + user, err := (*models.User).GetByPersonalNummer(nil, Session.GetInt(r.Context(), "user")) + if err != nil { + log.Println("Error getting user!", err) + showPWForm(w, r, http.StatusBadRequest) + } + auth, err := user.ChangePass(password, newPassword) + if err != nil { + log.Println("Error when changing password!", err) + } + if auth { + showPWForm(w, r, http.StatusOK) + return + } + showPWForm(w, r, http.StatusUnauthorized) +} + +func showPWForm(w http.ResponseWriter, r *http.Request, status int) { + templates.UserForm(status).Render(r.Context(), w) } diff --git a/Backend/main.go b/Backend/main.go index 2db8612..8ba7a39 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -14,15 +14,13 @@ import ( _ "github.com/lib/pq" ) - - func main() { var err error err = godotenv.Load(".env") if err != nil { log.Println("No .env file found in directory!") - } + } models.DB, err = OpenDatabase() if err != nil { @@ -36,11 +34,11 @@ func main() { server := http.NewServeMux() // handles the different http routes - withMiddleware := ParamsMiddleware(endpoints.TimeHandler) server.HandleFunc("/time/new", endpoints.TimeCreateHandler) - server.Handle("/time", withMiddleware) + server.Handle("/time", ParamsMiddleware(endpoints.TimeHandler)) server.HandleFunc("/logout", endpoints.LogoutHandler) - server.HandleFunc("/", endpoints.LoginHandler) + server.HandleFunc("/user/login", endpoints.LoginHandler) + server.HandleFunc("/user", endpoints.UserHandler) server.Handle("/static/", http.StripPrefix("/static/", fs)) serverSessionMiddleware := endpoints.Session.LoadAndSave(server) @@ -50,8 +48,8 @@ func main() { log.Fatal(http.ListenAndServe(":8080", serverSessionMiddleware)) } -func ParamsMiddleware(next http.HandlerFunc) http.Handler{ - return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { +func ParamsMiddleware(next http.HandlerFunc) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { queryParams := r.URL.Query() ctx := context.WithValue(r.Context(), "urlParams", queryParams) next.ServeHTTP(w, r.WithContext(ctx)) diff --git a/Backend/models/user.go b/Backend/models/user.go index adbc41a..4cf9989 100644 --- a/Backend/models/user.go +++ b/Backend/models/user.go @@ -6,11 +6,11 @@ import ( ) type User struct { - CardUID string `json:"card_uid"` - Name string `json:"name"` - Vorname string `json:"vorname"` - PersonalNummer int `json:"personal_nummer"` - Arbeitszeit float32 `json:"arbeitszeit"` + CardUID string `json:"card_uid"` + Name string `json:"name"` + Vorname string `json:"vorname"` + PersonalNummer int `json:"personal_nummer"` + Arbeitszeit float32 `json:"arbeitszeit"` } func (u *User) GetAll() ([]User, error) { @@ -67,7 +67,7 @@ func (u *User) Logout() error { return nil } -func (u *User) GetByPersonalNummer (personalNummer int) (User, error) { +func (u *User) GetByPersonalNummer(personalNummer int) (User, error) { var user User qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM personal_daten WHERE personal_nummer = $1;`)) @@ -76,7 +76,7 @@ func (u *User) GetByPersonalNummer (personalNummer int) (User, error) { } err = qStr.QueryRow(personalNummer).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.Arbeitszeit) - if err != nil{ + if err != nil { return user, err } return user, nil @@ -97,3 +97,21 @@ func (u *User) Login(password string) bool { } return loginSuccess } + +// checks if old password matches and changes to new password +// +// returns auth(bool), error +func (u *User) ChangePass(password, newPassword string) (bool, error) { + if !u.Login(password) { + return false, nil + } + qStr, err := DB.Prepare((`UPDATE user_password SET pass_hash = crypt($2, gen_salt('bf')) WHERE personal_nummer = $1;`)) + if err != nil { + return false, err + } + _, err = qStr.Exec(u.PersonalNummer, newPassword) + if err != nil { + return false, err + } + return true, nil +} diff --git a/Backend/static/css/styles.css b/Backend/static/css/styles.css index 50586e6..25acb06 100644 --- a/Backend/static/css/styles.css +++ b/Backend/static/css/styles.css @@ -565,6 +565,9 @@ .mt-1 { margin-top: calc(var(--spacing) * 1); } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } .block { display: block; } @@ -606,21 +609,24 @@ .h-full { height: 100%; } - .w-1 { - width: calc(var(--spacing) * 1); - } - .w-1\/2 { - width: calc(1/2 * 100%); - } .w-2 { width: calc(var(--spacing) * 2); } .w-4 { width: calc(var(--spacing) * 4); } + .w-9 { + width: calc(var(--spacing) * 9); + } + .w-9\/10 { + width: calc(9/10 * 100%); + } .w-\[2px\] { width: 2px; } + .w-\[90\%\] { + width: 90%; + } .w-full { width: 100%; } @@ -697,9 +703,6 @@ border-color: var(--color-neutral-400); } } - .self-end { - align-self: flex-end; - } .justify-self-end { justify-self: flex-end; } @@ -726,15 +729,9 @@ .border-neutral-300 { border-color: var(--color-neutral-300); } - .border-neutral-400 { - border-color: var(--color-neutral-400); - } .border-neutral-900 { border-color: var(--color-neutral-900); } - .border-red-600 { - border-color: var(--color-red-600); - } .bg-accent { background-color: var(--color-accent); } @@ -753,9 +750,6 @@ .bg-purple-600 { background-color: var(--color-purple-600); } - .bg-red-200 { - background-color: var(--color-red-200); - } .bg-red-600 { background-color: var(--color-red-600); } @@ -775,9 +769,9 @@ font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); } - .text-xs { - font-size: var(--text-xs); - line-height: var(--tw-leading, var(--text-xs--line-height)); + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); } .font-bold { --tw-font-weight: var(--font-weight-bold); @@ -786,12 +780,12 @@ .text-accent { color: var(--color-accent); } + .text-accent-600 { + color: var(--color-accent-600); + } .text-neutral-500 { color: var(--color-neutral-500); } - .text-neutral-600 { - color: var(--color-neutral-600); - } .text-neutral-700 { color: var(--color-neutral-700); } @@ -953,6 +947,11 @@ display: inline; } } + .md\:w-1\/2 { + @media (width >= 48rem) { + width: calc(1/2 * 100%); + } + } .md\:grid-cols-5 { @media (width >= 48rem) { grid-template-columns: repeat(5, minmax(0, 1fr)); diff --git a/Backend/templates/timeComponents.templ b/Backend/templates/timeComponents.templ index aefe297..23bf079 100644 --- a/Backend/templates/timeComponents.templ +++ b/Backend/templates/timeComponents.templ @@ -2,141 +2,140 @@ package templates import ( "arbeitszeitmessung/models" - "net/url" - "time" "fmt" + "net/url" "strconv" + "time" ) -templ inputForm(){ +templ inputForm() { {{ urlParams := ctx.Value("urlParams").(url.Values) user := ctx.Value("user").(models.User) }}
{user.Vorname + " " + user.Name}
-Überstunden
-4h 32min
+{ user.Vorname + " " + user.Name }
+Überstunden
+4h 32min
+{workDay.Day.Format("02.01.2006")}
-Arbeitszeit:
- if (workDay.RequiresAction()) { -Bitte anpassen
- }else { -{work}
- } -{pause}
+{ workDay.Day.Format("02.01.2006") }
+Arbeitszeit:
+ if (workDay.RequiresAction()) { +Bitte anpassen
+ } else { +{ work }
+ } +{ pause }
+Anzeigen