Skip to content
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

try and fix this weird login bug: #59

Closed
wants to merge 18 commits into from
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@types/validator": "^13.12.2",
"axios": "^1.7.9",
"cookies-next": "^5.0.2",
"next": "13.4.16",
"next": "14.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.54.2",
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/api/getCurrentUser/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import axios, { AxiosError } from 'axios';
* @param token Authorization bearer token
* @returns User's profile
*/
const getCurrentUser = async (token: string): Promise<GetCurrentUserResponse> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.user.user}`;
export const getCurrentUser = async (token: string): Promise<GetCurrentUserResponse> => {
const requestUrl = `${config.api.baseApiUrl}${config.api.endpoints.user.user}`;
const response = await axios.get<GetCurrentUserResponse>(requestUrl, {
headers: {
Authorization: `Bearer ${token}`,
Expand Down
28 changes: 22 additions & 6 deletions client/src/app/api/login/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import config from '@/lib/config';
import { setCookie } from '@/lib/services/CookieService';
import { serializeCookie } from '@/lib/services/CookieService';
import { LoginRequest } from '@/lib/types/apiRequests';
import { LoginResponse } from '@/lib/types/apiResponses';
import { CookieType } from '@/lib/types/enums';
import { getErrorMessage, getMessagesFromError } from '@/lib/utils';
import { getErrorMessage } from '@/lib/utils';
import axios, { AxiosError } from 'axios';
import { NextRequest, NextResponse } from 'next/server';

const login = async (email: string, password: string): Promise<LoginResponse> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.auth.login}`;
const requestUrl = `${config.api.baseApiUrl}${config.api.endpoints.auth.login}`;
const requestBody: LoginRequest = { email, password };
const response = await axios.post<LoginResponse>(requestUrl, requestBody);
return response.data;
Expand All @@ -18,11 +18,27 @@ export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { email, password } = body;
const response = await login(email, password);
await setCookie(CookieType.ACCESS_TOKEN, response.token);
await setCookie(CookieType.USER, JSON.stringify(response.user));
const loginResponse = await login(email, password);

const url = new URL('/', request.url);
const response = NextResponse.json(loginResponse);

response.cookies.set(CookieType.ACCESS_TOKEN, loginResponse.token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
});
response.cookies.set(CookieType.USER, JSON.stringify(loginResponse.user), {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
});

return response;
} catch (error) {
console.error(error);
if (error instanceof AxiosError) {
return NextResponse.json({ error: getErrorMessage(error) }, { status: error.status || 500 });
}
Expand Down
2 changes: 1 addition & 1 deletion client/src/app/api/updateUser/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const updateCurrentUserProfile = async (
token: string,
user: UserPatches
): Promise<PatchUserResponse> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.user.user}`;
const requestUrl = `${config.api.baseApiUrl}${config.api.endpoints.user.user}`;

const requestBody: PatchUserRequest = { user };

Expand Down
10 changes: 7 additions & 3 deletions client/src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Link from 'next/link';
import Alert from '@/components/Alert';
import { useForm, SubmitHandler } from 'react-hook-form';
import { UserAPI } from '@/lib/api';
import { useRouter } from 'next/navigation';
import { redirect, useRouter } from 'next/navigation';
import { useState } from 'react';
import { getErrorMessage } from '@/lib/utils';

Expand All @@ -20,6 +20,7 @@ interface LoginValues {
}

export default function LoginPage() {
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | undefined>(undefined);
const router = useRouter();

Expand All @@ -36,11 +37,14 @@ export default function LoginPage() {

const onSubmit: SubmitHandler<LoginValues> = async credentials => {
try {
setLoading(true);
await UserAPI.login(credentials.email, credentials.password);
router.push('/');
router.replace('/');
router.refresh();
} catch (error) {
setError(getErrorMessage(error));
}
setLoading(false);
};

return (
Expand Down Expand Up @@ -82,7 +86,7 @@ export default function LoginPage() {

{/* <Link href="/forgot-password">Forgot your password?</Link> */}

<Button variant="primary" onClick={handleSubmit(onSubmit)}>
<Button variant="primary" onClick={handleSubmit(onSubmit)} disabled={loading}>
Login
</Button>

Expand Down
16 changes: 14 additions & 2 deletions client/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,30 @@ import { UserAPI } from '@/lib/api';
import { redirect } from 'next/navigation';
import { getCookie } from '@/lib/services/CookieService';
import { CookieType } from '@/lib/types/enums';
import { getCurrentUser } from './api/getCurrentUser/route';
import { getErrorMessage } from '@/lib/utils';

export default async function Home() {
const accessToken = await getCookie(CookieType.ACCESS_TOKEN);
const accessToken = getCookie(CookieType.ACCESS_TOKEN);

if (!accessToken) {
console.error('Missing access token.');
return <main>this is broken</main>;
redirect('/login');
}

try {
const fetchedUser = await UserAPI.getCurrentUser(accessToken);
const response = await getCurrentUser(accessToken);

const fetchedUser = response.user;
return (
<main className={styles.main}>
<Dashboard faq={FAQ_QUESTIONS} timeline={TIMELINE} user={fetchedUser} />
</main>
);
} catch (error) {
console.error(getErrorMessage(error));
return <main>this is broken</main>;
redirect('/login');
}
}
4 changes: 3 additions & 1 deletion client/src/app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { getCookie } from '@/lib/services/CookieService';
import { CookieType } from '@/lib/types/enums';
import Profile from '@/components/Profile';
import { redirect } from 'next/navigation';
import { getCurrentUser } from '../api/getCurrentUser/route';

export default async function ProfilePage() {
const accessToken = await getCookie(CookieType.ACCESS_TOKEN);

try {
const fetchedUser = await UserAPI.getCurrentUser(accessToken);
const response = await getCurrentUser(accessToken);
const fetchedUser = response.user;
return <Profile user={fetchedUser} />;
} catch (error) {
redirect('/login');
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface ButtonProps {
for?: string;
submit?: boolean;
onClick?: () => void;
disabled?: boolean;
className?: string;
}

Expand All @@ -23,13 +24,15 @@ const Button = ({
for: htmlFor,
submit = false,
onClick,
disabled,
className = '',
children,
}: PropsWithChildren<ButtonProps>) => {
const props = {
className: `${styles.button} ${className}`,
'data-variant': variant,
onClick,
disabled,
children,
};
return htmlFor ? (
Expand Down
4 changes: 4 additions & 0 deletions client/src/components/Button/style.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@
border: 1px solid vars.$btn-primary;
color: vars.$btn-primary;
}

&:disabled {
background-color: vars.$card-stroke;
}
}
2 changes: 1 addition & 1 deletion client/src/lib/api/AuthAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import axios from 'axios';
* @returns PrivateProfile containing user information on successful creation
*/
export const register = async (user: UserRegistration): Promise<PrivateProfile> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.auth.register}`;
const requestUrl = `${config.api.baseApiUrl}${config.api.endpoints.auth.register}`;

const response = await axios.post<RegistrationResponse>(requestUrl, { user: user });

Expand Down
8 changes: 6 additions & 2 deletions client/src/lib/api/UserAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import type {
LoginResponse,
} from '@/lib/types/apiResponses';
import axios from 'axios';
import config from '../config';

export const login = async (email: string, password: string): Promise<string> => {
const requestBody: LoginRequest = { email, password };
const response = await axios.post<LoginResponse>('/api/login', requestBody);
const response = await axios.post<LoginResponse>('/api/login', requestBody, {
headers: { 'Cache-Control': 'no-cache' },
});
return response.data.token;
};

Expand All @@ -19,10 +22,11 @@ export const login = async (email: string, password: string): Promise<string> =>
*/
export const getCurrentUser = async (token: string): Promise<PrivateProfile> => {
const response = await axios.get<GetCurrentUserResponse>(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/getCurrentUser`,
`${config.api.baseUrl}/api/getCurrentUser`,
{
headers: {
Authorization: `Bearer ${token}`,
'Cache-Control': 'no-cache',
},
}
);
Expand Down
5 changes: 4 additions & 1 deletion client/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const config = {
api: {
baseUrl: process.env.NEXT_PUBLIC_ACM_API_URL,
baseApiUrl: process.env.NEXT_PUBLIC_ACM_API_URL,
baseUrl: process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: process.env.NEXT_PUBLIC_BASE_URL,
endpoints: {
auth: {
register: '/user',
Expand Down
21 changes: 16 additions & 5 deletions client/src/lib/services/CookieService.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { cookies, headers } from 'next/headers';
import { NextResponse } from 'next/server';

export const getCookie = async (key: string): Promise<string> => {
const cookie = await cookies();
export const getCookie = (key: string): string => {
const cookie = cookies();
return cookie.get(key)?.value as string;
};

export const setCookie = async (key: string, value: string): Promise<void> => {
const cookie = await cookies();
cookie.set(key, value);
export const serializeCookie = (key: string, value: string): string => {
const cookieOptions = {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
};

return `${key}=${value}; ${Object.entries(cookieOptions)
.map(([optionKey, optionValue]) =>
optionValue === true ? optionKey : `${optionKey}=${optionValue}`
)
.join('; ')}`;
};
3 changes: 2 additions & 1 deletion client/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { CookieType } from './lib/types/enums';

export function middleware(request: NextRequest) {
const userCookie = request.cookies.get(CookieType.USER);
const tokenCookie = request.cookies.get(CookieType.ACCESS_TOKEN);

if (!userCookie) {
if (!userCookie || !tokenCookie) {
return NextResponse.redirect(new URL('/login', request.url));
}

Expand Down
Loading
Loading