VueJS - JavaScript фреймворк, который впитал в себя лучшее из AngularJS и React. Первую версию фреймворка можно было бы охарактеризовать как "облегченный AngularJS". VueJS имеет шаблонную систему, подобную Angular, но использует ES5 геттеры и сеттеры, чтобы следить за тем, что нужно изменить в DOM.
Давайте создадим простое приложение, используя Express, PassportJS и VueJS 2.0, чтобы продемонстрировать как настроить аутентификацию в приложении, а затем как общаться с нашим бэкэнд-сервером со стороны клиента. Приложение позволит пользователю просматривать, добавлять и удалять "Возгласы". Вы сможете просматривать чьи-либо возгласы. Изначально вы можете только добавлять их. Вы сможете удалять свои собственные и реплики других пользователей, если у вас будет функция delete
.
Начнем с простого. Давайте создадим каталог для хранения нашего кода, а затем добавим наши первоначальные зависимости. Мы установим их с помощью npm.
mkdir vuejs2-authentication
cd vuejs2-authentication
npm init -y
npm install --save-dev nodemon
npm install --save express body-parser express-session connect-mongo flash node-uuid passport passport-local pug
Они все будут использоваться для создания нашего сервера. Далее давайте создадим несколько фиктивных данных и поместим их в файл с именем data.json.
{
"users": [
{
"username": "rachel@friends.com",
"password": "green",
"scopes": ["read", "add", "delete"]
},
{
"username": "ross@friends.com",
"password": "geller",
"scopes": ["read"]
}
],
"exclamations": [
{
"id": "10ed2d7b-4a6c-4dad-ac25-d0a56c697753",
"text": "I'm the holiday armadillo!",
"user": "ross@friends.com"
},
...
{
"id": "9aad4cbc-7fff-45b3-8373-a64d3fdb239b",
"text": "Ross, I am a human doodle!",
"user": "rachel@friends.com"
}
]
}
Кроме того, убедитесь, что вы добавили следующие скрипты к вашему файлу package.json:
"start": "node server.js",
"serve": "nodemon server.js"
Я использую Node 6.2 для построения серверной части нашего приложения, так что я могу использовать большую часть синтаксиса ES2015. Если вы не используете Node 6+, то выполните следующее: npm install --save-dev babel-cli babel-preset-es2015
. Теперь serve
должен стать таким: "serve": "nodemon --exec babel-node --presets=es2015 server.js"
.
Создайте файл с именем server.js и напишите в нем:
// Импорт необходимых модулей
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const flash = require('flash');
const passport = require('passport');
const LocalStrategy = require('passport-local');
const uuid = require('node-uuid');
const appData = require('./data.json');
// Создание данных приложения (подражаем БД)
const userData = appData.users;
const exclamationData = appData.exclamations;
function getUser(username) {
const user = userData.find(u => u.username === username);
return Object.assign({}, user);
}
// Создание порта по умолчанию
const PORT = process.env.PORT || 3000;
// Создание нового сервера
const server = express();
// Настройка сервера
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({ extended: false }));
server.use(session({
secret: process.env.SESSION_SECRET || 'awesomecookiesecret',
resave: false,
saveUninitialized: false,
store: new MongoStore({
url: process.env.MONGO_URL || 'mongodb://localhost/vue2-auth',
}),
}));
server.use(flash());
server.use(express.static('public'));
server.use(passport.initialize());
server.use(passport.session());
server.set('views', './views');
server.set('view engine', 'pug');
Что мы здесь сделали: во-первых, мы добавили наши зависимостей. Далее мы добавили нашему JSON-файле данные, которые будут использоваться в нашем приложении. Как правило, вы бы использовали какую-нибудь внешнюю базу данных, но для наших целей подойдет и такой вариант. И, наконец, мы создаем Express-сервер и настраиваем его с сессиями и body-парсерами. Мы также включили flash-сообщения и статический сервер так, чтобы мы могли обслуживать наши JavaScript файлы через наш сервер Node. Затем мы добавляем Pug в качестве шаблонизатора.
Я передаю MongoStore
к промежуточному ПО сессии, поэтому сессии будут теперь храниться в Mongo. Это не обязательно при разработке. Сессия будет использовать MemoryStore
по-умолчанию. Просто удалите store
из объекта конфигурации сессии, если он вам не нужен.
Далее, давайте настроим Passport для локальной аутентификации. Мы создадим форму, которая взаимодействует с ним позже.
// Настройка Passport
passport.use(new LocalStrategy(
(username, password, done) => {
const user = getUser(username);
if (!user || user.password !== password) {
return done(null, false, { message: 'Неправильные имя пользователя или пароль' });
}
delete user.password;
return done(null, user);
}
));
// Сериализация пользователя в сессии
passport.serializeUser((user, done) => {
done(null, user.username);
});
passport.deserializeUser((username, done) => {
const user = getUser(username);
delete user.password;
done(null, user);
});
Это довольно стандартный код. Мы рассказываем Passport о нашей стратегии. Когда он пытается аутентифицироваться, мы ищем пользователя в наших пользовательских данных. Если он существует и пароль правильный, мы продвигаемся вперед. В противном случае мы вернем сообщение пользователю. Мы также помещаем имя пользователя в сессию и находим пользователя на основе этого имени пользователя, когда нам нужно получить информацию о нем.
Следующий фрагмент кода, который мы напишем, будет состоять из нескольких middleware-функций, которые мы будем использовать на наших маршрутах, чтобы обеспечить пользователю возможность выполнять определенные действия.
// Создание собственных middleware-функций
function hasScope(scope) {
return (req, res, next) => {
const { scopes } = req.user;
if (!scopes.includes(scope)) {
req.flash('error', 'Имя пользователя и пароль недействительны.');
return res.redirect('/');
}
return next();
};
}
function canDelete(req, res, next) {
const { scopes, username } = req.user;
const { id } = req.params;
const exclamation = exclamationData.find(exc => exc.id === id);
if (!exclamation) {
return res.sendStatus(404);
}
if (exclamation.user !== username && !scopes.includes('delete')) {
return res.status(403).json({ message: "Вы не можете удалить эту реплику." });
}
return next();
}
function isAuthenticated(req, res, next) {
if (!req.user) {
req.flash('error', 'Вы должны быть зарегистрированы.');
return res.redirect('/');
}
return next();
}
Давайте рассмотрим их. Функция hasScope проверяет, есть ли в запросе пользователя необходимые ему возможности. Вызовем ее с помощью строки возможности, а она возвратит промежуточное значение, которое будет использоваться сервером. Функция canDelete похожа на hasScope, но она проверяет, есть ли у пользователя высказывание либо функция delete. Если нет, ее нельзя удалить. Она будет использоваться только позже на одном маршруте. Наконец, у нас есть isAuthenticated, который проверяет вошел ли пользователь в систему.
Далее давайте создадим два наших основных маршрута: /
и /dashboard
.
// Create home route
server.get('/', (req, res) => {
if (req.user) {
return res.redirect('/dashboard');
}
return res.render('index');
});
server.get('/dashboard',
isAuthenticated,
(req, res) => {
res.render('dashboard');
}
);
Здесь мы создаем маршрут «домой». Мы проверяем, вошел ли пользователь в систему. Если да, мы отправляем их на панель управления. Мы также создаем маршрут к панели управления. Сначала мы используем наши промежуточные isAuthenticated
для того, чтобы убедиться, что пользователь вошел в систему, а затем уже будем обрабатывать шаблон панели.
Теперь нам необходимо создать наши маршруты для аутентификации.
// Создаем маршрут «домой»
const authRoutes = express.Router();
authRoutes.post('/login',
passport.authenticate('local', {
failureRedirect: '/',
successRedirect: '/dashboard',
failureFlash: true,
})
);
server.use('/auth', authRoutes);
Мы создаем роутер, который монтируется на /auth
и имеет единственный маршрут в /login
. Там мы потом сделаем шикарную форму.
Затем мы создадим маршруты для нашего API, которое позволит нам получить все реплики, добавить новую или удалить ее. Существует также маршрут для получения информации о входе в систему /api/me
. Чтобы все у нас было красиво разложено, мы создаем новый маршрутизатор, добавляем в него наши маршруты и монтируем его на наш сервер как /api
.
// Создание API маршрутов
const apiRoutes = express.Router();
apiRoutes.use(isAuthenticated);
apiRoutes.get('/me', (req, res) => {
res.json({ user: req.user });
});
// Получить все высказывания пользователя
apiRoutes.get('/exclamations',
hasScope('read'),
(req, res) => {
const exclamations = exclamationData;
res.json({ exclamations });
}
);
// Добавить высказывание
apiRoutes.post('/exclamations',
hasScope('add'),
(req, res) => {
const { username } = req.user;
const { text } = req.body;
const exclamation = {
id: uuid.v4(),
text,
user: username,
};
exclamationData.unshift(exclamation);
res.status(201).json({ exclamation });
}
);
// Удалить высказывание
apiRoutes.delete('/exclamations/:id',
canDelete,
(req, res) => {
const { id } = req.params;
const exclamationIndex = exclamationData.findIndex(exc => exc.id === id);
exclamationData.splice(exclamationIndex, 1);
res.sendStatus(204);
}
);
server.use('/api', apiRoutes);
Теперь нам просто нужно запустить наш сервер.
// Start the server
server.listen(PORT, () => {
console.log(`API слушает порт ${PORT}`);
});
Это все, что нам нужно для нашего сервера! Займемся шаблонами. Создайте файл по адресу views/index.pug
и поместите в него следующее:
doctype html
html(lang='en')
head
title Exclamations!
link(rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7' crossorigin='anonymous')
style.
h1 {
margin-bottom: 20px;
}
body
.container-fluid
.row
.col-md-4.col-md-offset-4
while message = flash.shift()
.alert.alert-danger
p= message.message
h1.text-center Exclamations!
form(action='/auth/login' method='POST')
.form-group
label(for='username') Email Address
input.form-control(name='username')
.form-group
label(for='password') Password
input.form-control(name='password' type='password')
button.btn.btn-primary(type='submit') Login
Это базовая HTML-страница. Мы используем bootstrap, чтобы дать нам некоторые базовые стили. Мы создаем простую форму, которая будет отправлена на наш сервер. Мы также выводим любые сообщения об ошибках, которые могут быть появляться в сессии.
Теперь, если вы запустите сервер, используя npm run serve
и введете localhost:3000
в браузере, вы увидите страницу входа.
Войдите в систему с помощью одного из электронного письма и пароля в data.json файле. Однако, как только вы войдете, вы получите сообщение о том, что у нас нет панели управления. Так давайте создадим его сейчас!
doctype html
html(lang='en')
head
title Dashboard
link(rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7' crossorigin='anonymous')
link(rel='stylesheet' href='/styles.bundle.css')
body
#app-container
script(src='app.bundle.js')
Так мало? Да. Все, что нам нужно - это дать VueJS место, где можно смонтировать его начальный компонент. Вот почему нам потребуется только app-container
и скрипт, который содержит наш код.
Если мы заранее не создадим эти файлы, ничего работать не будет. Давайте создадим эти файлы, а затем загрузим те зависимости, которые нужны нам в первую очередь.
mkdir public
touch public/app.bundle.js public/styles.bundle.css
npm install --save vue@next axios
npm install --save-dev babel-core babel-runtime babel-plugin-transform-runtime babel-preset-es2015 browserify babelify vueify@next browserify-hmr vue-hot-reload-api watchify concurrently
Много зависимостей, я знаю. На самом деле все просто. Это позволит нам использовать Babel на полную его мощность. С помощью browserify мы соберем весь наш код воедино. Мы также используем vueify, поэтому можем поместить наши компоненты в один файл. Обратите внимание, мы используем в следующей версии Vue и vueify. Это даст нам доступ к самой новой альфа-версии VueJS и версии vueify, которая работает с новой версией Vue. Давайте поместим некоторые скрипты в наш package.json, чтобы сделать компиляцию нашего приложения гораздо проще.
"prestart": "npm run build:js",
"build:js": "browserify src/app.js -t vueify -p [ vueify/plugins/extract-css -o public/styles.bundle.css ] -t babelify -o public/app.bundle.js",
"watch:js": "watchify src/app.js -t vueify -t babelify -p browserify-hmr -p [ vueify/plugins/extract-css -o public/styles.bundle.css ] -o public/app.bundle.js",
"dev": "concurrently \"npm run serve\" \"npm run watch:js\""
Нам также необходимо настроить Babel. Создайте файл .babelrc и запишите в него следующее:
{
"presets": [
"es2015"
],
"plugins": [
"transform-runtime"
]
}
На этом все. Ставьте лайки и будет вам вторая часть.