refactor(handlers): ppolicy (#3103)
Add tests and makes the password policy a provider so the configuration can be loaded to memory on startup.pull/2828/head^2
parent
0f6ca55016
commit
9e05066097
|
@ -44,6 +44,20 @@ paths:
|
|||
description: Forbidden
|
||||
security:
|
||||
- authelia_auth: []
|
||||
/api/configuration/password-policy:
|
||||
get:
|
||||
tags:
|
||||
- State
|
||||
summary: Password Policy Configuration
|
||||
description: >
|
||||
The password policy configuration endpoint provides a password policy for resetting passwords.
|
||||
responses:
|
||||
"200":
|
||||
description: Successful Operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/handlers.configuration.PasswordPolicyConfigurationBody'
|
||||
/api/health:
|
||||
get:
|
||||
tags:
|
||||
|
@ -694,6 +708,43 @@ components:
|
|||
- "webauthn"
|
||||
- "mobile_push"
|
||||
example: [totp, webauthn, mobile_push]
|
||||
handlers.configuration.PasswordPolicyConfigurationBody:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: OK
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
description: The password policy mode.
|
||||
enum:
|
||||
- "disabled"
|
||||
- "standard"
|
||||
- "zxcvbn"
|
||||
min_length:
|
||||
type: integer
|
||||
description: The minimum password length when using the standard mode.
|
||||
max_length:
|
||||
type: integer
|
||||
description: The maximum password length when using the standard mode.
|
||||
min_score:
|
||||
type: integer
|
||||
description: The minimum password score when using the zxcvbn mode.
|
||||
require_uppercase:
|
||||
type: boolean
|
||||
description: If uppercase characters are required when using the standard mode.
|
||||
require_lowercase:
|
||||
type: boolean
|
||||
description: If uppercase characters are required when using the standard mode.
|
||||
require_number:
|
||||
type: boolean
|
||||
description: If numeric characters are required when using the standard mode.
|
||||
require_special:
|
||||
type: boolean
|
||||
description: If special characters are required when using the standard mode.
|
||||
handlers.DuoDeviceBody:
|
||||
required:
|
||||
- device
|
||||
|
|
|
@ -329,16 +329,29 @@ password_policy:
|
|||
## The standard policy allows you to tune individual settings manually.
|
||||
standard:
|
||||
enabled: false
|
||||
|
||||
## Require a minimum length for passwords.
|
||||
min_length: 8
|
||||
|
||||
## Require a maximum length for passwords.
|
||||
max_length: 0
|
||||
|
||||
## Require uppercase characters.
|
||||
require_uppercase: true
|
||||
|
||||
## Require lowercase characters.
|
||||
require_lowercase: true
|
||||
|
||||
## Require numeric characters.
|
||||
require_number: true
|
||||
|
||||
## Require special characters.
|
||||
require_special: true
|
||||
|
||||
## zxcvbn is a well known and used password strength algorithm. It does not have tunable settings.
|
||||
zxcvbn:
|
||||
enabled: false
|
||||
min_score: 0
|
||||
|
||||
##
|
||||
## Access Control Configuration
|
||||
|
|
|
@ -17,10 +17,10 @@ password_policy:
|
|||
enabled: false
|
||||
min_length: 8
|
||||
max_length: 0
|
||||
require_uppercase: true
|
||||
require_lowercase: true
|
||||
require_number: true
|
||||
require_special: true
|
||||
require_uppercase: false
|
||||
require_lowercase: false
|
||||
require_number: false
|
||||
require_special: false
|
||||
zxcvbn:
|
||||
enabled: false
|
||||
```
|
||||
|
@ -30,7 +30,7 @@ password_policy:
|
|||
### standard
|
||||
<div markdown="1">
|
||||
type: list
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
@ -40,7 +40,7 @@ This section allows you to enable standard security policies.
|
|||
#### enabled
|
||||
<div markdown="1">
|
||||
type: bool
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
@ -50,7 +50,9 @@ Enables standard password policy.
|
|||
#### min_length
|
||||
<div markdown="1">
|
||||
type: integer
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
default: 8
|
||||
{: .label .label-config .label-blue }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
@ -60,7 +62,9 @@ Determines the minimum allowed password length.
|
|||
#### max_length
|
||||
<div markdown="1">
|
||||
type: integer
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
default: 0
|
||||
{: .label .label-config .label-blue }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
@ -70,7 +74,7 @@ Determines the maximum allowed password length.
|
|||
#### require_uppercase
|
||||
<div markdown="1">
|
||||
type: bool
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
@ -80,7 +84,7 @@ Indicates that at least one UPPERCASE letter must be provided as part of the pas
|
|||
#### require_lowercase
|
||||
<div markdown="1">
|
||||
type: bool
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
@ -90,7 +94,7 @@ Indicates that at least one lowercase letter must be provided as part of the pas
|
|||
#### require_number
|
||||
<div markdown="1">
|
||||
type: bool
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
@ -100,7 +104,7 @@ Indicates that at least one number must be provided as part of the password.
|
|||
#### require_special
|
||||
<div markdown="1">
|
||||
type: bool
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
@ -117,7 +121,7 @@ password is.
|
|||
#### enabled
|
||||
<div markdown="1">
|
||||
type: bool
|
||||
{: .label .label-config .label-purple }
|
||||
{: .label .label-config .label-purple }
|
||||
required: no
|
||||
{: .label .label-config .label-green }
|
||||
</div>
|
||||
|
|
|
@ -329,16 +329,29 @@ password_policy:
|
|||
## The standard policy allows you to tune individual settings manually.
|
||||
standard:
|
||||
enabled: false
|
||||
|
||||
## Require a minimum length for passwords.
|
||||
min_length: 8
|
||||
|
||||
## Require a maximum length for passwords.
|
||||
max_length: 0
|
||||
|
||||
## Require uppercase characters.
|
||||
require_uppercase: true
|
||||
|
||||
## Require lowercase characters.
|
||||
require_lowercase: true
|
||||
|
||||
## Require numeric characters.
|
||||
require_number: true
|
||||
|
||||
## Require special characters.
|
||||
require_special: true
|
||||
|
||||
## zxcvbn is a well known and used password strength algorithm. It does not have tunable settings.
|
||||
zxcvbn:
|
||||
enabled: false
|
||||
min_score: 0
|
||||
|
||||
##
|
||||
## Access Control Configuration
|
||||
|
|
|
@ -2,7 +2,7 @@ package schema
|
|||
|
||||
// PasswordPolicyStandardParams represents the configuration related to standard parameters of password policy.
|
||||
type PasswordPolicyStandardParams struct {
|
||||
Enabled bool
|
||||
Enabled bool `koanf:"enabled"`
|
||||
MinLength int `koanf:"min_length"`
|
||||
MaxLength int `koanf:"max_length"`
|
||||
RequireUppercase bool `koanf:"require_uppercase"`
|
||||
|
@ -13,8 +13,8 @@ type PasswordPolicyStandardParams struct {
|
|||
|
||||
// PasswordPolicyZxcvbnParams represents the configuration related to zxcvbn parameters of password policy.
|
||||
type PasswordPolicyZxcvbnParams struct {
|
||||
Enabled bool
|
||||
MinScore int `koanf:"min_score"`
|
||||
Enabled bool `koanf:"enabled"`
|
||||
MinScore int `koanf:"min_score"`
|
||||
}
|
||||
|
||||
// PasswordPolicyConfiguration represents the configuration related to password policy.
|
||||
|
@ -26,15 +26,12 @@ type PasswordPolicyConfiguration struct {
|
|||
// DefaultPasswordPolicyConfiguration is the default password policy configuration.
|
||||
var DefaultPasswordPolicyConfiguration = PasswordPolicyConfiguration{
|
||||
Standard: PasswordPolicyStandardParams{
|
||||
Enabled: false,
|
||||
MinLength: 8,
|
||||
MaxLength: 0,
|
||||
RequireUppercase: true,
|
||||
RequireLowercase: true,
|
||||
RequireNumber: true,
|
||||
RequireSpecial: true,
|
||||
Enabled: false,
|
||||
MinLength: 8,
|
||||
MaxLength: 0,
|
||||
},
|
||||
Zxcvbn: PasswordPolicyZxcvbnParams{
|
||||
Enabled: false,
|
||||
Enabled: false,
|
||||
MinScore: 0,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
)
|
||||
|
||||
// PasswordPolicyConfigurationGet get the password policy configuration.
|
||||
func PasswordPolicyConfigurationGet(ctx *middlewares.AutheliaCtx) {
|
||||
policyResponse := PassworPolicyBody{
|
||||
Mode: "disabled",
|
||||
}
|
||||
|
||||
if ctx.Configuration.PasswordPolicy.Standard.Enabled {
|
||||
policyResponse.Mode = "standard"
|
||||
policyResponse.MinLength = ctx.Configuration.PasswordPolicy.Standard.MinLength
|
||||
policyResponse.MaxLength = ctx.Configuration.PasswordPolicy.Standard.MaxLength
|
||||
policyResponse.RequireLowercase = ctx.Configuration.PasswordPolicy.Standard.RequireLowercase
|
||||
policyResponse.RequireUppercase = ctx.Configuration.PasswordPolicy.Standard.RequireUppercase
|
||||
policyResponse.RequireNumber = ctx.Configuration.PasswordPolicy.Standard.RequireNumber
|
||||
policyResponse.RequireSpecial = ctx.Configuration.PasswordPolicy.Standard.RequireSpecial
|
||||
} else if ctx.Configuration.PasswordPolicy.Zxcvbn.Enabled {
|
||||
policyResponse.Mode = "zxcvbn"
|
||||
policyResponse.MinScore = ctx.Configuration.PasswordPolicy.Zxcvbn.MinScore
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if err = ctx.SetJSONBody(policyResponse); err != nil {
|
||||
ctx.Logger.Errorf("Unable to send password Policy: %s", err)
|
||||
}
|
||||
}
|
|
@ -53,28 +53,7 @@ func resetPasswordIdentityFinish(ctx *middlewares.AutheliaCtx, username string)
|
|||
ctx.Logger.Errorf("Unable to clear password reset flag in session for user %s: %s", userSession.Username, err)
|
||||
}
|
||||
|
||||
mode := ""
|
||||
if ctx.Configuration.PasswordPolicy.Standard.Enabled {
|
||||
mode = "standard"
|
||||
} else if ctx.Configuration.PasswordPolicy.Zxcvbn.Enabled {
|
||||
mode = "zxcvbn"
|
||||
}
|
||||
|
||||
policyResponse := PassworPolicyBody{
|
||||
Mode: mode,
|
||||
MinLength: ctx.Configuration.PasswordPolicy.Standard.MinLength,
|
||||
MaxLength: ctx.Configuration.PasswordPolicy.Standard.MaxLength,
|
||||
RequireLowercase: ctx.Configuration.PasswordPolicy.Standard.RequireLowercase,
|
||||
RequireUppercase: ctx.Configuration.PasswordPolicy.Standard.RequireUppercase,
|
||||
RequireNumber: ctx.Configuration.PasswordPolicy.Standard.RequireNumber,
|
||||
RequireSpecial: ctx.Configuration.PasswordPolicy.Standard.RequireSpecial,
|
||||
MinScore: ctx.Configuration.PasswordPolicy.Zxcvbn.MinScore,
|
||||
}
|
||||
|
||||
err = ctx.SetJSONBody(policyResponse)
|
||||
if err != nil {
|
||||
ctx.Logger.Errorf("Unable to send password Policy: %s", err)
|
||||
}
|
||||
ctx.ReplyOK()
|
||||
}
|
||||
|
||||
// ResetPasswordIdentityFinish the handler for finishing the identity validation.
|
||||
|
|
|
@ -65,6 +65,8 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
|
|||
r.GET("/api/configuration", autheliaMiddleware(
|
||||
middlewares.RequireFirstFactor(handlers.ConfigurationGet)))
|
||||
|
||||
r.GET("/api/configuration/password-policy", autheliaMiddleware(handlers.PasswordPolicyConfigurationGet))
|
||||
|
||||
r.GET("/api/verify", autheliaMiddleware(handlers.VerifyGet(configuration.AuthenticationBackend)))
|
||||
r.HEAD("/api/verify", autheliaMiddleware(handlers.VerifyGet(configuration.AuthenticationBackend)))
|
||||
|
||||
|
|
|
@ -133,6 +133,7 @@
|
|||
"@types/qrcode.react": "1.0.2",
|
||||
"@types/react": "17.0.43",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"@types/zxcvbn": "4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.17.0",
|
||||
"@typescript-eslint/parser": "5.17.0",
|
||||
"@vitejs/plugin-react": "1.3.0",
|
||||
|
|
|
@ -16,6 +16,7 @@ specifiers:
|
|||
'@types/qrcode.react': 1.0.2
|
||||
'@types/react': 17.0.43
|
||||
'@types/react-dom': 17.0.14
|
||||
'@types/zxcvbn': 4.4.1
|
||||
'@typescript-eslint/eslint-plugin': 5.17.0
|
||||
'@typescript-eslint/parser': 5.17.0
|
||||
'@vitejs/plugin-react': 1.3.0
|
||||
|
@ -91,6 +92,7 @@ devDependencies:
|
|||
'@types/qrcode.react': 1.0.2
|
||||
'@types/react': 17.0.43
|
||||
'@types/react-dom': 17.0.14
|
||||
'@types/zxcvbn': 4.4.1
|
||||
'@typescript-eslint/eslint-plugin': 5.17.0_4ad50a0fa85b91f236c35644695e4e45
|
||||
'@typescript-eslint/parser': 5.17.0_typescript@4.6.3
|
||||
'@vitejs/plugin-react': 1.3.0
|
||||
|
@ -2517,6 +2519,10 @@ packages:
|
|||
'@types/yargs-parser': 21.0.0
|
||||
dev: true
|
||||
|
||||
/@types/zxcvbn/4.4.1:
|
||||
resolution: {integrity: sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w==}
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/eslint-plugin/5.17.0_4ad50a0fa85b91f236c35644695e4e45:
|
||||
resolution: {integrity: sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
|
|
@ -3,11 +3,40 @@ import React from "react";
|
|||
import { render } from "@testing-library/react";
|
||||
|
||||
import PasswordMeter from "@components/PasswordMeter";
|
||||
import { PasswordPolicyMode } from "@models/PasswordPolicy";
|
||||
|
||||
it("renders without crashing", () => {
|
||||
render(<PasswordMeter value={""} minLength={4} />);
|
||||
render(
|
||||
<PasswordMeter
|
||||
value={""}
|
||||
policy={{
|
||||
max_length: 0,
|
||||
min_length: 4,
|
||||
min_score: 0,
|
||||
require_lowercase: false,
|
||||
require_number: false,
|
||||
require_special: false,
|
||||
require_uppercase: false,
|
||||
mode: PasswordPolicyMode.Standard,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it("renders adjusted height without crashing", () => {
|
||||
render(<PasswordMeter value={"Passw0rd!"} minLength={4} />);
|
||||
render(
|
||||
<PasswordMeter
|
||||
value={"Passw0rd!"}
|
||||
policy={{
|
||||
max_length: 0,
|
||||
min_length: 4,
|
||||
min_score: 0,
|
||||
require_lowercase: false,
|
||||
require_number: false,
|
||||
require_special: false,
|
||||
require_uppercase: false,
|
||||
mode: PasswordPolicyMode.Standard,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,24 +1,15 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { makeStyles } from "@material-ui/core";
|
||||
import classnames from "classnames";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import zxcvbn from "zxcvbn";
|
||||
|
||||
import { PasswordPolicyConfiguration, PasswordPolicyMode } from "@models/PasswordPolicy";
|
||||
|
||||
export interface Props {
|
||||
value: string;
|
||||
/**
|
||||
* mode password meter mode
|
||||
* classic: classic mode (checks lowercase, uppercase, specials and numbers)
|
||||
* zxcvbn: uses zxcvbn package to get the password strength
|
||||
**/
|
||||
mode: string;
|
||||
minLength: number;
|
||||
maxLength: number;
|
||||
requireLowerCase: boolean;
|
||||
requireUpperCase: boolean;
|
||||
requireNumber: boolean;
|
||||
requireSpecial: boolean;
|
||||
policy: PasswordPolicyConfiguration;
|
||||
}
|
||||
|
||||
const PasswordMeter = function (props: Props) {
|
||||
|
@ -39,17 +30,21 @@ const PasswordMeter = function (props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
const password = props.value;
|
||||
if (props.mode === "standard") {
|
||||
if (props.policy.mode === PasswordPolicyMode.Standard) {
|
||||
//use mode mode
|
||||
setMaxScores(4);
|
||||
if (password.length < props.minLength) {
|
||||
if (password.length < props.policy.min_length) {
|
||||
setPasswordScore(0);
|
||||
setFeedback(translate("Must be at least {{len}} characters in length", { len: props.minLength }));
|
||||
setFeedback(
|
||||
translate("Must be at least {{len}} characters in length", { len: props.policy.min_length }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (password.length > props.maxLength) {
|
||||
if (props.policy.max_length !== 0 && password.length > props.policy.max_length) {
|
||||
setPasswordScore(0);
|
||||
setFeedback(translate("Must not be more than {{len}} characters in length", { len: props.maxLength }));
|
||||
setFeedback(
|
||||
translate("Must not be more than {{len}} characters in length", { len: props.policy.max_length }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
setFeedback("");
|
||||
|
@ -57,7 +52,7 @@ const PasswordMeter = function (props: Props) {
|
|||
let required = 0;
|
||||
let hits = 0;
|
||||
let warning = "";
|
||||
if (props.requireLowerCase) {
|
||||
if (props.policy.require_lowercase) {
|
||||
required++;
|
||||
const hasLowercase = /[a-z]/.test(password);
|
||||
if (hasLowercase) {
|
||||
|
@ -67,7 +62,7 @@ const PasswordMeter = function (props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
if (props.requireUpperCase) {
|
||||
if (props.policy.require_uppercase) {
|
||||
required++;
|
||||
const hasUppercase = /[A-Z]/.test(password);
|
||||
if (hasUppercase) {
|
||||
|
@ -77,7 +72,7 @@ const PasswordMeter = function (props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
if (props.requireNumber) {
|
||||
if (props.policy.require_number) {
|
||||
required++;
|
||||
const hasNumber = /[0-9]/.test(password);
|
||||
if (hasNumber) {
|
||||
|
@ -87,7 +82,7 @@ const PasswordMeter = function (props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
if (props.requireSpecial) {
|
||||
if (props.policy.require_special) {
|
||||
required++;
|
||||
const hasSpecial = /[^0-9\w]/i.test(password);
|
||||
if (hasSpecial) {
|
||||
|
@ -102,7 +97,7 @@ const PasswordMeter = function (props: Props) {
|
|||
setFeedback(translate("The password does not meet the password policy") + ":\n" + warning);
|
||||
}
|
||||
setPasswordScore(score);
|
||||
} else if (props.mode === "zxcvbn") {
|
||||
} else if (props.policy.mode === PasswordPolicyMode.ZXCVBN) {
|
||||
//use zxcvbn mode
|
||||
setMaxScores(5);
|
||||
const { score, feedback } = zxcvbn(password);
|
||||
|
@ -111,14 +106,8 @@ const PasswordMeter = function (props: Props) {
|
|||
}
|
||||
}, [props, translate]);
|
||||
|
||||
if (props.mode === "" || props.mode === "none") return <span></span>;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<div style={{ width: "100%" }}>
|
||||
<div
|
||||
title={feedback}
|
||||
className={classnames(style.progressBar)}
|
||||
|
@ -126,7 +115,7 @@ const PasswordMeter = function (props: Props) {
|
|||
width: `${(passwordScore + 1) * (100 / maxScores)}%`,
|
||||
backgroundColor: progressColor[passwordScore],
|
||||
}}
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
export enum PasswordPolicyMode {
|
||||
Disabled = 0,
|
||||
Standard = 1,
|
||||
ZXCVBN = 2,
|
||||
}
|
||||
|
||||
export interface PasswordPolicyConfiguration {
|
||||
mode: PasswordPolicyMode;
|
||||
min_length: number;
|
||||
max_length: number;
|
||||
min_score: number;
|
||||
require_uppercase: boolean;
|
||||
require_lowercase: boolean;
|
||||
require_number: boolean;
|
||||
require_special: boolean;
|
||||
}
|
|
@ -25,6 +25,7 @@ export const CompleteTOTPSignInPath = basePath + "/api/secondfactor/totp";
|
|||
|
||||
export const InitiateResetPasswordPath = basePath + "/api/reset-password/identity/start";
|
||||
export const CompleteResetPasswordPath = basePath + "/api/reset-password/identity/finish";
|
||||
|
||||
// Do the password reset during completion.
|
||||
export const ResetPasswordPath = basePath + "/api/reset-password";
|
||||
export const ChecksSafeRedirectionPath = basePath + "/api/checks/safe-redirection";
|
||||
|
@ -36,6 +37,7 @@ export const UserInfo2FAMethodPath = basePath + "/api/user/info/2fa_method";
|
|||
export const UserInfoTOTPConfigurationPath = basePath + "/api/user/info/totp";
|
||||
|
||||
export const ConfigurationPath = basePath + "/api/configuration";
|
||||
export const PasswordPolicyConfigurationPath = basePath + "/api/configuration/password-policy";
|
||||
|
||||
export interface ErrorResponse {
|
||||
status: "KO";
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { PasswordPolicyConfiguration, PasswordPolicyMode } from "@models/PasswordPolicy";
|
||||
import { PasswordPolicyConfigurationPath } from "@services/Api";
|
||||
import { Get } from "@services/Client";
|
||||
|
||||
interface PasswordPolicyConfigurationPayload {
|
||||
mode: ModePasswordPolicy;
|
||||
min_length: number;
|
||||
max_length: number;
|
||||
min_score: number;
|
||||
require_uppercase: boolean;
|
||||
require_lowercase: boolean;
|
||||
require_number: boolean;
|
||||
require_special: boolean;
|
||||
}
|
||||
|
||||
export type ModePasswordPolicy = "disabled" | "standard" | "zxcvbn";
|
||||
|
||||
export function toEnum(method: ModePasswordPolicy): PasswordPolicyMode {
|
||||
switch (method) {
|
||||
case "disabled":
|
||||
return PasswordPolicyMode.Disabled;
|
||||
case "standard":
|
||||
return PasswordPolicyMode.Standard;
|
||||
case "zxcvbn":
|
||||
return PasswordPolicyMode.ZXCVBN;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPasswordPolicyConfiguration(): Promise<PasswordPolicyConfiguration> {
|
||||
const config = await Get<PasswordPolicyConfigurationPayload>(PasswordPolicyConfigurationPath);
|
||||
|
||||
return { ...config, mode: toEnum(config.mode) };
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { Grid, Button, makeStyles, InputAdornment, IconButton } from "@material-ui/core";
|
||||
import { Button, Grid, IconButton, InputAdornment, makeStyles } from "@material-ui/core";
|
||||
import { Visibility, VisibilityOff } from "@material-ui/icons";
|
||||
import classnames from "classnames";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
@ -11,6 +11,8 @@ import PasswordMeter from "@components/PasswordMeter";
|
|||
import { IndexRoute } from "@constants/Routes";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import LoginLayout from "@layouts/LoginLayout";
|
||||
import { PasswordPolicyConfiguration, PasswordPolicyMode } from "@models/PasswordPolicy";
|
||||
import { getPasswordPolicyConfiguration } from "@services/PasswordPolicyConfiguration";
|
||||
import { completeResetPasswordProcess, resetPassword } from "@services/ResetPassword";
|
||||
import { extractIdentityToken } from "@utils/IdentityToken";
|
||||
|
||||
|
@ -26,13 +28,17 @@ const ResetPasswordStep2 = function () {
|
|||
const { t: translate } = useTranslation("Portal");
|
||||
const navigate = useNavigate();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [pPolicyMode, setPPolicyMode] = useState("none");
|
||||
const [pPolicyMinLength, setPPolicyMinLength] = useState(0);
|
||||
const [pPolicyMaxLength, setPPolicyMaxLength] = useState(0);
|
||||
const [pPolicyRequireUpperCase, setPPolicyRequireUpperCase] = useState(false);
|
||||
const [pPolicyRequireLowerCase, setPPolicyRequireLowerCase] = useState(false);
|
||||
const [pPolicyRequireNumber, setPPolicyRequireNumber] = useState(false);
|
||||
const [pPolicyRequireSpecial, setPPolicyRequireSpecial] = useState(false);
|
||||
|
||||
const [pPolicy, setPPolicy] = useState<PasswordPolicyConfiguration>({
|
||||
max_length: 0,
|
||||
min_length: 8,
|
||||
min_score: 0,
|
||||
require_lowercase: false,
|
||||
require_number: false,
|
||||
require_special: false,
|
||||
require_uppercase: false,
|
||||
mode: PasswordPolicyMode.Disabled,
|
||||
});
|
||||
|
||||
// Get the token from the query param to give it back to the API when requesting
|
||||
// the secret for OTP.
|
||||
|
@ -47,22 +53,9 @@ const ResetPasswordStep2 = function () {
|
|||
|
||||
try {
|
||||
setFormDisabled(true);
|
||||
const {
|
||||
mode,
|
||||
min_length,
|
||||
max_length,
|
||||
require_uppercase,
|
||||
require_lowercase,
|
||||
require_number,
|
||||
require_special,
|
||||
} = await completeResetPasswordProcess(processToken);
|
||||
setPPolicyMode(mode);
|
||||
setPPolicyMinLength(min_length);
|
||||
setPPolicyMaxLength(max_length);
|
||||
setPPolicyRequireLowerCase(require_lowercase);
|
||||
setPPolicyRequireUpperCase(require_uppercase);
|
||||
setPPolicyRequireNumber(require_number);
|
||||
setPPolicyRequireSpecial(require_special);
|
||||
await completeResetPasswordProcess(processToken);
|
||||
const policy = await getPasswordPolicyConfiguration();
|
||||
setPPolicy(policy);
|
||||
setFormDisabled(false);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
@ -144,16 +137,9 @@ const ResetPasswordStep2 = function () {
|
|||
),
|
||||
}}
|
||||
/>
|
||||
<PasswordMeter
|
||||
value={password1}
|
||||
mode={pPolicyMode}
|
||||
minLength={pPolicyMinLength}
|
||||
maxLength={pPolicyMaxLength}
|
||||
requireLowerCase={pPolicyRequireLowerCase}
|
||||
requireUpperCase={pPolicyRequireUpperCase}
|
||||
requireNumber={pPolicyRequireNumber}
|
||||
requireSpecial={pPolicyRequireSpecial}
|
||||
></PasswordMeter>
|
||||
{pPolicy.mode === PasswordPolicyMode.Disabled ? null : (
|
||||
<PasswordMeter value={password1} policy={pPolicy} />
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FixedTextField
|
||||
|
|
Loading…
Reference in New Issue