110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
package adminreset
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
ErrVerificationUnavailable = errors.New("no verification method available")
|
|
ErrVerificationFailed = errors.New("verification failed")
|
|
)
|
|
|
|
func responseError(code string) error {
|
|
switch code {
|
|
case "VERIFICATION_UNAVAILABLE":
|
|
return ErrVerificationUnavailable
|
|
case "VERIFICATION_FAILED":
|
|
return ErrVerificationFailed
|
|
case "REPLAY_OR_EXPIRED":
|
|
return ErrExpiredSession
|
|
default:
|
|
return ErrInvalidRequest
|
|
}
|
|
}
|
|
|
|
func SendVerifiedReset(ctx context.Context, conn net.Conn, input func(string) (string, error), readPassword func() (string, error)) error {
|
|
if conn == nil || input == nil || readPassword == nil {
|
|
return ErrInvalidRequest
|
|
}
|
|
deadline := time.Now().Add(controlRequestTimeout)
|
|
if contextDeadline, ok := ctx.Deadline(); ok && contextDeadline.Before(deadline) {
|
|
deadline = contextDeadline
|
|
}
|
|
if err := conn.SetDeadline(deadline); err != nil {
|
|
return fmt.Errorf("set reset deadline: %w", err)
|
|
}
|
|
defer conn.SetDeadline(time.Time{})
|
|
challenge, err := DecodeResponse(conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !challenge.OK {
|
|
return responseError(challenge.Code)
|
|
}
|
|
if challenge.Code != "CHALLENGE" || challenge.Nonce == "" {
|
|
return ErrInvalidRequest
|
|
}
|
|
var prompt strings.Builder
|
|
prompt.WriteString("请选择安全验证方式(输入序号;选择邮箱即发送验证码):")
|
|
for i, method := range challenge.Methods {
|
|
fmt.Fprintf(&prompt, " %d.%s", i+1, method.Label)
|
|
}
|
|
choice, err := input(prompt.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
index, err := strconv.Atoi(strings.TrimSpace(choice))
|
|
if err != nil || index < 1 || index > len(challenge.Methods) {
|
|
return ErrInvalidRequest
|
|
}
|
|
exchange := func(request Request) error {
|
|
request.Version, request.Nonce = ProtocolVersion, challenge.Nonce
|
|
if err := json.NewEncoder(conn).Encode(request); err != nil {
|
|
return err
|
|
}
|
|
response, err := DecodeResponse(conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !response.OK {
|
|
return responseError(response.Code)
|
|
}
|
|
return nil
|
|
}
|
|
if err := exchange(Request{Action: "select", Method: challenge.Methods[index-1].ID}); err != nil {
|
|
return err
|
|
}
|
|
code, err := input("请输入所选方式的验证码或安全码")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = exchange(Request{Action: "verify", Code: code})
|
|
clearPassword(&code)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value, err := readPassword()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer clearPassword(&value)
|
|
if err := json.NewEncoder(conn).Encode(Request{Version: ProtocolVersion, Nonce: challenge.Nonce, Action: "reset", NewPassword: value}); err != nil {
|
|
return fmt.Errorf("send reset request: %w", err)
|
|
}
|
|
response, err := DecodeResponse(conn)
|
|
if err != nil {
|
|
return fmt.Errorf("read reset response: %w", err)
|
|
}
|
|
if !response.OK {
|
|
return responseError(response.Code)
|
|
}
|
|
return nil
|
|
}
|