diff --git a/backend/src/database/facilities.go b/backend/src/database/facilities.go index 9e8712c71..ab7db14cb 100644 --- a/backend/src/database/facilities.go +++ b/backend/src/database/facilities.go @@ -73,6 +73,10 @@ func (db *DB) CreateFacility(facility *models.Facility) error { func (db *DB) UpdateFacility(facility *models.Facility, id uint) error { facility.ID = id + if err := Validate().Struct(facility); err != nil { + log.Error("Validation Error") + return NewDBError(err, "facilities") + } if err := db.Save(&facility).Error; err != nil { log.WithField("facility_id", facility.ID).Error("error updating facility name database/UpdateFacility") return newUpdateDBError(err, "facilities") diff --git a/backend/src/handlers/auth.go b/backend/src/handlers/auth.go index 5f541086d..af86ddf53 100644 --- a/backend/src/handlers/auth.go +++ b/backend/src/handlers/auth.go @@ -370,6 +370,14 @@ func (srv *Server) handleResetPassword(w http.ResponseWriter, r *http.Request, l } log.add("username_to_reset", user.Username) if claims.Role == models.SystemAdmin && form.FacilityName != "" { + if strings.TrimSpace(form.Timezone) == "" { + tx.Rollback() + return newBadRequestServiceError(errors.New("timezone is required"), "Timezone is required") + } + if _, err := time.LoadLocation(form.Timezone); err != nil { + tx.Rollback() + return newBadRequestServiceError(err, "Invalid timezone") + } facility := models.Facility{Name: form.FacilityName, Timezone: form.Timezone} if err := srv.WithUserContext(r).UpdateFacility(&facility, 1); err != nil { tx.Rollback() diff --git a/backend/src/models/facilities.go b/backend/src/models/facilities.go index 60c2ee990..f0ccbb19b 100644 --- a/backend/src/models/facilities.go +++ b/backend/src/models/facilities.go @@ -16,7 +16,7 @@ type FacilityWithStats struct { type Facility struct { DatabaseFields Name string `gorm:"size:255;not null" json:"name"` - Timezone string `gorm:"size:255;not null" json:"timezone" validate:"timezone"` + Timezone string `gorm:"size:255;not null" json:"timezone" validate:"required,timezone"` Users []User `gorm:"foreignKey:FacilityID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"-"` Programs []Program `json:"programs" gorm:"-"` diff --git a/frontend/src/components/forms/ChangePasswordForm.tsx b/frontend/src/components/forms/ChangePasswordForm.tsx index f79fd4cd8..6a8a6fa7b 100644 --- a/frontend/src/components/forms/ChangePasswordForm.tsx +++ b/frontend/src/components/forms/ChangePasswordForm.tsx @@ -24,6 +24,12 @@ interface Inputs { timezone: string; } +function getDefaultTimezone(): string { + const allowed = Object.values(Timezones) as string[]; + const browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone; + return allowed.includes(browserTz) ? browserTz : Timezones.CST; +} + export default function ChangePasswordForm() { const navigate = useNavigate(); const [errorMessage, setErrorMessage] = useState(''); @@ -36,7 +42,9 @@ export default function ChangePasswordForm() { reset, setValue, formState: { errors } - } = useForm(); + } = useForm({ + defaultValues: { timezone: getDefaultTimezone() } + }); useEffect(() => { const getUser = async () => { @@ -55,11 +63,14 @@ export default function ChangePasswordForm() { const password = useWatch({ control, name: 'password' }); const confirm = useWatch({ control, name: 'confirm' }); const facility = useWatch({ control, name: 'facility_name' }); + const timezone = useWatch({ control, name: 'timezone' }); const isLengthValid = password && password.length >= 8; const hasNumber = /\d/.test(password); const passwordsMatch = password === confirm; - const isValid = isLengthValid && hasNumber && passwordsMatch; + const validTimezone = !isFirstLogin || timezone; + const isValid = + isLengthValid && hasNumber && passwordsMatch && validTimezone; const validFacility = facility && facility.length > 2 && facility.trim().length > 2; @@ -207,9 +218,18 @@ export default function ChangePasswordForm() { + + {errors.timezone && ( +

+ {errors.timezone.message} +

+ )} )}