v0.5.1 31f302d

review_test.go RawHistoryFolder

1package auth
2
3import (
4	"bytes"
5	"errors"
6	"io"
7	"testing"
8	"time"
9
10	"github.com/bnema/neferafk/internal/ports"
11	pam "github.com/bnema/purego-pam"
12)
13
14func TestFallbackReadyIsExplicitEvenWithoutPAM(t *testing.T) {
15	for _, available := range []bool{false, true} {
16		var output bytes.Buffer
17		var backend pamBackend
18		if available {
19			backend = newMockpamBackend(t)
20		}
21		if err := runScoped(bytes.NewReader(nil), &output, 3, nil, backend, "user", true); !errors.Is(err, io.EOF) {
22			t.Fatal(err)
23		}
24		f, err := ReadFrame(&output)
25		if err != nil {
26			t.Fatal(err)
27		}
28		if available {
29			if f.Code != ports.AuthFallbackPassword || len(f.Payload) != 0 {
30				t.Fatalf("fallback ready %+v", f)
31			}
32		} else if f.Code != ports.AuthUnavailable || !bytes.Equal(f.Payload, []byte{byte(ports.AuthFallbackToPassword)}) {
33			t.Fatalf("fallback unavailable %+v", f)
34		}
35	}
36}
37
38func TestEchoOnNeverReceivesPresubmittedCredential(t *testing.T) {
39	var input, output bytes.Buffer
40	if err := WriteFrame(&input, ports.AuthFrame{Kind: ports.AuthSecret, Generation: 3, Attempt: 9, Payload: []byte("explicit-response")}); err != nil {
41		t.Fatal(err)
42	}
43	w := &worker{r: &input, w: &output, generation: 3, attempt: 9, deadline: time.Now().Add(time.Second), first: true, secretLen: 8, secret: make([]byte, 512)}
44	copy(w.secret, "password")
45	answer := make([]byte, 512)
46	n, err := w.Respond(pam.Message{Style: pam.PromptEchoOn}, answer)
47	if err != nil || string(answer[:n]) != "explicit-response" || !w.first {
48		t.Fatal("echo-on leaked or consumed initial password")
49	}
50	clear(answer)
51	n, err = w.Respond(pam.Message{Style: pam.PromptEchoOff}, answer)
52	if err != nil || string(answer[:n]) != "password" || w.first || !bytes.Equal(w.secret, make([]byte, 512)) {
53		t.Fatal("echo-off did not consume and wipe initial credential")
54	}
55	f, err := ReadFrame(&output)
56	if err != nil || f.Kind != ports.AuthPrompt || len(f.Payload) != 0 {
57		t.Fatal("echo-on did not emit generic prompt")
58	}
59}
60
61func TestCodecRejectsContradictoryRoles(t *testing.T) {
62	frames := []ports.AuthFrame{
63		{Kind: ports.AuthReady, Generation: 1},
64		{Kind: ports.AuthResult, Generation: 1, Attempt: 1},
65		{Kind: ports.AuthStatus, Generation: 1, Attempt: 1},
66		{Kind: ports.AuthResult, Code: ports.AuthPIN, Generation: 1, Attempt: 1},
67		{Kind: ports.AuthResult, Code: ports.AuthSuccess, Generation: 1, Attempt: 1, Payload: []byte("raw")},
68		{Kind: ports.AuthReady, Code: ports.AuthUnavailable, Generation: 1, Payload: []byte{99}},
69		{Kind: ports.AuthReady, Code: ports.AuthPassword, Generation: 1, Attempt: 1},
70		{Kind: ports.AuthAttempt, Code: ports.AuthSuccess, Generation: 1, Attempt: 1},
71		{Kind: ports.AuthSecret, Generation: 1},
72	}
73	for _, f := range frames {
74		if err := WriteFrame(io.Discard, f); !errors.Is(err, ErrProtocol) {
75			t.Fatalf("contradictory role accepted: kind=%d code=%d", f.Kind, f.Code)
76		}
77	}
78}
79
80func TestStaleGenerationSecretNeverProducesResult(t *testing.T) {
81	var input, output bytes.Buffer
82	f := ports.AuthFrame{Kind: ports.AuthAttempt, Generation: 2, Attempt: 1, Payload: []byte("synthetic")}
83	if err := WriteFrame(&input, f); err != nil {
84		t.Fatal(err)
85	}
86	if err := run(&input, &output, 3, nil, nil, ""); !errors.Is(err, ErrProtocol) {
87		t.Fatalf("stale generation accepted: %v", err)
88	}
89	ready, err := ReadFrame(&output)
90	if err != nil || ready.Kind != ports.AuthReady {
91		t.Fatal("missing readiness")
92	}
93	if _, err := ReadFrame(&output); !errors.Is(err, io.EOF) {
94		t.Fatal("stale secret produced result")
95	}
96}
97
98func TestDedicatedSecretPageCapacityAndRetirement(t *testing.T) {
99	for _, size := range []int{32, 512} {
100		b, err := newSecret(size)
101		if err != nil {
102			t.Fatal(err)
103		}
104		if len(b.data) != size || cap(b.data) != size || len(b.page) < size {
105			t.Fatal("secret capacity not bounded")
106		}
107		copy(b.data, "synthetic")
108		b.close()
109		if b.data != nil || b.page != nil {
110			t.Fatal("retired native memory still accessible")
111		}
112		b.close()
113	}
114}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 9

↑↓ moveenter openesc close