Seems to be all working!!

This commit is contained in:
Luke Berndt
2023-04-02 22:27:36 -04:00
parent b8e60ab926
commit efe3794eee
10 changed files with 210 additions and 148 deletions

View File

@@ -26,6 +26,10 @@ const local = new LocalStrategy({
console.error("Auth Error - user not found: " + email); console.error("Auth Error - user not found: " + email);
return done(null, false, { message: `Invalid email or password` }) return done(null, false, { message: `Invalid email or password` })
} }
if (!user.confirmEmail) {
console.error("Auth Error - user has not confirmed email: " + email);
return done(null, false, { message: `User's email is not confirmed` })
}
user.comparePassword(password, (err, isMatch) => { user.comparePassword(password, (err, isMatch) => {
if (isMatch) { if (isMatch) {
return done(null, user) return done(null, user)

View File

@@ -91,10 +91,14 @@ exports.login = function (req, res, next) {
} }
if (!user.confirmEmail) { if (!user.confirmEmail) {
req.logout(); req.logout();
return res.json({ req.logout(function (err) {
success: false, if (err) { return next(err); }
message: "unconfirmed email", res.clearCookie('sessionId', { domain: cookie_domain, path: '/' });
userId: user.id return res.json({
success: false,
message: "unconfirmed email",
userId: user.id
});
}); });
} }
//console.log("account/server/controllers/users.js - req.login() Authenicated: " + user.email); //console.log("account/server/controllers/users.js - req.login() Authenicated: " + user.email);
@@ -370,20 +374,20 @@ function handleSendConfirmEmail(user) {
TextPart: "Thanks for signing up for " + site_name + ". We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address: " + account_server + "/confirm-email/" + user._id + "/" + token, TextPart: "Thanks for signing up for " + site_name + ". We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address: " + account_server + "/confirm-email/" + user._id + "/" + token,
HTMLPart: "<h3>Thanks for signing up for " + site_name + "</h3><br />We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address:<p> " + account_server + "/confirm-email/" + user._id + "/" + token + "</p>" HTMLPart: "<h3>Thanks for signing up for " + site_name + "</h3><br />We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address:<p> " + account_server + "/confirm-email/" + user._id + "/" + token + "</p>"
}] }]
}); }).then(result => {
}).then(result => { console.log("Confirm email sent to: " + user.email);
console.log("Confirm email sent to: " + user.email); resolve({
resolve({ success: true,
success: true, userId: user._id
userId: user._id });
}); }).catch(err => {
}).catch(err => { console.error("Admin Email: " + admin_email + " User Email: " + user.email);
console.error("Admin Email: " + admin_email + " User Email: " + user.email); console.error("Error - Send Confirm Email - caught: " + err);
console.error("Error - Send Confirm Email - caught: " + err); res.status(500);
res.status(500); reject({
reject({ success: false,
success: false, message: err
message: err });
}); });
}); });
} }
@@ -425,10 +429,13 @@ exports.sendConfirmEmail = async function (req, res, next) {
exports.logout = function (req, res, next) { exports.logout = function (req, res, next) {
// the logout method is added to the request object automatically by Passport // the logout method is added to the request object automatically by Passport
req.logout(); req.logout(function (err) {
res.clearCookie('sessionId', { domain: cookie_domain, path: '/' }); if (err) { return next(err); }
return res.json({
success: true res.clearCookie('sessionId', { domain: cookie_domain, path: '/' });
return res.json({
success: true
});
}); });
}; };
@@ -542,6 +549,30 @@ exports.updateProfile = async function (req, res, next) {
}); });
return; return;
} }
// Lets make sure someone else isn't using this screenName
screenNameUser = await User.findOne({ screenName: req.body.screenName }).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
// Did we find a user with the screeName, is it not us?
if (screenNameUser && (screenNameUser.userId != user.userId)) {
res.status(500);
res.json({
success: false,
message: "Screen Name already in use"
});
return;
}
// go ahead and create the new user // go ahead and create the new user
user.firstName = res.locals.firstName user.firstName = res.locals.firstName
user.lastName = res.locals.lastName user.lastName = res.locals.lastName
@@ -608,6 +639,24 @@ exports.register = async function (req, res, next) {
}); });
return; return;
} }
user = await User.findOne({ screenName: req.body.screenName }).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
// is email address already in use?
if (user) {
res.status(500);
res.json({
success: false,
message: "Screen Name already in use"
});
return;
}
// go ahead and create the new user // go ahead and create the new user
user = (({ user = (({
firstName, firstName,
@@ -628,12 +677,16 @@ exports.register = async function (req, res, next) {
user.password = req.body.password; user.password = req.body.password;
user.email = req.body.email; user.email = req.body.email;
await User.create(user) let savedUser = await User.create(user)
console.log("Successfully registered: " + user.email + ", now sending confirmation email."); console.log("Successfully registered: " + user.email + ", now sending confirmation email.");
// Since regiestration worked, send a confirmation email. // Since registration worked, send a confirmation email.
handleSendConfirmEmail(user).then(function (result) { handleSendConfirmEmail(savedUser).then(function (result) {
res.json(result); console.log("Confirmation email sent to: " + user.email);
res.json({
success: true,
message: result
});
return; return;
}).catch(function (err) { }).catch(function (err) {
console.error("Error creating user: " + user.email + " Error: " + err); console.error("Error creating user: " + user.email + " Error: " + err);

View File

@@ -15,25 +15,25 @@ import ResetPassword from "../User/ResetPassword"
import Terms from "../User/Terms" import Terms from "../User/Terms"
const App = (params) => { const App = (params) => {
const {store} = params; const { store } = params;
return ( return (
<div> <div>
<Navigation/> <Navigation />
<Routes> <Routes>
<Route exact path="/" element={ <Restricted><Profile/></Restricted>} /> <Route exact path="/" element={<Restricted><Profile /></Restricted>} />
<Route path="/login" element={ <Login/>} /> <Route path="/login" element={<Login />} />
<Route path="/register" element={ <Register/> } /> <Route path="/register" element={<Register />} />
<Route path="/wait-confirm-email" element={ <WaitConfirmEmail/> } /> <Route path="/wait-confirm-email" element={<WaitConfirmEmail />} />
<Route path="/sent-confirm-email" element={ <SentConfirmEmail/> } /> <Route path="/sent-confirm-email" element={<SentConfirmEmail />} />
<Route path="/send-reset-password" element={ <SendResetPassword/> } /> <Route path="/send-reset-password" element={<SendResetPassword />} />
<Route path="/reset-password/:userId/:token" element={ <ResetPassword/> } /> <Route path="/reset-password/:userId/:token" element={<ResetPassword />} />
<Route path="/confirm-email/:userId/:token" element={ <ConfirmEmail/> } /> <Route path="/confirm-email/:userId/:token" element={<ConfirmEmail />} />
<Route path="/terms" element={ <Restricted><Terms/></Restricted>} /> <Route path="/terms" element={<Restricted><Terms /></Restricted>} />
<Route path="/profile" element={ <Restricted><Profile/></Restricted>} /> <Route path="/profile" element={<Restricted><Profile /></Restricted>} />
<Route render={() => (<div>Miss</div>)} /> <Route render={() => (<div>Miss</div>)} />
</Routes> </Routes>
</div> </div>
) )
}; };
export default App export default App

View File

@@ -1,32 +1,31 @@
// in src/restricted.js // in src/restricted.js
import React, { useEffect} from "react"; import React, { useEffect } from "react";
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import { authenticateUser } from "../features/user/userSlice"; import { authenticateUser } from "../features/user/userSlice";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
/** /**
* Higher-order component (HOC) to wrap restricted pages * Higher-order component (HOC) to wrap restricted pages
*/ */
const Restricted = ({children }) => { const Restricted = ({ children }) => {
const { authenticated, hasAuthenticated } = useSelector((state) => state.user); const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
dispatch(authenticateUser({})); dispatch(authenticateUser({}));
},[]); }, []);
if (hasAuthenticated) { if (hasAuthenticated) {
if (authenticated) { if (authenticated) {
return children;; return children;
} }
else { else {
navigate("/login")
navigate("/list-systems") }
} }
} return <div />
return <div/>
}; };
export default Restricted export default Restricted

View File

@@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { useState, useEffect } from "react";
import { import {
Container, Container,
Dimmer, Dimmer,
@@ -7,72 +7,65 @@ import {
Message, Message,
Icon Icon
} from "semantic-ui-react"; } from "semantic-ui-react";
import { Link } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { confirmEmail } from "../features/user/userSlice";
import { useDispatch } from 'react-redux'
class ConfirmEmail extends Component { const ConfirmEmail = (props) => {
const dispatch = useDispatch();
const [loading, setLoading] = useState(true);
const [confirmMessage, setConfirmMessage] = useState({ success: false, message: "" });
const { userId, token } = useParams();
state = {
loading: true,
confirmMessage: { success: false, message:""}
};
componentDidMount() {
const { match: { params } } = this.props;
this.props const checkConfirmation = async(userId, token) => {
.confirmEmail(params.userId, params.token) const result = await dispatch(confirmEmail({ userId, token })).unwrap();
.then(confirmMessage => { if (result) {
if (confirmMessage) { setConfirmMessage(result);
// report to the user is there was a problem during login setLoading(false);
this.setState({ }
confirmMessage: confirmMessage, loading: false
});
}
});
} }
render() { useEffect(() => {
var dimmerProps = { }; checkConfirmation(userId, token);
if (this.state.loading) { }, []);
dimmerProps["active"] = true;
}
return (
<Container text> const dimmerProps = { active: loading };
return (
<Container text>
<Dimmer {...dimmerProps}> <Dimmer {...dimmerProps}>
<Loader indeterminate>Confirming Email Address</Loader> <Loader indeterminate>Confirming Email Address</Loader>
</Dimmer> </Dimmer>
{this.state.confirmMessage.success ? (
<Message icon>
<Icon name='check'/>
<Message.Content>
<Message.Header>Success</Message.Header>
<p>You have successfully confirmed you email address.</p>
<Link to="/">
<Button
size="large"
content="Continue to Login"
fluid
/></Link>
</Message.Content> {confirmMessage.success ? (
</Message> <Message icon>
) : ( <Icon name='check' />
<Message icon> <Message.Content>
<Icon name='exclamation'/> <Message.Header>Success</Message.Header>
<p>You have successfully confirmed you email address.</p>
<Link to="/login">
<Button
size="large"
content="Continue to Login"
fluid
/></Link>
</Message.Content>
</Message>
) : (
<Message icon>
<Icon name='exclamation' />
<Message.Content> <Message.Content>
<Message.Header>Error</Message.Header> <Message.Header>Error</Message.Header>
<p>{this.state.confirmMessage.message}</p> <p>{confirmMessage.message}</p>
</Message.Content> </Message.Content>
</Message>
</Message> )}
)}
</Container>
</Container> );
);
}
} }
export default ConfirmEmail; export default ConfirmEmail;

View File

@@ -20,7 +20,7 @@ const Profile = (props) => {
if (result.success) { if (result.success) {
console.log(result) console.log(result)
} else { } else {
setMessage = result.message; setMessage(result.message);
} }
} }

View File

@@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { registerUser } from "../features/user/userSlice"; import { registerUser } from "../features/user/userSlice";
import { useNavigate, useSearchParams, Link } from 'react-router-dom'; import { useNavigate, useSearchParams, Link } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import { import {
Container, Container,
@@ -15,32 +15,32 @@ function Register(props) {
const [requestMessage, setRequestMessage] = useState(""); const [requestMessage, setRequestMessage] = useState("");
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const onRegisterSubmit = async(user) => { const onRegisterSubmit = async (user) => {
const result = await dispatch(registerUser(user)).unwrap(); const result = await dispatch(registerUser(user)).unwrap();
if (result.success) { if (result.success) {
navigate("/sent-confirm-email"); navigate("/sent-confirm-email");
} else { } else {
console.error(result); console.error(result);
setRequestMessage(result.message); setRequestMessage(result.message);
} }
}; };
return ( return (
<div> <div>
<Container text> <Container text>
<Message icon> <Message icon>
<Icon name='trophy'/> <Icon name='trophy' />
<Message.Content> <Message.Content>
<Message.Header>Welcome!</Message.Header> <Message.Header>Welcome!</Message.Header>
<p>Creating an account on OpenMHz is the first step in sharing radio system recordings using Trunk Recorder.</p> <p>Creating an account on OpenMHz is the first step in sharing radio system recordings using Trunk Recorder.</p>
<p>After creating an account, head over <a href="https://github.com/robotastic/trunk-recorder">here</a> to learn more about Trunk Recorder</p> <p>After creating an account, head over <a href="https://github.com/robotastic/trunk-recorder">here</a> to learn more about Trunk Recorder</p>
<p> - Luke</p> <p> - Luke</p>
</Message.Content> </Message.Content>
</Message> </Message>
</Container> </Container>
<Container text> <Container text>
<Header style={{ paddingTop: '20px' }} as="h1">Register</Header> <Header style={{ paddingTop: '20px' }} as="h1">Register</Header>
<UserForm onSubmit={onRegisterSubmit} requestMessage={requestMessage} isWaiting={props.isWaiting}/> <UserForm onSubmit={onRegisterSubmit} requestMessage={requestMessage} isWaiting={props.isWaiting} />
</Container> </Container>
</div> </div>
); );

View File

@@ -43,7 +43,6 @@ const ResetPassword = (props) => {
setConfirmPasswordError(false); setConfirmPasswordError(false);
} }
const { match: { params } } = props;
setCheckInputMessages(inputMessages); setCheckInputMessages(inputMessages);
if (!error) { if (!error) {
const result = await dispatch(resetPassword({ userId, token, password })).unwrap(); const result = await dispatch(resetPassword({ userId, token, password })).unwrap();

View File

@@ -28,6 +28,7 @@ const UserForm = (props) => {
const [passwordError, setPasswordError] = useState(false); const [passwordError, setPasswordError] = useState(false);
const [confirmPasswordError, setConfirmPasswordError] = useState(false); const [confirmPasswordError, setConfirmPasswordError] = useState(false);
const [isEditing, setIsEditing] = useState(props.isEditing); const [isEditing, setIsEditing] = useState(props.isEditing);
const [changed, setChanged] = useState(false);
const requestMessage = props.requestMessage; const requestMessage = props.requestMessage;
useEffect(() => { useEffect(() => {
@@ -37,6 +38,7 @@ const UserForm = (props) => {
setScreenName(props.user.screenName); setScreenName(props.user.screenName);
setEmail(props.user.email); setEmail(props.user.email);
setLocation(props.user.location); setLocation(props.user.location);
setChanged(false);
} }
}, [props.isEditing, props.user]); }, [props.isEditing, props.user]);
@@ -120,6 +122,7 @@ const UserForm = (props) => {
if (!inputError) { if (!inputError) {
const user ={ firstName, lastName, screenName, location, email, password }; const user ={ firstName, lastName, screenName, location, email, password };
setChanged(false);
props.onSubmit(user); props.onSubmit(user);
} }
} }
@@ -153,6 +156,7 @@ const UserForm = (props) => {
</Message> </Message>
); );
} }
const buttonDisabled = changed?{}:{disabled:true}
return ( return (
<div> <div>
<Form <Form
@@ -168,7 +172,7 @@ const UserForm = (props) => {
<Form.Input <Form.Input
type="text" type="text"
name="firstName" name="firstName"
onChange={e => setFirstName(e.target.value)} onChange={e => {setFirstName(e.target.value); setChanged(true)}}
error={firstNameError} error={firstNameError}
value={firstName} value={firstName}
label="First name" label="First name"
@@ -179,7 +183,7 @@ const UserForm = (props) => {
<Form.Input <Form.Input
type="text" type="text"
name="lastName" name="lastName"
onChange={e => setLastName(e.target.value)} onChange={e => {setLastName(e.target.value); setChanged(true)}}
error={lastNameError} error={lastNameError}
value={lastName} value={lastName}
label="Last name" label="Last name"
@@ -192,7 +196,7 @@ const UserForm = (props) => {
<Form.Input <Form.Input
type="text" type="text"
name="email" name="email"
onChange={e => setEmail(e.target.value)} onChange={e => {setEmail(e.target.value); setChanged(true)}}
error={emailError} error={emailError}
value={email} value={email}
disabled={isEditing} disabled={isEditing}
@@ -205,7 +209,7 @@ const UserForm = (props) => {
<Form.Input <Form.Input
type="text" type="text"
name="screenName" name="screenName"
onChange={e => setScreenName(e.target.value)} onChange={e => {setScreenName(e.target.value); setChanged(true)}}
error={screenNameError} error={screenNameError}
value={screenName} value={screenName}
label="Screen Name" label="Screen Name"
@@ -221,7 +225,7 @@ const UserForm = (props) => {
<Form.Input <Form.Input
type="text" type="text"
name="location" name="location"
onChange={e => setLocation(e.target.value)} onChange={e => {setLocation(e.target.value); setChanged(true)}}
error={locationError} error={locationError}
value={location} value={location}
label="General Location" label="General Location"
@@ -235,7 +239,8 @@ const UserForm = (props) => {
<Form.Input <Form.Input
name="password" name="password"
type="password" type="password"
onChange={e => setPassword(e.target.value)} onChange={e => {setPassword(e.target.value); setChanged(true)}}
value={password}
error={passwordError} error={passwordError}
disabled={isEditing} disabled={isEditing}
label="Password" label="Password"
@@ -246,7 +251,8 @@ const UserForm = (props) => {
<Form.Input <Form.Input
name="confirmPassword" name="confirmPassword"
type="password" type="password"
onChange={e => setConfirmPassword(e.target.value)} onChange={e => {setConfirmPassword(e.target.value); setChanged(true)}}
value={confirmPassword}
error={confirmPasswordError} error={confirmPasswordError}
disabled={isEditing} disabled={isEditing}
label="Confirm Password" label="Confirm Password"
@@ -256,7 +262,7 @@ const UserForm = (props) => {
</Form.Group> </Form.Group>
)} )}
<div style={floatStyle}> <div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login" color='blue'> <Button type="submit" size="large" floated="right" value="Login" color='blue' disabled={!changed}>
{isEditing !== true ? 'Register' : 'Update'} {isEditing !== true ? 'Register' : 'Update'}
</Button> </Button>
</div> </div>

View File

@@ -90,6 +90,22 @@ export const logoutUser = createAsyncThunk(
} }
) )
export const confirmEmail = createAsyncThunk(
'user/confirmEmail',
async ({userId, token}) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/confirm/" + token;
const res = await fetch(url, {
method: 'POST'
}).then(
(data) => data.json()
)
return res;
}
)
export const sendConfirmEmail = createAsyncThunk( export const sendConfirmEmail = createAsyncThunk(
'user/sendConfirmEmail', 'user/sendConfirmEmail',
async (userId) => { async (userId) => {
@@ -111,7 +127,7 @@ export const resetPassword = createAsyncThunk(
'user/resetPassword', 'user/resetPassword',
async (data) => { async (data) => {
const {userId, token, password} = data; const {userId, token, password} = data;
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/confirm/" + token const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/reset-password/" + token
const res = await fetch(url, { const res = await fetch(url, {
method: 'POST', method: 'POST',
credentials: "include", credentials: "include",
@@ -192,19 +208,11 @@ export const userSlice = createSlice({
[updateProfile.fulfilled]: (state, data) => { [updateProfile.fulfilled]: (state, data) => {
const {payload} = data; const {payload} = data;
if (payload.success) { if (payload.success) {
state.authenticated = true;
state.hasAuthenticated = true;
state.userId = payload.user.userId;
state.admin = payload.user.admin;
state.email = payload.user.email;
state.firstName = payload.user.firstName; state.firstName = payload.user.firstName;
state.lastName = payload.user.lastName; state.lastName = payload.user.lastName;
state.location = payload.user.location; state.location = payload.user.location;
state.screenName = payload.user.screenName; state.screenName = payload.user.screenName;
} else { }
state.authenticated = false;
state.hasAuthenticated = true;
}
}, },
[loginUser.rejected]: (state, { payload }) => { [loginUser.rejected]: (state, { payload }) => {
state.authenticated = false; state.authenticated = false;