From 92d562baedb5fbdd6768a3435822805d1101ad2a Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Thu, 15 Feb 2024 02:10:33 +0600 Subject: [PATCH] fix: set cookie's `SameSite` and `Secure` --- Caddyfile | 11 +++++++++ .../AuthenticationFlow/EmailLoginForm.tsx | 8 ++----- .../AuthenticationFlow/GitHubButton.tsx | 8 ++----- .../AuthenticationFlow/GoogleButton.tsx | 8 ++----- .../AuthenticationFlow/LinkedInButton.tsx | 8 ++----- .../AuthenticationFlow/ResetPasswordForm.tsx | 10 +++----- .../TriggerVerifyAccount.tsx | 8 ++----- src/components/Navigation/navigation.ts | 7 ++---- .../UpdateProfile/UploadProfilePicture.tsx | 4 ++-- src/lib/http.ts | 24 +++++++++---------- src/lib/jwt.ts | 17 +++++++++++++ 11 files changed, 57 insertions(+), 56 deletions(-) create mode 100644 Caddyfile diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 000000000..5b2a3745f --- /dev/null +++ b/Caddyfile @@ -0,0 +1,11 @@ +api.roadmap.localhost { + reverse_proxy localhost:8080 +} + +roadmap.localhost { + reverse_proxy localhost:3000 +} + +draw.roadmap.localhost { + reverse_proxy localhost:4321 +} diff --git a/src/components/AuthenticationFlow/EmailLoginForm.tsx b/src/components/AuthenticationFlow/EmailLoginForm.tsx index 07304f00e..060ddc13e 100644 --- a/src/components/AuthenticationFlow/EmailLoginForm.tsx +++ b/src/components/AuthenticationFlow/EmailLoginForm.tsx @@ -2,7 +2,7 @@ import Cookies from 'js-cookie'; import type { FormEvent } from 'react'; import { useState } from 'react'; import { httpPost } from '../../lib/http'; -import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt'; type EmailLoginFormProps = { isDisabled?: boolean; @@ -34,11 +34,7 @@ export function EmailLoginForm(props: EmailLoginFormProps) { // Log the user in and reload the page if (response?.token) { - Cookies.set(TOKEN_COOKIE_NAME, response.token, { - path: '/', - expires: 30, - domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', - }); + setAuthToken(response.token); window.location.reload(); return; diff --git a/src/components/AuthenticationFlow/GitHubButton.tsx b/src/components/AuthenticationFlow/GitHubButton.tsx index 3ceaa7892..3ebc3a628 100644 --- a/src/components/AuthenticationFlow/GitHubButton.tsx +++ b/src/components/AuthenticationFlow/GitHubButton.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx'; import Cookies from 'js-cookie'; -import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt'; import { httpGet } from '../../lib/http'; import { Spinner } from '../ReactIcons/Spinner.tsx'; @@ -70,11 +70,7 @@ export function GitHubButton(props: GitHubButtonProps) { localStorage.removeItem(GITHUB_REDIRECT_AT); localStorage.removeItem(GITHUB_LAST_PAGE); - Cookies.set(TOKEN_COOKIE_NAME, response.token, { - path: '/', - expires: 30, - domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', - }); + setAuthToken(response.token); window.location.href = redirectUrl; }) .catch((err) => { diff --git a/src/components/AuthenticationFlow/GoogleButton.tsx b/src/components/AuthenticationFlow/GoogleButton.tsx index 4ccc917ac..3d92b1bde 100644 --- a/src/components/AuthenticationFlow/GoogleButton.tsx +++ b/src/components/AuthenticationFlow/GoogleButton.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import Cookies from 'js-cookie'; -import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt'; import { httpGet } from '../../lib/http'; import { Spinner } from '../ReactIcons/Spinner.tsx'; import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx'; @@ -69,11 +69,7 @@ export function GoogleButton(props: GoogleButtonProps) { localStorage.removeItem(GOOGLE_REDIRECT_AT); localStorage.removeItem(GOOGLE_LAST_PAGE); - Cookies.set(TOKEN_COOKIE_NAME, response.token, { - path: '/', - expires: 30, - domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', - }); + setAuthToken(response.token); window.location.href = redirectUrl; }) .catch((err) => { diff --git a/src/components/AuthenticationFlow/LinkedInButton.tsx b/src/components/AuthenticationFlow/LinkedInButton.tsx index e48481a86..851b2a9d8 100644 --- a/src/components/AuthenticationFlow/LinkedInButton.tsx +++ b/src/components/AuthenticationFlow/LinkedInButton.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import Cookies from 'js-cookie'; -import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt'; import { httpGet } from '../../lib/http'; import { Spinner } from '../ReactIcons/Spinner.tsx'; import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx'; @@ -69,11 +69,7 @@ export function LinkedInButton(props: LinkedInButtonProps) { localStorage.removeItem(LINKEDIN_REDIRECT_AT); localStorage.removeItem(LINKEDIN_LAST_PAGE); - Cookies.set(TOKEN_COOKIE_NAME, response.token, { - path: '/', - expires: 30, - domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', - }); + setAuthToken(response.token); window.location.href = redirectUrl; }) .catch((err) => { diff --git a/src/components/AuthenticationFlow/ResetPasswordForm.tsx b/src/components/AuthenticationFlow/ResetPasswordForm.tsx index 46dd404ce..eaf378388 100644 --- a/src/components/AuthenticationFlow/ResetPasswordForm.tsx +++ b/src/components/AuthenticationFlow/ResetPasswordForm.tsx @@ -1,7 +1,7 @@ import { type FormEvent, useEffect, useState } from 'react'; import { httpPost } from '../../lib/http'; import Cookies from 'js-cookie'; -import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt'; export function ResetPasswordForm() { const [code, setCode] = useState(''); @@ -37,7 +37,7 @@ export function ResetPasswordForm() { newPassword: password, confirmPassword: passwordConfirm, code, - } + }, ); if (error?.message) { @@ -53,11 +53,7 @@ export function ResetPasswordForm() { } const token = response.token; - Cookies.set(TOKEN_COOKIE_NAME, token, { - path: '/', - expires: 30, - domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', - }); + setAuthToken(response.token); window.location.href = '/'; }; diff --git a/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx b/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx index e46342a11..c9442ab52 100644 --- a/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx +++ b/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import Cookies from 'js-cookie'; import { httpPost } from '../../lib/http'; -import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt'; import { Spinner } from '../ReactIcons/Spinner'; import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2'; @@ -26,11 +26,7 @@ export function TriggerVerifyAccount() { return; } - Cookies.set(TOKEN_COOKIE_NAME, response.token, { - path: '/', - expires: 30, - domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', - }); + setAuthToken(response.token); window.location.href = '/'; }) .catch((err) => { diff --git a/src/components/Navigation/navigation.ts b/src/components/Navigation/navigation.ts index 5a725f495..04de8a5db 100644 --- a/src/components/Navigation/navigation.ts +++ b/src/components/Navigation/navigation.ts @@ -1,11 +1,8 @@ import Cookies from 'js-cookie'; -import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { TOKEN_COOKIE_NAME, removeAuthToken } from '../../lib/jwt'; export function logout() { - Cookies.remove(TOKEN_COOKIE_NAME, { - path: '/', - domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', - }); + removeAuthToken(); // Reloading will automatically redirect the user if required window.location.reload(); diff --git a/src/components/UpdateProfile/UploadProfilePicture.tsx b/src/components/UpdateProfile/UploadProfilePicture.tsx index aa61aa3e7..b8a91e34e 100644 --- a/src/components/UpdateProfile/UploadProfilePicture.tsx +++ b/src/components/UpdateProfile/UploadProfilePicture.tsx @@ -1,6 +1,6 @@ import Cookies from 'js-cookie'; import { type ChangeEvent, type FormEvent, useEffect, useRef, useState } from 'react'; -import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { TOKEN_COOKIE_NAME, removeAuthToken } from '../../lib/jwt'; interface PreviewFile extends File { preview: string; @@ -128,7 +128,7 @@ export default function UploadProfilePicture(props: UploadProfilePictureProps) { // Logout user if token is invalid if (data.status === 401) { - Cookies.remove(TOKEN_COOKIE_NAME); + removeAuthToken(); window.location.reload(); } }; diff --git a/src/lib/http.ts b/src/lib/http.ts index f0d7139c2..7f1692b27 100644 --- a/src/lib/http.ts +++ b/src/lib/http.ts @@ -1,6 +1,6 @@ import Cookies from 'js-cookie'; import fp from '@fingerprintjs/fingerprintjs'; -import { TOKEN_COOKIE_NAME } from './jwt'; +import { TOKEN_COOKIE_NAME, removeAuthToken } from './jwt'; type HttpOptionsType = RequestInit | { headers: Record }; @@ -30,10 +30,10 @@ type ApiReturn = { */ export async function httpCall< ResponseType = AppResponse, - ErrorType = AppError + ErrorType = AppError, >( url: string, - options?: HttpOptionsType + options?: HttpOptionsType, ): Promise> { try { const fingerprintPromise = await fp.load({ monitoring: false }); @@ -65,7 +65,7 @@ export async function httpCall< // Logout user if token is invalid if (data.status === 401) { - Cookies.remove(TOKEN_COOKIE_NAME); + removeAuthToken(); window.location.reload(); return { response: undefined, error: data as ErrorType }; } @@ -92,11 +92,11 @@ export async function httpCall< export async function httpPost< ResponseType = AppResponse, - ErrorType = AppError + ErrorType = AppError, >( url: string, body: Record, - options?: HttpOptionsType + options?: HttpOptionsType, ): Promise> { return httpCall(url, { ...options, @@ -108,7 +108,7 @@ export async function httpPost< export async function httpGet( url: string, queryParams?: Record, - options?: HttpOptionsType + options?: HttpOptionsType, ): Promise> { const searchParams = new URLSearchParams(queryParams).toString(); const queryUrl = searchParams ? `${url}?${searchParams}` : url; @@ -122,11 +122,11 @@ export async function httpGet( export async function httpPatch< ResponseType = AppResponse, - ErrorType = AppError + ErrorType = AppError, >( url: string, body: Record, - options?: HttpOptionsType + options?: HttpOptionsType, ): Promise> { return httpCall(url, { ...options, @@ -138,7 +138,7 @@ export async function httpPatch< export async function httpPut( url: string, body: Record, - options?: HttpOptionsType + options?: HttpOptionsType, ): Promise> { return httpCall(url, { ...options, @@ -149,10 +149,10 @@ export async function httpPut( export async function httpDelete< ResponseType = AppResponse, - ErrorType = AppError + ErrorType = AppError, >( url: string, - options?: HttpOptionsType + options?: HttpOptionsType, ): Promise> { return httpCall(url, { ...options, diff --git a/src/lib/jwt.ts b/src/lib/jwt.ts index 430774550..911c46312 100644 --- a/src/lib/jwt.ts +++ b/src/lib/jwt.ts @@ -31,3 +31,20 @@ export function getUser() { return decodeToken(token); } + +export function setAuthToken(token: string) { + Cookies.set(TOKEN_COOKIE_NAME, token, { + path: '/', + expires: 30, + sameSite: 'lax', + secure: true, + domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', + }); +} + +export function removeAuthToken() { + Cookies.remove(TOKEN_COOKIE_NAME, { + path: '/', + domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', + }); +}