Skip to content

Commit 6dcb65c

Browse files
authored
feature/cli-reset-password (#4585)
* feat: add cli to reset password * chore: add information for password reset command * fix: add information for password reset command
1 parent 2cd8db0 commit 6dcb65c

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
"start-worker": "run-script-os",
2121
"start-worker:windows": "cd packages/server/bin && run worker",
2222
"start-worker:default": "cd packages/server/bin && ./run worker",
23+
"user": "run-script-os",
24+
"user:windows": "cd packages/server/bin && run user",
25+
"user:default": "cd packages/server/bin && ./run user",
2326
"test": "turbo run test",
2427
"clean": "pnpm --filter \"./packages/**\" clean",
2528
"nuke": "pnpm --filter \"./packages/**\" nuke && rimraf node_modules .turbo",

packages/server/src/commands/base.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Command, Flags } from '@oclif/core'
2-
import path from 'path'
32
import dotenv from 'dotenv'
3+
import path from 'path'
44
import logger from '../utils/logger'
55

66
dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true })
@@ -120,7 +120,7 @@ export abstract class BaseCommand extends Command {
120120
logger.error('unhandledRejection: ', err)
121121
})
122122

123-
const { flags } = await this.parse(BaseCommand)
123+
const { flags } = await this.parse(this.constructor as any)
124124
if (flags.PORT) process.env.PORT = flags.PORT
125125
if (flags.CORS_ORIGINS) process.env.CORS_ORIGINS = flags.CORS_ORIGINS
126126
if (flags.IFRAME_ORIGINS) process.env.IFRAME_ORIGINS = flags.IFRAME_ORIGINS

packages/server/src/commands/user.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { Args } from '@oclif/core'
2+
import { QueryRunner } from 'typeorm'
3+
import * as DataSource from '../DataSource'
4+
import { User } from '../enterprise/database/entities/user.entity'
5+
import { getHash } from '../enterprise/utils/encryption.util'
6+
import { isInvalidPassword } from '../enterprise/utils/validation.util'
7+
import logger from '../utils/logger'
8+
import { BaseCommand } from './base'
9+
10+
export default class user extends BaseCommand {
11+
static args = {
12+
email: Args.string({
13+
description: 'Email address to search for in the user database'
14+
}),
15+
password: Args.string({
16+
description: 'New password for that user'
17+
})
18+
}
19+
20+
async run(): Promise<void> {
21+
const { args } = await this.parse(user)
22+
23+
let queryRunner: QueryRunner | undefined
24+
try {
25+
logger.info('Initializing DataSource')
26+
const dataSource = await DataSource.getDataSource()
27+
await dataSource.initialize()
28+
29+
queryRunner = dataSource.createQueryRunner()
30+
await queryRunner.connect()
31+
32+
if (args.email && args.password) {
33+
logger.info('Running resetPassword')
34+
await this.resetPassword(queryRunner, args.email, args.password)
35+
} else {
36+
logger.info('Running listUserEmails')
37+
await this.listUserEmails(queryRunner)
38+
}
39+
} catch (error) {
40+
logger.error(error)
41+
} finally {
42+
if (queryRunner && !queryRunner.isReleased) await queryRunner.release()
43+
await this.gracefullyExit()
44+
}
45+
}
46+
47+
async listUserEmails(queryRunner: QueryRunner) {
48+
logger.info('Listing all user emails')
49+
const users = await queryRunner.manager.find(User, {
50+
select: ['email']
51+
})
52+
53+
const emails = users.map((user) => user.email)
54+
logger.info(`Email addresses: ${emails.join(', ')}`)
55+
logger.info(`Email count: ${emails.length}`)
56+
logger.info('To reset user password, run the following command: pnpm user --email "myEmail" --password "myPassword"')
57+
}
58+
59+
async resetPassword(queryRunner: QueryRunner, email: string, password: string) {
60+
logger.info(`Finding user by email: ${email}`)
61+
const user = await queryRunner.manager.findOne(User, {
62+
where: { email }
63+
})
64+
if (!user) throw new Error(`User not found with email: ${email}`)
65+
66+
if (isInvalidPassword(password)) {
67+
const errors = []
68+
if (!/(?=.*[a-z])/.test(password)) errors.push('at least one lowercase letter')
69+
if (!/(?=.*[A-Z])/.test(password)) errors.push('at least one uppercase letter')
70+
if (!/(?=.*\d)/.test(password)) errors.push('at least one number')
71+
if (!/(?=.*[^a-zA-Z0-9])/.test(password)) errors.push('at least one special character')
72+
if (password.length < 8) errors.push('minimum length of 8 characters')
73+
throw new Error(`Invalid password: Must contain ${errors.join(', ')}`)
74+
}
75+
76+
user.credential = getHash(password)
77+
await queryRunner.manager.save(user)
78+
logger.info(`Password reset for user: ${email}`)
79+
}
80+
}

0 commit comments

Comments
 (0)