diff options
| author | Gabriel Arakaki Giovanini <mail@gabrielgio.me> | 2023-06-26 22:26:10 +0200 | 
|---|---|---|
| committer | Gabriel Arakaki Giovanini <mail@gabrielgio.me> | 2023-06-26 22:40:16 +0200 | 
| commit | 4d930c0c8cb585979798fac2bb254f991faa62fb (patch) | |
| tree | 0c33e0e0f2a2f47b0f64843f7d9a3eb299abb260 | |
| parent | d4e1ca3a48e74573df6965ceee217e119ff899ae (diff) | |
| download | lens-4d930c0c8cb585979798fac2bb254f991faa62fb.tar.gz lens-4d930c0c8cb585979798fac2bb254f991faa62fb.tar.bz2 lens-4d930c0c8cb585979798fac2bb254f991faa62fb.zip | |
feat: Add initial user setup
| -rw-r--r-- | cmd/server/main.go | 28 | ||||
| -rw-r--r-- | pkg/components/auth/controller.go | 44 | ||||
| -rw-r--r-- | pkg/components/auth/controller_test.go | 19 | ||||
| -rw-r--r-- | pkg/components/errors.go | 8 | ||||
| -rw-r--r-- | pkg/components/media/model.go | 5 | ||||
| -rw-r--r-- | pkg/components/user/model.go | 3 | ||||
| -rw-r--r-- | pkg/database/sql/user.go | 15 | ||||
| -rw-r--r-- | pkg/database/sql/user_test.go | 20 | ||||
| -rw-r--r-- | pkg/ext/middleware.go | 42 | ||||
| -rw-r--r-- | pkg/view/auth.go | 21 | ||||
| -rw-r--r-- | templates/login.html | 2 | ||||
| -rw-r--r-- | templates/media.html | 8 | ||||
| -rw-r--r-- | templates/register.html | 28 | 
13 files changed, 202 insertions, 41 deletions
| diff --git a/cmd/server/main.go b/cmd/server/main.go index f58366f..4942ac3 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -72,16 +72,6 @@ func main() {  	r := router.New()  	r.GET("/static/{filepath:*}", ext.FileServer(img.StaticFS, "static/")) -	authMiddleware := ext.NewAuthMiddleware(hexKey, logger.WithField("context", "auth")) -	logMiddleware := ext.NewLogMiddleare(logger.WithField("context", "http")) - -	extRouter := ext.NewRouter(r) -	extRouter.AddMiddleware(logMiddleware.HTTP) -	extRouter.AddMiddleware(authMiddleware.LoggedIn) -	extRouter.AddMiddleware(ext.HTML) - -	scheduler := worker.NewScheduler(*schedulerCount) -  	// repository  	var (  		userRepository       = sql.NewUserRepository(db) @@ -90,12 +80,24 @@ func main() {  		mediaRepository      = sql.NewMediaRepository(db)  	) -	//TODO: remove later -	userRepository.EnsureAdmin(context.Background()) +	// middleware +	var ( +		authMiddleware    = ext.NewAuthMiddleware(hexKey, logger.WithField("context", "auth")) +		logMiddleware     = ext.NewLogMiddleare(logger.WithField("context", "http")) +		initialMiddleware = ext.NewInitialSetupMiddleware(userRepository) +	) + +	extRouter := ext.NewRouter(r) +	extRouter.AddMiddleware(ext.HTML) +	extRouter.AddMiddleware(initialMiddleware.Check) +	extRouter.AddMiddleware(authMiddleware.LoggedIn) +	extRouter.AddMiddleware(logMiddleware.HTTP) + +	scheduler := worker.NewScheduler(*schedulerCount)  	// controller  	var ( -		userController       = auth.NewController(userRepository, hexKey) +		userController       = auth.NewController(userRepository, userRepository, hexKey)  		fileSystemController = filesystem.NewController(fileSystemRepository)  	) diff --git a/pkg/components/auth/controller.go b/pkg/components/auth/controller.go index a81a1c0..2f30fb5 100644 --- a/pkg/components/auth/controller.go +++ b/pkg/components/auth/controller.go @@ -5,18 +5,26 @@ import (  	"golang.org/x/crypto/bcrypt" +	"git.sr.ht/~gabrielgio/img/pkg/components" +	"git.sr.ht/~gabrielgio/img/pkg/components/user"  	"git.sr.ht/~gabrielgio/img/pkg/ext"  )  type Controller struct { -	repository Repository -	key        []byte +	repository     Repository +	userRepository user.Repository +	key            []byte  } -func NewController(repository Repository, key []byte) *Controller { +func NewController( +	repository Repository, +	userRepository user.Repository, +	key []byte, +) *Controller {  	return &Controller{ -		repository: repository, -		key:        key, +		repository:     repository, +		userRepository: userRepository, +		key:            key,  	}  } @@ -41,3 +49,29 @@ func (c *Controller) Login(ctx context.Context, username, password []byte) ([]by  	}  	return ext.WriteToken(token, c.key)  } + +// InitialRegister register a initial user, it will validate if there is another +// user stored already. If so an error `InvlidaInput` will be returned +func (c *Controller) InitialRegister(ctx context.Context, username, password []byte, path []byte) error { +	exist, err := c.userRepository.Any(ctx) +	if err != nil { +		return err +	} + +	if exist { +		return components.InvlidaInput +	} + +	hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost) +	if err != nil { +		return err +	} + +	err = c.userRepository.Create(ctx, &user.CreateUser{ +		Username: string(username), +		Password: hash, +		Path:     string(path), +	}) + +	return err +} diff --git a/pkg/components/auth/controller_test.go b/pkg/components/auth/controller_test.go index 33aa901..6b4e3cd 100644 --- a/pkg/components/auth/controller_test.go +++ b/pkg/components/auth/controller_test.go @@ -9,6 +9,7 @@ import (  	"github.com/samber/lo" +	"git.sr.ht/~gabrielgio/img/pkg/components/user"  	"git.sr.ht/~gabrielgio/img/pkg/ext"  	"git.sr.ht/~gabrielgio/img/pkg/testkit"  ) @@ -43,11 +44,11 @@ func setUp() *scene {  	return &scene{  		ctx:            context.Background(),  		mockRepository: mockUserRepository, -		controller:     *NewController(mockUserRepository, key), +		controller:     *NewController(mockUserRepository, nil, key),  	}  } -func TestRegisterAndLogin(t *testing.T) { +func TestInitialRegisterAndLogin(t *testing.T) {  	testCases := []struct {  		name     string  		username string @@ -64,7 +65,7 @@ func TestRegisterAndLogin(t *testing.T) {  		t.Run(tc.name, func(t *testing.T) {  			scene := setUp() -			err := scene.controller.Register(scene.ctx, []byte(tc.username), tc.password) +			err := scene.controller.InitialRegister(scene.ctx, []byte(tc.username), tc.password, []byte("/"))  			testkit.TestFatalError(t, "Register", err)  			userID := scene.mockRepository.GetLastId() @@ -85,8 +86,8 @@ func TestRegisterAndLogin(t *testing.T) {  	}  } -func toUser(m *mockUser, _ int) *User { -	return &User{ +func toUser(m *mockUser, _ int) *user.User { +	return &user.User{  		ID:       m.id,  		Username: m.username,  	} @@ -96,7 +97,7 @@ func (m *MockUserRepository) GetLastId() uint {  	return m.index  } -func (m *MockUserRepository) List(ctx context.Context) ([]*User, error) { +func (m *MockUserRepository) List(ctx context.Context) ([]*user.User, error) {  	if m.err != nil {  		return nil, m.err  	} @@ -104,7 +105,7 @@ func (m *MockUserRepository) List(ctx context.Context) ([]*User, error) {  	return lo.Map(m.users, toUser), nil  } -func (m *MockUserRepository) Get(ctx context.Context, id uint) (*User, error) { +func (m *MockUserRepository) Get(ctx context.Context, id uint) (*user.User, error) {  	if m.err != nil {  		return nil, m.err  	} @@ -143,7 +144,7 @@ func (m *MockUserRepository) GetPassword(ctx context.Context, id uint) ([]byte,  	return nil, errors.New("Item not found")  } -func (m *MockUserRepository) Create(ctx context.Context, createUser *CreateUser) (uint, error) { +func (m *MockUserRepository) Create(ctx context.Context, createUser *user.CreateUser) (uint, error) {  	if m.err != nil {  		return 0, m.err  	} @@ -159,7 +160,7 @@ func (m *MockUserRepository) Create(ctx context.Context, createUser *CreateUser)  	return m.index, nil  } -func (m *MockUserRepository) Update(ctx context.Context, id uint, update *UpdateUser) error { +func (m *MockUserRepository) Update(ctx context.Context, id uint, update *user.UpdateUser) error {  	if m.err != nil {  		return m.err  	} diff --git a/pkg/components/errors.go b/pkg/components/errors.go new file mode 100644 index 0000000..aedbe88 --- /dev/null +++ b/pkg/components/errors.go @@ -0,0 +1,8 @@ +package components + +import "errors" + +var ( +	NotFound     = errors.New("Not found") +	InvlidaInput = errors.New("Invalid Input") +) diff --git a/pkg/components/media/model.go b/pkg/components/media/model.go index 0e17e92..1962a23 100644 --- a/pkg/components/media/model.go +++ b/pkg/components/media/model.go @@ -2,6 +2,7 @@ package media  import (  	"context" +	"strings"  	"time"  ) @@ -57,3 +58,7 @@ type (  		CreateEXIF(context.Context, uint, *MediaEXIF) error  	}  ) + +func (m *Media) IsVideo() bool { +	return strings.HasPrefix(m.MIMEType, "video") +} diff --git a/pkg/components/user/model.go b/pkg/components/user/model.go index f957c39..ce1b3a5 100644 --- a/pkg/components/user/model.go +++ b/pkg/components/user/model.go @@ -20,7 +20,7 @@ type (  	CreateUser struct {  		Username string  		Name     string -		Password string +		Password []byte  		IsAdmin  bool  		Path     string  	} @@ -29,5 +29,6 @@ type (  		List(ctx context.Context) ([]*User, error)  		Create(ctx context.Context, createUser *CreateUser) error  		Update(ctx context.Context, id uint, updateUser *UpdateUser) error +		Any(ctx context.Context) (bool, error)  	}  ) diff --git a/pkg/database/sql/user.go b/pkg/database/sql/user.go index 2d74162..a02b67b 100644 --- a/pkg/database/sql/user.go +++ b/pkg/database/sql/user.go @@ -187,3 +187,18 @@ func (self *UserRepository) Delete(ctx context.Context, id uint) error {  	}  	return nil  } + +func (u *UserRepository) Any(ctx context.Context) (bool, error) { +	var exists bool +	result := u.db. +		WithContext(ctx). +		Model(&User{}). +		Select("count(id) > 0"). +		Find(&exists) + +	if result.Error != nil { +		return false, result.Error +	} + +	return exists, nil +} diff --git a/pkg/database/sql/user_test.go b/pkg/database/sql/user_test.go index 875b8e6..473ce03 100644 --- a/pkg/database/sql/user_test.go +++ b/pkg/database/sql/user_test.go @@ -12,7 +12,7 @@ import (  	"gorm.io/gorm"  	"gorm.io/gorm/logger" -	"git.sr.ht/~gabrielgio/img/pkg/components/auth" +	"git.sr.ht/~gabrielgio/img/pkg/components/user"  )  func setup(t *testing.T) (*gorm.DB, func()) { @@ -48,7 +48,7 @@ func TestCreate(t *testing.T) {  	repository := NewUserRepository(db) -	id, err := repository.Create(context.Background(), &auth.CreateUser{ +	err := repository.Create(context.Background(), &user.CreateUser{  		Username: "new_username",  		Name:     "new_name",  	}) @@ -56,12 +56,12 @@ func TestCreate(t *testing.T) {  		t.Fatalf("Error creating: %s", err.Error())  	} -	got, err := repository.Get(context.Background(), id) +	got, err := repository.Get(context.Background(), 1)  	if err != nil {  		t.Fatalf("Error getting: %s", err.Error())  	} -	want := &auth.User{ -		ID:       id, +	want := &user.User{ +		ID:       1,  		Username: "new_username",  		Name:     "new_name",  	} @@ -78,7 +78,7 @@ func TestUpdate(t *testing.T) {  	repository := NewUserRepository(db) -	id, err := repository.Create(context.Background(), &auth.CreateUser{ +	err := repository.Create(context.Background(), &user.CreateUser{  		Username: "username",  		Name:     "name",  	}) @@ -86,7 +86,7 @@ func TestUpdate(t *testing.T) {  		t.Fatalf("Error creating user: %s", err.Error())  	} -	err = repository.Update(context.Background(), id, &auth.UpdateUser{ +	err = repository.Update(context.Background(), 1, &user.UpdateUser{  		Username: "new_username",  		Name:     "new_name",  	}) @@ -94,12 +94,12 @@ func TestUpdate(t *testing.T) {  		t.Fatalf("Error update user: %s", err.Error())  	} -	got, err := repository.Get(context.Background(), id) +	got, err := repository.Get(context.Background(), 1)  	if err != nil {  		t.Fatalf("Error getting user: %s", err.Error())  	} -	want := &auth.User{ -		ID:       id, +	want := &user.User{ +		ID:       1,  		Username: "new_username",  		Name:     "new_name",  	} diff --git a/pkg/ext/middleware.go b/pkg/ext/middleware.go index 771c0ac..649272e 100644 --- a/pkg/ext/middleware.go +++ b/pkg/ext/middleware.go @@ -4,6 +4,7 @@ import (  	"encoding/base64"  	"time" +	"git.sr.ht/~gabrielgio/img/pkg/components/user"  	"github.com/sirupsen/logrus"  	"github.com/valyala/fasthttp"  ) @@ -54,7 +55,7 @@ func NewAuthMiddleware(key []byte, log *logrus.Entry) *AuthMiddleware {  func (a *AuthMiddleware) LoggedIn(next fasthttp.RequestHandler) fasthttp.RequestHandler {  	return func(ctx *fasthttp.RequestCtx) {  		path := string(ctx.Path()) -		if path == "/login" { +		if path == "/login" || path == "/initial" {  			next(ctx)  			return  		} @@ -87,3 +88,42 @@ func (a *AuthMiddleware) LoggedIn(next fasthttp.RequestHandler) fasthttp.Request  		next(ctx)  	}  } + +type InitialSetupMiddleware struct { +	userRepository user.Repository +} + +func NewInitialSetupMiddleware(userRepository user.Repository) *InitialSetupMiddleware { +	return &InitialSetupMiddleware{ +		userRepository: userRepository, +	} +} + +func (i *InitialSetupMiddleware) Check(next fasthttp.RequestHandler) fasthttp.RequestHandler { +	return func(ctx *fasthttp.RequestCtx) { +		// if user has been set to context it is logged in already +		_, ok := ctx.UserValue("token").(*Token) +		if ok { +			next(ctx) +			return +		} + +		path := string(ctx.Path()) +		if path == "/initial" { +			next(ctx) +			return +		} + +		exists, err := i.userRepository.Any(ctx) +		if err != nil { +			InternalServerError(ctx, err) +			return +		} + +		if exists { +			next(ctx) +			return +		} +		ctx.Redirect("/initial", 307) +	} +} diff --git a/pkg/view/auth.go b/pkg/view/auth.go index d44424d..3f9e414 100644 --- a/pkg/view/auth.go +++ b/pkg/view/auth.go @@ -68,10 +68,31 @@ func Index(ctx *fasthttp.RequestCtx) {  	ctx.Redirect("/login", 307)  } +func (v *AuthView) InitialRegisterView(ctx *fasthttp.RequestCtx) error { +	return img.Render[interface{}](ctx, "register.html", nil) +} + +func (v *AuthView) InitialRegister(ctx *fasthttp.RequestCtx) error { +	username := ctx.FormValue("username") +	password := ctx.FormValue("password") +	path := ctx.FormValue("path") + +	err := v.userController.InitialRegister(ctx, username, password, path) +	if err != nil { +		return err +	} + +	ctx.Redirect("/login", 307) +	return nil +} +  func (v *AuthView) SetMyselfIn(r *ext.Router) {  	r.GET("/login", v.LoginView)  	r.POST("/login", v.Login)  	r.GET("/logout", v.Logout)  	r.POST("/logout", v.Logout) + +	r.GET("/initial", v.InitialRegisterView) +	r.POST("/initial", v.InitialRegister)  } diff --git a/templates/login.html b/templates/login.html index f71d9d3..607faa1 100644 --- a/templates/login.html +++ b/templates/login.html @@ -1,5 +1,5 @@  {{template "layout.html" .}} -{{define "title"}} Register {{end}} +{{define "title"}} Login {{end}}  {{define "content"}}  <form action="/login" method="post">      <div class="field"> diff --git a/templates/media.html b/templates/media.html index 478d8ae..6302a57 100644 --- a/templates/media.html +++ b/templates/media.html @@ -5,9 +5,15 @@  {{range .Data.Medias}}  <div class="card">      <div class="card-image"> +        {{ if .IsVideo }} +        <video controls muted="true" preload="metadata"> +            <source src="/media/image?path_hash={{.PathHash}}" type="{{.MIMEType}}"> +        </video> +        {{ else }}          <figure class="image is-fit"> -            <img   src="/media/image?path_hash={{.PathHash}}"> +            <img src="/media/image?path_hash={{.PathHash}}">          </figure> +        {{ end }}      </div>  </div>  {{end}} diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..b026d33 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,28 @@ +{{template "layout.html" .}} +{{define "title"}} Initial Setup {{end}} +{{define "content"}} +<h1>Initial Setup</h1> +<form action="/initial" method="post"> +    <div class="field"> +        <label class="label">Username</label> +        <div class="control"> +            <input class="input" name="username" type="text"> +        </div> +    </div> +    <div class="field"> +        <label class="label">Password</label> +        <div class="control"> +            <input class="input" name="password" type="password"> +        </div> +    </div> +    <div class="field"> +        <label class="label">Root folder</label> +        <div class="control"> +            <input class="input" name="path" type="text"> +        </div> +    </div> +    <div class="field"> +        <input class="button is-pulled-right" value="Save" type="submit"> +    </div> +</form> +{{end}} | 
