Compare commits

...

2 Commits

Author SHA1 Message Date
Alex
2ffd1f439f backend moved to separate directory
backend: deleted the old structure
2025-02-28 08:53:14 +01:00
Alex
ad599ae3f4 frontend: disabled button while processing password reset 2025-02-28 08:51:35 +01:00
92 changed files with 129 additions and 16 deletions

1
.gitignore vendored
View File

@@ -40,7 +40,6 @@ go.work
!go.sum
!go.mod
#!*.sql
!README.md
!LICENSE

View File

@@ -1,10 +1,10 @@
services:
app:
build: .
build: ./go-backend
container_name: carsharingBackend
ports:
- "8080:8080"
volumes:
- ./configs/config.json:/root/configs/config.json:ro
- ./data/db.sqlite3:/root/data/db.sqlite3
- ./templates:/root/templates:ro
- ./go-backend/configs/config.json:/root/configs/config.json:ro
- ./go-backend/data/db.sqlite3:/root/data/db.sqlite3
- ./go-backend/templates:/root/templates:ro

View File

@@ -127,7 +127,7 @@
</div>
<div class="button-container">
{#if isUpdating}
<SmallLoader width={30} message={'Aktualisiere...'} />
<SmallLoader width={30} message={$t('loading.updating')} />
{:else}
<button type="button" class="button-dark" on:click={() => dispatch('cancel')}>
{$t('cancel')}</button

View File

@@ -234,7 +234,7 @@
options={userStatusOptions}
readonly={role_id === 0}
/>
{#if localUser.role_id === 8}
{#if role_id === 8}
<InputField
name="user[role_id]"
type="select"
@@ -261,7 +261,7 @@
/>
<InputField
name="user[first_name]"
label={$t('first_name')}
label={$t('user.first_name')}
bind:value={localUser.first_name}
placeholder={$t('placeholder.first_name')}
required={true}
@@ -322,7 +322,7 @@
bind:value={localUser.city}
placeholder={$t('placeholder.city')}
/>
{#if localUser.role_id > 0}
{#if role_id > 0}
<InputField
name="user[notes]"
type="textarea"
@@ -528,7 +528,7 @@
</div>
<div class="button-container">
{#if isUpdating}
<SmallLoader width={30} message={'Aktualisiere...'} />
<SmallLoader width={30} message={$t('loading.updating')} />
{:else}
<button type="button" class="button-dark" on:click={() => dispatch('cancel')}>
{$t('cancel')}</button

View File

@@ -134,7 +134,9 @@ export default {
},
loading: {
user_data: 'Lade Nutzerdaten',
subscription_data: 'Lade Modelldaten'
subscription_data: 'Lade Modelldaten',
please_wait: 'Bitte warten...',
updating: 'Aktualisiere...'
},
dialog: {
user_deletion: 'Soll der Nutzer {firstname} {lastname} wirklich gelöscht werden?',

View File

@@ -1,15 +1,19 @@
<script>
import { applyAction, enhance } from '$app/forms';
import SmallLoader from '$lib/components/SmallLoader.svelte';
import { receive, send } from '$lib/utils/helpers';
import { t } from 'svelte-i18n';
/** @type {import('./$types').ActionData} */
export let form;
let loading = false;
/** @type {import('./$types').SubmitFunction} */
const handleRequestChange = async () => {
loading = true;
return async ({ result }) => {
await applyAction(result);
loading = false;
};
};
</script>
@@ -40,6 +44,10 @@
required
/>
</div>
<button class="button-dark">{$t('confirm')}</button>
{#if loading}
<SmallLoader width={30} message={$t('loading.please_wait')} />
{:else}
<button class="button-dark" disabled={loading}>{$t('confirm')}</button>
{/if}
</form>
</div>

View File

@@ -0,0 +1,56 @@
package main
import (
"GoMembership/internal/config"
"GoMembership/internal/database"
"GoMembership/internal/server"
"GoMembership/pkg/logger"
"context"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
logger.Info.Println("startup...")
config.LoadConfig()
err := database.Open(config.DB.Path, config.Recipients.AdminEmail)
if err != nil {
logger.Error.Fatalf("Couldn't init database: %v", err)
}
defer func() {
if err := database.Close(); err != nil {
logger.Error.Fatalf("Failed to close database: %v", err)
}
}()
go server.Run()
gracefulShutdown()
}
func gracefulShutdown() {
// Create a channel to listen for OS signals
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
// Block until a signal is received
<-stop
logger.Info.Println("Received shutdown signal")
// Create a context with a timeout for the shutdown process
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Call the server's shutdown function
if err := server.Shutdown(ctx); err != nil {
logger.Error.Fatalf("Error during Server shutdown: %#v", err)
} else {
logger.Info.Println("Server gracefully stopped")
}
}

View File

@@ -0,0 +1,37 @@
{
"site": {
"WebsiteTitle": "My Carsharing Site",
"BaseUrl": "https://domain.de",
"AllowOrigins": "https://domain.de"
},
"Environment": "dev",
"db": {
"Path": "data/db.sqlite3"
},
"smtp": {
"Host": "mail.server.com",
"User": "username",
"Password": "password",
"Port": 465
},
"templates": {
"MailPath": "templates/email",
"HTMLPath": "templates/html",
"StaticPath": "templates/css",
"LogoURI": "/assets/LOGO.png"
},
"auth": {
"APIKey": ""
},
"recipients": {
"ContactForm": "contacts@server.com",
"UserRegistration": "registration@server.com",
"AdminEmail": "admin@server.com"
},
"security": {
"RateLimits": {
"Limit": 1,
"Burst": 60
}
}
}

View File

@@ -0,0 +1 @@
Database Folder according to the project structure and the template configuration

View File

@@ -72,7 +72,9 @@ func (uc *UserController) ChangePassword(c *gin.Context) {
verification, err := uc.Service.VerifyUser(&input.Token, &constants.VerificationTypes.Password)
if err != nil || uint(userIDint) != verification.UserID {
if err == errors.ErrAlreadyVerified {
utils.RespondWithError(c, err, "User already verified", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.PasswordAlreadyChanged)
utils.RespondWithError(c, err, "User already changed password", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.PasswordAlreadyChanged)
} else if err.Error() == "record not found" {
utils.RespondWithError(c, err, "Couldn't find verification. This is most probably a outdated token.", http.StatusGone, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
} else {
utils.RespondWithError(c, err, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
}

View File

@@ -256,7 +256,7 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
func (uc *UserController) RegisterUser(c *gin.Context) {
var regData RegistrationData
logger.Error.Printf("registering user...")
if err := c.ShouldBindJSON(&regData); err != nil {
utils.HandleValidationError(c, err)
return

View File

@@ -13,7 +13,7 @@ func CORSMiddleware() gin.HandlerFunc {
logger.Info.Print("Applying CORS")
return cors.New(cors.Config{
AllowOrigins: strings.Split(config.Site.AllowOrigins, ","),
AllowMethods: []string{"GET", "POST", "PATCH", "PUT"},
AllowMethods: []string{"GET", "POST", "PATCH", "PUT", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "X-CSRF-Token"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,

View File

@@ -9,6 +9,7 @@ import (
)
func CSPMiddleware() gin.HandlerFunc {
logger.Error.Printf("applying CSP")
return func(c *gin.Context) {
policy := "default-src 'self'; " +
"script-src 'self' 'unsafe-inline'" +

View File

@@ -1,8 +1,13 @@
package middlewares
import "github.com/gin-gonic/gin"
import (
"GoMembership/pkg/logger"
"github.com/gin-gonic/gin"
)
func SecurityHeadersMiddleware() gin.HandlerFunc {
logger.Error.Printf("applying headers")
return func(c *gin.Context) {
c.Header("X-Frame-Options", "DENY")
c.Header("X-Content-Type-Options", "nosniff")

View File

@@ -14,6 +14,7 @@ type ValidationKeys struct {
InvalidUserID string
PasswordAlreadyChanged string
UserDisabled string
NoAuthToken string
}
type ValidationFields struct {
@@ -63,6 +64,7 @@ var Responses = struct {
Duplicate: "server.validation.duplicate",
UserDisabled: "server.validation.user_disabled",
PasswordAlreadyChanged: "server.validation.password_already_changed",
NoAuthToken: "server.error.no_auth_token",
},
Fields: ValidationFields{
General: "general",