七、用户和权限

在完成了前面的章节之后,我们现在应该已经有了一个可以运行的博客。 我们可以点击所有的链接和帖子,甚至惰性加载更多的帖子。

在本章中,我们将添加后台登录和创建管理员用户。 我们还将创建编辑文章的模板,并使编辑按钮对管理用户可见,以便他们可以编辑和添加新内容。

在本章中,我们将学习以下概念:

  • Meteor 的accounts
  • 创建用户和登录
  • How to restrict certain routes to only logged-in users

    注释

    你可以从上一章中删除所有的会话示例,因为我们不需要它们来继续我们的应用。从my-meteor-blog/main.jsmy-meteor-blog/client/templates/home.jsmy-meteor-blog/client/templates/home.html中删除会话的代码,或者下载上一章的代码的新副本。

    如果你已经直接读了这一章,并想要遵循这些例子, 下载前一章的代码示例从这本书的 web 页面在 https://www.packtpub.com/books/content/support/17713从 GitHub 库 https://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter6

    这些代码示例还将包含所有样式文件,因此我们不必担心在过程中添加 CSS 代码。

Meteor 的帐户包

Meteor 使它非常容易添加认证到我们的 web 应用使用它的accounts包。 accounts包是一个完整的登录解决方案绑定到 Meteor 的核心。 在 Meteor 的许多服务器端功能中,创建的用户可以通过 ID 进行识别,例如,在出版物中:

Meteor.publish("examplePublication", function () {
  // the current loggedin user id can be accessed via
  this.userId;
}

此外,我们可以添加支持通过 Facebook, GitHub,谷歌,Twitter, Meetup,和微博登录简单地添加一个或多个accounts-*核心包。

Meteor 还带有一个简单的登录界面,一个额外的模板,可以添加使用{{> loginButtons}}助手。

所有注册的用户资料将存储在一个集合称为Users,Meteor 为我们创建。 所有的认证和通信进程都使用Secure Remote Password(SRP)协议,大多数外部服务使用 OAuth。

对于我们的博客,我们将简单地创建一个管理员用户,登录后将能够创建和编辑文章。

注释

如果我们想使用第三方服务登录,我们可以先完成本章,然后添加前面提到的一个包。

在我们添加了额外的包裹后,我们可以打开的形式。 我们将看到一个按钮,我们可以配置第三方服务使用我们的应用。

添加帐户包

要开始使用登录系统,我们需要添加accounts-uiaccounts-password包到我们的应用中,如下所示:

  1. 为此,我们打开终端,导航到我们的my-meteor-blog文件夹,并输入以下命令:

    ``` $ meteor add accounts-ui accounts-password

    ```

  2. 成功添加包后,我们可以使用meteor命令再次运行应用。

  3. 由于我们想要防止访问者创建额外的用户帐户,我们需要在我们的accountsconfig中禁用此功能。 首先,我们需要打开上一章创建的my-meteor-blog/main.js文件,并删除所有代码,因为我们不再需要会话示例了。
  4. Then add the following lines of code to this file, but make sure you don't use if(Meteor.isClient), as we want to execute the code on both the client and the server this time:

    Accounts.config({ forbidClientAccountCreation: true });

    这将禁止客户端调用Accounts.createUser()accounts-ui包将不会向我们的访客显示Register按钮。

    注释

    这个选项似乎不适用于第三方服务。 因此,当使用第三方服务时,每个人都可以注册并编辑帖子。 为了防止这种情况发生,我们需要在服务器端为用户创建“deny”规则,这超出了本章的范围。

在模板中添加管理功能

允许编辑我们的文章的最好的方法是添加一个编辑文章链接到我们的文章页面,只有登录后才能看到。 通过这种方式,我们可以节省为额外的后端重建类似的基础设施的时间,并使其易于使用,因为前端和后端之间没有很强的分离。

首先,我们将添加一个创建新职位home链接到我们的模板,然后加入编辑帖子链接邮报的pages模板,最后添加登录到主菜单按钮和形式。

为新帖子添加链接

让我们从添加创建新的链接开始。 在my-meteor-blog/clients/templates/home.html处打开home模板,并在{{#each postsList}}块助手的上方添加以下代码行:

{{#if currentUser}}
    <a href="/create-post" class="createNewPost">Create new post</a>
{{/if}}

{{currentUser}}助手附带accounts-base包,是在我们安装accounts包时安装的。 它将返回当前登录的用户,如果没有用户登录则返回 null。 使用它在一个{{#if}}块助手允许我们只显示内容登录用户。

添加编辑文章的链接

要编辑文章,我们只需在post模板中添加编辑文章链接。 在同一文件夹中打开post.html,在{{author}}后面添加{{#if currentUser}}..{{/if}},如下所示:

<small>
    Posted {{formatTime timeCreated "fromNow"}} by {{author}}

    {{#if currentUser}}
        | <a href="/edit-post/{{slug}}">Edit post</a>
    {{/if}}
</small>

添加登录表单

现在我们有了添加和编辑文章的链接,让我们添加登录表单。 我们可以创建自己的形式,但 Meteor 已经来了一个简单的登录形式,我们可以风格适合我们的设计。

因为我们之前添加了accounts-ui包,Meteor 为我们提供了{{> loginButtons}}模板助手,它作为一个直接放置的模板工作。 为了添加这个,我们将打开我们的layout.html模板,并在菜单的<ul></ul>标签中添加以下助手,如下所示:

<h1>My Meteor Single Page App</h1>
<ul>
    <li>
        <a href="/">Home</a>
    </li>
    <li>
        <a href="/about">About</a>
    </li>

</ul>

{{> loginButtons}}

创建编辑帖子的模板

现在我们只缺少编辑帖子的模板。 为了添加这个,我们将在我们的my-meteor-blog/client/templates文件夹中创建一个名为editPost.html的文件,并用以下代码行填充它:

<template name="editPost">
  <div class="editPost">
     <form>
        <label>
          Title
          <input type="text" name="title" placeholder="Awesome title" value="{{title}}">
        </label>

        <label>
          Description
          <textarea name="description" placeholder="Short description displayed in posts list" rows="3">{{description}}</textarea>
        </label>

        <label>
          Content
          <textarea name="text" rows="10" placeholder="Brilliant content">{{text}}</textarea>
        </label>

        <button type="submit" class="save">Save Post</button>
    </form>
  </div>
</template>

如我们所见,我们已经添加了{{title}}{{description}}{{text}}的助手,这些将在稍后从帖子的数据中得到。 这个简单的模板有三个文本字段,允许我们稍后编辑和创建新文章。

如果我们现在检查一下我们的浏览器,我们会注意到,除了在我们的网站的角落里的Sign in链接,我们目前看不到我们所做的任何更改。 为了能够登录,我们首先需要添加我们的管理员用户。

创建 admin 用户

由于我们取消了在客户端创建用户的操作,作为安全措施,我们将在服务器上创建管理用户,方法与创建示例帖子的方法相同。

打开my-meteor-blog/server/main.js文件,并在Meteor.startup(function(){...})中添加以下代码行:

if(Meteor.users.find().count() === 0) {

    console.log('Created Admin user');

    Accounts.createUser({
        username: 'johndoe',
        email: 'johndoe@example.com',
        password: '1234',
        profile: {
            name: 'John Doe'
        }
    });
}

如果现在转到浏览器,应该能够使用刚刚创建的用户登录,并且立即看到所有编辑链接都出现了。

然而,当我们点击任何编辑链接时,我们会看到notFound模板出现,因为我们还没有创建任何管理路由。

添加权限

Meteor 的account包没有默认的可配置权限的用户。

要添加权限控制,我们可以添加一个第三方包,如deepwell:authorization包,可以在 Atmosphere 上的http://atmospherejs.com/deepwell/authorization上找到,它还附带一个复杂的角色模型。

如果我们想手动操作,我们可以在创建用户时将简单的roles属性添加到用户文档中,然后在创建或更新帖子时在允许/拒绝角色中检查这些角色。 我们将在下一章学习 allow/deny 规则。

如果我们使用Accounts.createUser()函数创建用户,我们不能添加自定义属性,所以我们需要在创建用户后更新用户文档,如下所示:

var userId = Accounts.createUser({
  username: 'johndoe',
  email: 'johndoe@example.com',
  password: '1234',
  profile: {
    name: 'John Doe'
  }
});
// add the roles to our user
Meteor.users.update(userId, {$set: {
  roles: {admin: true},
}})

默认情况下,Meteor 发布当前登录用户的usernameemailsprofile属性。 为了添加额外的属性,例如我们的自定义roles属性,我们需要添加一个发布,以访问客户端上的roles属性,如下所示:

  1. 打开my-meteor/blog/server/publictions.js文件,添加以下出版物:

    Meteor.publish("userRoles", function () { if (this.userId) { return Meteor.users.find({_id: this.userId}, {fields: {roles: 1}}); } else { this.ready(); } });

  2. my-meteor-blog/main.js文件中添加订阅内容如下:

    if(Meteor.isClient){ Meteor.subscribe("userRoles"); }

  3. 现在我们有了客户端上可用的roles属性,我们可以将homepost模板中的{{#if currentUser}}..{{/if}}更改为{{#if currentUser.roles.admin}}..{{/if}},这样只有管理员才能看到按钮。

关于安全的说明

用户只能使用以下命令更新自己的profile属性:

Meteor.users.update(ownUserId, {$set: {profiles:{myProperty: 'xyz'}}})

如果我们想要更新roles属性,我们将失败。 要看到它的实际作用,我们可以打开浏览器的控制台并输入以下命令:

Meteor.users.update(Meteor.user()._id, {$set:{ roles: {admin: false}}});

这将给我们一个错误提示:update failed: Access denied,如下截图所示:

A note on security

注释

如果我们想让用户编辑其他属性,比如他们的roles属性,我们需要添加一个Meteor.users.allow()规则。

为管理员创建路由

现在我们有了一个管理用户,我们可以添加路由,这将导致editPost模板。 虽然在理论上,editPost模板对每个客户端都可用,但它不会产生任何风险,因为允许和拒绝规则是真正的安全层,我们将在下一章中讨论。

为了添加创建 posts 的路由,让我们打开我们的my-meteor-blog/routes.js文件,并将以下路由添加到Router.map()函数中:

this.route('Create Post', {
    path: '/create-post',
    template: 'editPost'
});

当我们点击首页上的创建新文章链接时,就会显示editPost模板,如下截图所示:

Creating routes for the admin

我们看到表单的是空的,因为我们没有为模板设置任何数据上下文,因此模板中的{{title}}{{description}}{{text}}占位符什么也没有显示。

为了使编辑张贴路线工作,我们需要添加订阅类似那些我们做的Post路线本身。 为了保持事物DRY(即Don’t Repeat Yourself),我们可以创建自定义控制器,这两种路由都将使用,如下所示:

  1. Router.configure(...);调用之后添加以下代码:
  2. 现在我们可以简单地编辑Post路线,删除waitOn()data()功能,添加PostController代替

    this.route('Post', { path: '/posts/:slug', template: 'post', controller: 'PostController' });

    。 3. 现在我们也可以通过改变pathtemplate属性来添加Edit Post路线:

    this.route('Edit Post', { path: '/edit-post/:slug', template: 'editPost', controller: 'PostController' });

  3. 就是这样! 当我们现在进入我们的浏览器,我们将能够访问任何文章,并点击编辑按钮,我们将被导向editPost模板。

如果你想知道为什么要用 post 数据填充表单,看看我们刚刚创建的PostController。 在这里,我们在data()函数中返回文章文档,将模板的数据上下文设置为文章的数据。

现在我们已经有了这些路线,我们应该完成了。 我们不应该?

还没有,因为每个知道/create-post/edit-post/my-title路由的人都可以简单地看到editPost模板,即使他或她不是管理员。

阻止访问者查看管理路径

为了防止访问者看到管理路由,我们需要在向他们展示路由之前检查用户是否已经登录。 iron:router配有Router.onBeforeAction()挂钩,可以运行所有或部分路线。 我们将使用它来运行一个函数来检查用户是否已经登录; 如果不存在,我们将假装路由不存在,并简单地显示notFound模板。

routes.js文件末尾添加以下代码片段:

var requiresLogin = function(){
    if (!Meteor.user() ||
        !Meteor.user().roles ||
        !Meteor.user().roles.admin) {
        this.render('notFound');

    } else {
        this.next();
    }
}; 

Router.onBeforeAction(requiresLogin, {only: ['Create Post','Edit Post']});

这里,我们首先创建了requiresLogin()函数,它将在Create PostEdit Post路由之前执行,因为我们将它们作为第二个参数传递给Router.onBeforeAction()函数。

requiresLogin()内,检查用户是否登录,调用Meteor.user()时返回用户文档,是否具有admin角色。 如果没有,我们就简单地渲染notFound模板,而不继续路由。 否则,我们运行this.next(),它将继续渲染当前路由。

就是这样! 如果我们现在注销并导航到/create-post路径,我们将看到notfound模板。

如果我们登录,模板将切换并立即显示editPost模板。

这是因为当我们将requiresLogin()函数传递给Router.onBeforeAction()时,它就会变成响应式的,而Meteor.user()是一个响应式的对象,用户状态的任何改变都会重新运行这个函数。

现在我们已经创建了管理用户和必要的模板,接下来可以实际创建和编辑文章了。

小结

在本章中,我们学习了如何创建和登录用户,如何只向登录用户显示内容和模板,以及如何根据登录状态更改路由。

想要了解更多,请看以下链接:

你可以在https://www.packtpub.com/books/content/support/17713或在 GitHub 上https://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter7找到本章的代码示例。

在下一章中,我们将学习如何创建和更新 posts,以及如何从客户端控制对数据库的更新。