| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672 |
- import { body } from 'express-validator';
- import { i18n } from '^/config/next-i18next.config';
- import { SupportedAction } from '~/interfaces/activity';
- import loggerFactory from '~/utils/logger';
- import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
- import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
- import EditorSettings from '../../models/editor-settings';
- import InAppNotificationSettings from '../../models/in-app-notification-settings';
- const logger = loggerFactory('growi:routes:apiv3:personal-setting');
- const express = require('express');
- const passport = require('passport');
- const router = express.Router();
- /**
- * @swagger
- * tags:
- * name: PersonalSetting
- */
- /**
- * @swagger
- *
- * components:
- * schemas:
- * PersonalSettings:
- * description: personal settings
- * type: object
- * properties:
- * name:
- * type: string
- * email:
- * type: string
- * lang:
- * type: string
- * isEmailPublished:
- * type: boolean
- * Passwords:
- * description: passwords for update
- * type: object
- * properties:
- * oldPassword:
- * type: string
- * newPassword:
- * type: string
- * newPasswordConfirm:
- * type: string
- * AssociateUser:
- * description: Ldap account for associate
- * type: object
- * properties:
- * username:
- * type: string
- * password:
- * type: string
- * DisassociateUser:
- * description: Ldap account for disassociate
- * type: object
- * properties:
- * providerType:
- * type: string
- * accountId:
- * type: string
- */
- module.exports = (crowi) => {
- const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
- const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
- const addActivity = generateAddActivityMiddleware(crowi);
- const { User, ExternalAccount } = crowi.models;
- const activityEvent = crowi.event('activity');
- const minPasswordLength = crowi.configManager.getConfig('crowi', 'app:minPasswordLength');
- const validator = {
- personal: [
- body('name').isString().not().isEmpty(),
- body('email')
- .isEmail()
- .custom((email) => {
- 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(),
- ],
- password: [
- body('oldPassword').isString(),
- body('newPassword').isString().not().isEmpty()
- .isLength({ min: minPasswordLength })
- .withMessage(`password must be at least ${minPasswordLength} characters long`),
- body('newPasswordConfirm').isString().not().isEmpty()
- .custom((value, { req }) => {
- return (value === req.body.newPassword);
- }),
- ],
- associateLdap: [
- body('username').isString().not().isEmpty(),
- body('password').isString().not().isEmpty(),
- ],
- disassociateLdap: [
- body('providerType').isString().not().isEmpty(),
- body('accountId').isString().not().isEmpty(),
- ],
- editorSettings: [
- body('theme').optional().isString(),
- body('keymapMode').optional().isString(),
- body('styleActiveLine').optional().isBoolean(),
- body('autoFormatMarkdownTable').optional().isBoolean(),
- ],
- inAppNotificationSettings: [
- body('defaultSubscribeRules.*.name').isString(),
- body('defaultSubscribeRules.*.isEnabled').optional().isBoolean(),
- ],
- };
- /**
- * @swagger
- *
- * /personal-setting:
- * get:
- * tags: [PersonalSetting]
- * operationId: getPersonalSetting
- * summary: /personal-setting
- * description: Get personal parameters
- * responses:
- * 200:
- * description: params of personal
- * content:
- * application/json:
- * schema:
- * properties:
- * currentUser:
- * type: object
- * description: personal params
- */
- router.get('/', accessTokenParser, 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
- *
- * /personal-setting/is-password-set:
- * get:
- * tags: [PersonalSetting]
- * operationId: getIsPasswordSet
- * summary: /personal-setting
- * description: Get whether a password has been set
- * responses:
- * 200:
- * description: Whether a password has been set
- * content:
- * application/json:
- * schema:
- * properties:
- * isPasswordSet:
- * type: boolean
- */
- router.get('/is-password-set', accessTokenParser, loginRequiredStrictly, async(req, res) => {
- const { username } = req.user;
- try {
- const user = await User.findUserByUsername(username);
- const isPasswordSet = user.isPasswordSet();
- const minPasswordLength = crowi.configManager.getConfig('crowi', 'app:minPasswordLength');
- return res.apiv3({ isPasswordSet, minPasswordLength });
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('fail-to-get-whether-password-is-set');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting:
- * put:
- * tags: [PersonalSetting]
- * operationId: updatePersonalSetting
- * summary: /personal-setting
- * description: Update personal setting
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/PersonalSettings'
- * responses:
- * 200:
- * description: params of personal
- * content:
- * application/json:
- * schema:
- * properties:
- * currentUser:
- * type: object
- * description: personal params
- */
- router.put('/', accessTokenParser, loginRequiredStrictly, addActivity, validator.personal, apiV3FormValidator, async(req, res) => {
- try {
- const user = await User.findOne({ _id: req.user.id });
- user.name = req.body.name;
- user.email = req.body.email;
- user.lang = req.body.lang;
- user.isEmailPublished = req.body.isEmailPublished;
- user.slackMemberId = req.body.slackMemberId;
- const updatedUser = await user.save();
- const parameters = { action: SupportedAction.ACTION_USER_PERSONAL_SETTINGS_UPDATE };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- return res.apiv3({ updatedUser });
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('update-personal-settings-failed');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting/image-type:
- * put:
- * tags: [PersonalSetting]
- * operationId: putUserImageType
- * summary: /personal-setting/image-type
- * description: Update user image type
- * responses:
- * 200:
- * description: succeded to update user image type
- * content:
- * application/json:
- * schema:
- * properties:
- * userData:
- * type: object
- * description: user data
- */
- router.put('/image-type', accessTokenParser, loginRequiredStrictly, addActivity, validator.imageType, apiV3FormValidator, async(req, res) => {
- const { isGravatarEnabled } = req.body;
- try {
- const userData = await req.user.updateIsGravatarEnabled(isGravatarEnabled);
- const parameters = { action: SupportedAction.ACTION_USER_IMAGE_TYPE_UPDATE };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- return res.apiv3({ userData });
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('update-personal-settings-failed');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting/external-accounts:
- * get:
- * tags: [PersonalSetting]
- * operationId: getExternalAccounts
- * summary: /personal-setting/external-accounts
- * description: Get external accounts that linked current user
- * responses:
- * 200:
- * description: external accounts
- * content:
- * application/json:
- * schema:
- * properties:
- * externalAccounts:
- * type: object
- * description: array of external accounts
- */
- router.get('/external-accounts', accessTokenParser, loginRequiredStrictly, async(req, res) => {
- const userData = req.user;
- try {
- const externalAccounts = await ExternalAccount.find({ user: userData });
- return res.apiv3({ externalAccounts });
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('get-external-accounts-failed');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting/password:
- * put:
- * tags: [PersonalSetting]
- * operationId: putUserPassword
- * summary: /personal-setting/password
- * description: Update user password
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/Passwords'
- * responses:
- * 200:
- * description: user password
- * content:
- * application/json:
- * schema:
- * properties:
- * userData:
- * type: object
- * description: user data updated
- */
- router.put('/password', accessTokenParser, loginRequiredStrictly, addActivity, validator.password, apiV3FormValidator, async(req, res) => {
- const { body, user } = req;
- const { oldPassword, newPassword } = body;
- if (user.isPasswordSet() && !user.isPasswordValid(oldPassword)) {
- return res.apiv3Err('wrong-current-password', 400);
- }
- try {
- const userData = await user.updatePassword(newPassword);
- const parameters = { action: SupportedAction.ACTION_USER_PASSWORD_UPDATE };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- return res.apiv3({ userData });
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('update-password-failed');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting/api-token:
- * put:
- * tags: [PersonalSetting]
- * operationId: putUserApiToken
- * summary: /personal-setting/api-token
- * description: Update user api token
- * responses:
- * 200:
- * description: succeded to update user api token
- * content:
- * application/json:
- * schema:
- * properties:
- * userData:
- * type: object
- * description: user data
- */
- router.put('/api-token', loginRequiredStrictly, addActivity, async(req, res) => {
- const { user } = req;
- try {
- const userData = await user.updateApiToken();
- 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
- *
- * /personal-setting/associate-ldap:
- * put:
- * tags: [PersonalSetting]
- * operationId: associateLdapAccount
- * summary: /personal-setting/associate-ldap
- * description: associate Ldap account
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/AssociateUser'
- * responses:
- * 200:
- * description: succeded to associate Ldap account
- * content:
- * application/json:
- * schema:
- * properties:
- * associateUser:
- * type: object
- * description: Ldap account associate to me
- */
- router.put('/associate-ldap', accessTokenParser, loginRequiredStrictly, addActivity, validator.associateLdap, apiV3FormValidator, async(req, res) => {
- const { passportService } = crowi;
- const { user, body } = req;
- const { username } = body;
- if (!passportService.isLdapStrategySetup) {
- logger.error('LdapStrategy has not been set up');
- return res.apiv3Err('associate-ldap-account-failed', 405);
- }
- try {
- await passport.authenticate('ldapauth');
- const associateUser = await ExternalAccount.associate('ldap', username, user);
- const parameters = { action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_ASSOCIATE };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- return res.apiv3({ associateUser });
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('associate-ldap-account-failed');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting/disassociate-ldap:
- * put:
- * tags: [PersonalSetting]
- * operationId: disassociateLdapAccount
- * summary: /personal-setting/disassociate-ldap
- * description: disassociate Ldap account
- * requestBody:
- * required: true
- * content:
- * application/json:
- * schema:
- * $ref: '#/components/schemas/DisassociateUser'
- * responses:
- * 200:
- * description: succeded to disassociate Ldap account
- * content:
- * application/json:
- * schema:
- * properties:
- * disassociateUser:
- * type: object
- * description: Ldap account disassociate to me
- */
- // eslint-disable-next-line max-len
- router.put('/disassociate-ldap', accessTokenParser, loginRequiredStrictly, addActivity, validator.disassociateLdap, apiV3FormValidator, async(req, res) => {
- const { user, body } = req;
- const { providerType, accountId } = body;
- try {
- const count = await ExternalAccount.count({ user });
- // make sure password set or this user has two or more ExternalAccounts
- if (user.password == null && count <= 1) {
- return res.apiv3Err('disassociate-ldap-account-failed');
- }
- const disassociateUser = await ExternalAccount.findOneAndRemove({ providerType, accountId, user });
- const parameters = { action: SupportedAction.ACTION_USER_LDAP_ACCOUNT_DISCONNECT };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- return res.apiv3({ disassociateUser });
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('disassociate-ldap-account-failed');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting/editor-settings:
- * put:
- * tags: [EditorSetting]
- * operationId: putEditorSettings
- * summary: /editor-setting
- * description: Put editor preferences
- * responses:
- * 200:
- * description: params of editor settings
- * content:
- * application/json:
- * schema:
- * properties:
- * currentUser:
- * type: object
- * description: editor settings
- */
- router.put('/editor-settings', accessTokenParser, loginRequiredStrictly, addActivity, validator.editorSettings, apiV3FormValidator, async(req, res) => {
- const query = { userId: req.user.id };
- const { body } = req;
- const {
- theme, keymapMode, styleActiveLine, autoFormatMarkdownTable,
- } = body;
- const document = {
- 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 };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- return res.apiv3(response);
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('updating-editor-settings-failed');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting/editor-settings:
- * get:
- * tags: [EditorSetting]
- * operationId: getEditorSettings
- * summary: /editor-setting
- * description: Get editor preferences
- * responses:
- * 200:
- * description: params of editor settings
- * content:
- * application/json:
- * schema:
- * properties:
- * currentUser:
- * type: object
- * description: editor settings
- */
- router.get('/editor-settings', accessTokenParser, 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
- *
- * /personal-setting/in-app-notification-settings:
- * put:
- * tags: [in-app-notification-settings]
- * operationId: putInAppNotificationSettings
- * summary: personal-setting/in-app-notification-settings
- * description: Put InAppNotificationSettings
- * responses:
- * 200:
- * description: params of InAppNotificationSettings
- * content:
- * application/json:
- * schema:
- * properties:
- * currentUser:
- * type: object
- * description: in-app-notification-settings
- */
- // eslint-disable-next-line max-len
- router.put('/in-app-notification-settings', accessTokenParser, loginRequiredStrictly, addActivity, validator.inAppNotificationSettings, apiV3FormValidator, async(req, res) => {
- const query = { userId: req.user.id };
- const subscribeRules = req.body.subscribeRules;
- if (subscribeRules == null) {
- return res.apiv3Err('no-rules-found');
- }
- 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 };
- activityEvent.emit('update', res.locals.activity._id, parameters);
- return res.apiv3(response);
- }
- catch (err) {
- logger.error(err);
- return res.apiv3Err('updating-in-app-notification-settings-failed');
- }
- });
- /**
- * @swagger
- *
- * /personal-setting/in-app-notification-settings:
- * get:
- * tags: [in-app-notification-settings]
- * operationId: getInAppNotificationSettings
- * summary: personal-setting/in-app-notification-settings
- * description: Get InAppNotificationSettings
- * responses:
- * 200:
- * description: params of InAppNotificationSettings
- * content:
- * application/json:
- * schema:
- * properties:
- * currentUser:
- * type: object
- * description: InAppNotificationSettings
- */
- router.get('/in-app-notification-settings', accessTokenParser, 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;
- };
|