items 5-7: passkey step-up, tools enable/disable, note RAG — end to end
Completes the three in-flight open items and fixes the away-fallthrough bug. Item 7 — passkey step-up (WebAuthn): - internal/webauthn: ES256/P-256 register + assert with real ecdsa signature verification, minimal CBOR/COSE decode, PasskeySession (L2→L3 on assert, decays after TTL). Drop the RS256 offer we can't verify (register-ok/ assert-fail trap). Verify rpIdHash + UP/UV flags in FinishAssertion — UV is the step-up gesture. Round-trip test with negative cases (tampered sig, missing UV, wrong origin). - cmd/mavweb: /auth/passkey enroll+assert page (the only surface that can do a WebAuthn gesture) + the four begin/finish endpoints. Without this the daemon's PasskeySession swap leaves /tools enable permanently blocked. - daemon wires PasskeySession as the auth Session + srv.StepUp; policy gates MethodAssertStepUp at AuthRead. Item 5 — tools page: DisableTool through store/ipc/client/wire; /tools grows a disable action and a link to the passkey page. Lifecycle test. Item 6 — note RAG: PhraseQuery on the phraser (LLM-composed answer over top-k notes, raw-notes fallback); IntentQuery routes through it. Stub returns a deterministic summary. Item 2 — away-fallthrough: on ErrVoiceNoSession the dispatcher now reroutes through the AWAY table (sev3→ntfy, sev4→telegram-repeat-til-ack, sev≤2→drop) instead of silently dropping / mis-routing to the present-list remainder. Covers DispatchNudge + DispatchReminder. 4 tests. Also: re-add ProposeTool to CoreAPI (dropped in a comment rewrite), fix missing imports + a duplicate block left mid-edit, drop dead AssertStepUpFunc, gitignore /mavcaldav. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kami/maven/internal/auth"
|
||||
)
|
||||
|
||||
// PasskeySession implements auth.Session backed by WebAuthn passkey assertion.
|
||||
// The session starts at Layer2 (passkey is enrolled, this session exists) and
|
||||
// bumps to Layer3 on successful Assert(), which lasts for assertionTTL before
|
||||
// decaying back to Layer2.
|
||||
//
|
||||
// A nil *PasskeySession is a valid zero: it acts like a session with no
|
||||
// credentials enrolled (always L2, Assert returns ErrStepUpUnsupported).
|
||||
// This mirrors the FloorSession behavior when passkey is not configured.
|
||||
type PasskeySession struct {
|
||||
mu sync.Mutex
|
||||
assertedAt time.Time // zero = not asserted this session
|
||||
assertionTTL time.Duration
|
||||
}
|
||||
|
||||
// NewPasskeySession creates a session. The caller chooses the assertion TTL
|
||||
// (how long a step-up gesture remains valid). 5 minutes is a sensible default.
|
||||
func NewPasskeySession(assertionTTL time.Duration) *PasskeySession {
|
||||
if assertionTTL <= 0 {
|
||||
assertionTTL = 5 * time.Minute
|
||||
}
|
||||
return &PasskeySession{assertionTTL: assertionTTL}
|
||||
}
|
||||
|
||||
// CurrentLayer returns L3 if step-up has been asserted within the TTL,
|
||||
// otherwise L2 (passkey enrolled, this session proven). A nil receiver
|
||||
// returns L2 (no way to reach L3 without a session).
|
||||
func (s *PasskeySession) CurrentLayer(_ context.Context, _ auth.Scope) auth.Layer {
|
||||
if s == nil {
|
||||
return auth.Layer2
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.assertedAt.IsZero() && time.Since(s.assertedAt) < s.assertionTTL {
|
||||
return auth.Layer3
|
||||
}
|
||||
return auth.Layer2
|
||||
}
|
||||
|
||||
// Assert records a successful step-up gesture. The session bumps to L3 for
|
||||
// the assertion TTL. A nil receiver returns ErrStepUpUnsupported.
|
||||
func (s *PasskeySession) Assert(_ context.Context, _ auth.Scope) error {
|
||||
if s == nil {
|
||||
return fmt.Errorf("%w: passkey session not configured", auth.ErrStepUpUnsupported)
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.assertedAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user