Horváth Győző
Egyetemi adjunktus
1117 Budapest, Pázmány Péter sétány 1/c., 2.420-as szoba
Tel: (1) 372-2500/1816
horvath.gyozo@inf.elte.hu
Eddig: egyetlen server.js
fájlba dolgoztunk
Cél: a különböző funkciójú kódrészletek rendszerezett elkülönítése!
MVC mintának megfelelően
models
views
(már van :) )controllers
(másik neve lehetne routes
)Egyéb lehetőségek
config
: konfigurációs állományokviewmodels
(ha már bevezettük)require
: importálásmodule.exports
: exportálás//Modul: math.js
function add(a, b) {
return a + b;
}
module.exports = add;
//Főprogram: index.js
var add = require('./math');
console.log(add(2, 3));
A fenti elgondolásnak megfelelően bontsuk részekre az alkalmazásunkat!
Waterline konfiguráció kiemelése
// config/waterline.js
module.exports = {
adapters: {
/* ... */
},
connections: {
/* ... */
},
defaults: {
/* ... */
},
};
// server.js
var waterlineConfig = require('./config/waterline');
orm.initialize(waterlineConfig, function(err, readyModels) {
/* ... */
});
Modellek kiemelése
// models/error.js
module.exports = {
identity: 'error',
/* ... */
};
// server.js
var errorCollection = require('./models/error');
orm.loadCollection(Waterline.Collection.extend(errorCollection));
Vezérlők kiemelése
// controllers/error.js
var express = require('express');
var router = express.Router();
router.get('/list', function (req, res) { /* ... */ });
router.get('/new', function (req, res) { /* ... */ });
router.post('/new', function (req, res) { /* ... */ });
module.exports = router;
// server.js
var errorRouter = require('./routes/error');
app.use('/errors', errorRouter);
Nézetmodellek
// viewmodels/error.js
module.exports = {
decorateErrors: function decorateErrors(errorContainer) {
/* ... */
}
}
Próbáld meg az Express applikációt (app
változó) és az ORM részt (orm
változó) is külön fájlba szervezni!
Ki használja az alkalmazást?
Ismerem?
Eredmény: vendég/azonosított felhasználó
Sikeres azonosítás után a felhasználó munkamenetébe egy speciális tokent helyezünk el.
Ennek megléte vagy hiánya jelzi az azonosítás állapotát.
Lépések:
npm install passport --save
var passport = require('passport');
Telepítés
npm install passport-<strategy> --save
Használat
var Strategy = require('passport-<strategy>');
passport.use([name, ] new Strategy([options, ]
function(username, password, done) {
//Felhasználónév és jelszó ellenőrzése
// Eredmény jelzése
return done(err); //Hiba történt
return done(null, false, { message: 'Üzenet' }); //Sikertelen azonosítás
return done(null, user); //Sikeres azonosítás, user objektum visszaadása
}
));
//Passport middlewares
app.use(passport.initialize());
//Session esetén (opcionális)
app.use(passport.session());
Mit tároljunk a munkamenetben?
// Sorosítás a munkamenetbe
passport.serializeUser(function(user, done) {
done(null, <munkamenetben tárolandó adat>);
});
// Visszaállítás a munkamenetből
passport.deserializeUser(function(obj, done) {
done(null, <az elvárt user objektum>);
});
passport.authenticate()
metódus a végpontoknál
// Átirányítási paraméterekkel
app.post('/signup', passport.authenticate(<strategy>, {
successRedirect: '/login',
failureRedirect: '/login/signup',
failureFlash: true, // req.flash() metódusra van szükség!
failureMessage: "Hibás adatok", // opcionális alapértelmezett üzenet
badRequestMessage: 'Hiányzó adatok', // hiányzó hitelesítési adatok hibaüzenete
}));
// Callback függvénnyel
app.post('/login', passport.authenticate(<strategy>),
function(req, res) {
// Sikeres hitelesítésnél hívódik meg
// `req.user` a hitelesített user
res.redirect('/users/' + req.user.username);
});
req.logout()
Jogosultságkezelés
Az azonosított felhasználó milyen erőforrásokhoz fér hozzá?
Többszintű lehet:
Felhasználók csoportosítása: szerepkörök
Tipikusan végponti middleware-ként épülnek be.
req.isAuthenticated()
connect-ensure-login
acl
A hibák kezelése (listázás, új felvétele) csak azonosított személyeknek legyen elérhető. Ehhez először tegyük lehetővé felhasználók regisztrálását és bejelentkeztetését, kijelentkeztetését, majd a szóban forgó végpontok védelmét.
Login oldal sablonja (views/login/index.hbs
)
<div class="page-header">
<h1>Bejelentkezés</h1>
</div>
<form class="form-horizontal" method="post">
<fieldset>
<div class="form-group">
<label for="neptun" class="col-lg-2 control-label">Neptun</label>
<div class="col-lg-10">
<input class="form-control" id="neptun" name="neptun" type="text" value="{{data.neptun}}">
</div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label">Jelszó</label>
<div class="col-lg-10">
<input class="form-control" type="password" id="password" name="password">
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
</form>
<div class="list-group">
<a href="/login/signup" class="list-group-item">
Regisztráció
</a>
</div>
/login
végpontra bekötése (controllers/login.js
)
router.get('/', function (req, res) {
res.render('login/index');
})
A regisztrációs oldal sablonja (views/index/signup
)
<div class="page-header">
<h1>Regisztráció</h1>
</div>
<form class="form-horizontal" method="post">
<fieldset>
<div class="form-group">
<label for="surname" class="col-lg-2 control-label">Vezetéknév</label>
<div class="col-lg-10">
<input class="form-control" type="text" id="surname" name="surname">
</div>
</div>
<div class="form-group">
<label for="forename" class="col-lg-2 control-label">Keresztnév</label>
<div class="col-lg-10">
<input class="form-control" type="text" id="forename" name="forename">
</div>
</div>
<div class="form-group">
<label for="neptun" class="col-lg-2 control-label">Neptun-kód</label>
<div class="col-lg-10">
<input class="form-control" type="text" id="neptun" name="neptun">
</div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label">Jelszó</label>
<div class="col-lg-10">
<input class="form-control" type="password" id="password" name="password">
</div>
</div>
<div class="form-group">
<label for="avatar" class="col-lg-2 control-label">Avatar URL</label>
<div class="col-lg-10">
<input class="form-control" type="text" id="avatar" name="avatar">
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
</form>
/login/signup
végpontra bekötése (controllers/login.js
)
router.get('/signup', function (req, res) {
res.render('login/signup');
});
Munkamenetbe sorosítás
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
Passport lokális stratégiájának beállítása (regisztrálás)
npm install passport-local --save
var LocalStrategy = require('passport-local').Strategy;
// Local Strategy for sign-up
passport.use('local-signup', new LocalStrategy({
usernameField: 'neptun',
passwordField: 'password',
passReqToCallback: true,
},
function(req, neptun, password, done) {
// Hiba ág
return done(null, false, { message: 'Létező neptun.' });
// Siker ág
return done(null, {username: 'anonymous'});
}
));
Regisztrációs adatok fogadása és regisztráció
router.post('/signup', passport.authenticate('local-signup', {
successRedirect: '/login',
failureRedirect: '/login/signup',
failureFlash: true,
badRequestMessage: 'Hiányzó adatok'
}));
Hibaüzenetek megjelenítése
Sablon
{{#each errorMessages}}
<div class="alert alert-dismissible alert-danger">
<button type="button" class="close" data-dismiss="alert">×</button>
{{this}}
</div>
{{/each}}
Vezérlő
router.get('/signup', function (req, res) {
res.render('login/signup', {
errorMessages: req.flash('error')
});
});
User modell létrehozása és regisztrálása az ORM-ben
module.exports = {
identity: 'user',
connection: 'default',
attributes: {
neptun: {
type: 'string',
required: true,
unique: true,
},
password: {
type: 'string',
required: true,
},
surname: {
type: 'string',
required: true,
},
forename: {
type: 'string',
required: true,
},
avatar: {
type: 'string',
url: true,
},
role: {
type: 'string',
enum: ['riporter', 'operator'],
required: true,
defaultsTo: 'riporter'
},
errors: {
collection: 'error',
via: 'user'
},
},
};
Az error
modell módosítása a user
modellel való összekapcsolás miatt
module.exports = {
identity: 'error',
/* ... */
user: {
model: 'user',
},
}
};
Regisztrálás a user
modell segítségével
function(req, neptun, password, done) {
req.app.models.user.findOne({ neptun: neptun }, function(err, user) {
if (err) { return done(err); }
if (user) {
return done(null, false, { message: 'Létező neptun.' });
}
req.app.models.user.create(req.body)
.then(function (user) {
return done(null, user);
})
.catch(function (err) {
return done(null, false, { message: err.details });
})
});
}
Bejelentkezési stratégia
// Stratégia
passport.use('local', new LocalStrategy({
usernameField: 'neptun',
passwordField: 'password',
passReqToCallback: true,
},
function(req, neptun, password, done) {
req.app.models.user.findOne({ neptun: neptun }, function(err, user) {
if (err) { return done(err); }
if (!user || !user.validPassword(password)) {
return done(null, false, { message: 'Helytelen adatok.' });
}
return done(null, user);
});
}
));
// User modell
module.exports = {
identity: 'user',
connection: 'default',
attributes: {
validPassword: function (password) {
return password === this.password;
}
},
};
Stratégia alkalmazása
router.post('/', passport.authenticate('local', {
successRedirect: '/errors/list',
failureRedirect: '/login',
failureFlash: true,
badRequestMessage: 'Hiányzó adatok'
}));
Layout: ki-/bejelentkezés váltogatása
<ul class="nav navbar-nav navbar-right">
{{#if loggedIn}}
<li><a href="/logout">Kilépés</a></li>
{{else}}
<li><a href="/login">Bejelentkezés</a></li>
{{/if}}
<li><a href="#">About</a></li>
</ul>
{{#if loggedIn}}
<p class="navbar-text navbar-right">Üdv, {{user.forename}}!</p>
{{/if}}
// Middleware segédfüggvény
function setLocalsForLayout() {
return function (req, res, next) {
res.locals.loggedIn = req.isAuthenticated();
res.locals.user = req.user;
next();
}
}
/* ... */
app.use(setLocalsForLayout());
/errors
végpontok védelme
Segédfüggvény
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login');
}
Használata
app.use('/errors', ensureAuthenticated, errorRouter);
Szerepkör szintű védelem
Pl. /operator
végpontot csak az operator
szerepkörűek érhetik el.
Segédfüggvény
function andRestrictTo(role) {
return function(req, res, next) {
if (req.user.role == role) {
next();
} else {
next(new Error('Unauthorized'));
}
}
}
app.get('/operator', ensureAuthenticated, andRestrictTo('operator'), function(req, res) {
res.end('operator');
});
Jelszó titkosítása
npm install bcryptjs --save
// User modell (models/user.js)
var bcrypt = require('bcryptjs');
module.exports = {
identity: 'user',
connection: 'default',
attributes: {
/* ... */
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
},
beforeCreate: function(values, next) {
bcrypt.hash(values.password, 10, function(err, hash) {
if (err) {
return next(err);
}
values.password = hash;
next();
});
}
};
Kijelentkezés
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
Tedd lehetővé, hogy a Google/Facebook/Github account-oddal lépjél be!
Telepítés
npm install passport-google-oauth --save
Stratégia
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
var GOOGLE_CLIENT_ID = "...";
var GOOGLE_CLIENT_SECRET = "...";
passport.use('google', new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "https://alkfejl-01-horvathgyozo.c9.io/login/return"
},
function(accessToken, refreshToken, profile, done) {
console.log(profile)
return done(null, profile);
}
));
Végpontok beállítása
router.get('/google', passport.authenticate('google', {
scope: 'https://www.googleapis.com/auth/plus.login'
}));
router.get('/return', passport.authenticate('google', {
successRedirect: '/errors/list',
failureRedirect: '/'
}));