diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 2935170..caab752 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -47,6 +47,21 @@ components: - userId type: object additionalProperties: false + UpdatePasswordPayload: + properties: + currentPassword: + type: string + description: 'Current Password' + example: currentPasswordString + newPassword: + type: string + description: 'New Password' + example: newPassword + required: + - currentPassword + - newPassword + type: object + additionalProperties: false ClientPayload: properties: clientId: @@ -632,6 +647,25 @@ paths: - bearerAuth: [] parameters: [] + /SASjsApi/auth/updatePassword: + patch: + operationId: UpdatePassword + responses: + '204': + description: 'No content' + summary: 'Update user''s password.' + tags: + - Auth + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePasswordPayload' /SASjsApi/authConfig: get: operationId: GetDetail diff --git a/api/src/controllers/auth.ts b/api/src/controllers/auth.ts index 78d5a5b..cddbd62 100644 --- a/api/src/controllers/auth.ts +++ b/api/src/controllers/auth.ts @@ -1,4 +1,16 @@ -import { Security, Route, Tags, Example, Post, Body, Query, Hidden } from 'tsoa' +import express from 'express' +import { + Security, + Route, + Tags, + Example, + Post, + Patch, + Request, + Body, + Query, + Hidden +} from 'tsoa' import jwt from 'jsonwebtoken' import { InfoJWT } from '../types' import { @@ -9,6 +21,7 @@ import { saveTokensInDB } from '../utils' import Client from '../model/Client' +import User from '../model/User' @Route('SASjsApi/auth') @Tags('Auth') @@ -62,6 +75,18 @@ export class AuthController { public async logout(@Query() @Hidden() data?: InfoJWT) { return logout(data!) } + + /** + * @summary Update user's password. + */ + @Security('bearerAuth') + @Patch('updatePassword') + public async updatePassword( + @Request() req: express.Request, + @Body() body: UpdatePasswordPayload + ) { + return updatePassword(req, body) + } } const token = async (data: any): Promise => { @@ -128,6 +153,39 @@ const logout = async (userInfo: InfoJWT) => { await removeTokensInDB(userInfo.userId, userInfo.clientId) } +const updatePassword = async ( + req: express.Request, + data: UpdatePasswordPayload +) => { + const { currentPassword, newPassword } = data + const userId = req.user?.userId + const dbUser = await User.findOne({ userId }) + + if (!dbUser) + throw { + code: 404, + message: `User not found!` + } + + if (dbUser?.authProvider) { + throw { + code: 405, + message: + 'Can not update password of user that is created by an external auth provider.' + } + } + + const validPass = dbUser.comparePassword(currentPassword) + if (!validPass) + throw { + code: 403, + message: `Invalid current password!` + } + + dbUser.password = User.hashPassword(newPassword) + await dbUser.save() +} + interface TokenPayload { /** * Client ID @@ -154,6 +212,19 @@ interface TokenResponse { refreshToken: string } +interface UpdatePasswordPayload { + /** + * Current Password + * @example "currentPasswordString" + */ + currentPassword: string + /** + * New Password + * @example "newPassword" + */ + newPassword: string +} + const verifyAuthCode = async ( clientId: string, code: string diff --git a/api/src/routes/api/auth.ts b/api/src/routes/api/auth.ts index f031df9..3e8a9ac 100644 --- a/api/src/routes/api/auth.ts +++ b/api/src/routes/api/auth.ts @@ -7,12 +7,28 @@ import { authenticateRefreshToken } from '../../middlewares' -import { tokenValidation } from '../../utils' +import { tokenValidation, updatePasswordValidation } from '../../utils' import { InfoJWT } from '../../types' const authRouter = express.Router() const controller = new AuthController() +authRouter.patch( + '/updatePassword', + authenticateAccessToken, + async (req, res) => { + const { error, value: body } = updatePasswordValidation(req.body) + if (error) return res.status(400).send(error.details[0].message) + + try { + await controller.updatePassword(req, body) + res.sendStatus(204) + } catch (err: any) { + res.status(err.code).send(err.message) + } + } +) + authRouter.post('/token', async (req, res) => { const { error, value: body } = tokenValidation(req.body) if (error) return res.status(400).send(error.details[0].message) diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index 6b1e067..feed970 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -85,6 +85,12 @@ export const updateUserValidation = ( return Joi.object(validationChecks).validate(data) } +export const updatePasswordValidation = (data: any): Joi.ValidationResult => + Joi.object({ + currentPassword: Joi.string().required(), + newPassword: passwordSchema.required() + }).validate(data) + export const registerClientValidation = (data: any): Joi.ValidationResult => Joi.object({ clientId: Joi.string().required(),