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

even better user cookies #126

Merged
merged 3 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/components/events/EventModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CommunityLogo, Modal, Typography } from '@/components/common';
import CalendarButtons from '@/components/events/CalendarButtons';
import PointsDisplay from '@/components/events/PointsDisplay';
import { PublicEvent } from '@/lib/types/apiResponses';
import { formatEventDate } from '@/lib/utils';
import { fixUrl, formatEventDate } from '@/lib/utils';
import LinkIcon from '@/public/assets/icons/link.svg';
import Image from 'next/image';
import Link from 'next/link';
Expand All @@ -19,10 +19,7 @@ const EventModal = ({ open, attended, event, onClose }: EventModalProps) => {
const { cover, title, start, end, location, description, eventLink } = event;

const displayCover = cover || '/assets/graphics/store/hero-photo.jpg';
const formattedEventLink = eventLink?.includes('https://') ? eventLink : `https://${eventLink}`;
const displayEventLink = eventLink
? formattedEventLink
: `https://acmucsd.com/events/${event.uuid}`;
const displayEventLink = fixUrl(eventLink) || `https://acmucsd.com/events/${event.uuid}`;

return (
<Modal open={open} onClose={onClose}>
Expand Down
23 changes: 22 additions & 1 deletion src/lib/api/UserAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { config } from '@/lib';
import { CookieService } from '@/lib/services';
import type { UUID } from '@/lib/types';
import {
InsertUserSocialMediaRequest,
Expand All @@ -21,7 +22,9 @@ import type {
UpdateProfilePictureResponse,
UpdateSocialMediaResponse,
} from '@/lib/types/apiResponses';
import { CookieType } from '@/lib/types/enums';
import axios from 'axios';
import { OptionsType } from 'cookies-next/lib/types';

/**
* Get current user's private profile
Expand All @@ -30,7 +33,6 @@ import axios from 'axios';
*/
export const getCurrentUser = async (token: string): Promise<PrivateProfile> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.user.user}`;

const response = await axios.get<GetCurrentUserResponse>(requestUrl, {
headers: {
Authorization: `Bearer ${token}`,
Expand All @@ -40,6 +42,25 @@ export const getCurrentUser = async (token: string): Promise<PrivateProfile> =>
return response.data.user;
};

export const getCurrentUserAndRefreshCookie = async (
token: string,
options: OptionsType
): Promise<PrivateProfile> => {
const userCookie = CookieService.getServerCookie(CookieType.USER, options);
if (userCookie) return JSON.parse(userCookie);

const user: PrivateProfile = await getCurrentUser(token);

const { req, res } = options;
CookieService.setServerCookie(CookieType.USER, JSON.stringify(user), {
req,
res,
maxAge: 5 * 60,
});

return user;
};

/**
* Get specified user's public profile
* @param token Authorization bearer token
Expand Down
6 changes: 2 additions & 4 deletions src/lib/hoc/withAccessType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function withAccessType(
// Generate a new getServerSideProps function by taking the return value of the original function and appending the user prop onto it if the user cookie exists, otherwise force user to login page
const modified: GetServerSideProps = async (context: GetServerSidePropsContext) => {
const { req, res } = context;
let userCookie = CookieService.getServerCookie(CookieType.USER, { req, res });
const userCookie = CookieService.getServerCookie(CookieType.USER, { req, res });
const authTokenCookie = CookieService.getServerCookie(CookieType.ACCESS_TOKEN, { req, res });

const { homeRoute, loginRoute } = config;
Expand Down Expand Up @@ -65,10 +65,8 @@ export default function withAccessType(
}

if (!user || !userAccessLevel) {
user = await UserAPI.getCurrentUser(authTokenCookie);
user = await UserAPI.getCurrentUserAndRefreshCookie(authTokenCookie, { req, res });
userAccessLevel = user.accessType;
userCookie = JSON.stringify(user);
CookieService.setServerCookie(CookieType.USER, JSON.stringify(userCookie), { req, res });
}

// This block should be impossible to hit assuming the portal API doesn't go down
Expand Down
28 changes: 0 additions & 28 deletions src/lib/managers/AccountManager.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/managers/AuthManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const login = async (data: LoginRequest & APIHandlerProps): Promise<void>
});

const user = await UserAPI.getCurrentUser(token);
CookieService.setClientCookie(CookieType.USER, JSON.stringify(user));
CookieService.setClientCookie(CookieType.USER, JSON.stringify(user), { maxAge: 5 * 60 });

onSuccessCallback?.(user);
} catch (e: any) {
Expand Down
1 change: 0 additions & 1 deletion src/lib/managers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * as AccountManager from './AccountManager';
export * as AdminEventManager from './AdminEventManager';
export * as AuthManager from './AuthManager';
export * as EventManager from './EventManager';
17 changes: 16 additions & 1 deletion src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,19 @@ export const getDefaultMerchItemPhoto = (item: PublicMerchItem | undefined): str
* @param url url to be fixed
* @returns url begnning with http://
*/
export const fixUrl = (url: string) => (url.includes('://') ? url : `http://${url}`);
export const fixUrl = (input: string, prefix?: string): string => {
// Return input as-is if it's blank or includes a protocol
if (!input || input.includes('://')) {
return input;
}
// Encourage https://
if (prefix && input.startsWith('http://')) {
return input.replace('http', 'https');
}
// If the user typed in their username
if (prefix && /^[\w.-]+(?<!\.com)\/?$/.test(input)) {
return `https://${prefix}/${input}`;
}
// Add https:// if it was left out
return `https://${input}`;
};
2 changes: 1 addition & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ const getServerSidePropsFunc: GetServerSideProps = async ({ req, res, query }) =
// After that, fetch the other API calls.
const eventsPromise = EventAPI.getAllEvents();
const attendancesPromise = UserAPI.getAttendancesForCurrentUser(authToken);
const userPromise = UserAPI.getCurrentUser(authToken);
const userPromise = UserAPI.getCurrentUserAndRefreshCookie(authToken, { req, res });

const [events, attendances, user] = await Promise.all([
eventsPromise,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default UserProfilePage;

const getServerSidePropsFunc: GetServerSideProps = async ({ req, res }) => {
const token = CookieService.getServerCookie(CookieType.ACCESS_TOKEN, { req, res });
const user = await UserAPI.getCurrentUser(token);
const user = await UserAPI.getCurrentUserAndRefreshCookie(token, { req, res });

return {
redirect: {
Expand Down
23 changes: 3 additions & 20 deletions src/pages/profile/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import withAccessType from '@/lib/hoc/withAccessType';
import { CookieService, PermissionService } from '@/lib/services';
import { PrivateProfile } from '@/lib/types/apiResponses';
import { CookieType, SocialMediaType } from '@/lib/types/enums';
import { capitalize, getMessagesFromError, getProfilePicture } from '@/lib/utils';
import { capitalize, fixUrl, getMessagesFromError, getProfilePicture } from '@/lib/utils';
import DownloadIcon from '@/public/assets/icons/download-icon.svg';
import DropdownIcon from '@/public/assets/icons/dropdown-arrow-1.svg';
import styles from '@/styles/pages/profile/edit.module.scss';
Expand All @@ -34,23 +34,6 @@ function reportError(title: string, error: unknown) {
}
}

function fixUrl(input: string, prefix?: string): string {
// Return input as-is if it's blank or includes a protocol
if (!input || input.includes('://')) {
return input;
}
// Encourage https://
if (prefix && input.startsWith('http://')) {
return input.replace('http', 'https');
}
// If the user typed in their username
if (prefix && /^[\w.-]+(?<!\.com)\/?$/.test(input)) {
return `https://${prefix}/${input}`;
}
// Add https:// if it was left out
return `https://${input}`;
}

interface EditProfileProps {
user: PrivateProfile;
authToken: string;
Expand All @@ -61,7 +44,7 @@ const EditProfilePage = ({ user: initUser, authToken }: EditProfileProps) => {

useEffect(() => {
if (user !== initUser) {
CookieService.setClientCookie(CookieType.USER, JSON.stringify(user));
CookieService.setClientCookie(CookieType.USER, JSON.stringify(user), { maxAge: 5 * 60 });
}
}, [user, initUser]);

Expand Down Expand Up @@ -593,7 +576,7 @@ export default EditProfilePage;
const getServerSidePropsFunc: GetServerSideProps<EditProfileProps> = async ({ req, res }) => {
const AUTH_TOKEN = CookieService.getServerCookie(CookieType.ACCESS_TOKEN, { req, res });
// Ensure `user` is up-to-date
const user = await UserAPI.getCurrentUser(AUTH_TOKEN);
const user = await UserAPI.getCurrentUserAndRefreshCookie(AUTH_TOKEN, { req, res });

return { props: { authToken: AUTH_TOKEN, user } };
};
Expand Down
4 changes: 1 addition & 3 deletions src/pages/u/[handle].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { config } from '@/lib';
import { UserAPI } from '@/lib/api';
import withAccessType from '@/lib/hoc/withAccessType';
import { CookieService, PermissionService } from '@/lib/services';
import { setServerCookie } from '@/lib/services/CookieService';
import { CookieType } from '@/lib/types/enums';
import type { GetServerSideProps } from 'next/types';

Expand All @@ -35,10 +34,9 @@ const getServerSidePropsFunc: GetServerSideProps = async ({ params, req, res })
try {
const [handleUser, user, signedInAttendances] = await Promise.all([
UserAPI.getUserByHandle(token, handle).catch(() => null),
UserAPI.getCurrentUser(token),
UserAPI.getCurrentUserAndRefreshCookie(token, { req, res }),
UserAPI.getAttendancesForCurrentUser(token),
]);
setServerCookie(CookieType.USER, JSON.stringify(user), { req, res });

// render UserHandleNotFoundPage when user with handle is not retrieved
if (handleUser === null) return { props: { handle } };
Expand Down