Mandanten, OAuth, Cleanup von der Main Methode.

This commit is contained in:
2025-08-20 16:08:58 +02:00
parent a6570d463a
commit 372dced0a1
16 changed files with 432 additions and 154 deletions

12
utils/applicationCtx.go Normal file
View File

@ -0,0 +1,12 @@
package utils
import (
"log/slog"
"gorm.io/gorm"
)
type Application struct {
Logger *slog.Logger
DB *gorm.DB
}

108
utils/authentication.go Normal file
View File

@ -0,0 +1,108 @@
package utils
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"os"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/adaptor"
"golang.org/x/oauth2"
)
func setCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
setCallbackCookieExp(w, r, name, value, int(time.Hour.Seconds()))
}
func setCallbackCookieExp(w http.ResponseWriter, r *http.Request, name, value string, maxAge int) {
c := &http.Cookie{
Name: name,
Value: value,
Path: "/",
MaxAge: maxAge,
Secure: r.TLS != nil,
HttpOnly: true,
}
http.SetCookie(w, c)
}
func CreateOIDCClient(ctx context.Context, app *fiber.App, logger *slog.Logger) {
provider, err := oidc.NewProvider(ctx, "https://keycloak.kocoder.xyz/realms/che")
if err != nil {
logger.Error("Error generating OIDC Provider. ", "error", err)
}
oauthConfig := oauth2.Config{
ClientID: os.Getenv("CLIENT_ID"),
ClientSecret: os.Getenv("CLIENT_SECRET"),
RedirectURL: "http://localhost:3000/api/auth/callback",
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile", "email"},
}
app.Get("/api/auth", adaptor.HTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
state, err := RandString(16)
if err != nil {
logger.Warn("Unable to create a state", "error", err)
http.Error(w, "Unable to create a state", http.StatusInternalServerError)
}
setCallbackCookie(w, r, "state", state)
http.Redirect(w, r, oauthConfig.AuthCodeURL(state), http.StatusFound)
}))
app.Get("/api/auth/callback", adaptor.HTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
state, err := r.Cookie("state")
if err != nil {
logger.Warn("State cookie not found", "error", err)
http.Error(w, "state not found", http.StatusBadRequest)
return
}
if r.URL.Query().Get("state") != state.Value {
logger.Warn("State cookie and header not matching", "error", err)
http.Error(w, "states not matching", http.StatusBadRequest)
return
}
oauth2Token, err := oauthConfig.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
logger.Warn("Failed to exchange token", "error", err)
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
userInfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
logger.Warn("failed to get userinfo", "error", err)
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
return
}
resp := struct {
Token *oauth2.Token
UserInfo *oidc.UserInfo
}{oauth2Token, userInfo}
data, err := json.MarshalIndent(resp, "", " ")
if err != nil {
logger.Warn("Failed to parse JSON", "error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
setCallbackCookieExp(w, r, "state", "", -1)
_, err = w.Write(data)
if err != nil {
logger.Error("Unable to send response", "error", err)
}
}))
}

49
utils/db.go Normal file
View File

@ -0,0 +1,49 @@
package utils
import (
"log/slog"
"git.kocoder.xyz/kocoded/vt/model"
"git.kocoder.xyz/kocoded/vt/query"
"gorm.io/driver/postgres"
"gorm.io/gen"
"gorm.io/gorm"
)
func SetupDatabase(dsn string, logger *slog.Logger) *gorm.DB {
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
logger.Error("Error connecting to the Database", "error", err)
}
err = db.SetupJoinTable(model.Ansprechpartner{}, "Firmen", model.FirmaAnsprechpartner{})
if err != nil {
logger.Error("Error setting up Join Tables", "error", err)
}
err = db.SetupJoinTable(model.Firma{}, "Ansprechpartner", model.FirmaAnsprechpartner{})
if err != nil {
logger.Error("Error setting up Join Tables", "error", err)
}
err = db.AutoMigrate(&model.Ansprechpartner{}, &model.FirmaAnsprechpartner{}, &model.Firma{})
if err != nil {
logger.Error("Error setting up Join Tables", "error", err)
}
g := gen.NewGenerator(gen.Config{
OutPath: "./query",
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode
})
// gormdb, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
g.UseDB(db) // reuse your gorm db
// Generate basic type-safe DAO API for struct `model.User` following conventions
g.ApplyBasic(model.Ansprechpartner{}, model.Dokument{}, model.Firma{}, model.Kalender{}, model.Kalendereintrag{}, model.Kostenstelle{}, model.Lager{}, model.Lagerplatz{}, model.Material{}, model.Nachricht{}, model.Projekt{}, model.Rechnung{}, model.Rechnungsposition{}, model.Scanobject{}, model.User{}, model.Zahlung{}, model.FirmaAnsprechpartner{})
// Generate the code
g.Execute()
query.SetDefault(db)
return db
}

33
utils/middleware.go Normal file
View File

@ -0,0 +1,33 @@
package utils
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/helmet"
"github.com/gofiber/fiber/v2/middleware/idempotency"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/monitor"
"github.com/gofiber/fiber/v2/middleware/pprof"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/fiber/v2/middleware/requestid"
"github.com/gofiber/fiber/v2/middleware/skip"
)
func RegisterMiddlewares(app *fiber.App) {
app.Use(requestid.New())
app.Use(compress.New())
app.Use(helmet.New())
app.Use(cors.New())
// app.Use(csrf.New())
// app.Use(healthcheck.New(healthcheck.Config{}))
app.Use(idempotency.New())
// app.Use(limiter.New())
app.Use(logger.New())
app.Use("/dbg/monitor", monitor.New())
app.Use(pprof.New())
app.Use(recover.New())
app.Use(skip.New(AddPaginationParams, func(c *fiber.Ctx) bool {
return c.Method() != fiber.MethodGet
}))
}

View File

@ -1,5 +1,12 @@
package utils
import (
"errors"
"strconv"
"github.com/gofiber/fiber/v2"
)
type OffsetPaginationError struct {
Page int
Pages int
@ -35,3 +42,27 @@ func (p *KeysetPaginationError) Error() string {
func NewKeysetPaginationError(key int, next int, previous int) error {
return &KeysetPaginationError{Key: key, NextKey: next, PreviousKey: previous}
}
func AddPaginationParams(c *fiber.Ctx) error {
err := c.Next()
if err != nil {
var offset *OffsetPaginationError
if errors.As(err, &offset) {
c.Append("X-Page", strconv.Itoa(offset.Page))
c.Append("X-Pages", strconv.Itoa(offset.Pages))
c.Append("X-Next-Page", strconv.Itoa(offset.NextPage))
c.Append("X-Last-Page", strconv.Itoa(offset.LastPage))
return nil
}
var keyset *KeysetPaginationError
if errors.As(err, &keyset) {
c.Append("X-Key", strconv.Itoa(keyset.Key))
c.Append("X-Previous-Key", strconv.Itoa(keyset.PreviousKey))
c.Append("X-Next-Key", strconv.Itoa(keyset.NextKey))
return nil
}
}
return err
}

15
utils/random.go Normal file
View File

@ -0,0 +1,15 @@
package utils
import (
"crypto/rand"
"encoding/base64"
"io"
)
func RandString(nByte int) (string, error) {
b := make([]byte, nByte)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
}