mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
Sudo Config Gui (#603)
* feat: add gui elements to configure ssh pw cache This adds a dropdown for on/off/notimeout, a number entry box for a timeout value, and a toggle for clearing when the computer sleeps. * fix: improve password timeout entry This makes the password timeout more consistent by using an inline settings element. It also creates the inline settings element to parse the input. * feat: turn sudo password caching on and off * feat: use configurable sudo timeout This makes it possible to control how long waveterm stores your sudo password. Note that if it changes, it immediately clears the cached passwords. * fix: clear existing sudo passwords if switched off When the sudo password store state is changed to "off", all existing passwords must immediately be cleared automatically. * feat: allow clearing sudo passwords on suspend This option makes it so the sudo passwords will be cleared when the computer falls asleep. It will never be used in the case where the password is set to never time out. * feat: allow notimeout to prevent sudo pw clear This option allows the sudo timeout to be ignored while it is selected. * feat: adjust current deadline based on user config This allows the deadline to update as changes to the config are happening. * fix: reject a sudopwtimeout of 0 on the backend * fix: use the default sudoPwTimeout for empty input * fix: specify the timeout length is minutes * fix: store sudopwtimeout in ms instead of minutes * fix: formatting the default sudo timeout By changing the order of operations, this no longer shows up as NaN if the default is used. * refactor: consolidate inlinesettingstextedit This removes the number variant and combines them into the same class with an option to switch between the two behaviors. * refactor: consolidate textfield and numberfield This removes the number variant of textfield. The textfield component can now act as a numberfield when the optional isNumber prop is true.
This commit is contained in:
parent
5e3243564b
commit
a449cec33a
@ -46,6 +46,8 @@ export const TabIcons = [
|
||||
"heart",
|
||||
"file",
|
||||
];
|
||||
export const DefaultSudoPwStore = "on";
|
||||
export const DefaultSudoPwTimeoutMs = 5 * 60 * 1000;
|
||||
|
||||
export const MaxWebSocketSendSize = 64 * 1024 - 100;
|
||||
|
||||
|
@ -197,6 +197,35 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
GlobalModel.clientSettingsViewModel.closeView();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
getSudoPwStoreOptions(): DropdownItem[] {
|
||||
const sudoCacheSources: DropdownItem[] = [];
|
||||
sudoCacheSources.push({ label: "On", value: "on" });
|
||||
sudoCacheSources.push({ label: "Off", value: "off" });
|
||||
sudoCacheSources.push({ label: "On Without Timeout", value: "notimeout" });
|
||||
return sudoCacheSources;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeSudoPwStoreConfig(store: string) {
|
||||
const prtn = GlobalCommandRunner.setSudoPwStore(store);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeSudoPwTimeoutConfig(timeout: string) {
|
||||
if (Number(timeout) != 0) {
|
||||
const prtn = GlobalCommandRunner.setSudoPwTimeout(timeout);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeSudoPwClearOnSleepConfig(clearOnSleep: boolean) {
|
||||
const prtn = GlobalCommandRunner.setSudoPwClearOnSleep(clearOnSleep);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
render() {
|
||||
const isHidden = GlobalModel.activeMainView.get() != "clientsettings";
|
||||
if (isHidden) {
|
||||
@ -217,6 +246,9 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
const curTheme = GlobalModel.getThemeSource();
|
||||
const termThemes = getTermThemes(GlobalModel.termThemes.get(), "Wave Default");
|
||||
const currTermTheme = GlobalModel.getTermThemeSettings()["root"] ?? termThemes[0].label;
|
||||
const curSudoPwStore = GlobalModel.getSudoPwStore();
|
||||
const curSudoPwTimeout = String(GlobalModel.getSudoPwTimeout());
|
||||
const curSudoPwClearOnSleep = GlobalModel.getSudoPwClearOnSleep();
|
||||
|
||||
return (
|
||||
<MainView className="clientsettings-view" title="Client Settings" onClose={this.handleClose}>
|
||||
@ -375,6 +407,40 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Remember Sudo Password</div>
|
||||
<div className="settings-input">
|
||||
<Dropdown
|
||||
className="hotkey-dropdown"
|
||||
options={this.getSudoPwStoreOptions()}
|
||||
defaultValue={curSudoPwStore}
|
||||
onChange={this.handleChangeSudoPwStoreConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Sudo Timeout (Minutes)</div>
|
||||
<div className="settings-input">
|
||||
<InlineSettingsTextEdit
|
||||
placeholder=""
|
||||
text={curSudoPwTimeout}
|
||||
value={curSudoPwTimeout}
|
||||
onChange={this.handleChangeSudoPwTimeoutConfig}
|
||||
maxLength={6}
|
||||
showIcon={true}
|
||||
isNumber={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Clear Sudo Password on Sleep</div>
|
||||
<div className="settings-input">
|
||||
<Toggle
|
||||
checked={curSudoPwClearOnSleep}
|
||||
onChange={this.handleChangeSudoPwClearOnSleepConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsError errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
</MainView>
|
||||
|
@ -8,7 +8,6 @@ export { InputDecoration } from "./inputdecoration";
|
||||
export { LinkButton } from "./linkbutton";
|
||||
export { Markdown } from "./markdown";
|
||||
export { Modal } from "./modal";
|
||||
export { NumberField } from "./numberfield";
|
||||
export { PasswordField } from "./passwordfield";
|
||||
export { ResizableSidebar } from "./resizablesidebar";
|
||||
export { SettingsError } from "./settingserror";
|
||||
|
@ -22,6 +22,7 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
maxLength: number;
|
||||
placeholder: string;
|
||||
showIcon?: boolean;
|
||||
isNumber?: boolean;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
@ -46,6 +47,12 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
handleChangeText(e: any): void {
|
||||
const isNumber = this.props.isNumber ?? false;
|
||||
const value = e.target.value;
|
||||
if (isNumber && value !== "" && !/^\d*$/.test(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mobx.action(() => {
|
||||
this.tempText.set(e.target.value);
|
||||
})();
|
||||
|
@ -1,39 +0,0 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
|
||||
import { TextField } from "./textfield";
|
||||
|
||||
class NumberField extends TextField {
|
||||
@boundMethod
|
||||
handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const { required, onChange } = this.props;
|
||||
const inputValue = e.target.value;
|
||||
|
||||
// Allow only numeric input
|
||||
if (inputValue === "" || /^\d*$/.test(inputValue)) {
|
||||
// Update the internal state only if the component is not controlled.
|
||||
if (this.props.value === undefined) {
|
||||
const isError = required ? inputValue.trim() === "" : false;
|
||||
|
||||
this.setState({
|
||||
internalValue: inputValue,
|
||||
error: isError,
|
||||
hasContent: Boolean(inputValue),
|
||||
});
|
||||
}
|
||||
|
||||
onChange && onChange(inputValue);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// Use the render method from TextField but add the onKeyDown handler
|
||||
const renderedTextField = super.render();
|
||||
return React.cloneElement(renderedTextField);
|
||||
}
|
||||
}
|
||||
|
||||
export { NumberField };
|
@ -27,6 +27,7 @@ interface TextFieldProps {
|
||||
maxLength?: number;
|
||||
autoFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
isNumber?: boolean;
|
||||
}
|
||||
|
||||
interface TextFieldState {
|
||||
@ -108,9 +109,13 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
|
||||
@boundMethod
|
||||
handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const { required, onChange } = this.props;
|
||||
const { required, onChange, isNumber } = this.props;
|
||||
const inputValue = e.target.value;
|
||||
|
||||
if (isNumber && inputValue !== "" && !/^\d*$/.test(inputValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if value is empty and the field is required
|
||||
if (required && !inputValue) {
|
||||
this.setState({ error: true, hasContent: false });
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "@/models";
|
||||
import { Modal, TextField, NumberField, InputDecoration, Dropdown, PasswordField, Tooltip } from "@/elements";
|
||||
import { Modal, TextField, InputDecoration, Dropdown, PasswordField, Tooltip } from "@/elements";
|
||||
import * as util from "@/util/util";
|
||||
|
||||
import "./createremoteconn.less";
|
||||
@ -236,11 +236,12 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
|
||||
/>
|
||||
</div>
|
||||
<div className="port-section">
|
||||
<NumberField
|
||||
<TextField
|
||||
label="Port"
|
||||
placeholder="22"
|
||||
value={this.tempPort.get()}
|
||||
onChange={this.handleChangePort}
|
||||
isNumber={true}
|
||||
decoration={{
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
|
@ -419,6 +419,17 @@ function mainResizeHandler(_: any, win: Electron.BrowserWindow) {
|
||||
});
|
||||
}
|
||||
|
||||
function mainPowerHandler(status: string) {
|
||||
const url = new URL(getBaseHostPort() + "/api/power-monitor");
|
||||
const fetchHeaders = getFetchHeaders();
|
||||
const body = { status: status };
|
||||
fetch(url, { method: "post", body: JSON.stringify(body), headers: fetchHeaders })
|
||||
.then((resp) => handleJsonFetchResponse(url, resp))
|
||||
.catch((err) => {
|
||||
console.log("error setting power monitor state", err);
|
||||
});
|
||||
}
|
||||
|
||||
function calcBounds(clientData: ClientDataType): Electron.Rectangle {
|
||||
const primaryDisplay = electron.screen.getPrimaryDisplay();
|
||||
const pdBounds = primaryDisplay.bounds;
|
||||
@ -946,3 +957,5 @@ function configureAutoUpdater(enabled: boolean) {
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
electron.powerMonitor.on("suspend", () => mainPowerHandler("suspend"));
|
||||
|
@ -467,6 +467,31 @@ class CommandRunner {
|
||||
return GlobalModel.submitCommand("client", "setrightsidebar", null, kwargs, false);
|
||||
}
|
||||
|
||||
setSudoPwStore(store: string): Promise<CommandRtnType> {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
sudopwstore: store,
|
||||
};
|
||||
return GlobalModel.submitCommand("client", "set", null, kwargs, false);
|
||||
}
|
||||
|
||||
setSudoPwTimeout(timeout: string): Promise<CommandRtnType> {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
sudopwtimeout: timeout,
|
||||
};
|
||||
return GlobalModel.submitCommand("client", "set", null, kwargs, false);
|
||||
}
|
||||
|
||||
setSudoPwClearOnSleep(clear: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
sudopwclearonsleep: String(clear),
|
||||
};
|
||||
console.log(kwargs);
|
||||
return GlobalModel.submitCommand("client", "set", null, kwargs, false);
|
||||
}
|
||||
|
||||
editBookmark(bookmarkId: string, desc: string, cmdstr: string) {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
|
@ -455,6 +455,22 @@ class Model {
|
||||
return this.termFontSize.get();
|
||||
}
|
||||
|
||||
getSudoPwStore(): string {
|
||||
let cdata = this.clientData.get();
|
||||
return cdata?.feopts?.sudopwstore ?? appconst.DefaultSudoPwStore;
|
||||
}
|
||||
|
||||
getSudoPwTimeout(): number {
|
||||
let cdata = this.clientData.get();
|
||||
const sudoPwTimeoutMs = cdata?.feopts?.sudopwtimeoutms ?? appconst.DefaultSudoPwTimeoutMs;
|
||||
return sudoPwTimeoutMs / 1000 / 60;
|
||||
}
|
||||
|
||||
getSudoPwClearOnSleep(): boolean {
|
||||
let cdata = this.clientData.get();
|
||||
return !cdata?.feopts?.nosudopwclearonsleep;
|
||||
}
|
||||
|
||||
updateTermFontSizeVars() {
|
||||
let lhe = this.recomputeLineHeightEnv();
|
||||
mobx.action(() => {
|
||||
|
3
src/types/custom.d.ts
vendored
3
src/types/custom.d.ts
vendored
@ -598,6 +598,9 @@ declare global {
|
||||
termfontfamily: string;
|
||||
theme: NativeThemeSource;
|
||||
termthemesettings: TermThemeSettingsType;
|
||||
sudopwstore: "on" | "off" | "notimeout";
|
||||
sudopwtimeoutms: number;
|
||||
nosudopwclearonsleep: boolean;
|
||||
};
|
||||
|
||||
type ConfirmFlagsType = {
|
||||
|
@ -209,6 +209,33 @@ func HandleSetWinSize(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJsonSuccess(w, true)
|
||||
}
|
||||
|
||||
func HandlePowerMonitor(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var body sstore.PowerMonitorEventType
|
||||
err := decoder.Decode(&body)
|
||||
if err != nil {
|
||||
WriteJsonError(w, fmt.Errorf(ErrorDecodingJson, err))
|
||||
return
|
||||
}
|
||||
cdata, err := sstore.EnsureClientData(r.Context())
|
||||
if err != nil {
|
||||
WriteJsonError(w, err)
|
||||
return
|
||||
}
|
||||
switch body.Status {
|
||||
case "suspend":
|
||||
if !cdata.FeOpts.NoSudoPwClearOnSleep && cdata.FeOpts.SudoPwStore != "notimeout" {
|
||||
for _, proc := range remote.GetRemoteMap() {
|
||||
proc.ClearCachedSudoPw()
|
||||
}
|
||||
}
|
||||
WriteJsonSuccess(w, true)
|
||||
default:
|
||||
WriteJsonError(w, fmt.Errorf("unknown status: %s", body.Status))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// params: fg, active, open
|
||||
func HandleLogActiveState(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
@ -1149,6 +1176,7 @@ func main() {
|
||||
gr.HandleFunc(bufferedpipe.BufferedPipeGetterUrl, AuthKeyWrapAllowHmac(bufferedpipe.HandleGetBufferedPipeOutput))
|
||||
gr.HandleFunc("/api/get-client-data", AuthKeyWrap(HandleGetClientData))
|
||||
gr.HandleFunc("/api/set-winsize", AuthKeyWrap(HandleSetWinSize))
|
||||
gr.HandleFunc("/api/power-monitor", AuthKeyWrap(HandlePowerMonitor))
|
||||
gr.HandleFunc("/api/log-active-state", AuthKeyWrap(HandleLogActiveState))
|
||||
gr.HandleFunc("/api/read-file", AuthKeyWrapAllowHmac(HandleReadFile))
|
||||
gr.HandleFunc("/api/write-file", AuthKeyWrap(HandleWriteFile)).Methods("POST")
|
||||
|
@ -642,10 +642,17 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.Up
|
||||
}
|
||||
runPacket.Command = strings.TrimSpace(cmdStr)
|
||||
runPacket.ReturnState = resolveBool(pk.Kwargs["rtnstate"], isRtnStateCmd)
|
||||
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot retrieve client data: %v", err)
|
||||
}
|
||||
feOpts := clientData.FeOpts
|
||||
|
||||
if sudoArg, ok := pk.Kwargs[KwArgSudo]; ok {
|
||||
runPacket.IsSudo = resolveBool(sudoArg, false)
|
||||
runPacket.IsSudo = resolveBool(sudoArg, false) && feOpts.SudoPwStore != "off"
|
||||
} else {
|
||||
runPacket.IsSudo = IsSudoCommand(cmdStr)
|
||||
runPacket.IsSudo = IsSudoCommand(cmdStr) && feOpts.SudoPwStore != "off"
|
||||
}
|
||||
rcOpts := remote.RunCommandOpts{
|
||||
SessionId: ids.SessionId,
|
||||
@ -5810,6 +5817,13 @@ func CheckOptionAlias(kwargs map[string]string, aliases ...string) (string, bool
|
||||
return "", false
|
||||
}
|
||||
|
||||
func validateSudoPwStore(config string) error {
|
||||
if utilfn.ContainsStr([]string{"on", "off", "notimeout"}, config) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s is not a config option", config)
|
||||
}
|
||||
|
||||
func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
@ -5996,6 +6010,58 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
}
|
||||
varsUpdated = append(varsUpdated, "webgl")
|
||||
}
|
||||
if sudoPwStoreStr, found := pk.Kwargs["sudopwstore"]; found {
|
||||
err := validateSudoPwStore(sudoPwStoreStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid sudo pw store, must be \"on\", \"off\", \"notimeout\": %v", err)
|
||||
}
|
||||
feOpts := clientData.FeOpts
|
||||
feOpts.SudoPwStore = strings.ToLower(sudoPwStoreStr)
|
||||
err = sstore.UpdateClientFeOpts(ctx, feOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating client feopts: %v", err)
|
||||
}
|
||||
// clear all sudo pw if turning off
|
||||
if feOpts.SudoPwStore == "off" {
|
||||
for _, proc := range remote.GetRemoteMap() {
|
||||
proc.ClearCachedSudoPw()
|
||||
}
|
||||
}
|
||||
varsUpdated = append(varsUpdated, "sudopwstore")
|
||||
}
|
||||
if sudoPwTimeoutStr, found := pk.Kwargs["sudopwtimeout"]; found {
|
||||
oldPwTimeout := clientData.FeOpts.SudoPwTimeoutMs / 1000 / 60 // ms to minutes
|
||||
if oldPwTimeout == 0 {
|
||||
oldPwTimeout = sstore.DefaultSudoTimeout
|
||||
}
|
||||
newSudoPwTimeout, err := resolveNonNegInt(sudoPwTimeoutStr, sstore.DefaultSudoTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid sudo pw timeout, must be a number greater than 0: %v", err)
|
||||
}
|
||||
if newSudoPwTimeout == 0 {
|
||||
return nil, fmt.Errorf("invalid sudo pw timeout, must be a number greater than 0")
|
||||
}
|
||||
feOpts := clientData.FeOpts
|
||||
feOpts.SudoPwTimeoutMs = newSudoPwTimeout * 60 * 1000 // minutes to ms
|
||||
err = sstore.UpdateClientFeOpts(ctx, feOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating client feopts: %v", err)
|
||||
}
|
||||
for _, proc := range remote.GetRemoteMap() {
|
||||
proc.ChangeSudoTimeout(int64(newSudoPwTimeout - oldPwTimeout))
|
||||
}
|
||||
varsUpdated = append(varsUpdated, "sudopwtimeout")
|
||||
}
|
||||
if sudoPwClearOnSleepStr, found := pk.Kwargs["sudopwclearonsleep"]; found {
|
||||
newSudoPwClearOnSleep := resolveBool(sudoPwClearOnSleepStr, true)
|
||||
feOpts := clientData.FeOpts
|
||||
feOpts.NoSudoPwClearOnSleep = !newSudoPwClearOnSleep
|
||||
err = sstore.UpdateClientFeOpts(ctx, feOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating client feopts: %v", err)
|
||||
}
|
||||
varsUpdated = append(varsUpdated, "sudopwclearonsleep")
|
||||
}
|
||||
if len(varsUpdated) == 0 {
|
||||
return nil, fmt.Errorf("/client:set requires a value to set: %s", formatStrs([]string{"termfontsize", "termfontfamily", "openaiapitoken", "openaimodel", "openaibaseurl", "openaimaxtokens", "openaimaxchoices", "openaitimeout", "webgl"}, "or", false))
|
||||
}
|
||||
|
@ -2626,11 +2626,21 @@ func sendScreenUpdates(screens []*sstore.ScreenType) {
|
||||
}
|
||||
}
|
||||
|
||||
func (msh *MShellProc) startSudoPwClearChecker() {
|
||||
func (msh *MShellProc) startSudoPwClearChecker(clientData *sstore.ClientData) {
|
||||
ctx, cancelFn := context.WithCancel(context.Background())
|
||||
defer cancelFn()
|
||||
sudoPwStore := clientData.FeOpts.SudoPwStore
|
||||
for {
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
log.Printf("*error: cannot obtain client data in sudo pw loop. using fallback: %v", err)
|
||||
} else {
|
||||
sudoPwStore = clientData.FeOpts.SudoPwStore
|
||||
}
|
||||
|
||||
shouldExit := false
|
||||
msh.WithLock(func() {
|
||||
if msh.sudoClearDeadline > 0 && time.Now().Unix() > msh.sudoClearDeadline {
|
||||
if msh.sudoClearDeadline > 0 && time.Now().Unix() > msh.sudoClearDeadline && sudoPwStore != "notimeout" {
|
||||
msh.sudoPw = nil
|
||||
msh.sudoClearDeadline = 0
|
||||
}
|
||||
@ -2668,13 +2678,25 @@ func (msh *MShellProc) sendSudoPassword(sudoPk *packet.SudoRequestPacketType) er
|
||||
}
|
||||
rawSecret = []byte(guiResponse.Text)
|
||||
}
|
||||
//new
|
||||
|
||||
ctx, cancelFn := context.WithCancel(context.Background())
|
||||
defer cancelFn()
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("*error: cannot obtain client data: %v", err)
|
||||
}
|
||||
sudoPwTimeout := clientData.FeOpts.SudoPwTimeoutMs / 1000 / 60
|
||||
if sudoPwTimeout == 0 {
|
||||
// 0 maps to default
|
||||
sudoPwTimeout = sstore.DefaultSudoTimeout
|
||||
}
|
||||
pwTimeoutDur := time.Duration(sudoPwTimeout) * time.Minute
|
||||
msh.WithLock(func() {
|
||||
msh.sudoPw = rawSecret
|
||||
if msh.sudoClearDeadline == 0 {
|
||||
go msh.startSudoPwClearChecker()
|
||||
go msh.startSudoPwClearChecker(clientData)
|
||||
}
|
||||
msh.sudoClearDeadline = time.Now().Add(SudoTimeoutTime).Unix()
|
||||
msh.sudoClearDeadline = time.Now().Add(pwTimeoutDur).Unix()
|
||||
})
|
||||
|
||||
srvPrivKey, err := ecdh.P256().GenerateKey(rand.Reader)
|
||||
@ -2767,6 +2789,15 @@ func (msh *MShellProc) ClearCachedSudoPw() {
|
||||
})
|
||||
}
|
||||
|
||||
func (msh *MShellProc) ChangeSudoTimeout(deltaTime int64) {
|
||||
msh.WithLock(func() {
|
||||
if msh.sudoClearDeadline != 0 {
|
||||
updated := msh.sudoClearDeadline + deltaTime*60
|
||||
msh.sudoClearDeadline = max(0, updated)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (msh *MShellProc) ProcessPackets() {
|
||||
defer msh.WithLock(func() {
|
||||
if msh.Status == StatusConnected {
|
||||
|
@ -43,6 +43,7 @@ const DBWALFileNameBackup = "backup.waveterm.db-wal"
|
||||
const MaxWebShareLineCount = 50
|
||||
const MaxWebShareScreenCount = 3
|
||||
const MaxLineStateSize = 4 * 1024 // 4k for now, can raise if needed
|
||||
const DefaultSudoTimeout = 5
|
||||
|
||||
const DefaultSessionName = "default"
|
||||
const LocalRemoteAlias = "local"
|
||||
@ -232,6 +233,10 @@ type ClientWinSizeType struct {
|
||||
FullScreen bool `json:"fullscreen,omitempty"`
|
||||
}
|
||||
|
||||
type PowerMonitorEventType struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type SidebarValueType struct {
|
||||
Collapsed bool `json:"collapsed"`
|
||||
Width int `json:"width"`
|
||||
@ -254,6 +259,10 @@ type FeOptsType struct {
|
||||
TermFontFamily string `json:"termfontfamily,omitempty"`
|
||||
Theme string `json:"theme,omitempty"`
|
||||
TermThemeSettings map[string]string `json:"termthemesettings"`
|
||||
SudoPwStore string `json:"sudopwstore,omitempty"`
|
||||
SudoPwTimeoutMs int `json:"sudopwtimeoutms,omitempty"`
|
||||
SudoPwTimeout int `json:"sudopwtimeout,omitempty"`
|
||||
NoSudoPwClearOnSleep bool `json:"nosudopwclearonsleep,omitempty"`
|
||||
}
|
||||
|
||||
type ReleaseInfoType struct {
|
||||
|
Loading…
Reference in New Issue
Block a user