login-passport.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. module.exports = function(crowi, app) {
  2. 'use strict';
  3. const debug = require('debug')('growi:routes:login-passport')
  4. , logger = require('@alias/logger')('growi:routes:login-passport')
  5. , passport = require('passport')
  6. , config = crowi.getConfig()
  7. , Config = crowi.model('Config')
  8. , ExternalAccount = crowi.model('ExternalAccount')
  9. , passportService = crowi.passportService
  10. ;
  11. /**
  12. * success handler
  13. * @param {*} req
  14. * @param {*} res
  15. */
  16. const loginSuccess = (req, res, user) => {
  17. // update lastLoginAt
  18. user.updateLastLoginAt(new Date(), (err, userData) => {
  19. if (err) {
  20. logger.error(`updateLastLoginAt dumps error: ${err}`);
  21. debug(`updateLastLoginAt dumps error: ${err}`);
  22. }
  23. });
  24. const jumpTo = req.session.jumpTo;
  25. if (jumpTo) {
  26. req.session.jumpTo = null;
  27. return res.redirect(jumpTo);
  28. }
  29. else {
  30. return res.redirect('/');
  31. }
  32. };
  33. /**
  34. * failure handler
  35. * @param {*} req
  36. * @param {*} res
  37. */
  38. const loginFailure = (req, res, next) => {
  39. req.flash('errorMessage', 'Sign in failure.');
  40. return res.redirect('/login');
  41. };
  42. /**
  43. * return true(valid) or false(invalid)
  44. *
  45. * true ... group filter is not defined or the user has one or more groups
  46. * false ... group filter is defined and the user has any group
  47. *
  48. */
  49. function isValidLdapUserByGroupFilter(user) {
  50. let bool = true;
  51. if (user._groups != null) {
  52. if (user._groups.length == 0) {
  53. bool = false;
  54. }
  55. }
  56. return bool;
  57. }
  58. /**
  59. * middleware that login with LdapStrategy
  60. * @param {*} req
  61. * @param {*} res
  62. * @param {*} next
  63. */
  64. const loginWithLdap = async(req, res, next) => {
  65. if (!passportService.isLdapStrategySetup) {
  66. debug('LdapStrategy has not been set up');
  67. return next();
  68. }
  69. if (!req.form.isValid) {
  70. debug('invalid form');
  71. return res.render('login', {
  72. });
  73. }
  74. const providerId = 'ldap';
  75. const strategyName = 'ldapauth';
  76. const ldapAccountInfo = await promisifiedPassportAuthentication(req, res, next, strategyName);
  77. // check groups for LDAP
  78. if (!isValidLdapUserByGroupFilter(ldapAccountInfo)) {
  79. return loginFailure(req, res, next);
  80. }
  81. /*
  82. * authentication success
  83. */
  84. // it is guaranteed that username that is input from form can be acquired
  85. // because this processes after authentication
  86. const ldapAccountId = passportService.getLdapAccountIdFromReq(req);
  87. const attrMapUsername = passportService.getLdapAttrNameMappedToUsername();
  88. const attrMapName = passportService.getLdapAttrNameMappedToName();
  89. const attrMapMail = passportService.getLdapAttrNameMappedToMail();
  90. const usernameToBeRegistered = ldapAccountInfo[attrMapUsername];
  91. const nameToBeRegistered = ldapAccountInfo[attrMapName];
  92. const mailToBeRegistered = ldapAccountInfo[attrMapMail];
  93. const userInfo = {
  94. 'id': ldapAccountId,
  95. 'username': usernameToBeRegistered,
  96. 'name': nameToBeRegistered,
  97. 'email': mailToBeRegistered,
  98. };
  99. const externalAccount = await getOrCreateUser(req, res, next, userInfo, providerId);
  100. if (!externalAccount) {
  101. return loginFailure(req, res, next);
  102. }
  103. const user = await externalAccount.getPopulatedUser();
  104. // login
  105. await req.logIn(user, err => {
  106. if (err) { return next(err) }
  107. return loginSuccess(req, res, user);
  108. });
  109. };
  110. /**
  111. * middleware that test credentials with LdapStrategy
  112. *
  113. * @param {*} req
  114. * @param {*} res
  115. */
  116. const testLdapCredentials = (req, res) => {
  117. if (!passportService.isLdapStrategySetup) {
  118. debug('LdapStrategy has not been set up');
  119. return res.json({
  120. status: 'warning',
  121. message: 'LdapStrategy has not been set up',
  122. });
  123. }
  124. passport.authenticate('ldapauth', (err, user, info) => {
  125. if (res.headersSent) { // dirty hack -- 2017.09.25
  126. return; // cz: somehow passport.authenticate called twice when ECONNREFUSED error occurred
  127. }
  128. if (err) { // DB Error
  129. logger.error('LDAP Server Error: ', err);
  130. return res.json({
  131. status: 'warning',
  132. message: 'LDAP Server Error occured.',
  133. err
  134. });
  135. }
  136. if (info && info.message) {
  137. return res.json({
  138. status: 'warning',
  139. message: info.message,
  140. ldapConfiguration: req.ldapConfiguration,
  141. ldapAccountInfo: req.ldapAccountInfo,
  142. });
  143. }
  144. if (user) {
  145. // check groups
  146. if (!isValidLdapUserByGroupFilter(user)) {
  147. return res.json({
  148. status: 'warning',
  149. message: 'The user is found, but that has no groups.',
  150. ldapConfiguration: req.ldapConfiguration,
  151. ldapAccountInfo: req.ldapAccountInfo,
  152. });
  153. }
  154. return res.json({
  155. status: 'success',
  156. message: 'Successfully authenticated.',
  157. ldapConfiguration: req.ldapConfiguration,
  158. ldapAccountInfo: req.ldapAccountInfo,
  159. });
  160. }
  161. })(req, res, () => {});
  162. };
  163. /**
  164. * middleware that login with LocalStrategy
  165. * @param {*} req
  166. * @param {*} res
  167. * @param {*} next
  168. */
  169. const loginWithLocal = (req, res, next) => {
  170. if (!req.form.isValid) {
  171. return res.render('login', {
  172. });
  173. }
  174. passport.authenticate('local', (err, user, info) => {
  175. debug('--- authenticate with LocalStrategy ---');
  176. debug('user', user);
  177. debug('info', info);
  178. if (err) { // DB Error
  179. logger.error('Database Server Error: ', err);
  180. req.flash('warningMessage', 'Database Server Error occured.');
  181. return next(); // pass and the flash message is displayed when all of authentications are failed.
  182. }
  183. if (!user) { return next() }
  184. req.logIn(user, (err) => {
  185. if (err) { return next() }
  186. else {
  187. return loginSuccess(req, res, user);
  188. }
  189. });
  190. })(req, res, next);
  191. };
  192. const loginWithGoogle = function(req, res, next) {
  193. if (!passportService.isGoogleStrategySetup) {
  194. debug('GoogleStrategy has not been set up');
  195. req.flash('warningMessage', 'GoogleStrategy has not been set up');
  196. return next();
  197. }
  198. passport.authenticate('google', {
  199. scope: ['profile', 'email'],
  200. })(req, res);
  201. };
  202. const loginPassportGoogleCallback = async(req, res, next) => {
  203. const providerId = 'google';
  204. const strategyName = 'google';
  205. const response = await promisifiedPassportAuthentication(req, res, next, strategyName);
  206. const userInfo = {
  207. 'id': response.id,
  208. 'username': response.displayName,
  209. 'name': `${response.name.givenName} ${response.name.familyName}`
  210. };
  211. const externalAccount = await getOrCreateUser(req, res, next, userInfo, providerId);
  212. if (!externalAccount) {
  213. return loginFailure(req, res, next);
  214. }
  215. const user = await externalAccount.getPopulatedUser();
  216. // login
  217. req.logIn(user, err => {
  218. if (err) { return next(err) }
  219. return loginSuccess(req, res, user);
  220. });
  221. };
  222. const loginWithGitHub = function(req, res, next) {
  223. if (!passportService.isGitHubStrategySetup) {
  224. debug('GitHubStrategy has not been set up');
  225. req.flash('warningMessage', 'GitHubStrategy has not been set up');
  226. return next();
  227. }
  228. passport.authenticate('github')(req, res);
  229. };
  230. const loginPassportGitHubCallback = async(req, res, next) => {
  231. const providerId = 'github';
  232. const strategyName = 'github';
  233. const response = await promisifiedPassportAuthentication(req, res, next, strategyName);
  234. const userInfo = {
  235. 'id': response.id,
  236. 'username': response.username,
  237. 'name': response.displayName
  238. };
  239. const externalAccount = await getOrCreateUser(req, res, next, userInfo, providerId);
  240. if (!externalAccount) {
  241. return loginFailure(req, res, next);
  242. }
  243. const user = await externalAccount.getPopulatedUser();
  244. // login
  245. req.logIn(user, err => {
  246. if (err) { return next(err) }
  247. return loginSuccess(req, res, user);
  248. });
  249. };
  250. const promisifiedPassportAuthentication = (req, res, next, strategyName) => {
  251. return new Promise((resolve, reject) => {
  252. passport.authenticate(strategyName, (err, response, info) => {
  253. if (res.headersSent) { // dirty hack -- 2017.09.25
  254. return; // cz: somehow passport.authenticate called twice when ECONNREFUSED error occurred
  255. }
  256. if (err) {
  257. logger.error(`'${strategyName}' passport authentication error: `, err);
  258. req.flash('warningMessage', `Error occured in '${strategyName}' passport authentication`);
  259. return next(); // pass and the flash message is displayed when all of authentications are failed.
  260. }
  261. // authentication failure
  262. if (!response) {
  263. return next();
  264. }
  265. resolve(response);
  266. })(req, res, next);
  267. });
  268. };
  269. const getOrCreateUser = async(req, res, next, userInfo, providerId) => {
  270. try {
  271. // find or register(create) user
  272. const externalAccount = await ExternalAccount.findOrRegister(
  273. providerId,
  274. userInfo.id,
  275. userInfo.username,
  276. userInfo.name,
  277. userInfo.email,
  278. );
  279. return externalAccount;
  280. }
  281. catch (err) {
  282. if (err.name === 'DuplicatedUsernameException') {
  283. // get option
  284. const isSameUsernameTreatedAsIdenticalUser = Config.isSameUsernameTreatedAsIdenticalUser(config, providerId);
  285. if (isSameUsernameTreatedAsIdenticalUser) {
  286. // associate to existing user
  287. debug(`ExternalAccount '${userInfo.username}' will be created and bound to the exisiting User account`);
  288. return ExternalAccount.associate(providerId, userInfo.id, err.user);
  289. }
  290. else {
  291. req.flash('provider-DuplicatedUsernameException', providerId);
  292. return;
  293. }
  294. }
  295. }
  296. };
  297. return {
  298. loginFailure,
  299. loginWithLdap,
  300. testLdapCredentials,
  301. loginWithLocal,
  302. loginWithGoogle,
  303. loginWithGitHub,
  304. loginPassportGoogleCallback,
  305. loginPassportGitHubCallback,
  306. };
  307. };