From 045c26d258d57a47cea220fb546240bdce1c1ad1 Mon Sep 17 00:00:00 2001 From: julius Date: Tue, 11 Mar 2025 08:12:29 +0100 Subject: [PATCH] feat: setup for setting first password --- main.py | 12 ++++++++--- security.py | 11 +++++++--- src/Login.tsx | 27 ++++++++++++++++++------ src/SetPassword.tsx | 50 ++++++++++++++++++++++++++++++++++++--------- 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/main.py b/main.py index 959a1da..4189d79 100644 --- a/main.py +++ b/main.py @@ -8,11 +8,13 @@ from sqlmodel import ( from fastapi.middleware.cors import CORSMiddleware from analysis import analysis_router from security import ( + change_password, get_current_active_user, login_for_access_token, logout, - read_users_me, + read_player_me, read_own_items, + set_first_password, ) @@ -66,6 +68,11 @@ def list_teams(): player_router = APIRouter(prefix="/player") player_router.add_api_route("/list", endpoint=list_players, methods=["GET"]) player_router.add_api_route("/add", endpoint=add_player, methods=["POST"]) +player_router.add_api_route("/me", endpoint=read_player_me, methods=["GET"]) +player_router.add_api_route("/me/items", endpoint=read_own_items, methods=["GET"]) +player_router.add_api_route( + "/change_password", endpoint=change_password, methods=["POST"] +) team_router = APIRouter(prefix="/team") team_router.add_api_route("/list", endpoint=list_teams, methods=["GET"]) @@ -103,8 +110,7 @@ api_router.include_router( dependencies=[Security(get_current_active_user, scopes=["analysis"])], ) api_router.add_api_route("/token", endpoint=login_for_access_token, methods=["POST"]) +api_router.add_api_route("/set_password", endpoint=set_first_password, methods=["POST"]) api_router.add_api_route("/logout", endpoint=logout, methods=["POST"]) -api_router.add_api_route("/users/me/", endpoint=read_users_me, methods=["GET"]) -api_router.add_api_route("/users/me/items/", endpoint=read_own_items, methods=["GET"]) app.include_router(api_router) app.mount("/", SPAStaticFiles(directory="dist", html=True), name="site") diff --git a/security.py b/security.py index d3d5e93..b23fa91 100644 --- a/security.py +++ b/security.py @@ -189,9 +189,14 @@ async def logout(response: Response): def generate_one_time_token(username): - expire = timedelta(days=7) - token = create_access_token(data={"sub": username}, expires_delta=expire) - return token + user = get_user(username) + if user: + expire = timedelta(days=7) + token = create_access_token( + data={"sub": username, "name": user.display_name}, + expires_delta=expire, + ) + return token class FirstPassword(BaseModel): diff --git a/src/Login.tsx b/src/Login.tsx index f872328..70b7ab8 100644 --- a/src/Login.tsx +++ b/src/Login.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { currentUser, login, User } from "./api"; import Header from "./Header"; @@ -9,12 +9,12 @@ export interface LoginProps { export const Login = ({ onLogin }: LoginProps) => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); - const [error, setError] = useState(null); + const [error, setError] = useState(""); const [loading, setLoading] = useState(false); async function doLogin() { setLoading(true); - setError(null); + setError(""); const timeout = new Promise((r) => setTimeout(r, 1000)); let user: User; try { @@ -22,7 +22,7 @@ export const Login = ({ onLogin }: LoginProps) => { user = await currentUser(); } catch (e) { await timeout; - setError(e); + setError("failed"); setLoading(false); return; } @@ -35,6 +35,14 @@ export const Login = ({ onLogin }: LoginProps) => { doLogin(); } + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const queryUsername = params.get("username"); + const queryPassword = params.get("password"); + if (queryUsername) setUsername(queryUsername); + if (queryPassword) setPassword(queryPassword); + }, []); + return ( <>
@@ -47,7 +55,10 @@ export const Login = ({ onLogin }: LoginProps) => { placeholder="username" required value={username} - onChange={(evt) => setUsername(evt.target.value)} + onChange={(evt) => { + setError(""); + setUsername(evt.target.value); + }} />
@@ -59,9 +70,13 @@ export const Login = ({ onLogin }: LoginProps) => { minLength={8} value={password} required - onChange={(evt) => setPassword(evt.target.value)} + onChange={(evt) => { + setError(""); + setPassword(evt.target.value); + }} />
+
{error && {error}}
diff --git a/src/SetPassword.tsx b/src/SetPassword.tsx index c07e671..de64cb5 100644 --- a/src/SetPassword.tsx +++ b/src/SetPassword.tsx @@ -1,25 +1,36 @@ -import { jwtDecode } from "jwt-decode"; +import { InvalidTokenError, jwtDecode, JwtPayload } from "jwt-decode"; import { useEffect, useState } from "react"; import { baseUrl } from "./api"; -import { Navigate, useNavigate } from "react-router"; +import { redirect, useNavigate } from "react-router"; + +interface SetPassToken extends JwtPayload { + name: string; +} export const SetPassword = () => { + const [name, setName] = useState("after getting your token."); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [passwordr, setPasswordr] = useState(""); const [token, setToken] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); - const navigate = useNavigate(); + useEffect(() => { const params = new URLSearchParams(window.location.search); const token = params.get("token"); if (token) { setToken(token); - const payload = jwtDecode(token); - payload.sub && setUsername(payload.sub); - console.log(payload); + try { + const payload = jwtDecode(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"); + } } }, []); @@ -40,20 +51,39 @@ export const SetPassword = () => { } catch (e) { throw new Error(`request failed: ${e}`); } + setLoading(false); + + if (resp.ok) { + console.log(resp); + navigate({ + pathname: "/", + search: `?username=${encodeURI(username)}&password=${encodeURI(password)}`, + }); + } if (!resp.ok) { if (resp.status === 401) { - setError("unauthorized"); - setLoading(false); + resp.statusText + ? setError(resp.statusText) + : setError("unauthorized"); throw new Error("Unauthorized"); } - } else navigate("/"); + } } else setError("passwords are not the same"); } return ( <> -

set your password, {username}

+

+ set your password, +
+ {name} +

+ {username && ( + + your username is: {username} + + )}