diff --git a/apps/app/.eslintrc.js b/apps/app/.eslintrc.js index 0bfea0b0fe1..9a677318c09 100644 --- a/apps/app/.eslintrc.js +++ b/apps/app/.eslintrc.js @@ -64,6 +64,11 @@ module.exports = { 'src/server/routes/*.js', 'src/server/routes/*.ts', 'src/server/routes/attachment/**', + 'src/server/routes/apiv3/interfaces/**', + 'src/server/routes/apiv3/pages/**', + 'src/server/routes/apiv3/user/**', + 'src/server/routes/apiv3/personal-setting/**', + 'src/server/routes/apiv3/security-settings/**', ], settings: { // resolve path aliases by eslint-import-resolver-typescript diff --git a/apps/app/src/server/routes/apiv3/interfaces/apiv3-response.ts b/apps/app/src/server/routes/apiv3/interfaces/apiv3-response.ts index 90328c78369..b640fda0794 100644 --- a/apps/app/src/server/routes/apiv3/interfaces/apiv3-response.ts +++ b/apps/app/src/server/routes/apiv3/interfaces/apiv3-response.ts @@ -1,6 +1,6 @@ import type { Response } from 'express'; export interface ApiV3Response extends Response { - apiv3(obj?: any, status?: number): any - apiv3Err(_err: any, status?: number, info?: any): any + apiv3(obj?: any, status?: number): any; + apiv3Err(_err: any, status?: number, info?: any): any; } diff --git a/apps/app/src/server/routes/apiv3/pages/index.js b/apps/app/src/server/routes/apiv3/pages/index.js index 85e9282999d..c3c339de382 100644 --- a/apps/app/src/server/routes/apiv3/pages/index.js +++ b/apps/app/src/server/routes/apiv3/pages/index.js @@ -1,14 +1,20 @@ - import { PageGrant } from '@growi/core'; import { SCOPE } from '@growi/core/dist/interfaces'; import { ErrorV3 } from '@growi/core/dist/models'; import { serializeUserSecurely } from '@growi/core/dist/models/serializers'; -import { isCreatablePage, isTrashPage, isUserPage } from '@growi/core/dist/utils/page-path-utils'; -import { normalizePath, addHeadingSlash } from '@growi/core/dist/utils/path-utils'; +import { + isCreatablePage, + isTrashPage, + isUserPage, +} from '@growi/core/dist/utils/page-path-utils'; +import { + addHeadingSlash, + normalizePath, +} from '@growi/core/dist/utils/path-utils'; import express from 'express'; import { body, query } from 'express-validator'; -import { SupportedTargetModel, SupportedAction } from '~/interfaces/activity'; +import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity'; import { subscribeRuleNames } from '~/interfaces/in-app-notification'; import { accessTokenParser } from '~/server/middlewares/access-token-parser'; import { GlobalNotificationSettingEvent } from '~/server/models/GlobalNotificationSetting'; @@ -23,7 +29,6 @@ import { excludeReadOnlyUser } from '../../../middlewares/exclude-read-only-user import { serializePageSecurely } from '../../../models/serializers/page-serializer'; import { isV5ConversionError } from '../../../models/vo/v5-conversion-error'; - const logger = loggerFactory('growi:routes:apiv3:pages'); // eslint-disable-line no-unused-vars const router = express.Router(); @@ -32,8 +37,13 @@ const LIMIT_FOR_MULTIPLE_PAGE_OP = 20; /** @param {import('~/server/crowi').default} crowi Crowi instance */ module.exports = (crowi) => { - const loginRequired = require('../../../middlewares/login-required')(crowi, true); - const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi); + const loginRequired = require('../../../middlewares/login-required')( + crowi, + true, + ); + const loginRequiredStrictly = require('../../../middlewares/login-required')( + crowi, + ); const adminRequired = require('../../../middlewares/admin-required')(crowi); const Page = crowi.model('Page'); @@ -49,14 +59,28 @@ module.exports = (crowi) => { recent: [ query('limit').optional().isInt().withMessage('limit must be integer'), query('offset').optional().isInt().withMessage('offset must be integer'), - query('includeWipPage').optional().isBoolean().withMessage('includeWipPage must be boolean'), + query('includeWipPage') + .optional() + .isBoolean() + .withMessage('includeWipPage must be boolean'), ], renamePage: [ body('pageId').isMongoId().withMessage('pageId is required'), - body('newPagePath').isLength({ min: 1 }).withMessage('newPagePath is required'), - body('isRecursively').if(value => value != null).isBoolean().withMessage('isRecursively must be boolean'), - body('isRenameRedirect').if(value => value != null).isBoolean().withMessage('isRenameRedirect must be boolean'), - body('updateMetadata').if(value => value != null).isBoolean().withMessage('updateMetadata must be boolean'), + body('newPagePath') + .isLength({ min: 1 }) + .withMessage('newPagePath is required'), + body('isRecursively') + .if((value) => value != null) + .isBoolean() + .withMessage('isRecursively must be boolean'), + body('isRenameRedirect') + .if((value) => value != null) + .isBoolean() + .withMessage('isRenameRedirect must be boolean'), + body('updateMetadata') + .if((value) => value != null) + .isBoolean() + .withMessage('updateMetadata must be boolean'), ], resumeRenamePage: [ body('pageId').isMongoId().withMessage('pageId is required'), @@ -68,33 +92,58 @@ module.exports = (crowi) => { ], duplicatePage: [ body('pageId').isMongoId().withMessage('pageId is required'), - body('pageNameInput').trim().isLength({ min: 1 }).withMessage('pageNameInput is required'), - body('isRecursively').if(value => value != null).isBoolean().withMessage('isRecursively must be boolean'), + body('pageNameInput') + .trim() + .isLength({ min: 1 }) + .withMessage('pageNameInput is required'), + body('isRecursively') + .if((value) => value != null) + .isBoolean() + .withMessage('isRecursively must be boolean'), ], deletePages: [ body('pageIdToRevisionIdMap') .exists() - .withMessage('The body property "pageIdToRevisionIdMap" must be an json map with pageId as key and revisionId as value.'), + .withMessage( + 'The body property "pageIdToRevisionIdMap" must be an json map with pageId as key and revisionId as value.', + ), body('isCompletely') - .custom(v => v === 'true' || v === true || v == null) - .withMessage('The body property "isCompletely" must be "true" or true. (Omit param for false)'), + .custom((v) => v === 'true' || v === true || v == null) + .withMessage( + 'The body property "isCompletely" must be "true" or true. (Omit param for false)', + ), body('isRecursively') - .custom(v => v === 'true' || v === true || v == null) - .withMessage('The body property "isRecursively" must be "true" or true. (Omit param for false)'), + .custom((v) => v === 'true' || v === true || v == null) + .withMessage( + 'The body property "isRecursively" must be "true" or true. (Omit param for false)', + ), body('isAnyoneWithTheLink') - .custom(v => v === 'true' || v === true || v == null) - .withMessage('The body property "isAnyoneWithTheLink" must be "true" or true. (Omit param for false)'), + .custom((v) => v === 'true' || v === true || v == null) + .withMessage( + 'The body property "isAnyoneWithTheLink" must be "true" or true. (Omit param for false)', + ), ], legacyPagesMigration: [ - body('convertPath').optional().isString().withMessage('convertPath must be a string'), - body('pageIds').optional().isArray().withMessage('pageIds must be an array'), + body('convertPath') + .optional() + .isString() + .withMessage('convertPath must be a string'), + body('pageIds') + .optional() + .isArray() + .withMessage('pageIds must be an array'), body('isRecursively') .optional() - .custom(v => v === 'true' || v === true || v == null) - .withMessage('The body property "isRecursively" must be "true" or true. (Omit param for false)'), + .custom((v) => v === 'true' || v === true || v == null) + .withMessage( + 'The body property "isRecursively" must be "true" or true. (Omit param for false)', + ), ], convertPagesByPath: [ - body('convertPath').optional().isString().withMessage('convertPath must be a string'), + body('convertPath') + .optional() + .isString() + .withMessage('convertPath must be a string'), ], }; @@ -127,18 +176,27 @@ module.exports = (crowi) => { * 200: * description: Return pages recently updated */ - router.get('/recent', accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), - loginRequired, validator.recent, apiV3FormValidator, async(req, res) => { + router.get( + '/recent', + accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), + loginRequired, + validator.recent, + apiV3FormValidator, + async (req, res) => { const limit = parseInt(req.query.limit) || 20; const offset = parseInt(req.query.offset) || 0; const includeWipPage = req.query.includeWipPage === 'true'; // Need validation using express-validator - const hideRestrictedByOwner = configManager.getConfig('security:list-policy:hideRestrictedByOwner'); - const hideRestrictedByGroup = configManager.getConfig('security:list-policy:hideRestrictedByGroup'); + const hideRestrictedByOwner = configManager.getConfig( + 'security:list-policy:hideRestrictedByOwner', + ); + const hideRestrictedByGroup = configManager.getConfig( + 'security:list-policy:hideRestrictedByGroup', + ); /** - * @type {import('~/server/models/page').FindRecentUpdatedPagesOption} - */ + * @type {import('~/server/models/page').FindRecentUpdatedPagesOption} + */ const queryOptions = { offset, limit, @@ -152,19 +210,30 @@ module.exports = (crowi) => { }; try { - const result = await Page.findRecentUpdatedPages('/', req.user, queryOptions); + const result = await Page.findRecentUpdatedPages( + '/', + req.user, + queryOptions, + ); if (result.pages.length > limit) { result.pages.pop(); } result.pages.forEach((page) => { - if (page.lastUpdateUser != null && page.lastUpdateUser instanceof User) { + if ( + page.lastUpdateUser != null && + page.lastUpdateUser instanceof User + ) { page.lastUpdateUser = serializeUserSecurely(page.lastUpdateUser); } }); - const ids = result.pages.map((page) => { return page._id }); - const relations = await PageTagRelation.find({ relatedPage: { $in: ids } }).populate('relatedTag'); + const ids = result.pages.map((page) => { + return page._id; + }); + const relations = await PageTagRelation.find({ + relatedPage: { $in: ids }, + }).populate('relatedTag'); // { pageId: [{ tag }, ...] } const relationsMap = new Map(); @@ -185,12 +254,15 @@ module.exports = (crowi) => { }); return res.apiv3(result); - } - catch (err) { + } catch (err) { logger.error('Failed to get recent pages', err); - return res.apiv3Err(new ErrorV3('Failed to get recent pages', 'unknown'), 500); + return res.apiv3Err( + new ErrorV3('Failed to get recent pages', 'unknown'), + 500, + ); } - }); + }, + ); /** * @swagger @@ -246,7 +318,7 @@ module.exports = (crowi) => { excludeReadOnlyUser, validator.renamePage, apiV3FormValidator, - async(req, res) => { + async (req, res) => { const { pageId, revisionId } = req.body; let newPagePath = normalizePath(req.body.newPagePath); @@ -263,13 +335,21 @@ module.exports = (crowi) => { }; if (!isCreatablePage(newPagePath)) { - return res.apiv3Err(new ErrorV3(`Could not use the path '${newPagePath}'`, 'invalid_path'), 409); + return res.apiv3Err( + new ErrorV3( + `Could not use the path '${newPagePath}'`, + 'invalid_path', + ), + 409, + ); } if (isUserPage(newPagePath)) { const isExistUser = await User.isExistUserByUserPagePath(newPagePath); if (!isExistUser) { - return res.apiv3Err("Unable to rename a page under a non-existent user's user page"); + return res.apiv3Err( + "Unable to rename a page under a non-existent user's user page", + ); } } @@ -278,8 +358,11 @@ module.exports = (crowi) => { const isExist = await Page.exists({ path: newPagePath, isEmpty: false }); if (isExist) { - // if page found, cannot rename to that path - return res.apiv3Err(new ErrorV3(`${newPagePath} already exists`, 'already_exists'), 409); + // if page found, cannot rename to that path + return res.apiv3Err( + new ErrorV3(`${newPagePath} already exists`, 'already_exists'), + 409, + ); } let page; @@ -290,71 +373,97 @@ module.exports = (crowi) => { options.isRecursively = page.descendantCount > 0; if (page == null) { - return res.apiv3Err(new ErrorV3(`Page '${pageId}' is not found or forbidden`, 'notfound_or_forbidden'), 401); + return res.apiv3Err( + new ErrorV3( + `Page '${pageId}' is not found or forbidden`, + 'notfound_or_forbidden', + ), + 401, + ); } // empty page does not require revisionId validation if (!page.isEmpty && revisionId == null) { - return res.apiv3Err(new ErrorV3('revisionId must be a mongoId', 'invalid_body'), 400); + return res.apiv3Err( + new ErrorV3('revisionId must be a mongoId', 'invalid_body'), + 400, + ); } if (!page.isEmpty && !page.isUpdatable(revisionId)) { - return res.apiv3Err(new ErrorV3('Someone could update this page, so couldn\'t delete.', 'notfound_or_forbidden'), 409); + return res.apiv3Err( + new ErrorV3( + "Someone could update this page, so couldn't delete.", + 'notfound_or_forbidden', + ), + 409, + ); } - renamedPage = await crowi.pageService.renamePage(page, newPagePath, req.user, options, activityParameters); + renamedPage = await crowi.pageService.renamePage( + page, + newPagePath, + req.user, + options, + activityParameters, + ); // Respond before sending notification const result = { page: serializePageSecurely(renamedPage ?? page) }; res.apiv3(result); - } - catch (err) { + } catch (err) { logger.error(err); - return res.apiv3Err(new ErrorV3('Failed to update page.', 'unknown'), 500); + return res.apiv3Err( + new ErrorV3('Failed to update page.', 'unknown'), + 500, + ); } try { - // global notification - await globalNotificationService.fire(GlobalNotificationSettingEvent.PAGE_MOVE, renamedPage, req.user, { - oldPath: page.path, - }); - } - catch (err) { + // global notification + await globalNotificationService.fire( + GlobalNotificationSettingEvent.PAGE_MOVE, + renamedPage, + req.user, + { + oldPath: page.path, + }, + ); + } catch (err) { logger.error('Move notification failed', err); } }, ); /** - * @swagger - * /pages/resume-rename: - * post: - * tags: [Pages] - * description: Resume rename page operation - * requestBody: - * content: - * application/json: - * schema: - * properties: - * pageId: - * $ref: '#/components/schemas/ObjectId' - * required: - * - pageId - * responses: - * 200: - * description: Succeeded to resume rename page operation. - * content: - * application/json: - * schema: - * type: object - */ + * @swagger + * /pages/resume-rename: + * post: + * tags: [Pages] + * description: Resume rename page operation + * requestBody: + * content: + * application/json: + * schema: + * properties: + * pageId: + * $ref: '#/components/schemas/ObjectId' + * required: + * - pageId + * responses: + * 200: + * description: Succeeded to resume rename page operation. + * content: + * application/json: + * schema: + * type: object + */ router.post( '/resume-rename', accessTokenParser([SCOPE.WRITE.FEATURES.PAGE], { acceptLegacy: true }), loginRequiredStrictly, validator.resumeRenamePage, apiV3FormValidator, - async(req, res) => { - + async (req, res) => { const { pageId } = req.body; const { user } = req; @@ -366,17 +475,20 @@ module.exports = (crowi) => { return res.apiv3Err(new ErrorV3(msg, code), 403); } - const pageOp = await crowi.pageOperationService.getRenameSubOperationByPageId(page._id); + const pageOp = + await crowi.pageOperationService.getRenameSubOperationByPageId( + page._id, + ); if (pageOp == null) { - const msg = 'PageOperation document for Rename Sub operation not found.'; + const msg = + 'PageOperation document for Rename Sub operation not found.'; const code = 'document_not_found'; return res.apiv3Err(new ErrorV3(msg, code), 404); } try { await crowi.pageService.resumeRenameSubOperation(page, pageOp); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err(err, 500); } @@ -410,12 +522,16 @@ module.exports = (crowi) => { excludeReadOnlyUser, addActivity, apiV3FormValidator, - async(req, res) => { + async (req, res) => { const options = {}; const pagesInTrash = await crowi.pageService.findAllTrashPages(req.user); - const deletablePages = crowi.pageService.filterPagesByCanDeleteCompletely(pagesInTrash, req.user, true); + const deletablePages = crowi.pageService.filterPagesByCanDeleteCompletely( + pagesInTrash, + req.user, + true, + ); if (deletablePages.length === 0) { const msg = 'No pages can be deleted.'; @@ -428,15 +544,21 @@ module.exports = (crowi) => { if (deletablePages.length < pagesInTrash.length) { try { const options = { isCompletely: true, isRecursively: true }; - await crowi.pageService.deleteMultiplePages(deletablePages, req.user, options); + await crowi.pageService.deleteMultiplePages( + deletablePages, + req.user, + options, + ); activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ deletablePages }); - } - catch (err) { + } catch (err) { logger.error(err); - return res.apiv3Err(new ErrorV3('Failed to update page.', 'unknown'), 500); + return res.apiv3Err( + new ErrorV3('Failed to update page.', 'unknown'), + 500, + ); } } // when all pages are deletable @@ -446,81 +568,97 @@ module.exports = (crowi) => { ip: req.ip, endpoint: req.originalUrl, }; - const pages = await crowi.pageService.emptyTrashPage(req.user, options, activityParameters); + const pages = await crowi.pageService.emptyTrashPage( + req.user, + options, + activityParameters, + ); activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ pages }); - } - catch (err) { + } catch (err) { logger.error(err); - return res.apiv3Err(new ErrorV3('Failed to update page.', 'unknown'), 500); + return res.apiv3Err( + new ErrorV3('Failed to update page.', 'unknown'), + 500, + ); } } }, ); validator.displayList = [ - query('limit').if(value => value != null).isInt({ max: 100 }).withMessage('You should set less than 100 or not to set limit.'), + query('limit') + .if((value) => value != null) + .isInt({ max: 100 }) + .withMessage('You should set less than 100 or not to set limit.'), ]; /** - * @swagger - * - * /pages/list: - * get: - * tags: [Pages] - * description: Get list of pages - * parameters: - * - name: path - * in: query - * description: Path to search - * schema: - * type: string - * - name: limit - * in: query - * description: Limit of acquisitions - * schema: - * type: number - * - name: page - * in: query - * description: Page number - * schema: - * type: number - * responses: - * 200: - * description: Succeeded to retrieve pages. - * content: - * application/json: - * schema: - * properties: - * totalCount: - * type: number - * description: Total count of pages - * example: 3 - * offset: - * type: number - * description: Offset of pages - * example: 0 - * limit: - * type: number - * description: Limit of pages - * example: 10 - * pages: - * type: array - * items: - * allOf: - * - $ref: '#/components/schemas/Page' - * - type: object - * properties: - * lastUpdateUser: - * $ref: '#/components/schemas/User' - */ - router.get('/list', accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), - loginRequired, validator.list, apiV3FormValidator, async(req, res) => { - + * @swagger + * + * /pages/list: + * get: + * tags: [Pages] + * description: Get list of pages + * parameters: + * - name: path + * in: query + * description: Path to search + * schema: + * type: string + * - name: limit + * in: query + * description: Limit of acquisitions + * schema: + * type: number + * - name: page + * in: query + * description: Page number + * schema: + * type: number + * responses: + * 200: + * description: Succeeded to retrieve pages. + * content: + * application/json: + * schema: + * properties: + * totalCount: + * type: number + * description: Total count of pages + * example: 3 + * offset: + * type: number + * description: Offset of pages + * example: 0 + * limit: + * type: number + * description: Limit of pages + * example: 10 + * pages: + * type: array + * items: + * allOf: + * - $ref: '#/components/schemas/Page' + * - type: object + * properties: + * lastUpdateUser: + * $ref: '#/components/schemas/User' + */ + router.get( + '/list', + accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), + loginRequired, + validator.list, + apiV3FormValidator, + async (req, res) => { const path = normalizePath(req.query.path ?? '/'); - const limit = parseInt(req.query.limit ?? configManager.getConfig('customize:showPageLimitationS')); + const limit = parseInt( + req.query.limit ?? + configManager.getConfig('customize:showPageLimitationS'), + ); const page = req.query.page || 1; const offset = (page - 1) * limit; let includeTrashed = false; @@ -536,21 +674,28 @@ module.exports = (crowi) => { }; try { - const result = await Page.findListWithDescendants(path, req.user, queryOptions); + const result = await Page.findListWithDescendants( + path, + req.user, + queryOptions, + ); result.pages.forEach((page) => { - if (page.lastUpdateUser != null && page.lastUpdateUser instanceof User) { + if ( + page.lastUpdateUser != null && + page.lastUpdateUser instanceof User + ) { page.lastUpdateUser = serializeUserSecurely(page.lastUpdateUser); } }); return res.apiv3(result); - } - catch (err) { + } catch (err) { logger.error('Failed to get Descendants Pages', err); return res.apiv3Err(err, 500); } - }); + }, + ); /** * @swagger @@ -600,27 +745,36 @@ module.exports = (crowi) => { addActivity, validator.duplicatePage, apiV3FormValidator, - async(req, res) => { - const { pageId, isRecursively, onlyDuplicateUserRelatedResources } = req.body; + async (req, res) => { + const { pageId, isRecursively, onlyDuplicateUserRelatedResources } = + req.body; const newPagePath = normalizePath(req.body.pageNameInput); const isCreatable = isCreatablePage(newPagePath); if (!isCreatable) { - return res.apiv3Err(new ErrorV3('This page path is invalid', 'invalid_path'), 400); + return res.apiv3Err( + new ErrorV3('This page path is invalid', 'invalid_path'), + 400, + ); } if (isUserPage(newPagePath)) { const isExistUser = await User.isExistUserByUserPagePath(newPagePath); if (!isExistUser) { - return res.apiv3Err("Unable to duplicate a page under a non-existent user's user page"); + return res.apiv3Err( + "Unable to duplicate a page under a non-existent user's user page", + ); } } // check page existence - const isExist = (await Page.exists({ path: newPagePath, isEmpty: false })); + const isExist = await Page.exists({ path: newPagePath, isEmpty: false }); if (isExist) { - return res.apiv3Err(new ErrorV3(`Page exists '${newPagePath})'`, 'already_exists'), 409); + return res.apiv3Err( + new ErrorV3(`Page exists '${newPagePath})'`, 'already_exists'), + 409, + ); } const page = await Page.findByIdAndViewer(pageId, req.user, null, true); @@ -629,27 +783,45 @@ module.exports = (crowi) => { if (page == null || isEmptyAndNotRecursively) { res.code = 'Page is not found'; logger.error('Failed to find the pages'); - return res.apiv3Err(new ErrorV3(`Page '${pageId}' is not found or forbidden`, 'notfound_or_forbidden'), 401); + return res.apiv3Err( + new ErrorV3( + `Page '${pageId}' is not found or forbidden`, + 'notfound_or_forbidden', + ), + 401, + ); } - const newParentPage = await crowi.pageService.duplicate(page, newPagePath, req.user, isRecursively, onlyDuplicateUserRelatedResources); + const newParentPage = await crowi.pageService.duplicate( + page, + newPagePath, + req.user, + isRecursively, + onlyDuplicateUserRelatedResources, + ); const result = { page: serializePageSecurely(newParentPage) }; // copy the page since it's used and updated in crowi.pageService.duplicate const copyPage = { ...page }; copyPage.path = newPagePath; try { - await globalNotificationService.fire(GlobalNotificationSettingEvent.PAGE_CREATE, copyPage, req.user); - } - catch (err) { + await globalNotificationService.fire( + GlobalNotificationSettingEvent.PAGE_CREATE, + copyPage, + req.user, + ); + } catch (err) { logger.error('Create grobal notification failed', err); } // create subscription (parent page only) try { - await crowi.inAppNotificationService.createSubscription(req.user.id, newParentPage._id, subscribeRuleNames.PAGE_CREATE); - } - catch (err) { + await crowi.inAppNotificationService.createSubscription( + req.user.id, + newParentPage._id, + subscribeRuleNames.PAGE_CREATE, + ); + } catch (err) { logger.error('Failed to create subscription document', err); } @@ -659,7 +831,13 @@ module.exports = (crowi) => { action: SupportedAction.ACTION_PAGE_DUPLICATE, }; - activityEvent.emit('update', res.locals.activity._id, parameters, page, preNotifyService.generatePreNotify); + activityEvent.emit( + 'update', + res.locals.activity._id, + parameters, + page, + preNotifyService.generatePreNotify, + ); return res.apiv3(result); }, @@ -700,65 +878,71 @@ module.exports = (crowi) => { '/subordinated-list', accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), loginRequired, - async(req, res) => { + async (req, res) => { const { path } = req.query; const limit = parseInt(req.query.limit) || LIMIT_FOR_LIST; try { const pageData = await Page.findByPath(path, true); - const result = await Page.findManageableListWithDescendants(pageData, req.user, { limit }); + const result = await Page.findManageableListWithDescendants( + pageData, + req.user, + { limit }, + ); return res.apiv3({ subordinatedPages: result }); - } - catch (err) { - return res.apiv3Err(new ErrorV3('Failed to update page.', 'unknown'), 500); + } catch (err) { + return res.apiv3Err( + new ErrorV3('Failed to update page.', 'unknown'), + 500, + ); } }, ); /** - * @swagger - * /pages/delete: - * post: - * tags: [Pages] - * description: Delete pages - * requestBody: - * content: - * application/json: - * schema: - * properties: - * pageIdToRevisionIdMap: - * type: object - * description: Map of page IDs to revision IDs - * example: { "5e2d6aede35da4004ef7e0b7": "5e07345972560e001761fa63" } - * isCompletely: - * type: boolean - * description: Whether to delete pages completely - * isRecursively: - * type: boolean - * description: Whether to delete pages recursively - * isAnyoneWithTheLink: - * type: boolean - * description: Whether the page is restricted to anyone with the link - * responses: - * 200: - * description: Succeeded to delete pages. - * content: - * application/json: - * schema: - * properties: - * paths: - * type: array - * items: - * type: string - * description: List of deleted page paths - * isRecursively: - * type: boolean - * description: Whether pages were deleted recursively - * isCompletely: - * type: boolean - * description: Whether pages were deleted completely - */ + * @swagger + * /pages/delete: + * post: + * tags: [Pages] + * description: Delete pages + * requestBody: + * content: + * application/json: + * schema: + * properties: + * pageIdToRevisionIdMap: + * type: object + * description: Map of page IDs to revision IDs + * example: { "5e2d6aede35da4004ef7e0b7": "5e07345972560e001761fa63" } + * isCompletely: + * type: boolean + * description: Whether to delete pages completely + * isRecursively: + * type: boolean + * description: Whether to delete pages recursively + * isAnyoneWithTheLink: + * type: boolean + * description: Whether the page is restricted to anyone with the link + * responses: + * 200: + * description: Succeeded to delete pages. + * content: + * application/json: + * schema: + * properties: + * paths: + * type: array + * items: + * type: string + * description: List of deleted page paths + * isRecursively: + * type: boolean + * description: Whether pages were deleted recursively + * isCompletely: + * type: boolean + * description: Whether pages were deleted completely + */ router.post( '/delete', accessTokenParser([SCOPE.WRITE.FEATURES.PAGE], { acceptLegacy: true }), @@ -766,42 +950,82 @@ module.exports = (crowi) => { excludeReadOnlyUser, validator.deletePages, apiV3FormValidator, - async(req, res) => { + async (req, res) => { const { - pageIdToRevisionIdMap, isCompletely, isRecursively, isAnyoneWithTheLink, + pageIdToRevisionIdMap, + isCompletely, + isRecursively, + isAnyoneWithTheLink, } = req.body; const pageIds = Object.keys(pageIdToRevisionIdMap); if (pageIds.length === 0) { - return res.apiv3Err(new ErrorV3('Select pages to delete.', 'no_page_selected'), 400); + return res.apiv3Err( + new ErrorV3('Select pages to delete.', 'no_page_selected'), + 400, + ); } if (isAnyoneWithTheLink && pageIds.length !== 1) { - return res.apiv3Err(new ErrorV3('Only one restricted page can be selected', 'not_single_page'), 400); + return res.apiv3Err( + new ErrorV3( + 'Only one restricted page can be selected', + 'not_single_page', + ), + 400, + ); } if (pageIds.length > LIMIT_FOR_MULTIPLE_PAGE_OP) { - return res.apiv3Err(new ErrorV3(`The maximum number of pages you can select is ${LIMIT_FOR_MULTIPLE_PAGE_OP}.`, 'exceeded_maximum_number'), 400); + return res.apiv3Err( + new ErrorV3( + `The maximum number of pages you can select is ${LIMIT_FOR_MULTIPLE_PAGE_OP}.`, + 'exceeded_maximum_number', + ), + 400, + ); } let pagesToDelete; try { - pagesToDelete = await Page.findByIdsAndViewer(pageIds, req.user, null, true, isAnyoneWithTheLink); - } - catch (err) { + pagesToDelete = await Page.findByIdsAndViewer( + pageIds, + req.user, + null, + true, + isAnyoneWithTheLink, + ); + } catch (err) { logger.error('Failed to find pages to delete.', err); return res.apiv3Err(new ErrorV3('Failed to find pages to delete.')); } - if (isAnyoneWithTheLink && pagesToDelete[0].grant !== PageGrant.GRANT_RESTRICTED) { - return res.apiv3Err(new ErrorV3('The grant of the retrieved page is not restricted'), 500); + if ( + isAnyoneWithTheLink && + pagesToDelete[0].grant !== PageGrant.GRANT_RESTRICTED + ) { + return res.apiv3Err( + new ErrorV3('The grant of the retrieved page is not restricted'), + 500, + ); } let pagesCanBeDeleted; if (isCompletely) { - pagesCanBeDeleted = await crowi.pageService.filterPagesByCanDeleteCompletely(pagesToDelete, req.user, isRecursively); - } - else { - const filteredPages = pagesToDelete.filter(p => p.isEmpty || p.isUpdatable(pageIdToRevisionIdMap[p._id].toString())); - pagesCanBeDeleted = await crowi.pageService.filterPagesByCanDelete(filteredPages, req.user, isRecursively); + pagesCanBeDeleted = + await crowi.pageService.filterPagesByCanDeleteCompletely( + pagesToDelete, + req.user, + isRecursively, + ); + } else { + const filteredPages = pagesToDelete.filter( + (p) => + p.isEmpty || p.isUpdatable(pageIdToRevisionIdMap[p._id].toString()), + ); + pagesCanBeDeleted = await crowi.pageService.filterPagesByCanDelete( + filteredPages, + req.user, + isRecursively, + ); } if (pagesCanBeDeleted.length === 0) { @@ -815,9 +1039,18 @@ module.exports = (crowi) => { endpoint: req.originalUrl, }; const options = { isCompletely, isRecursively }; - crowi.pageService.deleteMultiplePages(pagesCanBeDeleted, req.user, options, activityParameters); - - return res.apiv3({ paths: pagesCanBeDeleted.map(p => p.path), isRecursively, isCompletely }); + crowi.pageService.deleteMultiplePages( + pagesCanBeDeleted, + req.user, + options, + activityParameters, + ); + + return res.apiv3({ + paths: pagesCanBeDeleted.map((p) => p.path), + isRecursively, + isCompletely, + }); }, ); @@ -854,15 +1087,14 @@ module.exports = (crowi) => { adminRequired, validator.convertPagesByPath, apiV3FormValidator, - async(req, res) => { + async (req, res) => { const { convertPath } = req.body; // Convert by path const normalizedPath = normalizePath(convertPath); try { await crowi.pageService.normalizeParentByPath(normalizedPath, req.user); - } - catch (err) { + } catch (err) { logger.error(err); if (isV5ConversionError(err)) { @@ -904,7 +1136,7 @@ module.exports = (crowi) => { * schema: * type: object * description: Empty object - */ + */ router.post( '/legacy-pages-migration', accessTokenParser([SCOPE.WRITE.FEATURES.PAGE], { acceptLegacy: true }), @@ -912,14 +1144,20 @@ module.exports = (crowi) => { excludeReadOnlyUser, validator.legacyPagesMigration, apiV3FormValidator, - async(req, res) => { + async (req, res) => { const { pageIds: _pageIds, isRecursively } = req.body; // Convert by pageIds const pageIds = _pageIds == null ? [] : _pageIds; if (pageIds.length > LIMIT_FOR_MULTIPLE_PAGE_OP) { - return res.apiv3Err(new ErrorV3(`The maximum number of pages you can select is ${LIMIT_FOR_MULTIPLE_PAGE_OP}.`, 'exceeded_maximum_number'), 400); + return res.apiv3Err( + new ErrorV3( + `The maximum number of pages you can select is ${LIMIT_FOR_MULTIPLE_PAGE_OP}.`, + 'exceeded_maximum_number', + ), + 400, + ); } if (pageIds.length === 0) { return res.apiv3Err(new ErrorV3('No page is selected.'), 400); @@ -927,14 +1165,18 @@ module.exports = (crowi) => { try { if (isRecursively) { - await crowi.pageService.normalizeParentByPageIdsRecursively(pageIds, req.user); - } - else { + await crowi.pageService.normalizeParentByPageIdsRecursively( + pageIds, + req.user, + ); + } else { await crowi.pageService.normalizeParentByPageIds(pageIds, req.user); } - } - catch (err) { - return res.apiv3Err(new ErrorV3(`Failed to migrate pages: ${err.message}`), 500); + } catch (err) { + return res.apiv3Err( + new ErrorV3(`Failed to migrate pages: ${err.message}`), + 500, + ); } return res.apiv3({}); @@ -962,16 +1204,25 @@ module.exports = (crowi) => { * type: number * description: Number of pages that can be migrated */ - router.get('/v5-migration-status', accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), loginRequired, async(req, res) => { - try { - const isV5Compatible = configManager.getConfig('app:isV5Compatible'); - const migratablePagesCount = req.user != null ? await crowi.pageService.countPagesCanNormalizeParentByUser(req.user) : null; // null check since not using loginRequiredStrictly - return res.apiv3({ isV5Compatible, migratablePagesCount }); - } - catch (err) { - return res.apiv3Err(new ErrorV3('Failed to obtain migration status')); - } - }); + router.get( + '/v5-migration-status', + accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }), + loginRequired, + async (req, res) => { + try { + const isV5Compatible = configManager.getConfig('app:isV5Compatible'); + const migratablePagesCount = + req.user != null + ? await crowi.pageService.countPagesCanNormalizeParentByUser( + req.user, + ) + : null; // null check since not using loginRequiredStrictly + return res.apiv3({ isV5Compatible, migratablePagesCount }); + } catch (err) { + return res.apiv3Err(new ErrorV3('Failed to obtain migration status')); + } + }, + ); return router; }; diff --git a/apps/app/src/server/routes/apiv3/personal-setting/delete-access-token.ts b/apps/app/src/server/routes/apiv3/personal-setting/delete-access-token.ts index f55a87da8d0..a7edfc4ab5b 100644 --- a/apps/app/src/server/routes/apiv3/personal-setting/delete-access-token.ts +++ b/apps/app/src/server/routes/apiv3/personal-setting/delete-access-token.ts @@ -1,9 +1,9 @@ +import { SCOPE } from '@growi/core/dist/interfaces'; import { ErrorV3 } from '@growi/core/dist/models'; import type { Request, RequestHandler } from 'express'; import { query } from 'express-validator'; import { SupportedAction } from '~/interfaces/activity'; -import { SCOPE } from '@growi/core/dist/interfaces'; import type Crowi from '~/server/crowi'; import { accessTokenParser } from '~/server/middlewares/access-token-parser'; import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity'; @@ -14,13 +14,20 @@ import loggerFactory from '~/utils/logger'; import type { ApiV3Response } from '../interfaces/apiv3-response'; -const logger = loggerFactory('growi:routes:apiv3:personal-setting:generate-access-tokens'); +const logger = loggerFactory( + 'growi:routes:apiv3:personal-setting:generate-access-tokens', +); type ReqQuery = { - tokenId: string, -} + tokenId: string; +}; -type DeleteAccessTokenRequest = Request; +type DeleteAccessTokenRequest = Request< + undefined, + ApiV3Response, + undefined, + ReqQuery +>; type DeleteAccessTokenHandlersFactory = (crowi: Crowi) => RequestHandler[]; @@ -32,34 +39,39 @@ const validator = [ .withMessage('tokenId must be a string'), ]; -export const deleteAccessTokenHandlersFactory: DeleteAccessTokenHandlersFactory = (crowi) => { +export const deleteAccessTokenHandlersFactory: DeleteAccessTokenHandlersFactory = + (crowi) => { + const loginRequiredStrictly = + require('../../../middlewares/login-required')(crowi); + const addActivity = generateAddActivityMiddleware(); + const activityEvent = crowi.event('activity'); - const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi); - const addActivity = generateAddActivityMiddleware(); - const activityEvent = crowi.event('activity'); + return [ + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), + loginRequiredStrictly, + excludeReadOnlyUser, + addActivity, + validator, + apiV3FormValidator, + async (req: DeleteAccessTokenRequest, res: ApiV3Response) => { + const { query } = req; + const { tokenId } = query; - return [ - accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), - loginRequiredStrictly, - excludeReadOnlyUser, - addActivity, - validator, - apiV3FormValidator, - async(req: DeleteAccessTokenRequest, res: ApiV3Response) => { - const { query } = req; - const { tokenId } = query; + try { + await AccessToken.deleteTokenById(tokenId); - try { - await AccessToken.deleteTokenById(tokenId); + const parameters = { + action: SupportedAction.ACTION_USER_ACCESS_TOKEN_DELETE, + }; + activityEvent.emit('update', res.locals.activity._id, parameters); - const parameters = { action: SupportedAction.ACTION_USER_ACCESS_TOKEN_DELETE }; - activityEvent.emit('update', res.locals.activity._id, parameters); - - return res.apiv3({}); - } - catch (err) { - logger.error(err); - return res.apiv3Err(new ErrorV3(err.toString(), 'delete-access-token-failed')); - } - }]; -}; + return res.apiv3({}); + } catch (err) { + logger.error(err); + return res.apiv3Err( + new ErrorV3(err.toString(), 'delete-access-token-failed'), + ); + } + }, + ]; + }; diff --git a/apps/app/src/server/routes/apiv3/personal-setting/delete-all-access-tokens.ts b/apps/app/src/server/routes/apiv3/personal-setting/delete-all-access-tokens.ts index 4c862ebd4ee..7bdb349e078 100644 --- a/apps/app/src/server/routes/apiv3/personal-setting/delete-all-access-tokens.ts +++ b/apps/app/src/server/routes/apiv3/personal-setting/delete-all-access-tokens.ts @@ -1,9 +1,9 @@ import type { IUserHasId } from '@growi/core/dist/interfaces'; +import { SCOPE } from '@growi/core/dist/interfaces'; import { ErrorV3 } from '@growi/core/dist/models'; import type { Request, RequestHandler } from 'express'; import { SupportedAction } from '~/interfaces/activity'; -import { SCOPE } from '@growi/core/dist/interfaces'; import type Crowi from '~/server/crowi'; import { accessTokenParser } from '~/server/middlewares/access-token-parser'; import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity'; @@ -13,39 +13,47 @@ import loggerFactory from '~/utils/logger'; import type { ApiV3Response } from '../interfaces/apiv3-response'; -const logger = loggerFactory('growi:routes:apiv3:personal-setting:generate-access-tokens'); +const logger = loggerFactory( + 'growi:routes:apiv3:personal-setting:generate-access-tokens', +); -interface DeleteAllAccessTokensRequest extends Request { - user: IUserHasId, +interface DeleteAllAccessTokensRequest + extends Request { + user: IUserHasId; } type DeleteAllAccessTokensHandlersFactory = (crowi: Crowi) => RequestHandler[]; -export const deleteAllAccessTokensHandlersFactory: DeleteAllAccessTokensHandlersFactory = (crowi) => { - - const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi); - const addActivity = generateAddActivityMiddleware(); - const activityEvent = crowi.event('activity'); - - return [ - accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), - loginRequiredStrictly, - excludeReadOnlyUser, - addActivity, - async(req: DeleteAllAccessTokensRequest, res: ApiV3Response) => { - const { user } = req; - - try { - await AccessToken.deleteAllTokensByUserId(user._id); - - const parameters = { action: SupportedAction.ACTION_USER_ACCESS_TOKEN_DELETE }; - activityEvent.emit('update', res.locals.activity._id, parameters); - - return res.apiv3({}); - } - catch (err) { - logger.error(err); - return res.apiv3Err(new ErrorV3(err.toString(), 'delete-all-access-token-failed')); - } - }]; -}; +export const deleteAllAccessTokensHandlersFactory: DeleteAllAccessTokensHandlersFactory = + (crowi) => { + const loginRequiredStrictly = + require('../../../middlewares/login-required')(crowi); + const addActivity = generateAddActivityMiddleware(); + const activityEvent = crowi.event('activity'); + + return [ + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), + loginRequiredStrictly, + excludeReadOnlyUser, + addActivity, + async (req: DeleteAllAccessTokensRequest, res: ApiV3Response) => { + const { user } = req; + + try { + await AccessToken.deleteAllTokensByUserId(user._id); + + const parameters = { + action: SupportedAction.ACTION_USER_ACCESS_TOKEN_DELETE, + }; + activityEvent.emit('update', res.locals.activity._id, parameters); + + return res.apiv3({}); + } catch (err) { + logger.error(err); + return res.apiv3Err( + new ErrorV3(err.toString(), 'delete-all-access-token-failed'), + ); + } + }, + ]; + }; diff --git a/apps/app/src/server/routes/apiv3/personal-setting/generate-access-token.ts b/apps/app/src/server/routes/apiv3/personal-setting/generate-access-token.ts index fa80a429a7c..6e0ff23fad9 100644 --- a/apps/app/src/server/routes/apiv3/personal-setting/generate-access-token.ts +++ b/apps/app/src/server/routes/apiv3/personal-setting/generate-access-token.ts @@ -1,6 +1,4 @@ -import type { - IUserHasId, Scope, -} from '@growi/core/dist/interfaces'; +import type { IUserHasId, Scope } from '@growi/core/dist/interfaces'; import { ErrorV3 } from '@growi/core/dist/models'; import type { Request, RequestHandler } from 'express'; import { body } from 'express-validator'; @@ -16,16 +14,19 @@ import loggerFactory from '~/utils/logger'; import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator'; import type { ApiV3Response } from '../interfaces/apiv3-response'; -const logger = loggerFactory('growi:routes:apiv3:personal-setting:generate-access-tokens'); +const logger = loggerFactory( + 'growi:routes:apiv3:personal-setting:generate-access-tokens', +); type ReqBody = { - expiredAt: Date, - description?: string, - scopes?: Scope[], -} + expiredAt: Date; + description?: string; + scopes?: Scope[]; +}; -interface GenerateAccessTokenRequest extends Request { - user: IUserHasId, +interface GenerateAccessTokenRequest + extends Request { + user: IUserHasId; } type GenerateAccessTokenHandlerFactory = (crowi: Crowi) => RequestHandler[]; @@ -73,35 +74,43 @@ const validator = [ .withMessage('Invalid scope'), ]; -export const generateAccessTokenHandlerFactory: GenerateAccessTokenHandlerFactory = (crowi) => { - - const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi); - const activityEvent = crowi.event('activity'); - const addActivity = generateAddActivityMiddleware(); - - return [ - loginRequiredStrictly, - excludeReadOnlyUser, - addActivity, - validator, - apiV3FormValidator, - async(req: GenerateAccessTokenRequest, res: ApiV3Response) => { - - const { user, body } = req; - const { expiredAt, description, scopes } = body; - - try { - const tokenData = await AccessToken.generateToken(user._id, expiredAt, scopes, description); - - const parameters = { action: SupportedAction.ACTION_USER_ACCESS_TOKEN_CREATE }; - activityEvent.emit('update', res.locals.activity._id, parameters); - - return res.apiv3(tokenData); - } - catch (err) { - logger.error(err); - return res.apiv3Err(new ErrorV3(err.toString(), 'generate-access-token-failed')); - } - }, - ]; -}; +export const generateAccessTokenHandlerFactory: GenerateAccessTokenHandlerFactory = + (crowi) => { + const loginRequiredStrictly = + require('../../../middlewares/login-required')(crowi); + const activityEvent = crowi.event('activity'); + const addActivity = generateAddActivityMiddleware(); + + return [ + loginRequiredStrictly, + excludeReadOnlyUser, + addActivity, + validator, + apiV3FormValidator, + async (req: GenerateAccessTokenRequest, res: ApiV3Response) => { + const { user, body } = req; + const { expiredAt, description, scopes } = body; + + try { + const tokenData = await AccessToken.generateToken( + user._id, + expiredAt, + scopes, + description, + ); + + const parameters = { + action: SupportedAction.ACTION_USER_ACCESS_TOKEN_CREATE, + }; + activityEvent.emit('update', res.locals.activity._id, parameters); + + return res.apiv3(tokenData); + } catch (err) { + logger.error(err); + return res.apiv3Err( + new ErrorV3(err.toString(), 'generate-access-token-failed'), + ); + } + }, + ]; + }; diff --git a/apps/app/src/server/routes/apiv3/personal-setting/get-access-tokens.ts b/apps/app/src/server/routes/apiv3/personal-setting/get-access-tokens.ts index b048b8d35a8..171f959060a 100644 --- a/apps/app/src/server/routes/apiv3/personal-setting/get-access-tokens.ts +++ b/apps/app/src/server/routes/apiv3/personal-setting/get-access-tokens.ts @@ -1,8 +1,8 @@ import type { IUserHasId } from '@growi/core/dist/interfaces'; +import { SCOPE } from '@growi/core/dist/interfaces'; import { ErrorV3 } from '@growi/core/dist/models'; import type { Request, RequestHandler } from 'express'; -import { SCOPE } from '@growi/core/dist/interfaces'; import type Crowi from '~/server/crowi'; import { accessTokenParser } from '~/server/middlewares/access-token-parser'; import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity'; @@ -12,17 +12,23 @@ import loggerFactory from '~/utils/logger'; import type { ApiV3Response } from '../interfaces/apiv3-response'; -const logger = loggerFactory('growi:routes:apiv3:personal-setting:get-access-tokens'); +const logger = loggerFactory( + 'growi:routes:apiv3:personal-setting:get-access-tokens', +); -interface GetAccessTokenRequest extends Request { - user: IUserHasId, +interface GetAccessTokenRequest + extends Request { + user: IUserHasId; } type GetAccessTokenHandlerFactory = (crowi: Crowi) => RequestHandler[]; -export const getAccessTokenHandlerFactory: GetAccessTokenHandlerFactory = (crowi) => { - - const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi); +export const getAccessTokenHandlerFactory: GetAccessTokenHandlerFactory = ( + crowi, +) => { + const loginRequiredStrictly = require('../../../middlewares/login-required')( + crowi, + ); const addActivity = generateAddActivityMiddleware(); return [ @@ -30,16 +36,17 @@ export const getAccessTokenHandlerFactory: GetAccessTokenHandlerFactory = (crowi loginRequiredStrictly, excludeReadOnlyUser, addActivity, - async(req: GetAccessTokenRequest, res: ApiV3Response) => { + async (req: GetAccessTokenRequest, res: ApiV3Response) => { const { user } = req; try { const accessTokens = await AccessToken.findTokenByUserId(user._id); return res.apiv3({ accessTokens }); - } - catch (err) { + } catch (err) { logger.error(err); - return res.apiv3Err(new ErrorV3(err.toString(), 'colud_not_get_access_token')); + return res.apiv3Err( + new ErrorV3(err.toString(), 'colud_not_get_access_token'), + ); } }, ]; diff --git a/apps/app/src/server/routes/apiv3/personal-setting/index.js b/apps/app/src/server/routes/apiv3/personal-setting/index.js index 3a10e47ae34..49bced2dec0 100644 --- a/apps/app/src/server/routes/apiv3/personal-setting/index.js +++ b/apps/app/src/server/routes/apiv3/personal-setting/index.js @@ -2,7 +2,6 @@ import { SCOPE } from '@growi/core/dist/interfaces'; import { ErrorV3 } from '@growi/core/dist/models'; import { body } from 'express-validator'; - import { i18n } from '^/config/next-i18next.config'; import { SupportedAction } from '~/interfaces/activity'; @@ -14,13 +13,11 @@ import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator'; import EditorSettings from '../../../models/editor-settings'; import ExternalAccount from '../../../models/external-account'; import InAppNotificationSettings from '../../../models/in-app-notification-settings'; - import { deleteAccessTokenHandlersFactory } from './delete-access-token'; import { deleteAllAccessTokensHandlersFactory } from './delete-all-access-tokens'; import { generateAccessTokenHandlerFactory } from './generate-access-token'; import { getAccessTokenHandlerFactory } from './get-access-tokens'; - const logger = loggerFactory('growi:routes:apiv3:personal-setting'); const express = require('express'); @@ -76,14 +73,18 @@ const router = express.Router(); */ /** @param {import('~/server/crowi').default} crowi Crowi instance */ module.exports = (crowi) => { - const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi); + const loginRequiredStrictly = require('../../../middlewares/login-required')( + crowi, + ); const addActivity = generateAddActivityMiddleware(crowi); const { User } = crowi.models; const activityEvent = crowi.event('activity'); - const minPasswordLength = crowi.configManager.getConfig('app:minPasswordLength'); + const minPasswordLength = crowi.configManager.getConfig( + 'app:minPasswordLength', + ); const validator = { personal: [ @@ -91,24 +92,31 @@ module.exports = (crowi) => { body('email') .isEmail() .custom((email) => { - if (!User.isEmailValid(email)) throw new Error('email is not included in whitelist'); + if (!User.isEmailValid(email)) + throw new Error('email is not included in whitelist'); return true; }), body('lang').isString().isIn(i18n.locales), body('isEmailPublished').isBoolean(), body('slackMemberId').optional().isString(), ], - imageType: [ - body('isGravatarEnabled').isBoolean(), - ], + imageType: [body('isGravatarEnabled').isBoolean()], password: [ body('oldPassword').isString(), - body('newPassword').isString().not().isEmpty() + body('newPassword') + .isString() + .not() + .isEmpty() .isLength({ min: minPasswordLength }) - .withMessage(`password must be at least ${minPasswordLength} characters long`), - body('newPasswordConfirm').isString().not().isEmpty() + .withMessage( + `password must be at least ${minPasswordLength} characters long`, + ), + body('newPasswordConfirm') + .isString() + .not() + .isEmpty() .custom((value, { req }) => { - return (value === req.body.newPassword); + return value === req.body.newPassword; }), ], associateLdap: [ @@ -150,24 +158,28 @@ module.exports = (crowi) => { * type: object * description: personal params */ - router.get('/', accessTokenParser([SCOPE.READ.USER_SETTINGS.INFO], { acceptLegacy: true }), loginRequiredStrictly, async(req, res) => { - const { username } = req.user; - try { - const user = await User.findUserByUsername(username); - - // return email and apiToken - const { email, apiToken } = user; - const currentUser = user.toObject(); - currentUser.email = email; - currentUser.apiToken = apiToken; - - return res.apiv3({ currentUser }); - } - catch (err) { - logger.error(err); - return res.apiv3Err('update-personal-settings-failed'); - } - }); + router.get( + '/', + accessTokenParser([SCOPE.READ.USER_SETTINGS.INFO], { acceptLegacy: true }), + loginRequiredStrictly, + async (req, res) => { + const { username } = req.user; + try { + const user = await User.findUserByUsername(username); + + // return email and apiToken + const { email, apiToken } = user; + const currentUser = user.toObject(); + currentUser.email = email; + currentUser.apiToken = apiToken; + + return res.apiv3({ currentUser }); + } catch (err) { + logger.error(err); + return res.apiv3Err('update-personal-settings-failed'); + } + }, + ); /** * @swagger @@ -191,21 +203,28 @@ module.exports = (crowi) => { * type: number * description: Minimum password length */ - router.get('/is-password-set', accessTokenParser([SCOPE.READ.USER_SETTINGS.PASSWORD], { acceptLegacy: true }), loginRequiredStrictly, async(req, res) => { - const { username } = req.user; - - try { - const user = await User.findUserByUsername(username); - const isPasswordSet = user.isPasswordSet(); - const minPasswordLength = crowi.configManager.getConfig('app:minPasswordLength'); - return res.apiv3({ isPasswordSet, minPasswordLength }); - } - catch (err) { - logger.error(err); - return res.apiv3Err('fail-to-get-whether-password-is-set'); - } - - }); + router.get( + '/is-password-set', + accessTokenParser([SCOPE.READ.USER_SETTINGS.PASSWORD], { + acceptLegacy: true, + }), + loginRequiredStrictly, + async (req, res) => { + const { username } = req.user; + + try { + const user = await User.findUserByUsername(username); + const isPasswordSet = user.isPasswordSet(); + const minPasswordLength = crowi.configManager.getConfig( + 'app:minPasswordLength', + ); + return res.apiv3({ isPasswordSet, minPasswordLength }); + } catch (err) { + logger.error(err); + return res.apiv3Err('fail-to-get-whether-password-is-set'); + } + }, + ); /** * @swagger @@ -232,10 +251,14 @@ module.exports = (crowi) => { * type: object * description: personal params */ - router.put('/', - accessTokenParser([SCOPE.WRITE.USER_SETTINGS.INFO], { acceptLegacy: true }), loginRequiredStrictly, addActivity, validator.personal, apiV3FormValidator, - async(req, res) => { - + router.put( + '/', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.INFO], { acceptLegacy: true }), + loginRequiredStrictly, + addActivity, + validator.personal, + apiV3FormValidator, + async (req, res) => { try { const user = await User.findOne({ _id: req.user.id }); user.name = req.body.name; @@ -248,22 +271,28 @@ module.exports = (crowi) => { if (!isUniqueEmail) { logger.error('email-is-not-unique'); - return res.apiv3Err(new ErrorV3('The email is already in use', 'email-is-already-in-use')); + return res.apiv3Err( + new ErrorV3( + 'The email is already in use', + 'email-is-already-in-use', + ), + ); } const updatedUser = await user.save(); - const parameters = { action: SupportedAction.ACTION_USER_PERSONAL_SETTINGS_UPDATE }; + const parameters = { + action: SupportedAction.ACTION_USER_PERSONAL_SETTINGS_UPDATE, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ updatedUser }); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err('update-personal-settings-failed'); } - - }); + }, + ); /** * @swagger @@ -293,24 +322,32 @@ module.exports = (crowi) => { * type: object * description: user data */ - router.put('/image-type', accessTokenParser([SCOPE.WRITE.USER_SETTINGS.INFO], { acceptLegacy: true }), loginRequiredStrictly, addActivity, - validator.imageType, apiV3FormValidator, - async(req, res) => { + router.put( + '/image-type', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.INFO], { acceptLegacy: true }), + loginRequiredStrictly, + addActivity, + validator.imageType, + apiV3FormValidator, + async (req, res) => { const { isGravatarEnabled } = req.body; try { - const userData = await req.user.updateIsGravatarEnabled(isGravatarEnabled); + const userData = + await req.user.updateIsGravatarEnabled(isGravatarEnabled); - const parameters = { action: SupportedAction.ACTION_USER_IMAGE_TYPE_UPDATE }; + const parameters = { + action: SupportedAction.ACTION_USER_IMAGE_TYPE_UPDATE, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ userData }); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err('update-personal-settings-failed'); } - }); + }, + ); /** * @swagger @@ -331,20 +368,24 @@ module.exports = (crowi) => { * type: object * description: array of external accounts */ - router.get('/external-accounts', - accessTokenParser([SCOPE.READ.USER_SETTINGS.EXTERNAL_ACCOUNT], { acceptLegacy: true }), loginRequiredStrictly, async(req, res) => { + router.get( + '/external-accounts', + accessTokenParser([SCOPE.READ.USER_SETTINGS.EXTERNAL_ACCOUNT], { + acceptLegacy: true, + }), + loginRequiredStrictly, + async (req, res) => { const userData = req.user; try { const externalAccounts = await ExternalAccount.find({ user: userData }); return res.apiv3({ externalAccounts }); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err('get-external-accounts-failed'); } - - }); + }, + ); /** * @swagger @@ -376,9 +417,16 @@ module.exports = (crowi) => { * type: object * description: user data updated */ - router.put('/password', - accessTokenParser([SCOPE.WRITE.USER_SETTINGS.PASSWORD], { acceptLegacy: true }), loginRequiredStrictly, addActivity, validator.password, apiV3FormValidator, - async(req, res) => { + router.put( + '/password', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.PASSWORD], { + acceptLegacy: true, + }), + loginRequiredStrictly, + addActivity, + validator.password, + apiV3FormValidator, + async (req, res) => { const { body, user } = req; const { oldPassword, newPassword } = body; @@ -388,17 +436,18 @@ module.exports = (crowi) => { try { const userData = await user.updatePassword(newPassword); - const parameters = { action: SupportedAction.ACTION_USER_PASSWORD_UPDATE }; + const parameters = { + action: SupportedAction.ACTION_USER_PASSWORD_UPDATE, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ userData }); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err('update-password-failed'); } - - }); + }, + ); /** * @swagger @@ -421,23 +470,29 @@ module.exports = (crowi) => { * type: object * description: user data */ - router.put('/api-token', accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.API_TOKEN]), loginRequiredStrictly, addActivity, async(req, res) => { - const { user } = req; - - try { - const userData = await user.updateApiToken(); + router.put( + '/api-token', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.API_TOKEN]), + loginRequiredStrictly, + addActivity, + async (req, res) => { + const { user } = req; - const parameters = { action: SupportedAction.ACTION_USER_API_TOKEN_UPDATE }; - activityEvent.emit('update', res.locals.activity._id, parameters); + try { + const userData = await user.updateApiToken(); - return res.apiv3({ userData }); - } - catch (err) { - logger.error(err); - return res.apiv3Err('update-api-token-failed'); - } + const parameters = { + action: SupportedAction.ACTION_USER_API_TOKEN_UPDATE, + }; + activityEvent.emit('update', res.locals.activity._id, parameters); - }); + return res.apiv3({ userData }); + } catch (err) { + logger.error(err); + return res.apiv3Err('update-api-token-failed'); + } + }, + ); /** * @swagger @@ -458,7 +513,11 @@ module.exports = (crowi) => { * type: object * description: array of access tokens */ - router.get('/access-token', accessTokenParser([SCOPE.READ.USER_SETTINGS.API.ACCESS_TOKEN]), getAccessTokenHandlerFactory(crowi)); + router.get( + '/access-token', + accessTokenParser([SCOPE.READ.USER_SETTINGS.API.ACCESS_TOKEN]), + getAccessTokenHandlerFactory(crowi), + ); /** * @swagger @@ -493,7 +552,11 @@ module.exports = (crowi) => { * items: * type: string */ - router.post('/access-token', accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), generateAccessTokenHandlerFactory(crowi)); + router.post( + '/access-token', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), + generateAccessTokenHandlerFactory(crowi), + ); /** * @swagger @@ -508,7 +571,11 @@ module.exports = (crowi) => { * description: succeded to delete access token * */ - router.delete('/access-token', accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), deleteAccessTokenHandlersFactory(crowi)); + router.delete( + '/access-token', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), + deleteAccessTokenHandlersFactory(crowi), + ); /** * @swagger @@ -522,7 +589,11 @@ module.exports = (crowi) => { * 200: * description: succeded to delete all access tokens */ - router.delete('/access-token/all', accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), deleteAllAccessTokensHandlersFactory(crowi)); + router.delete( + '/access-token/all', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]), + deleteAllAccessTokensHandlersFactory(crowi), + ); /** * @swagger @@ -552,9 +623,14 @@ module.exports = (crowi) => { * type: object * description: Ldap account associate to me */ - router.put('/associate-ldap', accessTokenParser([SCOPE.WRITE.USER_SETTINGS.EXTERNAL_ACCOUNT]), loginRequiredStrictly, addActivity, - validator.associateLdap, apiV3FormValidator, - async(req, res) => { + router.put( + '/associate-ldap', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.EXTERNAL_ACCOUNT]), + loginRequiredStrictly, + addActivity, + validator.associateLdap, + apiV3FormValidator, + async (req, res) => { const { passportService } = crowi; const { user, body } = req; const { username } = body; @@ -566,19 +642,24 @@ module.exports = (crowi) => { try { await passport.authenticate('ldapauth'); - const associateUser = await ExternalAccount.associate('ldap', username, user); + const associateUser = await ExternalAccount.associate( + 'ldap', + username, + user, + ); - const parameters = { action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_ASSOCIATE }; + const parameters = { + action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_ASSOCIATE, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ associateUser }); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err('associate-ldap-account-failed'); } - - }); + }, + ); /** * @swagger @@ -605,9 +686,14 @@ module.exports = (crowi) => { * type: object * description: Ldap account disassociate to me */ - router.put('/disassociate-ldap', - accessTokenParser([SCOPE.WRITE.USER_SETTINGS.EXTERNAL_ACCOUNT]), loginRequiredStrictly, addActivity, validator.disassociateLdap, apiV3FormValidator, - async(req, res) => { + router.put( + '/disassociate-ldap', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.EXTERNAL_ACCOUNT]), + loginRequiredStrictly, + addActivity, + validator.disassociateLdap, + apiV3FormValidator, + async (req, res) => { const { user, body } = req; const { providerType, accountId } = body; @@ -623,17 +709,18 @@ module.exports = (crowi) => { user, }); - const parameters = { action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_DISCONNECT }; + const parameters = { + action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_DISCONNECT, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ disassociateUser }); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err('disassociate-ldap-account-failed'); } - - }); + }, + ); /** * @swagger @@ -666,37 +753,49 @@ module.exports = (crowi) => { * type: object * description: editor settings */ - router.put('/editor-settings', accessTokenParser([SCOPE.WRITE.USER_SETTINGS.OTHER]), loginRequiredStrictly, - addActivity, validator.editorSettings, apiV3FormValidator, - async(req, res) => { + router.put( + '/editor-settings', + accessTokenParser([SCOPE.WRITE.USER_SETTINGS.OTHER]), + loginRequiredStrictly, + addActivity, + validator.editorSettings, + apiV3FormValidator, + async (req, res) => { const query = { userId: req.user.id }; const { body } = req; - const { - theme, keymapMode, styleActiveLine, autoFormatMarkdownTable, - } = body; + const { theme, keymapMode, styleActiveLine, autoFormatMarkdownTable } = + body; const document = { - theme, keymapMode, styleActiveLine, autoFormatMarkdownTable, + theme, + keymapMode, + styleActiveLine, + autoFormatMarkdownTable, }; // Insert if document does not exist, and return new values // See: https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate const options = { upsert: true, new: true }; try { - const response = await EditorSettings.findOneAndUpdate(query, { $set: document }, options); - - const parameters = { action: SupportedAction.ACTION_USER_EDITOR_SETTINGS_UPDATE }; + const response = await EditorSettings.findOneAndUpdate( + query, + { $set: document }, + options, + ); + + const parameters = { + action: SupportedAction.ACTION_USER_EDITOR_SETTINGS_UPDATE, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3(response); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err('updating-editor-settings-failed'); } - }); - + }, + ); /** * @swagger @@ -715,17 +814,22 @@ module.exports = (crowi) => { * type: object * description: editor settings */ - router.get('/editor-settings', accessTokenParser([SCOPE.READ.USER_SETTINGS.OTHER]), loginRequiredStrictly, async(req, res) => { - try { - const query = { userId: req.user.id }; - const editorSettings = await EditorSettings.findOne(query) ?? new EditorSettings(); - return res.apiv3(editorSettings); - } - catch (err) { - logger.error(err); - return res.apiv3Err('getting-editor-settings-failed'); - } - }); + router.get( + '/editor-settings', + accessTokenParser([SCOPE.READ.USER_SETTINGS.OTHER]), + loginRequiredStrictly, + async (req, res) => { + try { + const query = { userId: req.user.id }; + const editorSettings = + (await EditorSettings.findOne(query)) ?? new EditorSettings(); + return res.apiv3(editorSettings); + } catch (err) { + logger.error(err); + return res.apiv3Err('getting-editor-settings-failed'); + } + }, + ); /** * @swagger @@ -758,9 +862,14 @@ module.exports = (crowi) => { * schema: * type: object */ - router.put('/in-app-notification-settings', + router.put( + '/in-app-notification-settings', accessTokenParser([SCOPE.WRITE.USER_SETTINGS.IN_APP_NOTIFICATION]), - loginRequiredStrictly, addActivity, validator.inAppNotificationSettings, apiV3FormValidator, async(req, res) => { + loginRequiredStrictly, + addActivity, + validator.inAppNotificationSettings, + apiV3FormValidator, + async (req, res) => { const query = { userId: req.user.id }; const subscribeRules = req.body.subscribeRules; @@ -770,18 +879,25 @@ module.exports = (crowi) => { const options = { upsert: true, new: true, runValidators: true }; try { - const response = await InAppNotificationSettings.findOneAndUpdate(query, { $set: { subscribeRules } }, options); - - const parameters = { action: SupportedAction.ACTION_USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE }; + const response = await InAppNotificationSettings.findOneAndUpdate( + query, + { $set: { subscribeRules } }, + options, + ); + + const parameters = { + action: + SupportedAction.ACTION_USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3(response); - } - catch (err) { + } catch (err) { logger.error(err); return res.apiv3Err('updating-in-app-notification-settings-failed'); } - }); + }, + ); /** * @swagger @@ -802,17 +918,21 @@ module.exports = (crowi) => { * type: object * description: InAppNotificationSettings */ - router.get('/in-app-notification-settings', accessTokenParser([SCOPE.READ.USER_SETTINGS.IN_APP_NOTIFICATION]), loginRequiredStrictly, async(req, res) => { - const query = { userId: req.user.id }; - try { - const response = await InAppNotificationSettings.findOne(query); - return res.apiv3(response); - } - catch (err) { - logger.error(err); - return res.apiv3Err('getting-in-app-notification-settings-failed'); - } - }); + router.get( + '/in-app-notification-settings', + accessTokenParser([SCOPE.READ.USER_SETTINGS.IN_APP_NOTIFICATION]), + loginRequiredStrictly, + async (req, res) => { + const query = { userId: req.user.id }; + try { + const response = await InAppNotificationSettings.findOne(query); + return res.apiv3(response); + } catch (err) { + logger.error(err); + return res.apiv3Err('getting-in-app-notification-settings-failed'); + } + }, + ); return router; }; diff --git a/apps/app/src/server/routes/apiv3/security-settings/checkSetupStrategiesHasAdmin.ts b/apps/app/src/server/routes/apiv3/security-settings/checkSetupStrategiesHasAdmin.ts index 6492784675e..9fd52723e17 100644 --- a/apps/app/src/server/routes/apiv3/security-settings/checkSetupStrategiesHasAdmin.ts +++ b/apps/app/src/server/routes/apiv3/security-settings/checkSetupStrategiesHasAdmin.ts @@ -6,7 +6,7 @@ interface AggregateResult { count: number; } -const checkLocalStrategyHasAdmin = async(): Promise => { +const checkLocalStrategyHasAdmin = async (): Promise => { const User = mongoose.model('User') as any; const localAdmins: AggregateResult[] = await User.aggregate([ @@ -23,7 +23,9 @@ const checkLocalStrategyHasAdmin = async(): Promise => { return localAdmins.length > 0 && localAdmins[0].count > 0; }; -const checkExternalStrategiesHasAdmin = async(setupExternalStrategies: IExternalAuthProviderType[]): Promise => { +const checkExternalStrategiesHasAdmin = async ( + setupExternalStrategies: IExternalAuthProviderType[], +): Promise => { const User = mongoose.model('User') as any; const externalAdmins: AggregateResult[] = await User.aggregate([ @@ -47,7 +49,9 @@ const checkExternalStrategiesHasAdmin = async(setupExternalStrategies: IExternal return externalAdmins.length > 0 && externalAdmins[0].count > 0; }; -export const checkSetupStrategiesHasAdmin = async(setupStrategies: (IExternalAuthProviderType | 'local')[]): Promise => { +export const checkSetupStrategiesHasAdmin = async ( + setupStrategies: (IExternalAuthProviderType | 'local')[], +): Promise => { if (setupStrategies.includes('local')) { const isLocalStrategyHasAdmin = await checkLocalStrategyHasAdmin(); if (isLocalStrategyHasAdmin) { @@ -55,12 +59,16 @@ export const checkSetupStrategiesHasAdmin = async(setupStrategies: (IExternalAut } } - const setupExternalStrategies = setupStrategies.filter(strategy => strategy !== 'local') as IExternalAuthProviderType[]; + const setupExternalStrategies = setupStrategies.filter( + (strategy) => strategy !== 'local', + ) as IExternalAuthProviderType[]; if (setupExternalStrategies.length === 0) { return false; } - const isExternalStrategiesHasAdmin = await checkExternalStrategiesHasAdmin(setupExternalStrategies); + const isExternalStrategiesHasAdmin = await checkExternalStrategiesHasAdmin( + setupExternalStrategies, + ); return isExternalStrategiesHasAdmin; }; diff --git a/apps/app/src/server/routes/apiv3/security-settings/index.js b/apps/app/src/server/routes/apiv3/security-settings/index.js index e6c0d6e0b52..2da166318d5 100644 --- a/apps/app/src/server/routes/apiv3/security-settings/index.js +++ b/apps/app/src/server/routes/apiv3/security-settings/index.js @@ -1,8 +1,11 @@ -import { ConfigSource, toNonBlankStringOrUndefined, SCOPE } from '@growi/core/dist/interfaces'; +import { + ConfigSource, + SCOPE, + toNonBlankStringOrUndefined, +} from '@growi/core/dist/interfaces'; import { ErrorV3 } from '@growi/core/dist/models'; import xss from 'xss'; - import { SupportedAction } from '~/interfaces/activity'; import { PageDeleteConfigValue } from '~/interfaces/page-delete-config'; import { accessTokenParser } from '~/server/middlewares/access-token-parser'; @@ -12,11 +15,13 @@ import ShareLink from '~/server/models/share-link'; import { configManager } from '~/server/service/config-manager'; import { getTranslation } from '~/server/service/i18next'; import loggerFactory from '~/utils/logger'; -import { validateDeleteConfigs, prepareDeleteConfigValuesForCalc } from '~/utils/page-delete-config'; +import { + prepareDeleteConfigValuesForCalc, + validateDeleteConfigs, +} from '~/utils/page-delete-config'; import { checkSetupStrategiesHasAdmin } from './checkSetupStrategiesHasAdmin'; - const logger = loggerFactory('growi:routes:apiv3:security-setting'); const express = require('express'); @@ -28,91 +33,199 @@ const { body } = require('express-validator'); const validator = { generalSetting: [ body('sessionMaxAge').optional({ checkFalsy: true }).trim().isInt(), - body('restrictGuestMode').if(value => value != null).isString().isIn([ - 'Deny', 'Readonly', - ]), - body('pageCompleteDeletionAuthority').if(value => value != null).isString().isIn(Object.values(PageDeleteConfigValue)), - body('hideRestrictedByOwner').if(value => value != null).isBoolean(), - body('hideRestrictedByGroup').if(value => value != null).isBoolean(), - body('isUsersHomepageDeletionEnabled').if(value => value != null).isBoolean(), - body('isForceDeleteUserHomepageOnUserDeletion').if(value => value != null).isBoolean(), + body('restrictGuestMode') + .if((value) => value != null) + .isString() + .isIn(['Deny', 'Readonly']), + body('pageCompleteDeletionAuthority') + .if((value) => value != null) + .isString() + .isIn(Object.values(PageDeleteConfigValue)), + body('hideRestrictedByOwner') + .if((value) => value != null) + .isBoolean(), + body('hideRestrictedByGroup') + .if((value) => value != null) + .isBoolean(), + body('isUsersHomepageDeletionEnabled') + .if((value) => value != null) + .isBoolean(), + body('isForceDeleteUserHomepageOnUserDeletion') + .if((value) => value != null) + .isBoolean(), ], shareLinkSetting: [ - body('disableLinkSharing').if(value => value != null).isBoolean(), + body('disableLinkSharing') + .if((value) => value != null) + .isBoolean(), ], authenticationSetting: [ - body('isEnabled').if(value => value != null).isBoolean(), - body('authId').isString().isIn([ - 'local', 'ldap', 'saml', 'oidc', 'google', 'github', - ]), + body('isEnabled') + .if((value) => value != null) + .isBoolean(), + body('authId') + .isString() + .isIn(['local', 'ldap', 'saml', 'oidc', 'google', 'github']), ], localSetting: [ - body('registrationMode').isString().isIn([ - 'Open', 'Restricted', 'Closed', - ]), - body('registrationWhitelist').if(value => value != null).isArray().customSanitizer((value, { req }) => { - return value.filter(email => email !== ''); - }), + body('registrationMode').isString().isIn(['Open', 'Restricted', 'Closed']), + body('registrationWhitelist') + .if((value) => value != null) + .isArray() + .customSanitizer((value, { req }) => { + return value.filter((email) => email !== ''); + }), ], ldapAuth: [ - body('serverUrl').if(value => value != null).isString(), - body('isUserBind').if(value => value != null).isBoolean(), - body('ldapBindDN').if(value => value != null).isString(), - body('ldapBindDNPassword').if(value => value != null).isString(), - body('ldapSearchFilter').if(value => value != null).isString(), - body('ldapAttrMapUsername').if(value => value != null).isString(), - body('isSameUsernameTreatedAsIdenticalUser').if(value => value != null).isBoolean(), - body('ldapAttrMapMail').if(value => value != null).isString(), - body('ldapAttrMapName').if(value => value != null).isString(), - body('ldapGroupSearchBase').if(value => value != null).isString(), - body('ldapGroupSearchFilter').if(value => value != null).isString(), - body('ldapGroupDnProperty').if(value => value != null).isString(), + body('serverUrl') + .if((value) => value != null) + .isString(), + body('isUserBind') + .if((value) => value != null) + .isBoolean(), + body('ldapBindDN') + .if((value) => value != null) + .isString(), + body('ldapBindDNPassword') + .if((value) => value != null) + .isString(), + body('ldapSearchFilter') + .if((value) => value != null) + .isString(), + body('ldapAttrMapUsername') + .if((value) => value != null) + .isString(), + body('isSameUsernameTreatedAsIdenticalUser') + .if((value) => value != null) + .isBoolean(), + body('ldapAttrMapMail') + .if((value) => value != null) + .isString(), + body('ldapAttrMapName') + .if((value) => value != null) + .isString(), + body('ldapGroupSearchBase') + .if((value) => value != null) + .isString(), + body('ldapGroupSearchFilter') + .if((value) => value != null) + .isString(), + body('ldapGroupDnProperty') + .if((value) => value != null) + .isString(), ], samlAuth: [ - body('entryPoint').if(value => value != null).isString(), - body('issuer').if(value => value != null).isString(), - body('cert').if(value => value != null).isString(), - body('attrMapId').if(value => value != null).isString(), - body('attrMapUsername').if(value => value != null).isString(), - body('attrMapMail').if(value => value != null).isString(), - body('attrMapFirstName').if(value => value != null).isString(), - body('attrMapLastName').if(value => value != null).isString(), - body('isSameUsernameTreatedAsIdenticalUser').if(value => value != null).isBoolean(), - body('isSameEmailTreatedAsIdenticalUser').if(value => value != null).isBoolean(), - body('ABLCRule').if(value => value != null).isString(), + body('entryPoint') + .if((value) => value != null) + .isString(), + body('issuer') + .if((value) => value != null) + .isString(), + body('cert') + .if((value) => value != null) + .isString(), + body('attrMapId') + .if((value) => value != null) + .isString(), + body('attrMapUsername') + .if((value) => value != null) + .isString(), + body('attrMapMail') + .if((value) => value != null) + .isString(), + body('attrMapFirstName') + .if((value) => value != null) + .isString(), + body('attrMapLastName') + .if((value) => value != null) + .isString(), + body('isSameUsernameTreatedAsIdenticalUser') + .if((value) => value != null) + .isBoolean(), + body('isSameEmailTreatedAsIdenticalUser') + .if((value) => value != null) + .isBoolean(), + body('ABLCRule') + .if((value) => value != null) + .isString(), ], oidcAuth: [ - body('oidcProviderName').if(value => value != null).isString(), - body('oidcIssuerHost').if(value => value != null).isString(), - body('oidcAuthorizationEndpoint').if(value => value != null).isString(), - body('oidcTokenEndpoint').if(value => value != null).isString(), - body('oidcRevocationEndpoint').if(value => value != null).isString(), - body('oidcIntrospectionEndpoint').if(value => value != null).isString(), - body('oidcUserInfoEndpoint').if(value => value != null).isString(), - body('oidcEndSessionEndpoint').if(value => value != null).isString(), - body('oidcRegistrationEndpoint').if(value => value != null).isString(), - body('oidcJWKSUri').if(value => value != null).isString(), - body('oidcClientId').if(value => value != null).isString(), - body('oidcClientSecret').if(value => value != null).isString(), - body('oidcAttrMapId').if(value => value != null).isString(), - body('oidcAttrMapUserName').if(value => value != null).isString(), - body('oidcAttrMapEmail').if(value => value != null).isString(), - body('isSameUsernameTreatedAsIdenticalUser').if(value => value != null).isBoolean(), - body('isSameEmailTreatedAsIdenticalUser').if(value => value != null).isBoolean(), + body('oidcProviderName') + .if((value) => value != null) + .isString(), + body('oidcIssuerHost') + .if((value) => value != null) + .isString(), + body('oidcAuthorizationEndpoint') + .if((value) => value != null) + .isString(), + body('oidcTokenEndpoint') + .if((value) => value != null) + .isString(), + body('oidcRevocationEndpoint') + .if((value) => value != null) + .isString(), + body('oidcIntrospectionEndpoint') + .if((value) => value != null) + .isString(), + body('oidcUserInfoEndpoint') + .if((value) => value != null) + .isString(), + body('oidcEndSessionEndpoint') + .if((value) => value != null) + .isString(), + body('oidcRegistrationEndpoint') + .if((value) => value != null) + .isString(), + body('oidcJWKSUri') + .if((value) => value != null) + .isString(), + body('oidcClientId') + .if((value) => value != null) + .isString(), + body('oidcClientSecret') + .if((value) => value != null) + .isString(), + body('oidcAttrMapId') + .if((value) => value != null) + .isString(), + body('oidcAttrMapUserName') + .if((value) => value != null) + .isString(), + body('oidcAttrMapEmail') + .if((value) => value != null) + .isString(), + body('isSameUsernameTreatedAsIdenticalUser') + .if((value) => value != null) + .isBoolean(), + body('isSameEmailTreatedAsIdenticalUser') + .if((value) => value != null) + .isBoolean(), ], googleOAuth: [ - body('googleClientId').if(value => value != null).isString(), - body('googleClientSecret').if(value => value != null).isString(), - body('isSameUsernameTreatedAsIdenticalUser').if(value => value != null).isBoolean(), + body('googleClientId') + .if((value) => value != null) + .isString(), + body('googleClientSecret') + .if((value) => value != null) + .isString(), + body('isSameUsernameTreatedAsIdenticalUser') + .if((value) => value != null) + .isBoolean(), ], githubOAuth: [ - body('githubClientId').if(value => value != null).isString(), - body('githubClientSecret').if(value => value != null).isString(), - body('isSameUsernameTreatedAsIdenticalUser').if(value => value != null).isBoolean(), + body('githubClientId') + .if((value) => value != null) + .isString(), + body('githubClientSecret') + .if((value) => value != null) + .isString(), + body('isSameUsernameTreatedAsIdenticalUser') + .if((value) => value != null) + .isBoolean(), ], }; - /** * @swagger * @@ -403,17 +516,26 @@ const validator = { */ /** @param {import('~/server/crowi').default} crowi Crowi instance */ module.exports = (crowi) => { - const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); + const loginRequiredStrictly = require('~/server/middlewares/login-required')( + crowi, + ); const adminRequired = require('~/server/middlewares/admin-required')(crowi); const addActivity = generateAddActivityMiddleware(crowi); const activityEvent = crowi.event('activity'); - async function updateAndReloadStrategySettings(authId, params, opts = { removeIfUndefined: false }) { + async function updateAndReloadStrategySettings( + authId, + params, + opts = { removeIfUndefined: false }, + ) { const { passportService } = crowi; // update config without publishing S2sMessage - await configManager.updateConfigs(params, { skipPubsub: true, removeIfUndefined: opts.removeIfUndefined }); + await configManager.updateConfigs(params, { + skipPubsub: true, + removeIfUndefined: opts.removeIfUndefined, + }); await passportService.setupStrategyById(authId); passportService.publishUpdatedMessage(authId); @@ -456,115 +578,300 @@ module.exports = (crowi) => { * githubOAuth: * $ref: '#/components/schemas/GitHubOAuthSetting' */ - router.get('/', accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, async(req, res) => { - - const securityParams = { - generalSetting: { - restrictGuestMode: crowi.aclService.getGuestModeValue(), - pageDeletionAuthority: await configManager.getConfig('security:pageDeletionAuthority'), - pageCompleteDeletionAuthority: await configManager.getConfig('security:pageCompleteDeletionAuthority'), - pageRecursiveDeletionAuthority: await configManager.getConfig('security:pageRecursiveDeletionAuthority'), - pageRecursiveCompleteDeletionAuthority: await configManager.getConfig('security:pageRecursiveCompleteDeletionAuthority'), - isAllGroupMembershipRequiredForPageCompleteDeletion: - await configManager.getConfig('security:isAllGroupMembershipRequiredForPageCompleteDeletion'), - hideRestrictedByOwner: await configManager.getConfig('security:list-policy:hideRestrictedByOwner'), - hideRestrictedByGroup: await configManager.getConfig('security:list-policy:hideRestrictedByGroup'), - isUsersHomepageDeletionEnabled: await configManager.getConfig('security:user-homepage-deletion:isEnabled'), - isForceDeleteUserHomepageOnUserDeletion: - await configManager.getConfig('security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'), - isRomUserAllowedToComment: await configManager.getConfig('security:isRomUserAllowedToComment'), - wikiMode: await configManager.getConfig('security:wikiMode'), - sessionMaxAge: await configManager.getConfig('security:sessionMaxAge'), - }, - shareLinkSetting: { - disableLinkSharing: await configManager.getConfig('security:disableLinkSharing'), - }, - localSetting: { - useOnlyEnvVarsForSomeOptions: await configManager.getConfig('env:useOnlyEnvVars:security:passport-local'), - registrationMode: await configManager.getConfig('security:registrationMode'), - registrationWhitelist: await configManager.getConfig('security:registrationWhitelist'), - isPasswordResetEnabled: await configManager.getConfig('security:passport-local:isPasswordResetEnabled'), - isEmailAuthenticationEnabled: await configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled'), - }, - generalAuth: { - isLocalEnabled: await configManager.getConfig('security:passport-local:isEnabled'), - isLdapEnabled: await configManager.getConfig('security:passport-ldap:isEnabled'), - isSamlEnabled: await configManager.getConfig('security:passport-saml:isEnabled'), - isOidcEnabled: await configManager.getConfig('security:passport-oidc:isEnabled'), - isGoogleEnabled: await configManager.getConfig('security:passport-google:isEnabled'), - isGitHubEnabled: await configManager.getConfig('security:passport-github:isEnabled'), - }, - ldapAuth: { - serverUrl: await configManager.getConfig('security:passport-ldap:serverUrl'), - isUserBind: await configManager.getConfig('security:passport-ldap:isUserBind'), - ldapBindDN: await configManager.getConfig('security:passport-ldap:bindDN'), - ldapBindDNPassword: await configManager.getConfig('security:passport-ldap:bindDNPassword'), - ldapSearchFilter: await configManager.getConfig('security:passport-ldap:searchFilter'), - ldapAttrMapUsername: await configManager.getConfig('security:passport-ldap:attrMapUsername'), - isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'), - ldapAttrMapMail: await configManager.getConfig('security:passport-ldap:attrMapMail'), - ldapAttrMapName: await configManager.getConfig('security:passport-ldap:attrMapName'), - ldapGroupSearchBase: await configManager.getConfig('security:passport-ldap:groupSearchBase'), - ldapGroupSearchFilter: await configManager.getConfig('security:passport-ldap:groupSearchFilter'), - ldapGroupDnProperty: await configManager.getConfig('security:passport-ldap:groupDnProperty'), - }, - samlAuth: { - missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(), - useOnlyEnvVarsForSomeOptions: await configManager.getConfig('env:useOnlyEnvVars:security:passport-saml', ConfigSource.env), - samlEntryPoint: await configManager.getConfig('security:passport-saml:entryPoint', ConfigSource.db), - samlEnvVarEntryPoint: await configManager.getConfig('security:passport-saml:entryPoint', ConfigSource.env), - samlIssuer: await configManager.getConfig('security:passport-saml:issuer', ConfigSource.db), - samlEnvVarIssuer: await configManager.getConfig('security:passport-saml:issuer', ConfigSource.env), - samlCert: await configManager.getConfig('security:passport-saml:cert', ConfigSource.db), - samlEnvVarCert: await configManager.getConfig('security:passport-saml:cert', ConfigSource.env), - samlAttrMapId: await configManager.getConfig('security:passport-saml:attrMapId', ConfigSource.db), - samlEnvVarAttrMapId: await configManager.getConfig('security:passport-saml:attrMapId', ConfigSource.env), - samlAttrMapUsername: await configManager.getConfig('security:passport-saml:attrMapUsername', ConfigSource.db), - samlEnvVarAttrMapUsername: await configManager.getConfig('security:passport-saml:attrMapUsername', ConfigSource.env), - samlAttrMapMail: await configManager.getConfig('security:passport-saml:attrMapMail', ConfigSource.db), - samlEnvVarAttrMapMail: await configManager.getConfig('security:passport-saml:attrMapMail', ConfigSource.env), - samlAttrMapFirstName: await configManager.getConfig('security:passport-saml:attrMapFirstName', ConfigSource.db), - samlEnvVarAttrMapFirstName: await configManager.getConfig('security:passport-saml:attrMapFirstName', ConfigSource.env), - samlAttrMapLastName: await configManager.getConfig('security:passport-saml:attrMapLastName', ConfigSource.db), - samlEnvVarAttrMapLastName: await configManager.getConfig('security:passport-saml:attrMapLastName', ConfigSource.env), - isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameUsernameTreatedAsIdenticalUser'), - isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameEmailTreatedAsIdenticalUser'), - samlABLCRule: await configManager.getConfig('security:passport-saml:ABLCRule', ConfigSource.db), - samlEnvVarABLCRule: await configManager.getConfig('security:passport-saml:ABLCRule', ConfigSource.env), - }, - oidcAuth: { - oidcProviderName: await configManager.getConfig('security:passport-oidc:providerName'), - oidcIssuerHost: await configManager.getConfig('security:passport-oidc:issuerHost'), - oidcAuthorizationEndpoint: await configManager.getConfig('security:passport-oidc:authorizationEndpoint'), - oidcTokenEndpoint: await configManager.getConfig('security:passport-oidc:tokenEndpoint'), - oidcRevocationEndpoint: await configManager.getConfig('security:passport-oidc:revocationEndpoint'), - oidcIntrospectionEndpoint: await configManager.getConfig('security:passport-oidc:introspectionEndpoint'), - oidcUserInfoEndpoint: await configManager.getConfig('security:passport-oidc:userInfoEndpoint'), - oidcEndSessionEndpoint: await configManager.getConfig('security:passport-oidc:endSessionEndpoint'), - oidcRegistrationEndpoint: await configManager.getConfig('security:passport-oidc:registrationEndpoint'), - oidcJWKSUri: await configManager.getConfig('security:passport-oidc:jwksUri'), - oidcClientId: await configManager.getConfig('security:passport-oidc:clientId'), - oidcClientSecret: await configManager.getConfig('security:passport-oidc:clientSecret'), - oidcAttrMapId: await configManager.getConfig('security:passport-oidc:attrMapId'), - oidcAttrMapUserName: await configManager.getConfig('security:passport-oidc:attrMapUserName'), - oidcAttrMapName: await configManager.getConfig('security:passport-oidc:attrMapName'), - oidcAttrMapEmail: await configManager.getConfig('security:passport-oidc:attrMapMail'), - isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'), - isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameEmailTreatedAsIdenticalUser'), - }, - googleOAuth: { - googleClientId: await configManager.getConfig('security:passport-google:clientId'), - googleClientSecret: await configManager.getConfig('security:passport-google:clientSecret'), - isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-google:isSameEmailTreatedAsIdenticalUser'), - }, - githubOAuth: { - githubClientId: await configManager.getConfig('security:passport-github:clientId'), - githubClientSecret: await configManager.getConfig('security:passport-github:clientSecret'), - isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-github:isSameUsernameTreatedAsIdenticalUser'), - }, - }; - return res.apiv3({ securityParams }); - }); + router.get( + '/', + accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + async (req, res) => { + const securityParams = { + generalSetting: { + restrictGuestMode: crowi.aclService.getGuestModeValue(), + pageDeletionAuthority: await configManager.getConfig( + 'security:pageDeletionAuthority', + ), + pageCompleteDeletionAuthority: await configManager.getConfig( + 'security:pageCompleteDeletionAuthority', + ), + pageRecursiveDeletionAuthority: await configManager.getConfig( + 'security:pageRecursiveDeletionAuthority', + ), + pageRecursiveCompleteDeletionAuthority: await configManager.getConfig( + 'security:pageRecursiveCompleteDeletionAuthority', + ), + isAllGroupMembershipRequiredForPageCompleteDeletion: + await configManager.getConfig( + 'security:isAllGroupMembershipRequiredForPageCompleteDeletion', + ), + hideRestrictedByOwner: await configManager.getConfig( + 'security:list-policy:hideRestrictedByOwner', + ), + hideRestrictedByGroup: await configManager.getConfig( + 'security:list-policy:hideRestrictedByGroup', + ), + isUsersHomepageDeletionEnabled: await configManager.getConfig( + 'security:user-homepage-deletion:isEnabled', + ), + isForceDeleteUserHomepageOnUserDeletion: + await configManager.getConfig( + 'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion', + ), + isRomUserAllowedToComment: await configManager.getConfig( + 'security:isRomUserAllowedToComment', + ), + wikiMode: await configManager.getConfig('security:wikiMode'), + sessionMaxAge: await configManager.getConfig( + 'security:sessionMaxAge', + ), + }, + shareLinkSetting: { + disableLinkSharing: await configManager.getConfig( + 'security:disableLinkSharing', + ), + }, + localSetting: { + useOnlyEnvVarsForSomeOptions: await configManager.getConfig( + 'env:useOnlyEnvVars:security:passport-local', + ), + registrationMode: await configManager.getConfig( + 'security:registrationMode', + ), + registrationWhitelist: await configManager.getConfig( + 'security:registrationWhitelist', + ), + isPasswordResetEnabled: await configManager.getConfig( + 'security:passport-local:isPasswordResetEnabled', + ), + isEmailAuthenticationEnabled: await configManager.getConfig( + 'security:passport-local:isEmailAuthenticationEnabled', + ), + }, + generalAuth: { + isLocalEnabled: await configManager.getConfig( + 'security:passport-local:isEnabled', + ), + isLdapEnabled: await configManager.getConfig( + 'security:passport-ldap:isEnabled', + ), + isSamlEnabled: await configManager.getConfig( + 'security:passport-saml:isEnabled', + ), + isOidcEnabled: await configManager.getConfig( + 'security:passport-oidc:isEnabled', + ), + isGoogleEnabled: await configManager.getConfig( + 'security:passport-google:isEnabled', + ), + isGitHubEnabled: await configManager.getConfig( + 'security:passport-github:isEnabled', + ), + }, + ldapAuth: { + serverUrl: await configManager.getConfig( + 'security:passport-ldap:serverUrl', + ), + isUserBind: await configManager.getConfig( + 'security:passport-ldap:isUserBind', + ), + ldapBindDN: await configManager.getConfig( + 'security:passport-ldap:bindDN', + ), + ldapBindDNPassword: await configManager.getConfig( + 'security:passport-ldap:bindDNPassword', + ), + ldapSearchFilter: await configManager.getConfig( + 'security:passport-ldap:searchFilter', + ), + ldapAttrMapUsername: await configManager.getConfig( + 'security:passport-ldap:attrMapUsername', + ), + isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser', + ), + ldapAttrMapMail: await configManager.getConfig( + 'security:passport-ldap:attrMapMail', + ), + ldapAttrMapName: await configManager.getConfig( + 'security:passport-ldap:attrMapName', + ), + ldapGroupSearchBase: await configManager.getConfig( + 'security:passport-ldap:groupSearchBase', + ), + ldapGroupSearchFilter: await configManager.getConfig( + 'security:passport-ldap:groupSearchFilter', + ), + ldapGroupDnProperty: await configManager.getConfig( + 'security:passport-ldap:groupDnProperty', + ), + }, + samlAuth: { + missingMandatoryConfigKeys: + await crowi.passportService.getSamlMissingMandatoryConfigKeys(), + useOnlyEnvVarsForSomeOptions: await configManager.getConfig( + 'env:useOnlyEnvVars:security:passport-saml', + ConfigSource.env, + ), + samlEntryPoint: await configManager.getConfig( + 'security:passport-saml:entryPoint', + ConfigSource.db, + ), + samlEnvVarEntryPoint: await configManager.getConfig( + 'security:passport-saml:entryPoint', + ConfigSource.env, + ), + samlIssuer: await configManager.getConfig( + 'security:passport-saml:issuer', + ConfigSource.db, + ), + samlEnvVarIssuer: await configManager.getConfig( + 'security:passport-saml:issuer', + ConfigSource.env, + ), + samlCert: await configManager.getConfig( + 'security:passport-saml:cert', + ConfigSource.db, + ), + samlEnvVarCert: await configManager.getConfig( + 'security:passport-saml:cert', + ConfigSource.env, + ), + samlAttrMapId: await configManager.getConfig( + 'security:passport-saml:attrMapId', + ConfigSource.db, + ), + samlEnvVarAttrMapId: await configManager.getConfig( + 'security:passport-saml:attrMapId', + ConfigSource.env, + ), + samlAttrMapUsername: await configManager.getConfig( + 'security:passport-saml:attrMapUsername', + ConfigSource.db, + ), + samlEnvVarAttrMapUsername: await configManager.getConfig( + 'security:passport-saml:attrMapUsername', + ConfigSource.env, + ), + samlAttrMapMail: await configManager.getConfig( + 'security:passport-saml:attrMapMail', + ConfigSource.db, + ), + samlEnvVarAttrMapMail: await configManager.getConfig( + 'security:passport-saml:attrMapMail', + ConfigSource.env, + ), + samlAttrMapFirstName: await configManager.getConfig( + 'security:passport-saml:attrMapFirstName', + ConfigSource.db, + ), + samlEnvVarAttrMapFirstName: await configManager.getConfig( + 'security:passport-saml:attrMapFirstName', + ConfigSource.env, + ), + samlAttrMapLastName: await configManager.getConfig( + 'security:passport-saml:attrMapLastName', + ConfigSource.db, + ), + samlEnvVarAttrMapLastName: await configManager.getConfig( + 'security:passport-saml:attrMapLastName', + ConfigSource.env, + ), + isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser', + ), + isSameEmailTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-saml:isSameEmailTreatedAsIdenticalUser', + ), + samlABLCRule: await configManager.getConfig( + 'security:passport-saml:ABLCRule', + ConfigSource.db, + ), + samlEnvVarABLCRule: await configManager.getConfig( + 'security:passport-saml:ABLCRule', + ConfigSource.env, + ), + }, + oidcAuth: { + oidcProviderName: await configManager.getConfig( + 'security:passport-oidc:providerName', + ), + oidcIssuerHost: await configManager.getConfig( + 'security:passport-oidc:issuerHost', + ), + oidcAuthorizationEndpoint: await configManager.getConfig( + 'security:passport-oidc:authorizationEndpoint', + ), + oidcTokenEndpoint: await configManager.getConfig( + 'security:passport-oidc:tokenEndpoint', + ), + oidcRevocationEndpoint: await configManager.getConfig( + 'security:passport-oidc:revocationEndpoint', + ), + oidcIntrospectionEndpoint: await configManager.getConfig( + 'security:passport-oidc:introspectionEndpoint', + ), + oidcUserInfoEndpoint: await configManager.getConfig( + 'security:passport-oidc:userInfoEndpoint', + ), + oidcEndSessionEndpoint: await configManager.getConfig( + 'security:passport-oidc:endSessionEndpoint', + ), + oidcRegistrationEndpoint: await configManager.getConfig( + 'security:passport-oidc:registrationEndpoint', + ), + oidcJWKSUri: await configManager.getConfig( + 'security:passport-oidc:jwksUri', + ), + oidcClientId: await configManager.getConfig( + 'security:passport-oidc:clientId', + ), + oidcClientSecret: await configManager.getConfig( + 'security:passport-oidc:clientSecret', + ), + oidcAttrMapId: await configManager.getConfig( + 'security:passport-oidc:attrMapId', + ), + oidcAttrMapUserName: await configManager.getConfig( + 'security:passport-oidc:attrMapUserName', + ), + oidcAttrMapName: await configManager.getConfig( + 'security:passport-oidc:attrMapName', + ), + oidcAttrMapEmail: await configManager.getConfig( + 'security:passport-oidc:attrMapMail', + ), + isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser', + ), + isSameEmailTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-oidc:isSameEmailTreatedAsIdenticalUser', + ), + }, + googleOAuth: { + googleClientId: await configManager.getConfig( + 'security:passport-google:clientId', + ), + googleClientSecret: await configManager.getConfig( + 'security:passport-google:clientSecret', + ), + isSameEmailTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-google:isSameEmailTreatedAsIdenticalUser', + ), + }, + githubOAuth: { + githubClientId: await configManager.getConfig( + 'security:passport-github:clientId', + ), + githubClientSecret: await configManager.getConfig( + 'security:passport-github:clientSecret', + ), + isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-github:isSameUsernameTreatedAsIdenticalUser', + ), + }, + }; + return res.apiv3({ securityParams }); + }, + ); /** * @swagger @@ -594,92 +901,118 @@ module.exports = (crowi) => { * description: updated param */ // eslint-disable-next-line max-len - router.put('/authentication/enabled', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, validator.authenticationSetting, apiV3FormValidator, async(req, res) => { - const { isEnabled, authId } = req.body; + router.put( + '/authentication/enabled', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.authenticationSetting, + apiV3FormValidator, + async (req, res) => { + const { isEnabled, authId } = req.body; - let setupStrategies = await crowi.passportService.getSetupStrategies(); + let setupStrategies = await crowi.passportService.getSetupStrategies(); - const parameters = {}; + const parameters = {}; - // Reflect request param - setupStrategies = setupStrategies.filter(strategy => strategy !== authId); + // Reflect request param + setupStrategies = setupStrategies.filter( + (strategy) => strategy !== authId, + ); - if (setupStrategies.length === 0) { - return res.apiv3Err(new ErrorV3('Can not turn everything off'), 405); - } + if (setupStrategies.length === 0) { + return res.apiv3Err(new ErrorV3('Can not turn everything off'), 405); + } - if (!isEnabled) { - const isSetupStrategiesHasAdmin = await checkSetupStrategiesHasAdmin(setupStrategies); + if (!isEnabled) { + const isSetupStrategiesHasAdmin = + await checkSetupStrategiesHasAdmin(setupStrategies); - // Return an error when disabling an strategy when there are no setup strategies with admin-enabled login - if (!isSetupStrategiesHasAdmin) { - return res.apiv3Err(new ErrorV3('Must have admin enabled authentication method'), 405); + // Return an error when disabling an strategy when there are no setup strategies with admin-enabled login + if (!isSetupStrategiesHasAdmin) { + return res.apiv3Err( + new ErrorV3('Must have admin enabled authentication method'), + 405, + ); + } } - } - const enableParams = { [`security:passport-${authId}:isEnabled`]: isEnabled }; + const enableParams = { + [`security:passport-${authId}:isEnabled`]: isEnabled, + }; - try { - await updateAndReloadStrategySettings(authId, enableParams); + try { + await updateAndReloadStrategySettings(authId, enableParams); - const responseParams = { - [`security:passport-${authId}:isEnabled`]: await configManager.getConfig(`security:passport-${authId}:isEnabled`), - }; - switch (authId) { - case 'local': - if (isEnabled) { - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_ENABLED; + const responseParams = { + [`security:passport-${authId}:isEnabled`]: + await configManager.getConfig( + `security:passport-${authId}:isEnabled`, + ), + }; + switch (authId) { + case 'local': + if (isEnabled) { + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_ENABLED; + break; + } + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_DISABLED; break; - } - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_DISABLED; - break; - case 'ldap': - if (isEnabled) { - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_LDAP_ENABLED; + case 'ldap': + if (isEnabled) { + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_LDAP_ENABLED; + break; + } + parameters.action = SupportedAction.ACTION_ADMIN_AUTH_LDAP_DISABLED; break; - } - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_LDAP_DISABLED; - break; - case 'saml': - if (isEnabled) { - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_SAML_ENABLED; + case 'saml': + if (isEnabled) { + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_SAML_ENABLED; + break; + } + parameters.action = SupportedAction.ACTION_ADMIN_AUTH_SAML_DISABLED; break; - } - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_SAML_DISABLED; - break; - case 'oidc': - if (isEnabled) { - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_OIDC_ENABLED; + case 'oidc': + if (isEnabled) { + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_OIDC_ENABLED; + break; + } + parameters.action = SupportedAction.ACTION_ADMIN_AUTH_OIDC_DISABLED; break; - } - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_OIDC_DISABLED; - break; - case 'google': - if (isEnabled) { - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_ENABLED; + case 'google': + if (isEnabled) { + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_ENABLED; + break; + } + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_DISABLED; break; - } - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_DISABLED; - break; - case 'github': - if (isEnabled) { - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_GITHUB_ENABLED; + case 'github': + if (isEnabled) { + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_GITHUB_ENABLED; + break; + } + parameters.action = + SupportedAction.ACTION_ADMIN_AUTH_GITHUB_DISABLED; break; - } - parameters.action = SupportedAction.ACTION_ADMIN_AUTH_GITHUB_DISABLED; - break; + } + activityEvent.emit('update', res.locals.activity._id, parameters); + return res.apiv3({ responseParams }); + } catch (err) { + const msg = 'Error occurred in updating enable setting'; + logger.error('Error', err); + return res.apiv3Err(new ErrorV3(msg, 'update-enable-setting failed')); } - activityEvent.emit('update', res.locals.activity._id, parameters); - return res.apiv3({ responseParams }); - } - catch (err) { - const msg = 'Error occurred in updating enable setting'; - logger.error('Error', err); - return res.apiv3Err(new ErrorV3(msg, 'update-enable-setting failed')); - } - - - }); + }, + ); /** * @swagger @@ -706,11 +1039,17 @@ module.exports = (crowi) => { * description: setup strategie * example: ["local"] */ - router.get('/authentication/', accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, async(req, res) => { - const setupStrategies = await crowi.passportService.getSetupStrategies(); + router.get( + '/authentication/', + accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + async (req, res) => { + const setupStrategies = await crowi.passportService.getSetupStrategies(); - return res.apiv3({ setupStrategies }); - }); + return res.apiv3({ setupStrategies }); + }, + ); /** * @swagger @@ -736,72 +1075,129 @@ module.exports = (crowi) => { * schema: * $ref: '#/components/schemas/GeneralSetting' */ - router.put('/general-setting', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, - validator.generalSetting, apiV3FormValidator, - async(req, res) => { + router.put( + '/general-setting', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.generalSetting, + apiV3FormValidator, + async (req, res) => { const updateData = { 'security:sessionMaxAge': parseInt(req.body.sessionMaxAge), 'security:restrictGuestMode': req.body.restrictGuestMode, 'security:pageDeletionAuthority': req.body.pageDeletionAuthority, - 'security:pageRecursiveDeletionAuthority': req.body.pageRecursiveDeletionAuthority, - 'security:pageCompleteDeletionAuthority': req.body.pageCompleteDeletionAuthority, - 'security:pageRecursiveCompleteDeletionAuthority': req.body.pageRecursiveCompleteDeletionAuthority, - 'security:isAllGroupMembershipRequiredForPageCompleteDeletion': req.body.isAllGroupMembershipRequiredForPageCompleteDeletion, - 'security:list-policy:hideRestrictedByOwner': req.body.hideRestrictedByOwner, - 'security:list-policy:hideRestrictedByGroup': req.body.hideRestrictedByGroup, - 'security:user-homepage-deletion:isEnabled': req.body.isUsersHomepageDeletionEnabled, + 'security:pageRecursiveDeletionAuthority': + req.body.pageRecursiveDeletionAuthority, + 'security:pageCompleteDeletionAuthority': + req.body.pageCompleteDeletionAuthority, + 'security:pageRecursiveCompleteDeletionAuthority': + req.body.pageRecursiveCompleteDeletionAuthority, + 'security:isAllGroupMembershipRequiredForPageCompleteDeletion': + req.body.isAllGroupMembershipRequiredForPageCompleteDeletion, + 'security:list-policy:hideRestrictedByOwner': + req.body.hideRestrictedByOwner, + 'security:list-policy:hideRestrictedByGroup': + req.body.hideRestrictedByGroup, + 'security:user-homepage-deletion:isEnabled': + req.body.isUsersHomepageDeletionEnabled, // Validate user-homepage-deletion config - 'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion': req.body.isUsersHomepageDeletionEnabled - ? req.body.isForceDeleteUserHomepageOnUserDeletion - : false, - 'security:isRomUserAllowedToComment': req.body.isRomUserAllowedToComment, + 'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion': + req.body.isUsersHomepageDeletionEnabled + ? req.body.isForceDeleteUserHomepageOnUserDeletion + : false, + 'security:isRomUserAllowedToComment': + req.body.isRomUserAllowedToComment, }; // Validate delete config - const [singleAuthority1, recursiveAuthority1] = prepareDeleteConfigValuesForCalc(req.body.pageDeletionAuthority, req.body.pageRecursiveDeletionAuthority); + const [singleAuthority1, recursiveAuthority1] = + prepareDeleteConfigValuesForCalc( + req.body.pageDeletionAuthority, + req.body.pageRecursiveDeletionAuthority, + ); // eslint-disable-next-line max-len - const [singleAuthority2, recursiveAuthority2] = prepareDeleteConfigValuesForCalc(req.body.pageCompleteDeletionAuthority, req.body.pageRecursiveCompleteDeletionAuthority); - const isDeleteConfigNormalized = validateDeleteConfigs(singleAuthority1, recursiveAuthority1) - && validateDeleteConfigs(singleAuthority2, recursiveAuthority2); + const [singleAuthority2, recursiveAuthority2] = + prepareDeleteConfigValuesForCalc( + req.body.pageCompleteDeletionAuthority, + req.body.pageRecursiveCompleteDeletionAuthority, + ); + const isDeleteConfigNormalized = + validateDeleteConfigs(singleAuthority1, recursiveAuthority1) && + validateDeleteConfigs(singleAuthority2, recursiveAuthority2); if (!isDeleteConfigNormalized) { - return res.apiv3Err(new ErrorV3('Delete config values are not correct.', 'delete_config_not_normalized')); + return res.apiv3Err( + new ErrorV3( + 'Delete config values are not correct.', + 'delete_config_not_normalized', + ), + ); } const wikiMode = await configManager.getConfig('security:wikiMode'); if (wikiMode === 'private' || wikiMode === 'public') { - logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set'); + logger.debug( + 'security:restrictGuestMode will not be changed because wiki mode is forced to set', + ); delete updateData['security:restrictGuestMode']; } try { await configManager.updateConfigs(updateData); const securitySettingParams = { - sessionMaxAge: await configManager.getConfig('security:sessionMaxAge'), - restrictGuestMode: await configManager.getConfig('security:restrictGuestMode'), - pageDeletionAuthority: await configManager.getConfig('security:pageDeletionAuthority'), - pageCompleteDeletionAuthority: await configManager.getConfig('security:pageCompleteDeletionAuthority'), - pageRecursiveDeletionAuthority: await configManager.getConfig('security:pageRecursiveDeletionAuthority'), - pageRecursiveCompleteDeletionAuthority: await configManager.getConfig('security:pageRecursiveCompleteDeletionAuthority'), + sessionMaxAge: await configManager.getConfig( + 'security:sessionMaxAge', + ), + restrictGuestMode: await configManager.getConfig( + 'security:restrictGuestMode', + ), + pageDeletionAuthority: await configManager.getConfig( + 'security:pageDeletionAuthority', + ), + pageCompleteDeletionAuthority: await configManager.getConfig( + 'security:pageCompleteDeletionAuthority', + ), + pageRecursiveDeletionAuthority: await configManager.getConfig( + 'security:pageRecursiveDeletionAuthority', + ), + pageRecursiveCompleteDeletionAuthority: await configManager.getConfig( + 'security:pageRecursiveCompleteDeletionAuthority', + ), isAllGroupMembershipRequiredForPageCompleteDeletion: - await configManager.getConfig('security:isAllGroupMembershipRequiredForPageCompleteDeletion'), - hideRestrictedByOwner: await configManager.getConfig('security:list-policy:hideRestrictedByOwner'), - hideRestrictedByGroup: await configManager.getConfig('security:list-policy:hideRestrictedByGroup'), - isUsersHomepageDeletionEnabled: await configManager.getConfig('security:user-homepage-deletion:isEnabled'), + await configManager.getConfig( + 'security:isAllGroupMembershipRequiredForPageCompleteDeletion', + ), + hideRestrictedByOwner: await configManager.getConfig( + 'security:list-policy:hideRestrictedByOwner', + ), + hideRestrictedByGroup: await configManager.getConfig( + 'security:list-policy:hideRestrictedByGroup', + ), + isUsersHomepageDeletionEnabled: await configManager.getConfig( + 'security:user-homepage-deletion:isEnabled', + ), isForceDeleteUserHomepageOnUserDeletion: - await configManager.getConfig('security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion'), - isRomUserAllowedToComment: await configManager.getConfig('security:isRomUserAllowedToComment'), + await configManager.getConfig( + 'security:user-homepage-deletion:isForceDeleteUserHomepageOnUserDeletion', + ), + isRomUserAllowedToComment: await configManager.getConfig( + 'security:isRomUserAllowedToComment', + ), }; - const parameters = { action: SupportedAction.ACTION_ADMIN_SECURITY_SETTINGS_UPDATE }; + const parameters = { + action: SupportedAction.ACTION_ADMIN_SECURITY_SETTINGS_UPDATE, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ securitySettingParams }); - } - catch (err) { + } catch (err) { const msg = 'Error occurred in updating security setting'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-secuirty-setting failed')); } - }); + }, + ); /** * @swagger @@ -829,29 +1225,40 @@ module.exports = (crowi) => { * securitySettingParams: * $ref: '#/components/schemas/ShareLinkSetting' */ - router.put('/share-link-setting', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, - validator.generalSetting, apiV3FormValidator, - async(req, res) => { + router.put( + '/share-link-setting', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.generalSetting, + apiV3FormValidator, + async (req, res) => { const updateData = { 'security:disableLinkSharing': req.body.disableLinkSharing, }; try { await configManager.updateConfigs(updateData); const securitySettingParams = { - disableLinkSharing: configManager.getConfig('security:disableLinkSharing'), + disableLinkSharing: configManager.getConfig( + 'security:disableLinkSharing', + ), }; // eslint-disable-next-line max-len - const parameters = { action: updateData['security:disableLinkSharing'] ? SupportedAction.ACTION_ADMIN_REJECT_SHARE_LINK : SupportedAction.ACTION_ADMIN_PERMIT_SHARE_LINK }; + const parameters = { + action: updateData['security:disableLinkSharing'] + ? SupportedAction.ACTION_ADMIN_REJECT_SHARE_LINK + : SupportedAction.ACTION_ADMIN_PERMIT_SHARE_LINK, + }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ securitySettingParams }); - } - catch (err) { + } catch (err) { const msg = 'Error occurred in updating security setting'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-secuirty-setting failed')); } - }); - + }, + ); /** * @swagger @@ -874,30 +1281,32 @@ module.exports = (crowi) => { * type: object * description: suceed to get all share links */ - router.get('/all-share-links/', accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, async(req, res) => { - const page = parseInt(req.query.page) || 1; - const limit = 10; - const linkQuery = {}; - try { - const paginateResult = await ShareLink.paginate( - linkQuery, - { + router.get( + '/all-share-links/', + accessTokenParser([SCOPE.READ.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + async (req, res) => { + const page = parseInt(req.query.page) || 1; + const limit = 10; + const linkQuery = {}; + try { + const paginateResult = await ShareLink.paginate(linkQuery, { page, limit, populate: { path: 'relatedPage', select: 'path', }, - }, - ); - return res.apiv3({ paginateResult }); - } - catch (err) { - const msg = 'Error occured in get share link'; - logger.error('Error', err); - return res.apiv3Err(new ErrorV3(msg, 'get-all-share-links-failed')); - } - }); + }); + return res.apiv3({ paginateResult }); + } catch (err) { + const msg = 'Error occured in get share link'; + logger.error('Error', err); + return res.apiv3Err(new ErrorV3(msg, 'get-all-share-links-failed')); + } + }, + ); /** * @swagger @@ -920,18 +1329,25 @@ module.exports = (crowi) => { * type: number * description: total number of removed share links */ - router.delete('/all-share-links/', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, async(req, res) => { - try { - const removedAct = await ShareLink.remove({}); - const removeTotal = await removedAct.n; - return res.apiv3({ removeTotal }); - } - catch (err) { - const msg = 'Error occured in delete all share links'; - logger.error('Error', err); - return res.apiv3Err(new ErrorV3(msg, 'failed-to-delete-all-share-links')); - } - }); + router.delete( + '/all-share-links/', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + async (req, res) => { + try { + const removedAct = await ShareLink.remove({}); + const removeTotal = await removedAct.n; + return res.apiv3({ removeTotal }); + } catch (err) { + const msg = 'Error occured in delete all share links'; + logger.error('Error', err); + return res.apiv3Err( + new ErrorV3(msg, 'failed-to-delete-all-share-links'), + ); + } + }, + ); /** * @swagger @@ -959,38 +1375,58 @@ module.exports = (crowi) => { * localSettingParams: * $ref: '#/components/schemas/LocalSetting' */ - router.put('/local-setting', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, - validator.localSetting, apiV3FormValidator, - async(req, res) => { + router.put( + '/local-setting', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.localSetting, + apiV3FormValidator, + async (req, res) => { try { - const sanitizedRegistrationWhitelist = req.body.registrationWhitelist - .map(line => xss(line, { stripIgnoreTag: true })); + const sanitizedRegistrationWhitelist = + req.body.registrationWhitelist.map((line) => + xss(line, { stripIgnoreTag: true }), + ); const requestParams = { 'security:registrationMode': req.body.registrationMode, 'security:registrationWhitelist': sanitizedRegistrationWhitelist, - 'security:passport-local:isPasswordResetEnabled': req.body.isPasswordResetEnabled, - 'security:passport-local:isEmailAuthenticationEnabled': req.body.isEmailAuthenticationEnabled, + 'security:passport-local:isPasswordResetEnabled': + req.body.isPasswordResetEnabled, + 'security:passport-local:isEmailAuthenticationEnabled': + req.body.isEmailAuthenticationEnabled, }; await updateAndReloadStrategySettings('local', requestParams); const localSettingParams = { - registrationMode: await configManager.getConfig('security:registrationMode'), - registrationWhitelist: await configManager.getConfig('security:registrationWhitelist'), - isPasswordResetEnabled: await configManager.getConfig('security:passport-local:isPasswordResetEnabled'), - isEmailAuthenticationEnabled: await configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled'), + registrationMode: await configManager.getConfig( + 'security:registrationMode', + ), + registrationWhitelist: await configManager.getConfig( + 'security:registrationWhitelist', + ), + isPasswordResetEnabled: await configManager.getConfig( + 'security:passport-local:isPasswordResetEnabled', + ), + isEmailAuthenticationEnabled: await configManager.getConfig( + 'security:passport-local:isEmailAuthenticationEnabled', + ), + }; + const parameters = { + action: SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_UPDATE, }; - const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_ID_PASS_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ localSettingParams }); - } - catch (err) { + } catch (err) { const msg = 'Error occurred in updating local setting'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-local-setting failed')); } - }); + }, + ); /** * @swagger @@ -1018,9 +1454,15 @@ module.exports = (crowi) => { * securitySettingParams: * $ref: '#/components/schemas/LdapAuthSetting' */ - router.put('/ldap', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, - validator.ldapAuth, apiV3FormValidator, - async(req, res) => { + router.put( + '/ldap', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.ldapAuth, + apiV3FormValidator, + async (req, res) => { const requestParams = { 'security:passport-ldap:serverUrl': req.body.serverUrl, 'security:passport-ldap:isUserBind': req.body.isUserBind, @@ -1028,11 +1470,13 @@ module.exports = (crowi) => { 'security:passport-ldap:bindDNPassword': req.body.ldapBindDNPassword, 'security:passport-ldap:searchFilter': req.body.ldapSearchFilter, 'security:passport-ldap:attrMapUsername': req.body.ldapAttrMapUsername, - 'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser, + 'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser': + req.body.isSameUsernameTreatedAsIdenticalUser, 'security:passport-ldap:attrMapMail': req.body.ldapAttrMapMail, 'security:passport-ldap:attrMapName': req.body.ldapAttrMapName, 'security:passport-ldap:groupSearchBase': req.body.ldapGroupSearchBase, - 'security:passport-ldap:groupSearchFilter': req.body.ldapGroupSearchFilter, + 'security:passport-ldap:groupSearchFilter': + req.body.ldapGroupSearchFilter, 'security:passport-ldap:groupDnProperty': req.body.ldapGroupDnProperty, }; @@ -1040,29 +1484,55 @@ module.exports = (crowi) => { await updateAndReloadStrategySettings('ldap', requestParams); const securitySettingParams = { - serverUrl: await configManager.getConfig('security:passport-ldap:serverUrl'), - isUserBind: await configManager.getConfig('security:passport-ldap:isUserBind'), - ldapBindDN: await configManager.getConfig('security:passport-ldap:bindDN'), - ldapBindDNPassword: await configManager.getConfig('security:passport-ldap:bindDNPassword'), - ldapSearchFilter: await configManager.getConfig('security:passport-ldap:searchFilter'), - ldapAttrMapUsername: await configManager.getConfig('security:passport-ldap:attrMapUsername'), - isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'), - ldapAttrMapMail: await configManager.getConfig('security:passport-ldap:attrMapMail'), - ldapAttrMapName: await configManager.getConfig('security:passport-ldap:attrMapName'), - ldapGroupSearchBase: await configManager.getConfig('security:passport-ldap:groupSearchBase'), - ldapGroupSearchFilter: await configManager.getConfig('security:passport-ldap:groupSearchFilter'), - ldapGroupDnProperty: await configManager.getConfig('security:passport-ldap:groupDnProperty'), + serverUrl: await configManager.getConfig( + 'security:passport-ldap:serverUrl', + ), + isUserBind: await configManager.getConfig( + 'security:passport-ldap:isUserBind', + ), + ldapBindDN: await configManager.getConfig( + 'security:passport-ldap:bindDN', + ), + ldapBindDNPassword: await configManager.getConfig( + 'security:passport-ldap:bindDNPassword', + ), + ldapSearchFilter: await configManager.getConfig( + 'security:passport-ldap:searchFilter', + ), + ldapAttrMapUsername: await configManager.getConfig( + 'security:passport-ldap:attrMapUsername', + ), + isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser', + ), + ldapAttrMapMail: await configManager.getConfig( + 'security:passport-ldap:attrMapMail', + ), + ldapAttrMapName: await configManager.getConfig( + 'security:passport-ldap:attrMapName', + ), + ldapGroupSearchBase: await configManager.getConfig( + 'security:passport-ldap:groupSearchBase', + ), + ldapGroupSearchFilter: await configManager.getConfig( + 'security:passport-ldap:groupSearchFilter', + ), + ldapGroupDnProperty: await configManager.getConfig( + 'security:passport-ldap:groupDnProperty', + ), + }; + const parameters = { + action: SupportedAction.ACTION_ADMIN_AUTH_LDAP_UPDATE, }; - const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_LDAP_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ securitySettingParams }); - } - catch (err) { + } catch (err) { const msg = 'Error occurred in updating SAML setting'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed')); } - }); + }, + ); /** * @swagger @@ -1090,25 +1560,44 @@ module.exports = (crowi) => { * securitySettingParams: * $ref: '#/components/schemas/SamlAuthSetting' */ - router.put('/saml', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, - validator.samlAuth, apiV3FormValidator, - async(req, res) => { - const { t } = await getTranslation({ lang: req.user.lang, ns: ['translation', 'admin'] }); + router.put( + '/saml', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.samlAuth, + apiV3FormValidator, + async (req, res) => { + const { t } = await getTranslation({ + lang: req.user.lang, + ns: ['translation', 'admin'], + }); // For the value of each mandatory items, // check whether it from the environment variables is empty and form value to update it is empty // validate the syntax of a attribute - based login control rule const invalidValues = []; - for (const configKey of crowi.passportService.mandatoryConfigKeysForSaml) { + for (const configKey of crowi.passportService + .mandatoryConfigKeysForSaml) { const key = configKey.replace('security:passport-saml:', ''); const formValue = req.body[key]; - if (configManager.getConfig(configKey, ConfigSource.env) == null && formValue == null) { + if ( + configManager.getConfig(configKey, ConfigSource.env) == null && + formValue == null + ) { const formItemName = t(`security_settings.form_item_name.${key}`); - invalidValues.push(t('input_validation.message.required', { param: formItemName })); + invalidValues.push( + t('input_validation.message.required', { param: formItemName }), + ); } } if (invalidValues.length !== 0) { - return res.apiv3Err(t('input_validation.message.error_message'), 400, invalidValues); + return res.apiv3Err( + t('input_validation.message.error_message'), + 400, + invalidValues, + ); } const rule = req.body.ABLCRule; @@ -1117,9 +1606,13 @@ module.exports = (crowi) => { if (rule != null) { try { crowi.passportService.parseABLCRule(rule); - } - catch (err) { - return res.apiv3Err(t('input_validation.message.invalid_syntax', { syntax: t('security_settings.form_item_name.ABLCRule') }), 400); + } catch (err) { + return res.apiv3Err( + t('input_validation.message.invalid_syntax', { + syntax: t('security_settings.form_item_name.ABLCRule'), + }), + 400, + ); } } @@ -1132,8 +1625,10 @@ module.exports = (crowi) => { 'security:passport-saml:attrMapMail': req.body.attrMapMail, 'security:passport-saml:attrMapFirstName': req.body.attrMapFirstName, 'security:passport-saml:attrMapLastName': req.body.attrMapLastName, - 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser, - 'security:passport-saml:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser, + 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser': + req.body.isSameUsernameTreatedAsIdenticalUser, + 'security:passport-saml:isSameEmailTreatedAsIdenticalUser': + req.body.isSameEmailTreatedAsIdenticalUser, 'security:passport-saml:ABLCRule': req.body.ABLCRule, }; @@ -1141,29 +1636,62 @@ module.exports = (crowi) => { await updateAndReloadStrategySettings('saml', requestParams); const securitySettingParams = { - missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(), - samlEntryPoint: await configManager.getConfig('security:passport-saml:entryPoint', ConfigSource.db), - samlIssuer: await configManager.getConfig('security:passport-saml:issuer', ConfigSource.db), - samlCert: await configManager.getConfig('security:passport-saml:cert', ConfigSource.db), - samlAttrMapId: await configManager.getConfig('security:passport-saml:attrMapId', ConfigSource.db), - samlAttrMapUsername: await configManager.getConfig('security:passport-saml:attrMapUsername', ConfigSource.db), - samlAttrMapMail: await configManager.getConfig('security:passport-saml:attrMapMail', ConfigSource.db), - samlAttrMapFirstName: await configManager.getConfig('security:passport-saml:attrMapFirstName', ConfigSource.db), - samlAttrMapLastName: await configManager.getConfig('security:passport-saml:attrMapLastName', ConfigSource.db), - isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameUsernameTreatedAsIdenticalUser'), - isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-saml:isSameEmailTreatedAsIdenticalUser'), - samlABLCRule: await configManager.getConfig('security:passport-saml:ABLCRule'), + missingMandatoryConfigKeys: + await crowi.passportService.getSamlMissingMandatoryConfigKeys(), + samlEntryPoint: await configManager.getConfig( + 'security:passport-saml:entryPoint', + ConfigSource.db, + ), + samlIssuer: await configManager.getConfig( + 'security:passport-saml:issuer', + ConfigSource.db, + ), + samlCert: await configManager.getConfig( + 'security:passport-saml:cert', + ConfigSource.db, + ), + samlAttrMapId: await configManager.getConfig( + 'security:passport-saml:attrMapId', + ConfigSource.db, + ), + samlAttrMapUsername: await configManager.getConfig( + 'security:passport-saml:attrMapUsername', + ConfigSource.db, + ), + samlAttrMapMail: await configManager.getConfig( + 'security:passport-saml:attrMapMail', + ConfigSource.db, + ), + samlAttrMapFirstName: await configManager.getConfig( + 'security:passport-saml:attrMapFirstName', + ConfigSource.db, + ), + samlAttrMapLastName: await configManager.getConfig( + 'security:passport-saml:attrMapLastName', + ConfigSource.db, + ), + isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser', + ), + isSameEmailTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-saml:isSameEmailTreatedAsIdenticalUser', + ), + samlABLCRule: await configManager.getConfig( + 'security:passport-saml:ABLCRule', + ), + }; + const parameters = { + action: SupportedAction.ACTION_ADMIN_AUTH_SAML_UPDATE, }; - const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_SAML_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ securitySettingParams }); - } - catch (err) { + } catch (err) { const msg = 'Error occurred in updating SAML setting'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed')); } - }); + }, + ); /** * @swagger @@ -1191,19 +1719,31 @@ module.exports = (crowi) => { * securitySettingParams: * $ref: '#/components/schemas/OidcAuthSetting' */ - router.put('/oidc', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, - validator.oidcAuth, apiV3FormValidator, - async(req, res) => { + router.put( + '/oidc', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.oidcAuth, + apiV3FormValidator, + async (req, res) => { const requestParams = { 'security:passport-oidc:providerName': req.body.oidcProviderName, 'security:passport-oidc:issuerHost': req.body.oidcIssuerHost, - 'security:passport-oidc:authorizationEndpoint': req.body.oidcAuthorizationEndpoint, + 'security:passport-oidc:authorizationEndpoint': + req.body.oidcAuthorizationEndpoint, 'security:passport-oidc:tokenEndpoint': req.body.oidcTokenEndpoint, - 'security:passport-oidc:revocationEndpoint': req.body.oidcRevocationEndpoint, - 'security:passport-oidc:introspectionEndpoint': req.body.oidcIntrospectionEndpoint, - 'security:passport-oidc:userInfoEndpoint': req.body.oidcUserInfoEndpoint, - 'security:passport-oidc:endSessionEndpoint': req.body.oidcEndSessionEndpoint, - 'security:passport-oidc:registrationEndpoint': req.body.oidcRegistrationEndpoint, + 'security:passport-oidc:revocationEndpoint': + req.body.oidcRevocationEndpoint, + 'security:passport-oidc:introspectionEndpoint': + req.body.oidcIntrospectionEndpoint, + 'security:passport-oidc:userInfoEndpoint': + req.body.oidcUserInfoEndpoint, + 'security:passport-oidc:endSessionEndpoint': + req.body.oidcEndSessionEndpoint, + 'security:passport-oidc:registrationEndpoint': + req.body.oidcRegistrationEndpoint, 'security:passport-oidc:jwksUri': req.body.oidcJWKSUri, 'security:passport-oidc:clientId': req.body.oidcClientId, 'security:passport-oidc:clientSecret': req.body.oidcClientSecret, @@ -1211,43 +1751,83 @@ module.exports = (crowi) => { 'security:passport-oidc:attrMapUserName': req.body.oidcAttrMapUserName, 'security:passport-oidc:attrMapName': req.body.oidcAttrMapName, 'security:passport-oidc:attrMapMail': req.body.oidcAttrMapEmail, - 'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser, - 'security:passport-oidc:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser, + 'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser': + req.body.isSameUsernameTreatedAsIdenticalUser, + 'security:passport-oidc:isSameEmailTreatedAsIdenticalUser': + req.body.isSameEmailTreatedAsIdenticalUser, }; try { await updateAndReloadStrategySettings('oidc', requestParams); const securitySettingParams = { - oidcProviderName: await configManager.getConfig('security:passport-oidc:providerName'), - oidcIssuerHost: await configManager.getConfig('security:passport-oidc:issuerHost'), - oidcAuthorizationEndpoint: await configManager.getConfig('security:passport-oidc:authorizationEndpoint'), - oidcTokenEndpoint: await configManager.getConfig('security:passport-oidc:tokenEndpoint'), - oidcRevocationEndpoint: await configManager.getConfig('security:passport-oidc:revocationEndpoint'), - oidcIntrospectionEndpoint: await configManager.getConfig('security:passport-oidc:introspectionEndpoint'), - oidcUserInfoEndpoint: await configManager.getConfig('security:passport-oidc:userInfoEndpoint'), - oidcEndSessionEndpoint: await configManager.getConfig('security:passport-oidc:endSessionEndpoint'), - oidcRegistrationEndpoint: await configManager.getConfig('security:passport-oidc:registrationEndpoint'), - oidcJWKSUri: await configManager.getConfig('security:passport-oidc:jwksUri'), - oidcClientId: await configManager.getConfig('security:passport-oidc:clientId'), - oidcClientSecret: await configManager.getConfig('security:passport-oidc:clientSecret'), - oidcAttrMapId: await configManager.getConfig('security:passport-oidc:attrMapId'), - oidcAttrMapUserName: await configManager.getConfig('security:passport-oidc:attrMapUserName'), - oidcAttrMapName: await configManager.getConfig('security:passport-oidc:attrMapName'), - oidcAttrMapEmail: await configManager.getConfig('security:passport-oidc:attrMapMail'), - isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'), - isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-oidc:isSameEmailTreatedAsIdenticalUser'), + oidcProviderName: await configManager.getConfig( + 'security:passport-oidc:providerName', + ), + oidcIssuerHost: await configManager.getConfig( + 'security:passport-oidc:issuerHost', + ), + oidcAuthorizationEndpoint: await configManager.getConfig( + 'security:passport-oidc:authorizationEndpoint', + ), + oidcTokenEndpoint: await configManager.getConfig( + 'security:passport-oidc:tokenEndpoint', + ), + oidcRevocationEndpoint: await configManager.getConfig( + 'security:passport-oidc:revocationEndpoint', + ), + oidcIntrospectionEndpoint: await configManager.getConfig( + 'security:passport-oidc:introspectionEndpoint', + ), + oidcUserInfoEndpoint: await configManager.getConfig( + 'security:passport-oidc:userInfoEndpoint', + ), + oidcEndSessionEndpoint: await configManager.getConfig( + 'security:passport-oidc:endSessionEndpoint', + ), + oidcRegistrationEndpoint: await configManager.getConfig( + 'security:passport-oidc:registrationEndpoint', + ), + oidcJWKSUri: await configManager.getConfig( + 'security:passport-oidc:jwksUri', + ), + oidcClientId: await configManager.getConfig( + 'security:passport-oidc:clientId', + ), + oidcClientSecret: await configManager.getConfig( + 'security:passport-oidc:clientSecret', + ), + oidcAttrMapId: await configManager.getConfig( + 'security:passport-oidc:attrMapId', + ), + oidcAttrMapUserName: await configManager.getConfig( + 'security:passport-oidc:attrMapUserName', + ), + oidcAttrMapName: await configManager.getConfig( + 'security:passport-oidc:attrMapName', + ), + oidcAttrMapEmail: await configManager.getConfig( + 'security:passport-oidc:attrMapMail', + ), + isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser', + ), + isSameEmailTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-oidc:isSameEmailTreatedAsIdenticalUser', + ), + }; + const parameters = { + action: SupportedAction.ACTION_ADMIN_AUTH_OIDC_UPDATE, }; - const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_OIDC_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ securitySettingParams }); - } - catch (err) { + } catch (err) { const msg = 'Error occurred in updating OpenIDConnect'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-OpenIDConnect-failed')); } - }); + }, + ); /** * @swagger @@ -1275,33 +1855,55 @@ module.exports = (crowi) => { * securitySettingParams: * $ref: '#/components/schemas/GoogleOAuthSetting' */ - router.put('/google-oauth', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, - validator.googleOAuth, apiV3FormValidator, - async(req, res) => { + router.put( + '/google-oauth', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.googleOAuth, + apiV3FormValidator, + async (req, res) => { try { await updateAndReloadStrategySettings('google', { - 'security:passport-google:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser, + 'security:passport-google:isSameEmailTreatedAsIdenticalUser': + req.body.isSameEmailTreatedAsIdenticalUser, }); - await updateAndReloadStrategySettings('google', { - 'security:passport-google:clientId': toNonBlankStringOrUndefined(req.body.googleClientId), - 'security:passport-google:clientSecret': toNonBlankStringOrUndefined(req.body.googleClientSecret), - }, { removeIfUndefined: true }); + await updateAndReloadStrategySettings( + 'google', + { + 'security:passport-google:clientId': toNonBlankStringOrUndefined( + req.body.googleClientId, + ), + 'security:passport-google:clientSecret': + toNonBlankStringOrUndefined(req.body.googleClientSecret), + }, + { removeIfUndefined: true }, + ); const securitySettingParams = { - googleClientId: await configManager.getConfig('security:passport-google:clientId'), - googleClientSecret: await configManager.getConfig('security:passport-google:clientSecret'), - isSameEmailTreatedAsIdenticalUser: await configManager.getConfig('security:passport-google:isSameEmailTreatedAsIdenticalUser'), + googleClientId: await configManager.getConfig( + 'security:passport-google:clientId', + ), + googleClientSecret: await configManager.getConfig( + 'security:passport-google:clientSecret', + ), + isSameEmailTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-google:isSameEmailTreatedAsIdenticalUser', + ), + }; + const parameters = { + action: SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_UPDATE, }; - const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GOOGLE_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ securitySettingParams }); - } - catch (err) { + } catch (err) { const msg = 'Error occurred in updating googleOAuth'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-googleOAuth-failed')); } - }); + }, + ); /** * @swagger @@ -1329,35 +1931,50 @@ module.exports = (crowi) => { * securitySettingParams: * $ref: '#/components/schemas/GitHubOAuthSetting' */ - router.put('/github-oauth', accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), loginRequiredStrictly, adminRequired, addActivity, - validator.githubOAuth, apiV3FormValidator, - async(req, res) => { + router.put( + '/github-oauth', + accessTokenParser([SCOPE.WRITE.ADMIN.SECURITY]), + loginRequiredStrictly, + adminRequired, + addActivity, + validator.githubOAuth, + apiV3FormValidator, + async (req, res) => { const requestParams = { 'security:passport-github:clientId': req.body.githubClientId, 'security:passport-github:clientSecret': req.body.githubClientSecret, - 'security:passport-github:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser, + 'security:passport-github:isSameUsernameTreatedAsIdenticalUser': + req.body.isSameUsernameTreatedAsIdenticalUser, }; try { await updateAndReloadStrategySettings('github', requestParams); const securitySettingParams = { - githubClientId: await configManager.getConfig('security:passport-github:clientId'), - githubClientSecret: await configManager.getConfig('security:passport-github:clientSecret'), - isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig('security:passport-github:isSameUsernameTreatedAsIdenticalUser'), + githubClientId: await configManager.getConfig( + 'security:passport-github:clientId', + ), + githubClientSecret: await configManager.getConfig( + 'security:passport-github:clientSecret', + ), + isSameUsernameTreatedAsIdenticalUser: await configManager.getConfig( + 'security:passport-github:isSameUsernameTreatedAsIdenticalUser', + ), + }; + const parameters = { + action: SupportedAction.ACTION_ADMIN_AUTH_GITHUB_UPDATE, }; - const parameters = { action: SupportedAction.ACTION_ADMIN_AUTH_GITHUB_UPDATE }; activityEvent.emit('update', res.locals.activity._id, parameters); return res.apiv3({ securitySettingParams }); - } - catch (err) { - // reset strategy + } catch (err) { + // reset strategy await crowi.passportService.resetGitHubStrategy(); const msg = 'Error occurred in updating githubOAuth'; logger.error('Error', err); return res.apiv3Err(new ErrorV3(msg, 'update-githubOAuth-failed')); } - }); + }, + ); return router; }; diff --git a/apps/app/src/server/routes/apiv3/user/get-related-groups.ts b/apps/app/src/server/routes/apiv3/user/get-related-groups.ts index 51eca046c1e..439814fcf80 100644 --- a/apps/app/src/server/routes/apiv3/user/get-related-groups.ts +++ b/apps/app/src/server/routes/apiv3/user/get-related-groups.ts @@ -1,8 +1,8 @@ import type { IUserHasId } from '@growi/core'; +import { SCOPE } from '@growi/core/dist/interfaces'; import { ErrorV3 } from '@growi/core/dist/models'; import type { Request, RequestHandler } from 'express'; -import { SCOPE } from '@growi/core/dist/interfaces'; import type Crowi from '~/server/crowi'; import { accessTokenParser } from '~/server/middlewares/access-token-parser'; import loggerFactory from '~/utils/logger'; @@ -14,22 +14,29 @@ const logger = loggerFactory('growi:routes:apiv3:user:get-related-groups'); type GetRelatedGroupsHandlerFactory = (crowi: Crowi) => RequestHandler[]; interface Req extends Request { - user: IUserHasId, + user: IUserHasId; } -export const getRelatedGroupsHandlerFactory: GetRelatedGroupsHandlerFactory = (crowi) => { - const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); +export const getRelatedGroupsHandlerFactory: GetRelatedGroupsHandlerFactory = ( + crowi, +) => { + const loginRequiredStrictly = require('~/server/middlewares/login-required')( + crowi, + ); return [ - accessTokenParser([SCOPE.READ.USER_SETTINGS.INFO], { acceptLegacy: true }), loginRequiredStrictly, - async(req: Req, res: ApiV3Response) => { + accessTokenParser([SCOPE.READ.USER_SETTINGS.INFO], { acceptLegacy: true }), + loginRequiredStrictly, + async (req: Req, res: ApiV3Response) => { try { - const relatedGroups = await crowi.pageGrantService?.getUserRelatedGroups(req.user); + const relatedGroups = + await crowi.pageGrantService?.getUserRelatedGroups(req.user); return res.apiv3({ relatedGroups }); - } - catch (err) { + } catch (err) { logger.error(err); - return res.apiv3Err(new ErrorV3('Error occurred while getting user related groups')); + return res.apiv3Err( + new ErrorV3('Error occurred while getting user related groups'), + ); } }, ]; diff --git a/biome.json b/biome.json index 1570bee9cf6..daf7fd67e88 100644 --- a/biome.json +++ b/biome.json @@ -30,7 +30,10 @@ "!packages/pdf-converter-client/specs", "!apps/app/src/client", "!apps/app/src/server/middlewares", - "!apps/app/src/server/routes/apiv3", + "!apps/app/src/server/routes/apiv3/app-settings", + "!apps/app/src/server/routes/apiv3/page", + "!apps/app/src/server/routes/apiv3/*.js", + "!apps/app/src/server/routes/apiv3/*.ts", "!apps/app/src/server/service" ] },