aboutsummaryrefslogtreecommitdiff
path: root/pkg/service
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/service')
-rw-r--r--pkg/service/auth.go123
-rw-r--r--pkg/service/auth_test.go119
-rw-r--r--pkg/service/git.go87
3 files changed, 312 insertions, 17 deletions
diff --git a/pkg/service/auth.go b/pkg/service/auth.go
new file mode 100644
index 0000000..0dbd960
--- /dev/null
+++ b/pkg/service/auth.go
@@ -0,0 +1,123 @@
+package service
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
+ "io"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+type (
+ AuthService struct {
+ authRepository authRepository
+ }
+
+ authRepository interface {
+ GetPassphrase() []byte
+ GetBase64AesKey() []byte
+ }
+)
+
+var tokenSeed = []byte("this is a token for cerrado")
+
+func NewAuthService(repostiory authRepository) *AuthService {
+ return &AuthService{
+ authRepository: repostiory,
+ }
+}
+
+func (a *AuthService) CheckAuth(username, password string) bool {
+ passphrase := a.authRepository.GetPassphrase()
+ pass := []byte(fmt.Sprintf("%s:%s", username, password))
+
+ err := bcrypt.CompareHashAndPassword(passphrase, pass)
+
+ return err == nil
+}
+
+func (a *AuthService) IssueToken() ([]byte, error) {
+ // TODO: do this block only once
+ base := a.authRepository.GetBase64AesKey()
+
+ dbuf, err := base64.StdEncoding.DecodeString(string(base))
+ if err != nil {
+ return nil, err
+ }
+
+ block, err := aes.NewCipher(dbuf)
+ if err != nil {
+ return nil, err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, err
+ }
+
+ nonce := make([]byte, gcm.NonceSize())
+ if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
+ return nil, err
+ }
+
+ ciphertext := gcm.Seal(nonce, nonce, tokenSeed, nil)
+
+ return ciphertext, nil
+}
+
+func (a *AuthService) ValidateToken(token []byte) (bool, error) {
+ base := a.authRepository.GetBase64AesKey()
+
+ dbuf, err := base64.StdEncoding.DecodeString(string(base))
+ if err != nil {
+ return false, err
+ }
+
+ block, err := aes.NewCipher(dbuf)
+ if err != nil {
+ return false, err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return false, err
+ }
+
+ nonceSize := gcm.NonceSize()
+ if len(token) < nonceSize {
+ return false, fmt.Errorf("ciphertext too short")
+ }
+
+ nonce, ciphertext := token[:nonceSize], token[nonceSize:]
+ plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
+ if err != nil {
+ return false, err
+ }
+
+ return bytes.Equal(tokenSeed, plaintext), nil
+}
+
+func GenerateHash(username, password string) (string, error) {
+ passphrase := fmt.Sprintf("%s:%s", username, password)
+ bytes, err := bcrypt.GenerateFromPassword([]byte(passphrase), 14)
+ if err != nil {
+ return "", err
+ }
+
+ return string(bytes), nil
+}
+
+func GenerateAesKey() (string, error) {
+ key := make([]byte, 32)
+
+ _, err := rand.Read(key)
+ if err != nil {
+ return "", err
+ }
+
+ return base64.StdEncoding.EncodeToString(key), nil
+}
diff --git a/pkg/service/auth_test.go b/pkg/service/auth_test.go
new file mode 100644
index 0000000..06bf76f
--- /dev/null
+++ b/pkg/service/auth_test.go
@@ -0,0 +1,119 @@
+// go:build unit
+
+package service
+
+import (
+ "testing"
+)
+
+func TestCheck(t *testing.T) {
+ testCases := []struct {
+ name string
+ passphrase []byte
+ username string
+ password string
+ wantError bool
+ }{
+ {
+ name: "generated",
+ passphrase: nil,
+ username: "gabrielgio",
+ password: "adminadmin",
+ wantError: false,
+ },
+ {
+ name: "static",
+ passphrase: []byte("$2a$14$W2yT0E6Zm8nTecqipHUQGOLC6PvNjIQqpQTW/MZmD5oqDfaBJnBV6"),
+ username: "gabrielgio",
+ password: "adminadmin",
+ wantError: false,
+ },
+ {
+ name: "error",
+ passphrase: []byte("This is not a valid hash"),
+ username: "gabrielgio",
+ password: "adminadmin",
+ wantError: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ mock := &mockAuthRepository{
+ username: tc.username,
+ password: tc.password,
+ passphrase: tc.passphrase,
+ }
+
+ service := AuthService{authRepository: mock}
+
+ if service.CheckAuth(tc.username, tc.password) == tc.wantError {
+ t.Errorf("Invalid result, wanted %t got %t", tc.wantError, !tc.wantError)
+ }
+ })
+ }
+}
+
+func TestValidate(t *testing.T) {
+ testCases := []struct {
+ name string
+ aesKey []byte
+ }{
+ {
+ name: "generated",
+ aesKey: nil,
+ },
+ {
+ name: "static",
+ aesKey: []byte("RTGkmunKmi5agh7jaqENunG2zI/godnkqhHaHyX/AVg="),
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ mock := &mockAuthRepository{
+ aesKey: tc.aesKey,
+ }
+
+ service := AuthService{authRepository: mock}
+
+ token, err := service.IssueToken()
+ if err != nil {
+ t.Fatalf("Error issuing token: %s", err.Error())
+ }
+
+ v, err := service.ValidateToken(token)
+ if err != nil {
+ t.Fatalf("Error validating token: %s", err.Error())
+ }
+
+ if !v {
+ t.Error("Invalid token generated")
+ }
+ })
+ }
+}
+
+type mockAuthRepository struct {
+ username string
+ password string
+ passphrase []byte
+
+ aesKey []byte
+}
+
+func (m *mockAuthRepository) GetPassphrase() []byte {
+ if m.passphrase == nil {
+ hash, _ := GenerateHash(m.username, m.password)
+ m.passphrase = []byte(hash)
+ }
+ return m.passphrase
+}
+
+func (m *mockAuthRepository) GetBase64AesKey() []byte {
+ if m.aesKey == nil {
+ key, _ := GenerateAesKey()
+ m.aesKey = []byte(key)
+ }
+ return m.aesKey
+}
diff --git a/pkg/service/git.go b/pkg/service/git.go
index f03ba42..6aa5cd6 100644
--- a/pkg/service/git.go
+++ b/pkg/service/git.go
@@ -2,22 +2,25 @@ package service
import (
"compress/gzip"
+ "context"
"errors"
"io"
"log/slog"
"git.gabrielgio.me/cerrado/pkg/config"
"git.gabrielgio.me/cerrado/pkg/git"
+ gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
)
type (
Repository struct {
- Name string
- Description string
- LastCommitDate string
- Ref string
+ Name string
+ Description string
+ Public bool
+ LastCommit *git.CommitReference
+ Ref string
}
GitService struct {
@@ -30,9 +33,7 @@ type (
}
)
-var (
- ErrRepositoryNotFound = errors.New("Repository not found")
-)
+var ErrRepositoryNotFound = errors.New("Repository not found")
// TODO: make it configurable
const timeFormat = "2006.01.02 15:04:05"
@@ -50,6 +51,10 @@ func (g *GitService) ListRepositories() ([]*Repository, error) {
for _, r := range rs {
repo, err := git.OpenRepository(r.Path)
if err != nil {
+ if errors.Is(err, gogit.ErrRepositoryNotExists) {
+ slog.Info("Path does not contain a repository", "path", r.Path)
+ continue
+ }
return nil, err
}
@@ -66,35 +71,36 @@ func (g *GitService) ListRepositories() ([]*Repository, error) {
}
repos = append(repos, &Repository{
- Name: r.Name,
- Description: r.Description,
- LastCommitDate: obj.Author.When.Format(timeFormat),
- Ref: head.Name().Short(),
+ Name: r.Name,
+ Description: r.Description,
+ Public: r.Public,
+ LastCommit: obj,
+ Ref: head.Name().Short(),
})
}
return repos, nil
}
-func (g *GitService) ListCommits(name, ref string, count int) ([]*object.Commit, error) {
+func (g *GitService) ListCommits(name, ref, from string, count int) ([]*git.CommitReference, *object.Commit, error) {
r := g.configRepo.GetByName(name)
if r == nil {
- return nil, ErrRepositoryNotFound
+ return nil, nil, ErrRepositoryNotFound
}
repo, err := git.OpenRepository(r.Path)
if err != nil {
- return nil, err
+ return nil, nil, err
}
err = repo.SetRef(ref)
if err != nil {
- return nil, err
+ return nil, nil, err
}
- return repo.Commits(count)
+ return repo.Commits(count, from)
}
-func (g *GitService) LastCommit(name, ref string) (*object.Commit, error) {
+func (g *GitService) LastCommit(name, ref string) (*git.CommitReference, error) {
r := g.configRepo.GetByName(name)
if r == nil {
return nil, ErrRepositoryNotFound
@@ -236,6 +242,25 @@ func (g *GitService) GetAbout(name string) ([]byte, error) {
return file, nil
}
+func (g *GitService) GetTag(ref, name string) (*object.Commit, *git.TagReference, error) {
+ r := g.configRepo.GetByName(name)
+ if r == nil {
+ return nil, nil, ErrRepositoryNotFound
+ }
+
+ repo, err := git.OpenRepository(r.Path)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ err = repo.SetRef(ref)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return repo.Tag()
+}
+
func (g *GitService) ListTags(name string) ([]*git.TagReference, error) {
r := g.configRepo.GetByName(name)
if r == nil {
@@ -275,3 +300,31 @@ func (g *GitService) GetHead(name string) (*plumbing.Reference, error) {
return repo.Head()
}
+
+func (g *GitService) WriteInfoRefs(ctx context.Context, name string, w io.Writer) error {
+ r := g.configRepo.GetByName(name)
+ if r == nil {
+ return ErrRepositoryNotFound
+ }
+
+ repo, err := git.OpenRepository(r.Path)
+ if err != nil {
+ return err
+ }
+
+ return repo.WriteInfoRefs(ctx, w)
+}
+
+func (g *GitService) WriteUploadPack(ctx context.Context, name string, re io.Reader, w io.Writer) error {
+ r := g.configRepo.GetByName(name)
+ if r == nil {
+ return ErrRepositoryNotFound
+ }
+
+ repo, err := git.OpenRepository(r.Path)
+ if err != nil {
+ return err
+ }
+
+ return repo.WriteUploadPack(ctx, re, w)
+}