summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--conf/create.go83
-rw-r--r--conf/delete.go54
-rw-r--r--conf/endpoint.go.bak98
-rw-r--r--conf/go.mod16
-rw-r--r--conf/go.sum8
-rw-r--r--conf/list.go69
-rw-r--r--conf/main.go96
-rw-r--r--conf/user.go.bak127
-rw-r--r--config.json6
-rw-r--r--config/config.go44
-rw-r--r--db/db.go207
-rw-r--r--db/endpoint.go96
-rw-r--r--db/go.mod8
-rw-r--r--db/go.sum4
-rw-r--r--db/session.go139
-rw-r--r--db/user.go165
-rw-r--r--go.mod3
-rw-r--r--main.go25
-rw-r--r--phlox/go.mod16
-rw-r--r--phlox/go.sum20
-rw-r--r--phlox/login.go119
-rw-r--r--phlox/main.go178
23 files changed, 79 insertions, 1506 deletions
diff --git a/.gitignore b/.gitignore
index d186e2e..4dbe037 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1 @@
-phlox/phlox*
-conf/phloxconf*
-conf/phlox.conf
+phlox*
diff --git a/conf/create.go b/conf/create.go
deleted file mode 100644
index 201149a..0000000
--- a/conf/create.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package main
-
-
-import (
- "fmt"
- "log"
- "strings"
- "syscall"
- "golang.org/x/term"
-)
-
-
-func hiddenPrompt(prompt string) (string, error) {
- fmt.Print(prompt)
- bytes, err := term.ReadPassword(int(syscall.Stdin))
- if err != nil {
- return "", err
- }
- fmt.Println()
-
- return strings.TrimSpace(string(bytes)), nil
-}
-
-
-var createUser = &Command{
- Name: "user",
- Execute: func(args []string) {
- if len(args) < 1 {
- fmt.Println("error: no username provided")
- return;
- }
- username := args[0]
- var password string
- var err error
- if len(args) == 1 {
- password, err = hiddenPrompt("Enter password: ")
- if err != nil { log.Fatal(err) }
- confirm, err := hiddenPrompt("Confirm password: ")
- if err != nil { log.Fatal(err) }
- if password != confirm {
- fmt.Println("passwords do not match!")
- return
- }
- } else {
- password = args[1]
- }
-
- _, err = p.CreateUser(username, password)
- if err != nil { log.Fatal(err) }
- fmt.Printf("created user %v\n", username)
- },
-}
-
-
-var createEndpoint = &Command{
- Name: "endpoint",
- Execute: func(args []string) {
- if len(args) < 3 {
- fmt.Println("error: name, URI, and address must be specified!")
- return
- }
-
- name := args[0]
- path := args[1]
- addr := args[2]
-
- _, err := p.CreateEndpoint(name, path, addr)
- if err != nil { log.Fatal(err) }
- fmt.Printf("created endpoint %v\n", name)
- },
-}
-
-
-func CreateInit(parser *Command) {
- create := &Command{
- Name: "create",
- Branch: true,
- }
-
- create.AddCommand(createUser)
- create.AddCommand(createEndpoint)
- parser.AddCommand(create)
-}
diff --git a/conf/delete.go b/conf/delete.go
deleted file mode 100644
index 384e392..0000000
--- a/conf/delete.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
-)
-
-
-var rmUser = &Command{
- Name: "user",
- Execute: func(args []string) {
- if len(args) < 1 {
- fmt.Println("error: username not provided")
- return
- }
- username := args[0]
- user, err := p.GetByUsername(username)
- if err != nil { log.Fatal(err) }
- err = p.DeleteUser(user)
- if err != nil { log.Fatal(err) }
- fmt.Printf("deleted user %v\n", username)
- },
-}
-
-
-var rmEndpoint = &Command{
- Name: "endpoint",
- Execute: func(args []string) {
- if len(args) < 1 {
- fmt.Println("error: endpointname not provided")
- return
- }
- name := args[0]
- endpoint, err := p.GetEndpointByName(name)
- if err != nil { log.Fatal(err) }
- err = p.DeleteEndpoint(endpoint)
- if err != nil { log.Fatal(err) }
- fmt.Printf("deleted endpoint %v\n", name)
- },
-}
-
-
-
-
-func RmInit(parser *Command) {
- rm := &Command{
- Name: "rm",
- Branch: true,
- }
-
- rm.AddCommand(rmUser)
- rm.AddCommand(rmEndpoint)
- parser.AddCommand(rm)
-}
diff --git a/conf/endpoint.go.bak b/conf/endpoint.go.bak
deleted file mode 100644
index 1f8fa11..0000000
--- a/conf/endpoint.go.bak
+++ /dev/null
@@ -1,98 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- db "sanine.net/git/phlox/db"
-)
-
-
-func CreateEndpoint() *Command {
- return &Command{
- Flag: "create-endpoint",
- Usage: "create a new endpoint",
- Invoke: func() {
- OpenDb()
-
- name := ReadLine("Endpoint Name: ")
- path := ReadLine("Endpoint Path: ")
- address := ReadLine("Endpoint Address: ")
- _, err := p.CreateEndpoint(name, path, address)
- if err != nil { log.Fatal(err) }
- fmt.Printf("created endpoint %v\n", name)
- },
- }
-}
-
-
-func GetEndpoint() db.Endpoint {
- OpenDb()
- name := ReadLine("Endpoint Name: ")
- endpoint, err := p.GetEndpointByName(name)
- if err != nil { log.Fatal(err) }
- return endpoint
-}
-
-
-func DeleteEndpoint() *Command {
- return &Command{
- Flag: "delete-endpoint",
- Usage: "delete an existing endpoint",
- Invoke: func() {
- endpoint := GetEndpoint()
- err := p.DeleteEndpoint(endpoint)
- if err != nil { log.Fatal(err) }
- fmt.Printf("deleted endpoint %v\n", endpoint.Name)
- },
- }
-}
-
-
-func SetPath() *Command {
- return &Command{
- Flag: "set-endpoint-path",
- Usage: "set the path of an existing endpoint",
- Invoke: func() {
- endpoint := GetEndpoint()
- fmt.Printf("Current Path: %v\n", endpoint.Path)
- path := ReadLine("New Path: ")
- err := p.SetEndpointPath(endpoint, path)
- if err != nil { log.Fatal(err) }
- fmt.Printf("updated path for endpoint %v\n", endpoint.Name)
- },
- }
-}
-
-
-func SetAddress() *Command {
- return &Command{
- Flag: "set-endpoint-address",
- Usage: "set the address of an existing endpoint",
- Invoke: func() {
- endpoint := GetEndpoint()
- fmt.Printf("Current Address: %v\n", endpoint.Address)
- address := ReadLine("New Address: ")
- err := p.SetEndpointAddress(endpoint, address)
- if err != nil { log.Fatal(err) }
- fmt.Printf("updated address for endpoint %v\n", endpoint.Name)
- },
- }
-}
-
-
-func ListEndpoints() *Command {
- return &Command{
- Flag: "list-endpoints",
- Usage: "list all endpoints",
- Invoke: func() {
- OpenDb()
- endpoints, err := p.AllEndpoints()
- if err != nil { log.Fatal(err) }
- fmt.Println("id\t\tname\t\tpath\t\taddress")
- fmt.Println("================================================================================")
- for _, endpoint := range endpoints {
- fmt.Printf("%v\t\t%v\t\t%v\t\t%v\n", endpoint.Id, endpoint.Name, endpoint.Path, endpoint.Address)
- }
- },
- }
-}
diff --git a/conf/go.mod b/conf/go.mod
deleted file mode 100644
index 7ec1d45..0000000
--- a/conf/go.mod
+++ /dev/null
@@ -1,16 +0,0 @@
-module sanine.net/git/phlox/phloxconf
-
-go 1.19
-
-replace sanine.net/git/phlox/db => ../db
-
-require (
- golang.org/x/term v0.7.0
- sanine.net/git/phlox/db v0.0.0-00010101000000-000000000000
-)
-
-require (
- github.com/mattn/go-sqlite3 v1.14.16 // indirect
- golang.org/x/crypto v0.8.0 // indirect
- golang.org/x/sys v0.7.0 // indirect
-)
diff --git a/conf/go.sum b/conf/go.sum
deleted file mode 100644
index bd19da9..0000000
--- a/conf/go.sum
+++ /dev/null
@@ -1,8 +0,0 @@
-github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
-github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
-golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
-golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
diff --git a/conf/list.go b/conf/list.go
deleted file mode 100644
index 4fa3829..0000000
--- a/conf/list.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package main
-
-import(
- "fmt"
- "log"
- "time"
-)
-
-
-var listUsers = Command{
- Name: "users",
- Execute: func([]string) {
- users, err := p.AllUsers()
- if err != nil {
- log.Fatal(err)
- }
- fmt.Printf("id\tname\n========================================\n")
- for _, user := range users {
- fmt.Printf("%v\t%v\n", user.Id, user.Name)
- }
- },
-}
-
-
-var listSessions = Command{
- Name: "sessions",
- Execute: func([]string) {
- sessions, err := p.AllSessions()
- if err != nil {
- log.Fatal(err)
- }
- // padding on created and modified to match RFC3339 format
- fmt.Printf("user\t\tcreated \tmodified \tid\n================================================================================\n")
- for _, session := range sessions {
- user, err := p.GetById(session.UserId)
- if err != nil {
- log.Fatal(err)
- }
- fmt.Printf("%v\t\t%v\t%v\t%v\n", user.Name, session.Created.Format(time.RFC3339), session.Modified.Format(time.RFC3339), session.Id)
- }
- },
-}
-
-
-var listEndpoints = Command{
- Name: "endpoints",
- Execute: func([]string) {
- endpoints, err := p.AllEndpoints()
- if err != nil { log.Fatal(err) }
-
- fmt.Printf("name\tpath\taddress\n========================================\n")
- for _, endpoint := range endpoints {
- fmt.Printf("%v\t%v\t%v\n", endpoint.Name, endpoint.Path, endpoint.Address)
- }
- },
-}
-
-
-func ListInit(parser *Command) {
- list := &Command{
- Name: "list",
- Branch: true,
- }
-
- list.AddCommand(&listUsers)
- list.AddCommand(&listSessions)
- list.AddCommand(&listEndpoints)
- parser.AddCommand(list)
-}
diff --git a/conf/main.go b/conf/main.go
deleted file mode 100644
index 5dc922c..0000000
--- a/conf/main.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package main
-
-import (
- "os"
- "fmt"
- "log"
- "flag"
-// "strings"
-// "bufio"
- db "sanine.net/git/phlox/db"
-)
-
-
-var Params struct {
- InitDb string
- Db string
-}
-
-
-var p *db.Phlox
-
-type Command struct {
- Name string
- Help string
- subcommands map[string]*Command
- Branch bool
- Execute func([]string)
-}
-
-func (c *Command) errNoSubCommand() {
- fmt.Println("error: a subcommand was required but not provided.")
- fmt.Println("available options are:\n")
- for name := range c.subcommands {
- fmt.Printf("\t* %v\n", name)
- }
- fmt.Println()
-}
-
-func (c *Command) Parse(args []string) {
- if !c.Branch {
- c.Execute(args)
- } else {
- if len(args) > 0 {
- cn, ok := c.subcommands[args[0]]
- if ok {
- cn.Parse(args[1:])
- } else {
- c.errNoSubCommand()
- }
- } else {
- c.errNoSubCommand()
- }
- }
-}
-
-
-func (c *Command) AddCommand(cn *Command) {
- if c.subcommands == nil {
- c.subcommands = make(map[string]*Command)
- }
- c.subcommands[cn.Name] = cn
-}
-
-
-var parser *Command
-
-
-func main() {
- p = &db.Phlox{}
-
- flag.StringVar(&Params.InitDb, "init", "", "initialize a new database at the given path")
- flag.StringVar(&Params.Db, "db", "/etc/phlox/phlox.conf", "open the database at the given path for processing")
- flag.Parse()
-
- if Params.InitDb != "" {
- // initialize a new db
- err := p.Create(Params.InitDb)
- if err != nil { log.Fatal(err) }
- fmt.Printf("created new database at %v\n", Params.InitDb)
- p.Close()
- os.Exit(0)
- }
-
- err := p.Open(Params.Db)
- if err != nil {
- log.Fatal(err)
- }
- defer p.Close()
-
- parser = &Command{Branch: true}
- ListInit(parser)
- CreateInit(parser)
- RmInit(parser)
-
- parser.Parse(flag.Args())
-}
diff --git a/conf/user.go.bak b/conf/user.go.bak
deleted file mode 100644
index 5ceaefb..0000000
--- a/conf/user.go.bak
+++ /dev/null
@@ -1,127 +0,0 @@
-package main
-
-import (
- "os"
- "fmt"
- "log"
- "strings"
- "bufio"
- "syscall"
- "golang.org/x/term"
- db "sanine.net/git/phlox/db"
-)
-
-
-func GetUsername(prompt string) (string, error) {
- reader := bufio.NewReader(os.Stdin)
-
- fmt.Print(prompt)
- username, err := reader.ReadString('\n')
- if err != nil {
- return "", err
- }
-
- return strings.TrimSpace(username), nil
-}
-
-
-func GetPassword(prompt string) (string, error) {
- fmt.Print(prompt)
- bytes, err := term.ReadPassword(int(syscall.Stdin))
- if err != nil {
- return "", err
- }
-
- password := strings.TrimSpace(string(bytes))
- return password, nil
-}
-
-
-func ConfirmPassword() string {
- password, err := GetPassword("Enter Password: ")
- if err != nil { log.Fatal(err) }
- fmt.Println()
- password1, err := GetPassword("Confirm Password: ")
- if err != nil { log.Fatal(err) }
- fmt.Println()
- if password != password1 { log.Fatal("passwords do not match!") }
- return password
-}
-
-
-func GetUser() db.User {
- OpenDb()
- username, err := GetUsername("Username: ")
- if err != nil { log.Fatal(err) }
- user, err := p.GetByUsername(username)
- if err != nil { log.Fatal(err) }
- return user
-}
-
-
-func CreateUser() *Command {
- return &Command{
- Flag: "create-user",
- Usage: "create a new user",
- Invoke: func() {
- OpenDb()
-
- username, err := GetUsername("Enter Username: ")
- if err != nil { log.Fatal(err) }
- password := ConfirmPassword()
-
- _, err = p.CreateUser(username, password)
- if err != nil { log.Fatal(err) }
- fmt.Printf("created user %v\n", username)
- },
- }
-}
-
-
-func DeleteUser() *Command {
- return &Command{
- Flag: "delete-user",
- Usage: "delete an existing user",
- Invoke: func() {
- user := GetUser()
- err := p.DeleteUser(user)
- if err != nil { log.Fatal(err) }
- fmt.Printf("deleted user %v\n", user.Name)
- },
- }
-}
-
-
-func UpdatePassword() *Command {
- return &Command{
- Flag: "update-password",
- Usage: "update a user's password",
- Invoke: func() {
- user := GetUser()
- password := ConfirmPassword()
- err := p.SetPassword(user, password)
- if err != nil { log.Fatal(err) }
- fmt.Printf("updated password for user %v\n", user.Name)
- },
- }
-}
-
-
-func ListUsers() *Command {
- return &Command{
- Flag: "list-users",
- Usage: "list all users",
- Invoke: func() {
- OpenDb()
- users, err := p.AllUsers()
- if err != nil { log.Fatal(err) }
- fmt.Println("id\t\tname")
- fmt.Println("========================================")
- for _, user := range users {
- fmt.Printf(
- "%v\t\t%v\n", user.Id, user.Name,
- )
- }
- },
- }
-}
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..16de309
--- /dev/null
+++ b/config.json
@@ -0,0 +1,6 @@
+{
+ "ListenAddress": "localhost:3333",
+ "AssetDirectory": "",
+ "Users": [],
+ "Endpoints": []
+}
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..8a22f9d
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,44 @@
+package config
+
+import (
+ "os"
+ "encoding/json"
+)
+
+
+type User struct {
+ Id string
+ Name string
+ PasswordHash []byte
+ Salt []byte
+}
+
+
+type Endpoint struct {
+ Path string
+ Address string
+}
+
+
+type Config struct {
+ ListenAddress string
+ AssetDirectory string
+ Users []User
+ Endpoints []Endpoint
+}
+
+
+func Load(filename string) (Config, error) {
+ blob, err := os.ReadFile(filename)
+ if err != nil {
+ return Config{}, err
+ }
+
+ config := Config{}
+ err = json.Unmarshal(blob, &config)
+ if err != nil {
+ return Config{}, err
+ }
+
+ return config, nil
+}
diff --git a/db/db.go b/db/db.go
deleted file mode 100644
index 55ef81b..0000000
--- a/db/db.go
+++ /dev/null
@@ -1,207 +0,0 @@
-package db
-
-import (
- "os"
- "time"
- "io/fs"
- "errors"
- "database/sql"
- _ "github.com/mattn/go-sqlite3"
-)
-
-
-type User struct {
- Id int
- Name string
- PasswordHash []byte
- Salt []byte
-}
-
-
-type Session struct {
- Id string
- UserId int
- Created time.Time
- Modified time.Time
-}
-
-type Endpoint struct {
- Id int
- Name string
- Path string
- Address string
-}
-
-
-type Model interface {
- Create(filename string) error
- Open(filename string) error
- Close() error
-
- GetSchemaVersion() (int, error)
- SetHostAddress(addr string) error
- GetHostAddress() (string, error)
- SetLoginPage(path string) error
- GetLoginPage() (string, error)
-
- CreateUser(username, password string) (User, error)
- DeleteUser(user User) error
- SetPassword(user User, password string) error
- AuthenticateUser(username, password string) (User, error)
- GetByUsername(username string) (User, error)
- GetById(id string) (User, error)
- AllUsers() ([]User, error)
-
- CreateSession(user User) (Session, error)
- DeleteSession(session Session) error
- TouchSession(session Session) error
- CheckSession(session Session) (bool, error)
- CheckSessionId(id string) (bool, error)
- CleanSessions(maxIdle time.Duration) error
- AllSessions() ([]Session, error)
-
- CreateEndpoint(name, path, address string) (Endpoint, error)
- DeleteEndpoint(endpoint Endpoint) error
- SetEndpointPath(endpoint Endpoint, path string) error
- SetEndpointAddress(endpoint Endpoint, address string) error
- GetEndpointByName(name string) (Endpoint, error)
- GetEndpointByPath(path string) (Endpoint, error)
- GetEndpointByAddress(address string) (Endpoint, error)
- AllEndpoints() ([]Endpoint, error)
-}
-
-
-// interface to wrap sql.Row and sql.Rows
-type Scanner interface {
- Scan(dest ...any) error
-}
-
-
-type Phlox struct {
- db *sql.DB
-}
-
-
-func fileExists(filename string) (bool, error) {
- _, err := os.Stat(filename)
- if err == nil {
- return true, nil
- } else if errors.Is(err, fs.ErrNotExist) {
- return false, nil
- } else {
- // unknown error
- return false, err
- }
-}
-
-
-func (p *Phlox) Create(filename string) error {
- exist, err := fileExists(filename)
- if err != nil {
- return err
- }
-
- if exist {
- // file already exists
- return fs.ErrExist
- }
-
- p.db, err = sql.Open("sqlite3", filename)
- if err != nil {
- return err
- }
-
- _, err = p.db.Exec(`
- create table schema(version integer);
- insert into schema values(0);
-
- create table config(key string primary key, value string);
- insert into config values("hostaddress", "localhost:3333");
- insert into config values("loginpage", "");
-
- create table users (userid integer not null primary key, username string unique, passwordhash string, salt string);
- create table sessions (sessionid string not null primary key, userid integer, created string, modified string, foreign key(userid) references users(userid));
- create table endpoints (endpointid integer not null primary key, name string, path string, address string);
- `)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-
-func (p *Phlox) Open(filename string) error {
- exist, err := fileExists(filename)
- if err != nil {
- return err
- }
-
- if !exist {
- // no such file!
- return fs.ErrNotExist
- }
-
- p.db, err = sql.Open("sqlite3", filename)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-
-func (p *Phlox) Close() error {
- err := p.db.Close()
- return err
-}
-
-
-func (p *Phlox) GetSchemaVersion() (int, error) {
- row := p.db.QueryRow("select max(version) from schema;")
- if row.Err() != nil {
- return -1, row.Err()
- }
-
- var version int
- err := row.Scan(&version)
- if err != nil {
- return -1, err
- }
-
- return version, nil
-}
-
-
-func (p *Phlox) SetHostAddress(addr string) error {
- _, err := p.db.Exec("update config set value=? where key='hostaddress';", addr)
- return err
-}
-
-
-func (p *Phlox) GetHostAddress() (string, error) {
- row := p.db.QueryRow("select value from config where key='hostaddress';")
- var addr string
- err := row.Scan(&addr)
- if err != nil {
- return "", err
- }
- return addr, nil
-}
-
-
-func (p *Phlox) SetLoginPage(path string) error {
- _, err := p.db.Exec("update config set value=? where key='loginpage';", path)
- return err
-}
-
-
-func (p *Phlox) GetLoginPage() (string, error) {
- row := p.db.QueryRow("select value from config where key='loginpage';")
- var path string
- err := row.Scan(&path)
- if err != nil {
- return "", err
- }
- return path, nil
-}
diff --git a/db/endpoint.go b/db/endpoint.go
deleted file mode 100644
index d880510..0000000
--- a/db/endpoint.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package db
-
-func (p *Phlox) CreateEndpoint(name, path, address string) (Endpoint, error) {
- var id int
- row := p.db.QueryRow("select coalesce(max(endpointid), 0) from endpoints;")
- err := row.Scan(&id)
- if err != nil {
- return Endpoint{}, err
- }
- id += 1
-
- _, err = p.db.Exec(
- "insert into endpoints values (?, ?, ?, ?)",
- id, name, path, address,
- )
- if err != nil {
- return Endpoint{}, err
- }
-
- endpoint := Endpoint{
- Id: id,
- Name: name,
- Path: path,
- Address: address,
- }
-
- return endpoint, nil
-}
-
-
-func (p *Phlox) DeleteEndpoint(endpoint Endpoint) error {
- _, err := p.db.Exec("delete from endpoints where endpointid = ?;", endpoint.Id)
- return err
-}
-
-
-func extractEndpoint(s Scanner) (Endpoint, error) {
- var endpoint Endpoint
- err := s.Scan(
- &endpoint.Id,
- &endpoint.Name,
- &endpoint.Path,
- &endpoint.Address,
- )
- return endpoint, err
-}
-
-
-func (p *Phlox) SetEndpointPath(endpoint Endpoint, path string) error {
- _, err := p.db.Exec("update endpoints set path=? where endpointid=?", path, endpoint.Id)
- return err
-}
-
-func (p *Phlox) SetEndpointAddress(endpoint Endpoint, address string) error {
- _, err := p.db.Exec("update endpoints set address=? where endpointid=?", address, endpoint.Id)
- return err
-}
-
-
-func queryEndpoint(p *Phlox, query, param string) (Endpoint, error) {
- row := p.db.QueryRow(query, param)
- endpoint, err := extractEndpoint(row)
- return endpoint, err
-}
-
-func (p *Phlox) GetEndpointByName(name string) (Endpoint, error) {
- return queryEndpoint(p, "select * from endpoints where name = ?;", name)
-}
-
-func (p *Phlox) GetEndpointByPath(path string) (Endpoint, error) {
- return queryEndpoint(p, "select * from endpoints where path = ?;", path)
-}
-
-func (p *Phlox) GetEndpointByAddress(address string) (Endpoint, error) {
- return queryEndpoint(p, "select * from endpoints where address = ?;", address)
-}
-
-
-func (p *Phlox) AllEndpoints() ([]Endpoint, error) {
- endpoints := make([]Endpoint, 0)
- rows, err := p.db.Query("select * from endpoints;")
- if err != nil {
- return endpoints, err
- }
- defer rows.Close()
-
- for rows.Next() {
- endpoint, err := extractEndpoint(rows)
- if err != nil {
- return endpoints, err
- }
- endpoints = append(endpoints, endpoint)
- }
-
- return endpoints, nil
-}
diff --git a/db/go.mod b/db/go.mod
deleted file mode 100644
index 7931102..0000000
--- a/db/go.mod
+++ /dev/null
@@ -1,8 +0,0 @@
-module sanine.net/git/phlox/db
-
-go 1.19
-
-require (
- github.com/mattn/go-sqlite3 v1.14.16
- golang.org/x/crypto v0.8.0
-)
diff --git a/db/go.sum b/db/go.sum
deleted file mode 100644
index 48de3bc..0000000
--- a/db/go.sum
+++ /dev/null
@@ -1,4 +0,0 @@
-github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
-github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
-golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
-golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
diff --git a/db/session.go b/db/session.go
deleted file mode 100644
index 7ebb6df..0000000
--- a/db/session.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package db
-
-import (
- "database/sql"
- "crypto/rand"
- "encoding/base64"
- "errors"
- "time"
-)
-
-
-func (p *Phlox) CreateSession(user User) (Session, error) {
- bytes := make([]byte, 32)
- _, err := rand.Read(bytes)
- if err != nil {
- return Session{}, err
- }
-
- sessionid := base64.StdEncoding.EncodeToString(bytes)
- userid := user.Id
- now := time.Now().UTC()
- nowStr := now.Format(time.RFC3339)
-
- _, err = p.db.Exec(
- "insert into sessions (sessionid, userid, created, modified) values (?, ?, ?, ?);",
- sessionid,
- userid,
- nowStr, nowStr,
- )
- if err != nil {
- return Session{}, err
- }
-
- return Session{
- Id: sessionid,
- UserId: userid,
- Created: now,
- Modified: now,
- }, nil
-}
-
-
-func (p *Phlox) DeleteSession(session Session) error {
- _, err := p.db.Exec("delete from sessions where sessionid = ?;", session.Id)
- return err
-}
-
-
-func extractSession(s Scanner) (Session, error) {
- var (
- session Session
- createdStr string
- modifiedStr string
- )
-
- // scan
- err := s.Scan(&session.Id, &session.UserId, &createdStr, &modifiedStr)
- if err != nil {
- return Session{}, err
- }
-
- // parse times
- session.Created, err = time.Parse(time.RFC3339, createdStr)
- if err != nil {
- return Session{}, err
- }
- session.Modified, err = time.Parse(time.RFC3339, modifiedStr)
- if err != nil {
- return Session{}, err
- }
-
- return session, nil
-}
-
-
-func (p *Phlox) CheckSession(session Session) (bool, error) {
- row := p.db.QueryRow("select * from sessions where sessionid = ?", session.Id)
- session, err := extractSession(row)
- if err != nil {
- if errors.Is(err, sql.ErrNoRows) {
- // no row returned, so invalid session
- return false, nil
- } else {
- // some other error
- return false, err
- }
- }
-
- return true, nil
-}
-
-
-
-func (p *Phlox) CheckSessionId(id string) (bool, error) {
- session := Session{ Id: id }
- return p.CheckSession(session)
-}
-
-
-func (p *Phlox) TouchSession(session Session) error {
- now := time.Now().UTC().Format(time.RFC3339)
- _, err := p.db.Exec(
- "update sessions set modified = ? where sessionid = ?;",
- now, session.Id,
- )
- return err
-}
-
-
-func (p *Phlox) TouchSessionId(id string) error {
- return p.TouchSession(Session{ Id: id })
-}
-
-
-func (p *Phlox) CleanSessions(maxIdle time.Duration) error {
- expire := time.Now().UTC().Add(-maxIdle).Format(time.RFC3339)
- _, err := p.db.Exec("delete from sessions where modified < ?;", expire)
- return err
-}
-
-
-func (p *Phlox) AllSessions() ([]Session, error) {
- sessions := make([]Session, 0)
- rows, err := p.db.Query("select * from sessions;")
- if err != nil {
- return sessions, err
- }
- defer rows.Close()
-
- for rows.Next() {
- session, err := extractSession(rows)
- if err != nil {
- return sessions, err
- }
- sessions = append(sessions, session)
- }
-
- return sessions, nil
-}
diff --git a/db/user.go b/db/user.go
deleted file mode 100644
index 1aff73f..0000000
--- a/db/user.go
+++ /dev/null
@@ -1,165 +0,0 @@
-package db
-
-import (
- "golang.org/x/crypto/argon2"
- "crypto/rand"
- "encoding/base64"
- "database/sql"
-)
-
-
-func getNextUserId(db *sql.DB) (int, error) {
- row := db.QueryRow("select coalesce(max(userid), 0) from users;")
- if row.Err() != nil {
- return -1, row.Err()
- }
-
- var id int
- err := row.Scan(&id)
- if err != nil {
- return -1, err
- }
- return id+1, nil
-}
-
-
-func hashPassword(password string, salt []byte) []byte {
- return argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
-}
-
-
-func (p *Phlox) CreateUser(username, password string) (User, error) {
- user := User{}
-
- userId, err := getNextUserId(p.db)
- if err != nil {
- return user, err
- }
-
- salt := make([]byte, 10)
- _, err = rand.Read(salt)
- if err != nil {
- return user, err
- }
-
- hash := hashPassword(password, salt)
-
- hash64 := base64.StdEncoding.EncodeToString(hash)
- salt64 := base64.StdEncoding.EncodeToString(salt)
-
- _, err = p.db.Exec("insert into users (userid, username, passwordhash, salt) values (?, ?, ?, ?)", userId, username, hash64, salt64)
- if err != nil {
- return user, err
- }
-
- user.Id = userId
- user.Name = username
- user.PasswordHash = hash
- user.Salt = salt
-
- return user, nil
-}
-
-
-
-func (p *Phlox) DeleteUser(user User) error {
- _, err := p.db.Exec("delete from users where userid=?;", user.Id)
- return err
-}
-
-
-
-func (p *Phlox) SetPassword(user User, password string) error {
- hash := hashPassword(password, user.Salt)
- hash64 := base64.StdEncoding.EncodeToString(hash)
-
- _, err := p.db.Exec("update users set passwordhash=? where userid=?;", hash64, user.Id)
- return err
-}
-
-
-
-func extractUser(s Scanner) (User, error) {
- var userid int
- var username string
- var hash64 string
- var salt64 string
- err := s.Scan(&userid, &username, &hash64, &salt64)
- if err != nil {
- return User{}, err
- }
-
- hash, err := base64.StdEncoding.DecodeString(hash64)
- if err != nil {
- return User{}, err
- }
- salt, err := base64.StdEncoding.DecodeString(salt64)
- if err != nil {
- return User{}, err
- }
-
- user := User{
- Id: userid,
- Name: username,
- PasswordHash: hash,
- Salt: salt,
- }
-
- return user, nil
-}
-
-
-func (p *Phlox) AuthenticateUser(username, password string) (bool, User, error) {
- row := p.db.QueryRow("select * from users where username = ?;", username)
- user, err := extractUser(row)
- if err != nil {
- return false, User{}, err
- }
-
- hash := hashPassword(password, user.Salt)
- for i, v := range user.PasswordHash {
- if v != hash[i] { return false, user, nil; }
- }
- return true, user, nil
-}
-
-
-func (p *Phlox) GetByUsername(username string) (User, error) {
- row := p.db.QueryRow("select * from users where username = ?;", username)
- user, err := extractUser(row)
- if err != nil {
- return User{}, err
- }
- return user, nil
-}
-
-
-func (p *Phlox) GetById(id int) (User, error) {
- row := p.db.QueryRow("select * from users where userid = ?;", id)
- user, err := extractUser(row)
- if err != nil {
- return User{}, err
- }
-
- return user, nil
-}
-
-
-func (p *Phlox) AllUsers() ([]User, error) {
- users := make([]User, 0)
- rows, err := p.db.Query("select * from users;")
- if err != nil {
- return users, err
- }
- defer rows.Close()
-
- for rows.Next() {
- user, err := extractUser(rows)
- if err != nil {
- return users, err
- }
- users = append(users, user)
- }
-
- return users, nil
-}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..50ff51b
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module sanine.net/git/phlox
+
+go 1.20
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..6e443a7
--- /dev/null
+++ b/main.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "os"
+ "fmt"
+ "sanine.net/git/phlox/config"
+)
+
+func main() {
+ conf := loadConfig("config.json")
+ fmt.Println(conf.ListenAddress)
+ fmt.Println(conf.AssetDirectory)
+ fmt.Println(len(conf.Users))
+ fmt.Println(len(conf.Endpoints))
+}
+
+
+func loadConfig(filename string) config.Config {
+ conf, err := config.Load(filename)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to load configuration file: %v\n", err.Error())
+ os.Exit(1)
+ }
+ return conf
+}
diff --git a/phlox/go.mod b/phlox/go.mod
deleted file mode 100644
index 6ea0cc1..0000000
--- a/phlox/go.mod
+++ /dev/null
@@ -1,16 +0,0 @@
-module sanine.net/git/phlox/phlox
-
-go 1.19
-
-replace sanine.net/git/phlox/db => ../db
-
-require (
- github.com/sirupsen/logrus v1.9.0
- sanine.net/git/phlox/db v0.0.0-00010101000000-000000000000
-)
-
-require (
- github.com/mattn/go-sqlite3 v1.14.16 // indirect
- golang.org/x/crypto v0.8.0 // indirect
- golang.org/x/sys v0.7.0 // indirect
-)
diff --git a/phlox/go.sum b/phlox/go.sum
deleted file mode 100644
index b99b4dc..0000000
--- a/phlox/go.sum
+++ /dev/null
@@ -1,20 +0,0 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
-github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
-github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
-golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/phlox/login.go b/phlox/login.go
deleted file mode 100644
index 9dd82f1..0000000
--- a/phlox/login.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package main
-
-import (
- "fmt"
- "strings"
- "net/http"
- "text/template"
- log "github.com/sirupsen/logrus"
- db "sanine.net/git/phlox/db"
-)
-
-
-func LoginUser(username, password string) (bool, db.Session, error) {
- p := &P
- auth, user, err := p.AuthenticateUser(username, password)
- if err != nil {
- return false, db.Session{}, err
- }
- if auth == false {
- return false, db.Session{}, nil
- }
- // auth was successful!
- session, err := p.CreateSession(user)
- if err != nil {
- return false, db.Session{}, err
- }
- return true, session, nil
-}
-
-
-func LoginPostHandler(w http.ResponseWriter, r *http.Request) {
- r.ParseForm()
- username := r.Form.Get("username")
- password := strings.TrimSpace(r.Form.Get("password"))
- log.Infof("username: %v\tpassword: '%v'", username, password)
- redirect := r.Form.Get("redirect")
-
- auth, session, err := LoginUser(username, password)
- if err != nil {
- // respond with error page
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprintf(w, "an error was encountered when processing the request")
- log.Error(err)
- return
- }
-
- if auth == false {
- // not allowed!
- w.WriteHeader(http.StatusUnauthorized)
- fmt.Fprintf(w, "bad username or password")
- log.Errorf("failed login for %v", username)
- return
- }
-
- http.SetCookie(w, &http.Cookie{
- Name: "phlox-session-id",
- Value: session.Id,
- SameSite: http.SameSiteLaxMode,
- })
-
- w.Header().Add("Location", redirect)
- w.WriteHeader(http.StatusTemporaryRedirect)
-}
-
-
-var page *template.Template
-
-type Page struct {
- Title string
- Body string
-}
-
-
-func InitLogin() {
- var err error
- page, err = template.New("").Parse(`
-<!doctype html>
-<html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>Login</title>
- </head>
- <body>
- {{ .Body }}
- </body>
-</html>
-`)
-
- if err != nil {
- log.Fatal(err)
- }
-
- http.HandleFunc("/login", func (w http.ResponseWriter, r *http.Request) {
- if r.Method == "POST" {
- LoginPostHandler(w, r)
- } else {
- LoginGetHandler(w, r)
- }
- })
-}
-
-
-func LoginGetHandler(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- page.Execute(w, Page{
- Title: "Login",
- Body: `
- <form method="post">
- <label for="user">Username</label>
- <input type="text" id="user" name="username">
- <br>
- <label for="pass">Password</label>
- <input type="text" id="pass" name="password">
- <br>
- <input type="submit" value="Submit">
- `,
- })
-}
diff --git a/phlox/main.go b/phlox/main.go
deleted file mode 100644
index da52654..0000000
--- a/phlox/main.go
+++ /dev/null
@@ -1,178 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "time"
- "strings"
- "errors"
- "flag"
- "net"
- "net/http"
- "net/url"
- log "github.com/sirupsen/logrus"
- db "sanine.net/git/phlox/db"
-)
-
-
-var P db.Phlox
-
-
-func main() {
- p := &P
- var dbfile string
- flag.StringVar(&dbfile, "db", "/etc/phlox/phlox.conf", "path to the configuration db")
- flag.Parse()
-
- err := p.Open(dbfile)
- if err != nil {
- log.Fatal(err)
- }
- defer p.Close()
-
- addr, err := p.GetHostAddress()
- if err != nil {
- log.Fatal(err)
- }
-
- endpoints, err := p.AllEndpoints()
- if err != nil {
- log.Fatal(err)
- }
- log.Info("configuring reverse proxy...")
- for _, endpoint := range endpoints {
- configureEndpoint(endpoint.Path, endpoint.Address)
- }
-
- InitLogin()
-
- c := time.Tick(5 * time.Minute)
- go (func() {
- p := &P
- for ;; {
- _ = <-c // wait for 5 minutes
- p.CleanSessions(time.Hour)
- }
- })()
-
- log.Infof("serving on %v", addr)
- log.Fatal(http.ListenAndServe(addr, nil))
-}
-
-
-type Endpoint struct {
- Path string
- Origin *url.URL
-}
-
-
-func configureEndpoint(path, address string) {
- log.Infof("proxying endpoint %v to %v", path, address)
- origin, err := url.Parse(address)
- if err != nil {
- log.Fatal(err)
- }
-
- end := Endpoint{
- Path: path,
- Origin: origin,
- }
-
- http.HandleFunc(path + "/", func(w http.ResponseWriter, req *http.Request) {
- log.Infof("REQ: %v", req.URL.Path)
- proxy(w, req, end)
- })
-}
-
-
-func proxy(w http.ResponseWriter, req *http.Request, end Endpoint) {
- p := &P
- cookie, err := req.Cookie("phlox-session-id")
- if errors.Is(err, http.ErrNoCookie) {
- // not logged in
- w.Header().Set("Location", "/login")
- w.WriteHeader(http.StatusTemporaryRedirect)
- return
- }
-
- // check cookie
- check, err := p.CheckSessionId(cookie.Value)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprintf(w, "internal server error")
- return
- }
- if !check {
- // not logged in
- w.Header().Set("Location", "/login")
- w.WriteHeader(http.StatusTemporaryRedirect)
- return
- }
- // update modified time
- p.TouchSessionId(cookie.Value)
-
- response := proxyRequest(w, req, end)
- if response != nil {
- proxyResponse(w, response)
- }
-}
-
-
-func proxyRequest(w http.ResponseWriter, req *http.Request, end Endpoint) *http.Response {
- // configure host address
- req.Host = end.Origin.Host
- req.URL.Host = end.Origin.Host
-
- // strip proxy endpoint path from request path
- req.URL.Path = strings.TrimPrefix(req.URL.Path, end.Path)
-
- // set X-Forwarded-For
- forwardedFor, _, _ := net.SplitHostPort(req.RemoteAddr)
- req.Header.Set("X-Forwarded-For", forwardedFor)
-
- // misc request cleanups
- req.URL.Scheme = end.Origin.Scheme
- req.RequestURI = ""
-
- // make request
- response, err := http.DefaultClient.Do(req)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprintf(w, "%v", err)
- log.Error(err)
- return nil
- }
-
- return response
-}
-
-
-func proxyResponse(w http.ResponseWriter, response *http.Response) {
- // copy header
- for key, values := range response.Header {
- for _, value := range values {
- w.Header().Add(key, value)
- }
- }
-
- // get trailer keys
- trailerKeys := []string{}
- for key := range response.Trailer {
- trailerKeys = append(trailerKeys, key)
- }
- if (len(trailerKeys) > 0) {
- w.Header().Set("Trailer", strings.Join(trailerKeys, ","))
- }
-
- w.WriteHeader(response.StatusCode)
-
- // copy body to client
- io.Copy(w, response.Body)
-
- // write trailers
- for key, values := range response.Trailer {
- for _, value := range values {
- w.Header().Set(key, value)
- }
- }
-}