• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-server / 10423

11 Nov 2025 04:53PM UTC coverage: 74.435% (-0.1%) from 74.562%
10423

push

gitlab-ci

web-flow
Merge pull request #1071 from mendersoftware/dependabot/npm_and_yarn/frontend/main/development-dependencies-92732187be

3868 of 5393 branches covered (71.72%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

176 existing lines in 95 files now uncovered.

64605 of 86597 relevant lines covered (74.6%)

7.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.51
/frontend/src/js/common-ui/forms/PasswordInput.tsx
1
// Copyright 2016 Northern.tech AS
2✔
2
//
2✔
3
//    Licensed under the Apache License, Version 2.0 (the "License");
2✔
4
//    you may not use this file except in compliance with the License.
2✔
5
//    You may obtain a copy of the License at
2✔
6
//
2✔
7
//        http://www.apache.org/licenses/LICENSE-2.0
2✔
8
//
2✔
9
//    Unless required by applicable law or agreed to in writing, software
2✔
10
//    distributed under the License is distributed on an "AS IS" BASIS,
2✔
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2✔
12
//    See the License for the specific language governing permissions and
2✔
13
//    limitations under the License.
2✔
14
import React, { useEffect, useRef, useState } from 'react';
2✔
15
import { Controller, useFormContext, useWatch } from 'react-hook-form';
2✔
16

2✔
17
import { CheckCircle as CheckIcon, Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon } from '@mui/icons-material';
2✔
18
import { Button, FormControl, FormHelperText, IconButton, InputAdornment, InputLabel, OutlinedInput } from '@mui/material';
2✔
19

2✔
20
import { TIMEOUTS } from '@northern.tech/store/constants';
2✔
21
import { toggle } from '@northern.tech/utils/helpers';
2✔
22
import copy from 'copy-to-clipboard';
2✔
23
import generator from 'generate-password-browser';
2✔
24

2✔
25
import { runValidations } from './Form';
2✔
26

2✔
27
const PasswordGenerateButtons = ({ clearPass, edit, generatePass, disabled }) => (
17✔
28
  <div className="pass-buttons">
141✔
29
    <Button onClick={generatePass} disabled={disabled}>
2✔
30
      Generate
2✔
31
    </Button>
2✔
32
    {edit ? <Button onClick={clearPass}>Cancel</Button> : null}
2!
33
  </div>
2✔
34
);
2✔
35

2✔
36
const SCORE_THRESHOLD = 3;
17✔
37

2✔
38
const PasswordGenerationControls = ({ score, feedback }) => (
17✔
39
  <>
304✔
40
    <div className="help-text" id="pass-strength">
2✔
41
      Strength: <meter max={4} min={0} value={score} high={3.9} optimum={4} low={2.5} />
2✔
42
      {score > SCORE_THRESHOLD ? <CheckIcon className="fadeIn green" style={{ height: 18, marginTop: -3, marginBottom: -3 }} /> : null}
2✔
43
    </div>
2✔
44
    {!!feedback.length && (
2!
45
      <p className="help-text">
2✔
46
        {feedback.map((message, index) => (
2✔
47
          <React.Fragment key={`feedback-${index}`}>
2✔
48
            <span>{message}</span>
2✔
49
            <br />
2✔
50
          </React.Fragment>
2✔
51
        ))}
2✔
52
      </p>
2✔
53
    )}
2✔
54
  </>
2✔
55
);
2✔
56

2✔
57
export const PasswordInput = ({
17✔
58
  autocomplete,
2✔
59
  className,
2✔
60
  control,
2✔
61
  create,
2✔
62
  defaultValue,
2✔
63
  disabled,
2✔
64
  edit,
2✔
65
  generate,
2✔
66
  id,
2✔
67
  InputLabelProps = {},
2✔
68
  label,
2✔
69
  onClear,
2✔
70
  placeholder,
2✔
71
  required,
2✔
72
  validations = ''
2✔
73
}) => {
2✔
74
  const [score, setScore] = useState(0);
568✔
75
  const [visible, setVisible] = useState(false);
568✔
76
  const [copied, setCopied] = useState(false);
568✔
77
  const [feedback, setFeedback] = useState([]);
568✔
78
  const [confirmationId] = useState(id.includes('current') ? '' : ['password', 'password_confirmation'].find(thing => thing !== id));
924✔
79
  const timer = useRef();
568✔
80
  const {
2✔
81
    clearErrors,
2✔
82
    formState: { errors },
2✔
83
    setError,
2✔
84
    setValue,
2✔
85
    trigger,
2✔
86
    getValues
2✔
87
  } = useFormContext();
568✔
88
  const confirmation = useWatch({ name: confirmationId });
568✔
89
  const errorKey = `${id}`;
568✔
90
  const { message } = errors[errorKey] ?? {};
568✔
91

2✔
92
  useEffect(() => {
568✔
93
    if (confirmationId === 'password' && !message) {
45✔
94
      trigger(confirmationId);
9✔
95
    }
2✔
96
  }, [confirmationId, message, trigger]);
2✔
97

2✔
98
  useEffect(() => () => {
568✔
99
    clearTimeout(timer.current);
567✔
100
  });
2✔
101

2✔
102
  const clearPassClick = () => {
568✔
103
    setValue(id, '');
2✔
104
    onClear();
2✔
105
    setCopied(false);
2✔
106
  };
2✔
107

2✔
108
  const generatePassClick = () => {
568✔
109
    const password = generator.generate({ length: 16, numbers: true });
5✔
110
    setValue(id, password);
5✔
111
    const form = getValues();
5✔
112
    if (form.hasOwnProperty(`${id}_confirmation`)) {
5✔
113
      setValue(`${id}_confirmation`, password);
3✔
114
    }
2✔
115
    copy(password);
5✔
116
    setCopied(true);
5✔
117
    setVisible(true);
5✔
118
    timer.current = setTimeout(() => setCopied(false), TIMEOUTS.fiveSeconds);
5✔
119
    trigger();
5✔
120
  };
2✔
121

2✔
122
  const validate = async (value = '') => {
568✔
123
    if (disabled) {
155!
124
      return true;
2✔
125
    }
2✔
126
    let { isValid, errortext } = runValidations({ id, required, validations, value });
155✔
127
    if (confirmation && value !== confirmation) {
155✔
128
      isValid = false;
41✔
129
      errortext = 'The passwords you provided do not match, please check again.';
41✔
130
    }
2✔
131
    if (isValid) {
155✔
132
      clearErrors(errorKey);
102✔
133
    } else {
2✔
134
      setError(errorKey, { type: 'validate', message: errortext });
55✔
135
    }
2✔
136
    // always calculate score to always give feedback on a cleared input
2✔
137
    const { default: zxcvbn } = await import(/* webpackChunkName: "zxcvbn" */ 'zxcvbn');
155✔
138
    const strength = zxcvbn(value);
155✔
139
    const score = strength.score;
155✔
140
    setScore(score);
155✔
141
    if (!create || (!required && !value)) {
155✔
142
      return isValid || errortext;
58✔
143
    }
2✔
144
    setFeedback(strength.feedback.suggestions || []);
99!
145
    return (score > SCORE_THRESHOLD && isValid) || errortext;
155✔
146
  };
2✔
147

2✔
148
  const showAsNotched = label && typeof label !== 'string' ? { notched: true } : {};
568✔
149
  return (
568✔
150
    <div className={className}>
2✔
151
      <div className="password-wrapper">
2✔
152
        <Controller
2✔
153
          name={id}
2✔
154
          control={control}
2✔
155
          rules={{ required, validate }}
2✔
156
          render={({ field: { value, onChange, onBlur, ref }, fieldState: { error } }) => (
2✔
157
            <FormControl className={required ? 'required' : ''} error={Boolean((error || errors[errorKey])?.message)} style={{ width: 400 }}>
568✔
158
              <InputLabel htmlFor={id} {...InputLabelProps}>
2✔
159
                {label}
2✔
160
              </InputLabel>
2✔
161
              <OutlinedInput
2✔
162
                autoComplete={autocomplete}
2✔
163
                id={id}
2✔
164
                label={label}
2✔
165
                name={id}
2✔
166
                type={visible ? 'text' : 'password'}
2✔
167
                defaultValue={defaultValue}
2✔
168
                placeholder={placeholder}
2✔
169
                value={value ?? ''}
2✔
170
                disabled={disabled}
2✔
171
                inputRef={ref}
2✔
172
                required={required}
2✔
173
                onChange={({ target: { value } }) => {
2✔
174
                  setValue(id, value);
164✔
175
                  onChange(value);
164✔
176
                }}
2✔
177
                onBlur={onBlur}
2✔
178
                endAdornment={
2✔
179
                  <InputAdornment position="end">
2✔
UNCOV
180
                    <IconButton onClick={() => setVisible(toggle)} size="large">
2✔
181
                      {visible ? <VisibilityIcon /> : <VisibilityOffIcon />}
2✔
182
                    </IconButton>
2✔
183
                  </InputAdornment>
2✔
184
                }
2✔
185
                {...showAsNotched}
2✔
186
              />
2✔
187
              <FormHelperText>{(errors[errorKey] || error)?.message}</FormHelperText>
2✔
188
            </FormControl>
2✔
189
          )}
2✔
190
        />
2✔
191
        {generate && !required && <PasswordGenerateButtons disabled={disabled} clearPass={clearPassClick} edit={edit} generatePass={generatePassClick} />}
2✔
192
      </div>
2✔
193
      {copied ? <div className="green fadeIn margin-bottom-small">Copied to clipboard</div> : null}
2✔
194
      {create && (
2✔
195
        <>
2✔
196
          <PasswordGenerationControls feedback={feedback} score={score} />
2✔
197
          {generate && required && <PasswordGenerateButtons disabled={disabled} clearPass={clearPassClick} edit={edit} generatePass={generatePassClick} />}
2✔
198
        </>
2✔
199
      )}
2✔
200
    </div>
2✔
201
  );
2✔
202
};
2✔
203

2✔
204
export default PasswordInput;
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc