Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
freitzzz committed Dec 7, 2024
2 parents 30ee815 + 3a64c14 commit 8d32937
Show file tree
Hide file tree
Showing 26 changed files with 816 additions and 14 deletions.
21 changes: 21 additions & 0 deletions cmd/master/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func main() {
e := initializeHttpServer(ctx, env.ServerHost, env.ServerPort, env.ServerTLSCert, env.ServerTLSKey, env.ServerVirtualHost)
defer e.Close()

go logIfAdminNeedsRegistration(sc.Authentication)

// 6. Await termination...
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
Expand Down Expand Up @@ -249,15 +251,34 @@ func updateServiceContainer(
) http.ServiceContainer {
zps := event.NewZeroMQEventPubSub(s)
stt, ok := db.Table(dbb.TableSpeedtest)
crt, _ := db.Table(dbb.TableCredentials)
ust, _ := db.Table(dbb.TableUser)

if !ok {
logging.LogFatal("table %s hasn't been initialized", dbb.TableSpeedtest)
}
sps := repositories.NewDatabaseSpeedtestStoreRepository(stt.(dbb.SpeedtestTable))
authRepo := repositories.NewDatabaseAuthenticationRepository(crt.(dbb.CredentialsTable), ust.(dbb.UserTable))
userRepo := repositories.NewDatabaseUserRepository(ust.(dbb.UserTable))

tokens := service.TokenBucket{}

sc.NodeCommander = service.NewNodeCommanderService(zps, zps)
sc.NodeSpeedtest = service.NewNodeSpeedtestService(zps, zps, sps)
sc.Network = service.NewNetworkService(zps)
sc.Networking = service.NewNetworkingService()
sc.Authentication = service.NewAuthenticationService(authRepo, userRepo, &tokens)
sc.Authorization = service.NewAuthorizationService(&tokens)

return sc
}

func logIfAdminNeedsRegistration(
as *service.AuthenticationService,
) {
if !as.NeedsAdminRegistration() {
return
}

logging.LogWarning("no admin account has been registered yet! register one now at /dashboard")
}
21 changes: 21 additions & 0 deletions internal/data/db/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package db

type CredentialsTable CrudTable[CredentialsEntity, string]
type CredentialsEntity struct {
Username string
Password string
}

func NewCredentialsEntity(
username string,
password string,
) CredentialsEntity {
return CredentialsEntity{
Username: username,
Password: password,
}
}

func (e CredentialsEntity) PK() string {
return e.Username
}
4 changes: 3 additions & 1 deletion internal/data/db/db-bolt/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ func (bdb *BoltDatabase) Open() error {
}

stt := NewBoltCrudTable[db.SpeedtestEntity](db.TableSpeedtest, bbdb)
crt := NewBoltCrudTable[db.CredentialsEntity](db.TableCredentials, bbdb)
ust := NewBoltCrudTable[db.UserEntity](db.TableUser, bbdb)

bdb.DB = bbdb
bdb.tables = []db.Table{stt}
bdb.tables = []db.Table{stt, crt, ust}

return nil
}
Expand Down
2 changes: 2 additions & 0 deletions internal/data/db/db-bolt/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ import (
// Register here all entities that will be persisted in a bolt database.
func init() {
gob.Register(db.SpeedtestEntity{})
gob.Register(db.UserEntity{})
gob.Register(db.CredentialsEntity{})
}
4 changes: 3 additions & 1 deletion internal/data/db/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package db

// All database tables managed by the master node.
const (
TableSpeedtest = "node.speedtests"
TableSpeedtest = "node.speedtests"
TableCredentials = "auth.credentials"
TableUser = "auth.user"
)

type Table interface {
Expand Down
20 changes: 20 additions & 0 deletions internal/data/db/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package db

import "github.com/guackamolly/zero-monitor/internal/data/models"

type UserTable CrudTable[UserEntity, string]
type UserEntity struct {
models.User
}

func NewUserEntity(
user models.User,
) UserEntity {
return UserEntity{
User: user,
}
}

func (e UserEntity) PK() string {
return e.ID()
}
8 changes: 8 additions & 0 deletions internal/data/models/role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package models

const (
AdminRole Role = iota + 1
GuestRole
)

type Role int
25 changes: 25 additions & 0 deletions internal/data/models/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package models

import "strings"

type User struct {
Role
Username string
}

func NewAdminUser(
username string,
) User {
return User{
Username: username,
Role: AdminRole,
}
}

func (u User) ID() string {
return strings.ToLower(u.Username)
}

func (u User) IsAdmin() bool {
return u.Role == AdminRole
}
33 changes: 33 additions & 0 deletions internal/data/models/user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package models_test

import (
"testing"

"github.com/guackamolly/zero-monitor/internal/data/models"
)

func TestUserIsAdmin(t *testing.T) {
testCases := []struct {
desc string
input models.User
output bool
}{
{
desc: "returns true if user has admin role",
input: models.User{Role: models.AdminRole},
output: true,
},
{
desc: "returns false if user has guest role",
input: models.User{Role: models.GuestRole},
output: false,
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
if output := tC.input.IsAdmin(); output != tC.output {
t.Errorf("expected %v but got %v", tC.output, output)
}
})
}
}
70 changes: 70 additions & 0 deletions internal/data/repositories/auth+db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package repositories

import (
"fmt"

"github.com/guackamolly/zero-monitor/internal/data/db"
"github.com/guackamolly/zero-monitor/internal/data/models"
"github.com/guackamolly/zero-monitor/internal/logging"
)

type DatabaseAuthenticationRepository struct {
authTable db.CredentialsTable
userTable db.UserTable
}

func NewDatabaseAuthenticationRepository(
authTable db.CredentialsTable,
userTable db.UserTable,
) *DatabaseAuthenticationRepository {
return &DatabaseAuthenticationRepository{
authTable: authTable,
userTable: userTable,
}
}

func (r DatabaseAuthenticationRepository) SignIn(username string, password string) (models.User, error) {
credsEntity, ok, err := r.authTable.Lookup(username)
if !ok || err != nil {
return models.User{}, fmt.Errorf("user credentials not found")
}

if credsEntity.Password != password {
return models.User{}, fmt.Errorf("user credentials don't match")
}

userEntity, ok, err := r.userTable.Lookup(username)
if !ok || err != nil {
return models.User{}, fmt.Errorf("user not found")
}

return userEntity.User, nil
}

func (r DatabaseAuthenticationRepository) RegisterAdmin(username string, password string) (models.User, error) {
if _, ok, _ := r.authTable.Lookup(username); ok {
return models.User{}, fmt.Errorf("username already exists")
}

if _, ok, _ := r.userTable.Lookup(username); ok {
return models.User{}, fmt.Errorf("username already exists")
}

user := models.NewAdminUser(username)
err := r.userTable.Insert(db.NewUserEntity(user))
if err != nil {
return models.User{}, fmt.Errorf("user table insert failed, %v", err)
}

err = r.authTable.Insert(db.NewCredentialsEntity(username, password))
if err == nil {
return user, nil
}

delErr := r.userTable.Delete(db.NewUserEntity(user))
if delErr != nil {
logging.LogWarning("failed to insert user credentials, and now can't delete user from user table!")
}

return models.User{}, err
}
8 changes: 8 additions & 0 deletions internal/data/repositories/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package repositories

import "github.com/guackamolly/zero-monitor/internal/data/models"

type AuthenticationRepository interface {
SignIn(username string, password string) (models.User, error)
RegisterAdmin(username string, password string) (models.User, error)
}
38 changes: 38 additions & 0 deletions internal/data/repositories/user+db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package repositories

import (
"strings"

"github.com/guackamolly/zero-monitor/internal/data/db"
)

type DatabaseUserRepository struct {
userTable db.UserTable
}

func NewDatabaseUserRepository(
userTable db.UserTable,
) *DatabaseUserRepository {
return &DatabaseUserRepository{
userTable: userTable,
}
}

func (r DatabaseUserRepository) AdminExists() (bool, error) {
users, err := r.userTable.All()
if err != nil && !strings.HasSuffix(err.Error(), "does not exist") {
return false, err
}

if len(users) == 0 {
return false, nil
}

for _, u := range users {
if u.IsAdmin() {
return true, nil
}
}

return false, nil
}
5 changes: 5 additions & 0 deletions internal/data/repositories/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package repositories

type UserRepository interface {
AdminExists() (bool, error)
}
30 changes: 30 additions & 0 deletions internal/http/cookie.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package http

import (
"net/http"
"time"

"github.com/labstack/echo/v4"
)

const (
tokenCookie = "token"
)

func NewCookie(
ectx echo.Context,
name string,
value string,
path string,
expiry time.Time,
) *http.Cookie {
c := new(http.Cookie)
c.Name = name
c.Value = value
c.Path = path
c.Expires = expiry
c.SameSite = http.SameSiteStrictMode
c.Secure = ectx.IsTLS()

return c
}
2 changes: 2 additions & 0 deletions internal/http/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type ServiceContainer struct {
MasterConfiguration *service.MasterConfigurationService
Network *service.NetworkService
Networking *service.NetworkingService
Authentication *service.AuthenticationService
Authorization *service.AuthorizationService
}

func InjectServiceContainer(
Expand Down
Loading

0 comments on commit 8d32937

Please sign in to comment.