feat: add ability to change password
This commit is contained in:
parent
39630725a4
commit
4252e737d7
18
security.py
18
security.py
@ -256,22 +256,28 @@ async def set_first_password(req: FirstPassword):
|
|||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
|
|
||||||
|
|
||||||
|
class ChangedPassword(BaseModel):
|
||||||
|
current_password: str
|
||||||
|
new_password: str
|
||||||
|
|
||||||
|
|
||||||
async def change_password(
|
async def change_password(
|
||||||
current_password: str,
|
request: ChangedPassword,
|
||||||
new_password: str,
|
|
||||||
user: Annotated[Player, Depends(get_current_active_user)],
|
user: Annotated[Player, Depends(get_current_active_user)],
|
||||||
):
|
):
|
||||||
if (
|
if (
|
||||||
new_password
|
request.new_password
|
||||||
and user.hashed_password
|
and user.hashed_password
|
||||||
and verify_password(current_password, user.hashed_password)
|
and verify_password(request.current_password, user.hashed_password)
|
||||||
):
|
):
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
user.hashed_password = get_password_hash(new_password)
|
user.hashed_password = get_password_hash(request.new_password)
|
||||||
session.add(user)
|
session.add(user)
|
||||||
session.commit()
|
session.commit()
|
||||||
return PlainTextResponse(
|
return PlainTextResponse(
|
||||||
"Password changed successfully", status_code=status.HTTP_200_OK
|
"Password changed successfully",
|
||||||
|
status_code=status.HTTP_200_OK,
|
||||||
|
media_type="text/plain",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
@ -153,7 +153,8 @@ input:checked+.slider:before {
|
|||||||
|
|
||||||
input {
|
input {
|
||||||
padding: 0.2em 16px;
|
padding: 0.2em 16px;
|
||||||
margin-bottom: 0.5em;
|
margin-top: 0.25em;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ function App() {
|
|||||||
<Route path="/network" element={<GraphComponent />} />
|
<Route path="/network" element={<GraphComponent />} />
|
||||||
<Route path="/analysis" element={<Analysis />} />
|
<Route path="/analysis" element={<Analysis />} />
|
||||||
<Route path="/mvp" element={<MVPChart />} />
|
<Route path="/mvp" element={<MVPChart />} />
|
||||||
|
<Route path="/changepassword" element={<SetPassword />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
<Footer />
|
<Footer />
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
|
@ -3,6 +3,7 @@ import { useSession } from "./Session";
|
|||||||
import { User } from "./api";
|
import { User } from "./api";
|
||||||
import { useTheme } from "./ThemeProvider";
|
import { useTheme } from "./ThemeProvider";
|
||||||
import { colourTheme, darkTheme, normalTheme, rainbowTheme } from "./themes";
|
import { colourTheme, darkTheme, normalTheme, rainbowTheme } from "./themes";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
interface ContextMenuItem {
|
interface ContextMenuItem {
|
||||||
label: string;
|
label: string;
|
||||||
@ -35,6 +36,7 @@ const UserInfo = (user: User) => {
|
|||||||
export default function Avatar() {
|
export default function Avatar() {
|
||||||
const { user, onLogout } = useSession();
|
const { user, onLogout } = useSession();
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [contextMenu, setContextMenu] = useState<{
|
const [contextMenu, setContextMenu] = useState<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
allowOpen: boolean;
|
allowOpen: boolean;
|
||||||
@ -46,6 +48,7 @@ export default function Avatar() {
|
|||||||
|
|
||||||
const contextMenuItems: ContextMenuItem[] = [
|
const contextMenuItems: ContextMenuItem[] = [
|
||||||
{ label: "view Profile", onClick: handleViewProfile },
|
{ label: "view Profile", onClick: handleViewProfile },
|
||||||
|
{ label: "change password", onClick: () => navigate("/changepassword") },
|
||||||
{
|
{
|
||||||
label: "change theme",
|
label: "change theme",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { jwtDecode, JwtPayload } from "jwt-decode";
|
import { jwtDecode, JwtPayload } from "jwt-decode";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { baseUrl } from "./api";
|
import { apiAuth, baseUrl } from "./api";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import eye from "./eye.svg";
|
|
||||||
import { Eye, EyeSlash } from "./Icons";
|
import { Eye, EyeSlash } from "./Icons";
|
||||||
import { relative } from "path";
|
import { useSession } from "./Session";
|
||||||
|
|
||||||
interface SetPassToken extends JwtPayload {
|
interface SetPassToken extends JwtPayload {
|
||||||
name: string;
|
name: string;
|
||||||
@ -13,6 +12,7 @@ interface SetPassToken extends JwtPayload {
|
|||||||
export const SetPassword = () => {
|
export const SetPassword = () => {
|
||||||
const [name, setName] = useState("after getting your token.");
|
const [name, setName] = useState("after getting your token.");
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
|
const [currentPassword, setCurrentPassword] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [passwordr, setPasswordr] = useState("");
|
const [passwordr, setPasswordr] = useState("");
|
||||||
const [token, setToken] = useState("");
|
const [token, setToken] = useState("");
|
||||||
@ -20,20 +20,26 @@ export const SetPassword = () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { user } = useSession();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
if (user) {
|
||||||
const token = params.get("token");
|
setUsername(user.username);
|
||||||
if (token) {
|
setName(user.display_name);
|
||||||
setToken(token);
|
} else {
|
||||||
try {
|
const params = new URLSearchParams(window.location.search);
|
||||||
const payload = jwtDecode<SetPassToken>(token);
|
const token = params.get("token");
|
||||||
if (payload.name) setName(payload.name);
|
if (token) {
|
||||||
else if (payload.sub) setName(payload.sub);
|
setToken(token);
|
||||||
else setName("Mr. I-have-no Token");
|
try {
|
||||||
payload.sub && setUsername(payload.sub);
|
const payload = jwtDecode<SetPassToken>(token);
|
||||||
} catch (InvalidTokenError) {
|
if (payload.name) setName(payload.name);
|
||||||
setName("Mr. I-have-no-valid Token");
|
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();
|
e.preventDefault();
|
||||||
if (password === passwordr) {
|
if (password === passwordr) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const req = new Request(`${baseUrl}api/set_password`, {
|
if (user) {
|
||||||
method: "POST",
|
const resp = await apiAuth(
|
||||||
headers: {
|
"player/change_password",
|
||||||
"Content-Type": "application/json",
|
{ current_password: currentPassword, new_password: password },
|
||||||
},
|
"POST"
|
||||||
body: JSON.stringify({ token: token, password: password }),
|
);
|
||||||
});
|
setLoading(false);
|
||||||
let resp: Response;
|
if (resp.detail) setError(resp.detail);
|
||||||
try {
|
else {
|
||||||
resp = await fetch(req);
|
setError(resp);
|
||||||
} catch (e) {
|
setTimeout(() => navigate("/"), 2000);
|
||||||
throw new Error(`request failed: ${e}`);
|
}
|
||||||
}
|
} else {
|
||||||
setLoading(false);
|
const req = new Request(`${baseUrl}api/set_password`, {
|
||||||
|
method: "POST",
|
||||||
if (resp.ok) {
|
headers: {
|
||||||
console.log(resp);
|
"Content-Type": "application/json",
|
||||||
navigate("/", {
|
},
|
||||||
replace: true,
|
body: JSON.stringify({ token: token, password: password }),
|
||||||
state: { username: username, 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.ok) {
|
||||||
if (resp.status === 401) {
|
console.log(resp);
|
||||||
const { detail } = await resp.json();
|
navigate("/", {
|
||||||
if (detail) setError(detail);
|
replace: true,
|
||||||
else setError("unauthorized");
|
state: { username: username, password: password },
|
||||||
throw new Error("Unauthorized");
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
} else setError("passwords are not the same");
|
||||||
@ -78,12 +98,16 @@ export const SetPassword = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2>
|
{user ? (
|
||||||
set your password,
|
<h2> change your password </h2>
|
||||||
<br />
|
) : (
|
||||||
{name}
|
<h2>
|
||||||
</h2>
|
set your password,
|
||||||
{username && (
|
<br />
|
||||||
|
{name}
|
||||||
|
</h2>
|
||||||
|
)}
|
||||||
|
{!user && username && (
|
||||||
<span>
|
<span>
|
||||||
your username is: <i>{username}</i>
|
your username is: <i>{username}</i>
|
||||||
</span>
|
</span>
|
||||||
@ -104,6 +128,30 @@ export const SetPassword = () => {
|
|||||||
flexDirection: "column",
|
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>
|
<div>
|
||||||
<input
|
<input
|
||||||
type={visible ? "text" : "password"}
|
type={visible ? "text" : "password"}
|
||||||
@ -148,7 +196,7 @@ export const SetPassword = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>{error && <span style={{ color: "red" }}>{error}</span>}</div>
|
<div>{error && <span style={{ color: "red" }}>{error}</span>}</div>
|
||||||
<button type="submit" value="login" style={{ fontSize: "small" }}>
|
<button type="submit" value="login" style={{ fontSize: "small" }}>
|
||||||
login
|
submit
|
||||||
</button>
|
</button>
|
||||||
{loading && <span className="loader" />}
|
{loading && <span className="loader" />}
|
||||||
</form>
|
</form>
|
||||||
|
13
src/api.ts
13
src/api.ts
@ -29,7 +29,18 @@ export async function apiAuth(
|
|||||||
throw new Error("Unauthorized");
|
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 = {
|
export type User = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user