Login page working. No registration or logout yet.

pull/31/head
Sander Vocke 5 years ago
parent ad114b52eb
commit 7144d07832
  1. 5
      client/src/App.tsx
  2. 4
      client/src/api.ts
  3. 61
      client/src/components/MainWindow.tsx
  4. 1
      client/src/components/windows/Windows.tsx
  5. 70
      client/src/components/windows/login/LoginWindow.tsx
  6. 140
      client/src/lib/useAuth.tsx
  7. 9
      server/app.ts

@ -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 (
<DndProvider backend={HTML5Backend}>
<MainWindow />
<ProvideAuth>
<MainWindow />
</ProvideAuth>
</DndProvider>
);
}

@ -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.
// Note: Login is handled by Passport.js, so it is not explicitly written here.
export const LoginEndpoint = "/login";
export const LogoutEndpoint = "/logout";

@ -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 <Route {...rest}
render={({ location }) =>
auth.user ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
}
export default function MainWindow(props: any) {
return <ThemeProvider theme={darkTheme}>
<CssBaseline />
@ -29,30 +50,34 @@ export default function MainWindow(props: any) {
<Route exact path="/">
<Redirect to={"/query"} />
</Route>
<Route path="/query">
<AppBar selectedTab={AppBarTab.Query} />
<QueryWindow/>
</Route>
<Route path="/artist/:id">
<Route path="/login">
<AppBar selectedTab={null} />
<ArtistWindow/>
<LoginWindow />
</Route>
<Route path="/tag/:id">
<PrivateRoute path="/query">
<AppBar selectedTab={AppBarTab.Query} />
<QueryWindow />
</PrivateRoute>
<PrivateRoute path="/artist/:id">
<AppBar selectedTab={null} />
<TagWindow/>
</Route>
<Route path="/album/:id">
<ArtistWindow />
</PrivateRoute>
<PrivateRoute path="/tag/:id">
<AppBar selectedTab={null} />
<AlbumWindow/>
</Route>
<Route path="/song/:id">
<TagWindow />
</PrivateRoute>
<PrivateRoute path="/album/:id">
<AppBar selectedTab={null} />
<SongWindow/>
</Route>
<Route path="/tags">
<AlbumWindow />
</PrivateRoute>
<PrivateRoute path="/song/:id">
<AppBar selectedTab={null} />
<SongWindow />
</PrivateRoute>
<PrivateRoute path="/tags">
<AppBar selectedTab={AppBarTab.Tags} />
<ManageTagsWindow/>
</Route>
<ManageTagsWindow />
</PrivateRoute>
</Switch>
</BrowserRouter>
</ThemeProvider>

@ -20,6 +20,7 @@ export enum WindowType {
Tag = "Tag",
Song = "Song",
ManageTags = "ManageTags",
Login = "Login",
}
export interface WindowState { }

@ -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<string>("");
const [password, setPassword] = useState<string>("");
const onSubmit = (event: any) => {
event.preventDefault();
auth.signin(email, password)
.then(() => {
history.replace(from);
})
}
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap">
<Box
m={1}
mt={4}
>
<Paper>
<Box p={3}>
<Typography variant="h5">Sign in</Typography>
<form noValidate onSubmit={onSubmit}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email"
name="email"
autoFocus
onInput={(e: any) => setEmail(e.target.value)}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="password"
label="Password"
name="password"
type="password"
onInput={(e: any) => setPassword(e.target.value)}
/>
<Button
type="submit"
fullWidth
variant="outlined"
color="primary"
>Sign in</Button>
</form>
</Box>
</Paper>
</Box>
</Box>
}

@ -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 (
// <ProvideAuth>
// {/*
// Route components here, depending on how your app is structured.
// If using Next.js this would be /pages/_app.js
// */}
// </ProvideAuth>
// );
// }
// // 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 (
// <NavbarContainer>
// <Logo />
// <Menu>
// <Link to="/about">About</Link>
// <Link to="/contact">Contact</Link>
// {auth.user ? (
// <Fragment>
// <Link to="/account">Account ({auth.user.email})</Link>
// <Button onClick={() => auth.signout()}>Signout</Button>
// </Fragment>
// ) : (
// <Link to="/signin">Signin</Link>
// )}
// </Menu>
// </NavbarContainer>
// );
// }
// 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<AuthUser>,
signup: (email: string, password: string) => void,
};
const authContext = createContext<Auth>({
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 <authContext.Provider value={auth}>{props.children}</authContext.Provider>;
}
export const useAuth = () => {
return useContext(authContext);
};
function useProvideAuth() {
const [user, setUser] = useState<AuthUser | null>(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,
};
}

@ -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();

Loading…
Cancel
Save