diff --git a/CHANGELOG.md b/CHANGELOG.md index efd5286a9..253f2f761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated +## [0.13.1] - 2023-01-20 + +### Fixed + +- **API:** Fixed bug that prevented user activation and gave new users wrong usernames +- **Frontend:** Clicking on user popover should take you to the user page #686 +- **API:** Fixing some export issues due to changes in how the export transformer work because of the change to ses-node-json-transform for evalutaing untrusted code. #685 +- **Frontend**: Praise sort should be dark in dark mode #688 +- **Frontend**: Export dropdown gets squeezed when "close period" button is visible #684 #687 + +### Changed + +- **Frontend**: Better handling of relative dates #720 +- **Devops**: Added docker-compose installation script and modified scripts #754 +- **Devops**: Allow mongodb port number to be configurable in .env #760 +- **Devops**: Enhanced the multi-setup scripts #761 + ## [0.13.0] - 2022-11-18 ### Added diff --git a/package.json b/package.json index b29f3f2e0..78814c562 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "praise", - "version": "0.13.0", + "version": "0.13.1", "license": "GPL-3.0-or-later", "description": "Praise community contributions to build a culture of giving and gratitude.", "private": true, diff --git a/packages/api/package.json b/packages/api/package.json index dd0c129bd..0760359af 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "0.13.0", + "version": "0.13.1", "license": "GPL-3.0-or-later", "description": "The Praise REST API running on Node/Express, using MongoDB for storage.", "type": "commonjs", diff --git a/packages/api/src/activate/controllers.ts b/packages/api/src/activate/controllers.ts index 42dbac80c..7921d8225 100644 --- a/packages/api/src/activate/controllers.ts +++ b/packages/api/src/activate/controllers.ts @@ -11,6 +11,7 @@ import { logEvent } from '@/eventlog/utils'; import { TypedRequestBody } from '@/shared/types'; import { UserModel } from '@/user/entities'; import { UserAccountModel } from '@/useraccount/entities'; +import { generateUserNameFromAccount } from '@/user/utils/entity'; import { ActivateRequestBody } from './types'; import { generateActivateMessage } from './utils'; @@ -27,23 +28,28 @@ const activate = async ( ): Promise => { const { identityEthAddress, signature, accountId } = req.body; - if (!identityEthAddress || !signature || !accountId) + if (!identityEthAddress || !signature || !accountId) { throw new BadRequestError( 'identityEthAddress, signature, and accountId required' ); + } // Find previously generated token const userAccount = await UserAccountModel.findOne({ accountId }) - .select('_id user activateToken') + .select('_id user activateToken name platform') .exec(); - if (!userAccount) throw new NotFoundError('UserAccount'); - if (!userAccount.activateToken) + if (!userAccount) { + throw new NotFoundError('UserAccount'); + } + if (!userAccount.activateToken) { throw new InternalServerError('Activation token not found.'); + } // You are only allowed to activate once - if (userAccount.user) + if (userAccount.user) { throw new BadRequestError('User account already activated.'); + } // Generate expected message, token included. const generatedMsg = generateActivateMessage( @@ -55,15 +61,34 @@ const activate = async ( // Verify signature against generated message // Recover signer and compare against query address const signerAddress = ethers.utils.verifyMessage(generatedMsg, signature); - if (signerAddress !== identityEthAddress) + if (signerAddress !== identityEthAddress) { throw new UnauthorizedError('Verification failed.'); + } // Find existing user or create new const user = await UserModel.findOneAndUpdate( { identityEthAddress }, { upsert: true, new: true } ); - if (!user) throw new NotFoundError('User'); + if (!user) { + throw new NotFoundError('User'); + } + + // Set rewardsEthAddress if not set + if (!user.rewardsEthAddress) { + user.rewardsEthAddress = identityEthAddress; + } + + // Set username if not set + if (!user.username || user.username === identityEthAddress) { + const generatedUserName = await generateUserNameFromAccount(userAccount); + if (generatedUserName) { + user.username = generatedUserName; + } + } + + // Save user changes + await user.save(); // Link user account with user userAccount.user = user; diff --git a/packages/api/src/database/seeder/app.ts b/packages/api/src/database/seeder/app.ts index 2aa51cc30..511e52828 100644 --- a/packages/api/src/database/seeder/app.ts +++ b/packages/api/src/database/seeder/app.ts @@ -109,7 +109,7 @@ export const seedAdminUsers = async (): Promise => { await UserModel.create({ identityEthAddress: e, rewardsEthAddress: e, - username: faker.internet.userName(), + username: e, roles: [UserRole.ADMIN, UserRole.USER], }); } diff --git a/packages/api/src/user/utils/entity.ts b/packages/api/src/user/utils/entity.ts index da4966065..3884bd3fb 100644 --- a/packages/api/src/user/utils/entity.ts +++ b/packages/api/src/user/utils/entity.ts @@ -24,9 +24,20 @@ export const generateUserNameFromAccount = async ( username = userAccount.name; } - const exists = await UserModel.find({ username }).lean(); - if (exists.length === 0) return username; - if (userAccount.platform === 'DISCORD') return userAccount.name; + // Return username if it is not taken + let exists = await UserModel.find({ username }).lean(); + if (exists.length === 0) { + return username; + } + + // If username is taken then try to create one with the Discord discriminator + if (userAccount.platform === 'DISCORD') { + exists = await UserModel.find({ username: userAccount.name }).lean(); + if (exists.length === 0) return userAccount.name; + return userAccount.name; + } + + // Unable to generate username return null; }; diff --git a/packages/discord-bot/package.json b/packages/discord-bot/package.json index 406f4d64c..06d56601b 100644 --- a/packages/discord-bot/package.json +++ b/packages/discord-bot/package.json @@ -1,6 +1,6 @@ { "name": "discord-bot", - "version": "0.13.0", + "version": "0.13.1", "license": "GPL-3.0-or-later", "description": "The Praise Discord bot is the main way for users to interact with the Praise system.", "dependencies": { diff --git a/packages/frontend/package.json b/packages/frontend/package.json index ede9fe5e2..5b85d6dbc 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "0.13.0", + "version": "0.13.1", "license": "GPL-3.0-or-later", "description": "The Praise dashboard built on React/Recoil/Tailwind CSS.", "private": true, diff --git a/packages/frontend/src/model/app.ts b/packages/frontend/src/model/app.ts index f06a6fb96..2289062b2 100644 --- a/packages/frontend/src/model/app.ts +++ b/packages/frontend/src/model/app.ts @@ -40,7 +40,7 @@ interface PraiseAppVersion { export const usePraiseAppVersion = (): PraiseAppVersion => { const appVersion: PraiseAppVersion = { - current: '0.13.0', //TODO: get this from package.json + current: '0.13.1', //TODO: get this from package.json latest: undefined, newVersionAvailable: false, }; diff --git a/packages/frontend/src/utils/axios.ts b/packages/frontend/src/utils/axios.ts index f8d751fb6..3f885cbf6 100644 --- a/packages/frontend/src/utils/axios.ts +++ b/packages/frontend/src/utils/axios.ts @@ -9,17 +9,14 @@ import { toast } from 'react-hot-toast'; export const handleErrors = (err: AxiosError): AxiosError => { // Any HTTP Code which is not 2xx will be considered as error - if (err?.response) { - const statusCode = err?.response?.status; - if (statusCode === 404) { - // Resource not found - // Redirect to 404 page - window.location.href = '/404'; - } else if ([403, 400].includes(statusCode)) { - // Forbidden or bad request - const isJsonBlob = (data): data is Blob => - data instanceof Blob && data.type === 'application/json'; - // If the response is a json blob, parse it and display the error message + const isJsonBlob = (data): data is Blob => + data instanceof Blob && data.type === 'application/json'; + + const displayMessageOrDefault = ( + err: AxiosError, + defaultMessage: string + ): void => { + if (err.response) { if (isJsonBlob(err.response.data)) { void (err.response.data as Blob).text().then((text) => { const json = JSON.parse(text); @@ -27,12 +24,27 @@ export const handleErrors = (err: AxiosError): AxiosError => { }); } else if ((err.response.data as Error).message) { toast.error((err.response.data as Error).message); - } else { - toast.error('Something went wrong'); } + } else { + toast.error(defaultMessage); + } + }; + + if (err?.response) { + const statusCode = err.response.status; + if (statusCode === 404) { + // Resource not found + // Redirect to 404 page + window.location.href = '/404'; + } else if (statusCode === 400) { + displayMessageOrDefault(err, 'Bad request'); } else if (statusCode === 401) { // Unauthorized window.location.href = '/'; + } else if (statusCode === 403) { + displayMessageOrDefault(err, 'Forbidden'); + } else if (statusCode === 500) { + displayMessageOrDefault(err, 'Internal Server Error'); } else { toast.error('Unknown Error'); } diff --git a/packages/mongodb/package.json b/packages/mongodb/package.json index 8bedb1d51..9d210af90 100644 --- a/packages/mongodb/package.json +++ b/packages/mongodb/package.json @@ -1,6 +1,6 @@ { "name": "mongodb", - "version": "0.13.0", + "version": "0.13.1", "license": "GPL-3.0-or-later", "description": "The Prasie data is stored in a MongoDb database." } diff --git a/packages/setup/package.json b/packages/setup/package.json index ba8ccdcd9..932d63968 100644 --- a/packages/setup/package.json +++ b/packages/setup/package.json @@ -1,6 +1,6 @@ { "name": "setup", - "version": "0.13.0", + "version": "0.13.1", "license": "GPL-3.0-or-later", "type": "commonjs", "description": "Praise ENV setup scripts.",