Files
mayatnikovandClaude Opus 5 597c55301c Этап 1: аккаунты — argon2id, сессии, ключевой блоб, вход и регистрация
Сервер: миграция 001 со всей схемой storage.md, store на modernc.org/sqlite
(WAL, foreign_keys, один писатель), фоновая чистка раз в час, argon2id
с параметрами ADR-021 и сверкой constant-time, сессии по SHA-256 токена,
cookie bare_session, глобальная проверка Origin, девять эндпоинтов аккаунта.
Ник в журнал не попадает: для /api/ пишется шаблон маршрута.

Клиент: crypto.js по crypto.md построчно — мастер из пароля, два независимых
ключа из мастера, ключевой блоб с ником в AAD, отпечаток от сырой точки;
db.js со всеми хранилищами версии 1; экран входа и регистрации, настройки
со сменой пароля, выходом и удалением аккаунта.

Пароль не покидает клиент: проверено на боевом сервере — ни пароля, ни priv.d
ни в одном теле запроса, вход на втором устройстве даёт тот же отпечаток.

ADR-027: код internal для 500, причина только в журнале.
ADR-028: тексты состояний клиента сведены в ui.md.
ADR-029: вход под другим ником стирает историю только после подтверждения.
ADR-030: верхняя граница итераций KDF, проверка границ на обеих сторонах.
ADR-031: служебный выход перед повторным входом не заканчивает сеанс.
ADR-032: каталог состояния 0700, файлы базы 0600.

Прямые зависимости: modernc.org/sqlite, golang.org/x/crypto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
2026-08-22 14:06:07 +03:00

86 lines
2.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package auth
import (
"crypto/sha256"
"encoding/base64"
"testing"
"github.com/xmatic-squad/bare/internal/store"
)
func TestParams(t *testing.T) {
if got := Current.String(); got != "argon2id,m=19456,t=2,p=1" {
t.Errorf("запись параметров: получено %q", got)
}
got, err := ParseParams("argon2id,m=19456,t=2,p=1")
if err != nil || got != Current {
t.Errorf("разбор параметров: получено %+v, %v", got, err)
}
for _, bad := range []string{"", "argon2id", "argon2i,m=1,t=1,p=1", "argon2id,m=0,t=2,p=1", "argon2id,m=19456,t=2,p=1,junk"} {
if _, err := ParseParams(bad); err == nil {
t.Errorf("%q разобрано, ожидалась ошибка", bad)
}
}
}
func TestHashVerify(t *testing.T) {
key := []byte("тридцать два байта authKey, ну почти")
cred, err := Hash(key)
if err != nil {
t.Fatalf("Hash: %v", err)
}
if len(cred.Hash) != KeyLen || len(cred.Salt) != SaltLen || cred.Params != Current.String() {
t.Fatalf("хеш: %d байт, соль %d байт, параметры %q", len(cred.Hash), len(cred.Salt), cred.Params)
}
if ok, rehash := Verify(key, cred); !ok || rehash {
t.Errorf("верный authKey: ok=%v rehash=%v", ok, rehash)
}
if ok, _ := Verify([]byte("другой ключ"), cred); ok {
t.Error("неверный authKey принят")
}
// Битая строка параметров — не повод считать хеш подошедшим.
broken := cred
broken.Params = "argon2id"
if ok, _ := Verify(key, broken); ok {
t.Error("хеш с неразобранными параметрами принят")
}
}
func TestVerifyAsksForRehash(t *testing.T) {
key := []byte("authKey")
old := Params{Memory: 8192, Time: 1, Threads: 1}
cred := store.Credential{
Hash: derive(key, make([]byte, SaltLen), old),
Salt: make([]byte, SaltLen),
Params: old.String(),
}
ok, rehash := Verify(key, cred)
if !ok || !rehash {
t.Errorf("устаревшие параметры: ok=%v rehash=%v, ожидалось true/true", ok, rehash)
}
}
func TestToken(t *testing.T) {
token, hash, err := NewToken()
if err != nil {
t.Fatalf("NewToken: %v", err)
}
raw, err := base64.RawURLEncoding.DecodeString(token)
if err != nil || len(raw) != TokenLen {
t.Fatalf("токен: %q (%v)", token, err)
}
sum := sha256.Sum256(raw)
if string(hash) != string(sum[:]) {
t.Error("в базу уходит не sha-256 токена")
}
got, ok := TokenHash(token)
if !ok || string(got) != string(hash) {
t.Error("TokenHash не совпал с NewToken")
}
for _, bad := range []string{"", "не base64!", base64.RawURLEncoding.EncodeToString([]byte("коротко"))} {
if _, ok := TokenHash(bad); ok {
t.Errorf("мусор %q принят за токен", bad)
}
}
}