Codevault initial commit

This commit is contained in:
2026-08-16 16:23:14 +03:00
commit 6ee80f1c8f
47 changed files with 3177 additions and 0 deletions

30
internal/db/db.go Normal file
View File

@@ -0,0 +1,30 @@
package db
import (
"database/sql"
_ "embed"
"fmt"
_ "modernc.org/sqlite"
)
//go:embed schema.sql
var schema string
// Open opens (creating the file if it does not exist) the SQLite database at path
// and applies the embedded schema.
func Open(path string) (*sql.DB, error) {
dsn := fmt.Sprintf("file:%s?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)", path)
database, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, fmt.Errorf("could not open database: %w", err)
}
database.SetMaxOpenConns(1)
if _, err := database.Exec(schema); err != nil {
_ = database.Close()
return nil, fmt.Errorf("could not apply schema: %w", err)
}
return database, nil
}

12
internal/db/schema.sql Normal file
View File

@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS users (
login TEXT PRIMARY KEY,
password_hash TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS secrets (
code TEXT PRIMARY KEY,
value TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

42
internal/db/secrets.go Normal file
View File

@@ -0,0 +1,42 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
)
var ErrNotFound = errors.New("secret not found")
type SecretsRepository struct {
db *sql.DB
}
func NewSecretsRepository(db *sql.DB) *SecretsRepository {
return &SecretsRepository{db: db}
}
func (r *SecretsRepository) Get(ctx context.Context, code string) (string, error) {
var value string
err := r.db.QueryRowContext(ctx, "SELECT value FROM secrets WHERE code = ?", code).Scan(&value)
if errors.Is(err, sql.ErrNoRows) {
return "", ErrNotFound
}
if err != nil {
return "", fmt.Errorf("could not get secret: %w", err)
}
return value, nil
}
func (r *SecretsRepository) Set(ctx context.Context, code, value string) error {
_, err := r.db.ExecContext(ctx, `
INSERT INTO secrets (code, value, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(code) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP
`, code, value)
if err != nil {
return fmt.Errorf("could not set secret: %w", err)
}
return nil
}

51
internal/db/users.go Normal file
View File

@@ -0,0 +1,51 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/kanootoko/codevault/internal/auth"
)
var ErrInvalidCredentials = errors.New("invalid login or password")
type UsersRepository struct {
db *sql.DB
}
func NewUsersRepository(db *sql.DB) *UsersRepository {
return &UsersRepository{db: db}
}
func (r *UsersRepository) Upsert(ctx context.Context, login, passwordHash string) error {
_, err := r.db.ExecContext(ctx, `
INSERT INTO users (login, password_hash)
VALUES (?, ?)
ON CONFLICT(login) DO UPDATE SET password_hash = excluded.password_hash
`, login, passwordHash)
if err != nil {
return fmt.Errorf("could not upsert user: %w", err)
}
return nil
}
// Verify checks the given login/password pair against the stored hash. A dummy hash
// comparison is performed for unknown logins so that lookup timing does not reveal
// whether the login exists.
func (r *UsersRepository) Verify(ctx context.Context, login, password string) error {
var passwordHash string
err := r.db.QueryRowContext(ctx, "SELECT password_hash FROM users WHERE login = ?", login).Scan(&passwordHash)
if errors.Is(err, sql.ErrNoRows) {
auth.CompareDummy()
return ErrInvalidCredentials
}
if err != nil {
return fmt.Errorf("could not get user: %w", err)
}
if !auth.Compare(login, passwordHash, password) {
return ErrInvalidCredentials
}
return nil
}