]> git.immae.eu Git - perso/Immae/Projets/Nodejs/Surfer.git/blob - src/auth.js
Bump manifest format
[perso/Immae/Projets/Nodejs/Surfer.git] / src / auth.js
1 'use strict';
2
3 var passport = require('passport'),
4 path = require('path'),
5 safe = require('safetydance'),
6 fs = require('fs'),
7 bcrypt = require('bcryptjs'),
8 uuid = require('uuid/v4'),
9 BearerStrategy = require('passport-http-bearer').Strategy,
10 ldapjs = require('ldapjs'),
11 HttpError = require('connect-lastmile').HttpError,
12 HttpSuccess = require('connect-lastmile').HttpSuccess,
13 webdavErrors = require('webdav-server').v2.Errors;
14
15 const LDAP_URL = process.env.CLOUDRON_LDAP_URL;
16 const LDAP_USERS_BASE_DN = process.env.CLOUDRON_LDAP_USERS_BASE_DN;
17 const LOCAL_AUTH_FILE = path.resolve(process.env.LOCAL_AUTH_FILE || './.users.json');
18 const TOKENSTORE_FILE = path.resolve(process.env.TOKENSTORE_FILE || './.tokens.json');
19 const AUTH_METHOD = (LDAP_URL && LDAP_USERS_BASE_DN) ? 'ldap' : 'local';
20
21 if (AUTH_METHOD === 'ldap') {
22 console.log('Use ldap auth');
23 } else {
24 console.log(`Use local auth file ${LOCAL_AUTH_FILE}`);
25 }
26
27 var tokenStore = {
28 data: {},
29 save: function () {
30 try {
31 fs.writeFileSync(TOKENSTORE_FILE, JSON.stringify(tokenStore.data), 'utf-8');
32 } catch (e) {
33 console.error(`Unable to save tokenstore file at ${TOKENSTORE_FILE}`, e);
34 }
35 },
36 get: function (token, callback) {
37 callback(tokenStore.data[token] ? null : 'not found', tokenStore.data[token]);
38 },
39 set: function (token, data, callback) {
40 tokenStore.data[token] = data;
41 tokenStore.save();
42 callback(null);
43 },
44 del: function (token, callback) {
45 delete tokenStore.data[token];
46 tokenStore.save();
47 callback(null);
48 }
49 };
50
51 // load token store data if any
52 try {
53 console.log(`Using tokenstore file: ${TOKENSTORE_FILE}`);
54 tokenStore.data = JSON.parse(fs.readFileSync(TOKENSTORE_FILE, 'utf-8'));
55 } catch (e) {
56 // start with empty token store
57 }
58
59 function issueAccessToken() {
60 return function (req, res, next) {
61 var accessToken = uuid();
62
63 tokenStore.set(accessToken, req.user, function (error) {
64 if (error) return next(new HttpError(500, error));
65 next(new HttpSuccess(201, { accessToken: accessToken, user: req.user }));
66 });
67 };
68 }
69
70 passport.serializeUser(function (user, done) {
71 console.log('serializeUser', user);
72 done(null, user.uid);
73 });
74
75 passport.deserializeUser(function (id, done) {
76 console.log('deserializeUser', id);
77 done(null, { uid: id });
78 });
79
80 function verifyUser(username, password, callback) {
81 if (AUTH_METHOD === 'ldap') {
82 var ldapClient = ldapjs.createClient({ url: process.env.CLOUDRON_LDAP_URL });
83 ldapClient.on('error', function (error) {
84 console.error('LDAP error', error);
85 });
86
87 ldapClient.bind(process.env.CLOUDRON_LDAP_BIND_DN, process.env.CLOUDRON_LDAP_BIND_PASSWORD, function (error) {
88 if (error) return callback(error);
89
90 var filter = `(|(uid=${username})(mail=${username})(username=${username})(sAMAccountName=${username}))`;
91 ldapClient.search(process.env.CLOUDRON_LDAP_USERS_BASE_DN, { filter: filter }, function (error, result) {
92 if (error) return callback(error);
93
94 var items = [];
95
96 result.on('searchEntry', function(entry) { items.push(entry.object); });
97 result.on('error', callback);
98 result.on('end', function (result) {
99 if (result.status !== 0 || items.length === 0) return callback('Invalid credentials');
100
101 // pick the first found
102 var user = items[0];
103
104 ldapClient.bind(user.dn, password, function (error) {
105 if (error) return callback('Invalid credentials');
106
107 callback(null, { username: username });
108 });
109 });
110 });
111 });
112 } else {
113 var users = safe.JSON.parse(safe.fs.readFileSync(LOCAL_AUTH_FILE));
114 if (!users || !users[username]) return callback('Invalid credentials');
115
116 bcrypt.compare(password, users[username].passwordHash, function (error, valid) {
117 if (error || !valid) return callback('Invalid credentials');
118
119 callback(null, { username: username });
120 });
121 }
122 }
123
124 exports.login = [
125 function (req, res, next) {
126 verifyUser(req.body.username, req.body.password, function (error, user) {
127 if (error) return next(new HttpError(401, 'Invalid credentials'));
128
129 req.user = user;
130
131 next();
132 });
133 },
134 issueAccessToken()
135 ];
136
137 exports.verify = passport.authenticate('bearer', { session: false });
138
139 passport.use(new BearerStrategy(function (token, done) {
140 tokenStore.get(token, function (error, result) {
141 if (error) {
142 console.error(error);
143 return done(null, false);
144 }
145
146 done(null, result, { accessToken: token });
147 });
148 }));
149
150 exports.logout = function (req, res, next) {
151 tokenStore.del(req.authInfo.accessToken, function (error) {
152 if (error) console.error(error);
153
154 next(new HttpSuccess(200, {}));
155 });
156 };
157
158 exports.getProfile = function (req, res, next) {
159 next(new HttpSuccess(200, { username: req.user.username }));
160 };
161
162 // webdav usermanager
163 exports.WebdavUserManager = WebdavUserManager;
164
165 // This implements the required interface only for the Basic Authentication for webdav-server
166 function WebdavUserManager() {};
167
168 WebdavUserManager.prototype.getDefaultUser = function (callback) {
169 // this is only a dummy user, since we always require authentication
170 var user = {
171 username: 'DefaultUser',
172 password: null,
173 isAdministrator: false,
174 isDefaultUser: true,
175 uid: 'DefaultUser'
176 };
177
178 callback(user);
179 };
180
181 WebdavUserManager.prototype.getUserByNamePassword = function (username, password, callback) {
182 verifyUser(username, password, function (error, user) {
183 if (error) return callback(webdavErrors.UserNotFound);
184
185 callback(null, {
186 username: user.username,
187 isAdministrator: true,
188 isDefaultUser: false,
189 uid: user.username
190 });
191 });
192 };