diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | conf/create.go | 83 | ||||
-rw-r--r-- | conf/delete.go | 54 | ||||
-rw-r--r-- | conf/endpoint.go.bak | 98 | ||||
-rw-r--r-- | conf/go.mod | 16 | ||||
-rw-r--r-- | conf/go.sum | 8 | ||||
-rw-r--r-- | conf/list.go | 69 | ||||
-rw-r--r-- | conf/main.go | 96 | ||||
-rw-r--r-- | conf/user.go.bak | 127 | ||||
-rw-r--r-- | config.json | 6 | ||||
-rw-r--r-- | config/config.go | 44 | ||||
-rw-r--r-- | db/db.go | 207 | ||||
-rw-r--r-- | db/endpoint.go | 96 | ||||
-rw-r--r-- | db/go.mod | 8 | ||||
-rw-r--r-- | db/go.sum | 4 | ||||
-rw-r--r-- | db/session.go | 139 | ||||
-rw-r--r-- | db/user.go | 165 | ||||
-rw-r--r-- | go.mod | 3 | ||||
-rw-r--r-- | main.go | 25 | ||||
-rw-r--r-- | phlox/go.mod | 16 | ||||
-rw-r--r-- | phlox/go.sum | 20 | ||||
-rw-r--r-- | phlox/login.go | 119 | ||||
-rw-r--r-- | phlox/main.go | 178 |
23 files changed, 79 insertions, 1506 deletions
@@ -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 -} @@ -0,0 +1,3 @@ +module sanine.net/git/phlox + +go 1.20 @@ -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) - } - } -} |