2023-09-06 22:20:09 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/rand"
|
|
|
|
"database/sql"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
2023-12-19 00:01:08 +00:00
|
|
|
"github.com/1f349/mjwt"
|
2024-01-29 23:45:46 +00:00
|
|
|
clientStore "github.com/1f349/tulip/client-store"
|
|
|
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
|
|
|
"github.com/1f349/tulip/cmd/red-tulip/server"
|
2023-09-06 22:20:09 +01:00
|
|
|
"github.com/1f349/tulip/database"
|
2023-10-09 20:31:41 +01:00
|
|
|
"github.com/1f349/tulip/mail/templates"
|
2024-01-29 23:45:46 +00:00
|
|
|
"github.com/1f349/tulip/oauth"
|
|
|
|
"github.com/1f349/tulip/openid"
|
2023-09-06 22:20:09 +01:00
|
|
|
"github.com/1f349/violet/utils"
|
|
|
|
"github.com/MrMelon54/exit-reload"
|
|
|
|
"github.com/google/subcommands"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
)
|
|
|
|
|
|
|
|
type serveCmd struct{ configPath string }
|
|
|
|
|
|
|
|
func (s *serveCmd) Name() string { return "serve" }
|
|
|
|
|
|
|
|
func (s *serveCmd) Synopsis() string { return "Serve user authentication service" }
|
|
|
|
|
|
|
|
func (s *serveCmd) SetFlags(f *flag.FlagSet) {
|
|
|
|
f.StringVar(&s.configPath, "conf", "", "/path/to/config.json : path to the config file")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *serveCmd) Usage() string {
|
|
|
|
return `serve [-conf <config file>]
|
|
|
|
Serve user authentication service using information from the config file
|
|
|
|
`
|
|
|
|
}
|
|
|
|
|
2023-09-09 01:38:10 +01:00
|
|
|
func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcommands.ExitStatus {
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Println("[RedTulip] Starting...")
|
2023-09-06 22:20:09 +01:00
|
|
|
|
|
|
|
if s.configPath == "" {
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Println("[RedTulip] Error: config flag is missing")
|
2023-09-06 22:20:09 +01:00
|
|
|
return subcommands.ExitUsageError
|
|
|
|
}
|
|
|
|
|
2024-01-29 23:45:46 +00:00
|
|
|
var conf server.Conf
|
|
|
|
err := loadConfig(s.configPath, &conf)
|
2023-09-06 22:20:09 +01:00
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Println("[RedTulip] Error: missing config file")
|
2023-09-06 22:20:09 +01:00
|
|
|
} else {
|
2024-01-29 23:45:46 +00:00
|
|
|
log.Println("[RedTulip] Error: loading config file: ", err)
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
|
|
|
return subcommands.ExitFailure
|
|
|
|
}
|
|
|
|
|
|
|
|
configPathAbs, err := filepath.Abs(s.configPath)
|
|
|
|
if err != nil {
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Fatal("[RedTulip] Failed to get absolute config path")
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
|
|
|
wd := filepath.Dir(configPathAbs)
|
|
|
|
|
2024-01-29 23:45:46 +00:00
|
|
|
signer, err := mjwt.NewMJwtSignerFromFileOrCreate(conf.OtpIssuer, filepath.Join(wd, "red-tulip.key.pem"), rand.Reader, 4096)
|
2023-12-19 00:01:08 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal("[Tulip] Failed to open signing key file:", err)
|
|
|
|
}
|
2023-09-06 22:20:09 +01:00
|
|
|
|
2024-01-29 23:45:46 +00:00
|
|
|
db, err := database.Open(filepath.Join(wd, "red-tulip.db.sqlite"))
|
2023-09-06 22:20:09 +01:00
|
|
|
if err != nil {
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Fatal("[RedTulip] Failed to open database:", err)
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
|
|
|
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Println("[RedTulip] Checking database contains at least one user")
|
2023-09-06 22:20:09 +01:00
|
|
|
if err := checkDbHasUser(db); err != nil {
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Fatal("[RedTulip] Failed check:", err)
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
|
|
|
|
2024-01-29 23:45:46 +00:00
|
|
|
if err = pages.LoadPages(wd); err != nil {
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Fatal("[RedTulip] Failed to load page templates:", err)
|
2023-10-09 20:31:41 +01:00
|
|
|
}
|
|
|
|
if err := templates.LoadMailTemplates(wd); err != nil {
|
2024-01-29 10:44:45 +00:00
|
|
|
log.Fatal("[RedTulip] Failed to load mail templates:", err)
|
2023-10-09 20:31:41 +01:00
|
|
|
}
|
|
|
|
|
2024-01-29 23:45:46 +00:00
|
|
|
openIdConf := openid.GenConfig(conf.BaseUrl, []string{"openid", "name", "username", "profile", "email", "birthdate", "age", "zoneinfo", "locale"}, []string{"sub", "name", "preferred_username", "profile", "picture", "website", "email", "email_verified", "gender", "birthdate", "zoneinfo", "locale", "updated_at"})
|
|
|
|
controller := oauth.NewOAuthController(signer, &server.RedAuthSource{DB: db}, clientStore.New(db), openIdConf)
|
|
|
|
|
|
|
|
srv := server.NewHttpServer(conf, db, controller, signer)
|
|
|
|
log.Printf("[RedTulip] Starting HTTP server on '%s'\n", srv.Addr)
|
2023-09-06 22:20:09 +01:00
|
|
|
go utils.RunBackgroundHttp("HTTP", srv)
|
|
|
|
|
2024-01-29 10:44:45 +00:00
|
|
|
exit_reload.ExitReload("RedTulip", func() {}, func() {
|
2024-01-29 23:45:46 +00:00
|
|
|
// stop http server
|
2023-09-08 00:30:03 +01:00
|
|
|
_ = srv.Close()
|
|
|
|
_ = db.Close()
|
2023-09-06 22:20:09 +01:00
|
|
|
})
|
2024-01-29 23:45:46 +00:00
|
|
|
|
|
|
|
return subcommands.ExitSuccess
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
|
|
|
|
2024-01-29 23:45:46 +00:00
|
|
|
func loadConfig(configPath string, conf *server.Conf) error {
|
|
|
|
openConf, err := os.Open(configPath)
|
2023-09-06 22:20:09 +01:00
|
|
|
if err != nil {
|
2024-01-29 23:45:46 +00:00
|
|
|
return err
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
2024-01-29 23:45:46 +00:00
|
|
|
|
|
|
|
return json.NewDecoder(openConf).Decode(conf)
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func checkDbHasUser(db *database.DB) error {
|
|
|
|
tx, err := db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to start transaction: %w", err)
|
|
|
|
}
|
2023-09-08 00:30:03 +01:00
|
|
|
defer tx.Rollback()
|
2023-09-06 22:20:09 +01:00
|
|
|
if err := tx.HasUser(); err != nil {
|
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
2023-10-10 18:06:43 +01:00
|
|
|
_, err := tx.InsertUser("Admin", "admin", "admin", "admin@localhost", false, database.RoleAdmin, false)
|
2023-09-06 22:20:09 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to add user: %w", err)
|
|
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
|
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
|
|
}
|
|
|
|
// continue normal operation now
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("failed to check if table has a user: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|