satellite/console,web/satellite: Add MFA to password reset

Users will be required to enter a MFA passcode or recovery code
upon attempting a password reset for an account with MFA enabled.

Change-Id: I08d07597035d5a25849dbc70f7fd686753530610
This commit is contained in:
Jeremy Wharton 2022-01-11 19:42:38 -06:00 committed by Maximillian von Briesen
parent b2d342aa9b
commit 66e6a75e2a
6 changed files with 360 additions and 64 deletions

View File

@ -688,8 +688,10 @@ func (a *Auth) ResetPassword(w http.ResponseWriter, r *http.Request) {
defer mon.Task()(&ctx)(&err)
var resetPassword struct {
RecoveryToken string `json:"token"`
NewPassword string `json:"password"`
RecoveryToken string `json:"token"`
NewPassword string `json:"password"`
MFAPasscode string `json:"mfaPasscode"`
MFARecoveryCode string `json:"mfaRecoveryCode"`
}
err = json.NewDecoder(r.Body).Decode(&resetPassword)
@ -697,7 +699,24 @@ func (a *Auth) ResetPassword(w http.ResponseWriter, r *http.Request) {
a.serveJSONError(w, err)
}
err = a.service.ResetPassword(ctx, resetPassword.RecoveryToken, resetPassword.NewPassword, time.Now())
err = a.service.ResetPassword(ctx, resetPassword.RecoveryToken, resetPassword.NewPassword, resetPassword.MFAPasscode, resetPassword.MFARecoveryCode, time.Now())
if console.ErrMFAMissing.Has(err) || console.ErrMFAPasscode.Has(err) || console.ErrMFARecoveryCode.Has(err) {
w.WriteHeader(a.getStatusCode(err))
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(map[string]string{
"error": a.getUserErrorMessage(err),
"code": "mfa_required",
})
if err != nil {
a.log.Error("failed to write json response", zap.Error(ErrUtils.Wrap(err)))
}
return
}
if err != nil {
a.serveJSONError(w, err)
}

View File

@ -526,9 +526,14 @@ func TestMFAEndpoints(t *testing.T) {
func TestResetPasswordEndpoint(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
Reconfigure: testplanet.Reconfigure{
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
config.Console.RateLimit.Burst = 10
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
newPass := "123a123"
sat := planet.Satellites[0]
service := sat.API.Console.Service
user, err := sat.AddUser(ctx, console.CreateUser{
FullName: "Test User",
@ -536,17 +541,23 @@ func TestResetPasswordEndpoint(t *testing.T) {
}, 1)
require.NoError(t, err)
token, err := sat.DB.Console().ResetPasswordTokens().Create(ctx, user.ID)
require.NoError(t, err)
require.NotNil(t, token)
tokenStr := token.Secret.String()
newPass := user.FullName
tryReset := func(token, password string) int {
getNewResetToken := func() *console.ResetPasswordToken {
token, err := sat.DB.Console().ResetPasswordTokens().Create(ctx, user.ID)
require.NoError(t, err)
require.NotNil(t, token)
return token
}
tryPasswordReset := func(tokenStr, password, mfaPasscode, mfaRecoveryCode string) (int, bool) {
url := sat.ConsoleURL() + "/api/v0/auth/reset-password"
bodyBytes, err := json.Marshal(map[string]string{
"password": password,
"token": token,
"password": password,
"token": tokenStr,
"mfaPasscode": mfaPasscode,
"mfaRecoveryCode": mfaRecoveryCode,
})
require.NoError(t, err)
@ -557,13 +568,61 @@ func TestResetPasswordEndpoint(t *testing.T) {
result, err := http.DefaultClient.Do(req)
require.NoError(t, err)
var response struct {
Code string `json:"code"`
}
if result.ContentLength > 0 {
err = json.NewDecoder(result.Body).Decode(&response)
require.NoError(t, err)
}
require.NoError(t, result.Body.Close())
return result.StatusCode
return result.StatusCode, response.Code == "mfa_required"
}
require.Equal(t, http.StatusUnauthorized, tryReset("badToken", newPass))
require.Equal(t, http.StatusBadRequest, tryReset(tokenStr, "bad"))
require.Equal(t, http.StatusOK, tryReset(tokenStr, newPass))
token := getNewResetToken()
status, mfaError := tryPasswordReset("badToken", newPass, "", "")
require.Equal(t, http.StatusUnauthorized, status)
require.False(t, mfaError)
status, mfaError = tryPasswordReset(token.Secret.String(), "bad", "", "")
require.Equal(t, http.StatusBadRequest, status)
require.False(t, mfaError)
status, mfaError = tryPasswordReset(token.Secret.String(), newPass, "", "")
require.Equal(t, http.StatusOK, status)
require.False(t, mfaError)
token = getNewResetToken()
// Enable MFA.
getNewAuthContext := func() context.Context {
authCtx, err := sat.AuthenticatedContext(ctx, user.ID)
require.NoError(t, err)
return authCtx
}
authCtx := getNewAuthContext()
key, err := service.ResetMFASecretKey(authCtx)
require.NoError(t, err)
authCtx = getNewAuthContext()
passcode, err := console.NewMFAPasscode(key, token.CreatedAt)
require.NoError(t, err)
err = service.EnableUserMFA(authCtx, passcode, token.CreatedAt)
require.NoError(t, err)
status, mfaError = tryPasswordReset(token.Secret.String(), newPass, "", "")
require.Equal(t, http.StatusBadRequest, status)
require.True(t, mfaError)
status, mfaError = tryPasswordReset(token.Secret.String(), newPass, "", "")
require.Equal(t, http.StatusBadRequest, status)
require.True(t, mfaError)
})
}

View File

@ -775,7 +775,7 @@ func (s *Service) ActivateAccount(ctx context.Context, activationToken string) (
}
// ResetPassword - is a method for resetting user password.
func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, password string, t time.Time) (err error) {
func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, password string, passcode string, recoveryCode string, t time.Time) (err error) {
defer mon.Task()(&ctx)(&err)
secret, err := ResetPasswordSecretFromBase64(resetPasswordToken)
@ -792,6 +792,31 @@ func (s *Service) ResetPassword(ctx context.Context, resetPasswordToken, passwor
return Error.Wrap(err)
}
if user.MFAEnabled {
if recoveryCode != "" {
found := false
for _, code := range user.MFARecoveryCodes {
if code == recoveryCode {
found = true
break
}
}
if !found {
return ErrUnauthorized.Wrap(ErrMFARecoveryCode.New(mfaRecoveryInvalidErrMsg))
}
} else if passcode != "" {
valid, err := ValidateMFAPasscode(passcode, user.MFASecretKey, t)
if err != nil {
return ErrValidation.Wrap(ErrMFAPasscode.Wrap(err))
}
if !valid {
return ErrValidation.Wrap(ErrMFAPasscode.New(mfaPasscodeInvalidErrMsg))
}
} else {
return ErrMFAMissing.New(mfaRequiredErrMsg)
}
}
if err := ValidatePassword(password); err != nil {
return ErrValidation.Wrap(err)
}

View File

@ -384,22 +384,21 @@ func TestMFA(t *testing.T) {
}, 1)
require.NoError(t, err)
var auth console.Authorization
var authCtx context.Context
updateAuth := func() {
authCtx, err = sat.AuthenticatedContext(ctx, user.ID)
getNewAuthorization := func() (context.Context, console.Authorization) {
authCtx, err := sat.AuthenticatedContext(ctx, user.ID)
require.NoError(t, err)
auth, err = console.GetAuth(authCtx)
auth, err := console.GetAuth(authCtx)
require.NoError(t, err)
return authCtx, auth
}
updateAuth()
authCtx, auth := getNewAuthorization()
var key string
t.Run("TestResetMFASecretKey", func(t *testing.T) {
key, err = service.ResetMFASecretKey(authCtx)
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
require.NotEmpty(t, auth.User.MFASecretKey)
})
@ -411,11 +410,11 @@ func TestMFA(t *testing.T) {
err = service.EnableUserMFA(authCtx, badCode, time.Time{})
require.True(t, console.ErrValidation.Has(err))
updateAuth()
authCtx, auth = getNewAuthorization()
_, err = service.ResetMFARecoveryCodes(authCtx)
require.True(t, console.ErrUnauthorized.Has(err))
updateAuth()
authCtx, auth = getNewAuthorization()
require.False(t, auth.User.MFAEnabled)
})
@ -424,11 +423,11 @@ func TestMFA(t *testing.T) {
goodCode, err := console.NewMFAPasscode(key, time.Time{})
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
err = service.EnableUserMFA(authCtx, goodCode, time.Time{})
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
require.True(t, auth.User.MFAEnabled)
require.Equal(t, auth.User.MFASecretKey, key)
})
@ -464,7 +463,7 @@ func TestMFA(t *testing.T) {
_, err = service.ResetMFARecoveryCodes(authCtx)
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
require.Len(t, auth.User.MFARecoveryCodes, console.MFARecoveryCodeCount)
for _, code := range auth.User.MFARecoveryCodes {
@ -482,7 +481,7 @@ func TestMFA(t *testing.T) {
require.True(t, console.ErrMFARecoveryCode.Has(err))
require.Empty(t, token)
updateAuth()
authCtx, auth = getNewAuthorization()
}
_, err = service.ResetMFARecoveryCodes(authCtx)
@ -494,11 +493,11 @@ func TestMFA(t *testing.T) {
badCode, err := console.NewMFAPasscode(key, time.Time{}.Add(time.Hour))
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
err = service.DisableUserMFA(authCtx, badCode, time.Time{}, "")
require.True(t, console.ErrValidation.Has(err))
updateAuth()
authCtx, auth = getNewAuthorization()
require.True(t, auth.User.MFAEnabled)
require.NotEmpty(t, auth.User.MFASecretKey)
require.NotEmpty(t, auth.User.MFARecoveryCodes)
@ -509,11 +508,11 @@ func TestMFA(t *testing.T) {
goodCode, err := console.NewMFAPasscode(key, time.Time{})
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
err = service.DisableUserMFA(authCtx, goodCode, time.Time{}, auth.User.MFARecoveryCodes[0])
require.True(t, console.ErrMFAConflict.Has(err))
updateAuth()
authCtx, auth = getNewAuthorization()
require.True(t, auth.User.MFAEnabled)
require.NotEmpty(t, auth.User.MFASecretKey)
require.NotEmpty(t, auth.User.MFARecoveryCodes)
@ -524,11 +523,11 @@ func TestMFA(t *testing.T) {
goodCode, err := console.NewMFAPasscode(key, time.Time{})
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
err = service.DisableUserMFA(authCtx, goodCode, time.Time{}, "")
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
require.False(t, auth.User.MFAEnabled)
require.Empty(t, auth.User.MFASecretKey)
require.Empty(t, auth.User.MFARecoveryCodes)
@ -543,15 +542,15 @@ func TestMFA(t *testing.T) {
goodCode, err := console.NewMFAPasscode(key, time.Time{})
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
err = service.EnableUserMFA(authCtx, goodCode, time.Time{})
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
_, err = service.ResetMFARecoveryCodes(authCtx)
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
require.True(t, auth.User.MFAEnabled)
require.NotEmpty(t, auth.User.MFASecretKey)
require.NotEmpty(t, auth.User.MFARecoveryCodes)
@ -560,7 +559,7 @@ func TestMFA(t *testing.T) {
err = service.DisableUserMFA(authCtx, "", time.Time{}, auth.User.MFARecoveryCodes[0])
require.NoError(t, err)
updateAuth()
authCtx, auth = getNewAuthorization()
require.False(t, auth.User.MFAEnabled)
require.Empty(t, auth.User.MFASecretKey)
require.Empty(t, auth.User.MFARecoveryCodes)
@ -572,7 +571,6 @@ func TestResetPassword(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
newPass := "123a123"
sat := planet.Satellites[0]
service := sat.API.Console.Service
@ -582,25 +580,73 @@ func TestResetPassword(t *testing.T) {
}, 1)
require.NoError(t, err)
token, err := sat.DB.Console().ResetPasswordTokens().Create(ctx, user.ID)
require.NoError(t, err)
require.NotNil(t, token)
tokenStr := token.Secret.String()
newPass := user.FullName
getNewResetToken := func() *console.ResetPasswordToken {
token, err := sat.DB.Console().ResetPasswordTokens().Create(ctx, user.ID)
require.NoError(t, err)
require.NotNil(t, token)
return token
}
token := getNewResetToken()
// Expect error when providing bad token.
err = service.ResetPassword(ctx, "badToken", newPass, token.CreatedAt)
err = service.ResetPassword(ctx, "badToken", newPass, "", "", token.CreatedAt)
require.True(t, console.ErrRecoveryToken.Has(err))
// Expect error when providing good but expired token.
err = service.ResetPassword(ctx, tokenStr, newPass, token.CreatedAt.Add(sat.Config.Console.TokenExpirationTime).Add(time.Second))
err = service.ResetPassword(ctx, token.Secret.String(), newPass, "", "", token.CreatedAt.Add(sat.Config.Console.TokenExpirationTime).Add(time.Second))
require.True(t, console.ErrTokenExpiration.Has(err))
// Expect error when providing good token with bad (too short) password.
err = service.ResetPassword(ctx, tokenStr, "bad", token.CreatedAt)
err = service.ResetPassword(ctx, token.Secret.String(), "bad", "", "", token.CreatedAt)
require.True(t, console.ErrValidation.Has(err))
// Expect success when providing good token and good password.
err = service.ResetPassword(ctx, tokenStr, newPass, token.CreatedAt)
err = service.ResetPassword(ctx, token.Secret.String(), newPass, "", "", token.CreatedAt)
require.NoError(t, err)
token = getNewResetToken()
// Enable MFA.
getNewAuthorization := func() (context.Context, console.Authorization) {
authCtx, err := sat.AuthenticatedContext(ctx, user.ID)
require.NoError(t, err)
auth, err := console.GetAuth(authCtx)
require.NoError(t, err)
return authCtx, auth
}
authCtx, _ := getNewAuthorization()
key, err := service.ResetMFASecretKey(authCtx)
require.NoError(t, err)
authCtx, auth := getNewAuthorization()
passcode, err := console.NewMFAPasscode(key, token.CreatedAt)
require.NoError(t, err)
err = service.EnableUserMFA(authCtx, passcode, token.CreatedAt)
require.NoError(t, err)
// Expect error when providing bad passcode.
badPasscode, err := console.NewMFAPasscode(key, token.CreatedAt.Add(time.Hour))
require.NoError(t, err)
err = service.ResetPassword(ctx, token.Secret.String(), newPass, badPasscode, "", token.CreatedAt)
require.True(t, console.ErrMFAPasscode.Has(err))
for _, recoveryCode := range auth.User.MFARecoveryCodes {
// Expect success when providing bad passcode and good recovery code.
err = service.ResetPassword(ctx, token.Secret.String(), newPass, badPasscode, recoveryCode, token.CreatedAt)
require.NoError(t, err)
token = getNewResetToken()
// Expect error when providing bad passcode and already-used recovery code.
err = service.ResetPassword(ctx, token.Secret.String(), newPass, badPasscode, recoveryCode, token.CreatedAt)
require.True(t, console.ErrMFARecoveryCode.Has(err))
}
// Expect success when providing good passcode.
err = service.ResetPassword(ctx, token.Secret.String(), newPass, passcode, "", token.CreatedAt)
require.NoError(t, err)
})
}

View File

@ -51,8 +51,8 @@ export class AuthHttpApi implements UsersApi {
const body = {
email,
password,
mfaPasscode: mfaPasscode ? mfaPasscode : null,
mfaRecoveryCode: mfaRecoveryCode ? mfaRecoveryCode : null,
mfaPasscode: mfaPasscode || null,
mfaRecoveryCode: mfaRecoveryCode || null,
};
const response = await this.http.post(path, JSON.stringify(body));
@ -374,24 +374,40 @@ export class AuthHttpApi implements UsersApi {
/**
* Used to reset user's password.
*
* @param token - user's password reset token
* @param password - user's new password
* @param mfaPasscode - MFA passcode
* @param mfaRecoveryCode - MFA recovery code
* @throws Error
*/
public async resetPassword(token: string, password: string): Promise<void> {
public async resetPassword(token: string, password: string, mfaPasscode: string, mfaRecoveryCode: string): Promise<void> {
const path = `${this.ROOT_PATH}/reset-password`;
const body = {
token: token,
password: password,
mfaPasscode: mfaPasscode || null,
mfaRecoveryCode: mfaRecoveryCode || null,
};
const response = await this.http.post(path, JSON.stringify(body));
const text = await response.text();
let errMsg = 'Cannot reset password';
if (text) {
const result = JSON.parse(text);
if (result.code == "mfa_required") {
throw new ErrorMFARequired();
}
if (result.error) {
errMsg = result.error;
}
}
if (response.ok) {
return;
}
const result = await response.json();
const errMsg = result.error || 'Cannot reset password';
switch (response.status) {
case 400:
throw new ErrorBadRequest(errMsg);

View File

@ -8,8 +8,34 @@
</div>
<div class="reset-area__content-area">
<div class="reset-area__content-area__container" :class="{'success': isSuccessfulPasswordResetShown}">
<template v-if="!isSuccessfulPasswordResetShown">
<h1 class="reset-area__content-area__container__title">Reset Password</h1>
<h1 v-if="!isSuccessfulPasswordResetShown" class="reset-area__content-area__container__title">Reset Password</h1>
<template v-if="isSuccessfulPasswordResetShown">
<KeyIcon />
<h2 class="reset-area__content-area__container__title success">Success!</h2>
<p class="reset-area__content-area__container__sub-title">
You have successfully changed your password.
</p>
</template>
<template v-else-if="isMFARequired">
<div class="info-box">
<div class="info-box__header">
<GreyWarningIcon />
<h2 class="info-box__header__label">
Two-Factor Authentication Required
</h2>
</div>
<p class="info-box__message">
You'll need the six-digit code from your authenticator app to continue.
</p>
</div>
<div class="reset-area__content-area__container__input-wrapper">
<ConfirmMFAInput ref="mfaInput" :on-input="onConfirmInput" :is-error="isMFAError" :is-recovery="isRecoveryCodeState" />
</div>
<span v-if="!isRecoveryCodeState" class="reset-area__content-area__container__recovery" @click="setRecoveryCodeState">
Or use recovery code
</span>
</template>
<template v-else>
<p class="reset-area__content-area__container__message">Please enter your new password.</p>
<div class="reset-area__content-area__container__input-wrapper password">
<HeaderlessInput
@ -35,15 +61,11 @@
@setData="setRepeatedPassword"
/>
</div>
<p class="reset-area__content-area__container__button" @click.prevent="onResetClick">Reset Password</p>
</template>
<template v-else>
<KeyIcon />
<h2 class="reset-area__content-area__container__title success">Success!</h2>
<p class="reset-area__content-area__container__sub-title">
You have successfully changed your password.
</p>
</template>
<p v-if="!isSuccessfulPasswordResetShown" class="reset-area__content-area__container__button" @click.prevent="onResetClick">Reset Password</p>
<span v-if="isMFARequired && !isSuccessfulPasswordResetShown" class="reset-area__content-area__container__cancel" :class="{ disabled: isLoading }" @click.prevent="onMFACancelClick">
Cancel
</span>
</div>
<router-link :to="loginPath" class="reset-area__content-area__login-link">
Back to Login
@ -55,13 +77,16 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ConfirmMFAInput from '@/components/account/mfa/ConfirmMFAInput.vue';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import PasswordStrength from '@/components/common/PasswordStrength.vue';
import GreyWarningIcon from '@/../static/images/common/greyWarning.svg';
import LogoIcon from '@/../static/images/logo.svg';
import KeyIcon from '@/../static/images/resetPassword/success.svg';
import { AuthHttpApi } from '@/api/auth';
import { ErrorMFARequired } from '@/api/errors/ErrorMFARequired';
import { RouteConfig } from '@/router';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { Validator } from '@/utils/validation';
@ -73,6 +98,8 @@ import { Validator } from '@/utils/validation';
HeaderlessInput,
PasswordStrength,
KeyIcon,
ConfirmMFAInput,
GreyWarningIcon,
},
})
@ -80,10 +107,15 @@ export default class ResetPassword extends Vue {
private token = '';
private password = '';
private repeatedPassword = '';
private passcode = '';
private recoveryCode = '';
private passwordError = '';
private repeatedPasswordError = '';
private isLoading = false;
private isMFARequired = false;
private isMFAError = false;
private isRecoveryCodeState = false;
private readonly auth: AuthHttpApi = new AuthHttpApi();
@ -91,6 +123,10 @@ export default class ResetPassword extends Vue {
public readonly loginPath: string = RouteConfig.Login.path;
public $refs!: {
mfaInput: ConfirmMFAInput;
};
/**
* Lifecycle hook on component destroy.
* Sets view to default state.
@ -138,10 +174,23 @@ export default class ResetPassword extends Vue {
}
try {
await this.auth.resetPassword(this.token, this.password);
await this.auth.resetPassword(this.token, this.password, this.passcode.trim(), this.recoveryCode.trim());
await this.auth.logout();
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PASSWORD_RESET);
} catch (error) {
this.isLoading = false;
if (error instanceof ErrorMFARequired) {
if (this.isMFARequired) this.isMFAError = true;
this.isMFARequired = true;
return;
}
if (this.isMFARequired) {
this.isMFAError = true;
return;
}
await this.$notify.error(error.message);
}
@ -203,6 +252,36 @@ export default class ResetPassword extends Vue {
this.repeatedPassword = value.trim();
this.repeatedPasswordError = '';
}
/**
* Sets page to recovery code state.
*/
public setRecoveryCodeState(): void {
this.isMFAError = false;
this.passcode = '';
this.$refs.mfaInput.clearInput();
this.isRecoveryCodeState = true;
}
/**
* Cancels MFA passcode input state.
*/
public onMFACancelClick(): void {
this.isMFARequired = false;
this.isRecoveryCodeState = false;
this.isMFAError = false;
this.passcode = '';
this.recoveryCode = '';
}
/**
* Sets confirmation passcode value from input.
*/
public onConfirmInput(value: string): void {
this.isMFAError = false;
this.isRecoveryCodeState ? this.recoveryCode = value : this.passcode = value;
}
}
</script>
@ -294,6 +373,26 @@ export default class ResetPassword extends Vue {
background-color: #0059d0;
}
}
&__cancel {
align-self: center;
font-size: 16px;
line-height: 21px;
color: #0068dc;
text-align: center;
margin-top: 30px;
cursor: pointer;
}
&__recovery {
font-size: 16px;
line-height: 19px;
color: #0068dc;
cursor: pointer;
margin-top: 20px;
text-align: center;
width: 100%;
}
}
&__login-link {
@ -307,6 +406,38 @@ export default class ResetPassword extends Vue {
}
}
.info-box {
background-color: #f7f8fb;
border-radius: 6px;
padding: 20px;
margin-top: 25px;
width: 100%;
box-sizing: border-box;
&.error {
background-color: #fff9f7;
border: 1px solid #f84b00;
}
&__header {
display: flex;
align-items: center;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
color: #1b2533;
margin-left: 15px;
}
}
&__message {
font-size: 16px;
color: #1b2533;
margin-top: 10px;
}
}
@media screen and (max-width: 750px) {
.reset-area {