Написание простого блога на SailsJS: наглядная практика для начинающих (Часть 2)

в 5:22, , рубрики: node.js, sails.js, tutorial, метки: , ,

Синопсис

Ранее мы изучили написание основы для нашего блога, при написании основы мы ознакомились с организацией статики, составлением модели и написанием кода контроллера. Узнали как можно работать с конфигурациями путей (routes.js), и как работать с представлениями в SailsJS. Во второй части о написании простого блога на SailsJS, мы рассмотрим следующие пункты: Пользователи: создание. Сессии: создание (вход), разрыв (выход). Написание Админ Панели, и работа с политикой и ограничениями доступа.

Пользователи

Для создания нового комплекса API вводим уже знакомую нам команду в корне нашего проекта.

sails generate api user

В этот раз при организации кода нам понадобится шифровать пароль, для этого нам потребуется отличный модуль password-hash, который подойдет для этой задачи. Чтобы его установить — в корне нашего проекта введите следующую команду

npm install password-hash --save

Параметр --save указывает на то что мы сохраним значение модуль как зависимость в package.json.

Так как в предыдущем посте я уже рассмотрел базовые навыки работы с моделями и контроллерами в SailsJS — где они находятся, и как их правильно составлять, я не буду обращать внимания на уже очевидные вещи.

Модель

Для нашего пользователя будет несколько атрибутов:

  1. Имя пользователя
  2. Пароль
  3. Админ — параметр доступа

При составлении модели мы не должны забывать что мы хотим также сделать шифрование пароля на сервере, а также сделать несколько дополнительных вспомогательных функций основанные на работе с жизненными циклами моделей

var passwordHash = require('password-hash');

var User = {

  attributes: {

	username: {type: 'string', required: true, unique: true},
	password: {type: 'string', required: true, minLength: 8},

    admin: {
      type: 'boolean',
      defaultsTo: false
    },
    toJSON: function() {
    	var element = this.toObject();
    	delete element.password;
    	return element;
    }

  },

  beforeCreate: function (values, next) {
    // Создаем зашифрованную запись пароля в БД
    var mainPass = passwordHash.generate(values.password);
    values.encryptPassword = mainPass;
    next();  	
  }

};

module.exports = User;

До создания нового пользователя мы добавляем дополнительный атрибут — encryptedPassword, который представляет собой зашифрованную версию пароля.

Контроллер

В контроллере мы сделаем только возможность создавать пользователя, и индексную страницу. Как сделать обработчики обновления и удаления пользователя вы сможете сами по подобию того что мы сделали в контроллере постов. Вот код контроллера.


module.exports = {

	//@API - создание пользователя

	/**
	 * Создание нового пользователя,
	 * в качестве параметров передаем
	 * имя пользователя, пароль, и булевое
	 * значение админ. После создания
	 * пользователя он аутентифицируется
	 * в сессии. После создания пользователя 
	 * администратора мы установим политику
	 * admin (api/policies/admin.js) чтобы к
	 * этой функции больше не могли обращаться
	 * не привелегированные пользователи
	 */
	 
	create: function (req, res) {
		var elem = {
			username : req.param('username'),
			password : req.param('password'),
			admin    : req.param('admin')
		};

		User.create(elem).exec(function (err, user) {
			if (err) return res.send(500);
			req.session.auth = true;
			res.redirect('/');
		});
	},

	// @MAIN
	index: function (req, res) {
		res.view();
	}
};

В конфигурации путей (config/routes.js) добавим следующее.

  '/register' : 'UserController',

В качестве простой защиты включим защиту CSRF в файле конфигурации (config/csrf.js) отредактируем строку следующим образом

module.exports.csrf = true;
Представление (views/user/index.ejs)

А теперь составим представление, оно будет иметь простейшую структуру.

<div class="container">
	<div class="row">
		<div class="col-md-4"></div>
		<div class="col-md-4">
			<div class="panel panel-default">
				<div class="panel-body">
					<form action="/user/create" method="post">
						<input type="text" name="username" placeholder="Имя Пользователя"><br>
						<input type="password" name="password" placeholder="Пароль"><br>
						<input type="hidden" name="_csrf" value="<%= _csrf %>">
						<!-- Поле которое можно удалить после создания администратора -->
						<input type="hidden" name="admin" value="true">
						
						<input type="submit" class="btn btn-success btn-block" value="Зарегистрироваться">
					</form>
				</div>
			</div>
		</div>
		<div class="col-md-4"></div>
	</div>
</div>

Или же вместо представления можно просто отключив csrf создать пользователя — например с помощью Postman. А потом заблокировать контроллер Пользователей.

На этом завершим написание составляющих для создания пользователей. Теперь у нас есть модель, контроллер, путь страницы регистрации, и шаблон представления.

Сессии

Для авторизации пользователей мы будем использовать встроенную в Sails систему сессий — это удобно и достаточно безопасно (если сравнивать с cookie) наш контроллер сессий сможет создавать сессии и уничтожать их. Создайте новый контроллер стандартным способом. Вот код нашего контроллера.

var passwordHash = require('password-hash');

module.exports = {


	// @API основные функции сессии

	create: function (req, res) {
		/**
		 * Задаем переменные запрашиваемых
		 * параметров, в нашем случае логин
		 * и пароль
		 */
		var username = req.param('username'),
			password = req.param('password');

		/**
		 * Если нет логина или пароля в запросе
		 * вывести ошибку, и перенаправить обратно
		 * (прим. здесь лучше сделать подробную
		 * обработку ошибок, например с flash)
		 */

		if (!username || !password) {
			return res.redirect('/session');
		};

		/**
		 * Найти пользователя из запроса логина
		 * (username - req.param('username'))
		 * когда пользователь найден производиться 
		 * сравнение зашифрованного пароля с паролем
		 * который был отправлен запросом, если он
		 * валиден, то создается внешний статус - 
		 * авторизован или нет, и дается доступ к
		 * данным через внешний доступ сессии. Это
		 * позволит нам в дальнейшем создать политику
		 * для ограничивания доступа к определенным 
		 * разделам нашего блога (используя сессии)
		 */
		User.findOneByUsername(username).exec(function (err, user) {
			if (!user || err) return res.send(500);
			if (passwordHash.verify(password, user.encryptPassword)) {
				// Авторизовать пользователя в сессии
                                // Дать доступ к данным авторизованного 
                                // пользователя из сессии
				req.session.auth = true;
				req.session.User = user;
				
				if (req.session.User.admin) {
					return res.redirect('/admin');
				};
			};
		});
	},
	/**
	 * Создаем выход из сессии который 
	 * просматривает есть ли пользователь
	 * в онлайне, и уничтожает сессию
	 */
	destroy: function (req, res) {
		User.findOne(req.session.User.id).exec(function (err, user) {
			if (user) {
				req.session.destroy();
				res.redirect('/');
			} else { res.redirect('/login'); };
		});
	},

	// @MAIN

	index: function (req, res) {
		res.view();
	}
};
Конфигурация путей

  '/login'    : 'SessionController',
  '/logout'   : {
    controller: 'session',
    action: 'destroy'
  },
Представление

И представление страницы входа

<div class="container">
	<div class="row">
		<div class="col-md-4"></div>
		<div class="col-md-4 text-center">
			<h2>Sign-in form</h2><hr>
			<form action="/session/create" method="POST">
				<input type="text" name="username" placeholder="Username" class="form-control" /><br>
				<input type="password" name="password" placeholder="password" class="form-control" /><br>
				<input type="submit" class="btn btn-default" value="Log-In" />
				<input type="hidden" name="_csrf" value="<%= _csrf %>" />
			</form>
		</div>
		<div class="col-md-4"></div>
	</div>
</div>

Теперь мы создали возможность регистрироваться и создавать сессии (входить с систему), теперь осталось сделать страницу админ панельки, и настроить политику разграничения доступа. И так завершающий этап.

Админ Панель и Разграничение Прав

У нас будет простейшая страница админки состоящая из списка постов, и формы добавления нового поста. А также страница для редактирования уже существующих постов. Теперь создайте новый контроллер admin. Вот код нашего контроллера.

module.exports = {
	
	index: function (req, res) {
		Post.find()
			.sort('id DESC')
			.exec(function (err, posts) {
				if (err) return res.send(500);
				res.view({
					posts: posts
				});
			});
	},

	edit: function (req, res) {
		var Id = req.param('id');

		Post.findOne(Id).exec(function (err, post) {
			if (!post) return res.send(404);
			if (err) return res.send(500);
			res.view({
				post: post
			});
		});
	}
};

И 2 наших представления.

views/admin/index.ejs

<div class="container text-center">
	<h2>CREATE NEW</h2><br>
	<div class="row">
		<form action="/post/create" method="POST">
			<div class="col-md-6">
				<input class="form-control" type="text" name="title" placeholder="Title Post"><hr>
				<textarea rows="3" class="form-control" name="description" placeholder="Description"></textarea>
			</div>
			<div class="col-md-6">
				<textarea rows="7" class="form-control" name="content" placeholder="Content"></textarea>
			</div>
			<div class="col-md-12">
				<input type="hidden" name="_csrf" value="<%= _csrf %>" />
				<br><input type="submit" class="btn btn-success" value="CREATE" />
			</div>
		</form>
	</div><br>
	<h2>POST LIST</h2>
	<table class="table text-left">
		<tr>
			<th>ID</th>
			<th>TITLE</th>
			<th></th>
			<th></th>
			<th></th>
		</tr>
		<% _.each(posts, function (post) { %>
			<tr>
				<td><%= post.id %></td>
				<td><%= post.title %></td>
				<td><a href="/post/watch/<%= post.id %>" class="btn btn-info">Look</a></td>
				<td><a href="/post/delete/<%= post.id %>" class="btn btn-danger">Delete</a></td>
				<td><a href="/admin/edit/<%= post.id %>" class="btn btn-warning">Edit</a></td>
			</tr>			
		<% }) %>
	</table>
</div>
views/admin/edit.ejs

<div class="container">
	<div class="row">
		<div class="col-md-2"></div>
		<div class="col-md-8 well text-center">
			<form action="/post/update" method="POST">
				<h4>Title</h4>
				<input type="text" class="form-control" name="title" value="<%= post.title %>"><br>
				<h4>Description</h4>
				<textarea rows="3" name="description" class="form-control" value="<%= post.description %>"></textarea>
				<h4>Content</h4>
				<textarea rows="7" name="content" class="form-control" value="<%= post.content %>"></textarea>
				<input type="hidden" name="_csrf" value="<%= _csrf %>" /><br>
				<input type="submit" class="btn btn-success" value="Update">
			</form>
		</div>
		<div class="col-md-2"></div>
	</div>
</div>

А теперь займемся созданием политики.

Политика

Система разграничения прав доступа в Sails весьма удобна и проста в использовании, в нашем случае достаточно лишь сверить чтобы пользователь:

  • Был Авторизован
  • И являлся администратором

Чтобы выразить это в коде создадим файл нашей политики — api/policies/admin.js. И вот код нашего разграничителя.

module.exports = function (req, res, ok) {
	if (req.session.auth && req.session.User.admin) {
		return ok();
	} else {
		return res.redirect('/login');
	};
}

В данном случае коллбек возвращается для того чтобы пропустить дальнейшие действия. если результат противоположный — запретить и перенаправить на страницу входа. Для активации нашей политики на определенном контроллере — откройте файл config/policies.js и приведите его в следующий вид.


module.exports.policies = {

  // Default policy for all controllers and actions
  // (`true` allows public access)
  '*': true,

  /**
   * Вставляем для нашего контроллера
   * Admin политику admin.js, которая
   * ограничивает доступ.
   */

  AdminController: {
  	'*': 'admin'
  },

  UserController: {
  	create: 'admin'
  },

  PostController: {
  	// То что могут видеть все
  	index  : true,
  	page   : true,
  	watch  : true,

  	// То что может только админ
  	create : 'admin',
  	update : 'admin',
  	delete : 'admin',
  }
};

На этом мы закончим написание простого блога на Sails, конечно в нем очень мало функционала и защиты — нет обработки ошибок (даже flash сообщений), нет полноценной мультипользовательской админки, но эта статья расcчитана как маленький вводный курс в этот фреймворк, дальше можете изучать его сами. В дальнейшем нарастить функционал вам не должно составить большого труда.

Автор: friktor

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js