diff --git a/client/src/App.tsx b/client/src/App.tsx
index 075a107..08a6107 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -4,11 +4,14 @@ import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import MainWindow from './components/MainWindow';
+import { ProvideAuth } from './lib/useAuth';
function App() {
return (
-
+
+
+
);
}
diff --git a/client/src/api.ts b/client/src/api.ts
index aec6cb8..df4093c 100644
--- a/client/src/api.ts
+++ b/client/src/api.ts
@@ -354,4 +354,6 @@ export function checkRegisterUserRequest(req: any): boolean {
checkPassword(req.body.password);
}
-// Note: Login is handled by Passport.js, so it is not explicitly written here.
\ No newline at end of file
+// Note: Login is handled by Passport.js, so it is not explicitly written here.
+export const LoginEndpoint = "/login";
+export const LogoutEndpoint = "/logout";
\ No newline at end of file
diff --git a/client/src/components/MainWindow.tsx b/client/src/components/MainWindow.tsx
index 2839106..dee2afe 100644
--- a/client/src/components/MainWindow.tsx
+++ b/client/src/components/MainWindow.tsx
@@ -10,6 +10,8 @@ import TagWindow from './windows/tag/TagWindow';
import SongWindow from './windows/song/SongWindow';
import ManageTagsWindow from './windows/manage_tags/ManageTagsWindow';
import { BrowserRouter, Switch, Route, useParams, Redirect } from 'react-router-dom';
+import LoginWindow from './windows/login/LoginWindow';
+import { useAuth } from '../lib/useAuth';
var _ = require('lodash');
const darkTheme = createMuiTheme({
@@ -21,6 +23,25 @@ const darkTheme = createMuiTheme({
},
});
+function PrivateRoute(props: any) {
+ const { children, ...rest } = props;
+ let auth = useAuth();
+ return
+ auth.user ? (
+ children
+ ) : (
+
+ )
+ }
+ />
+}
+
export default function MainWindow(props: any) {
return
@@ -29,30 +50,34 @@ export default function MainWindow(props: any) {
-
-
-
-
-
+
-
+
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
+
+
diff --git a/client/src/components/windows/Windows.tsx b/client/src/components/windows/Windows.tsx
index edd6768..a75ceac 100644
--- a/client/src/components/windows/Windows.tsx
+++ b/client/src/components/windows/Windows.tsx
@@ -20,6 +20,7 @@ export enum WindowType {
Tag = "Tag",
Song = "Song",
ManageTags = "ManageTags",
+ Login = "Login",
}
export interface WindowState { }
diff --git a/client/src/components/windows/login/LoginWindow.tsx b/client/src/components/windows/login/LoginWindow.tsx
new file mode 100644
index 0000000..5631852
--- /dev/null
+++ b/client/src/components/windows/login/LoginWindow.tsx
@@ -0,0 +1,70 @@
+import React, { useState } from 'react';
+import { WindowState } from "../Windows";
+import { Box, Paper, Typography, TextField, Button } from "@material-ui/core";
+import { useHistory, useLocation } from 'react-router';
+import { useAuth, Auth } from '../../../lib/useAuth';
+
+export interface LoginWindowState extends WindowState { }
+export enum LoginWindowStateActions { }
+export function LoginWindowReducer(state: LoginWindowState, action: any) { }
+
+export default function LoginWindow(props: {}) {
+ let history: any = useHistory();
+ let location: any = useLocation();
+ let auth: Auth = useAuth();
+ let { from } = location.state || { from: { pathname: "/" } };
+
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+
+ const onSubmit = (event: any) => {
+ event.preventDefault();
+ auth.signin(email, password)
+ .then(() => {
+ history.replace(from);
+ })
+ }
+
+ return
+
+
+
+ Sign in
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/client/src/lib/useAuth.tsx b/client/src/lib/useAuth.tsx
new file mode 100644
index 0000000..6373901
--- /dev/null
+++ b/client/src/lib/useAuth.tsx
@@ -0,0 +1,140 @@
+// Note: Based on https://usehooks.com/useAuth/
+
+// import React from "react";
+// import { ProvideAuth } from "./use-auth.js";
+// function App(props) {
+// return (
+//
+// {/*
+// Route components here, depending on how your app is structured.
+// If using Next.js this would be /pages/_app.js
+// */}
+//
+// );
+// }
+
+// // Any component that wants auth state
+// import React from "react";
+// import { useAuth } from "./use-auth.js";
+
+// function Navbar(props) {
+// // Get auth state and re-render anytime it changes
+// const auth = useAuth();
+
+// return (
+//
+//
+//
+//
+// );
+// }
+
+// Hook (use-auth.js)
+
+import React, { useState, useEffect, useContext, createContext } from "react";
+import * as serverApi from '../api';
+
+export interface AuthUser {
+ id: number,
+}
+
+export interface Auth {
+ user: AuthUser | null,
+ signout: () => void,
+ signin: (email: string, password: string) => Promise,
+ signup: (email: string, password: string) => void,
+};
+
+const authContext = createContext({
+ user: null,
+ signout: () => { },
+ signin: (email: string, password: string) => {
+ throw new Error("Auth object not initialized.");
+ },
+ signup: (email: string, password: string) => {
+ throw new Error("Auth object not initialized.");
+ },
+});
+
+export function ProvideAuth(props: { children: any }) {
+ const auth = useProvideAuth();
+ return {props.children};
+}
+
+export const useAuth = () => {
+ return useContext(authContext);
+};
+
+function useProvideAuth() {
+ const [user, setUser] = useState(null);
+
+ // FIXME: password shouldn't be encoded into the URL.
+ const signin = (email: string, password: string) => {
+ return (async () => {
+ const urlBase = (process.env.REACT_APP_BACKEND || "") + serverApi.LoginEndpoint;
+ const url = `${urlBase}?username=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`;
+
+ const response = await fetch(url, { method: "POST" });
+ const json = await response.json();
+ if(!("userId" in json)) {
+ throw new Error("No UserID received from login.");
+ }
+
+ const user = {
+ id: json.userId
+ }
+ setUser(user);
+ return user;
+ })();
+ };
+
+ const signup = (email: string, password: string) => {
+ return (async () => {
+ const requestOpts = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ email: email,
+ password: password,
+ })
+ };
+
+ return (async () => {
+ const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.RegisterUserEndpoint, requestOpts)
+ if(!response.ok) {
+ throw new Error("Failed to register user.")
+ }
+ })();
+ })();
+ };
+
+ const signout = () => {
+ return (async () => {
+ const url = (process.env.REACT_APP_BACKEND || "") + serverApi.LogoutEndpoint;
+ const response = await fetch(url, { method: "POST" });
+ if(!response.ok) {
+ throw new Error("Failed to log out.");
+ }
+ setUser(null);
+ })();
+ };
+
+ // Return the user object and auth methods
+ return {
+ user,
+ signin,
+ signup,
+ signout,
+ };
+}
\ No newline at end of file
diff --git a/server/app.ts b/server/app.ts
index a4302bb..5a34d00 100644
--- a/server/app.ts
+++ b/server/app.ts
@@ -87,7 +87,10 @@ const SetupApp = (app: any, knex: Knex, apiBaseUrl: string) => {
const checkLogin = () => {
return function (req: any, res: any, next: any) {
if (!req.isAuthenticated || !req.isAuthenticated()) {
- return res.status(401).send();
+ return res
+ .status(401)
+ .json({ reason: "NotLoggedIn" })
+ .send();
}
next();
}
@@ -111,7 +114,9 @@ const SetupApp = (app: any, knex: Knex, apiBaseUrl: string) => {
app.post(apiBaseUrl + api.MergeTagEndpoint, checkLogin(), _invoke(MergeTagEndpointHandler));
app.post(apiBaseUrl + api.RegisterUserEndpoint, _invoke(RegisterUserEndpointHandler));
- app.post('/login', passport.authenticate('local'), (req: any, res: any) => { res.status(200).send(); });
+ app.post('/login', passport.authenticate('local'), (req: any, res: any) => {
+ res.status(200).send({ userId: req.user.id });
+ });
app.post('/logout', function (req: any, res: any) {
req.logout();
res.status(200).send();