mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-10 10:01:51 -04:00
fix(learn): advanced-node-and-express username field (#47349)
Co-authored-by: Kristofer Koishigawa <scissorsneedfoodtoo@gmail.com> Co-authored-by: Tom <20648924+moT01@users.noreply.github.com> Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
committed by
GitHub
parent
756013372e
commit
de02417bd5
@@ -8,13 +8,13 @@ dashedName: announce-new-users
|
||||
|
||||
# --description--
|
||||
|
||||
Many chat rooms are able to announce when a user connects or disconnects and then display that to all of the connected users in the chat. Seeing as though you already are emitting an event on connect and disconnect, you will just have to modify this event to support such a feature. The most logical way of doing so is sending 3 pieces of data with the event: the name of the user who connected/disconnected, the current user count, and if that name connected or disconnected.
|
||||
Many chat rooms are able to announce when a user connects or disconnects and then display that to all of the connected users in the chat. Seeing as though you already are emitting an event on connect and disconnect, you will just have to modify this event to support such a feature. The most logical way of doing so is sending 3 pieces of data with the event: the username of the user who connected/disconnected, the current user count, and if that username connected or disconnected.
|
||||
|
||||
Change the event name to `'user'`, and pass an object along containing the fields 'name', 'currentUsers', and 'connected' (to be `true` in case of connection, or `false` for disconnection of the user sent). Be sure to change both 'user count' events and set the disconnect one to send `false` for the field 'connected' instead of `true` like the event emitted on connect.
|
||||
Change the event name to `'user'`, and pass an object along containing the fields `username`, `currentUsers`, and `connected` (to be `true` in case of connection, or `false` for disconnection of the user sent). Be sure to change both `'user count'` events and set the disconnect one to send `false` for the field `connected` instead of `true` like the event emitted on connect.
|
||||
|
||||
```js
|
||||
io.emit('user', {
|
||||
name: socket.request.user.name,
|
||||
username: socket.request.user.username,
|
||||
currentUsers,
|
||||
connected: true
|
||||
});
|
||||
@@ -28,55 +28,49 @@ An implementation of this could look like the following:
|
||||
socket.on('user', data => {
|
||||
$('#num-users').text(data.currentUsers + ' users online');
|
||||
let message =
|
||||
data.name +
|
||||
data.username +
|
||||
(data.connected ? ' has joined the chat.' : ' has left the chat.');
|
||||
$('#messages').append($('<li>').html('<b>' + message + '</b>'));
|
||||
});
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out <a href="https://gist.github.com/camperbot/bf95a0f74b756cf0771cd62c087b8286" target="_blank" rel="noopener noreferrer nofollow">the project completed up to this point </a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135/3#announce-new-users-10" target="_blank" rel="noopener noreferrer nofollow">the project completed up to this point </a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Event `'user'` should be emitted with name, currentUsers, and connected.
|
||||
Event `'user'` should be emitted with `name`, `currentUsers`, and `connected`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io.emit.*('|")user\1.*name.*currentUsers.*connected/gis,
|
||||
'You should have an event emitted named user sending name, currentUsers, and connected'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io.emit.*('|")user\1.*name.*currentUsers.*connected/s,
|
||||
'You should have an event emitted named user sending name, currentUsers, and connected'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Client should properly handle and display the new data from event `'user'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user\1[^]*num-users/gi,
|
||||
'You should change the text of "#num-users" within on your client within the "user" event listener to show the current users connected'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user\1[^]*messages.*li/gi,
|
||||
'You should append a list item to "#messages" on your client within the "user" event listener to announce a user came or went'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/public/client.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user\1[^]*num-users/s,
|
||||
'You should change the text of "#num-users" within on your client within the "user" event listener to show the current users connected'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user\1[^]*messages.*li/s,
|
||||
'You should append a list item to "#messages" on your client within the "user" event listener to announce a user came or went'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -10,79 +10,74 @@ dashedName: authentication-strategies
|
||||
|
||||
A strategy is a way of authenticating a user. You can use a strategy for allowing users to authenticate based on locally saved information (if you have them register first) or from a variety of providers such as Google or GitHub. For this project, we will use Passport middleware. Passport provides a comprehensive set of strategies that support authentication using a username and password, GitHub, Google, and more.
|
||||
|
||||
`passport-local@~1.0.0` has already been added as a dependency, so add it to your server as follows: `const LocalStrategy = require('passport-local');`
|
||||
`passport-local@~1.0.0` has already been added as a dependency. Add it to your server as follows:
|
||||
|
||||
Now you will have to tell passport to **use** an instantiated LocalStrategy object with a few settings defined. Make sure this (as well as everything from this point on) is encapsulated in the database connection since it relies on it!
|
||||
|
||||
```js
|
||||
passport.use(new LocalStrategy(
|
||||
function(username, password, done) {
|
||||
myDataBase.findOne({ username: username }, function (err, user) {
|
||||
console.log('User '+ username +' attempted to log in.');
|
||||
if (err) { return done(err); }
|
||||
if (!user) { return done(null, false); }
|
||||
if (password !== user.password) { return done(null, false); }
|
||||
return done(null, user);
|
||||
});
|
||||
}
|
||||
));
|
||||
```javascript
|
||||
const LocalStrategy = require('passport-local');
|
||||
```
|
||||
|
||||
This is defining the process to use when we try to authenticate someone locally. First, it tries to find a user in our database with the username entered, then it checks for the password to match, then finally, if no errors have popped up that we checked for, like an incorrect password, the `user`'s object is returned and they are authenticated.
|
||||
Tell passport to **use** an instantiated `LocalStrategy` object with a few settings defined. Make sure this (as well as everything from this point on) is encapsulated in the database connection since it relies on it!:
|
||||
|
||||
Many strategies are set up using different settings, but generally it is easy to set it up based on the README in that strategy's repository. A good example of this is the GitHub strategy where we don't need to worry about a username or password because the user will be sent to GitHub's auth page to authenticate. As long as they are logged in and agree then GitHub returns their profile for us to use.
|
||||
```javascript
|
||||
passport.use(new LocalStrategy((username, password, done) => {
|
||||
myDataBase.findOne({ username: username }, (err, user) => {
|
||||
console.log(`User ${username} attempted to log in.`);
|
||||
if (err) return done(err);
|
||||
if (!user) return done(null, false);
|
||||
if (password !== user.password) return done(null, false);
|
||||
return done(null, user);
|
||||
});
|
||||
}));
|
||||
```
|
||||
|
||||
In the next step, we will set up how to actually call the authentication strategy to validate a user based on form data!
|
||||
This is defining the process to use when you try to authenticate someone locally. First, it tries to find a user in your database with the username entered. Then, it checks for the password to match. Finally, if no errors have popped up that you checked for (e.g. an incorrect password), the `user` object is returned and they are authenticated.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/53b495c02b92adeee0aa1bd3f3be8a4b" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Many strategies are set up using different settings. Generally, it is easy to set it up based on the README in that strategy's repository. A good example of this is the GitHub strategy where you don't need to worry about a username or password because the user will be sent to GitHub's auth page to authenticate. As long as they are logged in and agree then GitHub returns their profile for you to use.
|
||||
|
||||
In the next step, you will set up how to actually call the authentication strategy to validate a user based on form data.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#authentication-strategies-6" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Passport-local should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport-local',
|
||||
'Your project should list "passport-local " as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport-local',
|
||||
'Your project should list "passport-local " as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Passport-local should be correctly required and setup.
|
||||
Passport-local should be correctly required and set up.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport-local("|')/gi,
|
||||
'You should have required passport-local'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/new LocalStrategy/gi,
|
||||
'You should have told passport to use a new strategy'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/findOne/gi,
|
||||
'Your new local strategy should use the findOne query to find a username based on the inputs'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport-local("|')/,
|
||||
'You should have required passport-local'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/new LocalStrategy/,
|
||||
'You should have told passport to use a new strategy'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/findOne/,
|
||||
'Your new local strategy should use the findOne query to find a username based on the inputs'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -60,87 +60,73 @@ function onAuthorizeFail(data, message, error, accept) {
|
||||
The user object is now accessible on your socket object as `socket.request.user`. For example, now you can add the following:
|
||||
|
||||
```js
|
||||
console.log('user ' + socket.request.user.name + ' connected');
|
||||
console.log('user ' + socket.request.user.username + ' connected');
|
||||
```
|
||||
|
||||
It will log to the server console who has connected!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project up to this point <a href="https://gist.github.com/camperbot/1414cc9433044e306dd7fd0caa1c6254" target="_blank" rel="noopener noreferrer nofollow">https://gist.github.com/camperbot/1414cc9433044e306dd7fd0caa1c6254</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#authentication-with-socketio-9" target="_blank" rel="noopener noreferrer nofollow">check out the project up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
`passport.socketio` should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport.socketio',
|
||||
'Your project should list "passport.socketio" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport.socketio',
|
||||
'Your project should list "passport.socketio" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`cookie-parser` should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'cookie-parser',
|
||||
'Your project should list "cookie-parser" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'cookie-parser',
|
||||
'Your project should list "cookie-parser" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
passportSocketIo should be properly required.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require\((['"])passport\.socketio\1\)/gi,
|
||||
'You should correctly require and instantiate "passport.socketio"'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require\((['"])passport\.socketio\1\)/gi,
|
||||
'You should correctly require and instantiate "passport.socketio"'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
passportSocketIo should be properly setup.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io\.use\(\s*\w+\.authorize\(/,
|
||||
'You should register "passport.socketio" as socket.io middleware and provide it correct options'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io\.use\(\s*\w+\.authorize\(/,
|
||||
'You should register "passport.socketio" as socket.io middleware and provide it correct options'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -24,33 +24,30 @@ Finally, take all of the routes in your server and paste them into your new file
|
||||
|
||||
Keep adding them until no more errors exist, and your server file no longer has any routing (**except for the route in the catch block**)!
|
||||
|
||||
Now do the same thing in your auth.js file with all of the things related to authentication such as the serialization and the setting up of the local strategy and erase them from your server file. Be sure to add the dependencies in and call `auth(app, myDataBase)` in the server in the same spot.
|
||||
Do the same thing in your `auth.js` file with all of the things related to authentication such as the serialization and the setting up of the local strategy and erase them from your server file. Be sure to add the dependencies in and call `auth(app, myDataBase)` in the server in the same spot.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/2d06ac5c7d850d8cf073d2c2c794cc92" target="_blank" rel="noopener noreferrer nofollow">check out an example of the completed project</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#clean-up-your-project-with-modules-2" target="_blank" rel="noopener noreferrer nofollow">check out an example of the completed project</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Modules should be present.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require\s*\(('|")\.\/routes(\.js)?\1\)/gi,
|
||||
'You should have required your new files'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/client\s*\.db[^]*routes/gi,
|
||||
'Your new modules should be called after your connection to the database'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require\s*\(('|")\.\/routes(\.js)?\1\)/gi,
|
||||
'You should have required your new files'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/client\s*\.db[^]*routes/gi,
|
||||
'Your new modules should be called after your connection to the database'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -38,62 +38,53 @@ socket.on('user count', function(data) {
|
||||
|
||||
Now, try loading up your app, authenticate, and you should see in your client console '1' representing the current user count! Try loading more clients up, and authenticating to see the number go up.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/28ef7f1078f56eb48c7b1aeea35ba1f5" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#communicate-by-emitting-7" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
currentUsers should be defined.
|
||||
`currentUsers` should be defined.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/currentUsers/gi,
|
||||
'You should have variable currentUsers defined'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/currentUsers/s,
|
||||
'You should have variable currentUsers defined'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Server should emit the current user count at each new connection.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io.emit.*('|")user count('|").*currentUsers/gi,
|
||||
'You should emit "user count" with data currentUsers'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io.emit.*('|")user count('|").*currentUsers/s,
|
||||
'You should emit "user count" with data currentUsers'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Your client should be listening for 'user count' event.
|
||||
Your client should be listening for `'user count'` event.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user count('|")/gi,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/public/client.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user count('|")/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -8,11 +8,13 @@ dashedName: create-new-middleware
|
||||
|
||||
# --description--
|
||||
|
||||
As is, any user can just go to `/profile` whether they have authenticated or not, by typing in the url. We want to prevent this, by checking if the user is authenticated first before rendering the profile page. This is the perfect example of when to create a middleware.
|
||||
As is, any user can just go to `/profile` whether they have authenticated or not by typing in the URL. You want to prevent this by checking if the user is authenticated first before rendering the profile page. This is the perfect example of when to create a middleware.
|
||||
|
||||
The challenge here is creating the middleware function `ensureAuthenticated(req, res, next)`, which will check if a user is authenticated by calling passport's `isAuthenticated` method on the `request` which, in turn, checks if `req.user` is defined. If it is, then `next()` should be called, otherwise, we can just respond to the request with a redirect to our homepage to login. An implementation of this middleware is:
|
||||
The challenge here is creating the middleware function `ensureAuthenticated(req, res, next)`, which will check if a user is authenticated by calling Passport's `isAuthenticated` method on the `request` which checks if `req.user` is defined. If it is, then `next()` should be called. Otherwise, you can just respond to the request with a redirect to your homepage to login.
|
||||
|
||||
```js
|
||||
An implementation of this middleware is:
|
||||
|
||||
```javascript
|
||||
function ensureAuthenticated(req, res, next) {
|
||||
if (req.isAuthenticated()) {
|
||||
return next();
|
||||
@@ -21,59 +23,53 @@ function ensureAuthenticated(req, res, next) {
|
||||
};
|
||||
```
|
||||
|
||||
Now add *ensureAuthenticated* as a middleware to the request for the profile page before the argument to the get request containing the function that renders the page.
|
||||
Create the above middleware function, then pass `ensureAuthenticated` as middleware to requests for the profile page before the argument to the GET request:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
app
|
||||
.route('/profile')
|
||||
.get(ensureAuthenticated, (req,res) => {
|
||||
res.render(process.cwd() + '/views/pug/profile');
|
||||
res.render('profile');
|
||||
});
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/ae49b8778cab87e93284a91343da0959" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#create-new-middleware-8" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Middleware ensureAuthenticated should be implemented and on our /profile route.
|
||||
The middleware `ensureAuthenticated` should be implemented and attached to the `/profile` route.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/ensureAuthenticated[^]*req.isAuthenticated/gi,
|
||||
'Your ensureAuthenticated middleware should be defined and utilize the req.isAuthenticated function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/profile[^]*get[^]*ensureAuthenticated/gi,
|
||||
'Your ensureAuthenticated middleware should be attached to the /profile route'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/ensureAuthenticated[^]*req.isAuthenticated/,
|
||||
'Your ensureAuthenticated middleware should be defined and utilize the req.isAuthenticated function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/profile[^]*get[^]*ensureAuthenticated/,
|
||||
'Your ensureAuthenticated middleware should be attached to the /profile route'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
A Get request to /profile should correctly redirect to / since we are not authenticated.
|
||||
An unauthenticated GET request to `/profile` should correctly redirect to `/`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/profile').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Home page/gi,
|
||||
'An attempt to go to the profile at this point should redirect to the homepage since we are not logged in'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/profile", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/Home page/,
|
||||
'An attempt to go to the profile at this point should redirect to the homepage since we are not logged in'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -18,44 +18,38 @@ socket.on('disconnect', () => {
|
||||
});
|
||||
```
|
||||
|
||||
To make sure clients continuously have the updated count of current users, you should decrease the currentUsers by 1 when the disconnect happens then emit the 'user count' event with the updated count!
|
||||
To make sure clients continuously have the updated count of current users, you should decrease `currentUsers` by 1 when the disconnect happens then emit the `'user count'` event with the updated count.
|
||||
|
||||
**Note:** Just like `'disconnect'`, all other events that a socket can emit to the server should be handled within the connecting listener where we have 'socket' defined.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/ab1007b76069884fb45b215d3c4496fa" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#handle-a-disconnect-8" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Server should handle the event disconnect from a socket.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(data, /socket.on.*('|")disconnect('|")/gi, '');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(data, /socket.on.*('|")disconnect('|")/s, '');
|
||||
}
|
||||
```
|
||||
|
||||
Your client should be listening for 'user count' event.
|
||||
Your client should be listening for `'user count'` event.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user count('|")/gi,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/public/client.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user count('|")/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -12,9 +12,9 @@ Going back to the information security section, you may remember that storing pl
|
||||
|
||||
`bcrypt@~5.0.0` has already been added as a dependency, so require it in your server. You will need to handle hashing in 2 key areas: where you handle registering/saving a new account, and when you check to see that a password is correct on login.
|
||||
|
||||
Currently on our registration route, you insert a user's password into the database like so: `password: req.body.password`. An easy way to implement saving a hash instead is to add the following before your database logic `const hash = bcrypt.hashSync(req.body.password, 12);`, and replacing the `req.body.password` in the database saving with just `password: hash`.
|
||||
Currently on your registration route, you insert a user's plaintext password into the database like so: `password: req.body.password`. Hash the passwords instead by adding the following before your database logic: `const hash = bcrypt.hashSync(req.body.password, 12);`, and replacing the `req.body.password` in the database saving with just `password: hash`.
|
||||
|
||||
Finally, on our authentication strategy, we check for the following in our code before completing the process: `if (password !== user.password) { return done(null, false); }`. After making the previous changes, now `user.password` is a hash. Before making a change to the existing code, notice how the statement is checking if the password is **not** equal then return non-authenticated. With this in mind, your code could look as follows to properly check the password entered against the hash:
|
||||
On your authentication strategy, you check for the following in your code before completing the process: `if (password !== user.password) return done(null, false);`. After making the previous changes, now `user.password` is a hash. Before making a change to the existing code, notice how the statement is checking if the password is **not** equal then return non-authenticated. With this in mind, change that code to look as follows to properly check the password entered against the hash:
|
||||
|
||||
```js
|
||||
if (!bcrypt.compareSync(password, user.password)) {
|
||||
@@ -22,57 +22,50 @@ if (!bcrypt.compareSync(password, user.password)) {
|
||||
}
|
||||
```
|
||||
|
||||
That is all it takes to implement one of the most important security features when you have to store passwords!
|
||||
That is all it takes to implement one of the most important security features when you have to store passwords.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/dc16cca09daea4d4151a9c36a1fab564" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#hashing-your-passwords-1" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
BCrypt should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'bcrypt',
|
||||
'Your project should list "bcrypt" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json()
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'bcrypt',
|
||||
'Your project should list "bcrypt" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
BCrypt should be correctly required and implemented.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')bcrypt\1/gi,
|
||||
'You should have required bcrypt'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/bcrypt.hashSync/gi,
|
||||
'You should use hash the password in the registration'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/bcrypt.compareSync/gi,
|
||||
'You should compare the password to the hash in your strategy'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')bcrypt\1/gi,
|
||||
'You should have required bcrypt'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/bcrypt.hashSync/gi,
|
||||
'You should use hash the password in the registration'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/bcrypt.compareSync/gi,
|
||||
'You should compare the password to the hash in your strategy'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -8,42 +8,41 @@ dashedName: how-to-put-a-profile-together
|
||||
|
||||
# --description--
|
||||
|
||||
Now that we can ensure the user accessing the `/profile` is authenticated, we can use the information contained in `req.user` on our page!
|
||||
Now that you can ensure the user accessing the `/profile` is authenticated, you can use the information contained in `req.user` on your page.
|
||||
|
||||
Pass an object containing the property `username` and value of `req.user.username` as the second argument for the render method of the profile view. Then, go to your `profile.pug` view, and add the following line below the existing `h1` element, and at the same level of indentation:
|
||||
Pass an object containing the property `username` and value of `req.user.username` as the second argument for the `render` method of the profile view.
|
||||
|
||||
Then, go to your `profile.pug` view, and add the following line below the existing `h1` element, and at the same level of indentation:
|
||||
|
||||
```pug
|
||||
h2.center#welcome Welcome, #{username}!
|
||||
```
|
||||
|
||||
This creates an `h2` element with the class '`center`' and id '`welcome`' containing the text '`Welcome,`' followed by the username.
|
||||
This creates an `h2` element with the class `center` and id `welcome` containing the text `Welcome, ` followed by the username.
|
||||
|
||||
Also, in `profile.pug`, add a link referring to the `/logout` route, which will host the logic to unauthenticate a user.
|
||||
Also, in `profile.pug`, add a link referring to the `/logout` route, which will host the logic to unauthenticate a user:
|
||||
|
||||
```pug
|
||||
a(href='/logout') Logout
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/136b3ad611cc80b41cab6f74bb460f6a" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#how-to-put-a-profile-together-9" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should correctly add a Pug render variable to /profile.
|
||||
You should correctly add a Pug render variable to `/profile`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/username:( |)req.user.username/gi,
|
||||
'You should be passing the variable username with req.user.username into the render function of the profile page'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/username:( |)req.user.username/,
|
||||
'You should be passing the variable username with req.user.username into the render function of the profile page'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -8,64 +8,60 @@ dashedName: how-to-use-passport-strategies
|
||||
|
||||
# --description--
|
||||
|
||||
In the `index.pug` file supplied, there is actually a login form. It has previously been hidden because of the inline JavaScript `if showLogin` with the form indented after it. Before `showLogin` as a variable was never defined, so it never rendered the code block containing the form. Go ahead and on the `res.render` for that page add a new variable to the object `showLogin: true`. When you refresh your page, you should then see the form! This form is set up to **POST** on `/login`, so this is where we should set up to accept the POST and authenticate the user.
|
||||
In the `index.pug` file supplied, there is a login form. It is hidden because of the inline JavaScript `if showLogin` with the form indented after it.
|
||||
|
||||
For this challenge you should add the route `/login` to accept a POST request. To authenticate on this route, you need to add a middleware to do so before then sending a response. This is done by just passing another argument with the middleware before your `function(req,res)` with your response! The middleware to use is `passport.authenticate('local')`.
|
||||
In the `res.render` for that page, add a new variable to the object, `showLogin: true`. When you refresh your page, you should then see the form! This form is set up to **POST** on `/login`. So, this is where you should set up to accept the POST request and authenticate the user.
|
||||
|
||||
`passport.authenticate` can also take some options as an argument such as: `{ failureRedirect: '/' }` which is incredibly useful, so be sure to add that in as well. The response after using the middleware (which will only be called if the authentication middleware passes) should be to redirect the user to `/profile` and that route should render the view `profile.pug`.
|
||||
For this challenge, you should add the route `/login` to accept a POST request. To authenticate on this route, you need to add a middleware to do so before then sending a response. This is done by just passing another argument with the middleware before with your response. The middleware to use is `passport.authenticate('local')`.
|
||||
|
||||
`passport.authenticate` can also take some options as an argument such as `{ failureRedirect: '/' }` which is incredibly useful, so be sure to add that in as well. Add a response after using the middleware (which will only be called if the authentication middleware passes) that redirects the user to `/profile`. Add that route, as well, and make it render the view `profile.pug`.
|
||||
|
||||
If the authentication was successful, the user object will be saved in `req.user`.
|
||||
|
||||
At this point, if you enter a username and password in the form, it should redirect to the home page `/`, and the console of your server should display `'User {USERNAME} attempted to log in.'`, since we currently cannot login a user who isn't registered.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/7ad011ac54612ad53188b500c5e99cb9" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#how-to-use-passport-strategies-7" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
All steps should be correctly implemented in the server.js.
|
||||
All steps should be correctly implemented in `server.js`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/showLogin:( |)true/gi,
|
||||
'You should be passing the variable "showLogin" as true to your render function for the homepage'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/failureRedirect:( |)('|")\/('|")/gi,
|
||||
'Your code should include a failureRedirect to the "/" route'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/login[^]*post[^]*local/gi,
|
||||
'You should have a route for login which accepts a POST and passport.authenticates local'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/showLogin:( |)true/,
|
||||
'You should be passing the variable "showLogin" as true to your render function for the homepage'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/failureRedirect:( |)('|")\/('|")/,
|
||||
'Your code should include a failureRedirect to the "/" route'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/login[^]*post[^]*local/,
|
||||
'You should have a route for login which accepts a POST and passport.authenticates local'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
A POST request to /login should correctly redirect to /.
|
||||
A POST request to `/login` should correctly redirect to `/`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.post(getUserInput('url') + '/login').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Looks like this page is being rendered from Pug into HTML!/gi,
|
||||
'A login attempt at this point should redirect to the homepage since we do not have any registered users'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/login", getUserInput("url"));
|
||||
const res = await fetch(url, { method: 'POST' });
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/Looks like this page is being rendered from Pug into HTML!/,
|
||||
'A login attempt at this point should redirect to the homepage since we do not have any registered users'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -8,20 +8,20 @@ dashedName: implement-the-serialization-of-a-passport-user
|
||||
|
||||
# --description--
|
||||
|
||||
Right now, we're not loading an actual user object since we haven't set up our database. This can be done many different ways, but for our project we will connect to the database once when we start the server and keep a persistent connection for the full life-cycle of the app. To do this, add your database's connection string (for example: `mongodb+srv://:@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority`) to the environment variable `MONGO_URI`. This is used in the `connection.js` file.
|
||||
You are not loading an actual user object since the database is not set up. Connect to the database once, when you start the server, and keep a persistent connection for the full life-cycle of the app. To do this, add your database's connection string (for example: `mongodb+srv://<username>:<password>@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority`) to the environment variable `MONGO_URI`. This is used in the `connection.js` file.
|
||||
|
||||
*If you are having issues setting up a free database on MongoDB Atlas, check out <a href="https://www.freecodecamp.org/news/get-started-with-mongodb-atlas/" target="_blank" rel="noopener noreferrer nofollow">tutorial</a>.*
|
||||
*If you are having issues setting up a free database on MongoDB Atlas, check out this <a href="https://www.freecodecamp.org/news/get-started-with-mongodb-atlas/" target="_blank" rel="noopener noreferrer nofollow">tutorial</a>.*
|
||||
|
||||
Now we want to connect to our database then start listening for requests. The purpose of this is to not allow requests before our database is connected or if there is a database error. To accomplish this, you will want to encompass your serialization and your app routes in the following code:
|
||||
Now you want to connect to your database, then start listening for requests. The purpose of this is to not allow requests before your database is connected or if there is a database error. To accomplish this, encompass your serialization and app routes in the following code:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
myDB(async client => {
|
||||
const myDataBase = await client.db('database').collection('users');
|
||||
|
||||
// Be sure to change the title
|
||||
app.route('/').get((req, res) => {
|
||||
//Change the response to render the Pug template
|
||||
res.render('pug', {
|
||||
// Change the response to render the Pug template
|
||||
res.render('index', {
|
||||
title: 'Connected to Database',
|
||||
message: 'Please login'
|
||||
});
|
||||
@@ -32,7 +32,7 @@ myDB(async client => {
|
||||
// Be sure to add this...
|
||||
}).catch(e => {
|
||||
app.route('/').get((req, res) => {
|
||||
res.render('pug', { title: e, message: 'Unable to login' });
|
||||
res.render('index', { title: e, message: 'Unable to connect to database' });
|
||||
});
|
||||
});
|
||||
// app.listen out here...
|
||||
@@ -40,44 +40,38 @@ myDB(async client => {
|
||||
|
||||
Be sure to uncomment the `myDataBase` code in `deserializeUser`, and edit your `done(null, null)` to include the `doc`.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/175f2f585a2d8034044c7e8857d5add7" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#implement-the-serialization-of-a-passport-user-5" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Database connection should be present.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Connected to Database/gi,
|
||||
'You successfully connected to the database!'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/Connected to Database/gi,
|
||||
'You successfully connected to the database!'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Deserialization should now be correctly using the DB and `done(null, null)` should be called with the `doc`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*doc/gi,
|
||||
'The callback in deserializeUser of (null, null) should be altered to (null, doc)'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*doc/gi,
|
||||
'The callback in deserializeUser of (null, null) should be altered to (null, doc)'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -12,7 +12,7 @@ The last part of setting up your GitHub authentication is to create the strategy
|
||||
|
||||
To set up the GitHub strategy, you have to tell Passport to use an instantiated `GitHubStrategy`, which accepts 2 arguments: an object (containing `clientID`, `clientSecret`, and `callbackURL`) and a function to be called when a user is successfully authenticated, which will determine if the user is new and what fields to save initially in the user's database object. This is common across many strategies, but some may require more information as outlined in that specific strategy's GitHub README. For example, Google requires a *scope* as well which determines what kind of information your request is asking to be returned and asks the user to approve such access.
|
||||
|
||||
The current strategy we are implementing authenticates users using a GitHub account and OAuth 2.0 tokens. The client ID and secret obtained when creating an application are supplied as options when creating the strategy. The strategy also requires a `verify` callback, which receives the access token and optional refresh token, as well as `profile` which contains the authenticated user's GitHub profile. The `verify` callback must call `cb` providing a user to complete authentication.
|
||||
The current strategy you are implementing authenticates users using a GitHub account and OAuth 2.0 tokens. The client ID and secret obtained when creating an application are supplied as options when creating the strategy. The strategy also requires a `verify` callback, which receives the access token and optional refresh token, as well as `profile` which contains the authenticated user's GitHub profile. The `verify` callback must call `cb` providing a user to complete authentication.
|
||||
|
||||
Here's how your new strategy should look at this point:
|
||||
|
||||
@@ -24,85 +24,75 @@ passport.use(new GitHubStrategy({
|
||||
},
|
||||
function(accessToken, refreshToken, profile, cb) {
|
||||
console.log(profile);
|
||||
//Database logic here with callback containing our user object
|
||||
//Database logic here with callback containing your user object
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
Your authentication won't be successful yet, and it will actually throw an error without the database logic and callback, but it should log your GitHub profile to your console if you try it!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/ff3a1166684c1b184709ac0bee30dee6" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#implementation-of-social-authentication-ii-4" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
passport-github dependency should be added.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport-github',
|
||||
'Your project should list "passport-github" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport-github',
|
||||
'Your project should list "passport-github" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
passport-github should be required.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/auth.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport-github("|')/gi,
|
||||
'You should have required passport-github'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/auth.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport-github("|')/gi,
|
||||
'You should have required passport-github'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
GitHub strategy should be setup correctly thus far.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/auth.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/passport\.use.*new GitHubStrategy/gis,
|
||||
'Passport should use a new GitHubStrategy'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/callbackURL:\s*("|').*("|')/gi,
|
||||
'You should have a callbackURL'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/process\.env(\.GITHUB_CLIENT_SECRET|\[(?<q>"|')GITHUB_CLIENT_SECRET\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_SECRET'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/process\.env(\.GITHUB_CLIENT_ID|\[(?<q>"|')GITHUB_CLIENT_ID\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_ID'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/auth.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/passport\.use.*new GitHubStrategy/gis,
|
||||
'Passport should use a new GitHubStrategy'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/callbackURL:\s*("|').*("|')/gi,
|
||||
'You should have a callbackURL'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/process\.env(\.GITHUB_CLIENT_SECRET|\[(?<q>"|')GITHUB_CLIENT_SECRET\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_SECRET'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/process\.env(\.GITHUB_CLIENT_ID|\[(?<q>"|')GITHUB_CLIENT_ID\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_ID'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -16,6 +16,7 @@ myDataBase.findOneAndUpdate(
|
||||
{
|
||||
$setOnInsert: {
|
||||
id: profile.id,
|
||||
username: profile.username,
|
||||
name: profile.displayName || 'John Doe',
|
||||
photo: profile.photos[0].value || '',
|
||||
email: Array.isArray(profile.emails)
|
||||
@@ -40,33 +41,30 @@ myDataBase.findOneAndUpdate(
|
||||
|
||||
`findOneAndUpdate` allows you to search for an object and update it. If the object doesn't exist, it will be inserted and made available to the callback function. In this example, we always set `last_login`, increment the `login_count` by `1`, and only populate the majority of the fields when a new object (new user) is inserted. Notice the use of default values. Sometimes a profile returned won't have all the information filled out or the user will keep it private. In this case, you handle it to prevent an error.
|
||||
|
||||
You should be able to login to your app now--try it!
|
||||
You should be able to login to your app now. Try it!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/183e968f0e01d81dde015d45ba9d2745" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#implementation-of-social-authentication-iii-5" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
GitHub strategy setup should be complete.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/auth.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/GitHubStrategy[^]*myDataBase/gi,
|
||||
'Strategy should use now use the database to search for the user'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/GitHubStrategy[^]*return cb/gi,
|
||||
'Strategy should return the callback function "cb"'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/auth.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/GitHubStrategy[^]*myDataBase/gi,
|
||||
'Strategy should use now use the database to search for the user'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/GitHubStrategy[^]*return cb/gi,
|
||||
'Strategy should return the callback function "cb"'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -10,19 +10,21 @@ dashedName: implementation-of-social-authentication
|
||||
|
||||
The basic path this kind of authentication will follow in your app is:
|
||||
|
||||
1. User clicks a button or link sending them to our route to authenticate using a specific strategy (e.g. GitHub).
|
||||
1. User clicks a button or link sending them to your route to authenticate using a specific strategy (e.g. GitHub).
|
||||
2. Your route calls `passport.authenticate('github')` which redirects them to GitHub.
|
||||
3. The page the user lands on, on GitHub, allows them to login if they aren't already. It then asks them to approve access to their profile from our app.
|
||||
4. The user is then returned to our app at a specific callback url with their profile if they are approved.
|
||||
3. The page the user lands on, on GitHub, allows them to login if they aren't already. It then asks them to approve access to their profile from your app.
|
||||
4. The user is then returned to your app at a specific callback url with their profile if they are approved.
|
||||
5. They are now authenticated, and your app should check if it is a returning profile, or save it in your database if it is not.
|
||||
|
||||
Strategies with OAuth require you to have at least a *Client ID* and a *Client Secret* which is a way for the service to verify who the authentication request is coming from and if it is valid. These are obtained from the site you are trying to implement authentication with, such as GitHub, and are unique to your app--**THEY ARE NOT TO BE SHARED** and should never be uploaded to a public repository or written directly in your code. A common practice is to put them in your `.env` file and reference them like so: `process.env.GITHUB_CLIENT_ID`. For this challenge we're going to use the GitHub strategy.
|
||||
Strategies with OAuth require you to have at least a *Client ID* and a *Client Secret* which is a way for the service to verify who the authentication request is coming from and if it is valid. These are obtained from the site you are trying to implement authentication with, such as GitHub, and are unique to your app--**THEY ARE NOT TO BE SHARED** and should never be uploaded to a public repository or written directly in your code. A common practice is to put them in your `.env` file and reference them like so: `process.env.GITHUB_CLIENT_ID`. For this challenge you are going to use the GitHub strategy.
|
||||
|
||||
Obtaining your *Client ID and Secret* from GitHub is done in your account profile settings under 'developer settings', then <a href="https://github.com/settings/developers" target="_blank" rel="noopener noreferrer nofollow">'OAuth applications'</a>. Click 'Register a new application', name your app, paste in the url to your Replit homepage (**Not the project code's url**), and lastly, for the callback url, paste in the same url as the homepage but with `/auth/github/callback` added on. This is where users will be redirected for us to handle after authenticating on GitHub. Save the returned information as `'GITHUB_CLIENT_ID'` and `'GITHUB_CLIENT_SECRET'` in your `.env` file.
|
||||
Follow these instructions to obtain your *Client ID and Secret* from GitHub. Go to your GitHub profile settings and click 'developer settings', then <a href="https://github.com/settings/developers" target="_blank" rel="noopener noreferrer nofollow">'OAuth Apps'</a>. Click 'New OAuth App', then give your app a name, paste in the URL to your Replit homepage (**Not the project code's url**) and, for the callback URL, paste in the same URL as the homepage but add `/auth/github/callback` to the end of it. This is where users will be redirected after authenticating on GitHub. After you do all that, click 'Register application'.
|
||||
|
||||
In your `routes.js` file, add `showSocialAuth: true` to the homepage route, after `showRegistration: true`. Now, create 2 routes accepting GET requests: `/auth/github` and `/auth/github/callback`. The first should only call passport to authenticate `'github'`. The second should call passport to authenticate `'github'` with a failure redirect to `/`, and then if that is successful redirect to `/profile` (similar to our last project).
|
||||
On the next page, click 'Generate a new client secret' to create a new client secret. Save the client ID and your client secret in your project's `.env` file as `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`.
|
||||
|
||||
An example of how `/auth/github/callback` should look is similar to how we handled a normal login:
|
||||
In your `routes.js` file, add `showSocialAuth: true` to the homepage route, after `showRegistration: true`. Now, create 2 routes accepting GET requests: `/auth/github` and `/auth/github/callback`. The first should only call passport to authenticate `'github'`. The second should call passport to authenticate `'github'` with a failure redirect to `/`, and then if that is successful redirect to `/profile` (similar to your last project).
|
||||
|
||||
An example of how `/auth/github/callback` should look is similar to how you handled a normal login:
|
||||
|
||||
```js
|
||||
app.route('/login')
|
||||
@@ -31,7 +33,7 @@ app.route('/login')
|
||||
});
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project up to this point <a href="https://gist.github.com/camperbot/1f7f6f76adb178680246989612bea21e" target="_blank" rel="noopener noreferrer nofollow">https://gist.github.com/camperbot/1f7f6f76adb178680246989612bea21e</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#implementation-of-social-authentication-3" target="_blank" rel="noopener noreferrer nofollow">check out the project up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ dashedName: logging-a-user-out
|
||||
|
||||
# --description--
|
||||
|
||||
Creating the logout logic is easy. The route should just unauthenticate the user and redirect to the home page instead of rendering any view.
|
||||
Creating the logout logic is easy. The route should just unauthenticate the user, and redirect to the home page instead of rendering any view.
|
||||
|
||||
In passport, unauthenticating a user is as easy as just calling `req.logout();` before redirecting.
|
||||
In passport, unauthenticating a user is as easy as just calling `req.logout()` before redirecting. Add this `/logout` route to do that:
|
||||
|
||||
```js
|
||||
app.route('/logout')
|
||||
@@ -20,7 +20,7 @@ app.route('/logout')
|
||||
});
|
||||
```
|
||||
|
||||
You may have noticed that we're not handling missing pages (404). The common way to handle this in Node is with the following middleware. Go ahead and add this in after all your other routes:
|
||||
You may have noticed that you are not handling missing pages (404). The common way to handle this in Node is with the following middleware. Go ahead and add this in after all your other routes:
|
||||
|
||||
```js
|
||||
app.use((req, res, next) => {
|
||||
@@ -30,44 +30,38 @@ app.use((req, res, next) => {
|
||||
});
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/c3eeb8a3ebf855e021fd0c044095a23b" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#logging-a-user-out-10" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
`req.logout()` should be called in your `/logout` route.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/req.logout/gi,
|
||||
'You should be calling req.logout() in your /logout route'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/req.logout/gi,
|
||||
'You should be calling req.logout() in your /logout route'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Logout should redirect to the home page.
|
||||
`/logout` should redirect to the home page.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/logout').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Home page/gi,
|
||||
'When a user logs out they should be redirected to the homepage'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/logout", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/Home page/gi,
|
||||
'When a user logs out they should be redirected to the homepage'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -8,16 +8,25 @@ dashedName: registration-of-new-users
|
||||
|
||||
# --description--
|
||||
|
||||
Now we need to allow a new user on our site to register an account. On the `res.render` for the home page add a new variable to the object passed along--`showRegistration: true`. When you refresh your page, you should then see the registration form that was already created in your `index.pug` file! This form is set up to **POST** on `/register`, so this is where we should set up to accept the **POST** and create the user object in the database.
|
||||
Now you need to allow a new user on your site to register an account. In the `res.render` for the home page add a new variable to the object passed along - `showRegistration: true`. When you refresh your page, you should then see the registration form that was already created in your `index.pug` file. This form is set up to **POST** on `/register`, so create that route and have it add the user object to the database by following the logic below.
|
||||
|
||||
The logic of the registration route should be as follows: Register the new user > Authenticate the new user > Redirect to /profile
|
||||
The logic of the registration route should be as follows:
|
||||
|
||||
The logic of step 1, registering the new user, should be as follows: Query database with a findOne command > if user is returned then it exists and redirect back to home *OR* if user is undefined and no error occurs then 'insertOne' into the database with the username and password, and, as long as no errors occur, call *next* to go to step 2, authenticating the new user, which we've already written the logic for in our POST */login* route.
|
||||
1. Register the new user
|
||||
2. Authenticate the new user
|
||||
3. Redirect to `/profile`
|
||||
|
||||
The logic of step 1 should be as follows:
|
||||
|
||||
1. Query database with `findOne`
|
||||
2. If there is an error, call `next` with the error
|
||||
3. If a user is returned, redirect back to home
|
||||
4. If a user is not found and no errors occur, then `insertOne` into the database with the username and password. As long as no errors occur there, call `next` to go to step 2, authenticating the new user, which you already wrote the logic for in your `POST /login` route.
|
||||
|
||||
```js
|
||||
app.route('/register')
|
||||
.post((req, res, next) => {
|
||||
myDataBase.findOne({ username: req.body.username }, function(err, user) {
|
||||
myDataBase.findOne({ username: req.body.username }, (err, user) => {
|
||||
if (err) {
|
||||
next(err);
|
||||
} else if (user) {
|
||||
@@ -47,33 +56,30 @@ app.route('/register')
|
||||
);
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/b230a5b3bbc89b1fa0ce32a2aa7b083e" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#registration-of-new-users-11" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
**NOTE:** From this point onwards, issues can arise relating to the use of the *picture-in-picture* browser. If you are using an online IDE which offers a preview of the app within the editor, it is recommended to open this preview in a new tab.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should register route and display on home.
|
||||
You should have a `/register` route and display a registration form on the home page.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/showRegistration:( |)true/gi,
|
||||
'You should be passing the variable showRegistration as true to your render function for the homepage'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi,
|
||||
'You should have a route accepted a post request on register that querys the db with findone and the query being username: req.body.username'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/showRegistration:( |)true/gi,
|
||||
'You should be passing the variable showRegistration as true to your render function for the homepage'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi,
|
||||
'You should have a route that accepts a POST request on /register that queries the db with findOne and the query being username: req.body.username'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Registering should work.
|
||||
|
||||
@@ -22,50 +22,44 @@ Within the form submit code, you should emit an event after you define `messageT
|
||||
socket.emit('chat message', messageToSend);
|
||||
```
|
||||
|
||||
Now, on your server, you should be listening to the socket for the event `'chat message'` with the data being named `message`. Once the event is received, it should emit the event `'chat message'` to all sockets `io.emit` with the data being an object containing `name` and `message`.
|
||||
Now, on your server, you should be listening to the socket for the event `'chat message'` with the data being named `message`. Once the event is received, it should emit the event `'chat message'` to all sockets using `io.emit`, sending a data object containing the `username` and `message`.
|
||||
|
||||
In `client.js`, you should now listen for event `'chat message'` and, when received, append a list item to `#messages` with the name, a colon, and the message!
|
||||
In `client.js`, you should now listen for event `'chat message'` and, when received, append a list item to `#messages` with the username, a colon, and the message!
|
||||
|
||||
At this point, the chat should be fully functional and sending messages across all clients!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/d7af9864375207e254f73262976d2016" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#send-and-display-chat-messages-11" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Server should listen for `'chat message'` and then emit it properly.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*name.*message/gis,
|
||||
'Your server should listen to the socket for "chat message" then emit to all users "chat message" with name and message in the data object'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*username.*message/s,
|
||||
'Your server should listen to the socket for "chat message" then emit to all users "chat message" with name and message in the data object'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Client should properly handle and display the new data from event `'chat message'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")chat message('|")[^]*messages.*li/gis,
|
||||
'You should append a list item to #messages on your client within the "chat message" event listener to display the new message'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/public/client.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")chat message('|")[^]*messages.*li/s,
|
||||
'You should append a list item to #messages on your client within the "chat message" event listener to display the new message'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -10,11 +10,23 @@ dashedName: serialization-of-a-user-object
|
||||
|
||||
Serialization and deserialization are important concepts in regards to authentication. To serialize an object means to convert its contents into a small *key* that can then be deserialized into the original object. This is what allows us to know who has communicated with the server without having to send the authentication data, like the username and password, at each request for a new page.
|
||||
|
||||
To set this up properly, we need to have a serialize function and a deserialize function. In Passport, we create these with `passport.serializeUser( OURFUNCTION )` and `passport.deserializeUser( OURFUNCTION )`
|
||||
To set this up properly, you need to have a serialize function and a deserialize function. In Passport, these can be created with:
|
||||
|
||||
The `serializeUser` is called with 2 arguments, the full user object and a callback used by passport. A unique key to identify that user should be returned in the callback, the easiest one to use being the user's `_id` in the object. It should be unique as it is generated by MongoDB. Similarly, `deserializeUser` is called with that key and a callback function for passport as well, but, this time, we have to take that key and return the full user object to the callback. To make a query search for a Mongo `_id`, you will have to create `const ObjectID = require('mongodb').ObjectID;`, and then to use it you call `new ObjectID(THE_ID)`. `mongodb@~3.6.0` has already been added as a dependency. You can see this in the examples below:
|
||||
```javascript
|
||||
passport.serializeUser(cb);
|
||||
passport.deserializeUser(cb);
|
||||
```
|
||||
|
||||
```js
|
||||
The callback function passed to `serializeUser` is called with two arguments: the full user object, and a callback used by passport.
|
||||
|
||||
The callback expects two arguments: An error, if any, and a unique key to identify the user that should be returned in the callback. You will use the user's `_id` in the object. This is guaranteed to be unique, as it is generated by MongoDB.
|
||||
|
||||
Similarly, `deserializeUser` is called with two arguments: the unique key, and a callback function.
|
||||
|
||||
This callback expects two arguments: An error, if any, and the full user object. To get the full user object, make a query search for a Mongo `_id`, as shown below:
|
||||
|
||||
|
||||
```javascript
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user._id);
|
||||
});
|
||||
@@ -26,98 +38,91 @@ passport.deserializeUser((id, done) => {
|
||||
});
|
||||
```
|
||||
|
||||
NOTE: This `deserializeUser` will throw an error until we set up the DB in the next step, so for now comment out the whole block and just call `done(null, null)` in the function `deserializeUser`.
|
||||
Add the two functions above to your server. The `ObjectID` class comes from the `mongodb` package. `mongodb@~3.6.0` has already been added as a dependency. Declare this class with:
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/7068a0d09e61ec7424572b366751f048" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
```javascript
|
||||
const { ObjectID } = require('mongodb');
|
||||
```
|
||||
|
||||
The `deserializeUser` will throw an error until you set up the database connection. So, for now, comment out the `myDatabase.findOne` call, and just call `done(null, null)` in the `deserializeUser` callback function.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#serialization-of-a-user-object-4" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should serialize user function correctly.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/passport.serializeUser/gi,
|
||||
'You should have created your passport.serializeUser function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*user._id/gi,
|
||||
'There should be a callback in your serializeUser with (null, user._id)'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/passport.serializeUser/gi,
|
||||
'You should have created your passport.serializeUser function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*user._id/gi,
|
||||
'There should be a callback in your serializeUser with (null, user._id)'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You should deserialize user function correctly.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/passport.deserializeUser/gi,
|
||||
'You should have created your passport.deserializeUser function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*null/gi,
|
||||
'There should be a callback in your deserializeUser with (null, null) for now'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/passport.deserializeUser/gi,
|
||||
'You should have created your passport.deserializeUser function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*null/gi,
|
||||
'There should be a callback in your deserializeUser with (null, null) for now'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
MongoDB should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'mongodb',
|
||||
'Your project should list "mongodb" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'mongodb',
|
||||
'Your project should list "mongodb" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Mongodb should be properly required including the ObjectId.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')mongodb\1/gi,
|
||||
'You should have required mongodb'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/new ObjectID.*id/gi,
|
||||
'Even though the block is commented out, you should use new ObjectID(id) for when we add the database'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')mongodb\1/gi,
|
||||
'You should have required mongodb'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/new ObjectID.*id/gi,
|
||||
'Even though the block is commented out, you should use new ObjectID(id) for when we add the database'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -20,89 +20,87 @@ A template engine enables you to use static template files (such as those writte
|
||||
|
||||
`pug@~3.0.0` has already been installed, and is listed as a dependency in your `package.json` file.
|
||||
|
||||
Express needs to know which template engine you are using. We will use the `set` method to assign `pug` as the `view engine` property's value: `app.set('view engine', 'pug')`
|
||||
Express needs to know which template engine you are using. Use the `set` method to assign `pug` as the `view engine` property's value:
|
||||
|
||||
Your page will be blank until you correctly render the index file in the `views/pug` directory.
|
||||
```javascript
|
||||
app.set('view engine', 'pug');
|
||||
```
|
||||
|
||||
To render the `pug` template, you need to use `res.render()` in the `/` route. Pass the file path to the `views/pug` directory as the argument to the method. The path can be a relative path (relative to views), or an absolute path, and does not require a file extension.
|
||||
After that, add another `set` method that sets the `views` property of your `app` to point to the `./views/pug` directory. This tells Express to render all views relative to that directory.
|
||||
|
||||
If all went as planned, your app home page will no longer be blank and will display a message indicating you've successfully rendered the Pug template!
|
||||
Finally, use `res.render()` in the route for your home page, passing `index` as the first argument. This will render the `pug` template.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/3515cd676ea4dfceab4e322f59a37791" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
If all went as planned, your app home page will no longer be blank. Instead, it will display a message indicating you've successfully rendered the Pug template!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#set-up-a-template-engine-1" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Pug should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'pug',
|
||||
'Your project should list "pug" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'pug',
|
||||
'Your project should list "pug" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
View engine should be Pug.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/('|")view engine('|"),( |)('|")pug('|")/gi,
|
||||
'Your project should set Pug as a view engine'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/app", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const app = await res.json();
|
||||
assert.equal(app?.settings?.['view engine'], "pug");
|
||||
}
|
||||
```
|
||||
|
||||
You should set the `views` property of the application to `./views/pug`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/app", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const app = await res.json();
|
||||
assert.equal(app?.settings?.views, "./views/pug");
|
||||
}
|
||||
```
|
||||
|
||||
Use the correct ExpressJS method to render the index page from the response.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/').then(
|
||||
(data) => {
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/FCC Advanced Node and Express/gi,
|
||||
'You successfully rendered the Pug template!'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Pug should be working.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/').then(
|
||||
(data) => {
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/pug-success-message/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -8,15 +8,15 @@ dashedName: set-up-passport
|
||||
|
||||
# --description--
|
||||
|
||||
It's time to set up *Passport* so we can finally start allowing a user to register or login to an account! In addition to Passport, we will use Express-session to handle sessions. Express-session has a ton of advanced features you can use, but for now we're just going to use the basics! Using this middleware saves the session id as a cookie in the client and allows us to access the session data using that id on the server. This way we keep personal account information out of the cookie used by the client to verify to our server they are authenticated and just keep the *key* to access the data stored on the server.
|
||||
It's time to set up *Passport* so you can finally start allowing a user to register or log in to an account. In addition to Passport, you will use Express-session to handle sessions. Express-session has a ton of advanced features you can use, but for now you are just going to use the basics. Using this middleware saves the session id as a cookie in the client, and allows us to access the session data using that id on the server. This way, you keep personal account information out of the cookie used by the client to tell to your server clients are authenticated and keep the *key* to access the data stored on the server.
|
||||
|
||||
`passport@~0.4.1` and `express-session@~1.17.1` are already installed, and are both listed as dependencies in your `package.json` file.
|
||||
|
||||
You will need to set up the session settings now and initialize Passport. Be sure to first create the variables 'session' and 'passport' to require 'express-session' and 'passport' respectively.
|
||||
You will need to set up the session settings and initialize Passport. First, create the variables `session` and `passport` to require `express-session` and `passport` respectively.
|
||||
|
||||
To set up your express app to use the session we'll define just a few basic options. Be sure to add 'SESSION_SECRET' to your .env file and give it a random value. This is used to compute the hash used to encrypt your cookie!
|
||||
Then, set up your Express app to use the session by defining the following options:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
resave: true,
|
||||
@@ -25,98 +25,79 @@ app.use(session({
|
||||
}));
|
||||
```
|
||||
|
||||
As well you can go ahead and tell your express app to **use** 'passport.initialize()' and 'passport.session()'. (For example, `app.use(passport.initialize());`)
|
||||
Be sure to add `SESSION_SECRET` to your `.env` file, and give it a random value. This is used to compute the hash used to encrypt your cookie!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/4068a7662a2f9f5d5011074397d6788c" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
After you do all that, tell your express app to **use** `passport.initialize()` and `passport.session()`.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#set-up-passport-3" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Passport and Express-session should be dependencies.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport',
|
||||
'Your project should list "passport" as a dependency'
|
||||
);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'express-session',
|
||||
'Your project should list "express-session" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport',
|
||||
'Your project should list "passport" as a dependency'
|
||||
);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'express-session',
|
||||
'Your project should list "express-session" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Dependencies should be correctly required.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport("|')/gi,
|
||||
'You should have required passport'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')express-session("|')/gi,
|
||||
'You should have required express-session'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport("|')/,
|
||||
'You should have required passport'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')express-session("|')/,
|
||||
'You should have required express-session'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Express app should use new dependencies.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/passport.initialize/gi,
|
||||
'Your express app should use "passport.initialize()"'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/passport.session/gi,
|
||||
'Your express app should use "passport.session()"'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(data, /passport\.initialize/, 'Your express app should use "passport.initialize()"');
|
||||
assert.match(data, /passport\.session/, 'Your express app should use "passport.session()"');
|
||||
}
|
||||
```
|
||||
|
||||
Session and session secret should be correctly set up.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/secret *: *process\.env(\.SESSION_SECRET|\[(?<q>"|')SESSION_SECRET\k<q>\])/g,
|
||||
'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/secret *:\s*process\.env(\.SESSION_SECRET|\[(?<q>"|')SESSION_SECRET\k<q>\])/,
|
||||
'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -19,7 +19,7 @@ const io = require('socket.io')(http);
|
||||
|
||||
Now that the *http* server is mounted on the *express app*, you need to listen from the *http* server. Change the line with `app.listen` to `http.listen`.
|
||||
|
||||
The first thing needing to be handled is listening for a new connection from the client. The <dfn>on</dfn> keyword does just that- listen for a specific event. It requires 2 arguments: a string containing the title of the event that's emitted, and a function with which the data is passed through. In the case of our connection listener, we use *socket* to define the data in the second argument. A socket is an individual client who is connected.
|
||||
The first thing needing to be handled is listening for a new connection from the client. The <dfn>on</dfn> keyword does just that- listen for a specific event. It requires 2 arguments: a string containing the title of the event that's emitted, and a function with which the data is passed through. In the case of our connection listener, use `socket` to define the data in the second argument. A socket is an individual client who is connected.
|
||||
|
||||
To listen for connections to your server, add the following within your database connection:
|
||||
|
||||
@@ -36,105 +36,89 @@ Now for the client to connect, you just need to add the following to your `clien
|
||||
let socket = io();
|
||||
```
|
||||
|
||||
The comment suppresses the error you would normally see since 'io' is not defined in the file. We've already added a reliable CDN to the Socket.IO library on the page in chat.pug.
|
||||
The comment suppresses the error you would normally see since 'io' is not defined in the file. You have already added a reliable CDN to the Socket.IO library on the page in `chat.pug`.
|
||||
|
||||
Now try loading up your app and authenticate and you should see in your server console 'A user has connected'!
|
||||
Now try loading up your app and authenticate and you should see in your server console `A user has connected`.
|
||||
|
||||
**Note:**`io()` works only when connecting to a socket hosted on the same url/server. For connecting to an external socket hosted elsewhere, you would use `io.connect('URL');`.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://gist.github.com/camperbot/aae41cf59debc1a4755c9a00ee3859d1" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#set-up-the-environment-6" target="_blank" rel="noopener noreferrer nofollow">check out the project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
`socket.io` should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'socket.io',
|
||||
'Your project should list "socket.io" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/package.json", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'socket.io',
|
||||
'Your project should list "socket.io" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You should correctly require and instantiate `http` as `http`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/http.*=.*require.*('|")http\1/gi,
|
||||
'Your project should list "http" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/http.*=.*require.*('|")http\1/s,
|
||||
'Your project should list "http" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You should correctly require and instantiate `socket.io` as `io`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io.*=.*require.*('|")socket.io\1.*http/gi,
|
||||
'You should correctly require and instantiate socket.io as io.'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io.*=.*require.*('|")socket.io\1.*http/s,
|
||||
'You should correctly require and instantiate socket.io as io.'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Socket.IO should be listening for connections.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io.on.*('|")connection\1.*socket/gi,
|
||||
'io should listen for "connection" and socket should be the 2nd arguments variable'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/_api/server.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/io.on.*('|")connection\1.*socket/s,
|
||||
'io should listen for "connection" and socket should be the 2nd arguments variable'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Your client should connect to your server.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.*=.*io/gi,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/public/client.js", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/socket.*=.*io/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
@@ -12,58 +12,63 @@ One of the greatest features of using a template engine is being able to pass va
|
||||
|
||||
In your Pug file, you're able to use a variable by referencing the variable name as `#{variable_name}` inline with other text on an element or by using an equal sign on the element without a space such as `p=variable_name` which assigns the variable's value to the p element's text.
|
||||
|
||||
Pug is all about using whitespace and tabs to show nested elements and cutting down on the amount of code needed to make a beautiful site. Read the Pug documentation for more information on usage and syntax.
|
||||
Pug is all about using whitespace and tabs to show nested elements and cutting down on the amount of code needed to make a beautiful site.
|
||||
|
||||
Take the following Pug code for example:
|
||||
|
||||
Here is an example:
|
||||
|
||||
```html
|
||||
<!--Typing this using Pug-->
|
||||
head
|
||||
script(type='text/javascript').
|
||||
if (foo) bar(1 + 5);
|
||||
body
|
||||
if youAreUsingPug
|
||||
p You are amazing
|
||||
else
|
||||
p Get on it!
|
||||
|
||||
<!--will lead to creating this code-->
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
if (foo) bar(1 + 5);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>You are amazing</p>
|
||||
</body>
|
||||
```
|
||||
```pug
|
||||
head
|
||||
script(type='text/javascript').
|
||||
if (foo) bar(1 + 5);
|
||||
body
|
||||
if youAreUsingPug
|
||||
p You are amazing
|
||||
else
|
||||
p Get on it!
|
||||
```
|
||||
|
||||
Looking at our pug file `index.pug` included in your project, we used the variables `title` and `message`.
|
||||
The above yields the following HTML:
|
||||
|
||||
To pass those along from our server, you will need to add an object as a second argument to your `res.render` with the variables and their values. For example, pass this object along setting the variables for your index view: `{title: 'Hello', message: 'Please login'}`
|
||||
```html
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
if (foo) bar(1 + 5);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>You are amazing</p>
|
||||
</body>
|
||||
```
|
||||
|
||||
It should look like: `res.render(process.cwd() + '/views/pug/index', {title: 'Hello', message: 'Please login'});` Now refresh your page and you should see those values rendered in your view in the correct spot as laid out in your `index.pug` file!
|
||||
Your `index.pug` file included in your project, uses the variables `title` and `message`.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the <a href="https://gist.github.com/camperbot/4af125119ed36e6e6a8bb920db0c0871" target="_blank" rel="noopener noreferrer nofollow">project completed up to this point</a>.
|
||||
Pass those from your server to the Pug file by adding an object as a second argument to your `res.render` call with the variables and their values. Give the `title` a value of `Hello` and `message` a value of `Please log in`.
|
||||
|
||||
It should look like:
|
||||
|
||||
```javascript
|
||||
res.render('index', { title: 'Hello', message: 'Please log in' });
|
||||
```
|
||||
|
||||
Now refresh your page, and you should see those values rendered in your view in the correct spot as laid out in your `index.pug` file!
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the <a href="https://forum.freecodecamp.org/t/advanced-node-and-express/567135#use-a-template-engines-power-2" target="_blank" rel="noopener noreferrer nofollow">project completed up to this point</a>.
|
||||
|
||||
# --hints--
|
||||
|
||||
Pug should correctly render variables.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/pug-variable("|')>Please login/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
async (getUserInput) => {
|
||||
const url = new URL("/", getUserInput("url"));
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data,
|
||||
/pug-variable("|')>Please log in/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
Reference in New Issue
Block a user