Can replace user password from profile
This commit is contained in:
parent
e3bec527f0
commit
71db3339d8
@ -11,6 +11,14 @@ export interface User {
|
|||||||
has_password: boolean;
|
has_password: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ReplacePasswordResponse {
|
||||||
|
Error,
|
||||||
|
Success,
|
||||||
|
InvalidOldPassword,
|
||||||
|
InvalidNewPassword,
|
||||||
|
TooManyRequests,
|
||||||
|
}
|
||||||
|
|
||||||
export class UserApi {
|
export class UserApi {
|
||||||
/**
|
/**
|
||||||
* Get current user information
|
* Get current user information
|
||||||
@ -36,4 +44,39 @@ export class UserApi {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace user password
|
||||||
|
*/
|
||||||
|
static async ReplacePassword(
|
||||||
|
oldPwd: string,
|
||||||
|
newPwd: string
|
||||||
|
): Promise<ReplacePasswordResponse> {
|
||||||
|
const res = await APIClient.exec({
|
||||||
|
uri: "/user/replace_password",
|
||||||
|
method: "POST",
|
||||||
|
jsonData: {
|
||||||
|
old_password: oldPwd,
|
||||||
|
new_password: newPwd,
|
||||||
|
},
|
||||||
|
allowFail: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status >= 200 && res.status < 300)
|
||||||
|
return ReplacePasswordResponse.Success;
|
||||||
|
|
||||||
|
switch (res.status) {
|
||||||
|
case 400:
|
||||||
|
return ReplacePasswordResponse.InvalidNewPassword;
|
||||||
|
|
||||||
|
case 401:
|
||||||
|
return ReplacePasswordResponse.InvalidOldPassword;
|
||||||
|
|
||||||
|
case 429:
|
||||||
|
return ReplacePasswordResponse.TooManyRequests;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ReplacePasswordResponse.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||||
import { User, UserApi } from "../api/UserApi";
|
import { ReplacePasswordResponse, User, UserApi } from "../api/UserApi";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
@ -9,43 +9,26 @@ import {
|
|||||||
CardActions,
|
CardActions,
|
||||||
CardContent,
|
CardContent,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
CircularProgress,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { TimeWidget, formatDate } from "../widgets/TimeWidget";
|
import { TimeWidget, formatDate } from "../widgets/TimeWidget";
|
||||||
import { ServerApi } from "../api/ServerApi";
|
import { ServerApi } from "../api/ServerApi";
|
||||||
|
import { PasswordInput } from "../widgets/PasswordInput";
|
||||||
|
import { normalize } from "path/win32";
|
||||||
|
|
||||||
export function ProfileRoute(): React.ReactElement {
|
export function ProfileRoute(): React.ReactElement {
|
||||||
const [user, setUser] = React.useState<null | User>(null);
|
const [user, setUser] = React.useState<null | User>(null);
|
||||||
const [newName, setNewName] = React.useState("");
|
|
||||||
|
|
||||||
const [error, setError] = React.useState<string | null>(null);
|
|
||||||
const [success, setSuccess] = React.useState<string | null>(null);
|
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
const u = await UserApi.GetUserInfo();
|
const u = await UserApi.GetUserInfo();
|
||||||
setUser(u);
|
setUser(u);
|
||||||
setNewName(u.name);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const counter = useRef(0);
|
const counter = useRef(0);
|
||||||
|
|
||||||
const updateProfile = async () => {
|
|
||||||
try {
|
|
||||||
setSuccess(null);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
await UserApi.UpdateProfile(newName);
|
|
||||||
|
|
||||||
counter.current += 1;
|
|
||||||
setSuccess("Informations du profil mises à jour avec succès !");
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
setError("Echec de la mise à jour du profil !");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncWidget
|
<AsyncWidget
|
||||||
loadKey={counter.current}
|
loadKey={counter.current}
|
||||||
@ -55,10 +38,44 @@ export function ProfileRoute(): React.ReactElement {
|
|||||||
<div style={{ maxWidth: "500px", margin: "auto" }}>
|
<div style={{ maxWidth: "500px", margin: "auto" }}>
|
||||||
<Typography variant="h3">Profil</Typography>
|
<Typography variant="h3">Profil</Typography>
|
||||||
|
|
||||||
|
<ProfileSettingsCard
|
||||||
|
user={user!}
|
||||||
|
onUpdate={() => (counter.current += 1)}
|
||||||
|
/>
|
||||||
|
{user?.has_password && <ChangePasswordCard />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProfileSettingsCard(p: { user: User; onUpdate: () => {} }) {
|
||||||
|
const [newName, setNewName] = React.useState(p.user.name);
|
||||||
|
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
const updateProfile = async () => {
|
||||||
|
try {
|
||||||
|
setSuccess(null);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
await UserApi.UpdateProfile(newName);
|
||||||
|
|
||||||
|
p.onUpdate();
|
||||||
|
setSuccess("Informations du profil enregistrées avec succès !");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setError("Echec de la mise à jour du profil !");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card style={{ marginTop: "10px" }}>
|
||||||
{error && <Alert severity="error">{error}</Alert>}
|
{error && <Alert severity="error">{error}</Alert>}
|
||||||
{success && <Alert severity="success">{success}</Alert>}
|
{success && <Alert severity="success">{success}</Alert>}
|
||||||
|
|
||||||
<Card style={{ marginTop: "10px" }}>
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography gutterBottom variant="h5" component="div">
|
<Typography gutterBottom variant="h5" component="div">
|
||||||
Paramètres du compte
|
Paramètres du compte
|
||||||
@ -67,7 +84,7 @@ export function ProfileRoute(): React.ReactElement {
|
|||||||
<Box
|
<Box
|
||||||
component="form"
|
component="form"
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiTextField-root": { m: 1 },
|
"& .MuiTextField-root": { my: 1 },
|
||||||
}}
|
}}
|
||||||
noValidate
|
noValidate
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -76,28 +93,28 @@ export function ProfileRoute(): React.ReactElement {
|
|||||||
disabled
|
disabled
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Identifiant"
|
label="Identifiant"
|
||||||
value={user?.id}
|
value={p.user.id}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
disabled
|
disabled
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Création du compte"
|
label="Création du compte"
|
||||||
value={formatDate(user!.time_create)}
|
value={formatDate(p.user.time_create)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
disabled
|
disabled
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Activation du compte"
|
label="Activation du compte"
|
||||||
value={formatDate(user!.time_activate)}
|
value={formatDate(p.user.time_activate)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
disabled
|
disabled
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Adresse mail"
|
label="Adresse mail"
|
||||||
value={user?.email}
|
value={p.user.email}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
@ -112,7 +129,7 @@ export function ProfileRoute(): React.ReactElement {
|
|||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
disabled
|
disabled
|
||||||
control={<Checkbox checked={user!.admin} />}
|
control={<Checkbox checked={p.user.admin} />}
|
||||||
label="Compte administrateur"
|
label="Compte administrateur"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -123,8 +140,123 @@ export function ProfileRoute(): React.ReactElement {
|
|||||||
</Button>
|
</Button>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</>
|
||||||
)}
|
);
|
||||||
/>
|
}
|
||||||
|
|
||||||
|
function ChangePasswordCard(): React.ReactElement {
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
const [oldPassword, setOldpassword] = React.useState("");
|
||||||
|
const [newPassword, setNewpassword] = React.useState("");
|
||||||
|
const [confirmNewPassword, setConfirmNewpassword] = React.useState("");
|
||||||
|
|
||||||
|
const isValid =
|
||||||
|
ServerApi.CheckPassword(newPassword) === null &&
|
||||||
|
oldPassword.length > 0 &&
|
||||||
|
confirmNewPassword === newPassword;
|
||||||
|
|
||||||
|
const updatePassword = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!isValid || loading) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setSuccess(null);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await UserApi.ReplacePassword(oldPassword, newPassword);
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case ReplacePasswordResponse.Error:
|
||||||
|
setError("Echec du changement de mot de passe !");
|
||||||
|
break;
|
||||||
|
case ReplacePasswordResponse.Success:
|
||||||
|
setSuccess("Mot de passe changé avec succès !");
|
||||||
|
break;
|
||||||
|
case ReplacePasswordResponse.InvalidOldPassword:
|
||||||
|
setError("Ancien mot de passe saisi invalide !");
|
||||||
|
break;
|
||||||
|
case ReplacePasswordResponse.InvalidNewPassword:
|
||||||
|
setError("Nouveau mot de passe saisi invalide !");
|
||||||
|
break;
|
||||||
|
case ReplacePasswordResponse.TooManyRequests:
|
||||||
|
setError(
|
||||||
|
"Trop de tentatives de changement de mot de passe, veuillez réessayer ultérieurement !"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setError("Echec de la mise à jour du mot de passe !");
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card style={{ marginTop: "10px" }}>
|
||||||
|
{error && <Alert severity="error">{error}</Alert>}
|
||||||
|
{success && <Alert severity="success">{success}</Alert>}
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{
|
||||||
|
"& .MuiTextField-root": { my: 1 },
|
||||||
|
}}
|
||||||
|
noValidate
|
||||||
|
autoComplete="off"
|
||||||
|
onSubmit={updatePassword}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Typography gutterBottom variant="h5" component="div">
|
||||||
|
Changement du mot de passe
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Mot de passe actuel"
|
||||||
|
value={oldPassword}
|
||||||
|
type="password"
|
||||||
|
onChange={(e) => setOldpassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PasswordInput
|
||||||
|
value={newPassword}
|
||||||
|
onChange={(n) => setNewpassword(n)}
|
||||||
|
label={"Nouveau mot de passe"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
error={
|
||||||
|
confirmNewPassword !== "" && confirmNewPassword !== newPassword
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
confirmNewPassword !== newPassword
|
||||||
|
? "Le nouveau mot de passe et sa confirmation doivent être identiques !"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
label="Confirmation du nouveau mot de passe"
|
||||||
|
value={confirmNewPassword}
|
||||||
|
type="password"
|
||||||
|
onChange={(e) => setConfirmNewpassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions>
|
||||||
|
<Button
|
||||||
|
disabled={!isValid && !loading}
|
||||||
|
type="submit"
|
||||||
|
style={{ marginLeft: "auto" }}
|
||||||
|
>
|
||||||
|
Enregistrer
|
||||||
|
</Button>
|
||||||
|
</CardActions>{" "}
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ pub async fn replace_password(
|
|||||||
.password_len
|
.password_len
|
||||||
.validate(&q.new_password)
|
.validate(&q.new_password)
|
||||||
{
|
{
|
||||||
return Ok(HttpResponse::BadRequest().json("Nouveau mot de passe invalide!"));
|
return Ok(HttpResponse::BadRequest().json("Invalid new password!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut user = users_service::get_by_id(token.user_id).await?;
|
let mut user = users_service::get_by_id(token.user_id).await?;
|
||||||
@ -90,7 +90,7 @@ pub async fn replace_password(
|
|||||||
RatedAction::RequestReplacePasswordSignedIn,
|
RatedAction::RequestReplacePasswordSignedIn,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(HttpResponse::BadRequest().json("Ancien mot de passe invalide !"));
|
return Ok(HttpResponse::Unauthorized().json("Invalid old password!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
users_service::change_password(&mut user, &q.new_password).await?;
|
users_service::change_password(&mut user, &q.new_password).await?;
|
||||||
|
Loading…
Reference in New Issue
Block a user