diff --git a/README.md b/README.md index a9dd2a5..135cd85 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ```bash SESSION_SECRET= NODE_ENV=development -ALLOWED_ORIGINS=commaseparated,regexes,slashesnotrequired +ALLOWED_ORIGINS=commaseparated,urls,or,regexes DATABASE_URL="postgresql://.../postgres?pgbouncer=true" DIRECT_URL="postgresql://.../postgres" REDIS_PORT=6379 diff --git a/backend/src/index.ts b/backend/src/index.ts index ac877e5..82e76f1 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -35,11 +35,26 @@ declare module "express-session" { } // Initialize client. -if (process.env["REDIS_PORT"] === undefined) { +if ( + process.env["REDIS_PORT"] === undefined || + process.env["REDIS_PORT"] === "" +) { console.log(process.env); console.error("Redis port not provided in .env file"); process.exit(1); } + +let allowed_origins; +if ( + process.env["ALLOWED_ORIGINS"] === undefined || + process.env["ALLOWED_ORIGINS"] === "" +) { + console.log("Warning: ALLOWED_ORIGINS not specified. Using wildcard *."); + allowed_origins = ["*"]; +} else { + allowed_origins = process.env["ALLOWED_ORIGINS"]?.split(","); +} + let redisClient = createClient({ url: `redis://localhost:${process.env["REDIS_PORT"]}`, }); @@ -56,7 +71,12 @@ const app = express(); const SERVER_PORT = 5180; const SALT_ROUNDS = 10; -app.use(cors()); +app.use( + cors({ + origin: allowed_origins, + credentials: true, + }) +); app.use(express.json()); if (process.env["SESSION_SECRET"] === undefined) { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 695a58c..ed7db3c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,6 @@ import "./App.css"; import NavBar from "./NavBar/NavBar"; -import { BrowserRouter, Route, Routes } from "react-router"; +import { BrowserRouter, Navigate, Route, Routes } from "react-router"; import HomePage from "./HomePage/HomePage"; import AboutPage from "./About/About"; import Calendar from "./Calendar/Calendar"; @@ -11,26 +11,82 @@ import { ProfilePage } from "./Settings/SettingsPage/ProfilePage/ProfilePage"; import { EventManagementPage } from "./Settings/SettingsPage/EventManagementPage/EventManagementPage"; import { CreateNewEventPage } from "./Settings/SettingsPage/EventManagementPage/CreateNewEvent/CreateNewEvent"; import { DiscordPage } from "./Settings/SettingsPage/DiscordPage/DiscordPage"; +import { Unauthenticated } from "./Unauthenticated/Unauthenticated"; +import { ProtectedRoute } from "./ProtectedRoute/ProtectedRoute"; +import { useEffect, useState } from "react"; +import { User, UserContext } from "./UserContext/UserContext"; function App() { + const [user, setUser] = useState(null); + + useEffect(() => { + fetch("http://localhost:5180/user", { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }).then((res) => { + if (res.ok) { + res.json().then((data) => { + setUser(data); + }); + } + }); + }, []); + return ( -
- - } /> - } /> - } /> // - } /> - } /> - }> - } /> - } /> - } /> - } /> - - -
- + +
+ + } /> + } /> + } /> // + } + > + + + } + /> + } + > + + + } + /> + } + > + + + } + > + } /> + } /> + } /> + } /> + + } /> + +
+ +
); } diff --git a/frontend/src/AuthScreen/AuthScreen.module.css b/frontend/src/AuthScreen/AuthScreen.module.css index e9b2533..9306c9b 100644 --- a/frontend/src/AuthScreen/AuthScreen.module.css +++ b/frontend/src/AuthScreen/AuthScreen.module.css @@ -1,37 +1,45 @@ -.container{ - width: 20%; - height: 50%; - background-color: hsl(0, 0%, 100%); - border-radius: 10px; - margin: auto; - padding: 30px; - min-width: 500px; - min-height: 300px; +.container { + width: 20%; + height: 50%; + background-color: hsl(0, 0%, 100%); + border-radius: 10px; + margin: auto; + padding: 30px; + min-width: 500px; + min-height: 300px; } -.form{ - display: flex; - flex-direction: column; - align-items: center; - width: 100%; +.form { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; } -.headerText{ - padding-top: 34px; - padding-bottom: 46px; +.headerText { + padding-top: 34px; + padding-bottom: 46px; } -.footer{ - text-align: center; - color: hsl(0, 0%, 62%); +.footer { + text-align: center; + color: hsl(0, 0%, 62%); } .footer div:first-child { - padding-top: 50px; + padding-top: 50px; } -.error{ - padding: 10px; - color: hsl(0, 100%, 64%); - font-weight: bold; -} \ No newline at end of file +.error, +.success { + padding: 10px; + font-weight: bold; +} + +.error { + color: hsl(0, 100%, 64%); +} + +.success { + color: hsl(119, 100%, 30%); +} diff --git a/frontend/src/AuthScreen/AuthScreen.tsx b/frontend/src/AuthScreen/AuthScreen.tsx index dc6a2c3..56bc9ff 100644 --- a/frontend/src/AuthScreen/AuthScreen.tsx +++ b/frontend/src/AuthScreen/AuthScreen.tsx @@ -1,4 +1,4 @@ -import { ReactNode, FormEvent } from "react"; +import { ReactNode, FormEvent, Fragment } from "react"; import classes from "./AuthScreen.module.css"; import Button from "../Button/Button"; import { AuthError } from "../errorHandler"; @@ -12,6 +12,7 @@ type AuthScreenProp = { footer?: ReactNode; onSubmit: (event: FormEvent) => void; error?: AuthError; + success?: string; }; export function AuthScreen(props: AuthScreenProp) { @@ -23,7 +24,9 @@ export function AuthScreen(props: AuthScreenProp) {
- {props.inputs.map((input: ReactNode) => input)} + {props.inputs.map((input: ReactNode, index) => ( + {input} + ))}
diff --git a/frontend/src/NavBar/NavBar.tsx b/frontend/src/NavBar/NavBar.tsx index d04398b..da4de4b 100644 --- a/frontend/src/NavBar/NavBar.tsx +++ b/frontend/src/NavBar/NavBar.tsx @@ -31,7 +31,7 @@ function NavBar(props: NavBarProps) { className={({ isActive }) => isActive && view === "calendar" ? classes.active : "" } - to="/timeline?view=calendar" + to="/timeline?view=calendar" > Calendar @@ -52,7 +52,7 @@ function NavBar(props: NavBarProps) { style={{ backgroundImage: `url(${props.profileImage})`, }} - to="/login" + to="/settings" > ); diff --git a/frontend/src/ProtectedRoute/ProtectedRoute.tsx b/frontend/src/ProtectedRoute/ProtectedRoute.tsx new file mode 100644 index 0000000..d9004c3 --- /dev/null +++ b/frontend/src/ProtectedRoute/ProtectedRoute.tsx @@ -0,0 +1,20 @@ +import { ReactNode } from "react"; +import { Navigate } from "react-router"; + +interface ProtectedRouteProps { + isAuthenticated: boolean; + children: ReactNode; + fallback?: ReactNode; +} + +export const ProtectedRoute = (props: ProtectedRouteProps) => { + if (!props.isAuthenticated) { + if (props.fallback) { + return props.fallback; + } else { + return ; + } + } + + return props.children; +}; diff --git a/frontend/src/Unauthenticated/Unauthenticated.module.css b/frontend/src/Unauthenticated/Unauthenticated.module.css new file mode 100644 index 0000000..9bbce5e --- /dev/null +++ b/frontend/src/Unauthenticated/Unauthenticated.module.css @@ -0,0 +1,10 @@ +.container { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} + +.container > article { + line-height: 2; +} diff --git a/frontend/src/Unauthenticated/Unauthenticated.tsx b/frontend/src/Unauthenticated/Unauthenticated.tsx new file mode 100644 index 0000000..60555ac --- /dev/null +++ b/frontend/src/Unauthenticated/Unauthenticated.tsx @@ -0,0 +1,18 @@ +import { Link } from "react-router"; +import classes from "./Unauthenticated.module.css"; + +export const Unauthenticated = () => { + return ( +
+
+

Sorry, you don't permission to view this page.

+

+ Are you logged in? +

+

+ Home +

+
+
+ ); +}; diff --git a/frontend/src/UserContext/UserContext.ts b/frontend/src/UserContext/UserContext.ts new file mode 100644 index 0000000..29fde88 --- /dev/null +++ b/frontend/src/UserContext/UserContext.ts @@ -0,0 +1,19 @@ +import { createContext, Dispatch, SetStateAction } from "react"; + +export interface User { + id: number; + username: string; + email: string; + dateJoined: string; + profilePicture: string | undefined; +} + +export interface UserContextType { + user: User | null; + setUser: Dispatch> | undefined; +} + +export const UserContext = createContext({ + user: null, + setUser: undefined, +});