Merge pull request #17 from Fishwaldo/Auth

chore: More Refactoring and Auth Updates
This commit is contained in:
Justin Hammond 2022-08-18 12:09:28 +08:00 committed by GitHub
commit 6b47566749
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 555 additions and 377 deletions

1
.gitignore vendored
View file

@ -22,3 +22,4 @@ frontend/node_modules/
frontend/dist/ frontend/dist/
dist/ dist/
test.db-journal test.db-journal
logs/

View file

@ -7,4 +7,4 @@ import (
//go:generate npm run build //go:generate npm run build
//go:embed dist //go:embed dist
var FrontEndFiles embed.FS var FrontEndFiles embed.FS

1
go.mod
View file

@ -29,6 +29,7 @@ require (
github.com/spf13/viper v1.12.0 github.com/spf13/viper v1.12.0
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
gopkg.in/natefinch/lumberjack.v2 v2.0.0
) )
require ( require (

3
go.sum
View file

@ -58,6 +58,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
@ -1231,6 +1232,8 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -2,36 +2,37 @@ package app
import ( import (
"errors" "errors"
"context"
"github.com/Fishwaldo/mouthpiece/internal/db"
"github.com/Fishwaldo/mouthpiece/internal/errors"
"github.com/Fishwaldo/mouthpiece/internal/filter"
. "github.com/Fishwaldo/mouthpiece/internal/log" . "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/message" "github.com/Fishwaldo/mouthpiece/internal/message"
"github.com/Fishwaldo/mouthpiece/internal/user" "github.com/Fishwaldo/mouthpiece/internal/user"
"github.com/Fishwaldo/mouthpiece/internal/filter"
"github.com/Fishwaldo/mouthpiece/internal/db"
"github.com/Fishwaldo/mouthpiece/internal/errors"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"gorm.io/gorm" "gorm.io/gorm"
) )
type AppDetails struct { type AppDetails struct {
AppName string `doc:"Application Name" pattern:"^[a-z0-9]+$" gorm:"unique;uniqueIndex"` AppName string `doc:"Application Name" pattern:"^[a-z0-9]+$" gorm:"unique;uniqueIndex"`
Status string `doc:"Status of Application" enum:"Enabled,Disabled" default:"Enabled"` Status string `doc:"Status of Application" enum:"Enabled,Disabled" default:"Enabled"`
Description string `doc:"Description of Application"` Description string `doc:"Description of Application"`
Icon string `doc:"Icon of Application"` Icon string `doc:"Icon of Application"`
URL string `doc:"URL of Application"` URL string `doc:"URL of Application"`
} }
type ApplicationFilters struct { type ApplicationFilters struct {
gorm.Model `json:"-"` gorm.Model `json:"-"`
AppID uint `json:"-"` AppID uint `json:"-"`
Name string Name string
} }
type App struct { type App struct {
gorm.Model `json:"-"` gorm.Model `json:"-"`
AppDetails AppDetails
AssociatedUsers []*user.User `gorm:"many2many:app_user;"` AssociatedUsers []*user.User `gorm:"many2many:app_user;"`
Filters []ApplicationFilters Filters []ApplicationFilters
} }
func InitializeApps() { func InitializeApps() {
@ -39,50 +40,50 @@ func InitializeApps() {
db.Db.AutoMigrate(&ApplicationFilters{}) db.Db.AutoMigrate(&ApplicationFilters{})
} }
func GetApps() []App { func GetApps(ctx context.Context) []App {
var apps []App var apps []App
db.Db.Preload("AssociatedUsers").Preload("AssociatedUsers.TransportConfigs").Preload("Filters").Find(&apps) db.Db.WithContext(ctx).Preload("AssociatedUsers").Preload("AssociatedUsers.TransportConfigs").Preload("Filters").Find(&apps)
return apps return apps
} }
func FindApp(app_name string) (app *App, err error) { func FindApp(ctx context.Context, app_name string) (app *App, err error) {
tx := db.Db.Debug().Preload("AssociatedUsers").Preload("AssociatedUsers.TransportConfigs").Preload("Filters").First(&app, "app_name = ?", app_name) tx := db.Db.WithContext(ctx).Preload("AssociatedUsers").Preload("AssociatedUsers.TransportConfigs").Preload("Filters").First(&app, "app_name = ?", app_name)
Log.V(1).Info("Finding App", "App", app_name, "Result", tx, "app", app) Log.V(1).Info("Finding App", "App", app_name, "Result", tx, "app", app)
return app, tx.Error return app, tx.Error
} }
func AppExists(app_name string) (bool) { func AppExists(ctx context.Context, app_name string) bool {
var app App var app App
tx := db.Db.First(&app, "app_name = ?", app_name) tx := db.Db.WithContext(ctx).First(&app, "app_name = ?", app_name)
return tx.Error == nil return tx.Error == nil
} }
func CreateApp(app AppDetails) (newapp *App, err error) { func CreateApp(ctx context.Context, app AppDetails) (newapp *App, err error) {
newapp, err = FindApp(app.AppName); newapp, err = FindApp(ctx, app.AppName)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
Log.Info("Creating New App", "App", app) Log.Info("Creating New App", "App", app)
var dbApp App var dbApp App
copier.Copy(&dbApp, &app) copier.Copy(&dbApp, &app)
adminuser, _ := user.GetUser("admin@example.com") adminuser, _ := user.GetUser(ctx, "admin@example.com")
dbApp.AssociatedUsers = append(dbApp.AssociatedUsers, adminuser) dbApp.AssociatedUsers = append(dbApp.AssociatedUsers, adminuser)
normaluser, _ := user.GetUser("user@example.com") normaluser, _ := user.GetUser(ctx, "user@example.com")
dbApp.AssociatedUsers = append(dbApp.AssociatedUsers, normaluser) dbApp.AssociatedUsers = append(dbApp.AssociatedUsers, normaluser)
if (filter.FindFilter("CopyShortMessage") != nil) { if filter.FindFilter("CopyShortMessage") != nil {
dbApp.Filters = append(dbApp.Filters, ApplicationFilters{Name: "CopyShortMessage"}) dbApp.Filters = append(dbApp.Filters, ApplicationFilters{Name: "CopyShortMessage"})
} }
if (filter.FindFilter("FindSeverity") != nil) { if filter.FindFilter("FindSeverity") != nil {
dbApp.Filters = append(dbApp.Filters, ApplicationFilters{Name: "FindSeverity"}) dbApp.Filters = append(dbApp.Filters, ApplicationFilters{Name: "FindSeverity"})
} }
result := db.Db.Create(&dbApp) result := db.Db.WithContext(ctx).Create(&dbApp)
if result.Error != nil { if result.Error != nil {
return newapp, result.Error return newapp, result.Error
} }
return FindApp(app.AppName) return FindApp(ctx, app.AppName)
} }
Log.Error(err, "App Already Exists", "App", newapp) Log.Error(err, "App Already Exists", "App", newapp)
return newapp, mperror.ErrAppExists return newapp, mperror.ErrAppExists
} }
func (app App) ProcessMessage(msg *msg.Message) (error) { func (app App) ProcessMessage(ctx context.Context, msg *msg.Message) error {
Log.V(1).Info("App Processing Message", "App", app.AppName, "MessageID", msg.ID) Log.V(1).Info("App Processing Message", "App", app.AppName, "MessageID", msg.ID)
/* populate Message Fields with App Data */ /* populate Message Fields with App Data */
msg.Body.Fields["app_description"] = app.Description msg.Body.Fields["app_description"] = app.Description
@ -90,10 +91,10 @@ func (app App) ProcessMessage(msg *msg.Message) (error) {
msg.Body.Fields["app_url"] = app.URL msg.Body.Fields["app_url"] = app.URL
for _, appfilter := range app.Filters { for _, appfilter := range app.Filters {
flt := filter.FindFilter(appfilter.Name) flt := filter.FindFilter(appfilter.Name)
if (flt != nil) { if flt != nil {
Log.V(1).Info("App Processing Message with Filter", "Filter", appfilter) Log.V(1).Info("App Processing Message with Filter", "Filter", appfilter)
ok, _ := flt.ProcessMessage(msg); ok, _ := flt.ProcessMessage(ctx, msg)
if (!ok) { if !ok {
Log.Info("App Filter Blocked Message", "App", app.AppName, "Filter", appfilter, "Message", msg) Log.Info("App Filter Blocked Message", "App", app.AppName, "Filter", appfilter, "Message", msg)
return nil return nil
} }
@ -102,7 +103,7 @@ func (app App) ProcessMessage(msg *msg.Message) (error) {
} }
} }
for _, user := range app.AssociatedUsers { for _, user := range app.AssociatedUsers {
user.ProcessMessage(*msg) user.ProcessMessage(ctx, *msg)
} }
return nil return nil
} }

42
internal/app/restapi.go Normal file
View file

@ -0,0 +1,42 @@
package app
import (
"net/http"
"github.com/Fishwaldo/mouthpiece/internal/auth"
"github.com/danielgtaylor/huma"
"github.com/danielgtaylor/huma/responses"
)
func InitializeAppRestAPI(res *huma.Resource) error {
auth.AuthService.AddResourceURL("/v1/apps/", "apigroup:apps")
appapi := res.SubResource("/apps/")
appapi.Get("get-apps", "Get A List of Applications",
responses.OK().ContentType("application/json"),
responses.OK().Headers("Set-Cookie"),
responses.OK().Model([]App{}),
).Run(func(ctx huma.Context) {
ctx.WriteModel(http.StatusOK, GetApps(ctx))
})
appapi.Put("create-app", "Create a Application",
responses.OK().ContentType("application/json"),
responses.OK().Headers("Set-Cookie"),
responses.OK().Model(&App{}),
responses.NotAcceptable().ContentType("application/json"),
responses.NotAcceptable().Headers("Set-Cookie"),
).Run(func(ctx huma.Context, input struct {
Body AppDetails
}) {
if app, err := CreateApp(ctx, input.Body); err != nil {
ctx.WriteError(http.StatusNotAcceptable, "Database Error", err)
} else {
ctx.WriteModel(http.StatusOK, app)
}
})
return nil
}

View file

@ -6,14 +6,17 @@ import (
"context" "context"
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"io/fs"
"net/http" "net/http"
"os" "os"
// "strings"
// "strings"
dbauth "github.com/Fishwaldo/mouthpiece/internal/auth/db" dbauth "github.com/Fishwaldo/mouthpiece/internal/auth/db"
telegramauth "github.com/Fishwaldo/mouthpiece/internal/auth/telegram" telegramauth "github.com/Fishwaldo/mouthpiece/internal/auth/telegram"
"github.com/Fishwaldo/mouthpiece/internal/db" "github.com/Fishwaldo/mouthpiece/internal/db"
. "github.com/Fishwaldo/mouthpiece/internal/log" . "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/go-logr/logr"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -23,6 +26,7 @@ import (
"github.com/go-pkgz/auth/token" "github.com/go-pkgz/auth/token"
"github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/util" "github.com/casbin/casbin/v2/util"
//"github.com/casbin/casbin/v2/log" //"github.com/casbin/casbin/v2/log"
@ -38,21 +42,22 @@ type Auth struct {
} }
var AuthService *Auth var AuthService *Auth
var alog *authLogger
var llog logr.Logger
type AuthLogger struct { type authLogger struct {
} }
var AL *AuthLogger func (AL authLogger) Logf(format string, args ...interface{}) {
llog.V(1).Info("Authentication", "message", fmt.Sprintf(format, args...))
func (AL AuthLogger) Logf(format string, args ...interface{}) {
Log.WithName("Auth").Info("Authentication", "message", fmt.Sprintf(format, args...))
} }
type AuthConfig struct { type AuthConfig struct {
CredChecker func(username string, password string) (ok bool, err error) CredChecker func(username string, password string) (ok bool, err error)
MapClaimsToUser token.ClaimsUpdFunc MapClaimsToUser token.ClaimsUpdFunc
Validator token.ValidatorFunc Validator token.ValidatorFunc
Host string Host string
ConfigDir fs.FS
} }
func init() { func init() {
@ -95,15 +100,16 @@ func customGitHubProvider() (cred pkauth.Client, ch provider.CustomHandlerOpt) {
return cred, ch return cred, ch
} }
func InitAuth(Config AuthConfig) { func InitAuth(Config AuthConfig) {
AL = &AuthLogger{} llog = Log.WithName("Auth")
alog = &authLogger{}
AuthService = &Auth{} AuthService = &Auth{}
var avatarcachedir string var avatarcachedir string
if viper.IsSet("auth.avatar.cachedir") { if viper.IsSet("auth.avatar.cachedir") {
avatarcachedir = viper.GetString("auth.avatar.cachedir") avatarcachedir = viper.GetString("auth.avatar.cachedir")
} else { } else {
avatarcachedir, _ = os.MkdirTemp("", "mouthpiece_avatar") avatarcachedir, _ = os.MkdirTemp("", "mouthpiece_avatar")
} }
options := pkauth.Opts{ options := pkauth.Opts{
@ -114,20 +120,20 @@ func InitAuth(Config AuthConfig) {
CookieDuration: time.Hour * 24, // cookie fine to keep for long time CookieDuration: time.Hour * 24, // cookie fine to keep for long time
DisableXSRF: true, // don't disable XSRF in real-life applications! DisableXSRF: true, // don't disable XSRF in real-life applications!
Issuer: "mouthpiece", // part of token, just informational Issuer: "mouthpiece", // part of token, just informational
URL: Config.Host, // base url of the protected service URL: Config.Host, // base url of the protected service
//AdminPasswd: "password", // admin password //AdminPasswd: "password", // admin password
AvatarStore: avatar.NewLocalFS(avatarcachedir), // stores avatars locally AvatarStore: avatar.NewLocalFS(avatarcachedir), // stores avatars locally
AvatarResizeLimit: 200, // resizes avatars to 200x200 AvatarResizeLimit: 200, // resizes avatars to 200x200
ClaimsUpd: token.ClaimsUpdFunc(Config.MapClaimsToUser), ClaimsUpd: token.ClaimsUpdFunc(Config.MapClaimsToUser),
Validator: Config.Validator, Validator: Config.Validator,
Logger: AL, // optional logger for auth library Logger: alog, // optional logger for auth library
UseGravatar: true, // for verified provider use gravatar service UseGravatar: true, // for verified provider use gravatar service
} }
// create auth service // create auth service
AuthService.Service = pkauth.NewService(options) AuthService.Service = pkauth.NewService(options)
if viper.GetBool("auth.dev.enabled") { if viper.GetBool("auth.dev.enabled") {
Log.Info("Auth Dev Mode Enabled!") llog.Info("Auth Dev Mode Enabled!")
AuthService.Service.AddProvider("dev", "", "") AuthService.Service.AddProvider("dev", "", "")
// run dev/test oauth2 server on :8084 // run dev/test oauth2 server on :8084
go func() { go func() {
@ -136,7 +142,7 @@ func InitAuth(Config AuthConfig) {
return "admin@example.com" return "admin@example.com"
} }
if err != nil { if err != nil {
Log.Error(err, "[PANIC] failed to start dev oauth2 server") llog.Error(err, "[PANIC] failed to start dev oauth2 server")
} }
devAuthServer.Run(context.Background()) devAuthServer.Run(context.Background())
@ -144,24 +150,24 @@ func InitAuth(Config AuthConfig) {
} }
if viper.GetBool("auth.github.enabled") { if viper.GetBool("auth.github.enabled") {
if !viper.IsSet("auth.github.client_id") { if !viper.IsSet("auth.github.client_id") {
Log.Error(nil, "Github auth is enabled but client_id is not set") llog.Error(nil, "Github auth is enabled but client_id is not set")
} else { } else {
if !viper.IsSet("auth.github.client_secret") { if !viper.IsSet("auth.github.client_secret") {
Log.Error(nil, "Github auth is enabled but client_secret is not set") llog.Error(nil, "Github auth is enabled but client_secret is not set")
} else { } else {
Log.Info("Auth Github Enabled!") llog.Info("Auth Github Enabled!")
gcred, gch := customGitHubProvider() gcred, gch := customGitHubProvider()
AuthService.Service.AddCustomProvider("github", gcred, gch) AuthService.Service.AddCustomProvider("github", gcred, gch)
} }
} }
} }
if viper.GetBool("auth.microsoft.enabled") { if viper.GetBool("auth.microsoft.enabled") {
Log.Info("Auth Microsoft Enabled!") llog.Info("Auth Microsoft Enabled!")
AuthService.Service.AddProvider("microsoft", os.Getenv("AEXMPL_MS_APIKEY"), os.Getenv("AEXMPL_MS_APISEC")) AuthService.Service.AddProvider("microsoft", os.Getenv("AEXMPL_MS_APIKEY"), os.Getenv("AEXMPL_MS_APISEC"))
} }
/* direct loging (username/password) is always handled */ /* direct loging (username/password) is always handled */
dbprovider := dbauth.DirectHandler{ dbprovider := dbauth.DirectHandler{
L: AL, L: alog,
ProviderName: "direct", ProviderName: "direct",
Issuer: options.Issuer, Issuer: options.Issuer,
TokenService: AuthService.Service.TokenService(), TokenService: AuthService.Service.TokenService(),
@ -171,7 +177,7 @@ func InitAuth(Config AuthConfig) {
AuthService.Service.AddCustomHandler(dbprovider) AuthService.Service.AddCustomHandler(dbprovider)
if viper.GetBool("auth.email.enabled") { if viper.GetBool("auth.email.enabled") {
Log.Info("Auth Email Enabled!") llog.Info("Auth Email Enabled!")
AuthService.Service.AddVerifProvider("email", AuthService.Service.AddVerifProvider("email",
"To confirm use {{.Token}}\nor follow http://arm64-1.dmz.dynam.ac:8888/auth/email/login?token={{.Token}}", "To confirm use {{.Token}}\nor follow http://arm64-1.dmz.dynam.ac:8888/auth/email/login?token={{.Token}}",
provider.SenderFunc(func(address string, text string) error { // sender just prints token provider.SenderFunc(func(address string, text string) error { // sender just prints token
@ -183,14 +189,14 @@ func InitAuth(Config AuthConfig) {
if viper.GetBool("auth.telegram.enabled") { if viper.GetBool("auth.telegram.enabled") {
if viper.IsSet("auth.telegram.token") { if viper.IsSet("auth.telegram.token") {
Log.Info("Auth Telegram Enabled!") llog.Info("Auth Telegram Enabled!")
// add telegram provider // add telegram provider
telegram := telegramauth.TelegramHandler{ telegram := telegramauth.TelegramHandler{
ProviderName: "telegram", ProviderName: "telegram",
ErrorMsg: "❌ Invalid auth request. Please try clicking link again.", ErrorMsg: "❌ Invalid auth request. Please try clicking link again.",
SuccessMsg: "✅ You have successfully authenticated!", SuccessMsg: "✅ You have successfully authenticated!",
Telegram: telegramauth.NewTelegramAPI(viper.GetString("auth.telegram.token"), http.DefaultClient), Telegram: telegramauth.NewTelegramAPI(viper.GetString("auth.telegram.token"), http.DefaultClient),
L: AL, L: alog,
TokenService: AuthService.Service.TokenService(), TokenService: AuthService.Service.TokenService(),
AvatarSaver: AuthService.Service.AvatarProxy(), AvatarSaver: AuthService.Service.AvatarProxy(),
} }
@ -198,40 +204,49 @@ func InitAuth(Config AuthConfig) {
go func() { go func() {
err := telegram.Run(context.Background()) err := telegram.Run(context.Background())
if err != nil { if err != nil {
Log.Error(err, "[PANIC] failed to start telegram") llog.Error(err, "[PANIC] failed to start telegram")
} }
}() }()
AuthService.Service.AddCustomHandler(&telegram) AuthService.Service.AddCustomHandler(&telegram)
} else { } else {
Log.Error(nil, "Telegram auth is enabled but token is not set") llog.Error(nil, "Telegram auth is enabled but token is not set")
} }
} }
InitCasbin() InitCasbin(Config)
Log.Info("Auth service started") llog.Info("Auth service started")
} }
func InitCasbin() { func InitCasbin(config AuthConfig) {
cdb, err := gormadapter.NewAdapterByDB(db.Db) cdb, err := gormadapter.NewAdapterByDB(db.Db)
if err != nil { if err != nil {
Log.Error(err, "Failed to Setup Casbin Auth Adapter") llog.Error(err, "Failed to Setup Casbin Auth Adapter")
} }
AuthService.AuthEnforcer, err = casbin.NewEnforcer("config/auth_model.conf", cdb) casbinmodel, err := fs.ReadFile(config.ConfigDir, "config/auth_model.conf")
if err != nil { if err != nil {
Log.Error(err, "Failed to setup Casbin") llog.Error(err, "Failed to read casbin model")
} }
m, err := model.NewModelFromString(string(casbinmodel))
if err != nil {
llog.Error(err, "Failed to parse casbin model")
}
AuthService.AuthEnforcer, err = casbin.NewEnforcer(m, cdb)
if err != nil {
llog.Error(err, "Failed to setup Casbin")
}
AuthService.AuthEnforcer.EnableLog(viper.GetBool("auth.debug")) AuthService.AuthEnforcer.EnableLog(viper.GetBool("auth.debug"))
AuthService.AuthEnforcer.EnableAutoSave(true) AuthService.AuthEnforcer.EnableAutoSave(true)
AuthService.AuthEnforcer.SetRoleManager(defaultrolemanager.NewRoleManager(10)) AuthService.AuthEnforcer.SetRoleManager(defaultrolemanager.NewRoleManager(10))
if err := AuthService.AuthEnforcer.LoadModel(); err != nil { //if err := AuthService.AuthEnforcer.LoadModel(); err != nil {
Log.Error(err, "Failed to load Casbin model") // llog.Error(err, "Failed to load Casbin model")
} //}
if err := AuthService.AuthEnforcer.LoadPolicy(); err != nil { if err := AuthService.AuthEnforcer.LoadPolicy(); err != nil {
Log.Error(err, "Failed to Load Casbin Policy") llog.Error(err, "Failed to Load Casbin Policy")
} }
if !AuthService.AuthEnforcer.AddNamedMatchingFunc("g2", "KeyMatch3", util.KeyMatch3) { if !AuthService.AuthEnforcer.AddNamedMatchingFunc("g2", "KeyMatch3", util.KeyMatch3) {
Log.Error(nil, "Failed to add g2 matching function") llog.Error(nil, "Failed to add g2 matching function")
} }
AuthService.AuthEnforcer.AddPolicy("role:admin", "apigroup:apps", "PUT") AuthService.AuthEnforcer.AddPolicy("role:admin", "apigroup:apps", "PUT")
AuthService.AuthEnforcer.AddPolicy("role:user", "apigroup:apps", "GET") AuthService.AuthEnforcer.AddPolicy("role:user", "apigroup:apps", "GET")
@ -247,21 +262,20 @@ func InitCasbin() {
// AuthService.AuthEnforcer.AddRoleForUser("admin", "role:admin") // AuthService.AuthEnforcer.AddRoleForUser("admin", "role:admin")
// AuthService.AuthEnforcer.AddRoleForUser("dev_user", "role:admin") // AuthService.AuthEnforcer.AddRoleForUser("dev_user", "role:admin")
p, _ := AuthService.AuthEnforcer.GetImplicitPermissionsForUser("admin@example.com")
fmt.Printf("Admin Permissions: %+v\n", p)
AuthService.AuthEnforcer.SavePolicy() AuthService.AuthEnforcer.SavePolicy()
rm := AuthService.AuthEnforcer.GetPolicy() rm := AuthService.AuthEnforcer.GetPolicy()
Log.Info("Casbin Policy", "policy", rm) llog.Info("Casbin Policy", "policy", rm)
Log.Info("Casbin User Roles", "Roles", AuthService.AuthEnforcer.GetGroupingPolicy()) llog.Info("Casbin User Roles", "Roles", AuthService.AuthEnforcer.GetGroupingPolicy())
Log.Info("Casbin API Groups", "API Groups", AuthService.AuthEnforcer.GetNamedGroupingPolicy("g2")) llog.Info("Casbin API Groups", "API Groups", AuthService.AuthEnforcer.GetNamedGroupingPolicy("g2"))
} }
func (a *Auth) AddResourceURL(url string, group string) bool { func (a *Auth) AddResourceURL(url string, group string) bool {
ok, err := a.AuthEnforcer.AddNamedGroupingPolicy("g2", url, group) ok, err := a.AuthEnforcer.AddNamedGroupingPolicy("g2", url, group)
if err != nil { if err != nil {
Log.Error(err, "Failed to add g2 policy", "url", url, "group", group) llog.Error(err, "Failed to add g2 policy", "url", url, "group", group)
} }
return ok return ok
} }

View file

@ -1,8 +1,8 @@
package dbauth package dbauth
import ( import (
"crypto/sha1" //nolint
"crypto/rand" "crypto/rand"
"crypto/sha1" //nolint
"encoding/json" "encoding/json"
"fmt" "fmt"
"mime" "mime"
@ -13,8 +13,8 @@ import (
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/go-pkgz/auth/logger" "github.com/go-pkgz/auth/logger"
"github.com/go-pkgz/auth/token"
"github.com/go-pkgz/auth/provider" "github.com/go-pkgz/auth/provider"
"github.com/go-pkgz/auth/token"
) )
const ( const (
@ -22,7 +22,6 @@ const (
MaxHTTPBodySize = 1024 * 1024 MaxHTTPBodySize = 1024 * 1024
) )
type ICredChecker func(user string, password string) (ok bool, err error) type ICredChecker func(user string, password string) (ok bool, err error)
// DirectHandler implements non-oauth2 provider authorizing user in traditional way with storage // DirectHandler implements non-oauth2 provider authorizing user in traditional way with storage
@ -83,8 +82,8 @@ func (p DirectHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
userID := p.ProviderName + "_" + token.HashID(sha1.New(), creds.User) userID := p.ProviderName + "_" + token.HashID(sha1.New(), creds.User)
u := token.User{ u := token.User{
Name: creds.User, Name: creds.User,
ID: userID, ID: userID,
Email: creds.User, Email: creds.User,
} }
u, err = setAvatar(p.AvatarSaver, u, &http.Client{Timeout: 5 * time.Second}) u, err = setAvatar(p.AvatarSaver, u, &http.Client{Timeout: 5 * time.Second})
@ -196,4 +195,4 @@ func randToken() (string, error) {
return "", fmt.Errorf("can't write randoms to sha1: %w", err) return "", fmt.Errorf("can't write randoms to sha1: %w", err)
} }
return fmt.Sprintf("%x", s.Sum(nil)), nil return fmt.Sprintf("%x", s.Sum(nil)), nil
} }

View file

@ -1,8 +1,8 @@
package telegramauth package telegramauth
import ( import (
"context" "context"
"crypto/rand"
"crypto/sha1" "crypto/sha1"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -13,7 +13,6 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"crypto/rand"
"github.com/go-pkgz/repeater" "github.com/go-pkgz/repeater"
"github.com/go-pkgz/rest" "github.com/go-pkgz/rest"
@ -146,7 +145,7 @@ func (th *TelegramHandler) ProcessUpdate(ctx context.Context, textUpdate string)
th.requests.data = make(map[string]tgAuthRequest) th.requests.data = make(map[string]tgAuthRequest)
} }
th.requests.Unlock() th.requests.Unlock()
fmt.Printf("Processing update: %s\n", textUpdate) fmt.Printf("Processing update: %s\n", textUpdate)
var updates telegramUpdate var updates telegramUpdate
@ -511,4 +510,4 @@ func (tg *tgAPI) parseError(r io.Reader, statusCode int) error {
return fmt.Errorf("unexpected telegram API status code %d", statusCode) return fmt.Errorf("unexpected telegram API status code %d", statusCode)
} }
return fmt.Errorf("unexpected telegram API status code %d, error: %q", statusCode, tgErr.Description) return fmt.Errorf("unexpected telegram API status code %d, error: %q", statusCode, tgErr.Description)
} }

View file

@ -5,30 +5,30 @@ import (
) )
type OAuthConfig struct { type OAuthConfig struct {
ClientID string `json:"clientid" doc:"OAuth Client ID"` ClientID string `json:"clientid" doc:"OAuth Client ID"`
} }
type FEConfig struct { type FEConfig struct {
OAuthProviders map[string]OAuthConfig `json:"oauthproviders" doc:"Provider OAuth Config for Frontend"` OAuthProviders map[string]OAuthConfig `json:"oauthproviders" doc:"Provider OAuth Config for Frontend"`
} }
func GetFEConfig() (config *FEConfig) { func GetFEConfig() (config *FEConfig) {
config = &FEConfig{} config = &FEConfig{}
config.OAuthProviders = make(map[string]OAuthConfig) config.OAuthProviders = make(map[string]OAuthConfig)
if (viper.GetBool("auth.github.enabled")) { if viper.GetBool("auth.github.enabled") {
config.OAuthProviders["github"] = OAuthConfig{ config.OAuthProviders["github"] = OAuthConfig{
ClientID: viper.GetString("auth.github.client_id"), ClientID: viper.GetString("auth.github.client_id"),
} }
} }
if (viper.GetBool("auth.google.enabled")) { if viper.GetBool("auth.google.enabled") {
config.OAuthProviders["google"] = OAuthConfig{ config.OAuthProviders["google"] = OAuthConfig{
ClientID: viper.GetString("auth.google.client_id"), ClientID: viper.GetString("auth.google.client_id"),
} }
} }
if (viper.GetBool("auth.dev.enabled")) { if viper.GetBool("auth.dev.enabled") {
config.OAuthProviders["dev"] = OAuthConfig{ config.OAuthProviders["dev"] = OAuthConfig{
ClientID: "123456", ClientID: "123456",
} }
} }
return config return config
} }

View file

@ -5,7 +5,7 @@ import (
) )
var ( var (
ErrAppExists = errors.New("App Already Exists") ErrAppExists = errors.New("App Already Exists")
ErrAppNotFound = errors.New("App Not Found") ErrAppNotFound = errors.New("App Not Found")
ErrUserNotFound = errors.New("User Not Found") ErrUserNotFound = errors.New("User Not Found")
) )

View file

@ -4,15 +4,16 @@ import (
"io/fs" "io/fs"
"path/filepath" "path/filepath"
"strings" "strings"
"context"
//"io/ioutil" //"io/ioutil"
"embed" "embed"
"fmt" "fmt"
. "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/message"
"github.com/Fishwaldo/mouthpiece/internal/db" "github.com/Fishwaldo/mouthpiece/internal/db"
"github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/message"
"github.com/go-logr/logr"
"github.com/skx/evalfilter/v2" "github.com/skx/evalfilter/v2"
"github.com/skx/evalfilter/v2/object" "github.com/skx/evalfilter/v2/object"
"gorm.io/gorm" "gorm.io/gorm"
@ -21,9 +22,11 @@ import (
//go:embed scripts //go:embed scripts
var ScriptFiles embed.FS var ScriptFiles embed.FS
type FilterType int type FilterType int
var llog logr.Logger
const ( const (
AppFilter = iota AppFilter = iota
UserFilter UserFilter
@ -35,14 +38,14 @@ func (ft FilterType) String() string {
} }
type Filter struct { type Filter struct {
gorm.Model `json:"-"` gorm.Model `json:"-"`
Name string Name string
Content string Content string
Type FilterType Type FilterType
Enabled bool Enabled bool
script *evalfilter.Eval `gorm:"-" json:"-"` script *evalfilter.Eval `gorm:"-" json:"-"`
ok bool `gorm:"-"` ok bool `gorm:"-"`
processedMessage *msg.Message `gorm:"-" json:"-"` processedMessage *msg.Message `gorm:"-" json:"-"`
} }
var Filters []*Filter var Filters []*Filter
@ -72,34 +75,35 @@ func loadScriptFiles(files []string, scripttype FilterType) {
var flt *Filter var flt *Filter
tx := db.Db.Where("name = ? and type = ?", trimFileExtension(filepath.Base(script)), scripttype).First(&flt) tx := db.Db.Where("name = ? and type = ?", trimFileExtension(filepath.Base(script)), scripttype).First(&flt)
if tx.RowsAffected == 0 { if tx.RowsAffected == 0 {
Log.Info("Reading Filter Script from Filesystem", "type", scripttype, "filter", trimFileExtension(filepath.Base(script))) llog.V(1).Info("Reading Filter Script from Filesystem", "type", scripttype, "filter", trimFileExtension(filepath.Base(script)))
content, err := fs.ReadFile(ScriptFiles, script) content, err := fs.ReadFile(ScriptFiles, script)
if err != nil { if err != nil {
Log.Error(err, "Failed to read Filter Script File", "filename", script) llog.Error(err, "Failed to read Filter Script File", "filename", script)
continue continue
} }
// //
// Create an evalfilter, with the script inside it. // Create an evalfilter, with the script inside it.
// //
flt = &Filter{ flt = &Filter{
Name: trimFileExtension(filepath.Base(script)), Name: trimFileExtension(filepath.Base(script)),
Content: string(content), Content: string(content),
Type: scripttype, Type: scripttype,
} }
} else { } else {
Log.Info("Loading Filter Script from Databse", "type", scripttype, "filter", flt.Name) llog.V(1).Info("Loading Filter Script from Databse", "type", scripttype, "filter", flt.Name)
} }
if err := flt.SetupEvalFilter(); err == nil { if err := flt.SetupEvalFilter(); err == nil {
Log.Info("Loaded Filter Script ", "type", scripttype, "filter", flt.Name) llog.Info("Loaded Filter Script ", "type", scripttype, "filter", flt.Name)
Filters = append(Filters, flt) Filters = append(Filters, flt)
db.Db.Save(flt) db.Db.Save(flt)
} else { } else {
Log.Error(err, "Failed to load Filter Script", "type", scripttype, "filter", flt.Name) llog.Error(err, "Failed to load Filter Script", "type", scripttype, "filter", flt.Name)
} }
} }
} }
func InitFilter() { func InitFilter() {
llog = log.Log.WithName("filter")
db.Db.AutoMigrate(&Filter{}) db.Db.AutoMigrate(&Filter{})
Filters = make([]*Filter, 0) Filters = make([]*Filter, 0)
scripts := filterFiles("scripts/apps", ".scp") scripts := filterFiles("scripts/apps", ".scp")
@ -144,13 +148,13 @@ func (ev *Filter) fnPrintf(args []object.Object) object.Object {
// Call the helper // Call the helper
out := fmt.Sprintf(fs, fmtArgs...) out := fmt.Sprintf(fs, fmtArgs...)
Log.Info("Filter Script Output", "filter", ev.Name, "output", out) llog.Info("Filter Script Output", "filter", ev.Name, "output", out)
return &object.Void{} return &object.Void{}
} }
func (ev *Filter) fnPrint(args []object.Object) object.Object { func (ev *Filter) fnPrint(args []object.Object) object.Object {
for _, e := range args { for _, e := range args {
Log.Info("Filter Script Output", "filter", ev.Name, "Output", e.Inspect()) llog.Info("Filter Script Output", "filter", ev.Name, "Output", e.Inspect())
} }
return &object.Void{} return &object.Void{}
} }
@ -171,78 +175,79 @@ func (ev *Filter) fnSetField(args []object.Object) object.Object {
arg := args[0].ToInterface() arg := args[0].ToInterface()
Log.Info("Setting Field Value", "filter", ev.Name, "field", fld, "value", arg) llog.Info("Setting Field Value", "filter", ev.Name, "field", fld, "value", arg)
ev.processedMessage.Body.Fields[fld] = arg ev.processedMessage.Body.Fields[fld] = arg
return &object.Void{} return &object.Void{}
} }
func (ev *Filter) fnClearField(args[] object.Object) object.Object { func (ev *Filter) fnClearField(args []object.Object) object.Object {
if (len(args) != 1) { if len(args) != 1 {
return &object.Null{} return &object.Null{}
} }
// Type-check // Type-check
if (args[0].Type() != object.STRING) { if args[0].Type() != object.STRING {
return &object.Null{} return &object.Null{}
} }
fld := args[0].(*object.String).Value fld := args[0].(*object.String).Value
Log.Info("Clearing Field Value", "filter", ev.Name, "field", fld) llog.Info("Clearing Field Value", "filter", ev.Name, "field", fld)
if _, ok := ev.processedMessage.Body.Fields[fld]; ok { if _, ok := ev.processedMessage.Body.Fields[fld]; ok {
delete(ev.processedMessage.Body.Fields, fld) delete(ev.processedMessage.Body.Fields, fld)
} else { } else {
Log.Info("Field Not Found", "filter", ev.Name, "field", fld) llog.Info("Field Not Found", "filter", ev.Name, "field", fld)
} }
return &object.Void{} return &object.Void{}
} }
func (ev *Filter) fnSetShortMessage(arg[] object.Object) object.Object { func (ev *Filter) fnSetShortMessage(arg []object.Object) object.Object {
if (len(arg) != 1) { if len(arg) != 1 {
return &object.Null{} return &object.Null{}
} }
// Type-check // Type-check
if (arg[0].Type() != object.STRING) { if arg[0].Type() != object.STRING {
return &object.Null{} return &object.Null{}
} }
msg := arg[0].(*object.String).Value msg := arg[0].(*object.String).Value
Log.Info("Setting Short Message", "filter", ev.Name, "message", msg) llog.Info("Setting Short Message", "filter", ev.Name, "message", msg)
ev.processedMessage.Body.ShortMsg = msg ev.processedMessage.Body.ShortMsg = msg
return &object.Void{} return &object.Void{}
} }
func (ev *Filter) fnSetSeverity(arg[] object.Object) object.Object { func (ev *Filter) fnSetSeverity(arg []object.Object) object.Object {
if (len(arg) != 1) { if len(arg) != 1 {
return &object.Null{} return &object.Null{}
} }
// Type-check // Type-check
if (arg[0].Type() != object.STRING) { if arg[0].Type() != object.STRING {
return &object.Null{} return &object.Null{}
} }
msg := arg[0].(*object.String).Value msg := arg[0].(*object.String).Value
Log.Info("Setting Severity", "filter", ev.Name, "Severity", msg) llog.Info("Setting Severity", "filter", ev.Name, "Severity", msg)
ev.processedMessage.Body.Severity = msg ev.processedMessage.Body.Severity = msg
return &object.Void{} return &object.Void{}
} }
func (ev *Filter) ProcessMessage(msg *msg.Message) (bool, error) { func (ev *Filter) ProcessMessage(ctx context.Context, msg *msg.Message) (bool, error) {
if !ev.Enabled { if !ev.Enabled {
return true, nil return true, nil
} }
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
Log.Error(err.(error), "Filter Script Error", "filter", ev.Name) llog.Error(err.(error), "Filter Script Error", "filter", ev.Name)
} }
}() }()
if (!ev.ok) { if !ev.ok {
Log.Info("Filter Script Not ready", "filter", ev.Name) llog.Info("Filter Script Not ready", "filter", ev.Name)
return true, nil return true, nil
} }
ev.processedMessage = msg ev.processedMessage = msg
ev.script.SetContext(ctx)
ok, err := ev.script.Run(msg.Body) ok, err := ev.script.Run(msg.Body)
ev.processedMessage = nil ev.processedMessage = nil
if err != nil { if err != nil {
Log.Info("Filter Run Failed", "filter", ev.Name, "result", ok, "Error", err) llog.Info("Filter Run Failed", "filter", ev.Name, "result", ok, "Error", err)
return true, err return true, err
} }
Log.V(1).Info("Filter Run Success", "filter", ev.Name, "result", ok) llog.V(1).Info("Filter Run Success", "filter", ev.Name, "result", ok)
return ok, nil return ok, nil
} }
@ -251,7 +256,7 @@ func (ev *Filter) SetupEvalFilter() error {
// Create an evaluator, with the script inside it. // Create an evaluator, with the script inside it.
// //
ev.script = evalfilter.New(ev.Content) ev.script = evalfilter.New(ev.Content)
Log.Info("Filter Script Content", "filter", ev.Name, "content", ev.Content) llog.V(1).Info("Filter Script Content", "filter", ev.Name, "content", ev.Content)
ev.script.AddFunction("printf", ev.fnPrintf) ev.script.AddFunction("printf", ev.fnPrintf)
ev.script.AddFunction("print", ev.fnPrint) ev.script.AddFunction("print", ev.fnPrint)
ev.script.AddFunction("setfield", ev.fnSetField) ev.script.AddFunction("setfield", ev.fnSetField)
@ -259,11 +264,11 @@ func (ev *Filter) SetupEvalFilter() error {
ev.script.AddFunction("setshortmessage", ev.fnSetShortMessage) ev.script.AddFunction("setshortmessage", ev.fnSetShortMessage)
ev.script.AddFunction("setseverity", ev.fnSetSeverity) ev.script.AddFunction("setseverity", ev.fnSetSeverity)
if err := ev.script.Prepare(); err != nil { if err := ev.script.Prepare(); err != nil {
Log.Info("Compile Filter Script Failed", "filter", ev.Name, "error", err) llog.Error(err, "Compile Filter Script Failed", "filter", ev.Name)
ev.ok = false ev.ok = false
return err return err
} }
Log.Info("Compile Filter Script Success", "filter", ev.Name) llog.V(1).Info("Compile Filter Script Success", "filter", ev.Name)
ev.ok = true ev.ok = true
return nil return nil
} }

View file

@ -5,19 +5,18 @@ import (
_ "fmt" _ "fmt"
"time" "time"
. "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/db" "github.com/Fishwaldo/mouthpiece/internal/db"
"github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/go-logr/logr"
"github.com/alexliesenfeld/health" "github.com/alexliesenfeld/health"
httpCheck "github.com/hellofresh/health-go/v4/checks/http" httpCheck "github.com/hellofresh/health-go/v4/checks/http"
) )
var llog logr.Logger
var HealthChecker health.Checker var HealthChecker health.Checker
func StartHealth() { func StartHealth() {
llog = log.Log.WithName("health")
HealthChecker = health.NewChecker( HealthChecker = health.NewChecker(
health.WithTimeout(10*time.Second), health.WithTimeout(10*time.Second),
//health.WithInterceptors(interceptors.BasicLogger()), //health.WithInterceptors(interceptors.BasicLogger()),
@ -49,7 +48,7 @@ func BasicLogger() health.Interceptor {
return func(ctx context.Context, name string, state health.CheckState) health.CheckState { return func(ctx context.Context, name string, state health.CheckState) health.CheckState {
now := time.Now() now := time.Now()
result := next(ctx, name, state) result := next(ctx, name, state)
Log.V(1).Info("processed health check request", llog.V(1).Info("processed health check request",
"check", name, "seconds", time.Now().Sub(now).Seconds(), "result", result.Status) "check", name, "seconds", time.Now().Sub(now).Seconds(), "result", result.Status)
return result return result
} }

View file

@ -2,21 +2,81 @@ package log
import ( import (
"fmt" "fmt"
"time"
"path/filepath"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"github.com/go-logr/zapr" "github.com/go-logr/zapr"
"github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/danielgtaylor/huma/middleware" "go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
) )
var Log logr.Logger var Log logr.Logger
var zapLog *zap.Logger var zapLog *zap.Logger
func InitLogger() { func init() {
zapLog, err := middleware.NewDefaultLogger() viper.SetDefault("debug", false)
if err != nil { viper.SetDefault("log.dir", "logs")
panic(fmt.Sprintf("Initilize Logging Failed (%v)?", err)) viper.SetDefault("log.maxsize", 1)
} viper.SetDefault("log.maxbackups", 3)
Log = zapr.NewLogger(zapLog) viper.SetDefault("log.maxage", 7)
viper.SetDefault("log.compress", true)
viper.SetDefault("log.level", "info")
Log.Info("Logging Started") }
func InitLogger() {
var cfg zap.Config
var lvl zapcore.Level
var err error
if lvl, err = zapcore.ParseLevel(viper.GetString("log.level")); err != nil {
panic(err)
}
if viper.GetBool("debug") {
fmt.Printf("Debug Enabled at %s level\n", viper.GetString("log.level"))
cfg = zap.NewDevelopmentConfig()
cfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
cfg.OutputPaths = []string{"stdout"}
cfg.EncoderConfig.EncodeTime = iso8601UTCTimeEncoder
cfg.Level = zap.NewAtomicLevelAt(lvl)
var err error
if zapLog, err = cfg.Build(); err != nil {
panic(err)
}
} else {
fmt.Printf("Debug Disabled. Logging to file %s at %s level\n", filepath.Join(viper.GetString("log.dir"), "mouthpiece.log"), viper.GetString("log.level"))
lumberJackLogger := &lumberjack.Logger{
Filename: filepath.Join(viper.GetString("log.dir"), "mouthpiece.log"),
MaxSize: viper.GetInt("log.maxsize"), // megabytes
MaxBackups: viper.GetInt("log.maxbackups"),
MaxAge: viper.GetInt("log.maxage"), //days
Compress: viper.GetBool("log.compress"),
}
ws := zapcore.AddSync(lumberJackLogger)
enccfg := zap.NewProductionEncoderConfig()
enccfg.EncodeTime = iso8601UTCTimeEncoder
core := zapcore.NewCore(
zapcore.NewJSONEncoder(enccfg),
ws,
zap.NewAtomicLevelAt(lvl),
)
zapLog = zap.New(core)
zap.ReplaceGlobals(zapLog)
}
Log = zapr.NewLogger(zapLog)
zap.RedirectStdLog(zapLog)
Log.Info("Logging Started", "level", viper.GetString("log.level"))
}
// A UTC variation of ZapCore.ISO8601TimeEncoder with millisecond precision
func iso8601UTCTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.UTC().Format("2006-01-02T15:04:05.000Z"))
}
func GetZapLogger() (*zap.Logger, error) {
return zapLog.Named("huma"), nil
} }

View file

@ -1,11 +1,11 @@
package msg package msg
import ( import (
"time"
. "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/errors"
"github.com/Fishwaldo/mouthpiece/internal/db" "github.com/Fishwaldo/mouthpiece/internal/db"
"github.com/Fishwaldo/mouthpiece/internal/errors"
. "github.com/Fishwaldo/mouthpiece/internal/log"
"gorm.io/gorm" "gorm.io/gorm"
"time"
) )
func InitializeMessage() { func InitializeMessage() {
@ -15,21 +15,21 @@ func InitializeMessage() {
type Message struct { type Message struct {
gorm.Model gorm.Model
AppName string `path:"application" doc:"Application Name" Example:"MyApp"` AppName string `path:"application" doc:"Application Name" Example:"MyApp"`
Body struct { Body struct {
Message string `json:"message" doc:"Message to be Sent"` Message string `json:"message" doc:"Message to be Sent"`
ShortMsg string `json:"shortmessage,omitempty" doc:"Short Message to be Sent"` ShortMsg string `json:"shortmessage,omitempty" doc:"Short Message to be Sent"`
Topic string `json:"topic,omitempty" doc:"Topic of Message"` Topic string `json:"topic,omitempty" doc:"Topic of Message"`
Severity string `json:"severity,omitempty" doc:"Severity of Message" default:"INFO"` Severity string `json:"severity,omitempty" doc:"Severity of Message" default:"INFO"`
Timestamp time.Time `json:"timestamp,omitempty" doc:"Timestamp of Message"` Timestamp time.Time `json:"timestamp,omitempty" doc:"Timestamp of Message"`
Fields map[string]interface{} `json:"fields,omitempty" doc:"Additional Fields" gorm:"-"` Fields map[string]interface{} `json:"fields,omitempty" doc:"Additional Fields" gorm:"-"`
} `json:"body" doc:"Message Body" gorm:"embedded"` } `json:"body" doc:"Message Body" gorm:"embedded"`
Result *MessageResult `json:"result,omitempty" doc:"Result of Message"` Result *MessageResult `json:"result,omitempty" doc:"Result of Message"`
} }
type MessageResult struct { type MessageResult struct {
MessageID uint `json:"message_id" doc:"Message ID"` MessageID uint `json:"message_id" doc:"Message ID"`
Status string `json:"status" doc:"Status of Message"` Status string `json:"status" doc:"Status of Message"`
} }
func (msg *Message) ProcessMessage() (err error) { func (msg *Message) ProcessMessage() (err error) {
@ -43,4 +43,4 @@ func (msg *Message) ProcessMessage() (err error) {
msg.Result = &MessageResult{MessageID: msg.ID, Status: "Queued"} msg.Result = &MessageResult{MessageID: msg.ID, Status: "Queued"}
return nil return nil
} }
} }

View file

@ -2,7 +2,7 @@ package middleware
import ( import (
"context" "context"
// "fmt" "strconv"
"net/http" "net/http"
"github.com/Fishwaldo/mouthpiece/internal/auth" "github.com/Fishwaldo/mouthpiece/internal/auth"
@ -18,7 +18,6 @@ type Middleware struct {
} }
type CtxUserValue struct{}
// Update user info in request context from go-pkgz/auth token.User to mouthpiece.User // Update user info in request context from go-pkgz/auth token.User to mouthpiece.User
func (a *Middleware) Update() func(http.Handler) http.Handler { func (a *Middleware) Update() func(http.Handler) http.Handler {
@ -27,24 +26,26 @@ func (a *Middleware) Update() func(http.Handler) http.Handler {
// call update only if user info exists, otherwise do nothing // call update only if user info exists, otherwise do nothing
if tknuser, err := token.GetUserInfo(r); err == nil { if tknuser, err := token.GetUserInfo(r); err == nil {
/* find out DB User */ /* find out DB User */
if dbUser, err := user.GetUser(tknuser.Email); err != nil { id, _ := strconv.Atoi(tknuser.ID)
ctx := huma.ContextFromRequest(w, r)
if dbUser, err := user.GetUserByID(ctx, uint(id)); err != nil {
Log.Info("DBUser Not Found", "token", tknuser, "error", err) Log.Info("DBUser Not Found", "token", tknuser, "error", err)
ctx := huma.ContextFromRequest(w, r)
/* do Something */ /* do Something */
ctx.WriteError(http.StatusForbidden, "User not found", err) ctx.WriteError(http.StatusUnauthorized, "User not found", err)
return return
} else { } else {
ok, res, err := auth.AuthService.AuthEnforcer.EnforceEx(dbUser.Email, r.URL.Path, r.Method) ok, res, err := auth.AuthService.AuthEnforcer.EnforceEx(dbUser.Email, r.URL.Path, r.Method)
Log.V(1).Info("Access Control", "result", ok, "Policy", res, "Error", err) Log.V(1).Info("Access Control", "result", ok, "Policy", res, "Error", err)
if (!ok) { if !ok {
huma.ContextFromRequest(w, r).WriteError(http.StatusForbidden, "Access Denied", err) huma.ContextFromRequest(w, r).WriteError(http.StatusForbidden, "Access Denied", err)
return; return
} }
r = r.WithContext(context.WithValue(r.Context(), CtxUserValue{}, tknuser)) r = r.WithContext(context.WithValue(r.Context(), user.CtxUserValue{}, tknuser))
} }
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
return; return
} else { } else {
ctx := huma.ContextFromRequest(w, r) ctx := huma.ContextFromRequest(w, r)
ctx.WriteError(http.StatusUnauthorized, "Access Denied") ctx.WriteError(http.StatusUnauthorized, "Access Denied")
} }
@ -52,4 +53,5 @@ func (a *Middleware) Update() func(http.Handler) http.Handler {
return http.HandlerFunc(fn) return http.HandlerFunc(fn)
} }
return f return f
} }

View file

@ -1,16 +1,18 @@
package mouthpiece package mouthpiece
import ( import (
. "github.com/Fishwaldo/mouthpiece/internal/log" "context"
"github.com/Fishwaldo/mouthpiece/internal/errors"
"github.com/Fishwaldo/mouthpiece/internal/app" "github.com/Fishwaldo/mouthpiece/internal/app"
"github.com/Fishwaldo/mouthpiece/internal/errors"
. "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/message" "github.com/Fishwaldo/mouthpiece/internal/message"
) )
func RouteMessage(msg *msg.Message) { func RouteMessage(ctx context.Context, msg *msg.Message) {
if app, err := app.FindApp(msg.AppName); err == nil { if app, err := app.FindApp(ctx, msg.AppName); err == nil {
app.ProcessMessage(msg) app.ProcessMessage(ctx, msg)
} else { } else {
Log.Error(mperror.ErrAppNotFound, "App Not Found", "App", msg.AppName) Log.Error(mperror.ErrAppNotFound, "App Not Found", "App", msg.AppName)
} }
} }

View file

@ -1,19 +1,16 @@
package stdout package stdout
import ( import (
"fmt" "fmt"
"context"
. "github.com/Fishwaldo/mouthpiece/internal/log" . "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/transport"
"github.com/Fishwaldo/mouthpiece/internal/message" "github.com/Fishwaldo/mouthpiece/internal/message"
"github.com/Fishwaldo/mouthpiece/internal/transport"
) )
type StdoutTransport struct { type StdoutTransport struct {
} }
func init() { func init() {
tp := NewStdoutTransport() tp := NewStdoutTransport()
transport.RegisterTransport(tp) transport.RegisterTransport(tp)
@ -27,11 +24,11 @@ func (t StdoutTransport) GetName() string {
return "stdout" return "stdout"
} }
func (t StdoutTransport) SendMessage(config transport.TransportConfig, msg msg.Message) (err error) { func (t StdoutTransport) SendMessage(ctx context.Context, config transport.TransportConfig, msg msg.Message) (err error) {
fmt.Println("=========================================================") fmt.Println("=========================================================")
fmt.Printf("Message: %s\n", msg.Body.Message) fmt.Printf("Message: %s\n", msg.Body.Message)
fmt.Println("=========================================================") fmt.Println("=========================================================")
transport.UpdateTransportStatus(t, msg, "sent") transport.UpdateTransportStatus(ctx, t, msg, "sent")
return nil return nil
} }
@ -39,10 +36,10 @@ func (t StdoutTransport) Start() {
Log.Info("Transport Started", "name", t.GetName()) Log.Info("Transport Started", "name", t.GetName())
} }
func (t StdoutTransport) NewTransportConfig(){ func (t StdoutTransport) NewTransportConfig(ctx context.Context) {
// user.TransportConfigs = append(user.TransportConfigs, mouthpiece.TransportConfig{ // user.TransportConfigs = append(user.TransportConfigs, mouthpiece.TransportConfig{
// Transport: t.GetName(), // Transport: t.GetName(),
// Config: user.Username, // Config: user.Username,
// }) // })
return return
} }

View file

@ -2,8 +2,7 @@ package telegram
import ( import (
"fmt" "fmt"
// "os" "context"
. "github.com/Fishwaldo/mouthpiece/internal/log" . "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/message" "github.com/Fishwaldo/mouthpiece/internal/message"
@ -16,9 +15,7 @@ import (
"github.com/mymmrac/telego/telegoutil" "github.com/mymmrac/telego/telegoutil"
) )
type TelegramTransport struct { type TelegramTransport struct {
} }
func init() { func init() {
@ -76,17 +73,17 @@ func (t TelegramTransport) Start() {
Log.Info("Transport Started", "name", t.GetName()) Log.Info("Transport Started", "name", t.GetName())
} }
func (t TelegramTransport) NewTransportConfig(){ func (t TelegramTransport) NewTransportConfig(ctx context.Context) {
// user.TransportConfigs = append(user.TransportConfigs, mouthpiece.TransportConfig{ // user.TransportConfigs = append(user.TransportConfigs, mouthpiece.TransportConfig{
// Transport: t.GetName(), // Transport: t.GetName(),
// Config: user.Username, // Config: user.Username,
// }) // })
} }
func (t TelegramTransport) SendMessage(config transport.TransportConfig, msg msg.Message) (err error) { func (t TelegramTransport) SendMessage(ctx context.Context, config transport.TransportConfig, msg msg.Message) (err error) {
fmt.Println("=========================================================") fmt.Println("=========================================================")
fmt.Printf("Message: %s\n", msg.Body.Message) fmt.Printf("Message: %s\n", msg.Body.Message)
fmt.Println("=========================================================") fmt.Println("=========================================================")
transport.UpdateTransportStatus(t, msg, "sent") transport.UpdateTransportStatus(ctx, t, msg, "sent")
return nil return nil
} }

View file

@ -2,25 +2,26 @@ package transport
import ( import (
"errors" "errors"
"context"
"github.com/Fishwaldo/mouthpiece/internal/db"
. "github.com/Fishwaldo/mouthpiece/internal/log" . "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/message" "github.com/Fishwaldo/mouthpiece/internal/message"
"github.com/Fishwaldo/mouthpiece/internal/db"
"gorm.io/gorm" "gorm.io/gorm"
) )
type TransportConfig struct { type TransportConfig struct {
gorm.Model `json:"-"` gorm.Model `json:"-"`
UserID uint `json:"-"` UserID uint `json:"-"`
Transport string Transport string
Config string Config string
} }
type ITransport interface { type ITransport interface {
GetName() string GetName() string
Start() Start()
SendMessage(config TransportConfig, message msg.Message) (err error) SendMessage(ctx context.Context, config TransportConfig, message msg.Message) (err error)
NewTransportConfig() NewTransportConfig(ctx context.Context)
} }
var transports map[string]ITransport var transports map[string]ITransport
@ -43,14 +44,14 @@ func StartTransports() {
} }
} }
func GetTransport(name string) (ITransport, error) { func GetTransport(ctx context.Context, name string) (ITransport, error) {
if t, ok := transports[name]; ok { if t, ok := transports[name]; ok {
return t, nil return t, nil
} }
return nil, errors.New("Transport Not Found") return nil, errors.New("Transport Not Found")
} }
func GetTransports() []string { func GetTransports(ctx context.Context) []string {
var a []string var a []string
for k := range transports { for k := range transports {
a = append(a, k) a = append(a, k)
@ -58,6 +59,6 @@ func GetTransports() []string {
return a return a
} }
func UpdateTransportStatus(t ITransport, m msg.Message, status string) { func UpdateTransportStatus(ctx context.Context, t ITransport, m msg.Message, status string) {
Log.Info("Transport Status", "status", status, "MessageID", m.ID, "Transport", t.GetName()) Log.Info("Transport Status", "status", status, "MessageID", m.ID, "Transport", t.GetName())
} }

View file

@ -1,51 +1,69 @@
package user package user
import ( import (
"context"
"fmt"
"strconv"
"strings" "strings"
"github.com/Fishwaldo/mouthpiece/internal/errors" "github.com/Fishwaldo/mouthpiece/internal/errors"
. "github.com/Fishwaldo/mouthpiece/internal/log" . "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/go-pkgz/auth/token" "github.com/go-pkgz/auth/token"
) )
type CtxUserValue struct{}
func dbAuthProvider(user, pass string) (ok bool, err error) { func dbAuthProvider(user, pass string) (ok bool, err error) {
user = strings.TrimSpace(user) user = strings.TrimSpace(user)
Log.Info("Direct Login", "user", user, "pass", pass) Log.Info("Direct Login", "user", user)
dbUser, err := GetUser(user) dbUser, err := GetUser(context.Background(), user)
Log.Info("User", "user", dbUser, "error", err) Log.Info("User", "user", dbUser, "error", err)
if err == mperror.ErrUserNotFound { if err == mperror.ErrUserNotFound {
Log.Info("User not found", "user", user) Log.Info("User not found", "user", user)
return false, nil return false, nil
} }
if !dbUser.CheckPassword(pass) { if !dbUser.CheckPassword(context.Background(), pass) {
Log.Info("Password Invalid", "user", user) Log.Info("Password Invalid", "user", user)
return false, nil return false, nil
} }
return true, nil return true, nil
} }
// Called when the Tokens are created/refreshed. // Called when the Tokens are created/refreshed.
func MapClaimsToUser(claims token.Claims) token.Claims { func MapClaimsToUser(claims token.Claims) token.Claims {
Log.Info("Map Claims To User", "claims", claims) //Log.Info("Map Claims To User", "claims", claims)
// if claims.User != nil { // if claims.User != nil {
// if user, err := GetUser(claims.User.Name); err != nil { // if user, err := GetUser(claims.User.Name); err != nil {
// Log.Info("User not found", "user", claims.User.Name) // Log.Info("User not found", "user", claims.User.Name)
// claims.User.SetBoolAttr("valid", false) // claims.User.SetBoolAttr("valid", false)
// } else { // } else {
// claims.User.SetStrAttr("backenduser", user.Username) // claims.User.SetStrAttr("backenduser", user.Username)
// claims.User.SetBoolAttr("valid", true) // claims.User.SetBoolAttr("valid", true)
// } // }
// } // }
return claims return claims
} }
// called on every access to the API // called on every access to the API
func UserValidator(token string, claims token.Claims) (bool) { func UserValidator(token string, claims token.Claims) bool {
Log.Info("User Validator", "token", token, "claims", claims) //Log.Info("User Validator", "user", claims.User.Name)
if claims.User != nil { if claims.User != nil {
return true if user, _ := GetUser(context.Background(), claims.User.Name); user != nil {
} claims.User.ID = fmt.Sprintf("%d", user.ID)
return true
}
}
return false return false
}
func GetUserFromContext(ctx context.Context) (bool, *User) {
v := ctx.Value(CtxUserValue{}).(token.User)
if id, _ := strconv.Atoi(v.ID); id > 0 {
if user, _ := GetUserByID(ctx, uint(id)); user != nil {
return true, user
}
}
return false, nil
} }

View file

@ -1,64 +1,64 @@
package user package user
import ( import (
"context"
"fmt" "fmt"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
. "github.com/Fishwaldo/mouthpiece/internal/log"
"github.com/Fishwaldo/mouthpiece/internal/message"
"github.com/Fishwaldo/mouthpiece/internal/transport"
"github.com/Fishwaldo/mouthpiece/internal/db"
"github.com/Fishwaldo/mouthpiece/internal/auth" "github.com/Fishwaldo/mouthpiece/internal/auth"
"github.com/Fishwaldo/mouthpiece/internal/errors" "github.com/Fishwaldo/mouthpiece/internal/db"
mperror "github.com/Fishwaldo/mouthpiece/internal/errors"
. "github.com/Fishwaldo/mouthpiece/internal/log"
msg "github.com/Fishwaldo/mouthpiece/internal/message"
"github.com/Fishwaldo/mouthpiece/internal/transport"
"github.com/go-playground/validator/v10"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
"github.com/go-playground/validator/v10"
) )
type User struct { type User struct {
gorm.Model `json:"-"` gorm.Model `json:"-"`
ID uint `gorm:"primarykey"` ID uint `gorm:"primarykey"`
Email string `validate:"required,email"` Email string `validate:"required,email"`
FirstName string `validate:"required"` FirstName string `validate:"required"`
LastName string `validate:"required"` LastName string `validate:"required"`
Password string `json:"-" writeOnly:"true" validate:"required"` Password string `json:"-" writeOnly:"true" validate:"required"`
TransportConfigs []transport.TransportConfig `json:"transports,omitempty" gorm:"many2many:user_transports;" validate:"-"` TransportConfigs []transport.TransportConfig `json:"transports,omitempty" gorm:"many2many:user_transports;" validate:"-"`
} }
var AuthConfig auth.AuthConfig var AuthConfig auth.AuthConfig
func init() { func init() {
AuthConfig = auth.AuthConfig { AuthConfig = auth.AuthConfig{
CredChecker: dbAuthProvider, CredChecker: dbAuthProvider,
MapClaimsToUser: MapClaimsToUser, MapClaimsToUser: MapClaimsToUser,
Validator: UserValidator, Validator: UserValidator,
} }
} }
func CreateUser(ctx context.Context, user *User) error {
func CreateUser(user *User) error {
validate := validator.New() validate := validator.New()
if err := validate.Struct(user); err != nil { if err := validate.Struct(user); err != nil {
Log.Info("User Validation Error", "Error", err) Log.Info("User Validation Error", "Error", err)
return err; return err
} }
tx := db.Db.Omit("Password").Create(&user) tx := db.Db.WithContext(ctx).Omit("Password").Create(&user)
if tx.Error != nil { if tx.Error != nil {
return tx.Error return tx.Error
} }
if dbuser, err := GetUser(user.Email); err == nil { if dbuser, err := GetUser(ctx, user.Email); err == nil {
/* Set the Users Initial Password */ /* Set the Users Initial Password */
if err := dbuser.SetPassword(user.Password); err != nil { if err := dbuser.SetPassword(ctx, user.Password); err != nil {
if tx := db.Db.Delete(&dbuser); tx.Error != nil { if tx := db.Db.WithContext(ctx).Delete(&dbuser); tx.Error != nil {
Log.Info("Error Deleting User after failed Password", "Error", tx.Error) Log.Info("Error Deleting User after failed Password", "Error", tx.Error)
return err; return err
} }
return err return err
} }
/* New Users all Start with User Role */ /* New Users all Start with User Role */
if !dbuser.addUserRole("user") { if !dbuser.addUserRole(ctx, "user") {
Log.Info("Error Adding User Role", "Error", err) Log.Info("Error Adding User Role", "Error", err)
} }
return nil return nil
@ -67,16 +67,16 @@ func CreateUser(user *User) error {
} }
} }
func (u *User) addUserRole(role string) bool { func (u *User) addUserRole(ctx context.Context, role string) bool {
_, err := auth.AuthService.AuthEnforcer.AddRoleForUser(u.Email, fmt.Sprintf("role:%s", role)) _, err := auth.AuthService.AuthEnforcer.AddRoleForUser(u.Email, fmt.Sprintf("role:%s", role))
if err != nil { if err != nil {
Log.Info("Failed to add role for user", "email", u.Email, "role", role, "error", err) Log.Info("Failed to add role for user", "email", u.Email, "role", role, "error", err)
return false return false
} }
return true; return true
} }
func (u *User) CheckPassword(password string) bool { func (u *User) CheckPassword(ctx context.Context, password string) bool {
Log.Info("Checking Password", "email", u.Email) Log.Info("Checking Password", "email", u.Email)
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
if err != nil { if err != nil {
@ -86,14 +86,14 @@ func (u *User) CheckPassword(password string) bool {
return true return true
} }
func (u *User) SetPassword(password string) error { func (u *User) SetPassword(ctx context.Context, password string) error {
Log.Info("Setting Password", "Email", u.Email) Log.Info("Setting Password", "Email", u.Email)
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if (err != nil) { if err != nil {
Log.Info("Error Generating SetPassword Hash", "Error", err) Log.Info("Error Generating SetPassword Hash", "Error", err)
return err return err
} }
if tx := db.Db.Model(&u).Update("password", string(hashedPassword)); tx.Error != nil { if tx := db.Db.WithContext(ctx).Model(&u).Update("password", string(hashedPassword)); tx.Error != nil {
Log.Info("Error Setting Password", "Error", tx.Error) Log.Info("Error Setting Password", "Error", tx.Error)
return tx.Error return tx.Error
} }
@ -105,54 +105,52 @@ func InitializeUsers() {
var count int64 var count int64
db.Db.Model(&User{}).Count(&count) db.Db.Model(&User{}).Count(&count)
Log.V(1).Info("Initializing Users", "count", count) Log.V(1).Info("Initializing Users", "count", count)
if (count == 0) { if count == 0 {
Log.Info("Creating Default Users") Log.Info("Creating Default Users")
admin := &User{FirstName: "Admin", LastName: "User", Email: "admin@example.com", Password: "password"} admin := &User{FirstName: "Admin", LastName: "User", Email: "admin@example.com", Password: "password"}
if err := CreateUser(admin); err == nil { if err := CreateUser(context.Background(), admin); err == nil {
admin.addUserRole("admin") admin.addUserRole(context.Background(), "admin")
Log.Info("Created Default Admin admin@example.com") Log.Info("Created Default Admin admin@example.com")
} }
if err := CreateUser(&User{FirstName: "User", LastName: "User", Email: "user@example.com", Password: "password"}); err == nil { if err := CreateUser(context.Background(), &User{FirstName: "User", LastName: "User", Email: "user@example.com", Password: "password"}); err == nil {
Log.Info("Created Default User user@example.com") Log.Info("Created Default User user@example.com")
} }
} }
} }
func GetUsers() []User { func GetUsers(ctx context.Context) []User {
var users []User var users []User
db.Db.Find(&users) db.Db.WithContext(ctx).Find(&users)
return users return users
} }
func GetUser(email string) (user *User, err error) { func GetUser(ctx context.Context, email string) (user *User, err error) {
tx := db.Db.Preload(clause.Associations).First(&user, "email = ?", email) tx := db.Db.WithContext(ctx).Preload(clause.Associations).First(&user, "email = ?", email)
if tx.Error == gorm.ErrRecordNotFound { if tx.Error == gorm.ErrRecordNotFound {
return nil, mperror.ErrUserNotFound return nil, mperror.ErrUserNotFound
} }
return return
} }
func GetUserByID(id uint) (user *User, err error) { func GetUserByID(ctx context.Context, id uint) (user *User, err error) {
tx := db.Db.Preload(clause.Associations).First(&user, "ID = ?", id) tx := db.Db.WithContext(ctx).Preload(clause.Associations).First(&user, "ID = ?", id)
if tx.Error == gorm.ErrRecordNotFound { if tx.Error == gorm.ErrRecordNotFound {
return nil, mperror.ErrUserNotFound return nil, mperror.ErrUserNotFound
} }
return return
} }
func (u User) ProcessMessage(ctx context.Context, msg msg.Message) (err error) {
func (u User) ProcessMessage(msg msg.Message) (err error) {
/* add User Fields to Message */ /* add User Fields to Message */
msg.Body.Fields["first_name"] = u.FirstName msg.Body.Fields["first_name"] = u.FirstName
msg.Body.Fields["last_name"] = u.LastName msg.Body.Fields["last_name"] = u.LastName
msg.Body.Fields["email"] = u.Email msg.Body.Fields["email"] = u.Email
Log.V(1).Info("User Processing Message", "Email", u.Email, "MessageID", msg.ID) Log.V(1).Info("User Processing Message", "Email", u.Email, "MessageID", msg.ID)
for _, tc := range u.TransportConfigs { for _, tc := range u.TransportConfigs {
t, err := transport.GetTransport(tc.Transport); t, err := transport.GetTransport(ctx, tc.Transport)
if err != nil { if err != nil {
Log.Info("Cant find Transport", "Transport", tc.Transport) Log.Info("Cant find Transport", "Transport", tc.Transport)
} }
go t.SendMessage(tc, msg) go t.SendMessage(ctx, tc, msg)
} }
return return
} }

View file

@ -202,4 +202,4 @@ func (i *Info) CheckFontName(fontName string) bool {
fmt.Fprintln(os.Stderr, "font not valid, using default") fmt.Fprintln(os.Stderr, "font not valid, using default")
return false return false
} }

199
main.go
View file

@ -25,27 +25,25 @@ SOFTWARE.
package main package main
import ( import (
//"fmt" "embed"
// "context"
"fmt" "fmt"
"net/http"
"io/fs" "io/fs"
"net/http"
// "reflect"
"strings"
// "unsafe"
"encoding/json"
"os" "os"
"runtime/debug" "os/signal"
"strings"
"syscall"
"time"
"context"
"path/filepath"
"github.com/Fishwaldo/mouthpiece/frontend" "github.com/Fishwaldo/mouthpiece/frontend"
_ "github.com/Fishwaldo/mouthpiece/frontend"
mouthpiece "github.com/Fishwaldo/mouthpiece/internal" mouthpiece "github.com/Fishwaldo/mouthpiece/internal"
"github.com/Fishwaldo/mouthpiece/internal/app" "github.com/Fishwaldo/mouthpiece/internal/app"
"github.com/Fishwaldo/mouthpiece/internal/auth" "github.com/Fishwaldo/mouthpiece/internal/auth"
"github.com/Fishwaldo/mouthpiece/internal/db" "github.com/Fishwaldo/mouthpiece/internal/db"
"github.com/Fishwaldo/mouthpiece/internal/filter" "github.com/Fishwaldo/mouthpiece/internal/filter"
. "github.com/Fishwaldo/mouthpiece/internal/log" "github.com/Fishwaldo/mouthpiece/internal/log"
msg "github.com/Fishwaldo/mouthpiece/internal/message" msg "github.com/Fishwaldo/mouthpiece/internal/message"
"github.com/Fishwaldo/mouthpiece/internal/middleware" "github.com/Fishwaldo/mouthpiece/internal/middleware"
"github.com/Fishwaldo/mouthpiece/internal/transport" "github.com/Fishwaldo/mouthpiece/internal/transport"
@ -60,11 +58,50 @@ import (
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/danielgtaylor/huma" "github.com/danielgtaylor/huma"
"github.com/danielgtaylor/huma/cli" hmw "github.com/danielgtaylor/huma/middleware"
"github.com/danielgtaylor/huma/responses" "github.com/danielgtaylor/huma/responses"
"github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
//go:embed config
var ConfigFiles embed.FS
type mpserver struct {
*huma.Router
root *cobra.Command
prestart []func()
}
var Server mpserver
func (c *mpserver) Flag(name, short, description string, defaultValue interface{}) {
viper.SetDefault(name, defaultValue)
flags := c.root.PersistentFlags()
switch v := defaultValue.(type) {
case bool:
flags.BoolP(name, short, viper.GetBool(name), description)
case int, int16, int32, int64, uint16, uint32, uint64:
flags.IntP(name, short, viper.GetInt(name), description)
case float32, float64:
flags.Float64P(name, short, viper.GetFloat64(name), description)
default:
flags.StringP(name, short, fmt.Sprintf("%v", v), description)
}
viper.BindPFlag(name, flags.Lookup(name))
}
func (c *mpserver) PreStart(f func()) {
c.prestart = append(c.prestart, f)
}
func (c *mpserver) Run() {
if err := c.root.Execute(); err != nil {
panic(err)
}
}
func init() { func init() {
viper.SetDefault("frontend.path", "frontend/dist") viper.SetDefault("frontend.path", "frontend/dist")
viper.SetDefault("frontend.external", false) viper.SetDefault("frontend.external", false)
@ -77,7 +114,7 @@ func fileServer(r chi.Router, path string, root http.FileSystem) {
panic("FileServer does not permit URL parameters.") panic("FileServer does not permit URL parameters.")
} }
//log.Printf("[INFO] serving static files from %v", root) //log.Log.Printf("[INFO] serving static files from %v", root)
fs := http.StripPrefix(path, http.FileServer(root)) fs := http.StripPrefix(path, http.FileServer(root))
if path != "/" && path[len(path)-1] != '/' { if path != "/" && path[len(path)-1] != '/' {
@ -91,22 +128,12 @@ func fileServer(r chi.Router, path string, root http.FileSystem) {
}) })
} }
func printBuildInfo() {
bi, ok := debug.ReadBuildInfo()
if !ok {
fmt.Println("Getting build info failed (not in module mode?)!")
return
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(bi); err != nil {
panic(err)
}
}
func main() { func main() {
viper.SetEnvPrefix("MP")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.AutomaticEnv()
viper.SetConfigName("config") viper.SetConfigName("config")
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.AddConfigPath(".") viper.AddConfigPath(".")
@ -135,50 +162,80 @@ func main() {
fmt.Println(bi.String()) fmt.Println(bi.String())
// Create a new router & CLI with default middleware. // Create a new router & CLI with default middleware.
InitLogger()
Server = mpserver{
Router: huma.New(bi.Name, bi.GitVersion),
}
hmw.Defaults(Server.Router)
Server.root = &cobra.Command{
Use: filepath.Base(os.Args[0]),
Version: bi.GitVersion,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Starting %s (%s)\n", bi.Name, bi.GitVersion)
for _, f := range Server.prestart {
f()
}
go func() {
if err := Server.Listen(fmt.Sprintf("%s:%v", viper.Get("host"), viper.Get("port"))); err != nil && err != http.ErrServerClosed {
panic(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
Server.Shutdown(ctx)
},
}
Server.Flag("host", "", "Hostname", "0.0.0.0")
Server.Flag("port", "p", "Port", 8888)
log.InitLogger()
db.InitializeDB() db.InitializeDB()
humucli := cli.NewRouter(bi.Name, bi.GitVersion) hmw.NewLogger = log.GetZapLogger
humucli.DisableSchemaProperty() Server.DisableSchemaProperty()
humucli.PreStart(transport.InitializeTransports) Server.PreStart(transport.InitializeTransports)
humucli.PreStart(msg.InitializeMessage) Server.PreStart(msg.InitializeMessage)
humucli.PreStart(user.InitializeUsers) Server.PreStart(user.InitializeUsers)
humucli.PreStart(app.InitializeApps) Server.PreStart(app.InitializeApps)
humucli.PreStart(transport.StartTransports) Server.PreStart(transport.StartTransports)
humucli.PreStart(filter.InitFilter) Server.PreStart(filter.InitFilter)
// app.PreStart() Server.PreStart(healthChecker.StartHealth)
humucli.PreStart(healthChecker.StartHealth) Server.GatewayClientCredentials("mouthpiece", "/oauth2/token", nil)
humucli.GatewayClientCredentials("mouthpiece", "/oauth2/token", nil) Server.GatewayAuthCode("mouthpiece2", "/oauth2/token", "/oauth2/token", nil)
humucli.GatewayAuthCode("mouthpiece2", "/oauth2/token", "/oauth2/token", nil) Server.GatewayBasicAuth("basic")
humucli.GatewayBasicAuth("basic")
user.AuthConfig.Host = fmt.Sprintf("http://arm64-1.dmz.dynam.ac:%v", viper.Get("Port")) user.AuthConfig.Host = fmt.Sprintf("http://arm64-1.dmz.dynam.ac:%v", viper.Get("Port"))
user.AuthConfig.ConfigDir = ConfigFiles
auth.InitAuth(user.AuthConfig) auth.InitAuth(user.AuthConfig)
m := auth.AuthService.Service.Middleware() m := auth.AuthService.Service.Middleware()
p := middleware.Middleware{} p := middleware.Middleware{}
authRoutes, avaRoutes := auth.AuthService.Service.Handlers() authRoutes, avaRoutes := auth.AuthService.Service.Handlers()
mux := humucli.Resource("/").GetMux() mux := Server.Resource("/").GetMux()
mux.Mount("/auth", authRoutes) mux.Mount("/auth", authRoutes)
mux.Mount("/avatar", avaRoutes) mux.Mount("/avatar", avaRoutes)
var httpfiles http.FileSystem var httpfiles http.FileSystem
if viper.GetBool("frontend.external") { if viper.GetBool("frontend.external") {
Log.Info("Serving frontend from external location", "path", viper.GetString("frontend.path")) log.Log.Info("Serving frontend from external location", "path", viper.GetString("frontend.path"))
httpfiles = http.Dir(viper.GetString("frontend.path")) httpfiles = http.Dir(viper.GetString("frontend.path"))
} else { } else {
Log.Info("Serving frontend from Bundled Files") log.Log.Info("Serving frontend from Bundled Files")
subdir, err := fs.Sub(frontend.FrontEndFiles, "dist") subdir, err := fs.Sub(frontend.FrontEndFiles, "dist")
if err != nil { if err != nil {
Log.Error(err, "Failed to get subdir") log.Log.Error(err, "Failed to get subdir")
} }
httpfiles = http.FS(subdir) httpfiles = http.FS(subdir)
} }
fileServer(mux, "/static", httpfiles) fileServer(mux, "/static", httpfiles)
// Declare the root resource and a GET operation on it. // Declare the root resource and a GET operation on it.
humucli.Resource("/health").Get("get-health", "Get Health of the Service", Server.Resource("/health").Get("get-health", "Get Health of the Service",
responses.OK().ContentType("application/json"), responses.OK().ContentType("application/json"),
responses.OK().Headers("Content-Type"), responses.OK().Headers("Content-Type"),
responses.OK().Model(health.CheckerResult{}), responses.OK().Model(health.CheckerResult{}),
@ -194,7 +251,7 @@ func main() {
ctx.WriteModel(status, test) ctx.WriteModel(status, test)
}) })
humucli.Resource("/config/frontend").Get("get-config", "Get Config of the Service", Server.Resource("/config/frontend").Get("get-config", "Get Config of the Service",
responses.OK().ContentType("application/json"), responses.OK().ContentType("application/json"),
responses.OK().Headers("Content-Type"), responses.OK().Headers("Content-Type"),
responses.OK().Model(&mouthpiece.FEConfig{}), responses.OK().Model(&mouthpiece.FEConfig{}),
@ -202,7 +259,7 @@ func main() {
ctx.WriteModel(http.StatusOK, mouthpiece.GetFEConfig()) ctx.WriteModel(http.StatusOK, mouthpiece.GetFEConfig())
}) })
v1api := humucli.Resource("/v1") v1api := Server.Resource("/v1")
v1api.Middleware(m.Trace) v1api.Middleware(m.Trace)
v1api.Middleware(p.Update()) v1api.Middleware(p.Update())
@ -212,10 +269,10 @@ func main() {
responses.OK().Model(&msg.MessageResult{}), responses.OK().Model(&msg.MessageResult{}),
responses.NotFound().ContentType("application/json"), responses.NotFound().ContentType("application/json"),
).Run(func(ctx huma.Context, input msg.Message) { ).Run(func(ctx huma.Context, input msg.Message) {
Log.Info("Recieved Message", "message", input) log.Log.Info("Recieved Message", "message", input)
if app.AppExists(input.AppName) { if app.AppExists(ctx, input.AppName) {
if err := input.ProcessMessage(); err == nil { if err := input.ProcessMessage(); err == nil {
mouthpiece.RouteMessage(&input) mouthpiece.RouteMessage(ctx, &input)
ctx.WriteModel(http.StatusOK, input.Result) ctx.WriteModel(http.StatusOK, input.Result)
} else { } else {
ctx.WriteError(http.StatusInternalServerError, err.Error()) ctx.WriteError(http.StatusInternalServerError, err.Error())
@ -225,30 +282,11 @@ func main() {
} }
}) })
auth.AuthService.AddResourceURL("/v1/apps/", "apigroup:apps")
appapi := v1api.SubResource("/apps/") if err := app.InitializeAppRestAPI(v1api); err != nil {
appapi.Get("get-apps", "Get A List of Applications", log.Log.Error(err, "Failed to initialize App Rest API")
responses.OK().ContentType("application/json"), }
responses.OK().Headers("Set-Cookie"),
responses.OK().Model([]app.App{}),
).Run(func(ctx huma.Context) {
ctx.WriteModel(http.StatusOK, app.GetApps())
})
appapi.Put("create-app", "Create a Application",
responses.OK().ContentType("application/json"),
responses.OK().Headers("Set-Cookie"),
responses.OK().Model(&app.App{}),
responses.NotAcceptable().ContentType("application/json"),
responses.NotAcceptable().Headers("Set-Cookie"),
).Run(func(ctx huma.Context, input struct {
Body app.AppDetails
}) {
if app, err := app.CreateApp(input.Body); err != nil {
ctx.WriteError(http.StatusNotAcceptable, "Database Error", err)
} else {
ctx.WriteModel(http.StatusOK, app)
}
})
auth.AuthService.AddResourceURL("/v1/users/", "apigroup:users") auth.AuthService.AddResourceURL("/v1/users/", "apigroup:users")
userapi := v1api.SubResource("/users/") userapi := v1api.SubResource("/users/")
@ -257,7 +295,7 @@ func main() {
responses.OK().Headers("Set-Cookie"), responses.OK().Headers("Set-Cookie"),
responses.OK().Model([]user.User{}), responses.OK().Model([]user.User{}),
).Run(func(ctx huma.Context) { ).Run(func(ctx huma.Context) {
ctx.WriteModel(http.StatusOK, user.GetUsers()) ctx.WriteModel(http.StatusOK, user.GetUsers(ctx))
}) })
auth.AuthService.AddResourceURL("/v1/users/{userid}/transports/", "apigroup:users") auth.AuthService.AddResourceURL("/v1/users/{userid}/transports/", "apigroup:users")
@ -270,7 +308,7 @@ func main() {
).Run(func(ctx huma.Context, input struct { ).Run(func(ctx huma.Context, input struct {
User uint `path:"userid"` User uint `path:"userid"`
}) { }) {
if user, err := user.GetUserByID(input.User); err != nil { if user, err := user.GetUserByID(ctx, input.User); err != nil {
ctx.WriteError(http.StatusNotFound, "User Not Found", err) ctx.WriteError(http.StatusNotFound, "User Not Found", err)
} else { } else {
var transport []string var transport []string
@ -287,11 +325,12 @@ func main() {
responses.OK().Headers("Set-Cookie"), responses.OK().Headers("Set-Cookie"),
responses.OK().Model(transport.TransportConfig{}), responses.OK().Model(transport.TransportConfig{}),
responses.NotFound().ContentType("application/json"), responses.NotFound().ContentType("application/json"),
responses.NotFound().Headers("Set-Cookie"),
).Run(func(ctx huma.Context, input struct { ).Run(func(ctx huma.Context, input struct {
User uint `path:"userid"` User uint `path:"userid"`
Transport string `path:"transportid"` Transport string `path:"transportid"`
}) { }) {
if user, err := user.GetUserByID(input.User); err != nil { if user, err := user.GetUserByID(ctx, input.User); err != nil {
ctx.WriteError(http.StatusNotFound, "User Not Found", err) ctx.WriteError(http.StatusNotFound, "User Not Found", err)
} else { } else {
ok := false ok := false
@ -313,9 +352,9 @@ func main() {
responses.OK().Headers("Set-Cookie"), responses.OK().Headers("Set-Cookie"),
responses.OK().Model([]string{}), responses.OK().Model([]string{}),
).Run(func(ctx huma.Context) { ).Run(func(ctx huma.Context) {
ctx.WriteModel(http.StatusOK, transport.GetTransports()) ctx.WriteModel(http.StatusOK, transport.GetTransports(ctx))
}) })
// Run the CLI. When passed no arguments, it starts the server. // Run the CLI. When passed no arguments, it starts the server.
humucli.Run() Server.Run()
} }