Skip to content
Draft
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
Empty file removed .app-config.yaml.example
Empty file.
9 changes: 5 additions & 4 deletions .claude/rules/security-compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@ verifiable backbone the regulatory sections below lean on.

| # | Control | Standard | Status |
|---|---------|----------|--------|
| X1 | Passwords hashed with **Argon2id** (RFC 9106) — never bcrypt/sha/plaintext. Parameters checked in on introduction. | OWASP ASVS 2.4 | partial (`internal/shared/hasher` built + CI-tested: argon2id, PHC storage, rehash-on-verify, semaphore; no live hashing path until Login/Setup service — Phase 5) |
| X2 | Session/access tokens are **JWT** with the algorithm pinned server-side (reject `alg=none`; RFC 7519/8725); secret ≥ 256-bit; `exp` always set. | RFC 8725 | partial (`internal/shared/token` built + CI-tested: HS256 pinned, `alg=none`/confusion rejected, `exp` required, ≥256-bit secret floor; issuance/verification wiring — Phase 5) |
| X1 | Passwords hashed with **Argon2id** (RFC 9106) — never bcrypt/sha/plaintext. Parameters checked in on introduction. | OWASP ASVS 2.4 | partial (`internal/shared/hasher` built + CI-tested: argon2id, PHC storage, rehash-on-verify, semaphore; wired into the `api` command's DI graph from `config.Auth.Argon2` — Phase 6; no HTTP route reaches Login/Setup yet — Phase 7) |
| X2 | Session/access tokens are **JWT** with the algorithm pinned server-side (reject `alg=none`; RFC 7519/8725); secret ≥ 256-bit; `exp` always set. | RFC 8725 | partial (`internal/shared/token` built + CI-tested: HS256 pinned, `alg=none`/confusion rejected, `exp` required, ≥256-bit secret floor; `config.Load` now fails fast if `AUTH_JWT_SECRET` is unset or < 32 bytes, before any handler can start — Phase 6; issuance/verification call sites — Phase 7) |
| X3 | Refresh tokens are opaque, stored **hashed** (SHA-256), single-use, with reuse-detection that revokes the family (RFC 6749 Section 10.4). | RFC 6749 | partial (model + repo built + CI-tested: hashed at rest, `Rotate`/`RevokeFamily`/`RevokeAllForUser` primitives; rotation + reuse-detection call sites — Phase 5) |
| X4 | Auth cookies use `__Host-` prefix, `HttpOnly`, `Secure`, `SameSite=Lax`. | OWASP | planned |
| X5 | Every request carries a **UUIDv7** request id (RFC 9562); it appears in logs and in the `instance` field of error responses. | RFC 9562 | partial (uuidv7 in models) |
| X6 | API errors use **`application/problem+json`** (RFC 9457); success uses the `{code, message, data}` envelope. Internal error text/stack traces never reach clients. | RFC 9457 | planned |
| X7 | All SQL is **parameterised**; identifiers interpolated into SQL are validated against an allowlist (see `migrationx` table-name check). | CWE-89 | enforced |
| X8 | Money is `shopspring/decimal` end to end — never `float64`. Currency is explicit (ISO 4217). | — | enforced (models) |
| X9 | Structured logging via `slog`; **PII and secrets are redacted before the log call**, never after. | GDPR Art. 25 | planned |
| X9 | Structured logging via `slog`; **PII and secrets are redacted before the log call**, never after. | GDPR Art. 25 | partial (`config.Load` never logs `AUTH_JWT_SECRET`/`MAILGUN_API_KEY`; the `api` command logs only that Mailgun is unconfigured, not the empty credential — Phase 6; broader PII redaction across handlers still `planned`) |
| X10 | Dependencies scanned every CI run: `govulncheck` (CVEs) + `gosec` (SAST); a finding is a red build. | ISO 27001 A.8.8 | enforced |
| X11 | **Uniform authentication errors (anti-enumeration).** Login/forgot/reset never reveal whether an email exists — one generic sentinel per flow (`ErrInvalidCredentials`, `ErrInvalidReset`) and a uniform forgot-password response; timing is equalised via a dummy Argon2 verify on unknown email. The handler must map each sentinel to a single message naming both factors ("email or password is incorrect"), never a factor-specific one. | ISO 27001 A.8.5 · UU PDP 35–36 · OWASP | partial (service enforced: `adminUserService` Login/Forgot/Reset uniform + dummy-verify; handler message mapping — Phase 7) |

---

Expand All @@ -43,7 +44,7 @@ Operator owns the ISMS; we supply technical controls that map to Annex A.
- **A.8.8 Vulnerability management** — `govulncheck` + `gosec` + Dependabot are wired into CI (`enforced`). No merge with an open high finding.
- **A.8.24 Cryptography** — crypto choices are centralised (X1–X4), never hand-rolled per feature. Use `crypto/*` and vetted libraries only.
- **A.8.15 Logging** — security-relevant events (admin login, password change, permission change, data export/delete) emit an audit log entry with actor, action, target id, timestamp, request id. `planned`.
- **A.8.5 Secure authentication** — X1–X4.
- **A.8.5 Secure authentication** — X1–X4, X11 (uniform auth errors / anti-enumeration).
- **A.5.34 Privacy & PII protection** — see Section 2.
- **Data at rest** — SQLite file permissions `0600`; document operator guidance for full-disk / volume encryption. `planned`.

Expand Down
36 changes: 36 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Logres configuration — copy to .env and fill in.
# Each binary reads .env from its working directory at startup; a real
# environment variable always overrides the file. Everything here has a code
# default except AUTH_JWT_SECRET, so you only need to set what you change.
# Lines marked SECRET must never be committed.

# --- Server ---
# api defaults to 8080, web to 8081, webadmin to 8082 when PORT is unset.
PORT=8080

# --- Database (api only) ---
DB_PATH=logres.db

# --- Auth: JWT (api only) ---
# SECRET. Required. Minimum 32 bytes (256-bit HS256 key) or the api won't start.
AUTH_JWT_SECRET=
AUTH_ACCESS_TTL=15m
AUTH_REFRESH_TTL=720h

# --- Auth: Argon2id password hashing (api only) ---
AUTH_ARGON2_MEMORY=65536
AUTH_ARGON2_TIME=3
AUTH_ARGON2_THREADS=1
AUTH_ARGON2_CONCURRENCY=4

# --- Auth: password-reset OTP (api only) ---
OTP_TTL=10m
OTP_ATTEMPT_MAX=5
OTP_LENGTH=8

# --- Mailgun (api only; leave blank to use a no-op mailer in dev) ---
# MAILGUN_API_KEY is SECRET.
MAILGUN_API_KEY=
MAILGUN_DOMAIN=
MAILGUN_BASE_URL=https://api.mailgun.net
MAILGUN_FROM=
18 changes: 11 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# AI Local Settings
# Secrets
.env

# AI / editor local settings
CLAUDE.local.md
.claude/settings.local.json

# Secret and Config
.env
.app-config.yaml
tasks/*

# Build artifacts
/bin/
Expand All @@ -14,10 +14,14 @@ CLAUDE.local.md
coverage.out
cov.out

# Local database files
# Local databases
/db/*.db
/db/*.db-shm
/db/*.db-wal
*.db
*.db-shm
*.db-wal

# OS junk
# OS / tooling junk
.DS_Store
node_modules/
11 changes: 10 additions & 1 deletion .mockery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ packages:
filename: "{{.InterfaceName}}.go"
interfaces:
IAdminUserRepository:
IAdminPasswordResetRepository:
IAdminRefreshTokenRepository:
ICartRepository:
ICategoryRepository:
IFileRepository:
Expand All @@ -27,4 +29,11 @@ packages:
IAdminOrderService:
IProductService:
ICartService:
IOrderService:
IOrderService:
github.com/wigata-intech/logres/internal/shared/mailer:
config:
inpackage: false
dir: "mock/shared/mailer"
filename: "{{.InterfaceName}}.go"
interfaces:
Mailer:
30 changes: 22 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Logres — build & quality automation.
# Single binary; subcommands select the surface (api / web / webAdmin / migrate).
# Four independent binaries, one per surface: the CLI (version/migrate), and
# the api/web/webAdmin HTTP servers — each links only its own deps.

BINARY := logres
PKG := github.com/wigata-intech/logres
Expand All @@ -9,11 +10,17 @@ DIST_DIR := dist
COVER_FILE := coverage.out
COVER_MIN ?= 80

# name:mainpkg pairs built by `build`/`release`.
CMDS := logres:. logres-api:./cmd/api logres-web:./cmd/web logres-webadmin:./cmd/webAdmin

# Version metadata stamped into the binary.
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo none)
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildDate=$(DATE)
LDFLAGS := -s -w \
-X $(PKG)/internal/shared/cli.Version=$(VERSION) \
-X $(PKG)/internal/shared/cli.Commit=$(COMMIT) \
-X $(PKG)/internal/shared/cli.Date=$(DATE)

# Pinned tool versions (kept out of the module graph to keep the binary lean).
GOLANGCI_VERSION := v2.12.2
Expand Down Expand Up @@ -107,17 +114,21 @@ cover-html: test
ci: tidy vet lint vuln sec cover build
@echo "CI OK"

## build: build host binary into bin/
## build: build all 4 host binaries into bin/
.PHONY: build
build:
CGO_ENABLED=0 $(GO) build -trimpath -ldflags '$(LDFLAGS)' -o $(BIN_DIR)/$(BINARY) $(MAIN)
@for pair in $(CMDS); do \
name=$${pair%%:*}; mainpkg=$${pair#*:}; \
echo "build $$name"; \
CGO_ENABLED=0 $(GO) build -trimpath -ldflags '$(LDFLAGS)' -o $(BIN_DIR)/$$name $$mainpkg || exit 1; \
done

## run: build then run (pass ARGS="migrate status")
.PHONY: run
run: build
$(BIN_DIR)/$(BINARY) $(ARGS)

## release: cross-compile for darwin/linux/windows (amd64+arm64)
## release: cross-compile all 4 binaries for darwin/linux/windows (amd64+arm64)
.PHONY: release
release:
@mkdir -p $(DIST_DIR)
Expand All @@ -129,9 +140,12 @@ release:

# xbuild,<os>,<arch> — modernc.org/sqlite is pure Go, so CGO stays off.
define xbuild
@echo "build $(1)/$(2)"
@GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 $(GO) build -trimpath -ldflags '$(LDFLAGS)' \
-o $(DIST_DIR)/$(BINARY)-$(1)-$(2)$(if $(filter windows,$(1)),.exe,) $(MAIN)
@for pair in $(CMDS); do \
name=$${pair%%:*}; mainpkg=$${pair#*:}; \
echo "build $(1)/$(2) $$name"; \
GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 $(GO) build -trimpath -ldflags '$(LDFLAGS)' \
-o $(DIST_DIR)/$$name-$(1)-$(2)$(if $(filter windows,$(1)),.exe,) $$mainpkg || exit 1; \
done
endef

## clean: remove build + coverage artifacts
Expand Down
1 change: 1 addition & 0 deletions assets/css/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "tailwindcss";
Loading
Loading