mirror of
https://github.com/kokonect-link/cherrypick
synced 2025-01-24 02:34:00 +09:00
commit
81920f124d
@ -101,6 +101,7 @@
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.17.1",
|
||||
"archiver": "7.0.1",
|
||||
"argon2": "^0.40.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
|
@ -5,7 +5,8 @@
|
||||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import { IsNull, DataSource } from 'typeorm';
|
||||
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
||||
import { MiUser } from '@/models/User.js';
|
||||
@ -32,8 +33,8 @@ export class CreateSystemUserService {
|
||||
const password = randomUUID();
|
||||
|
||||
// Generate hash of password
|
||||
const salt = await bcrypt.genSalt(8);
|
||||
const hash = await bcrypt.hash(password, salt);
|
||||
//const salt = await bcrypt.genSalt(8);
|
||||
const hash = await argon2.hash(password);
|
||||
|
||||
// Generate secret
|
||||
const secret = generateNativeUserToken();
|
||||
|
@ -5,7 +5,8 @@
|
||||
|
||||
import { generateKeyPair } from 'node:crypto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { DataSource, IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
|
||||
@ -69,8 +70,8 @@ export class SignupService {
|
||||
}
|
||||
|
||||
// Generate hash of password
|
||||
const salt = await bcrypt.genSalt(8);
|
||||
hash = await bcrypt.hash(password, salt);
|
||||
//const salt = await bcrypt.genSalt(8);
|
||||
hash = await argon2.hash(password);
|
||||
}
|
||||
|
||||
// Generate secret
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import * as OTPAuth from 'otpauth';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@ -123,7 +124,7 @@ export class SigninApiService {
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(password, profile.password!);
|
||||
const same = await argon2.verify(profile.password!, password) || bcrypt.compareSync(password, profile.password!);
|
||||
|
||||
const fail = async (status?: number, failure?: { id: string }) => {
|
||||
// Append signin history
|
||||
@ -140,6 +141,12 @@ export class SigninApiService {
|
||||
|
||||
if (!profile.twoFactorEnabled) {
|
||||
if (same) {
|
||||
if (profile.password!.startsWith('$2')) {
|
||||
const newHash = await argon2.hash(password);
|
||||
this.userProfilesRepository.update(user.id, {
|
||||
password: newHash
|
||||
});
|
||||
}
|
||||
return this.signinService.signin(request, reply, user);
|
||||
} else {
|
||||
return await fail(403, {
|
||||
@ -156,6 +163,12 @@ export class SigninApiService {
|
||||
}
|
||||
|
||||
try {
|
||||
if (profile.password!.startsWith('$2')) {
|
||||
const newHash = await argon2.hash(password);
|
||||
this.userProfilesRepository.update(user.id, {
|
||||
password: newHash
|
||||
});
|
||||
}
|
||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||
} catch (e) {
|
||||
return await fail(403, {
|
||||
|
@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket, MiMeta } from '@/models/_.js';
|
||||
@ -179,8 +180,8 @@ export class SignupApiService {
|
||||
const code = secureRndstr(16, { chars: L_CHARS });
|
||||
|
||||
// Generate hash of password
|
||||
const salt = await bcrypt.genSalt(8);
|
||||
const hash = await bcrypt.hash(password, salt);
|
||||
//const salt = await bcrypt.genSalt(8);
|
||||
const hash = await argon2.hash(password);
|
||||
|
||||
const pendingUser = await this.userPendingsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
|
@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@ -65,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
const passwd = secureRndstr(8);
|
||||
|
||||
// Generate hash of password
|
||||
const hash = bcrypt.hashSync(passwd);
|
||||
const hash = await argon2.hash(passwd);
|
||||
|
||||
await this.userProfilesRepository.update({
|
||||
userId: user.id,
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
@ -85,7 +86,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
|
||||
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);
|
||||
if (!passwordMatched) {
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
@ -216,7 +217,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
|
||||
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);
|
||||
if (!passwordMatched) {
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import * as OTPAuth from 'otpauth';
|
||||
import * as QRCode from 'qrcode';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
@ -77,7 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
|
||||
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);
|
||||
if (!passwordMatched) {
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js';
|
||||
@ -66,7 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
|
||||
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);
|
||||
if (!passwordMatched) {
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
@ -62,7 +63,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? '');
|
||||
const passwordMatched = await argon2.verify(profile.password ?? '', ps.password);
|
||||
if (!passwordMatched) {
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserSecurityKeysRepository } from '@/models/_.js';
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
@ -50,15 +51,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatched = await bcrypt.compare(ps.currentPassword, profile.password!);
|
||||
const passwordMatched = await argon2.verify(profile.password!, ps.currentPassword);
|
||||
|
||||
if (!passwordMatched) {
|
||||
throw new Error('incorrect password');
|
||||
}
|
||||
|
||||
// Generate hash of password
|
||||
const salt = await bcrypt.genSalt(8);
|
||||
const hash = await bcrypt.hash(ps.newPassword, salt);
|
||||
//const salt = await bcrypt.genSalt(8);
|
||||
const hash = await argon2.hash(ps.newPassword);
|
||||
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
password: hash,
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
@ -59,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordMatched = await bcrypt.compare(ps.password, profile.password!);
|
||||
const passwordMatched = await argon2.verify(profile.password!, ps.password);
|
||||
if (!passwordMatched) {
|
||||
throw new Error('incorrect password');
|
||||
}
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
@ -43,7 +44,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||
const same = await argon2.verify(profile.password!, ps.password);
|
||||
|
||||
if (!same) {
|
||||
throw new Error('incorrect password');
|
||||
|
@ -5,7 +5,8 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
@ -96,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatched = await bcrypt.compare(ps.password, profile.password!);
|
||||
const passwordMatched = await argon2.verify(profile.password!, ps.password);
|
||||
if (!passwordMatched) {
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
@ -53,8 +54,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
}
|
||||
|
||||
// Generate hash of password
|
||||
const salt = await bcrypt.genSalt(8);
|
||||
const hash = await bcrypt.hash(ps.password, salt);
|
||||
//const salt = await bcrypt.genSalt(8);
|
||||
const hash = await argon2.hash(ps.password);
|
||||
|
||||
await this.userProfilesRepository.update(req.userId, {
|
||||
password: hash,
|
||||
|
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@ -188,6 +188,9 @@ importers:
|
||||
archiver:
|
||||
specifier: 7.0.1
|
||||
version: 7.0.1
|
||||
argon2:
|
||||
specifier: ^0.40.1
|
||||
version: 0.40.3
|
||||
async-mutex:
|
||||
specifier: 0.5.0
|
||||
version: 0.5.0
|
||||
@ -3558,6 +3561,10 @@ packages:
|
||||
resolution: {integrity: sha512-aGQIwo6/sWtyyqhVK4e1MtxYz4N1X8CNt6SOtCc+Wnczs5S5ONaLHDDR8LYaGn0MgOwvGgXyuZ5sJIfd7iyoUw==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
'@phc/format@1.0.0':
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@ -5434,6 +5441,10 @@ packages:
|
||||
arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
argon2@0.40.3:
|
||||
resolution: {integrity: sha512-FrSmz4VeM91jwFvvjsQv9GYp6o/kARWoYKjbjDB2U5io1H3e5X67PYGclFDeQff6UXIhUd4aHR3mxCdBbMMuQw==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
|
||||
@ -7373,12 +7384,10 @@ packages:
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
glob@8.1.0:
|
||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
global-dirs@3.0.1:
|
||||
resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
|
||||
@ -9098,6 +9107,10 @@ packages:
|
||||
node-addon-api@3.2.1:
|
||||
resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
|
||||
|
||||
node-addon-api@8.2.0:
|
||||
resolution: {integrity: sha512-qnyuI2ROiCkye42n9Tj5aX1ns7rzj6n7zW1XReSnLSL9v/vbLeR6fJq6PU27YU/ICfYw6W7Ouk/N7cysWu/hlw==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
node-bitmap@0.0.1:
|
||||
resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==}
|
||||
engines: {node: '>=v0.6.5'}
|
||||
@ -9149,6 +9162,10 @@ packages:
|
||||
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
||||
hasBin: true
|
||||
|
||||
node-gyp-build@4.8.2:
|
||||
resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==}
|
||||
hasBin: true
|
||||
|
||||
node-gyp@10.2.0:
|
||||
resolution: {integrity: sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==}
|
||||
engines: {node: ^16.14.0 || >=18.0.0}
|
||||
@ -14491,6 +14508,8 @@ snapshots:
|
||||
jsprim: 1.4.2
|
||||
sshpk: 1.17.0
|
||||
|
||||
'@phc/format@1.0.0': {}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
@ -16923,6 +16942,12 @@ snapshots:
|
||||
|
||||
arg@5.0.2: {}
|
||||
|
||||
argon2@0.40.3:
|
||||
dependencies:
|
||||
'@phc/format': 1.0.0
|
||||
node-addon-api: 8.2.0
|
||||
node-gyp-build: 4.8.2
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
@ -21746,6 +21771,8 @@ snapshots:
|
||||
node-addon-api@3.2.1:
|
||||
optional: true
|
||||
|
||||
node-addon-api@8.2.0: {}
|
||||
|
||||
node-bitmap@0.0.1: {}
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
@ -21782,6 +21809,8 @@ snapshots:
|
||||
node-gyp-build@4.6.0:
|
||||
optional: true
|
||||
|
||||
node-gyp-build@4.8.2: {}
|
||||
|
||||
node-gyp@10.2.0:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
|
Loading…
Reference in New Issue
Block a user