feat: add ability to change password

This commit is contained in:
2025-03-16 18:11:28 +01:00
parent 39630725a4
commit 4252e737d7
6 changed files with 127 additions and 57 deletions

View File

@@ -153,7 +153,8 @@ input:checked+.slider:before {
input {
padding: 0.2em 16px;
margin-bottom: 0.5em;
margin-top: 0.25em;
margin-bottom: 0.25em;
border-radius: 1em;
}

View File

@@ -36,6 +36,7 @@ function App() {
<Route path="/network" element={<GraphComponent />} />
<Route path="/analysis" element={<Analysis />} />
<Route path="/mvp" element={<MVPChart />} />
<Route path="/changepassword" element={<SetPassword />} />
</Routes>
<Footer />
</SessionProvider>

View File

@@ -3,6 +3,7 @@ import { useSession } from "./Session";
import { User } from "./api";
import { useTheme } from "./ThemeProvider";
import { colourTheme, darkTheme, normalTheme, rainbowTheme } from "./themes";
import { useNavigate } from "react-router";
interface ContextMenuItem {
label: string;
@@ -35,6 +36,7 @@ const UserInfo = (user: User) => {
export default function Avatar() {
const { user, onLogout } = useSession();
const { theme, setTheme } = useTheme();
const navigate = useNavigate();
const [contextMenu, setContextMenu] = useState<{
open: boolean;
allowOpen: boolean;
@@ -46,6 +48,7 @@ export default function Avatar() {
const contextMenuItems: ContextMenuItem[] = [
{ label: "view Profile", onClick: handleViewProfile },
{ label: "change password", onClick: () => navigate("/changepassword") },
{
label: "change theme",
onClick: () => {

View File

@@ -1,10 +1,9 @@
import { jwtDecode, JwtPayload } from "jwt-decode";
import { useEffect, useState } from "react";
import { baseUrl } from "./api";
import { apiAuth, baseUrl } from "./api";
import { useNavigate } from "react-router";
import eye from "./eye.svg";
import { Eye, EyeSlash } from "./Icons";
import { relative } from "path";
import { useSession } from "./Session";
interface SetPassToken extends JwtPayload {
name: string;
@@ -13,6 +12,7 @@ interface SetPassToken extends JwtPayload {
export const SetPassword = () => {
const [name, setName] = useState("after getting your token.");
const [username, setUsername] = useState("");
const [currentPassword, setCurrentPassword] = useState("");
const [password, setPassword] = useState("");
const [passwordr, setPasswordr] = useState("");
const [token, setToken] = useState("");
@@ -20,20 +20,26 @@ export const SetPassword = () => {
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const navigate = useNavigate();
const { user } = useSession();
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const token = params.get("token");
if (token) {
setToken(token);
try {
const payload = jwtDecode<SetPassToken>(token);
if (payload.name) setName(payload.name);
else if (payload.sub) setName(payload.sub);
else setName("Mr. I-have-no Token");
payload.sub && setUsername(payload.sub);
} catch (InvalidTokenError) {
setName("Mr. I-have-no-valid Token");
if (user) {
setUsername(user.username);
setName(user.display_name);
} else {
const params = new URLSearchParams(window.location.search);
const token = params.get("token");
if (token) {
setToken(token);
try {
const payload = jwtDecode<SetPassToken>(token);
if (payload.name) setName(payload.name);
else if (payload.sub) setName(payload.sub);
else setName("Mr. I-have-no Token");
payload.sub && setUsername(payload.sub);
} catch (InvalidTokenError) {
setName("Mr. I-have-no-valid Token");
}
}
}
}, []);
@@ -42,35 +48,49 @@ export const SetPassword = () => {
e.preventDefault();
if (password === passwordr) {
setLoading(true);
const req = new Request(`${baseUrl}api/set_password`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ token: token, password: password }),
});
let resp: Response;
try {
resp = await fetch(req);
} catch (e) {
throw new Error(`request failed: ${e}`);
}
setLoading(false);
if (resp.ok) {
console.log(resp);
navigate("/", {
replace: true,
state: { username: username, password: password },
if (user) {
const resp = await apiAuth(
"player/change_password",
{ current_password: currentPassword, new_password: password },
"POST"
);
setLoading(false);
if (resp.detail) setError(resp.detail);
else {
setError(resp);
setTimeout(() => navigate("/"), 2000);
}
} else {
const req = new Request(`${baseUrl}api/set_password`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ token: token, password: password }),
});
}
let resp: Response;
try {
resp = await fetch(req);
} catch (e) {
throw new Error(`request failed: ${e}`);
}
setLoading(false);
if (!resp.ok) {
if (resp.status === 401) {
const { detail } = await resp.json();
if (detail) setError(detail);
else setError("unauthorized");
throw new Error("Unauthorized");
if (resp.ok) {
console.log(resp);
navigate("/", {
replace: true,
state: { username: username, password: password },
});
}
if (!resp.ok) {
if (resp.status === 401) {
const { detail } = await resp.json();
if (detail) setError(detail);
else setError("unauthorized");
throw new Error("Unauthorized");
}
}
}
} else setError("passwords are not the same");
@@ -78,12 +98,16 @@ export const SetPassword = () => {
return (
<>
<h2>
set your password,
<br />
{name}
</h2>
{username && (
{user ? (
<h2> change your password </h2>
) : (
<h2>
set your password,
<br />
{name}
</h2>
)}
{!user && username && (
<span>
your username is: <i>{username}</i>
</span>
@@ -104,6 +128,30 @@ export const SetPassword = () => {
flexDirection: "column",
}}
>
{user && (
<div>
<input
type={visible ? "text" : "password"}
id="password"
name="password"
placeholder="current password"
minLength={8}
value={currentPassword}
required
onChange={(evt) => {
setError("");
setCurrentPassword(evt.target.value);
}}
/>
<hr
style={{
margin: "8px",
borderStyle: "inset",
display: "block",
}}
/>
</div>
)}
<div>
<input
type={visible ? "text" : "password"}
@@ -148,7 +196,7 @@ export const SetPassword = () => {
</div>
<div>{error && <span style={{ color: "red" }}>{error}</span>}</div>
<button type="submit" value="login" style={{ fontSize: "small" }}>
login
submit
</button>
{loading && <span className="loader" />}
</form>

View File

@@ -29,7 +29,18 @@ export async function apiAuth(
throw new Error("Unauthorized");
}
}
return resp.json();
const contentType = resp.headers.get("Content-Type");
switch (contentType) {
case "application/json":
return resp.json();
case "text/plain":
case "text/plain; charset=utf-8":
return resp.text();
case null:
return null;
default:
throw new Error(`Unsupported content type: ${contentType}`);
}
}
export type User = {