HEAVY: migrate accounts and logs to service/repo architecture

This commit is contained in:
ari melody 2026-07-31 02:43:45 +01:00
parent 5c255fb34b
commit 49e14b5bc5
Signed by: ari
GPG key ID: CF99829C92678188
37 changed files with 1019 additions and 687 deletions

321
main.go
View file

@ -5,6 +5,7 @@ import (
"embed"
"errors"
"fmt"
"log"
stdLog "log"
"math"
"math/rand"
@ -21,8 +22,13 @@ import (
"arimelody-web/colour"
"arimelody-web/controller"
"arimelody-web/cursor"
"arimelody-web/log"
"arimelody-web/model"
"arimelody-web/model/app"
accountRepo "arimelody-web/repository/account"
logRepo "arimelody-web/repository/log"
repo "arimelody-web/repository/postgres"
accountService "arimelody-web/service/account"
logService "arimelody-web/service/log"
"arimelody-web/view"
"github.com/jmoiron/sqlx"
@ -35,14 +41,17 @@ const DB_VERSION = 1
const DEFAULT_PORT int64 = 8080
const HRT_DATE int64 = 1756478697
const DEFAULT_LOG_FLAGS = log.Ldate | log.Ltime | log.Lmicroseconds
//go:embed "public"
var publicFS embed.FS
func main() {
fmt.Printf("made with <3 by ari melody\n\n")
logger := log.New(os.Stderr, "main", DEFAULT_LOG_FLAGS)
app := model.AppState{
logger.Print("made with <3 by ari melody\n\n")
app := app.AppState{
Config: controller.GetConfig(),
Twitch: nil,
PublicFS: publicFS,
@ -50,44 +59,49 @@ func main() {
// initialise database connection
if app.Config.DB.Host == "" {
fmt.Fprintf(os.Stderr, "FATAL: db.host not provided! Exiting...\n")
os.Exit(1)
logger.Fatalf("FATAL: db.host not provided! Exiting...\n")
}
if app.Config.DB.Name == "" {
fmt.Fprintf(os.Stderr, "FATAL: db.name not provided! Exiting...\n")
os.Exit(1)
logger.Fatalf("FATAL: db.name not provided! Exiting...\n")
}
if app.Config.DB.User == "" {
fmt.Fprintf(os.Stderr, "FATAL: db.user not provided! Exiting...\n")
os.Exit(1)
logger.Fatalf("FATAL: db.user not provided! Exiting...\n")
}
if app.Config.DB.Pass == "" {
fmt.Fprintf(os.Stderr, "FATAL: db.pass not provided! Exiting...\n")
os.Exit(1)
logger.Fatalf("FATAL: db.pass not provided! Exiting...\n")
}
var err error
app.DB, err = sqlx.Connect(
psqlDB, err := sqlx.Connect(
"postgres",
fmt.Sprintf(
"host=%s port=%d user=%s dbname=%s password='%s' sslmode=disable",
"host=%s port=%d user=%s password='%s' dbname=%s sslmode=disable",
app.Config.DB.Host,
app.Config.DB.Port,
app.Config.DB.User,
app.Config.DB.Name,
app.Config.DB.Pass,
app.Config.DB.Name,
),
)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Unable to initialise database: %v\n", err)
os.Exit(1)
logger.Fatalf("Failed to connect to database: %v", err)
}
app.DB.SetConnMaxLifetime(time.Minute * 3)
app.DB.SetMaxOpenConns(10)
app.DB.SetMaxIdleConns(10)
defer app.DB.Close()
defer psqlDB.Close()
psqlDB.SetConnMaxLifetime(time.Minute * 3)
psqlDB.SetMaxOpenConns(10)
psqlDB.SetMaxIdleConns(10)
app.DB = psqlDB
app.Log = log.Logger{ DB: app.DB }
logRepo := logRepo.NewLogRepositoryPostgres(psqlDB)
app.Log = logService.NewLogService(
logRepo,
log.New(os.Stderr, "logger", DEFAULT_LOG_FLAGS),
)
accountRepo := accountRepo.NewAccountRepositoryPostgres(psqlDB)
app.AccountService = accountService.NewAccountService(
accountRepo,
log.New(os.Stderr, "account-repo", DEFAULT_LOG_FLAGS),
)
// handle command arguments
if len(os.Args) > 1 {
@ -96,21 +110,18 @@ func main() {
switch arg {
case "createTOTP":
if len(os.Args) < 4 {
fmt.Fprintf(os.Stderr, "FATAL: `username` and `name` must be specified for createTOTP.\n")
os.Exit(1)
logger.Fatalf("FATAL: `username` and `name` must be specified for createTOTP.\n")
}
username := os.Args[2]
totpName := os.Args[3]
account, err := controller.GetAccountByUsername(app.DB, username)
account, err := app.AccountService.GetByUsername(username)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch account \"%s\": %v\n", username, err)
}
if account == nil {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" does not exist.\n", username)
}
secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH)
@ -123,133 +134,116 @@ func main() {
err = controller.CreateTOTP(app.DB, &totp)
if err != nil {
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" already has a TOTP method named \"%s\"!\n", account.Username, totp.Name)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" already has a TOTP method named \"%s\"!\n", account.Username, totp.Name)
}
fmt.Fprintf(os.Stderr, "FATAL: Failed to create TOTP method: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to create TOTP method: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "TOTP method \"%s\" for \"%s\" created via config utility.", totp.Name, account.Username)
app.Log.Info(model.LOG_ACCOUNT, "TOTP method \"%s\" for \"%s\" created via config utility.", totp.Name, account.Username)
url := controller.GenerateTOTPURI(account.Username, totp.Secret)
fmt.Printf("%s\n", url)
logger.Printf("%s\n", url)
return
case "deleteTOTP":
if len(os.Args) < 4 {
fmt.Fprintf(os.Stderr, "FATAL: `username` and `name` must be specified for deleteTOTP.\n")
os.Exit(1)
logger.Fatalf("FATAL: `username` and `name` must be specified for deleteTOTP.\n")
}
username := os.Args[2]
totpName := os.Args[3]
account, err := controller.GetAccountByUsername(app.DB, username)
account, err := app.AccountService.GetByUsername(username)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch account \"%s\": %v\n", username, err)
}
if account == nil {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" does not exist.\n", username)
}
err = controller.DeleteTOTP(app.DB, account.ID, totpName)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to create TOTP method: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to create TOTP method: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "TOTP method \"%s\" for \"%s\" deleted via config utility.", totpName, account.Username)
fmt.Printf("TOTP method \"%s\" deleted.\n", totpName)
app.Log.Info(model.LOG_ACCOUNT, "TOTP method \"%s\" for \"%s\" deleted via config utility.", totpName, account.Username)
logger.Printf("TOTP method \"%s\" deleted.\n", totpName)
return
case "listTOTP":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "FATAL: `username` must be specified for listTOTP.\n")
os.Exit(1)
logger.Fatalf("FATAL: `username` must be specified for listTOTP.\n")
}
username := os.Args[2]
account, err := controller.GetAccountByUsername(app.DB, username)
account, err := app.AccountService.GetByUsername(username)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch account \"%s\": %v\n", username, err)
}
if account == nil {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" does not exist.\n", username)
}
totps, err := controller.GetTOTPsForAccount(app.DB, account.ID)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to create TOTP methods: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to create TOTP methods: %v\n", err)
}
for i, totp := range totps {
fmt.Printf("%d. %s - Created %s\n", i + 1, totp.Name, totp.CreatedAt)
logger.Printf("%d. %s - Created %s\n", i + 1, totp.Name, totp.CreatedAt)
}
if len(totps) == 0 {
fmt.Printf("\"%s\" has no TOTP methods.\n", account.Username)
logger.Printf("\"%s\" has no TOTP methods.\n", account.Username)
}
return
case "testTOTP":
if len(os.Args) < 4 {
fmt.Fprintf(os.Stderr, "FATAL: `username` and `name` must be specified for testTOTP.\n")
os.Exit(1)
logger.Fatalf("FATAL: `username` and `name` must be specified for testTOTP.\n")
}
username := os.Args[2]
totpName := os.Args[3]
account, err := controller.GetAccountByUsername(app.DB, username)
account, err := app.AccountService.GetByUsername(username)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch account \"%s\": %v\n", username, err)
}
if account == nil {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" does not exist.\n", username)
}
totp, err := controller.GetTOTP(app.DB, account.ID, totpName)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch TOTP method \"%s\": %v\n", totpName, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch TOTP method \"%s\": %v\n", totpName, err)
}
if totp == nil {
fmt.Fprintf(os.Stderr, "FATAL: TOTP method \"%s\" does not exist for account \"%s\"\n", totpName, username)
os.Exit(1)
logger.Fatalf("FATAL: TOTP method \"%s\" does not exist for account \"%s\"\n", totpName, username)
}
code := controller.GenerateTOTP(totp.Secret, 0)
fmt.Printf("%s\n", code)
logger.Printf("%s\n", code)
return
case "cleanTOTP":
err := controller.DeleteUnconfirmedTOTPs(app.DB)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to clean up TOTP methods: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to clean up TOTP methods: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "TOTP methods pruned via config utility.")
fmt.Printf("Cleaned up dangling TOTP methods successfully.\n")
app.Log.Info(model.LOG_ACCOUNT, "TOTP methods pruned via config utility.")
logger.Printf("Cleaned up dangling TOTP methods successfully.\n")
return
case "createInvite":
fmt.Printf("Creating invite...\n")
logger.Printf("Creating invite...\n")
invite, err := controller.CreateInvite(app.DB, 16, time.Hour * 24)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to create invite code: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to create invite code: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "Invite generted via config utility (%s).", invite.Code)
fmt.Printf(
app.Log.Info(model.LOG_ACCOUNT, "Invite generted via config utility (%s).", invite.Code)
logger.Printf(
"Here you go! This code expires in %d hours: %s\n",
int(math.Ceil(invite.ExpiresAt.Sub(invite.CreatedAt).Hours())),
invite.Code,
@ -257,28 +251,26 @@ func main() {
return
case "purgeInvites":
fmt.Printf("Deleting all invites...\n")
logger.Printf("Deleting all invites...\n")
err := controller.DeleteAllInvites(app.DB)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to delete invites: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to delete invites: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "Invites purged via config utility.")
fmt.Printf("Invites deleted successfully.\n")
app.Log.Info(model.LOG_ACCOUNT, "Invites purged via config utility.")
logger.Printf("Invites deleted successfully.\n")
return
case "listAccounts":
accounts, err := controller.GetAllAccounts(app.DB)
accounts, err := app.AccountService.GetAll()
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch accounts: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch accounts: %v\n", err)
}
for _, account := range accounts {
email := "<none>"
if account.Email.Valid { email = account.Email.String }
fmt.Printf(
logger.Printf(
"User: %s\n" +
"\tID: %s\n" +
"\tEmail: %s\n" +
@ -295,150 +287,140 @@ func main() {
case "changePassword":
if len(os.Args) < 4 {
fmt.Fprintf(os.Stderr, "FATAL: `username` and `password` must be specified for changePassword\n")
os.Exit(1)
logger.Fatalf("FATAL: `username` and `password` must be specified for changePassword\n")
}
username := os.Args[2]
password := os.Args[3]
account, err := controller.GetAccountByUsername(app.DB, username)
account, err := app.AccountService.GetByUsername(username)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch account \"%s\": %v\n", username, err)
}
if account == nil {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" does not exist.\n", username)
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to update password: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to update password: %v\n", err)
}
account.Password = string(hashedPassword)
err = controller.UpdateAccount(app.DB, account)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to update password: %v\n", err)
os.Exit(1)
var email *string = nil
if account.Email.Valid { email = &account.Email.String }
var avatarURL *string = nil
if account.AvatarURL.Valid { email = &account.AvatarURL.String }
if err = app.AccountService.Update(
account.ID,
username, string(hashedPassword),
email, avatarURL,
); err != nil {
logger.Fatalf("FATAL: Failed to update password: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "Password for '%s' updated via config utility.", account.Username)
fmt.Printf("Password for \"%s\" updated successfully.\n", account.Username)
app.Log.Info(model.LOG_ACCOUNT, "Password for '%s' updated via config utility.", account.Username)
logger.Printf("Password for \"%s\" updated successfully.\n", account.Username)
return
case "deleteAccount":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "FATAL: `username` must be specified for deleteAccount\n")
os.Exit(1)
logger.Fatalf("FATAL: `username` must be specified for deleteAccount\n")
}
username := os.Args[2]
fmt.Printf("Deleting account \"%s\"...\n", username)
logger.Printf("Deleting account \"%s\"...\n", username)
account, err := controller.GetAccountByUsername(app.DB, username)
account, err := app.AccountService.GetByUsername(username)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch account \"%s\": %v\n", username, err)
}
if account == nil {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" does not exist.\n", username)
}
fmt.Printf("You are about to delete \"%s\". Are you sure? (y/[N]): ", account.Username)
logger.Printf("You are about to delete \"%s\". Are you sure? (y/[N]): ", account.Username)
res := ""
fmt.Scanln(&res)
if !strings.HasPrefix(res, "y") {
return
}
err = controller.DeleteAccount(app.DB, account.ID)
err = app.AccountService.Delete(account.ID)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to delete account: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to delete account: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "Account '%s' deleted via config utility.", account.Username)
fmt.Printf("Account \"%s\" deleted successfully.\n", account.Username)
app.Log.Info(model.LOG_ACCOUNT, "Account '%s' deleted via config utility.", account.Username)
logger.Printf("Account \"%s\" deleted successfully.\n", account.Username)
return
case "lockAccount":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "FATAL: `username` must be specified for lockAccount\n")
os.Exit(1)
logger.Fatalf("FATAL: `username` must be specified for lockAccount\n")
}
username := os.Args[2]
fmt.Printf("Unlocking account \"%s\"...\n", username)
logger.Printf("Unlocking account \"%s\"...\n", username)
account, err := controller.GetAccountByUsername(app.DB, username)
account, err := app.AccountService.GetByUsername(username)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch account \"%s\": %v\n", username, err)
}
if account == nil {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" does not exist.\n", username)
}
err = controller.LockAccount(app.DB, account.ID)
err = app.AccountService.Lock(account.ID)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to lock account: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to lock account: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "Account '%s' locked via config utility.", account.Username)
fmt.Printf("Account \"%s\" locked successfully.\n", account.Username)
app.Log.Info(model.LOG_ACCOUNT, "Account '%s' locked via config utility.", account.Username)
logger.Printf("Account \"%s\" locked successfully.\n", account.Username)
return
case "unlockAccount":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "FATAL: `username` must be specified for unlockAccount\n")
os.Exit(1)
logger.Fatalf("FATAL: `username` must be specified for unlockAccount\n")
}
username := os.Args[2]
fmt.Printf("Unlocking account \"%s\"...\n", username)
logger.Printf("Unlocking account \"%s\"...\n", username)
account, err := controller.GetAccountByUsername(app.DB, username)
account, err := app.AccountService.GetByUsername(username)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch account \"%s\": %v\n", username, err)
}
if account == nil {
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
os.Exit(1)
logger.Fatalf("FATAL: Account \"%s\" does not exist.\n", username)
}
err = controller.UnlockAccount(app.DB, account.ID)
err = app.AccountService.Unlock(account.ID)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to unlock account: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to unlock account: %v\n", err)
}
app.Log.Info(log.TYPE_ACCOUNT, "Account '%s' unlocked via config utility.", account.Username)
fmt.Printf("Account \"%s\" unlocked successfully.\n", account.Username)
app.Log.Info(model.LOG_ACCOUNT, "Account '%s' unlocked via config utility.", account.Username)
logger.Printf("Account \"%s\" unlocked successfully.\n", account.Username)
return
case "logs":
// TODO: add log search parameters
logs, err := app.Log.Search([]log.LogLevel{}, []string{}, "", 100, 0)
logs, err := app.Log.Search([]model.LogLevel{}, []string{}, "", 100, 0)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch logs: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to fetch logs: %v\n", err)
}
for _, item := range(logs) {
levelStr := ""
switch item.Level {
case log.LEVEL_INFO:
case model.LEVEL_INFO:
levelStr = "INFO"
case log.LEVEL_WARN:
case model.LEVEL_WARN:
levelStr = "WARN"
default:
levelStr = fmt.Sprintf("? (%d)", item.Level)
}
fmt.Printf("[%s] %s:\n\t[%s] %s: %s\n", item.CreatedAt.Format(time.UnixDate), item.ID, item.Type, levelStr, item.Content)
logger.Printf("[%s] %s:\n\t[%s] %s: %s\n", item.CreatedAt.Format(time.UnixDate), item.ID, item.Type, levelStr, item.Content)
}
return
}
@ -464,68 +446,66 @@ func main() {
}
// handle DB migrations
controller.CheckDBVersionAndMigrate(app.DB)
if psqlDB != nil {
repo.CheckDBVersionAndMigrate(psqlDB)
}
if app.Config.Twitch != nil {
err = controller.TwitchSetup(&app)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to set up Twitch integration: %v\n", err)
logger.Printf("WARN: Failed to set up Twitch integration: %v\n", err)
}
}
// initial invite code
accountsCount := 0
err = app.DB.Get(&accountsCount, "SELECT count(*) FROM account")
accountsCount, err := app.AccountService.GetCount()
if err != nil { panic(err) }
if accountsCount == 0 {
_, err := app.DB.Exec("DELETE FROM invite")
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear existing invite codes: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to clear existing invite codes: %v\n", err)
}
invite, err := controller.CreateInvite(app.DB, 16, time.Hour * 24)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to create invite code: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to create invite code: %v\n", err)
}
fmt.Printf("No accounts exist! Generated invite code: %s\n", invite.Code)
logger.Printf("No accounts exist! Generated invite code: %s\n", invite.Code)
}
// delete expired sessions
err = controller.DeleteExpiredSessions(app.DB)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear expired sessions: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to clear expired sessions: %v\n", err)
}
// delete expired invites
err = controller.DeleteExpiredInvites(app.DB)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear expired invite codes: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to clear expired invite codes: %v\n", err)
}
// clean up unconfirmed TOTP methods
err = controller.DeleteUnconfirmedTOTPs(app.DB)
if err != nil {
fmt.Fprintf(os.Stderr, "FATAL: Failed to clean up unconfirmed TOTP methods: %v\n", err)
os.Exit(1)
logger.Fatalf("FATAL: Failed to clean up unconfirmed TOTP methods: %v\n", err)
}
go cursor.StartCursor(&app)
httpLogger := log.New(os.Stderr, "http", DEFAULT_LOG_FLAGS)
// start the web server!
mux := createServeMux(&app)
fmt.Printf("Now serving at http://%s:%d\n", app.Config.Host, app.Config.Port)
logger.Printf("Now serving at http://%s:%d\n", app.Config.Host, app.Config.Port)
stdLog.Fatal(
http.ListenAndServe(fmt.Sprintf("%s:%d", app.Config.Host, app.Config.Port),
CheckRequest(&app, HTTPLog(DefaultHeaders(mux))),
CheckRequest(&app, httpLogger, HTTPLog(httpLogger, DefaultHeaders(mux))),
))
}
func createServeMux(app *model.AppState) *http.ServeMux {
func createServeMux(app *app.AppState) *http.ServeMux {
mux := http.NewServeMux()
mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app)))
@ -568,7 +548,7 @@ var PoweredByStrings = []string{
"30 billion dollars in VC funding",
}
func CheckRequest(app *model.AppState, next http.Handler) http.Handler {
func CheckRequest(app *app.AppState, log *log.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// requests with empty user agents are considered suspicious.
// every browser supplies them; hell, even curl supplies them.
@ -585,8 +565,7 @@ func CheckRequest(app *model.AppState, next http.Handler) http.Handler {
if strings.HasSuffix(r.URL.Path, ".php") ||
strings.HasSuffix(r.URL.Path, ".php7") {
http.NotFound(w, r)
fmt.Fprintf(
os.Stderr,
log.Printf(
"WARN: Suspicious activity blocked: {\"path\":\"%s\",\"address\":\"%s\"}\n",
r.URL.Path,
r.RemoteAddr,
@ -636,7 +615,7 @@ func (lrw *LoggingResponseWriter) WriteHeader(status int) {
lrw.ResponseWriter.WriteHeader(status)
}
func HTTPLog(next http.Handler) http.Handler {
func HTTPLog(log *log.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
@ -658,7 +637,7 @@ func HTTPLog(next http.Handler) http.Handler {
if lrw.Status - 400 <= 0 { statusColour = colour.White }
if lrw.Status - 300 <= 0 { statusColour = colour.Green }
fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n",
log.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n",
after.Format(time.UnixDate),
r.Method,
r.URL.Path,