passport.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. const debug = require('debug')('growi:service:PassportService');
  2. const urljoin = require('url-join');
  3. const passport = require('passport');
  4. const LocalStrategy = require('passport-local').Strategy;
  5. const LdapStrategy = require('passport-ldapauth');
  6. const GoogleStrategy = require('passport-google-oauth20').Strategy;
  7. const GitHubStrategy = require('passport-github').Strategy;
  8. const TwitterStrategy = require('passport-twitter').Strategy;
  9. const OidcStrategy = require('openid-client').Strategy;
  10. const SamlStrategy = require('passport-saml').Strategy;
  11. const OIDCIssuer = require('openid-client').Issuer;
  12. const BasicStrategy = require('passport-http').BasicStrategy;
  13. /**
  14. * the service class of Passport
  15. */
  16. class PassportService {
  17. // see '/lib/form/login.js'
  18. static get USERNAME_FIELD() { return 'loginForm[username]' }
  19. static get PASSWORD_FIELD() { return 'loginForm[password]' }
  20. constructor(crowi) {
  21. this.crowi = crowi;
  22. /**
  23. * the flag whether LocalStrategy is set up successfully
  24. */
  25. this.isLocalStrategySetup = false;
  26. /**
  27. * the flag whether LdapStrategy is set up successfully
  28. */
  29. this.isLdapStrategySetup = false;
  30. /**
  31. * the flag whether GoogleStrategy is set up successfully
  32. */
  33. this.isGoogleStrategySetup = false;
  34. /**
  35. * the flag whether GitHubStrategy is set up successfully
  36. */
  37. this.isGitHubStrategySetup = false;
  38. /**
  39. * the flag whether TwitterStrategy is set up successfully
  40. */
  41. this.isTwitterStrategySetup = false;
  42. /**
  43. * the flag whether OidcStrategy is set up successfully
  44. */
  45. this.isOidcStrategySetup = false;
  46. /**
  47. * the flag whether SamlStrategy is set up successfully
  48. */
  49. this.isSamlStrategySetup = false;
  50. /**
  51. * the flag whether BasicStrategy is set up successfully
  52. */
  53. this.isBasicStrategySetup = false;
  54. /**
  55. * the flag whether serializer/deserializer are set up successfully
  56. */
  57. this.isSerializerSetup = false;
  58. /**
  59. * the keys of mandatory configs for SAML
  60. */
  61. this.mandatoryConfigKeysForSaml = [
  62. 'security:passport-saml:isEnabled',
  63. 'security:passport-saml:entryPoint',
  64. 'security:passport-saml:issuer',
  65. 'security:passport-saml:cert',
  66. 'security:passport-saml:attrMapId',
  67. 'security:passport-saml:attrMapUsername',
  68. 'security:passport-saml:attrMapMail',
  69. ];
  70. }
  71. /**
  72. * reset LocalStrategy
  73. *
  74. * @memberof PassportService
  75. */
  76. resetLocalStrategy() {
  77. debug('LocalStrategy: reset');
  78. passport.unuse('local');
  79. this.isLocalStrategySetup = false;
  80. }
  81. /**
  82. * setup LocalStrategy
  83. *
  84. * @memberof PassportService
  85. */
  86. setupLocalStrategy() {
  87. // check whether the strategy has already been set up
  88. if (this.isLocalStrategySetup) {
  89. throw new Error('LocalStrategy has already been set up');
  90. }
  91. const { configManager } = this.crowi;
  92. const isEnabled = configManager.getConfig('crowi', 'security:passport-local:isEnabled');
  93. // when disabled
  94. if (!isEnabled) {
  95. return;
  96. }
  97. debug('LocalStrategy: setting up..');
  98. const User = this.crowi.model('User');
  99. passport.use(new LocalStrategy(
  100. {
  101. usernameField: PassportService.USERNAME_FIELD,
  102. passwordField: PassportService.PASSWORD_FIELD,
  103. },
  104. (username, password, done) => {
  105. // find user
  106. User.findUserByUsernameOrEmail(username, password, (err, user) => {
  107. if (err) { return done(err) }
  108. // check existence and password
  109. if (!user || !user.isPasswordValid(password)) {
  110. return done(null, false, { message: 'Incorrect credentials.' });
  111. }
  112. return done(null, user);
  113. });
  114. },
  115. ));
  116. this.isLocalStrategySetup = true;
  117. debug('LocalStrategy: setup is done');
  118. }
  119. /**
  120. * reset LdapStrategy
  121. *
  122. * @memberof PassportService
  123. */
  124. resetLdapStrategy() {
  125. debug('LdapStrategy: reset');
  126. passport.unuse('ldapauth');
  127. this.isLdapStrategySetup = false;
  128. }
  129. /**
  130. * Asynchronous configuration retrieval
  131. *
  132. * @memberof PassportService
  133. */
  134. setupLdapStrategy() {
  135. // check whether the strategy has already been set up
  136. if (this.isLdapStrategySetup) {
  137. throw new Error('LdapStrategy has already been set up');
  138. }
  139. const config = this.crowi.config;
  140. const { configManager } = this.crowi;
  141. const isLdapEnabled = configManager.getConfig('crowi', 'security:passport-ldap:isEnabled');
  142. // when disabled
  143. if (!isLdapEnabled) {
  144. return;
  145. }
  146. debug('LdapStrategy: setting up..');
  147. passport.use(new LdapStrategy(this.getLdapConfigurationFunc(config, { passReqToCallback: true }),
  148. (req, ldapAccountInfo, done) => {
  149. debug('LDAP authentication has succeeded', ldapAccountInfo);
  150. // store ldapAccountInfo to req
  151. req.ldapAccountInfo = ldapAccountInfo;
  152. done(null, ldapAccountInfo);
  153. }));
  154. this.isLdapStrategySetup = true;
  155. debug('LdapStrategy: setup is done');
  156. }
  157. /**
  158. * return attribute name for mapping to username of Crowi DB
  159. *
  160. * @returns
  161. * @memberof PassportService
  162. */
  163. getLdapAttrNameMappedToUsername() {
  164. return this.crowi.configManager.getConfig('crowi', 'security:passport-ldap:attrMapUsername') || 'uid';
  165. }
  166. /**
  167. * return attribute name for mapping to name of Crowi DB
  168. *
  169. * @returns
  170. * @memberof PassportService
  171. */
  172. getLdapAttrNameMappedToName() {
  173. return this.crowi.configManager.getConfig('crowi', 'security:passport-ldap:attrMapName') || '';
  174. }
  175. /**
  176. * return attribute name for mapping to name of Crowi DB
  177. *
  178. * @returns
  179. * @memberof PassportService
  180. */
  181. getLdapAttrNameMappedToMail() {
  182. return this.crowi.configManager.getConfig('crowi', 'security:passport-ldap:attrMapMail') || 'mail';
  183. }
  184. /**
  185. * CAUTION: this method is capable to use only when `req.body.loginForm` is not null
  186. *
  187. * @param {any} req
  188. * @returns
  189. * @memberof PassportService
  190. */
  191. getLdapAccountIdFromReq(req) {
  192. return req.body.loginForm.username;
  193. }
  194. /**
  195. * Asynchronous configuration retrieval
  196. * @see https://github.com/vesse/passport-ldapauth#asynchronous-configuration-retrieval
  197. *
  198. * @param {object} config
  199. * @param {object} opts
  200. * @returns
  201. * @memberof PassportService
  202. */
  203. getLdapConfigurationFunc(config, opts) {
  204. /* eslint-disable no-multi-spaces */
  205. const { configManager } = this.crowi;
  206. // get configurations
  207. const isUserBind = configManager.getConfig('crowi', 'security:passport-ldap:isUserBind');
  208. const serverUrl = configManager.getConfig('crowi', 'security:passport-ldap:serverUrl');
  209. const bindDN = configManager.getConfig('crowi', 'security:passport-ldap:bindDN');
  210. const bindCredentials = configManager.getConfig('crowi', 'security:passport-ldap:bindDNPassword');
  211. const searchFilter = configManager.getConfig('crowi', 'security:passport-ldap:searchFilter') || '(uid={{username}})';
  212. const groupSearchBase = configManager.getConfig('crowi', 'security:passport-ldap:groupSearchBase');
  213. const groupSearchFilter = configManager.getConfig('crowi', 'security:passport-ldap:groupSearchFilter');
  214. const groupDnProperty = configManager.getConfig('crowi', 'security:passport-ldap:groupDnProperty') || 'uid';
  215. /* eslint-enable no-multi-spaces */
  216. // parse serverUrl
  217. // see: https://regex101.com/r/0tuYBB/1
  218. const match = serverUrl.match(/(ldaps?:\/\/[^/]+)\/(.*)?/);
  219. if (match == null || match.length < 1) {
  220. debug('LdapStrategy: serverUrl is invalid');
  221. return (req, callback) => { callback({ message: 'serverUrl is invalid' }) };
  222. }
  223. const url = match[1];
  224. const searchBase = match[2] || '';
  225. debug(`LdapStrategy: url=${url}`);
  226. debug(`LdapStrategy: searchBase=${searchBase}`);
  227. debug(`LdapStrategy: isUserBind=${isUserBind}`);
  228. if (!isUserBind) {
  229. debug(`LdapStrategy: bindDN=${bindDN}`);
  230. debug(`LdapStrategy: bindCredentials=${bindCredentials}`);
  231. }
  232. debug(`LdapStrategy: searchFilter=${searchFilter}`);
  233. debug(`LdapStrategy: groupSearchBase=${groupSearchBase}`);
  234. debug(`LdapStrategy: groupSearchFilter=${groupSearchFilter}`);
  235. debug(`LdapStrategy: groupDnProperty=${groupDnProperty}`);
  236. return (req, callback) => {
  237. // get credentials from form data
  238. const loginForm = req.body.loginForm;
  239. if (!req.form.isValid) {
  240. return callback({ message: 'Incorrect credentials.' });
  241. }
  242. // user bind
  243. const fixedBindDN = (isUserBind)
  244. ? bindDN.replace(/{{username}}/, loginForm.username)
  245. : bindDN;
  246. const fixedBindCredentials = (isUserBind) ? loginForm.password : bindCredentials;
  247. let serverOpt = {
  248. url,
  249. bindDN: fixedBindDN,
  250. bindCredentials: fixedBindCredentials,
  251. searchBase,
  252. searchFilter,
  253. attrMapUsername: this.getLdapAttrNameMappedToUsername(),
  254. attrMapName: this.getLdapAttrNameMappedToName(),
  255. };
  256. if (groupSearchBase && groupSearchFilter) {
  257. serverOpt = Object.assign(serverOpt, { groupSearchBase, groupSearchFilter, groupDnProperty });
  258. }
  259. process.nextTick(() => {
  260. const mergedOpts = Object.assign({
  261. usernameField: PassportService.USERNAME_FIELD,
  262. passwordField: PassportService.PASSWORD_FIELD,
  263. server: serverOpt,
  264. }, opts);
  265. debug('ldap configuration: ', mergedOpts);
  266. // store configuration to req
  267. req.ldapConfiguration = mergedOpts;
  268. callback(null, mergedOpts);
  269. });
  270. };
  271. }
  272. /**
  273. * Asynchronous configuration retrieval
  274. *
  275. * @memberof PassportService
  276. */
  277. setupGoogleStrategy() {
  278. // check whether the strategy has already been set up
  279. if (this.isGoogleStrategySetup) {
  280. throw new Error('GoogleStrategy has already been set up');
  281. }
  282. const { configManager } = this.crowi;
  283. const isGoogleEnabled = configManager.getConfig('crowi', 'security:passport-google:isEnabled');
  284. // when disabled
  285. if (!isGoogleEnabled) {
  286. return;
  287. }
  288. debug('GoogleStrategy: setting up..');
  289. passport.use(
  290. new GoogleStrategy(
  291. {
  292. clientID: configManager.getConfig('crowi', 'security:passport-google:clientId'),
  293. clientSecret: configManager.getConfig('crowi', 'security:passport-google:clientSecret'),
  294. callbackURL: (this.crowi.appService.getSiteUrl() != null)
  295. ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/google/callback') // auto-generated with v3.2.4 and above
  296. : configManager.getConfig('crowi', 'security:passport-google:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
  297. skipUserProfile: false,
  298. },
  299. (accessToken, refreshToken, profile, done) => {
  300. if (profile) {
  301. return done(null, profile);
  302. }
  303. return done(null, false);
  304. },
  305. ),
  306. );
  307. this.isGoogleStrategySetup = true;
  308. debug('GoogleStrategy: setup is done');
  309. }
  310. /**
  311. * reset GoogleStrategy
  312. *
  313. * @memberof PassportService
  314. */
  315. resetGoogleStrategy() {
  316. debug('GoogleStrategy: reset');
  317. passport.unuse('google');
  318. this.isGoogleStrategySetup = false;
  319. }
  320. setupGitHubStrategy() {
  321. // check whether the strategy has already been set up
  322. if (this.isGitHubStrategySetup) {
  323. throw new Error('GitHubStrategy has already been set up');
  324. }
  325. const { configManager } = this.crowi;
  326. const isGitHubEnabled = configManager.getConfig('crowi', 'security:passport-github:isEnabled');
  327. // when disabled
  328. if (!isGitHubEnabled) {
  329. return;
  330. }
  331. debug('GitHubStrategy: setting up..');
  332. passport.use(
  333. new GitHubStrategy(
  334. {
  335. clientID: configManager.getConfig('crowi', 'security:passport-github:clientId'),
  336. clientSecret: configManager.getConfig('crowi', 'security:passport-github:clientSecret'),
  337. callbackURL: (this.crowi.appService.getSiteUrl() != null)
  338. ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/github/callback') // auto-generated with v3.2.4 and above
  339. : configManager.getConfig('crowi', 'security:passport-github:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
  340. skipUserProfile: false,
  341. },
  342. (accessToken, refreshToken, profile, done) => {
  343. if (profile) {
  344. return done(null, profile);
  345. }
  346. return done(null, false);
  347. },
  348. ),
  349. );
  350. this.isGitHubStrategySetup = true;
  351. debug('GitHubStrategy: setup is done');
  352. }
  353. /**
  354. * reset GitHubStrategy
  355. *
  356. * @memberof PassportService
  357. */
  358. resetGitHubStrategy() {
  359. debug('GitHubStrategy: reset');
  360. passport.unuse('github');
  361. this.isGitHubStrategySetup = false;
  362. }
  363. setupTwitterStrategy() {
  364. // check whether the strategy has already been set up
  365. if (this.isTwitterStrategySetup) {
  366. throw new Error('TwitterStrategy has already been set up');
  367. }
  368. const { configManager } = this.crowi;
  369. const isTwitterEnabled = configManager.getConfig('crowi', 'security:passport-twitter:isEnabled');
  370. // when disabled
  371. if (!isTwitterEnabled) {
  372. return;
  373. }
  374. debug('TwitterStrategy: setting up..');
  375. passport.use(
  376. new TwitterStrategy(
  377. {
  378. consumerKey: configManager.getConfig('crowi', 'security:passport-twitter:consumerKey'),
  379. consumerSecret: configManager.getConfig('crowi', 'security:passport-twitter:consumerSecret'),
  380. callbackURL: (this.crowi.appService.getSiteUrl() != null)
  381. ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/twitter/callback') // auto-generated with v3.2.4 and above
  382. : configManager.getConfig('crowi', 'security:passport-twitter:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
  383. skipUserProfile: false,
  384. },
  385. (accessToken, refreshToken, profile, done) => {
  386. if (profile) {
  387. return done(null, profile);
  388. }
  389. return done(null, false);
  390. },
  391. ),
  392. );
  393. this.isTwitterStrategySetup = true;
  394. debug('TwitterStrategy: setup is done');
  395. }
  396. /**
  397. * reset TwitterStrategy
  398. *
  399. * @memberof PassportService
  400. */
  401. resetTwitterStrategy() {
  402. debug('TwitterStrategy: reset');
  403. passport.unuse('twitter');
  404. this.isTwitterStrategySetup = false;
  405. }
  406. async setupOidcStrategy() {
  407. // check whether the strategy has already been set up
  408. if (this.isOidcStrategySetup) {
  409. throw new Error('OidcStrategy has already been set up');
  410. }
  411. const { configManager } = this.crowi;
  412. const isOidcEnabled = configManager.getConfig('crowi', 'security:passport-oidc:isEnabled');
  413. // when disabled
  414. if (!isOidcEnabled) {
  415. return;
  416. }
  417. debug('OidcStrategy: setting up..');
  418. // setup client
  419. // extend oidc request timeouts
  420. OIDCIssuer.defaultHttpOptions = { timeout: 5000 };
  421. const issuerHost = configManager.getConfig('crowi', 'security:passport-oidc:issuerHost');
  422. const clientId = configManager.getConfig('crowi', 'security:passport-oidc:clientId');
  423. const clientSecret = configManager.getConfig('crowi', 'security:passport-oidc:clientSecret');
  424. const redirectUri = (configManager.getConfig('crowi', 'app:siteUrl') != null)
  425. ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/oidc/callback')
  426. : configManager.getConfig('crowi', 'security:passport-oidc:callbackUrl'); // DEPRECATED: backward compatible with v3.2.3 and below
  427. const oidcIssuer = await OIDCIssuer.discover(issuerHost);
  428. debug('Discovered issuer %s %O', oidcIssuer.issuer, oidcIssuer.metadata);
  429. const client = new oidcIssuer.Client({
  430. client_id: clientId,
  431. client_secret: clientSecret,
  432. redirect_uris: [redirectUri],
  433. response_types: ['code'],
  434. });
  435. passport.use('oidc', new OidcStrategy({
  436. client,
  437. params: { scope: 'openid email profile' },
  438. },
  439. ((tokenset, userinfo, done) => {
  440. if (userinfo) {
  441. return done(null, userinfo);
  442. }
  443. return done(null, false);
  444. })));
  445. this.isOidcStrategySetup = true;
  446. debug('OidcStrategy: setup is done');
  447. }
  448. /**
  449. * reset OidcStrategy
  450. *
  451. * @memberof PassportService
  452. */
  453. resetOidcStrategy() {
  454. debug('OidcStrategy: reset');
  455. passport.unuse('oidc');
  456. this.isOidcStrategySetup = false;
  457. }
  458. setupSamlStrategy() {
  459. // check whether the strategy has already been set up
  460. if (this.isSamlStrategySetup) {
  461. throw new Error('SamlStrategy has already been set up');
  462. }
  463. const { configManager } = this.crowi;
  464. const isSamlEnabled = configManager.getConfig('crowi', 'security:passport-saml:isEnabled');
  465. // when disabled
  466. if (!isSamlEnabled) {
  467. return;
  468. }
  469. debug('SamlStrategy: setting up..');
  470. passport.use(
  471. new SamlStrategy(
  472. {
  473. entryPoint: configManager.getConfig('crowi', 'security:passport-saml:entryPoint'),
  474. callbackUrl: (this.crowi.appService.getSiteUrl() != null)
  475. ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/saml/callback') // auto-generated with v3.2.4 and above
  476. : configManager.getConfig('crowi', 'security:passport-saml:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
  477. issuer: configManager.getConfig('crowi', 'security:passport-saml:issuer'),
  478. cert: configManager.getConfig('crowi', 'security:passport-saml:cert'),
  479. },
  480. (profile, done) => {
  481. if (profile) {
  482. return done(null, profile);
  483. }
  484. return done(null, false);
  485. },
  486. ),
  487. );
  488. this.isSamlStrategySetup = true;
  489. debug('SamlStrategy: setup is done');
  490. }
  491. /**
  492. * reset SamlStrategy
  493. *
  494. * @memberof PassportService
  495. */
  496. resetSamlStrategy() {
  497. debug('SamlStrategy: reset');
  498. passport.unuse('saml');
  499. this.isSamlStrategySetup = false;
  500. }
  501. /**
  502. * return the keys of the configs mandatory for SAML whose value are empty.
  503. */
  504. getSamlMissingMandatoryConfigKeys() {
  505. const missingRequireds = [];
  506. for (const key of this.mandatoryConfigKeysForSaml) {
  507. if (this.crowi.configManager.getConfig('crowi', key) === null) {
  508. missingRequireds.push(key);
  509. }
  510. }
  511. return missingRequireds;
  512. }
  513. /**
  514. * reset BasicStrategy
  515. *
  516. * @memberof PassportService
  517. */
  518. resetBasicStrategy() {
  519. debug('BasicStrategy: reset');
  520. passport.unuse('basic');
  521. this.isBasicStrategySetup = false;
  522. }
  523. /**
  524. * setup BasicStrategy
  525. *
  526. * @memberof PassportService
  527. */
  528. setupBasicStrategy() {
  529. // check whether the strategy has already been set up
  530. if (this.isBasicStrategySetup) {
  531. throw new Error('BasicStrategy has already been set up');
  532. }
  533. const configManager = this.crowi.configManager;
  534. const isBasicEnabled = configManager.getConfig('crowi', 'security:passport-basic:isEnabled');
  535. // when disabled
  536. if (!isBasicEnabled) {
  537. return;
  538. }
  539. debug('BasicStrategy: setting up..');
  540. passport.use(new BasicStrategy(
  541. (userId, password, done) => {
  542. if (userId != null) {
  543. return done(null, userId);
  544. }
  545. return done(null, false, { message: 'Incorrect credentials.' });
  546. },
  547. ));
  548. this.isBasicStrategySetup = true;
  549. debug('BasicStrategy: setup is done');
  550. }
  551. /**
  552. * setup serializer and deserializer
  553. *
  554. * @memberof PassportService
  555. */
  556. setupSerializer() {
  557. // check whether the serializer/deserializer have already been set up
  558. if (this.isSerializerSetup) {
  559. throw new Error('serializer/deserializer have already been set up');
  560. }
  561. debug('setting up serializer and deserializer');
  562. const User = this.crowi.model('User');
  563. passport.serializeUser((user, done) => {
  564. done(null, user.id);
  565. });
  566. passport.deserializeUser(async(id, done) => {
  567. try {
  568. const user = await User.findById(id).populate(User.IMAGE_POPULATION);
  569. if (user == null) {
  570. throw new Error('user not found');
  571. }
  572. done(null, user);
  573. }
  574. catch (err) {
  575. done(err);
  576. }
  577. });
  578. this.isSerializerSetup = true;
  579. }
  580. isSameUsernameTreatedAsIdenticalUser(providerType) {
  581. const key = `security:passport-${providerType}:isSameUsernameTreatedAsIdenticalUser`;
  582. return this.crowi.configManager.getConfig('crowi', key);
  583. }
  584. isSameEmailTreatedAsIdenticalUser(providerType) {
  585. const key = `security:passport-${providerType}:isSameEmailTreatedAsIdenticalUser`;
  586. return this.crowi.configManager.getConfig('crowi', key);
  587. }
  588. }
  589. module.exports = PassportService;