我使用MongoDB Atlas作为我的数据库来存储我的应用程序的用户。
然而,我只是注意到,虽然我的应用程序阻止已注册的用户再次注册,但我不明白为什么它一开始就要验证不在MongoDB中的用户??
我使用passport.js进行身份验证,特别是local strategy:
这是我的模型:
/* eslint-disable no-var */
var mongoose = require('mongoose')
var emailValidator = require('email-validator')
var bcrypt = require('bcrypt') // hashing function dedicated for passwords
const SALT_ROUNDS = 12
var UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
lowercase: true,
index: { unique: true },
validate: {
validator: emailValidator.validate,
message: props => `${props.value} is not a valid email address`
}
},
password: {
type: String,
required: true,
trim: true,
index: { unique: true },
minlength: 8
}
}, {
timestamps: true
})
UserSchema.pre('save', async function preSave(next) {
var user = this
var hash
if (!user.isModified('password')) return next()
try {
hash = await bcrypt.hash(user.password, SALT_ROUNDS)
user.password = hash
return next()
} catch (err) {
return next(err)
}
})
UserSchema.methods.comparePassword = async function comparePassword(candidate) {
return bcrypt.compare(candidate, this.password)
};
module.exports = mongoose.model('User', UserSchema)这是我身份验证文件,
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('../models/UserModel');
passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, async(username, password, done) => {
try {
var user = await UserModel.findOne({ username: username }).exec();
if (!user) {
return done(null, false, { message: 'Invalid username or password' })
}
var passwordOk = await user.comparePassword(password);
if (!passwordOk) {
return done(null, false, { message: 'Invalid username or password' })
}
return done(null, user)
} catch (err) {
return done(err)
}
}))
passport.serializeUser((user, done) => {
return done(null, user._id)
})
passport.deserializeUser(async(id, done) => {
try {
var user = await UserModel.findById(id).exec();
return done(null, user);
} catch (err) {
return done(err)
}
})
module.exports = {
initialize: passport.initialize(),
session: passport.session(),
setUser: (req, res, next) => {
res.locals.user = req.user;
next();
}
}这是我在服务器上的中心应用程序文件。
const express = require('express');
require('dotenv').config()
const nextJS = require('next');
var cookieParser = require('cookie-parser')
var session = require('express-session')
var MongoStore = require('connect-mongo')(session)
var bodyParser = require('body-parser')
var auth = require('./lib/auth');
var cors = require('cors')
var morgan = require('morgan')
var HttpStatus = require('http-status-codes')
var PORT = process.env.PORT || 8016
const { isBlockedPage, isInternalUrl } = require('next-server/dist/server/utils');
function NODE_ENVSetter(ENV) {
var environment,
environments = {
'production': () => {
environment = process.env.PRODUCTION_DB_DSN;
return environment;
},
'test': () => {
environment = process.env.TEST_DB_DSN;
return environment;
},
'default': () => {
environment = process.env.DEVELOPMENT_DB_DSN;
console.log("environment ", environment);
return environment;
},
};
(environments[ENV] || environments['default'])();
return environment
}
var db = NODE_ENVSetter('development')
var mongoose = require('mongoose')
function errorHandler(err, req, res, next) {
// Set locals, only providing error in development
res.locals.message = err.message
res.locals.error = req.app.get('env') === 'development' ? err : {}
// Log error
console.error(err.stack)
// Render the error page
res.status(err.status || 500)
// Default error message by HTTP code
res.render('error', {
title: HttpStatus.getStatusText(err.status),
message: HttpStatus.getStatusText(err.status)
})
}
async function start() {
const dev = process.env.NODE_ENV !== 'production';
const app = nextJS({ dev });
const server = express();
await app.prepare()
.then(() => {
mongoose.connect(db, { useNewUrlParser: true })
mongoose.Promise = global.Promise
mongoose.connection
.on('connected', () => {
console.log(`Mongoose connection open on ${db}`)
})
.on('error', err => {
console.log(`Connection error: ${err.message}`)
});
})
.catch(err => {
console.error(err)
})
server.use('/uploads', express.static(__dirname + '/uploads'))
server.use(bodyParser.json())
server.use(bodyParser.urlencoded({ extended: true }))
server.use(morgan('dev'))
server.use(cookieParser())
server.use(session({
secret: 'very secret 12345',
resave: true,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
server.use(auth.initialize);
server.use(auth.session);
server.use(auth.setUser);
server.use(cors())
server.use('/users', require('./users'))
server.use('/images', require('./images'))
// Redirect all requests to main entrypoint pages/index.js
server.get('/*', async(req, res, next) => {
try {
// @NOTE code duplication from here
// https://github.com/zeit/next.js/blob/cc6fe5fdf92c9c618a739128fbd5192a6d397afa/packages/next-server/server/next-server.ts#L405
const pathName = req.originalUrl;
if (isInternalUrl(req.url)) {
return app.handleRequest(req, res, req.originalUrl)
}
if (isBlockedPage(pathName)) {
return app.render404(req, res, req.originalUrl)
}
// Provide react-router static router with a context object
// https://reacttraining.com/react-router/web/guides/server-rendering
req.locals = {};
req.locals.context = {};
const html = await app.renderToHTML(req, res, '/', {});
// Handle client redirects
const context = req.locals.context;
if (context.url) {
return res.redirect(context.url)
}
// Handle client response statuses
if (context.status) {
return res.status(context.status).send();
}
// Request was ended by the user
if (html === null) {
return;
}
app.sendHTML(req, res, html);
} catch (e) {
next(e);
}
});
server.use(function(req, res, next) {
res.status(404).send('404 - Not Found!');
});
// eslint-disable-next-line func-names
server.use(errorHandler, function(error, req, res, next) {
res.json({ message: error.message })
})
server.listen(PORT, err => {
if (err) throw err;
console.log(`> Ready on http://localhost:${PORT}`)
});
}
start();这是我的客户端组件:
import React, { Component } from 'react'
import { Loader, Dimmer, Transition, Button, Form, Grid, Header, Message, Segment } from 'semantic-ui-react'
import Link from 'next/link';
import { login } from 'next-authentication'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { logInUser } from '../../store/reducers/users/index'
class LoginForm extends Component {
constructor(props) {
super(props)
this.state = {
fadeUp: 'fade up',
duration: 500,
username: '',
password: '',
usernameError: false,
passwordError: false,
formSuccess: false,
formError: false,
isLoading: true,
}
this.handleChange = this.handleChange.bind(this)
this.handleBlur = this.handleBlur.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
componentDidMount() {
this.setState({ isLoading: false })
}
handleChange(event) {
var { name, value } = event.target;
this.setState({
[name]: value
})
}
handleBlur() {
var { username } = this.state;
var error = false;
var mailFormat = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if ((!username.match(mailFormat)) || (!username)) {
error = true;
this.setState({ usernameError: true });
} else {
this.setState({ usernameError: false, });
}
}
handleSubmit(event) {
event.preventDefault();
this.setState({
isLoading: true
})
var error = false;
var { username, password, isLoading } = this.state;
var { history } = this.props
var mailFormat = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
if (!username.match(mailFormat)) {
this.setState({ usernameError: true });
error = true;
} else {
this.setState({ usernameError: false });
}
if (password.length < 8) {
this.setState({ passwordError: true });
error = true;
} else {
this.setState({ passwordError: false })
}
if (error) {
this.setState({ formSuccess: false });
return;
}
return window.fetch('http://localhost:8016/users/login', {
method: 'POST',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify({ username: username, password: password })
})
.then((response) => {
console.log('response', response)
if (response.ok) {
const { token } = response.clone();
const loginOptions = {
token,
cookieOptions: { expires: 1 },
callback: () => history.push('/profile')
}
setTimeout(() => {
this.props.logInUser()
login(loginOptions);
}, 5000)
this.setState({
username: '' , password: '', formError: false, formSuccess: true, isLoading: false
})
return response.json();
} else if (!response.ok) {
if (response.status === 404) {
console.log("response.status ", response.status);
console.log("isLoading 2", isLoading)
this.setState({
formError: true, formSuccess: false, isLoading: false
});
return;
}
}
return response;
})
.catch(err => console.dir(err))
}
render() {
var { username, password, usernameError, passwordError, formSuccess, formError, duration, isLoading } = this.state;
console.log("LoginForm this.props ", this.props);
var { isLoggedIn } = this.props;
console.log("isLoggedIn ", isLoggedIn);
(formSuccess === true) ? isLoggedIn = true : isLoggedIn = false;
return (<div className='login-form'> {
}<style>{`body > div, body > div > div, body > div > div > div.login-form { height: 100%;}`} </style>
<Grid textAlign='center'
style={{ height: '100%' }}
verticalAlign='middle' >
<Grid.Column style={{ maxWidth: 450 }}>
<Header as='h2'
color='teal'
textAlign='center'>
Log-in to your account
</Header>
<Form size='large'
onSubmit={this.handleSubmit}
error={formError}>
<Segment stacked>
<Form.Input fluid icon='user'
iconPosition='left'
placeholder='E-mail address, e.g. joe@schmoe.com'
name='username'
value={username}
onBlur={this.handleBlur}
onChange={this.handleChange}
error={usernameError}
/>
<Transition visible={usernameError}
animation='scale'
duration={duration}>
<Message error content='username_Email is in incorrect format e.g. joe@schmoe.com' />
</Transition>
<Form.Input fluid icon='lock'
iconPosition='left'
placeholder='Password'
name='password'
value={password}
onBlur={this.handleBlur}
onChange={this.handleChange}
error={passwordError}
/>
<Transition visible={passwordError}
animation='scale'
duration={duration}>
<Message error content='Password is incorrect, please try again.' />
</Transition>
<Button color='teal'
fluid size='large'
disabled={!username || !password}>
Log-in
</Button>
<Transition visible={formError}
unmountOnHide={true}
animation='scale'
duration={duration}>
{isLoading ?
<Dimmer active inverted>
<Loader />
</Dimmer>
:
<Message
error
centered="true" header='This email does not exist...'
content='Please re-enter another email address, or click the link below to register.' />
}
</Transition>
<Transition visible={formSuccess}
unmountOnHide={true}
animation='scale'
duration={duration}>
{isLoading ?
<Dimmer active inverted>
<Loader />
</Dimmer>
:
<Message
success
header='Your have successfully logged in.'
content='Welcome to Hillfinder!' />
}
</Transition>
</Segment>
</Form>
{formError ?
<Transition visible={formError}
animation='scale'
duration={1000}>
<Message>
<Link href="/register">
<a>Register</a>
</Link> </Message>
</Transition>
: null
}
</Grid.Column> </Grid> </div>
)
}
}
function mapStateToProps(state) {
console.log("state ", state);
const { users } = state
console.log("users ", users);
const { isLoggedIn } = users
console.log("isLoggedIn ", isLoggedIn);
return { isLoggedIn }
}
const mapDispatchToProps = dispatch =>
bindActionCreators({ logInUser }, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(LoginForm)发布于 2019-12-30 07:15:45
我还没有测试整个东西,但bug可能就在这里:
var user = await UserModel.findOne({ username: username }).exec();使用async/await,您不会以这种方式执行承诺,您只需等待它:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
另一点是将bcrypt与passportJS相结合,我认为这不是正确的方法,请参阅文档:
http://www.passportjs.org/docs/username-password/#configuration
PassportJS有它自己的内置散列,所以它可能会混淆!:)
https://stackoverflow.com/questions/59521420
复制相似问题