-
Notifications
You must be signed in to change notification settings - Fork 80
Expand file tree
/
Copy pathaccess_tfa.go
More file actions
137 lines (124 loc) · 5.17 KB
/
Copy pathaccess_tfa.go
File metadata and controls
137 lines (124 loc) · 5.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package proxmox
import (
"context"
"errors"
"fmt"
)
// This file wraps /access/tfa/* — the modern TFA endpoint family that
// supports listing, adding, updating, and removing individual TFA entries
// per user. The older /access/users/{userid}/tfa surface (which returns the
// summary `TFA` struct) is wrapped in access.go as User.GetTFA and is
// retained for backward compatibility.
// TFAUserEntry is one row in GET /access/tfa — a user that has at least one
// TFA entry configured.
type TFAUserEntry struct {
UserID string `json:"userid,omitempty"`
Entries []TFAEntryInfo `json:"entries,omitempty"`
TOTP bool `json:"totp,omitempty"`
YubicoOTP bool `json:"yubico,omitempty"`
U2F bool `json:"u2f,omitempty"`
Webauthn bool `json:"webauthn,omitempty"`
Recovery []int `json:"recovery,omitempty"`
}
// TFAEntryInfo is the read shape of a single TFA entry.
type TFAEntryInfo struct {
ID string `json:"id,omitempty"`
Type string `json:"type,omitempty"` // totp | webauthn | u2f | yubico | recovery
Description string `json:"description,omitempty"`
Created int64 `json:"created,omitempty"`
Enable IntOrBool `json:"enable,omitempty"`
}
// TFAEntryOptions is the POST body for adding a TFA entry.
// - Type is required ("totp" | "webauthn" | "u2f" | "yubico" | "recovery").
// - For TOTP: set TOTP (the otpauth:// URI) and Value (the current OTP to prove enrollment).
// - For Webauthn/U2F: set Challenge / Value with the client's signed assertion.
// - Password is the requesting user's current password (required when changing another user's TFA).
type TFAEntryOptions struct {
Type string `json:"type"`
Description string `json:"description,omitempty"`
TOTP string `json:"totp,omitempty"`
Value string `json:"value,omitempty"`
Challenge string `json:"challenge,omitempty"`
Password string `json:"password,omitempty"`
}
// TFAEntryUpdateOptions is the PUT body for updating an existing entry.
// Only Enable / Description are settable.
type TFAEntryUpdateOptions struct {
Enable *bool `json:"enable,omitempty"`
Description string `json:"description,omitempty"`
Password string `json:"password,omitempty"`
}
// TFAUsers lists every user with at least one TFA entry configured.
func (c *Client) TFAUsers(ctx context.Context) (users []*TFAUserEntry, err error) {
err = c.Get(ctx, "/access/tfa", &users)
return
}
// TFAEntries lists all TFA entries for a given user.
func (c *Client) TFAEntries(ctx context.Context, userid string) (entries []*TFAEntryInfo, err error) {
if userid == "" {
err = errors.New("userid is required")
return
}
err = c.Get(ctx, fmt.Sprintf("/access/tfa/%s", userid), &entries)
return
}
// TFAEntry reads a single TFA entry by id.
func (c *Client) TFAEntry(ctx context.Context, userid, id string) (entry *TFAEntryInfo, err error) {
if userid == "" || id == "" {
err = errors.New("userid and entry id are required")
return
}
err = c.Get(ctx, fmt.Sprintf("/access/tfa/%s/%s", userid, id), &entry)
return
}
// NewTFAEntry adds a TFA entry for a user. Returns the new entry id on
// success — PVE wraps it in a {"id": "..."} envelope inside data.
func (c *Client) NewTFAEntry(ctx context.Context, userid string, opts *TFAEntryOptions) (id string, err error) {
if userid == "" {
return "", errors.New("userid is required")
}
if opts == nil || opts.Type == "" {
return "", errors.New("tfa entry type is required")
}
var resp struct {
ID string `json:"id"`
}
if err = c.Post(ctx, fmt.Sprintf("/access/tfa/%s", userid), opts, &resp); err != nil {
return "", err
}
return resp.ID, nil
}
// UpdateTFAEntry mutates an existing entry (enable/disable, description).
func (c *Client) UpdateTFAEntry(ctx context.Context, userid, id string, opts *TFAEntryUpdateOptions) error {
if userid == "" || id == "" {
return errors.New("userid and entry id are required")
}
if opts == nil {
opts = &TFAEntryUpdateOptions{}
}
return c.Put(ctx, fmt.Sprintf("/access/tfa/%s/%s", userid, id), opts, nil)
}
// DeleteTFAEntry removes a single TFA entry. password is the caller's current
// password when changing another user's TFA (PVE may require it server-side).
// Pass "" to omit.
func (c *Client) DeleteTFAEntry(ctx context.Context, userid, id, password string) error {
if userid == "" || id == "" {
return errors.New("userid and entry id are required")
}
path := fmt.Sprintf("/access/tfa/%s/%s", userid, id)
if password != "" {
// PVE accepts password via body on DELETE; we pass it as a body map.
return c.Delete(ctx, path, map[string]string{"password": password})
}
return c.Delete(ctx, path, nil)
}
// UnlockUserTFA clears the TFA lockout flag set after too many failed attempts
// for the given user. Unlike User.UnlockTFA (which actually removes the user's
// TFA configuration via the legacy endpoint), this leaves the entries intact —
// it just resets the failure counter. PUT /access/users/{userid}/unlock-tfa.
func (c *Client) UnlockUserTFA(ctx context.Context, userid string) error {
if userid == "" {
return errors.New("userid is required")
}
return c.Put(ctx, fmt.Sprintf("/access/users/%s/unlock-tfa", userid), nil, nil)
}