feat(auth): Added authentication
This commit is contained in:
parent
d85d4334f8
commit
5e100c75ed
39 changed files with 704 additions and 86 deletions
|
|
@ -1,12 +1,19 @@
|
|||
import { Navbar as BsNavbar, Container } from "react-bootstrap";
|
||||
import { Navbar as BsNavbar, Button, Container } from "react-bootstrap";
|
||||
// import styles from "./Navbar.module.scss";
|
||||
import Searchbar from "../Searchbar/Searchbar";
|
||||
import { useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const Navbar = () => {
|
||||
const [searchText, setSearchText] = useState<string>("");
|
||||
const navigate = useNavigate();
|
||||
const session = Cookies.get("session");
|
||||
|
||||
function onLogout() {
|
||||
Cookies.remove("session");
|
||||
navigate("/login");
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
navigate(`/search?search=${searchText}`);
|
||||
|
|
@ -22,9 +29,10 @@ const Navbar = () => {
|
|||
<BsNavbar.Toggle />
|
||||
<BsNavbar.Collapse>
|
||||
<Container className="justify-content-center d-flex">
|
||||
<Searchbar text={searchText} setText={setSearchText} onSearch={onSearch} />
|
||||
<Searchbar text={searchText} setText={setSearchText} onSearch={onSearch} enabled={session !== undefined} />
|
||||
</Container>
|
||||
</BsNavbar.Collapse>
|
||||
{session && <Button onClick={onLogout}>Logout</Button>}
|
||||
</Container>
|
||||
</BsNavbar>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,4 +2,10 @@
|
|||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.searchbarDisabled {
|
||||
@extend .searchbar;
|
||||
background-color: lightgray;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
|
@ -5,9 +5,10 @@ interface SearchbarProps {
|
|||
text: string,
|
||||
setText: (text: string) => void;
|
||||
onSearch?: () => void;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
const Searchbar = ({ text, setText, onSearch }: SearchbarProps) => {
|
||||
const Searchbar = ({ text, setText, onSearch, enabled = true }: SearchbarProps) => {
|
||||
|
||||
function onKeyPressed(event: React.KeyboardEvent<HTMLInputElement>) {
|
||||
if (onSearch === undefined) {
|
||||
|
|
@ -20,7 +21,7 @@ const Searchbar = ({ text, setText, onSearch }: SearchbarProps) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<input className={styles.searchbar} type="text" placeholder="Search" value={text} onChange={e => setText(e.target.value)} onKeyUp={onKeyPressed} />
|
||||
<input className={enabled ? styles.searchbar : styles.searchbarDisabled} type="text" placeholder="Search" value={text} onChange={e => setText(e.target.value)} onKeyUp={onKeyPressed} disabled={!enabled} />
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ const ServerList = () => {
|
|||
setServers(serverList);
|
||||
}).catch(err => {
|
||||
setServers([]);
|
||||
if (err.response && err.response.status === 401) {
|
||||
return;
|
||||
}
|
||||
alert(err);
|
||||
})
|
||||
}, []);
|
||||
|
|
|
|||
13
frontend/src/Lib/Auth.ts
Normal file
13
frontend/src/Lib/Auth.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import axios from "axios"
|
||||
import { apiUrl } from "./api"
|
||||
|
||||
export interface Session {
|
||||
sessionToken: string;
|
||||
expiresOn: string;
|
||||
}
|
||||
|
||||
export const logIn = async (username: string, password: string) => {
|
||||
const response = await axios.post<Session>(`${apiUrl}/auth`, `username=${encodeURI(username)}&password=${encodeURI(password)}`);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export interface SearchResult {
|
|||
}
|
||||
|
||||
export const search = async (searchTerm: string, serverId: string): Promise<Array<SearchResult>> => {
|
||||
const response = await axios.get<Array<SearchResult>>(encodeURI(`${apiUrl}/search?searchTerm=${searchTerm}&serverId=${serverId}`));
|
||||
const response = await axios.get<Array<SearchResult>>(encodeURI(`${apiUrl}/search?searchTerm=${searchTerm}&serverId=${serverId}`), { withCredentials: true });
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export interface Server {
|
|||
|
||||
export const getServerList = async (): Promise<Array<Server>> => {
|
||||
console.log("fetching server list");
|
||||
const response = await axios.get<Array<Server>>(`${apiUrl}/servers`);
|
||||
const response = await axios.get<Array<Server>>(`${apiUrl}/servers`, { withCredentials: true });
|
||||
|
||||
console.log(response);
|
||||
|
||||
|
|
|
|||
9
frontend/src/Pages/Login/Login.module.scss
Normal file
9
frontend/src/Pages/Login/Login.module.scss
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.form {
|
||||
margin: auto;
|
||||
margin-top: 50px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.formButton {
|
||||
margin-top: 10px;
|
||||
}
|
||||
54
frontend/src/Pages/Login/Login.tsx
Normal file
54
frontend/src/Pages/Login/Login.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Form, FormGroup } from "react-bootstrap";
|
||||
import { logIn } from "../../Lib/Auth";
|
||||
import Cookies from "js-cookie";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import styles from "./Login.module.scss";
|
||||
|
||||
|
||||
const Login = () => {
|
||||
const [username, setUsername] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (Cookies.get("session") !== undefined) {
|
||||
navigate("/");
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
function onSubmit() {
|
||||
logIn(username, password).then(sessionToken => {
|
||||
Cookies.set("session", sessionToken.sessionToken, { expires: Date.parse(sessionToken.expiresOn) });
|
||||
navigate("/");
|
||||
}).catch(err => {
|
||||
alert(err);
|
||||
})
|
||||
}
|
||||
|
||||
function onFormKeypress(e: React.KeyboardEvent<HTMLFormElement>) {
|
||||
if (e.key === "Enter") {
|
||||
onSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.form}>
|
||||
<Form onKeyUp={onFormKeypress}>
|
||||
<h3>Login</h3>
|
||||
<FormGroup>
|
||||
<Form.Label>Username</Form.Label>
|
||||
<Form.Control type="text" value={username} onChange={e => setUsername(e.target.value)} />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Form.Label>Password</Form.Label>
|
||||
<Form.Control type="password" value={password} onChange={e => setPassword(e.target.value)} />
|
||||
</FormGroup>
|
||||
<Button className={styles.formButton} variant="primary" onClick={onSubmit}>Login</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
|
|
@ -3,35 +3,51 @@ import { getServerList, type Server } from "../../Lib/Servers";
|
|||
import ServerSearch from "../../Components/ServerSearch/ServerSearch";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Spinner } from "react-bootstrap";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
|
||||
const Search = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [servers, setServers] = useState<Array<Server>>([]);
|
||||
const navigate = useNavigate();
|
||||
const sessionCookie = Cookies.get("session");
|
||||
|
||||
const searchTerm = searchParams.get("search") || "";
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionCookie) {
|
||||
navigate("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchTerm === "") {
|
||||
alert(`Error search term missing: ${searchTerm}`);
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
if (servers.length > 0) {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setServers([]);
|
||||
}
|
||||
|
||||
getServerList().then(servers => {
|
||||
if (servers.length === 0) {
|
||||
alert("No servers found");
|
||||
}
|
||||
setServers(servers);
|
||||
|
||||
const workingServers: Array<Server> = [];
|
||||
|
||||
servers.forEach(s => {
|
||||
if (!s.errored) {
|
||||
workingServers.push(s);
|
||||
}
|
||||
})
|
||||
|
||||
if (workingServers.length === 0) {
|
||||
alert("No working servers");
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
setServers(workingServers);
|
||||
}).catch(e => {
|
||||
alert(e);
|
||||
});
|
||||
}, [searchTerm]);
|
||||
}, [searchTerm, navigate]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
import ServerList from "./Components/ServerList/ServerList"
|
||||
import { useEffect } from "react";
|
||||
import ServerList from "./Components/ServerList/ServerList";
|
||||
import Cookies from "js-cookie";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const Index = () => {
|
||||
const navigate = useNavigate();
|
||||
const sessionCookie = Cookies.get("session");
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionCookie) {
|
||||
navigate("/login");
|
||||
}
|
||||
}, [navigate, sessionCookie]);
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", padding: "20px", display: "flex", flexDirection: "column", alignItems: "center" }}>
|
||||
<h1>Available Servers</h1>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Index from './index.tsx'
|
|||
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
||||
import Navbar from './Components/Navbar/Navbar.tsx'
|
||||
import Search from './Pages/Search/Search.tsx';
|
||||
import Login from './Pages/Login/Login.tsx';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
|
|
@ -13,6 +14,7 @@ createRoot(document.getElementById('root')!).render(
|
|||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="/search" element={<Search />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</StrictMode>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue