chore(i8n,learn): processed translations
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
15047f2d90
commit
e5c44a3ae5
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589fc832f9fc0f352b528e78
|
||||
title: 宣布新用户
|
||||
title: Announce New Users
|
||||
challengeType: 2
|
||||
forumTopicId: 301546
|
||||
dashedName: announce-new-users
|
||||
@@ -8,7 +8,9 @@ dashedName: announce-new-users
|
||||
|
||||
# --description--
|
||||
|
||||
许多聊天室都有这个功能:所有已连接到服务器的在线用户都会看到有人加入或退出的提醒。我们已经写好了处理连接和断开事件的代码,只要对这个方法稍作修改就可以实现这个功能。在事件中,我们需要发送这三条信息:连接或断开的用户名、当前用户数量、事件类型(即需要知道用户究竟是连接还是断开)。 请将事件名称更改为 `'user'`,其中应包含如下字段:'name'、'currentUsers'、'connected'(布尔值,连接上即为 `true`,断开则是 `false`)。记得要修改之前我们写好的处理 'user count' 的那部分代码,现在我们应在用户连接时传入布尔值 `true`;在用户断开连接是传入布尔值 `false`。
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
```js
|
||||
io.emit('user', {
|
||||
@@ -18,7 +20,9 @@ io.emit('user', {
|
||||
});
|
||||
```
|
||||
|
||||
现在,我们的客户端已经有足够的信息显示现有用户数量和发送用户上下线通知。接下来我们需要在客户端监听 'user' 事件,然后使用 jQuery 把 `#num-users` 节点的文本内容更新为 '{NUMBER} users online'。同时,我们需要为 `<ul>` 添加一个 id 为 'messages' 且带有 '{NAME} has {joined/left} the chat.' 文本的`<li>`。 一种实现方式如下:
|
||||
Now your client will have all the necessary information to correctly display the current user count and announce when a user connects or disconnects! To handle this event on the client side we should listen for `'user'`, then update the current user count by using jQuery to change the text of `#num-users` to `'{NUMBER} users online'`, as well as append a `<li>` to the unordered list with id `messages` with `'{NAME} has {joined/left} the chat.'`.
|
||||
|
||||
An implementation of this could look like the following:
|
||||
|
||||
```js
|
||||
socket.on('user', data => {
|
||||
@@ -30,11 +34,11 @@ socket.on('user', data => {
|
||||
});
|
||||
```
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/bf95a0f74b756cf0771cd62c087b8286) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/bf95a0f74b756cf0771cd62c087b8286).
|
||||
|
||||
# --hints--
|
||||
|
||||
`'user'` 事件应发送包含 name、currentUsers、connected 字段的对象。
|
||||
Event `'user'` should be emitted with name, currentUsers, and connected.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -52,7 +56,7 @@ socket.on('user', data => {
|
||||
);
|
||||
```
|
||||
|
||||
客户端应处理和显示 `'user'` 对象中的信息
|
||||
Client should properly handle and display the new data from event `'user'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e68
|
||||
title: 身份验证策略
|
||||
title: Authentication Strategies
|
||||
challengeType: 2
|
||||
forumTopicId: 301547
|
||||
dashedName: authentication-strategies
|
||||
@@ -8,11 +8,11 @@ dashedName: authentication-strategies
|
||||
|
||||
# --description--
|
||||
|
||||
现在,我们需要构建验证用户的策略,策略的选择有很多。比如,如果我们已经让用户在注册时填写了用户信息,那我们就可以基于这些信息验证;或者也可以引入第三方登录,如 Google 或者 Github。为此,你可以参考 [Passports 中提供的策略插件](http://passportjs.org/)。对于这个项目的验证策略,我们会采用自己搭建的方式完成。
|
||||
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 set up a local strategy. To see a list of the hundreds of strategies, visit Passport's site [here](http://passportjs.org/).
|
||||
|
||||
首先,我们需要引入 *passport-local* 作为依赖,然后将它添加到服务器,就像这样:`const LocalStrategy = require('passport-local');`
|
||||
Add `passport-local` as a dependency and add it to your server as follows: `const LocalStrategy = require('passport-local');`
|
||||
|
||||
然后,我们需要让 passport 使用实例化的本地策略对象。请注意,接下来的所有代码都应写在连接数据库的回调中,因为它们的执行都依赖数据库。
|
||||
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(
|
||||
@@ -28,11 +28,17 @@ passport.use(new LocalStrategy(
|
||||
));
|
||||
```
|
||||
|
||||
这就是我们的用户验证逻辑:首先根据用户输入的用户名在数据库中寻找用户;然后检查密码是否匹配;最后如果没有发生错误,那么就会返回用户对象并通过验证。 我们也可以采用上面链接中提供的验证策略,一般来说,根据该策略仓库中的 README 来进行配置就足够了。一个很好的例子是 Github 策略,在该策略中,我们不需要写用户名或密码的相关验证逻辑,因为用户将被引导到 Github 页面进行验证。只要他们登录并同意,Github 就会返回他们的个人信息供我们使用。 以上就是本次挑战的内容。在下一个挑战中,我们会基于表单数据调用上面写好的验证策略。 完成上述要求后,你可以在下方提交你的页面链接。
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
In the next step, we 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 check out the project completed up to this point [here](https://gist.github.com/camperbot/53b495c02b92adeee0aa1bd3f3be8a4b).
|
||||
|
||||
# --hints--
|
||||
|
||||
你的项目需要使用 `passport-local` 作为依赖。
|
||||
Passport-local should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -51,7 +57,7 @@ passport.use(new LocalStrategy(
|
||||
);
|
||||
```
|
||||
|
||||
应正确地引入和设置 `passport-local`。
|
||||
Passport-local should be correctly required and setup.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e77
|
||||
title: 使用Socket.IO进行身份验证
|
||||
title: Authentication with Socket.IO
|
||||
challengeType: 2
|
||||
forumTopicId: 301548
|
||||
dashedName: authentication-with-socket-io
|
||||
@@ -8,9 +8,9 @@ dashedName: authentication-with-socket-io
|
||||
|
||||
# --description--
|
||||
|
||||
目前,我们还无法确定连接到服务器的用户身份。虽然 `req.user` 包含用户信息,但这个只在用户直接与服务器交互(即不通过 web socket 访问服务器资源)时产生。当我们的用户通过 web socket 与服务器连接时,由于不存在 `req` 对象,因此我们无法获取用户数据。解决这个问题的方法之一是通过读取和解析请求中包含 passport session 的 cookie,然后反序列化,进而获取用户信息对象。幸运的是,npm 上有可以让这个复杂的流程简单化的库。
|
||||
Currently, you cannot determine who is connected to your web socket. While `req.user` contains the user object, that's only when your user interacts with the web server, and with web sockets you have no `req` (request) and therefore no user data. One way to solve the problem of knowing who is connected to your web socket is by parsing and decoding the cookie that contains the passport session then deserializing it to obtain the user object. Luckily, there is a package on NPM just for this that turns a once complex task into something simple!
|
||||
|
||||
添加 `passport.socketio`、`connect-mongo`、`cookie-parser` 作为依赖,把它们分别赋值给 `passportSocketIo`、`MongoStore`、`cookieParser`。同时,我们需要从之前引入的 `express-session` 中开辟新的内存空间,就像接下来这样:
|
||||
Add `passport.socketio`, `connect-mongo`, and `cookie-parser` as dependencies and require them as `passportSocketIo`, `MongoStore`, and `cookieParser` respectively. Also, we need to initialize a new memory store, from `express-session` which we previously required. It should look like this:
|
||||
|
||||
```js
|
||||
const MongoStore = require('connect-mongo')(session);
|
||||
@@ -18,7 +18,7 @@ const URI = process.env.MONGO_URI;
|
||||
const store = new MongoStore({ url: URI });
|
||||
```
|
||||
|
||||
现在我们只需要让 Socket.IO 调用它并进行相应的配置即可。请注意,以上这些都必须放在现有的 socket 相关代码之前,而且不能放到连接的监听回调函数里。你的服务器代码应类似于这样:
|
||||
Now we just have to tell Socket.IO to use it and set the options. Be sure this is added before the existing socket code and not in the existing connection listener. For your server, it should look like this:
|
||||
|
||||
```js
|
||||
io.use(
|
||||
@@ -33,11 +33,11 @@ io.use(
|
||||
);
|
||||
```
|
||||
|
||||
记得要把 `key` 和 `store` 加到 app 的 `session` 中间件。这样,SocketIO 才知道该对哪个 session 执行此配置。
|
||||
Be sure to add the `key` and `store` to the `session` middleware mounted on the app. This is necessary to tell *SocketIO* which session to relate to.
|
||||
|
||||
<hr>
|
||||
<hr />
|
||||
|
||||
接下来,我们可以定义 `success` 与 `fail` 的回调函数:
|
||||
Now, define the `success`, and `fail` callback functions:
|
||||
|
||||
```js
|
||||
function onAuthorizeSuccess(data, accept) {
|
||||
@@ -53,19 +53,19 @@ function onAuthorizeFail(data, message, error, accept) {
|
||||
}
|
||||
```
|
||||
|
||||
现在,我们可以通过 socket 对象通过 `socket.request.user` 访问 `user` 对象。为此,你可以这样做:
|
||||
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 里看到谁连接到了我们的服务器。
|
||||
It will log to the server console who has connected!
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。 完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/1414cc9433044e306dd7fd0caa1c6254) 的答案。
|
||||
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 [here](https://gist.github.com/camperbot/1414cc9433044e306dd7fd0caa1c6254).
|
||||
|
||||
# --hints--
|
||||
|
||||
应添加 `passport.socketio` 作为依赖。
|
||||
`passport.socketio` should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -84,7 +84,7 @@ console.log('user ' + socket.request.user.name + ' connected');
|
||||
);
|
||||
```
|
||||
|
||||
应添加 `cookie-parser` 作为依赖。
|
||||
`cookie-parser` should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -103,7 +103,7 @@ console.log('user ' + socket.request.user.name + ' connected');
|
||||
);
|
||||
```
|
||||
|
||||
应正确引入 passportSocketIo。
|
||||
passportSocketIo should be properly required.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -121,7 +121,7 @@ console.log('user ' + socket.request.user.name + ' connected');
|
||||
);
|
||||
```
|
||||
|
||||
应正确配置 passportSocketIo。
|
||||
passportSocketIo should be properly setup.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589690e6f9fc0f352b528e6e
|
||||
title: 使用模块清理项目
|
||||
title: Clean Up Your Project with Modules
|
||||
challengeType: 2
|
||||
forumTopicId: 301549
|
||||
dashedName: clean-up-your-project-with-modules
|
||||
@@ -8,7 +8,9 @@ dashedName: clean-up-your-project-with-modules
|
||||
|
||||
# --description--
|
||||
|
||||
目前,我们把所有的代码都放到了 server.js 文件里,这会导致代码难以维护,且扩展性差。 现在让我们来创建两个新文件:`routes.js` 和 `auth.js` 在每个文件的开头,我们都需要写上这段代码:
|
||||
Right now, everything you have is in your `server.js` file. This can lead to hard to manage code that isn't very expandable. Create 2 new files: `routes.js` and `auth.js`
|
||||
|
||||
Both should start with the following code:
|
||||
|
||||
```js
|
||||
module.exports = function (app, myDataBase) {
|
||||
@@ -16,19 +18,19 @@ module.exports = function (app, myDataBase) {
|
||||
}
|
||||
```
|
||||
|
||||
然后,在 server.js 文件的开头,我们需要像这样引入文件:`const routes = require('./routes.js');` 在成功连接数据库之后,我们需要像这样进行实例化:`routes(app, myDataBase)`。 最后,我们需要把所有路由相关的代码从 server.js 移动到新文件 routes.js。不要忘了,`ensureAuthenticated` 方法的定义也要移动到新文件中,这个是我们在之前的挑战中,为在路由中判断用户是否已登录创建的函数。然后,我们还需要在 routes.js 文件开头添加所需要的依赖,如:`const passport = require('passport');`。
|
||||
Now, in the top of your server file, require these files like so: `const routes = require('./routes.js');` Right after you establish a successful connection with the database, instantiate each of them like so: `routes(app, myDataBase)`
|
||||
|
||||
如果在这些步骤后没有报错,那么恭喜你,你已成功地从 server.js 文件中分离出了路由文件(**尽管 catch 那部分还是包含了路由的逻辑**)。
|
||||
Finally, take all of the routes in your server and paste them into your new files, and remove them from your server file. Also take the `ensureAuthenticated` function, since it was specifically created for routing. Now, you will have to correctly add the dependencies in which are used, such as `const passport = require('passport');`, at the very top, above the export line in your `routes.js` file.
|
||||
|
||||
现在,我们来把 server.js 中与验证相关的代码分离到 auth.js 中,例如序列化,设置验证策略等。请正确添加依赖,并在 server.js 中调用`auth(app,myDataBase)`。另外,由于我们的注册路由依赖 passport,所以我们需要先调用`auth(app, myDataBase)`,再调用`routes(app, myDataBase)`。
|
||||
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**)!
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/2d06ac5c7d850d8cf073d2c2c794cc92) 的答案。
|
||||
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.
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out an example of the completed project [here](https://gist.github.com/camperbot/2d06ac5c7d850d8cf073d2c2c794cc92).
|
||||
|
||||
# --hints--
|
||||
|
||||
应正确引入新文件。
|
||||
Modules should be present.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e75
|
||||
title: 通过 Emitting 通信
|
||||
title: Communicate by Emitting
|
||||
challengeType: 2
|
||||
forumTopicId: 301550
|
||||
dashedName: communicate-by-emitting
|
||||
@@ -8,27 +8,27 @@ dashedName: communicate-by-emitting
|
||||
|
||||
# --description--
|
||||
|
||||
<dfn>Emit</dfn> 是你会用到的,最常见的通信方式。如果我们从服务器发送信息给 'io',就相当于把事件的名称和数据发送给了所有处于连接状态的 socket。我们可以利用这个特性实现这样的功能:只要有新用户连接到服务器,我们就可以把目前连接的总用户数发给所有已连接的用户,这样所有用户随时都可以看到实时的在线人数。
|
||||
<dfn>Emit</dfn> is the most common way of communicating you will use. When you emit something from the server to 'io', you send an event's name and data to all the connected sockets. A good example of this concept would be emitting the current count of connected users each time a new user connects!
|
||||
|
||||
首先,我们需要在监听连接的地方之前添加一个用于追踪用户数的变量:
|
||||
Start by adding a variable to keep track of the users, just before where you are currently listening for connections.
|
||||
|
||||
```js
|
||||
let currentUsers = 0;
|
||||
```
|
||||
|
||||
然后,只要有人连接到服务器,我们需要在发出用户数量之前先给这个变量加 1:
|
||||
Now, when someone connects, you should increment the count before emitting the count. So, you will want to add the incrementer within the connection listener.
|
||||
|
||||
```js
|
||||
++currentUsers;
|
||||
```
|
||||
|
||||
最后,在监听连接的地方发出(emit)该事件即可。这个事件应命名为 'user count',且数据应该为 `currentUsers`:
|
||||
Finally, after incrementing the count, you should emit the event (still within the connection listener). The event should be named 'user count', and the data should just be the `currentUsers`.
|
||||
|
||||
```js
|
||||
io.emit('user count', currentUsers);
|
||||
```
|
||||
|
||||
接下来,我们还需要让客户端监听从服务端发出的事件。为此,我们还是需要用到 *on* 这个方法:
|
||||
Now, you can implement a way for your client to listen for this event! Similar to listening for a connection on the server, you will use the `on` keyword.
|
||||
|
||||
```js
|
||||
socket.on('user count', function(data) {
|
||||
@@ -36,13 +36,13 @@ socket.on('user count', function(data) {
|
||||
});
|
||||
```
|
||||
|
||||
现在你可以尝试启动你的 app 并登录,你会看到在客户端的控制台打印出了 1,这就表示目前连接到服务器的用户数为 1。你可以试着通过打开多个 app 来验证数量是否会增加。
|
||||
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.
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/28ef7f1078f56eb48c7b1aeea35ba1f5) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/28ef7f1078f56eb48c7b1aeea35ba1f5).
|
||||
|
||||
# --hints--
|
||||
|
||||
应定义 currentUsers。
|
||||
currentUsers should be defined.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -60,7 +60,7 @@ socket.on('user count', function(data) {
|
||||
);
|
||||
```
|
||||
|
||||
服务器应在有新的连接时 emit 当前用户数量。
|
||||
Server should emit the current user count at each new connection.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -78,7 +78,7 @@ socket.on('user count', function(data) {
|
||||
);
|
||||
```
|
||||
|
||||
客户端应监听 'user count' 事件。
|
||||
Your client should be listening for 'user count' event.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e6a
|
||||
title: 创建新的中间件
|
||||
title: Create New Middleware
|
||||
challengeType: 2
|
||||
forumTopicId: 301551
|
||||
dashedName: create-new-middleware
|
||||
@@ -8,9 +8,9 @@ dashedName: create-new-middleware
|
||||
|
||||
# --description--
|
||||
|
||||
无论是否登录,或者哪怕用户试图访问其他页面,目前都会跳转到 `/profile`。为了解决这个问题,我们需要在 profile 页面渲染之前进行用户验证,创建中间件就可以实现这个功能。
|
||||
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.
|
||||
|
||||
这个挑战的目标是创建`ensureAuthenticated(req, res, next)`中间件方法,通过在 *request* 上调用 passports 的`isAuthenticated` 方法,我们可以检查 *req.user* 是否定义,从而确定用户是否通过认证。如果用户已通过验证,就会调用 *next()*,否则我们应重定向到主页并让用户登录。该中间件的实现如下:
|
||||
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:
|
||||
|
||||
```js
|
||||
function ensureAuthenticated(req, res, next) {
|
||||
@@ -21,7 +21,7 @@ function ensureAuthenticated(req, res, next) {
|
||||
};
|
||||
```
|
||||
|
||||
然后,我们需要把 *ensureAuthenticated* 中间件添加到处理请求的回调之前:
|
||||
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.
|
||||
|
||||
```js
|
||||
app
|
||||
@@ -31,11 +31,11 @@ app
|
||||
});
|
||||
```
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/ae49b8778cab87e93284a91343da0959) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/ae49b8778cab87e93284a91343da0959).
|
||||
|
||||
# --hints--
|
||||
|
||||
`ensureAuthenticated` 中间件应添加到 `/profile`路由中。
|
||||
Middleware ensureAuthenticated should be implemented and on our /profile route.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -58,7 +58,7 @@ app
|
||||
);
|
||||
```
|
||||
|
||||
如果没有通过验证,对 /profile 的 GET 请求应重定向到 /
|
||||
A Get request to /profile should correctly redirect to / since we are not authenticated.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e76
|
||||
title: 处理连接断开
|
||||
title: Handle a Disconnect
|
||||
challengeType: 2
|
||||
forumTopicId: 301552
|
||||
dashedName: handle-a-disconnect
|
||||
@@ -8,9 +8,9 @@ dashedName: handle-a-disconnect
|
||||
|
||||
# --description--
|
||||
|
||||
你也许注意到,目前为止我们只处理用户数量的增加,没有处理减少。事实上,处理用户断开连接也很简单。区别在于,新连接的监听是发生在整个服务器上,但连接断开的监听是发生在每个 socket 上。
|
||||
You may notice that up to now you have only been increasing the user count. Handling a user disconnecting is just as easy as handling the initial connect, except you have to listen for it on each socket instead of on the whole server.
|
||||
|
||||
为此,我们需要在目前的监听回调里面监听 socket 断开连接的事件。在断开连接的回调函数中,我们可以不传任何参数,但你可以在这里添加连接断开的测试代码:
|
||||
To do this, add another listener inside the existing `'connect'` listener that listens for `'disconnect'` on the socket with no data passed through. You can test this functionality by just logging that a user has disconnected to the console.
|
||||
|
||||
```js
|
||||
socket.on('disconnect', () => {
|
||||
@@ -18,15 +18,15 @@ socket.on('disconnect', () => {
|
||||
});
|
||||
```
|
||||
|
||||
为确保客户端可以看到实时的用户数量,显然,我们应该在用户断开时让 currentUsers 减 1,然后发送 'user count' 事件,并使用修改后的用户数量。
|
||||
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!
|
||||
|
||||
**注意:** 和 `'disconnect'` 类似,所有 socket 可以发送到服务器的事件,我们都应该在有 'socket' 定义的连接监听器里处理。
|
||||
**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.
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/ab1007b76069884fb45b215d3c4496fa) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/ab1007b76069884fb45b215d3c4496fa).
|
||||
|
||||
# --hints--
|
||||
|
||||
服务器应处理断开 socket 连接的事件。
|
||||
Server should handle the event disconnect from a socket.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -40,7 +40,7 @@ socket.on('disconnect', () => {
|
||||
);
|
||||
```
|
||||
|
||||
客户端应监听 'user count' 事件。
|
||||
Your client should be listening for 'user count' event.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 58a25c98f9fc0f352b528e7f
|
||||
title: 哈希密码
|
||||
title: Hashing Your Passwords
|
||||
challengeType: 2
|
||||
forumTopicId: 301553
|
||||
dashedName: hashing-your-passwords
|
||||
@@ -8,13 +8,13 @@ dashedName: hashing-your-passwords
|
||||
|
||||
# --description--
|
||||
|
||||
回过头来看信息安全,你也许记得在数据库中存储明文密码是*绝对*禁止的。现在,我们需要引入 BCrypt 来解决这个问题。
|
||||
Going back to the information security section, you may remember that storing plaintext passwords is *never* okay. Now it is time to implement BCrypt to solve this issue.
|
||||
|
||||
添加 BCrypt 作为依赖,并通过`require`添加到服务器代码中。你需要在两个步骤中使用哈希运算:注册和保存新账户,以及登录时检查密码是否正确。
|
||||
Add BCrypt as a dependency, and 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.
|
||||
|
||||
目前处理注册的路由中,我们是这样把密码添加到数据库的:`password: req.body.password`。我们可以通过这段代码创建哈希值:`var hash = bcrypt.hashSync(req.body.password, 12);`,然后就可以把`passsword: req.body.password`替换为`password: hash`。
|
||||
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`.
|
||||
|
||||
最后,在验证逻辑中,我们已经有这样一段代码执行检查:`if (password !== user.password) { return done(null, false); }`。但我们现在存储的密码`user.password`已经是哈希值了。由于目前的检测机制是密码不匹配时就返回未认证,因此修改后,用于比对用户密码哈希值的代码应该是这样:
|
||||
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:
|
||||
|
||||
```js
|
||||
if (!bcrypt.compareSync(password, user.password)) {
|
||||
@@ -22,13 +22,13 @@ 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!
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/dc16cca09daea4d4151a9c36a1fab564) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/dc16cca09daea4d4151a9c36a1fab564).
|
||||
|
||||
# --hints--
|
||||
|
||||
应存在 BCrypt 依赖。
|
||||
BCrypt should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -47,7 +47,7 @@ if (!bcrypt.compareSync(password, user.password)) {
|
||||
);
|
||||
```
|
||||
|
||||
BCrypt 应正确地引入和调用。
|
||||
BCrypt should be correctly required and implemented.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f70ef9fc0f352b528e6b
|
||||
title: 如何将 Profile 放在一起
|
||||
title: How to Put a Profile Together
|
||||
challengeType: 2
|
||||
forumTopicId: 301554
|
||||
dashedName: how-to-put-a-profile-together
|
||||
@@ -8,17 +8,27 @@ dashedName: how-to-put-a-profile-together
|
||||
|
||||
# --description--
|
||||
|
||||
现在,只有通过验证的用户才能进入 */profile* 页面,这样我们就可以在页面上使用 'req.user' 里的信息了。
|
||||
Now that we can ensure the user accessing the `/profile` is authenticated, we can use the information contained in `req.user` on our page!
|
||||
|
||||
请在变量中包含 *username* 键,值为 'req.user.username',并通过 render 方法传给 profile 页面。然后在 'profile.pug' 页面,添加这行 `h2.center#welcome Welcome, #{username}!` 代码来创建 class 为 `center`、id 为 `welcome` 且文本内容为 'Welcome, ' 后加用户名的 h2 元素。
|
||||
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:
|
||||
|
||||
以及,请在 profile 里添加 */logout* 链接,后续会用于处理用户退出登录的逻辑:`a(href='/logout') Logout`
|
||||
```pug
|
||||
h2.center#welcome Welcome, #{username}!
|
||||
```
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/136b3ad611cc80b41cab6f74bb460f6a) 的答案。
|
||||
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.
|
||||
|
||||
```pug
|
||||
a(href='/logout') Logout
|
||||
```
|
||||
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/136b3ad611cc80b41cab6f74bb460f6a).
|
||||
|
||||
# --hints--
|
||||
|
||||
应在 Pug render 中给 /profile 传一个变量。
|
||||
You should correctly add a Pug render variable to /profile.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e69
|
||||
title: 如何使用 Passport 策略
|
||||
title: How to Use Passport Strategies
|
||||
challengeType: 2
|
||||
forumTopicId: 301555
|
||||
dashedName: how-to-use-passport-strategies
|
||||
@@ -8,21 +8,21 @@ dashedName: how-to-use-passport-strategies
|
||||
|
||||
# --description--
|
||||
|
||||
在提供的 index.pug 文件里有一个登录表单。因为这个表单中存在行内 JavaScript 代码 `if showLogin`,因此它是隐藏的。因为 showLogin 未定义,所以表单不会渲染。如果在该页面的 `res.render()` 里添加一个包含 `showLogin: true` 的对象,你就可以在刷新页面后看到表单。当你点击 login 时,表单会向服务器的 */login 发送 POST 请求,此时服务器端就可以接受 POST 请求信息并进行用户验证。*
|
||||
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.
|
||||
|
||||
在这个挑战中,你需要为 POST 请求添加路由`/login`。为了用这个路由进行验证,你需要添加一个中间件,中间件应作为参数添加到用于处理请求的回调函数 `function(req,res)` 之前。对于 passport 的验证中间件,应这样调用:`passport.authenticate('local')`。
|
||||
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')`.
|
||||
|
||||
*passport.authenticate* 也接收选项作为参数,这些选项用于设置验证,例如`{ failureRedirect: '/' }`就很有用,请记得添加到你的代码中。如果中间件验证通过,我们就应该提供相应的后续处理。在这个挑战中,我们需要让用户重定到 */profile*,这样 `profile.pug` 页面就会渲染。
|
||||
`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`.
|
||||
|
||||
如果验证通过,用户对象将会储存到 *req.user* 中。
|
||||
If the authentication was successful, the user object will be saved in `req.user`.
|
||||
|
||||
这时,如果你在表单里输入了用户名和密码,路由将会重定向到主页 */*,在服务端将会打印 'User {USERNAME} attempted to log in.',由于现在我们还没有实现注册功能,因此所有登录尝试都会失败。
|
||||
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.
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/7ad011ac54612ad53188b500c5e99cb9) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/7ad011ac54612ad53188b500c5e99cb9).
|
||||
|
||||
# --hints--
|
||||
|
||||
server.js 中应正确执行所有步骤。
|
||||
All steps should be correctly implemented in the server.js.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -50,7 +50,7 @@ server.js 中应正确执行所有步骤。
|
||||
);
|
||||
```
|
||||
|
||||
到 /login 的 POST 请求应重定向到 /
|
||||
A POST request to /login should correctly redirect to /.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e67
|
||||
title: 实现 Passport 用户的序列化
|
||||
title: Implement the Serialization of a Passport User
|
||||
challengeType: 2
|
||||
forumTopicId: 301556
|
||||
dashedName: implement-the-serialization-of-a-passport-user
|
||||
@@ -8,13 +8,11 @@ dashedName: implement-the-serialization-of-a-passport-user
|
||||
|
||||
# --description--
|
||||
|
||||
截至目前,我们还没有配置完数据库,因此还无法加载用户数据。实现这个的方式很多,但对于我们的项目,一旦服务器启动,那么只要有 app 实例在运行,数据库就应一直处于连接状态。
|
||||
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.
|
||||
|
||||
为此,你需要在环境变量 `MONGO_URI` 中添加你的数据库地址(比如:`mongodb+srv://:@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority`),我们会在 *connection.js* 中调用它。
|
||||
*You can set up a free database on [MongoDB Atlas](https://www.mongodb.com/cloud/atlas).*
|
||||
|
||||
*你可以在 [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) 创建一个免费的数据库。*
|
||||
|
||||
在连接数据库之后,我们才能让服务器开始监听请求,这样做可以保证服务器在数据库连接前或数据库发生错误时不接受任何请求。为此,我们需要这样写:
|
||||
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:
|
||||
|
||||
```js
|
||||
myDB(async client => {
|
||||
@@ -40,13 +38,13 @@ myDB(async client => {
|
||||
// app.listen out here...
|
||||
```
|
||||
|
||||
记得要取消 deserializeUser 中 `myDataBase` 的注释,并把 `doc` 添加到 `done(null, null)`。
|
||||
Be sure to uncomment the `myDataBase` code in `deserializeUser`, and edit your `done(null, null)` to include the `doc`.
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/175f2f585a2d8034044c7e8857d5add7) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/175f2f585a2d8034044c7e8857d5add7).
|
||||
|
||||
# --hints--
|
||||
|
||||
应存在数据库连接。
|
||||
Database connection should be present.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -64,7 +62,7 @@ myDB(async client => {
|
||||
);
|
||||
```
|
||||
|
||||
反序列化应正确使用,且应正确调用 `done(null, null)`。
|
||||
Deserialization should now be correctly using the DB and `done(null, null)` should be called with the `doc`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589a69f5f9fc0f352b528e71
|
||||
title: 实现社交账号登陆 (2)
|
||||
title: Implementation of Social Authentication II
|
||||
challengeType: 2
|
||||
forumTopicId: 301557
|
||||
dashedName: implementation-of-social-authentication-ii
|
||||
@@ -8,11 +8,11 @@ dashedName: implementation-of-social-authentication-ii
|
||||
|
||||
# --description--
|
||||
|
||||
设置 GitHub 验证的最后一步是创建策略本身。为此,你需要在 `auth.js` 中`require`'passport-github',且实例化为 GithubStrategy:`const GithubStrategy = require('passport-github').Strategy;`。别忘了在 `dotenv` 中修改环境变量。
|
||||
The last part of setting up your GitHub authentication is to create the strategy itself. For this, you will need to add the dependency of 'passport-github' to your project and require it in your `auth.js` as `GithubStrategy` like this: `const GitHubStrategy = require('passport-github').Strategy;`. Do not forget to require and configure `dotenv` to use your environment variables.
|
||||
|
||||
为了设置 GitHub 策略,我们需要在 **passport** 中使用实例化的 **GithubStrategy**,它可以接收两个参数:一个对象(包括 *clientID*, *clientSecret* 和 *callbackURL*),以及一个回调函数。在这个回调函数中,我们要处理验证成功时,判断用户是否已经在数据库中存在的逻辑,还有如果数据库中不存在,把用户数据添加到数据库的代码。这种处理方式适用于绝大部分第三方验证策略,但有些策略会需要我们提供更多的信息,详情请参考相关策略的 README。例如,Google 的验证策略会要求你提供一个 *scope*,用于标示用户成功登录后,你需要从返回的对象中获取那些信息。以及,这也需要经过用户同意,你才可以获取到。你可以在 [这里](https://github.com/jaredhanson/passport-github/) 了解当前我们使用的验证策略的用法,不过我们也会在这里进行详细讲解。
|
||||
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 has its usage outlined [here](https://github.com/jaredhanson/passport-github/), but we're going through it all right here on freeCodeCamp!
|
||||
|
||||
你的新策略应该这样去实现:
|
||||
Here's how your new strategy should look at this point:
|
||||
|
||||
```js
|
||||
passport.use(new GitHubStrategy({
|
||||
@@ -27,13 +27,13 @@ passport.use(new GitHubStrategy({
|
||||
));
|
||||
```
|
||||
|
||||
目前,你的验证部分不会成功。由于没有数据库的逻辑和回调函数,你的代码目前还会报错。但如果你试一试,就可以在右边的控制台里看到输出了你的 GitHub 的个人信息。
|
||||
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!
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/ff3a1166684c1b184709ac0bee30dee6) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/ff3a1166684c1b184709ac0bee30dee6).
|
||||
|
||||
# --hints--
|
||||
|
||||
应正确添加依赖 passport-github。
|
||||
passport-github dependency should be added.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -52,7 +52,7 @@ passport.use(new GitHubStrategy({
|
||||
);
|
||||
```
|
||||
|
||||
应正确引入依赖 passport-github。
|
||||
passport-github should be required.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -70,7 +70,7 @@ passport.use(new GitHubStrategy({
|
||||
);
|
||||
```
|
||||
|
||||
到目前为止,Github 策略应正确设置。
|
||||
GitHub strategy should be setup correctly thus far.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589a8eb3f9fc0f352b528e72
|
||||
title: 实现社交账号登陆 (3)
|
||||
title: Implementation of Social Authentication III
|
||||
challengeType: 2
|
||||
forumTopicId: 301558
|
||||
dashedName: implementation-of-social-authentication-iii
|
||||
@@ -8,40 +8,45 @@ dashedName: implementation-of-social-authentication-iii
|
||||
|
||||
# --description--
|
||||
|
||||
验证策略的最后一部分是处理从 GitHub 返回的个人信息。如果用户存在,我们就需要从数据库中读取用户数据并在 profile 页面加载;否则,我们需要把用户信息添加到数据库。GitHub 在用户信息中为我们提供了独一无二的 *id*,我们可以通过序列化的 id 在数据库中搜索用户(已实现)。以下是这个逻辑的实现示例,我们应该把它传到新策略的第二个参数,就是目前 `console.log(profile);` 的下方:
|
||||
The final part of the strategy is handling the profile returned from GitHub. We need to load the user's database object if it exists, or create one if it doesn't, and populate the fields from the profile, then return the user's object. GitHub supplies us a unique *id* within each profile which we can use to search with to serialize the user with (already implemented). Below is an example implementation you can use in your project--it goes within the function that is the second argument for the new strategy, right below where `console.log(profile);` currently is:
|
||||
|
||||
```js
|
||||
myDataBase.findAndModify(
|
||||
{id: profile.id},
|
||||
{},
|
||||
{$setOnInsert:{
|
||||
id: profile.id,
|
||||
name: profile.displayName || 'John Doe',
|
||||
photo: profile.photos[0].value || '',
|
||||
email: Array.isArray(profile.emails) ? profile.emails[0].value : 'No public email',
|
||||
created_on: new Date(),
|
||||
provider: profile.provider || ''
|
||||
},$set:{
|
||||
last_login: new Date()
|
||||
},$inc:{
|
||||
login_count: 1
|
||||
}},
|
||||
{upsert:true, new: true},
|
||||
myDataBase.findOneAndUpdate(
|
||||
{ id: profile.id },
|
||||
{
|
||||
$setOnInsert: {
|
||||
id: profile.id,
|
||||
name: profile.displayName || 'John Doe',
|
||||
photo: profile.photos[0].value || '',
|
||||
email: Array.isArray(profile.emails)
|
||||
? profile.emails[0].value
|
||||
: 'No public email',
|
||||
created_on: new Date(),
|
||||
provider: profile.provider || ''
|
||||
},
|
||||
$set: {
|
||||
last_login: new Date()
|
||||
},
|
||||
$inc: {
|
||||
login_count: 1
|
||||
}
|
||||
},
|
||||
{ upsert: true, new: true },
|
||||
(err, doc) => {
|
||||
return cb(null, doc.value);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
`findAndModify` 的作用是在数据库中查询对象并更新,如果对象不存在,我们也可以 `upsert`(注,upsert 可以理解为 update + insert)它,然后我们可以在回调方法里获取到插入数据后的新对象。在这个例子中,我们会把 last_login 设置成为 now,而且总会为 login_count 加 1。只有在插入一个新对象(新用户)时,我们才会初始化这些字段。另外,还需要注意默认值的使用。有时返回的用户信息可能不全,可能是因为用户没有填写,也可能是因为用户选择不公开一部分信息。在这种情况下,我们需要进行相应的处理,以防我们的 app 报错。
|
||||
`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!
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/183e968f0e01d81dde015d45ba9d2745) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/183e968f0e01d81dde015d45ba9d2745).
|
||||
|
||||
# --hints--
|
||||
|
||||
GitHub 策略应配置完成。
|
||||
GitHub strategy setup should be complete.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589a69f5f9fc0f352b528e70
|
||||
title: 实现社交账号登陆 (1)
|
||||
title: Implementation of Social Authentication
|
||||
challengeType: 2
|
||||
forumTopicId: 301559
|
||||
dashedName: implementation-of-social-authentication
|
||||
@@ -8,21 +8,21 @@ dashedName: implementation-of-social-authentication
|
||||
|
||||
# --description--
|
||||
|
||||
第三方用户验证的实现逻辑如下:
|
||||
The basic path this kind of authentication will follow in your app is:
|
||||
|
||||
1. 在用户点击按钮或者链接后,进入验证页面,通过第三方平台(如 GitHub)来进行用户验证。
|
||||
2. 需要在路由中调用`passport.authenticate('github')`,跳转至 GitHub 验证页面。
|
||||
3. 页面跳转到 GitHub 上,如果用户未登录 GitHub,就需要在这里进行登录。登录成功后,会出现向用户申请访问权限的确认页。
|
||||
4. 如果用户同意访问,则用户会回到我们提供的回调地址,带着 GitHub 那边提供的用户信息回到我们的 app 中。
|
||||
5. 验证已完成。在我们的应用中,我们需要查询这个用户是否已经存在。如果是新用户,那我们需要把他的用户信息存储到数据库。
|
||||
1. User clicks a button or link sending them to our 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.
|
||||
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.
|
||||
|
||||
在 OAuth 验证策略中,我们至少需要提供 *Client ID* 和 *Client Secret*,这样第三方平台就会获悉验证请求的来源,以及这个来源是否有效。为此,需要去我们使用的第三方验证平台(比如 GitHub)获取这两个字段的值。注意,我们获取到的这个值是唯一的,且仅对我们的当前 app 有效——**因此,千万不要分享给别人**,更不要上传到公共仓库或者直接写在代码里。通常,我们会在 *.env* 文件里配置,并在 Node.js 里通过:`process.env.GITHUB_CLIENT_ID`获取。对于这次挑战,我们将会使用 GitHub 作为验证平台。
|
||||
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.
|
||||
|
||||
首先,你需要进入账户设置里的 [Developer settings](https://github.com/settings/developers)板块,然后在 OAuth Apps 获取 *Client ID* 和 *Client Secret*。点击 'Register a new application',设置你的应用名称,然后把你的 glitch 主页地址(**注意,不是项目代码的地址**)粘贴到 Homepage URL。然后,回调 url 需要设置成上面 Homepage URL 里你粘贴的地址,但后面要加上 '/auth/github/callback'。这样在用户通过 Github 验证后才能跳转到我们指定的页面。别忘了,我们还需要在 .env 文件里配置好 'GITHUB_CLIENT_ID' 和 'GITHUB_CLIENT_SECRET'。
|
||||
Obtaining your *Client ID and Secret* from GitHub is done in your account profile settings under 'developer settings', then '[OAuth applications](https://github.com/settings/developers)'. Click 'Register a new application', name your app, paste in the url to your Repl.it 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.
|
||||
|
||||
然后,请在你现在的项目里,为 /auth/github 和 /auth/github/callback 创建两个接收 GET 请求的路由。第一个只需要通过 passport 来调用 'github' 验证,第二个需要调用 passport 来验证 'github',但需要在失败时跳转回主页 '/',成功是跳转到用户页面 '/profile'。跳转的逻辑与上一个项目中的逻辑一样。
|
||||
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).
|
||||
|
||||
例如 '/auth/github/callback' 应该像我们处理在上一个项目中一般的登录一样:
|
||||
An example of how `/auth/github/callback` should look is similar to how we handled a normal login:
|
||||
|
||||
```js
|
||||
app.route('/login')
|
||||
@@ -31,19 +31,19 @@ app.route('/login')
|
||||
});
|
||||
```
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/1f7f6f76adb178680246989612bea21e) 的答案。
|
||||
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 [here](https://gist.github.com/camperbot/1f7f6f76adb178680246989612bea21e).
|
||||
|
||||
# --hints--
|
||||
|
||||
路由 /auth/github 应正确配置。
|
||||
Route /auth/github should be correct.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/routes.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/('|")\/auth\/github('|")[^]*get.*passport.authenticate.*github/gi,
|
||||
data.replace(/\s/g, ''),
|
||||
/('|")\/auth\/github\/?\1[^]*?get.*?passport.authenticate.*?github/gi,
|
||||
'Route auth/github should only call passport.authenticate with github'
|
||||
);
|
||||
},
|
||||
@@ -53,15 +53,15 @@ app.route('/login')
|
||||
);
|
||||
```
|
||||
|
||||
路由 /auth/github/callback 应正确配置。
|
||||
Route /auth/github/callback should be correct.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/routes.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/('|")\/auth\/github\/callback('|")[^]*get.*passport.authenticate.*github.*failureRedirect:( |)("|')\/\2/gi,
|
||||
data.replace(/\s/g, ''),
|
||||
/('|")\/auth\/github\/callback\/?\1[^]*?get.*?passport.authenticate.*?github.*?failureRedirect:("|')\/\2/gi,
|
||||
'Route auth/github/callback should accept a get request and call passport.authenticate for github with a failure redirect to home'
|
||||
);
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 58965611f9fc0f352b528e6c
|
||||
title: 用户退出登录
|
||||
title: Logging a User Out
|
||||
challengeType: 2
|
||||
forumTopicId: 301560
|
||||
dashedName: logging-a-user-out
|
||||
@@ -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.
|
||||
|
||||
在 passport 里,只需要在重定向前调用 `req.logout();` 即可完成用户的退出登录。
|
||||
In passport, unauthenticating a user is as easy as just calling `req.logout();` before redirecting.
|
||||
|
||||
```js
|
||||
app.route('/logout')
|
||||
@@ -20,7 +20,7 @@ app.route('/logout')
|
||||
});
|
||||
```
|
||||
|
||||
你可能注意到我们还没有处理 404 错误,这个错误码代表页面无法找到。在 Node.js 中我们通常会用如下的中间件来处理,请在所有路由之后添加这段代码:
|
||||
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:
|
||||
|
||||
```js
|
||||
app.use((req, res, next) => {
|
||||
@@ -30,11 +30,11 @@ app.use((req, res, next) => {
|
||||
});
|
||||
```
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/c3eeb8a3ebf855e021fd0c044095a23b) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/c3eeb8a3ebf855e021fd0c044095a23b).
|
||||
|
||||
# --hints--
|
||||
|
||||
`req.Logout` 应在 `/logout` 中调用。
|
||||
`req.Logout` should be called in your `/logout` route.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -52,7 +52,7 @@ app.use((req, res, next) => {
|
||||
);
|
||||
```
|
||||
|
||||
退出登录后应重定向到主页 /
|
||||
Logout should redirect to the home page.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 58966a17f9fc0f352b528e6d
|
||||
title: 注册新用户
|
||||
title: Registration of New Users
|
||||
challengeType: 2
|
||||
forumTopicId: 301561
|
||||
dashedName: registration-of-new-users
|
||||
@@ -8,11 +8,11 @@ dashedName: registration-of-new-users
|
||||
|
||||
# --description--
|
||||
|
||||
现在我们需要为新用户添加注册帐号的功能,首先我们需要在主页的 res.render 接收的变量对象中添加 `showRegistration: true`。此时刷新页面,你会看到页面上已经显示了我们在 index.pug 文件中定义的注册表单。这个表单设置了请求路径 */register*,并将请求方法设置成 **POST**,所以我们需要在服务器接受 **POST** 请求,且在数据库中创建用户对象。
|
||||
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.
|
||||
|
||||
用户注册的逻辑如下:注册新用户 > 认证新用户 > 重定向到 /profile。
|
||||
The logic of the registration route should be as follows: Register the new user > Authenticate the new user > Redirect to /profile
|
||||
|
||||
对于步骤一的注册新用户,详细逻辑如下:用 findOne 命令查询数据库 > 如果返回了用户对象,则表示用户存在,然后返回主页;如果用户未定义且没有报错,则会将包含用户名和密码的用户对象通过 `insertOne` 添加到数据库,只要没有报错则会继续下一步:认证新用户——我们已经在 /login 路由的 POST 请求中写好了这部分逻辑。
|
||||
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.
|
||||
|
||||
```js
|
||||
app.route('/register')
|
||||
@@ -47,13 +47,13 @@ app.route('/register')
|
||||
);
|
||||
```
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/b230a5b3bbc89b1fa0ce32a2aa7b083e) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/b230a5b3bbc89b1fa0ce32a2aa7b083e).
|
||||
|
||||
**注意:** 接下来的挑战可能会在运行 *picture-in-picture*(画中画)模式的浏览器中出现问题。如果你使用的线上 IDE 提供在 IDE 内预览 app 的功能,请考虑打开新的标签页预览。
|
||||
**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.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -76,7 +76,7 @@ app.route('/register')
|
||||
);
|
||||
```
|
||||
|
||||
注册功能应可以正常运行。
|
||||
Registering should work.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
@@ -104,7 +104,7 @@ async (getUserInput) => {
|
||||
};
|
||||
```
|
||||
|
||||
登录功能应可以正常运行。
|
||||
Login should work.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
@@ -153,7 +153,7 @@ async (getUserInput) => {
|
||||
};
|
||||
```
|
||||
|
||||
退出登录功能应可以正常运行。
|
||||
Logout should work.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -171,7 +171,7 @@ async (getUserInput) => {
|
||||
);
|
||||
```
|
||||
|
||||
退出登录后,profile 页面应无法访问。
|
||||
Profile should no longer work after logout.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589fc832f9fc0f352b528e79
|
||||
title: 发送和显示聊天消息
|
||||
title: Send and Display Chat Messages
|
||||
challengeType: 2
|
||||
forumTopicId: 301562
|
||||
dashedName: send-and-display-chat-messages
|
||||
@@ -8,7 +8,7 @@ dashedName: send-and-display-chat-messages
|
||||
|
||||
# --description--
|
||||
|
||||
现在,我们可以开始实现聊天室功能了。整体逻辑很简单,只需要获取用户发给服务端的消息,再通过服务端给所有客户端发送信息就可以了。在 client.js 文件里,你应该已经注意到了这段提交表单的代码:
|
||||
It's time you start allowing clients to send a chat message to the server to emit to all the clients! In your `client.js` file, you should see there is already a block of code handling when the message form is submitted.
|
||||
|
||||
```js
|
||||
$('form').submit(function() {
|
||||
@@ -16,23 +16,23 @@ $('form').submit(function() {
|
||||
});
|
||||
```
|
||||
|
||||
现在我们需要处理事件的 emit,它应该发生在定义 `messageToSend` 之后,以及清除 `#m` 中的文本之前。我们称这个事件叫 `'chat message'`,需发送的数据叫 `messageToSend`:
|
||||
Within the form submit code, you should emit an event after you define `messageToSend` but before you clear the text box `#m`. The event should be named `'chat message'` and the data should just be `messageToSend`.
|
||||
|
||||
```js
|
||||
socket.emit('chat message', messageToSend);
|
||||
```
|
||||
|
||||
在服务端,我们需要监听包含 `message` 的 `'chat message'` 事件。一旦事件发生,我们就通过`io.emit` 把包含 `name` 和 `message` 的 `'chat message'` 事件发送给所有已连接的 socket。
|
||||
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`.
|
||||
|
||||
回到客户端,我们需要监听 `'chat message'` 事件。只要接收到这个事件,就把包含名字和消息的内容(注意:需要在名字后添加冒号)通过 `<li>` 添加到 `#messages`。
|
||||
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!
|
||||
|
||||
至此,我们已经完成发送信息到所有客户端的功能。
|
||||
At this point, the chat should be fully functional and sending messages across all clients!
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/d7af9864375207e254f73262976d2016) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/d7af9864375207e254f73262976d2016).
|
||||
|
||||
# --hints--
|
||||
|
||||
服务端应监听 `'chat message'`,且应在监听到后 emit。
|
||||
Server should listen for `'chat message'` and then emit it properly.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -50,7 +50,7 @@ socket.emit('chat message', messageToSend);
|
||||
);
|
||||
```
|
||||
|
||||
客户端应正确处理和展示从 `'chat message'` 事件发来的新数据。
|
||||
Client should properly handle and display the new data from event `'chat message'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e66
|
||||
title: 用户对象的序列化
|
||||
title: Serialization of a User Object
|
||||
challengeType: 2
|
||||
forumTopicId: 301563
|
||||
dashedName: serialization-of-a-user-object
|
||||
@@ -8,11 +8,11 @@ dashedName: serialization-of-a-user-object
|
||||
|
||||
# --description--
|
||||
|
||||
序列化和反序列化在身份认证中是很重要的概念。序列化一个对象就是将其内容转换成一个体积很小的 *key*,后续可以通过这个 *key* 反序列化为原始对象。这样,服务器就可以在用户未登录时识别用户,或者说给这个用户一个唯一标识,用户也不需要在每次访问不同页面时都给服务器发送用户名和密码。
|
||||
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.
|
||||
|
||||
我们需要用到序列化和反序列化的方法来进行配置。passport 为我们提供了 `passport.serializeUser( OURFUNCTION )` 和 `passport.deserializeUser( OURFUNCTION )` 两个方法。
|
||||
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 )`
|
||||
|
||||
code>serializeUser方法接收两个参数,分别是表示用户的对象和一个回调函数。其中,回调函数的返回值应为这个用户的唯一标识符:最简单的写法就是让它返回用户的`_id`,这个`_id`属性是 MongoDB 为用户创建的唯一字段。类似地,反序列化也接收两个参数,分别是在序列化时生成的标识符以及一个回调函数。在回调函数里,我们需要根据根据传入的标识符(比如 \_id)返回表示用户的对象。为了在 MongoDB 中通过 query(查询语句)获取 `_id` 字段,首先我们需要引入 MongoDB 的`ObjectID`方法:`const ObjectID = require('mongodb').ObjectID;`;然后调用它:`new ObjectID(THE_ID)`。当然,这一切的前提都是先引入 MongoDB 作为依赖。你可以在下面的例子中看到:
|
||||
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 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)`. Be sure to add MongoDB as a dependency. You can see this in the examples below:
|
||||
|
||||
```js
|
||||
passport.serializeUser((user, done) => {
|
||||
@@ -26,13 +26,13 @@ passport.deserializeUser((id, done) => {
|
||||
});
|
||||
```
|
||||
|
||||
注意:在完全配置好 MongoDB 前,`deserializeUser` 会抛出错误。因此,现在请先注释掉上面的代码,在 `deserializeUser` 中仅调用 `done(null, null)` 即可。
|
||||
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`.
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/7068a0d09e61ec7424572b366751f048) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/7068a0d09e61ec7424572b366751f048).
|
||||
|
||||
# --hints--
|
||||
|
||||
应存在正确的 `serializeUser` 方法。
|
||||
You should serialize user function correctly.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -55,7 +55,7 @@ passport.deserializeUser((id, done) => {
|
||||
);
|
||||
```
|
||||
|
||||
应存在正确的 `deserializeUser` 方法。
|
||||
You should deserialize user function correctly.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -78,7 +78,7 @@ passport.deserializeUser((id, done) => {
|
||||
);
|
||||
```
|
||||
|
||||
MongoDB 应作为项目的依赖。
|
||||
MongoDB should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -97,7 +97,7 @@ MongoDB 应作为项目的依赖。
|
||||
);
|
||||
```
|
||||
|
||||
注释掉的代码中应包含 `ObjectId`。
|
||||
Mongodb should be properly required including the ObjectId.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f700f9fc0f352b528e63
|
||||
title: 设置模板引擎
|
||||
title: Set up a Template Engine
|
||||
challengeType: 2
|
||||
forumTopicId: 301564
|
||||
dashedName: set-up-a-template-engine
|
||||
@@ -8,25 +8,25 @@ dashedName: set-up-a-template-engine
|
||||
|
||||
# --description--
|
||||
|
||||
请注意,本项目在 [这个 Repl.it 项目](https://repl.it/github/freeCodeCamp/boilerplate-mochachai) 的基础上进行开发。你也可以从 [GitHub](https://repl.it/github/freeCodeCamp/boilerplate-mochachai) 上克隆。
|
||||
As a reminder, this project is built upon the following starter project on [Repl.it](https://repl.it/github/freeCodeCamp/boilerplate-advancednode), or clone from [GitHub](https://github.com/freeCodeCamp/boilerplate-advancednode/).
|
||||
|
||||
你可以在应用的模版引擎中使用静态模板文件(如那些写在 *Pug* 里的)。在运行时,模版引擎会用服务端的真实数据替换掉模版文件中的变量,然后将模版转译成发送给客户端的 HTML 静态文件。这样可以轻松地构造 HTML 页面,允许在页面直接显示变量内容而不需要发送 API 请求。
|
||||
A template engine enables you to use static template files (such as those written in *Pug*) in your app. At runtime, the template engine replaces variables in a template file with actual values which can be supplied by your server. Then it transforms the template into a static HTML file that is sent to the client. This approach makes it easier to design an HTML page and allows for displaying variables on the page without needing to make an API call from the client.
|
||||
|
||||
为了在项目中使用 *Pug*,你需要在 package.json 中添加依赖 `"pug": "^0.1.0"`。注意,依赖的名称和版本号都要添加。
|
||||
Add `pug@~3.0.0` as a dependency in your `package.json` file.
|
||||
|
||||
为了在 Express 中使用 pug 作为模版引擎,你需要在 express 中将 **app** 的 "view-engine" 设置为 "pug",就像这样:`app.set('view engine', 'pug')`。
|
||||
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')`
|
||||
|
||||
如果没有正确的 *render* *'views/pug'* 路径下的 index 文件,页面将不会被加载。
|
||||
Your page will not load until you correctly render the index file in the `views/pug` directory.
|
||||
|
||||
最后, 你需要修改 `res.render()` 方法,设置 `/` 的响应。`res.render()` 方法接收一个文件路径作为参数,这个路径既可以是相对路径(相对于 views),也可以是绝对路径。而且,我们不需要给它添加文件扩展名(文件后缀名)。
|
||||
Change the argument of the `res.render()` declaration in the `/` route to be the file path to the `views/pug` directory. The path can be a relative path (relative to views), or an absolute path, and does not require a file extension.
|
||||
|
||||
如果一切顺利,刷新一下应用的主页就不会看到 "Pug template is not defined." 的报错了,而是会看到 Pug 成功加载的提示。
|
||||
If all went as planned, your app home page will stop showing the message "`Pug template is not defined.`" and will now display a message indicating you've successfully rendered the Pug template!
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/3515cd676ea4dfceab4e322f59a37791) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/3515cd676ea4dfceab4e322f59a37791).
|
||||
|
||||
# --hints--
|
||||
|
||||
项目中应使用 Pug 作为依赖。
|
||||
Pug should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -45,7 +45,7 @@ dashedName: set-up-a-template-engine
|
||||
);
|
||||
```
|
||||
|
||||
项目中应使用 Pug 作为模版引擎。
|
||||
View engine should be Pug.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -63,7 +63,7 @@ dashedName: set-up-a-template-engine
|
||||
);
|
||||
```
|
||||
|
||||
在 Response 里使用正确的 ExpressJS 方法渲染主页面。
|
||||
Use the correct ExpressJS method to render the index page from the response.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -81,7 +81,7 @@ dashedName: set-up-a-template-engine
|
||||
);
|
||||
```
|
||||
|
||||
Pug 应该生效。
|
||||
Pug should be working.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e65
|
||||
title: 设置 Passport
|
||||
title: Set up Passport
|
||||
challengeType: 2
|
||||
forumTopicId: 301565
|
||||
dashedName: set-up-passport
|
||||
@@ -8,15 +8,15 @@ dashedName: set-up-passport
|
||||
|
||||
# --description--
|
||||
|
||||
现在我们来创建 *Passport*,最终我们需要用它来实现用户注册和登录。除了 Passport,我们还会用 express-session 来处理 session(会话)。在客户端,我们可以用这个中间件把 session id 储存到 cookie。同时,我们可以在服务器上通过这个 id 访问 session 数据。通过这种方式,我们无需把用户的个人信息存到 cookie 来只完成用户的验证,只需要用这个 id 作为 *key* 来访问服务器上用户的数据即可。
|
||||
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. 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.
|
||||
|
||||
为了在你的项目中使用 Passport,首先你需要在 package.json 文件中添加依赖:`"passport": "^0.3.2"`
|
||||
To set up Passport for use in your project, you will need to add it as a dependency first in your package.json. `"passport": "^0.3.2"`
|
||||
|
||||
此外,还需要添加 express-session 作为依赖,就像这样:`"express-session": "^1.15.0"`。express-session 有许多高级特性,但我们暂时只需要了解其基础功能。
|
||||
In addition, add Express-session as a dependency now as well. Express-session has a ton of advanced features you can use but for now we're just going to use the basics! `"express-session": "^1.15.0"`
|
||||
|
||||
现在,我们需要配置 session 并初始化 Passport。请先创建变量 `session` 和 `passport` 来引入 express-session 和 passport。
|
||||
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.
|
||||
|
||||
为了让 express 应用可以使用 session,我们需要添加一些基础选项。请在 .env 文件中添加字段 'SESSION_SECRET',并给它赋一个随机值,便于加密 cookie、计算哈希。
|
||||
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!
|
||||
|
||||
```js
|
||||
app.use(session({
|
||||
@@ -27,13 +27,13 @@ app.use(session({
|
||||
}));
|
||||
```
|
||||
|
||||
还有,我们需要让 express 使用 `passport.initialize()` 和 `passport.session()`。为此,你可以这样写:`app.use(passport.initialize());`。
|
||||
As well you can go ahead and tell your express app to **use** 'passport.initialize()' and 'passport.session()'. (For example, `app.use(passport.initialize());`)
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/4068a7662a2f9f5d5011074397d6788c) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/4068a7662a2f9f5d5011074397d6788c).
|
||||
|
||||
# --hints--
|
||||
|
||||
应添加 Passort 和 express-session 作为依赖。
|
||||
Passport and Express-session should be dependencies.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -57,7 +57,7 @@ app.use(session({
|
||||
);
|
||||
```
|
||||
|
||||
依赖应正确引入。
|
||||
Dependencies should be correctly required.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -80,7 +80,7 @@ app.use(session({
|
||||
);
|
||||
```
|
||||
|
||||
express 应调用 passport 的方法。
|
||||
Express app should use new dependencies.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -103,7 +103,7 @@ express 应调用 passport 的方法。
|
||||
);
|
||||
```
|
||||
|
||||
应正确设置 session 和 session secret。
|
||||
Session and session secret should be correctly set up.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 589fc830f9fc0f352b528e74
|
||||
title: 设置环境
|
||||
title: Set up the Environment
|
||||
challengeType: 2
|
||||
forumTopicId: 301566
|
||||
dashedName: set-up-the-environment
|
||||
@@ -8,20 +8,20 @@ dashedName: set-up-the-environment
|
||||
|
||||
# --description--
|
||||
|
||||
在接下来的挑战中,我们将会用到 `chat.pug` 文件。首先,你需要在你的 `routes.js` 文件中为 `/chat` 添加一个处理 GET 请求的路由,并给它传入 `ensureAuthenticated`。在回调函数中,我们需要让它 render `chat.pug` 文件,并在响应中包含 `{ user: req.user }` 信息。现在,请修改 `/auth/github/callback` 路由,让它可以像这样设置 user_id:`req.session.user_id = req.user.id`,并在设置完成后重定向至 `/chat`。
|
||||
The following challenges will make use of the `chat.pug` file. So, in your `routes.js` file, add a GET route pointing to `/chat` which makes use of `ensureAuthenticated`, and renders `chat.pug`, with `{ user: req.user }` passed as an argument to the response. Now, alter your existing `/auth/github/callback` route to set the `req.session.user_id = req.user.id`, and redirect to `/chat`.
|
||||
|
||||
我们还需要添加 `http` 和 `socket.io` 两个依赖项,并且像这样引入:
|
||||
Add `http` and `socket.io` as a dependency and require/instantiate them in your server defined as follows:
|
||||
|
||||
```javascript
|
||||
const http = require('http').createServer(app);
|
||||
const io = require('socket.io')(http);
|
||||
```
|
||||
|
||||
现在我们的 *express* 应用已经包含了 *http* 服务,接下来我们需要监听 *http* 服务的事件。为此,我们需要把 `app.listen` 更新为 `http.listen`。
|
||||
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`.
|
||||
|
||||
我们需要处理的第一件事是监听从客户端发出的连接事件,我们可以调用 <dfn>on</dfn> 方法来监听具体的事件。它接收两个参数:一个是发出的事件的标题字符串,另一个是后续用来传递数据的回调函数。在这个回调函数中,我们用 *socket* 来代表它所包含的数据。简单来说,socket 就是指已连接到服务器的客户端。
|
||||
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 thats emitted, and a function with which the data is passed though. 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.
|
||||
|
||||
为了可以监听服务器的连接事件,我们在数据库连接的部分加入如下代码:
|
||||
To listen for connections to your server, add the following within your database connection:
|
||||
|
||||
```javascript
|
||||
io.on('connection', socket => {
|
||||
@@ -29,24 +29,24 @@ io.on('connection', socket => {
|
||||
});
|
||||
```
|
||||
|
||||
对于发出连接事件的客户端,只需要在 `client.js` 中添加以下内容:
|
||||
Now for the client to connect, you just need to add the following to your `client.js` which is loaded by the page after you've authenticated:
|
||||
|
||||
```js
|
||||
/*global io*/
|
||||
let socket = io();
|
||||
```
|
||||
|
||||
注意,这个 `client.js` 文件是在用户通过验证后才加载到客户端的。在这个文件中,我们没有定义 io 变量,但第一行的注释会阻止运行时产生的报错。不过,我们在 chat.pug 的页面上已经为你添加好了 Socket.IO 库的 CDN。
|
||||
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.
|
||||
|
||||
现在你可以重启一下你的 app,尝试一下验证用户,然后你应该会看到服务器的 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'!
|
||||
|
||||
**注意:** 只有在连接到处于同一个 url/server 上的 socket 时,`io()`才可以正常执行。如果需要连接到外部的 socket,就需要这样调用:`io.connect('URL');`。
|
||||
**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');`.
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/aae41cf59debc1a4755c9a00ee3859d1) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/aae41cf59debc1a4755c9a00ee3859d1).
|
||||
|
||||
# --hints--
|
||||
|
||||
应添加 Socket.IO 作为依赖。
|
||||
`socket.io` should be a dependency.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -65,7 +65,7 @@ let socket = io();
|
||||
);
|
||||
```
|
||||
|
||||
应正确引入 `http`,并示例化为 `http`。
|
||||
You should correctly require and instantiate `http` as `http`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -83,7 +83,7 @@ let socket = io();
|
||||
);
|
||||
```
|
||||
|
||||
应正确引入 `socket.io`,并示例化为 `io`。
|
||||
You should correctly require and instantiate `socket.io` as `io`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -101,7 +101,7 @@ let socket = io();
|
||||
);
|
||||
```
|
||||
|
||||
Socket.IO 应监听连接。
|
||||
Socket.IO should be listening for connections.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
@@ -119,7 +119,7 @@ Socket.IO 应监听连接。
|
||||
);
|
||||
```
|
||||
|
||||
客户端应连接到服务器。
|
||||
Your client should connect to your server.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: 5895f70bf9fc0f352b528e64
|
||||
title: 使用模板引擎
|
||||
title: Use a Template Engine's Powers
|
||||
challengeType: 2
|
||||
forumTopicId: 301567
|
||||
dashedName: use-a-template-engines-powers
|
||||
@@ -8,25 +8,23 @@ dashedName: use-a-template-engines-powers
|
||||
|
||||
# --description--
|
||||
|
||||
模版引擎最大的特点之一就是在 HTML 页面展示之前,可以从服务端传变量到模版文件。
|
||||
One of the greatest features of using a template engine is being able to pass variables from the server to the template file before rendering it to HTML.
|
||||
|
||||
在 Pug 文件中,你可以用变量名来调用变量,比如写成 `#{variable_name}` 来实现行内调用,或像 `p= variable_name` 把元素与变量直接写在一起,这表示 p 元素的内容等价于这个变量。
|
||||
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 的 README](https://github.com/pugjs/pug) 里看看它的语法和用法,这样你写出的代码会相对简练。另外要注意,Pug 使用缩进来表示嵌套的代码块。
|
||||
We strongly recommend looking at the syntax and structure of Pug [here](https://github.com/pugjs/pug) on GitHub's README. 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.
|
||||
|
||||
在 pug 的 'index.pug' 文件中,我们使用了 *title* 和 *message* 两个变量。
|
||||
Looking at our pug file 'index.pug' included in your project, we used the variables *title* and *message*.
|
||||
|
||||
为了从服务器传递这些信息,你需要给 *res.render* 的第二个参数传入一个对象,其中包含变量对应的值。比如,如果你想传递对象 `{title: 'Hello', message: 'Please login'` 到你的主页,那么应该这样写:
|
||||
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'}`
|
||||
|
||||
`res.render(process.cwd() + '/views/pug/index', {title: 'Hello', message: 'Please login'});`
|
||||
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!
|
||||
|
||||
刷新页面,如果页面中数据显示正确,你就可以提交你的页面了。
|
||||
|
||||
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/4af125119ed36e6e6a8bb920db0c0871) 的答案。
|
||||
Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point [here](https://gist.github.com/camperbot/4af125119ed36e6e6a8bb920db0c0871).
|
||||
|
||||
# --hints--
|
||||
|
||||
Pug 应正确地展示变量。
|
||||
Pug should correctly render variables.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
|
Reference in New Issue
Block a user