Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
Expand Down Expand Up @@ -642,16 +641,18 @@ func (a *Authenticator) ReadResponseFile(userID, path string) (*Session, error)
// Tries Argon2id (.hash) first, then falls back to legacy LTHN (.lthn).
// Returns nil on success, or an error describing the failure.
func (a *Authenticator) verifyPassword(userID, password string) error {
const op = "auth.verifyPassword"

// Try Argon2id hash first (.hash file)
if a.medium.IsFile(userPath(userID, ".hash")) {
storedHash, err := a.medium.Read(userPath(userID, ".hash"))
if err == nil && strings.HasPrefix(storedHash, "$argon2id$") {
valid, verr := crypt.VerifyPassword(password, storedHash)
if verr != nil {
return errors.New("failed to verify password")
return coreerr.E(op, "failed to verify password", nil)
}
if !valid {
return errors.New("invalid password")
return coreerr.E(op, "invalid password", nil)
}
return nil
}
Expand All @@ -660,20 +661,22 @@ func (a *Authenticator) verifyPassword(userID, password string) error {
// Fall back to legacy LTHN hash (.lthn file)
storedHash, err := a.medium.Read(userPath(userID, ".lthn"))
if err != nil {
return errors.New("user not found")
return coreerr.E(op, "user not found", nil)
}
if !lthn.Verify(password, storedHash) {
return errors.New("invalid password")
return coreerr.E(op, "invalid password", nil)
}
return nil
}

// createSession generates a cryptographically random session token and
// stores the session via the SessionStore.
func (a *Authenticator) createSession(userID string) (*Session, error) {
const op = "auth.createSession"

tokenBytes := make([]byte, 32)
if _, err := rand.Read(tokenBytes); err != nil {
return nil, fmt.Errorf("auth: failed to generate session token: %w", err)
return nil, coreerr.E(op, "failed to generate session token", err)
}

session := &Session{
Expand All @@ -683,7 +686,7 @@ func (a *Authenticator) createSession(userID string) (*Session, error) {
}

if err := a.store.Set(session); err != nil {
return nil, fmt.Errorf("auth: failed to persist session: %w", err)
return nil, coreerr.E(op, "failed to persist session", err)
}

return session, nil
Expand Down
5 changes: 3 additions & 2 deletions auth/session_store.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package auth

import (
"errors"
"maps"
"sync"
"time"

coreerr "forge.lthn.ai/core/go-log"
)

// ErrSessionNotFound is returned when a session token is not found.
var ErrSessionNotFound = errors.New("auth: session not found")
var ErrSessionNotFound = coreerr.E("auth", "session not found", nil)

// SessionStore abstracts session persistence.
type SessionStore interface {
Expand Down
12 changes: 7 additions & 5 deletions cmd/crypt/cmd_encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package crypt

import (
"fmt"
"os"
"strings"

"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go-crypt/crypt"
coreio "forge.lthn.ai/core/go-io"
)

// Encrypt command flags
Expand Down Expand Up @@ -53,10 +53,11 @@ func runEncrypt(path string) error {
return cli.Err("passphrase cannot be empty")
}

data, err := os.ReadFile(path)
raw, err := coreio.Local.Read(path)
if err != nil {
return cli.Wrap(err, "failed to read file")
}
data := []byte(raw)

var encrypted []byte
if encryptAES {
Expand All @@ -69,7 +70,7 @@ func runEncrypt(path string) error {
}

outPath := path + ".enc"
if err := os.WriteFile(outPath, encrypted, 0o600); err != nil {
if err := coreio.Local.Write(outPath, string(encrypted)); err != nil {
return cli.Wrap(err, "failed to write encrypted file")
}

Expand All @@ -86,10 +87,11 @@ func runDecrypt(path string) error {
return cli.Err("passphrase cannot be empty")
}

data, err := os.ReadFile(path)
raw, err := coreio.Local.Read(path)
if err != nil {
return cli.Wrap(err, "failed to read file")
}
data := []byte(raw)

var decrypted []byte
if encryptAES {
Expand All @@ -106,7 +108,7 @@ func runDecrypt(path string) error {
outPath = path + ".dec"
}

if err := os.WriteFile(outPath, decrypted, 0o600); err != nil {
if err := coreio.Local.Write(outPath, string(decrypted)); err != nil {
return cli.Wrap(err, "failed to write decrypted file")
}

Expand Down
8 changes: 4 additions & 4 deletions cmd/testcmd/cmd_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package testcmd

import (
"bufio"
"errors"
"fmt"
"io"
"os"
Expand All @@ -11,12 +10,13 @@ import (
"strings"

"forge.lthn.ai/core/go-i18n"
coreerr "forge.lthn.ai/core/go-log"
)

func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bool) error {
// Detect if we're in a Go project
if _, err := os.Stat("go.mod"); os.IsNotExist(err) {
return errors.New(i18n.T("cmd.test.error.no_go_mod"))
return coreerr.E("cmd.test", i18n.T("cmd.test.error.no_go_mod"), nil)
}

// Build command arguments
Expand Down Expand Up @@ -94,7 +94,7 @@ func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bo
// JSON output for CI/agents
printJSONResults(results, exitCode)
if exitCode != 0 {
return errors.New(i18n.T("i18n.fail.run", "tests"))
return coreerr.E("cmd.test", i18n.T("i18n.fail.run", "tests"), nil)
}
return nil
}
Expand All @@ -110,7 +110,7 @@ func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bo

if exitCode != 0 {
fmt.Printf("\n%s %s\n", testFailStyle.Render(i18n.T("cli.fail")), i18n.T("cmd.test.tests_failed"))
return errors.New(i18n.T("i18n.fail.run", "tests"))
return coreerr.E("cmd.test", i18n.T("i18n.fail.run", "tests"), nil)
}

fmt.Printf("\n%s %s\n", testPassStyle.Render(i18n.T("cli.pass")), i18n.T("common.result.all_passed"))
Expand Down
4 changes: 3 additions & 1 deletion crypt/chachapoly/chachapoly.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"io"

coreerr "forge.lthn.ai/core/go-log"

"golang.org/x/crypto/chacha20poly1305"
)

Expand Down Expand Up @@ -32,7 +34,7 @@ func Decrypt(ciphertext []byte, key []byte) ([]byte, error) {

minLen := aead.NonceSize() + aead.Overhead()
if len(ciphertext) < minLen {
return nil, fmt.Errorf("ciphertext too short: got %d bytes, need at least %d bytes", len(ciphertext), minLen)
return nil, coreerr.E("chachapoly.Decrypt", fmt.Sprintf("ciphertext too short: got %d bytes, need at least %d bytes", len(ciphertext), minLen), nil)
}

nonce, ciphertext := ciphertext[:aead.NonceSize()], ciphertext[aead.NonceSize():]
Expand Down
Loading