Can replace user password from profile
This commit is contained in:
		@@ -11,6 +11,14 @@ export interface User {
 | 
			
		||||
  has_password: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum ReplacePasswordResponse {
 | 
			
		||||
  Error,
 | 
			
		||||
  Success,
 | 
			
		||||
  InvalidOldPassword,
 | 
			
		||||
  InvalidNewPassword,
 | 
			
		||||
  TooManyRequests,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UserApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * 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 { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { User, UserApi } from "../api/UserApi";
 | 
			
		||||
import { ReplacePasswordResponse, User, UserApi } from "../api/UserApi";
 | 
			
		||||
import {
 | 
			
		||||
  Alert,
 | 
			
		||||
  Box,
 | 
			
		||||
@@ -9,43 +9,26 @@ import {
 | 
			
		||||
  CardActions,
 | 
			
		||||
  CardContent,
 | 
			
		||||
  Checkbox,
 | 
			
		||||
  CircularProgress,
 | 
			
		||||
  FormControlLabel,
 | 
			
		||||
  TextField,
 | 
			
		||||
  Typography,
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import { TimeWidget, formatDate } from "../widgets/TimeWidget";
 | 
			
		||||
import { ServerApi } from "../api/ServerApi";
 | 
			
		||||
import { PasswordInput } from "../widgets/PasswordInput";
 | 
			
		||||
import { normalize } from "path/win32";
 | 
			
		||||
 | 
			
		||||
export function ProfileRoute(): React.ReactElement {
 | 
			
		||||
  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 u = await UserApi.GetUserInfo();
 | 
			
		||||
    setUser(u);
 | 
			
		||||
    setNewName(u.name);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  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 (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={counter.current}
 | 
			
		||||
@@ -55,76 +38,225 @@ export function ProfileRoute(): React.ReactElement {
 | 
			
		||||
        <div style={{ maxWidth: "500px", margin: "auto" }}>
 | 
			
		||||
          <Typography variant="h3">Profil</Typography>
 | 
			
		||||
 | 
			
		||||
          {error && <Alert severity="error">{error}</Alert>}
 | 
			
		||||
          {success && <Alert severity="success">{success}</Alert>}
 | 
			
		||||
 | 
			
		||||
          <Card style={{ marginTop: "10px" }}>
 | 
			
		||||
            <CardContent>
 | 
			
		||||
              <Typography gutterBottom variant="h5" component="div">
 | 
			
		||||
                Paramètres du compte
 | 
			
		||||
              </Typography>
 | 
			
		||||
 | 
			
		||||
              <Box
 | 
			
		||||
                component="form"
 | 
			
		||||
                sx={{
 | 
			
		||||
                  "& .MuiTextField-root": { m: 1 },
 | 
			
		||||
                }}
 | 
			
		||||
                noValidate
 | 
			
		||||
                autoComplete="off"
 | 
			
		||||
              >
 | 
			
		||||
                <TextField
 | 
			
		||||
                  disabled
 | 
			
		||||
                  fullWidth
 | 
			
		||||
                  label="Identifiant"
 | 
			
		||||
                  value={user?.id}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <TextField
 | 
			
		||||
                  disabled
 | 
			
		||||
                  fullWidth
 | 
			
		||||
                  label="Création du compte"
 | 
			
		||||
                  value={formatDate(user!.time_create)}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <TextField
 | 
			
		||||
                  disabled
 | 
			
		||||
                  fullWidth
 | 
			
		||||
                  label="Activation du compte"
 | 
			
		||||
                  value={formatDate(user!.time_activate)}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <TextField
 | 
			
		||||
                  disabled
 | 
			
		||||
                  fullWidth
 | 
			
		||||
                  label="Adresse mail"
 | 
			
		||||
                  value={user?.email}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <TextField
 | 
			
		||||
                  fullWidth
 | 
			
		||||
                  label="Nom d'utilisateur"
 | 
			
		||||
                  value={newName}
 | 
			
		||||
                  onChange={(e) => setNewName(e.target.value)}
 | 
			
		||||
                  inputProps={{
 | 
			
		||||
                    maxLength: ServerApi.Config.constraints.user_name_len.max,
 | 
			
		||||
                  }}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <FormControlLabel
 | 
			
		||||
                  disabled
 | 
			
		||||
                  control={<Checkbox checked={user!.admin} />}
 | 
			
		||||
                  label="Compte administrateur"
 | 
			
		||||
                />
 | 
			
		||||
              </Box>
 | 
			
		||||
            </CardContent>
 | 
			
		||||
            <CardActions>
 | 
			
		||||
              <Button onClick={updateProfile} style={{ marginLeft: "auto" }}>
 | 
			
		||||
                Enregistrer
 | 
			
		||||
              </Button>
 | 
			
		||||
            </CardActions>
 | 
			
		||||
          </Card>
 | 
			
		||||
          <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>}
 | 
			
		||||
        {success && <Alert severity="success">{success}</Alert>}
 | 
			
		||||
 | 
			
		||||
        <CardContent>
 | 
			
		||||
          <Typography gutterBottom variant="h5" component="div">
 | 
			
		||||
            Paramètres du compte
 | 
			
		||||
          </Typography>
 | 
			
		||||
 | 
			
		||||
          <Box
 | 
			
		||||
            component="form"
 | 
			
		||||
            sx={{
 | 
			
		||||
              "& .MuiTextField-root": { my: 1 },
 | 
			
		||||
            }}
 | 
			
		||||
            noValidate
 | 
			
		||||
            autoComplete="off"
 | 
			
		||||
          >
 | 
			
		||||
            <TextField
 | 
			
		||||
              disabled
 | 
			
		||||
              fullWidth
 | 
			
		||||
              label="Identifiant"
 | 
			
		||||
              value={p.user.id}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <TextField
 | 
			
		||||
              disabled
 | 
			
		||||
              fullWidth
 | 
			
		||||
              label="Création du compte"
 | 
			
		||||
              value={formatDate(p.user.time_create)}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <TextField
 | 
			
		||||
              disabled
 | 
			
		||||
              fullWidth
 | 
			
		||||
              label="Activation du compte"
 | 
			
		||||
              value={formatDate(p.user.time_activate)}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <TextField
 | 
			
		||||
              disabled
 | 
			
		||||
              fullWidth
 | 
			
		||||
              label="Adresse mail"
 | 
			
		||||
              value={p.user.email}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <TextField
 | 
			
		||||
              fullWidth
 | 
			
		||||
              label="Nom d'utilisateur"
 | 
			
		||||
              value={newName}
 | 
			
		||||
              onChange={(e) => setNewName(e.target.value)}
 | 
			
		||||
              inputProps={{
 | 
			
		||||
                maxLength: ServerApi.Config.constraints.user_name_len.max,
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <FormControlLabel
 | 
			
		||||
              disabled
 | 
			
		||||
              control={<Checkbox checked={p.user.admin} />}
 | 
			
		||||
              label="Compte administrateur"
 | 
			
		||||
            />
 | 
			
		||||
          </Box>
 | 
			
		||||
        </CardContent>
 | 
			
		||||
        <CardActions>
 | 
			
		||||
          <Button onClick={updateProfile} style={{ marginLeft: "auto" }}>
 | 
			
		||||
            Enregistrer
 | 
			
		||||
          </Button>
 | 
			
		||||
        </CardActions>
 | 
			
		||||
      </Card>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
        .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?;
 | 
			
		||||
@@ -90,7 +90,7 @@ pub async fn replace_password(
 | 
			
		||||
            RatedAction::RequestReplacePasswordSignedIn,
 | 
			
		||||
        )
 | 
			
		||||
        .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?;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user