config.go RawHistoryFolder
1package ports
2
3import (
4 "fmt"
5 "strings"
6 "time"
7 "unicode"
8 "unicode/utf8"
9)
10
11const MaxDuration = 24 * time.Hour
12
13// Zero After disables only that action. No deadline implies another action.
14type Config struct {
15 LockAfter, FadeAfter, OffAfter, SleepAfter time.Duration
16 FadeDuration time.Duration
17 Auth AuthConfig
18}
19type AuthMode string
20
21const (
22 AuthModePassword AuthMode = "password"
23 AuthModePIN AuthMode = "pin"
24)
25
26type PINSource string
27
28const (
29 PINSourceEnv PINSource = "env"
30 PINSourcePass PINSource = "pass"
31)
32
33// Only metadata: values resolved by the visual/auth worker never reach policy.
34// PIN mode always falls back to PAM when its selected source is unavailable or
35// invalid. A wrong PIN is authentication failure, never a fallback trigger.
36type AuthConfig struct {
37 Mode AuthMode
38 PINSource PINSource
39 PINEnv, PINEntry string
40}
41
42func Defaults() Config {
43 return Config{LockAfter: 5 * time.Minute, FadeAfter: 6 * time.Minute, FadeDuration: 2 * time.Second, OffAfter: 7 * time.Minute, SleepAfter: 20 * time.Minute, Auth: AuthConfig{Mode: AuthModePassword}}
44}
45func (c Config) Validate() error {
46 for _, d := range []time.Duration{c.LockAfter, c.FadeAfter, c.OffAfter, c.SleepAfter, c.FadeDuration} {
47 if d < 0 || d > MaxDuration {
48 return fmt.Errorf("duration outside 0..24h")
49 }
50 }
51 a := c.Auth
52 for _, r := range []string{string(a.Mode), string(a.PINSource), a.PINEnv, a.PINEntry} {
53 if !utf8.ValidString(r) {
54 return fmt.Errorf("auth metadata is not valid UTF-8")
55 }
56 }
57 if a.Mode != AuthModePassword && a.Mode != AuthModePIN {
58 return fmt.Errorf("unknown auth mode")
59 }
60 for _, r := range []string{a.PINEnv, a.PINEntry} {
61 if len(r) > 4096 {
62 return fmt.Errorf("auth metadata too large")
63 }
64 for _, c := range r {
65 if unicode.IsControl(c) {
66 return fmt.Errorf("control character in auth metadata")
67 }
68 }
69 }
70 if a.PINEnv != "" {
71 for i, r := range a.PINEnv {
72 if !(r == '_' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || i > 0 && r >= '0' && r <= '9') {
73 return fmt.Errorf("invalid environment reference")
74 }
75 }
76 }
77 if a.PINEntry != "" {
78 if strings.HasPrefix(a.PINEntry, "-") || strings.HasPrefix(a.PINEntry, "/") || strings.Contains(a.PINEntry, "\\") {
79 return fmt.Errorf("invalid pass entry")
80 }
81 for _, part := range strings.Split(a.PINEntry, "/") {
82 if part == "" || part == "." || part == ".." {
83 return fmt.Errorf("invalid pass entry")
84 }
85 }
86 }
87 switch a.PINSource {
88 case "":
89 if a.Mode == AuthModePIN || a.PINEnv != "" || a.PINEntry != "" {
90 return fmt.Errorf("PIN metadata requires source")
91 }
92 case PINSourceEnv:
93 if a.PINEnv == "" || a.PINEntry != "" {
94 return fmt.Errorf("env source requires only pin-env")
95 }
96 case PINSourcePass:
97 if a.PINEntry == "" || a.PINEnv != "" {
98 return fmt.Errorf("pass source requires only pin-entry")
99 }
100 default:
101 return fmt.Errorf("unknown PIN source")
102 }
103 return nil
104}
105
106type ConfigChanged struct{ Config Config }