-
Notifications
You must be signed in to change notification settings - Fork 0
fet: add all the me routes , persists user and add rate-limiter middl… #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b503c29
d6acfa6
7e38abd
bcc2ae9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ import type{ ZodType } from "zod"; | |
| import type { Request, Response, NextFunction } from "express"; | ||
| import jwt from "jsonwebtoken" | ||
| import {config} from "../config/config" | ||
|
|
||
| import { rateLimit } from 'express-rate-limit'; | ||
|
|
||
| export class ApiError extends Error{ | ||
| constructor(public statusCode:number,message:string){ | ||
|
|
@@ -56,3 +56,42 @@ export const requireAuth = async(req:Request,res:Response , next:NextFunction) = | |
| throw new ApiError(401, "Invalid or expired token"); | ||
| } | ||
| }; | ||
|
|
||
| // Configure the specific limiter for auth routes | ||
| export const authLimiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 10, // Strict limit for sign-in attempts | ||
| standardHeaders: 'draft-8', | ||
| legacyHeaders: false, | ||
| handler: (req: Request, res: Response) => { | ||
| res.status(429).json({ | ||
| error: 'Too many sign-in attempts. Please try again in 15 minutes.' | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| // DO NOT TOUCH THIS I AM KEEPING IT FOR MY KNOWLEDGE | ||
|
|
||
| // const store = new Map<string, { count: number; resetAt: number }>(); | ||
|
|
||
| // export const rateLimiter = (req: Request, res: Response, next: NextFunction) => { | ||
| // const ip = req.ip ?? "unknown"; | ||
| // const now = Date.now(); | ||
| // const window = 15 * 60 * 1000; // 15 min | ||
| // const limit = 10; | ||
|
|
||
| // let record = store.get(ip); | ||
|
|
||
| // if (!record || now > record.resetAt) { | ||
| // record = { count: 0, resetAt: now + window }; | ||
| // } | ||
|
|
||
| // record.count++; | ||
| // store.set(ip, record); | ||
|
|
||
| // if (record.count > limit) { | ||
| // return res.status(429).json({ message: "Too many requests" }); | ||
| // } | ||
|
|
||
| // next(); | ||
| // }; | ||
|
Comment on lines
+73
to
+97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value Remove the commented-out limiter implementation. The block is dead code. Version control preserves it, so the comment is not needed to keep the knowledge. The in-memory 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,39 @@ | ||
| import { Router } from "express"; | ||
| import {signup,signin,logout,logoutall , refresh} from "../controllers/auth.controllers"; | ||
| import { requireAuth, validate } from "../middleware/auth.middleware"; | ||
| import { signinSchema, signupSchema } from "@devdraw/shared"; | ||
| import { | ||
| signup, | ||
| signin, | ||
| logout, | ||
| logoutall, | ||
| refresh, | ||
| getMe, | ||
| deleteMe, | ||
| patchMe, | ||
| updateMe, | ||
| } from "../controllers/auth.controllers"; | ||
| import { | ||
| requireAuth, | ||
| validate , | ||
| authLimiter | ||
| } from "../middleware/auth.middleware"; | ||
| import { | ||
| deleteMeSchema, | ||
| signinSchema, | ||
| signupSchema, | ||
| updateMeSchema, | ||
| patchMeSchema | ||
| } from "@devdraw/shared"; | ||
|
Comment on lines
+22
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 8 \
'updateMeSchema|patchMeSchema|updateMe|patchMe|updateUser|export const validate' \
apps/api/src/controllers/auth.controllers.ts \
apps/api/src/middleware/auth.middleware.ts \
packages/shared/src/schemas/auth.schemas.ts \
packages/db/queries.tsRepository: ArpanMondalGITHUB/Devdraw Length of output: 8953 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- route files and relevant route definitions ---'
fd -i 'auth.*route.*' apps/api/src
rg -n -C 10 'updateMeSchema|patchMeSchema|updateMe|patchMe' apps/api/src/routes
printf '%s\n' '--- controller and database update implementation ---'
sed -n '78,105p' apps/api/src/controllers/auth.controllers.ts
sed -n '82,125p' packages/db/queries.ts
printf '%s\n' '--- schema helpers and package versions ---'
sed -n '1,50p' packages/shared/src/schemas/auth.schemas.ts
rg -n -C 2 '"zod"|"version"' packages/shared/package.json package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || trueRepository: ArpanMondalGITHUB/Devdraw Length of output: 6161 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
schema = Path("packages/shared/src/schemas/auth.schemas.ts").read_text()
controller = Path("apps/api/src/controllers/auth.controllers.ts").read_text()
middleware = Path("apps/api/src/middleware/auth.middleware.ts").read_text()
queries = Path("packages/db/queries.ts").read_text()
update_schema = re.search(
r"export const updateMeSchema = z\.object\(\{(?P<body>.*?)\n\}\);",
schema, re.S
).group("body")
required = re.findall(r"^\s+([A-Za-z][A-Za-z0-9]*)\s*:", update_schema, re.M)
update_me = re.search(
r"export const updateMe = async .*?\n\};",
controller, re.S
).group(0)
destructure = re.search(r"const \{([^}]+)\} = req\.body", update_me).group(1)
read_fields = re.findall(r"\b(name|email)\b", destructure)
patch_me = re.search(
r"export const patchMe = async .*?\n\};",
controller, re.S
).group(0)
db_signature = re.search(
r"export const updateUser = async \(\s*.*?updates:\{([^}]+)\}",
queries, re.S
).group(1)
db_fields = re.findall(r"\b(name|email)\??\s*:", db_signature)
db_checks = re.findall(r"updates\.(name|email)\s*!==\s*undefined", queries)
print("updateMeSchema required fields:", required)
print("updateMe reads:", read_fields)
print("updateUser typed fields:", db_fields)
print("updateUser persisted fields:", sorted(set(db_checks)))
print("patchMe forwards req.body unchanged:", "updateUser((req as any).userId,updates)" in patch_me)
print("validation replaces req.body:", bool(re.search(r"req\[source\]\s*=\s*result\.data", middleware)))
assert set(required) == {"name", "username", "avatarUrl", "bio"}
assert set(read_fields) == {"name", "email"}
assert set(db_fields) == {"name", "email"}
assert set(db_checks) == {"name", "email"}
assert "updateUser((req as any).userId,updates)" in patch_me
assert not re.search(r"req\[source\]\s*=\s*result\.data", middleware)
PYRepository: ArpanMondalGITHUB/Devdraw Length of output: 438 Align the
🤖 Prompt for AI Agents |
||
|
|
||
| const router = Router() | ||
|
|
||
| router.route("/signup").post(validate(signupSchema), signup); | ||
| router.route("/signin").post(validate(signinSchema),signin); | ||
| router.route("/refresh-token").post(refresh); | ||
| router.route("/logout").post(requireAuth,logout); | ||
| router.route("/signup").post(authLimiter,validate(signupSchema), signup); | ||
| router.route("/signin").post(authLimiter,validate(signinSchema),signin); | ||
| router.route("/refresh-token").post(authLimiter,refresh); | ||
| router.route("/logout").post(logout); | ||
| router.route("/logoutall").post(requireAuth,logoutall); | ||
| router.route("/me") | ||
| .get(requireAuth, getMe) | ||
| .put(requireAuth, validate(updateMeSchema), updateMe) | ||
| .patch(requireAuth, validate(patchMeSchema), patchMe) | ||
| .delete(requireAuth, validate(deleteMeSchema), deleteMe); | ||
|
|
||
| export default router; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import axiosInstance from "./axios.config"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import type { Signin, Signup , AuthResponse } from "@devdraw/shared"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { type Signin, type Signup , type AuthResponse,type User } from "@devdraw/shared"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const authApi = { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -14,7 +14,18 @@ const authApi = { | |||||||||||||||||||||||||||||||||||||||||||||
| withCredentials:true | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| refresh: async () : Promise<{ accessToken:string }> => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance.post("/api/v1/auth/refresh-token"); | ||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| me: async (accessToken:string) : Promise<{user:User}> => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const response = await axiosInstance.get("/api/v1/auth/me",{ | ||||||||||||||||||||||||||||||||||||||||||||||
| headers:{Authorization:`Bearer ${accessToken}`}, | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| logout: async () => axiosInstance.post("/api/v1/auth/logout"), | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value Add response generics so the declared return types are checked.
♻️ Proposed typing refresh: async () : Promise<{ accessToken:string }> => {
- const response = await axiosInstance.post("/api/v1/auth/refresh-token");
+ const response = await axiosInstance.post<{ accessToken: string }>("/api/v1/auth/refresh-token");
return response.data;
},
me: async (accessToken:string) : Promise<{user:User}> => {
- const response = await axiosInstance.get("/api/v1/auth/me",{
+ const response = await axiosInstance.get<{ user: User }>("/api/v1/auth/me",{
headers:{Authorization:`Bearer ${accessToken}`},
});
return response.data;
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export default authApi; | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,4 +9,61 @@ const axiosInstance = axios.create({ | |||||||||||||||||||||||||||||
| withCredentials: true, | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let isRefreshing = false; | ||||||||||||||||||||||||||||||
| let failedQueue: any[] = []; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const processQueue = (error: any, token: string | null = null) => { | ||||||||||||||||||||||||||||||
| failedQueue.forEach((prom) => { | ||||||||||||||||||||||||||||||
| if (error) prom.reject(error); | ||||||||||||||||||||||||||||||
| else prom.resolve(token); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| failedQueue = []; | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| axiosInstance.interceptors.response.use( | ||||||||||||||||||||||||||||||
| (response) => response, | ||||||||||||||||||||||||||||||
| async (error) => { | ||||||||||||||||||||||||||||||
| const originalRequest = error.config; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Don't intercept if no response or not 401 | ||||||||||||||||||||||||||||||
| if (!error.response || error.response.status !== 401) { | ||||||||||||||||||||||||||||||
| return Promise.reject(error); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // ✅ CRITICAL: Skip refresh requests to prevent infinite loop | ||||||||||||||||||||||||||||||
| if (originalRequest.url?.includes('/refresh')) { | ||||||||||||||||||||||||||||||
| return Promise.reject(error); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Already retried once | ||||||||||||||||||||||||||||||
| if (originalRequest._retry) { | ||||||||||||||||||||||||||||||
| return Promise.reject(error); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Queue subsequent requests while refreshing | ||||||||||||||||||||||||||||||
| if (isRefreshing) { | ||||||||||||||||||||||||||||||
| return new Promise((resolve, reject) => { | ||||||||||||||||||||||||||||||
| failedQueue.push({ resolve, reject }); | ||||||||||||||||||||||||||||||
| }).then(() => axiosInstance(originalRequest)); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Queued retries drop the new token and can start extra refresh cycles. Two defects in the queue branch:
Apply the resolved token to the retried request, and mark the request as retried. 🐛 Proposed fix // Queue subsequent requests while refreshing
if (isRefreshing) {
+ originalRequest._retry = true;
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
- }).then(() => axiosInstance(originalRequest));
+ }).then((token) => {
+ originalRequest.headers.Authorization = `Bearer ${token}`;
+ return axiosInstance(originalRequest);
+ });
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| originalRequest._retry = true; | ||||||||||||||||||||||||||||||
| isRefreshing = true; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| const { data } = await axiosInstance.post("/api/v1/auth/refresh-token"); | ||||||||||||||||||||||||||||||
| processQueue(null, data.accessToken); | ||||||||||||||||||||||||||||||
| originalRequest.headers.Authorization = `Bearer ${data.accessToken}`; | ||||||||||||||||||||||||||||||
| return axiosInstance(originalRequest); | ||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||
| processQueue(err); | ||||||||||||||||||||||||||||||
| // Optionally redirect to login | ||||||||||||||||||||||||||||||
| // window.location.href = '/auth'; | ||||||||||||||||||||||||||||||
| return Promise.reject(err); | ||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||
| isRefreshing = false; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift The refreshed access token never reaches the auth context, and a failed refresh leaves the session in place. The interceptor keeps the new When the refresh fails, the code rejects and the redirect stays commented out (Line 61). Expose a callback that the auth provider registers with this module. Call it with the new token on success, and call 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export default axiosInstance; | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,9 @@ | ||||||||||||||||||||||||||
| import { Navigate } from "react-router-dom"; | ||||||||||||||||||||||||||
| import { useAuth } from "../context/auth.context"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { | ||||||||||||||||||||||||||
| const { user, loading } = useAuth(); | ||||||||||||||||||||||||||
| if (loading) return null; | ||||||||||||||||||||||||||
| if (!user) return <Navigate to="/" replace />; | ||||||||||||||||||||||||||
|
Comment on lines
+6
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win Render a fallback while loading, and preserve the attempted location on redirect.
The redirect also discards the target route. After sign-in the user lands on the default page instead of the page they requested. Pass the current location in the navigation state. ♻️ Proposed change-import { Navigate } from "react-router-dom";
+import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "../context/auth.context";
export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const { user, loading } = useAuth();
- if (loading) return null;
- if (!user) return <Navigate to="/" replace />;
+ const location = useLocation();
+ if (loading) return <div role="status" aria-live="polite">Loading…</div>;
+ if (!user) return <Navigate to="/" replace state={{ from: location }} />;
return <>{children}</>;
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| return <>{children}</>; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,12 @@ | ||
| import { createContext,useContext,useState, type ReactNode } from "react"; | ||
| import { createContext,useContext,useEffect,useState, type ReactNode } from "react"; | ||
| import type { User } from "@devdraw/shared"; | ||
| import authApi from "../api/auth.api"; | ||
| import { useNavigate, useLocation } from "react-router-dom"; | ||
|
|
||
| type AuthCtx = { | ||
| user: User | null; | ||
| accessToken: string | null; | ||
| loading:boolean; | ||
| setAuth: (user: User, token: string) => void; | ||
| clearAuth: () => void; | ||
| }; | ||
|
|
@@ -13,14 +16,29 @@ const AuthContext = createContext<AuthCtx>(null!); | |
| export const AuthProvider = ({ children }: { children: ReactNode }) => { | ||
| const [user, setUser] = useState<User | null>(null); | ||
| const [accessToken, setAccessToken] = useState<string | null>(null); | ||
| const [loading, setLoading] = useState(true); | ||
| const navigate = useNavigate(); | ||
| const location = useLocation(); | ||
|
|
||
| const setAuth = (u: User, t: string) => { | ||
| setUser(u); | ||
| setAccessToken(t); | ||
| }; | ||
| const clearAuth = () => { setUser(null); setAccessToken(null); }; | ||
|
|
||
| useEffect(() => { | ||
| authApi.refresh() | ||
| .then(async ({ accessToken }) => { | ||
| const { user } = await authApi.me(accessToken); | ||
| setAuth(user, accessToken); | ||
| if (location.pathname === "/") navigate("/home"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value Move the post-restore redirect out of the provider. The provider performs routing. This couples authentication state to a specific route layout and makes the provider hard to reuse or test. 🤖 Prompt for AI Agents |
||
| }) | ||
| .catch((err) => console.error("Session restore failed:", err)) | ||
| .finally(() => setLoading(false)); | ||
| }, []); | ||
|
Comment on lines
+29
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Inspect the refresh controller for token rotation.
rg -n -C 15 'export const refresh' --glob '*.ts'Repository: ArpanMondalGITHUB/Devdraw Length of output: 163 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '== candidate authentication files =='
git ls-files | grep -Ei '(^|/)(auth|authentication|session|user)([^/]*\.(ts|tsx|js|jsx))?$|auth\.context\.tsx|refresh' | head -200 || true
printf '%s\n' '== refresh references =='
rg -n -C 8 'authApi\.refresh|refresh\s*\(|refreshToken|refresh-token|refresh_token' --glob '*.{ts,tsx,js,jsx}' . | head -400 || true
printf '%s\n' '== auth context =='
fd -i 'auth.context.tsx' . -x sh -c 'echo "--- $1"; wc -l "$1"; cat -n "$1"' sh {} \;Repository: ArpanMondalGITHUB/Devdraw Length of output: 15286 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '== refresh controller and service =='
cat -n apps/api/src/controllers/auth.controllers.ts | sed -n '1,90p'
cat -n apps/api/src/services/auth.services.ts | sed -n '1,80p'
printf '%s\n' '== refresh-token persistence helpers =='
rg -n -C 10 'findRefreshToken|deleteRefreshToken|createRefreshToken' --glob '*.{ts,tsx,js,jsx}' .
printf '%s\n' '== lint and hooks configuration =='
rg -n -C 8 'react-hooks|exhaustive-deps|eslint' --glob '*.{json,js,cjs,mjs,ts,yml,yaml}' . | head -300 || true
printf '%s\n' '== StrictMode and AuthProvider mounting =='
rg -n -C 10 'StrictMode|AuthProvider|createRoot|BrowserRouter' --glob '*.{tsx,ts,jsx,js}' apps packages . | head -300 || trueRepository: ArpanMondalGITHUB/Devdraw Length of output: 21085 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
controller = Path("apps/api/src/controllers/auth.controllers.ts").read_text()
queries = Path("packages/db/queries.ts").read_text()
context = Path("apps/web/src/context/auth.context.tsx").read_text()
main = Path("apps/web/src/main.tsx").read_text()
eslint = Path("apps/web/eslint.config.js").read_text()
refresh = re.search(
r"export const refresh\s*=\s*async[\s\S]*?(?=\nexport const |\Z)",
controller,
)
if not refresh:
raise SystemExit("refresh controller not found")
refresh_body = refresh.group(0)
print("refresh_controller_calls_find:", "findRefreshToken(" in refresh_body)
print("refresh_controller_calls_delete:", "deleteRefreshToken(" in refresh_body)
print("refresh_controller_calls_create:", "createRefreshToken(" in refresh_body)
print("refresh_controller_calls_issue_tokens:", "issueTokens(" in refresh_body)
print("find_refresh_token_is_select:", bool(re.search(
r"export const findRefreshToken[\s\S]*?SELECT \* FROM [\"']RefreshToken[\"']",
queries,
)))
print("strict_mode_in_main:", bool(re.search(r"<StrictMode\b|StrictMode>", main)))
print("effect_has_cleanup_return:", bool(re.search(
r"useEffect\(\(\) => \{[\s\S]*?return\s*\(\s*\)\s*=>",
context,
)))
effect = re.search(r"useEffect\(\(\) => \{[\s\S]*?\n \}, \[\]\);", context)
if not effect:
raise SystemExit("target effect not found")
effect_text = effect.group(0)
print("effect_empty_dependency_array:", "}, []);" in effect_text)
print("effect_reads_location:", "location.pathname" in effect_text)
print("effect_calls_navigate:", "navigate(" in effect_text)
print("hooks_recommended_configured:", "reactHooks.configs.flat.recommended" in eslint)
PYRepository: ArpanMondalGITHUB/Devdraw Length of output: 544 Guard session restoration against unmounts and fix effect dependencies The refresh endpoint does not rotate or consume the refresh token, and 🤖 Prompt for AI Agents |
||
|
|
||
| return ( | ||
| <AuthContext.Provider value={{ | ||
| user, | ||
| accessToken, | ||
| setAuth: (u, t) => { setUser(u); setAccessToken(t); }, | ||
| clearAuth: () => { setUser(null); setAccessToken(null); }, | ||
| }}> | ||
| <AuthContext.Provider value={{ user, accessToken, loading, setAuth, clearAuth }}> | ||
| {children} | ||
| </AuthContext.Provider> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,21 @@ | ||||||||||||||||||||||||||||||||
| import { useNavigate } from "react-router-dom"; | ||||||||||||||||||||||||||||||||
| import authApi from "../../api/auth.api"; | ||||||||||||||||||||||||||||||||
| import { useAuth } from "../../context/auth.context"; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| export const Home = () =>{ | ||||||||||||||||||||||||||||||||
| const {user} = useAuth(); | ||||||||||||||||||||||||||||||||
| const {user,clearAuth} = useAuth(); | ||||||||||||||||||||||||||||||||
| const navigate = useNavigate() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const logout = async() =>{ | ||||||||||||||||||||||||||||||||
| await authApi.logout(); | ||||||||||||||||||||||||||||||||
| clearAuth(); | ||||||||||||||||||||||||||||||||
| navigate("/"); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Clear the local session even if the logout request fails.
Clear the local state in a 🛡️ Proposed fix const logout = async() =>{
- await authApi.logout();
- clearAuth();
- navigate("/");
+ try {
+ await authApi.logout();
+ } catch (err) {
+ console.error("Logout request failed:", err);
+ } finally {
+ clearAuth();
+ navigate("/");
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return( | ||||||||||||||||||||||||||||||||
| <div className="text-2xl text-red-400 ">Welocome{user?.name} | ||||||||||||||||||||||||||||||||
| <div className="text-2xl bg-amber-300 items-center ">Welocome{user?.name} | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Fix the greeting text. "Welocome" is misspelled, and no space separates the greeting from the name. The output currently reads ✏️ Proposed fix- <div className="text-2xl bg-amber-300 items-center ">Welocome{user?.name}
+ <div className="text-2xl bg-amber-300 items-center ">Welcome {user?.name}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| <div className="h-5 w-10 bg-red-400 " onClick={logout}></div> | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Use a The logout action is bound to a ♿ Proposed fix- <div className="h-5 w-10 bg-red-400 " onClick={logout}></div>
+ <button type="button" className="h-5 w-10 bg-red-400" onClick={logout}>
+ Log out
+ </button>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: ArpanMondalGITHUB/Devdraw
Length of output: 1977
🏁 Script executed:
Repository: ArpanMondalGITHUB/Devdraw
Length of output: 28508
🏁 Script executed:
Repository: ArpanMondalGITHUB/Devdraw
Length of output: 300
🏁 Script executed:
Repository: ArpanMondalGITHUB/Devdraw
Length of output: 4394
🌐 Web query:
Bun.password.verify undefined non-string argument behavior TypeError💡 Result:
In Bun, the Bun.password.verify and Bun.password.verifySync functions are designed to receive string or buffer arguments [1][2]. If non-string or non-buffer arguments are passed, the Bun runtime typically handles this by throwing a TypeError or an invalid argument error [3][2]. Specifically, the internal implementation of these methods uses wrappers (such as
BlobOrStringOrBuffer::fromJS) that explicitly validate the input types [3][2]. When these functions encounter an invalid type—such as an undefined, null, or boxed String object—they are configured to reject the input [3]. Recent updates to the Bun runtime have explicitly improved the handling of these inputs to throw consistent errors, similar to the behavior of Node.js's crypto module (e.g., ERR_INVALID_ARG_TYPE or an explicit "expected string or buffer" error message) [3]. To avoid this error, ensure that both the password and the hash arguments are passed as valid primitive strings or Buffer objects [1][2]. If you are retrieving these values from an external source, verify that they are not undefined before passing them to the verify function [2].Citations:
Apply
deleteMeSchematoDELETE /me.deleteMeSchemarequires a non-emptypassword, but the route does not use it. Addvalidate(deleteMeSchema)to the route so invalid requests receive a 400 response before password verification.The
"RefreshToken"."userId"foreign key usesON DELETE CASCADE, so deleting the user also deletes the user's refresh tokens.🤖 Prompt for AI Agents