school-timetracker/backend/errors.go
Patryk Hegenberg 3ac1947106 feat: improve app security and error handling
Improve overall app security by:
- using dynamic statements for all sql querries
- introducing environment variables for initial admin password
- introducing enironment variable for cors address
- improving error handling
2025-11-09 12:13:47 +01:00

205 lines
4.7 KiB
Go

package main
import (
"fmt"
"net/http"
)
type ErrorCode string
const (
// Authentifizierung
ErrInvalidCredentials ErrorCode = "INVALID_CREDENTIALS"
ErrUnauthorized ErrorCode = "UNAUTHORIZED"
ErrTokenExpired ErrorCode = "TOKEN_EXPIRED"
ErrAccessDenied ErrorCode = "ACCESS_DENIED"
// Validierung
ErrInvalidInput ErrorCode = "INVALID_INPUT"
ErrMissingField ErrorCode = "MISSING_FIELD"
ErrInvalidDateFormat ErrorCode = "INVALID_DATE_FORMAT"
ErrInvalidTimeFormat ErrorCode = "INVALID_TIME_FORMAT"
// Ressourcen
ErrNotFound ErrorCode = "NOT_FOUND"
ErrAlreadyExists ErrorCode = "ALREADY_EXISTS"
ErrCannotDelete ErrorCode = "CANNOT_DELETE"
ErrProtectedUser ErrorCode = "PROTECTED_USER"
ErrNoActiveSchool ErrorCode = "NO_ACTIVE_SCHOOL_YEAR"
// Datenbank
ErrDatabase ErrorCode = "DATABASE_ERROR"
ErrTransaction ErrorCode = "TRANSACTION_ERROR"
ErrQueryFailed ErrorCode = "QUERY_FAILED"
// Server
ErrInternal ErrorCode = "INTERNAL_ERROR"
ErrServiceUnavail ErrorCode = "SERVICE_UNAVAILABLE"
)
type AppError struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
UserMsg string `json:"user_message"`
HTTPStatus int `json:"-"`
Internal error `json:"-"`
}
func (e *AppError) Error() string {
if e.Internal != nil {
return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Internal)
}
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
func NewAppError(code ErrorCode, message, userMsg string, httpStatus int, internal error) *AppError {
return &AppError{
Code: code,
Message: message,
UserMsg: userMsg,
HTTPStatus: httpStatus,
Internal: internal,
}
}
func ErrInvalidCredentialsMsg() *AppError {
return NewAppError(
ErrInvalidCredentials,
"Invalid username or password",
"Benutzername oder Passwort ungültig",
http.StatusUnauthorized,
nil,
)
}
func ErrUnauthorizedMsg() *AppError {
return NewAppError(
ErrUnauthorized,
"Unauthorized access",
"Keine Berechtigung für diese Aktion",
http.StatusUnauthorized,
nil,
)
}
func ErrTokenExpiredMsg() *AppError {
return NewAppError(
ErrTokenExpired,
"Token has expired",
"Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an",
http.StatusUnauthorized,
nil,
)
}
func ErrAccessDeniedMsg() *AppError {
return NewAppError(
ErrAccessDenied,
"Access denied - admin privileges required",
"Zugriff verweigert. Administrator-Rechte erforderlich",
http.StatusForbidden,
nil,
)
}
func ErrInvalidInputMsg(field string) *AppError {
return NewAppError(
ErrInvalidInput,
fmt.Sprintf("Invalid input for field: %s", field),
fmt.Sprintf("Ungültige Eingabe im Feld: %s", field),
http.StatusBadRequest,
nil,
)
}
func ErrMissingFieldMsg(field string) *AppError {
return NewAppError(
ErrMissingField,
fmt.Sprintf("Required field missing: %s", field),
fmt.Sprintf("Pflichtfeld fehlt: %s", field),
http.StatusBadRequest,
nil,
)
}
func ErrNotFoundMsg(resource string) *AppError {
return NewAppError(
ErrNotFound,
fmt.Sprintf("%s not found", resource),
fmt.Sprintf("%s nicht gefunden", resource),
http.StatusNotFound,
nil,
)
}
func ErrAlreadyExistsMsg(resource string) *AppError {
return NewAppError(
ErrAlreadyExists,
fmt.Sprintf("%s already exists", resource),
fmt.Sprintf("%s existiert bereits", resource),
http.StatusConflict,
nil,
)
}
func ErrCannotDeleteMsg(resource, reason string) *AppError {
return NewAppError(
ErrCannotDelete,
fmt.Sprintf("Cannot delete %s: %s", resource, reason),
fmt.Sprintf("%s kann nicht gelöscht werden: %s", resource, reason),
http.StatusBadRequest,
nil,
)
}
func ErrProtectedUserMsg() *AppError {
return NewAppError(
ErrProtectedUser,
"Cannot modify protected admin user",
"Der Admin-Benutzer ist geschützt und kann nicht geändert werden",
http.StatusForbidden,
nil,
)
}
func ErrNoActiveSchoolYearMsg() *AppError {
return NewAppError(
ErrNoActiveSchool,
"No active school year configured",
"Kein aktives Schuljahr konfiguriert. Bitte aktivieren Sie ein Schuljahr",
http.StatusNotFound,
nil,
)
}
func ErrDatabaseMsg(internal error) *AppError {
return NewAppError(
ErrDatabase,
"Database operation failed",
"Ein Datenbankfehler ist aufgetreten. Bitte versuchen Sie es erneut",
http.StatusInternalServerError,
internal,
)
}
func ErrInternalMsg(internal error) *AppError {
return NewAppError(
ErrInternal,
"Internal server error",
"Ein interner Fehler ist aufgetreten. Bitte versuchen Sie es später erneut",
http.StatusInternalServerError,
internal,
)
}
type ErrorResponse struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
}
func (e *AppError) ToResponse() ErrorResponse {
return ErrorResponse{
Code: e.Code,
Message: e.UserMsg,
}
}