mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-03 09:49:20 +02:00
feat: add user password constraints (#6945)
* feat: add user password length constraints
* add password length changes in locale files
* revert maximum password length changes
* add tests
* fix lint
* fix lint and test
* fix tests
* Revert "add password length changes in locale files"
This reverts commit eaaf63ba7c
.
* Update PR
---------
Co-authored-by: Chocobozzz <me@florianbigard.com>
This commit is contained in:
parent
eedfb8b0a2
commit
efa32646ed
23 changed files with 195 additions and 76 deletions
|
@ -6,12 +6,12 @@ import { AuthService, Notifier, ScreenService, ServerService } from '@app/core'
|
|||
import {
|
||||
USER_CHANNEL_NAME_VALIDATOR,
|
||||
USER_EMAIL_VALIDATOR,
|
||||
USER_PASSWORD_OPTIONAL_VALIDATOR,
|
||||
USER_PASSWORD_VALIDATOR,
|
||||
USER_ROLE_VALIDATOR,
|
||||
USER_USERNAME_VALIDATOR,
|
||||
USER_VIDEO_QUOTA_DAILY_VALIDATOR,
|
||||
USER_VIDEO_QUOTA_VALIDATOR
|
||||
USER_VIDEO_QUOTA_VALIDATOR,
|
||||
getUserNewPasswordOptionalValidator,
|
||||
getUserNewPasswordValidator
|
||||
} from '@app/shared/form-validators/user-validators'
|
||||
import { AdminConfigService } from '@app/shared/shared-admin/admin-config.service'
|
||||
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
|
||||
|
@ -76,11 +76,17 @@ export class UserCreateComponent extends UserEdit implements OnInit {
|
|||
videoQuotaDaily: -1
|
||||
}
|
||||
|
||||
const passwordConstraints = this.serverService.getHTMLConfig().fieldsConstraints.users.password
|
||||
|
||||
this.buildForm({
|
||||
username: USER_USERNAME_VALIDATOR,
|
||||
channelName: USER_CHANNEL_NAME_VALIDATOR,
|
||||
email: USER_EMAIL_VALIDATOR,
|
||||
password: this.isPasswordOptional() ? USER_PASSWORD_OPTIONAL_VALIDATOR : USER_PASSWORD_VALIDATOR,
|
||||
|
||||
password: this.isPasswordOptional()
|
||||
? getUserNewPasswordOptionalValidator(passwordConstraints.minLength, passwordConstraints.maxLength)
|
||||
: getUserNewPasswordValidator(passwordConstraints.minLength, passwordConstraints.maxLength),
|
||||
|
||||
role: USER_ROLE_VALIDATOR,
|
||||
videoQuota: USER_VIDEO_QUOTA_VALIDATOR,
|
||||
videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { NgClass, NgIf } from '@angular/common'
|
||||
import { Component, OnInit, inject, input } from '@angular/core'
|
||||
import { Notifier } from '@app/core'
|
||||
import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { Notifier, ServerService } from '@app/core'
|
||||
import { getUserNewPasswordValidator } from '@app/shared/form-validators/user-validators'
|
||||
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
|
||||
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
|
||||
import { UserUpdate } from '@peertube/peertube-models'
|
||||
import { NgClass, NgIf } from '@angular/common'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { UserAdminService } from '@app/shared/shared-users/user-admin.service'
|
||||
import { UserUpdate } from '@peertube/peertube-models'
|
||||
|
||||
@Component({
|
||||
selector: 'my-user-password',
|
||||
|
@ -18,6 +18,7 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
|
|||
protected formReactiveService = inject(FormReactiveService)
|
||||
private notifier = inject(Notifier)
|
||||
private userAdminService = inject(UserAdminService)
|
||||
private serverService = inject(ServerService)
|
||||
|
||||
readonly userId = input<number>(undefined)
|
||||
readonly username = input<string>(undefined)
|
||||
|
@ -26,9 +27,9 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
|
|||
showPassword = false
|
||||
|
||||
ngOnInit () {
|
||||
this.buildForm({
|
||||
password: USER_PASSWORD_VALIDATOR
|
||||
})
|
||||
const { minLength, maxLength } = this.serverService.getHTMLConfig().fieldsConstraints.users.password
|
||||
|
||||
this.buildForm({ password: getUserNewPasswordValidator(minLength, maxLength) })
|
||||
}
|
||||
|
||||
formValidated () {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { NgIf } from '@angular/common'
|
||||
import { Component, OnInit, inject } from '@angular/core'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { AuthService, Notifier, UserService } from '@app/core'
|
||||
import { AuthService, Notifier, ServerService, UserService } from '@app/core'
|
||||
import {
|
||||
USER_CONFIRM_PASSWORD_VALIDATOR,
|
||||
USER_EXISTING_PASSWORD_VALIDATOR,
|
||||
USER_PASSWORD_VALIDATOR
|
||||
getUserNewPasswordValidator
|
||||
} from '@app/shared/form-validators/user-validators'
|
||||
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
|
||||
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
|
||||
|
@ -25,14 +25,17 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
|
|||
private notifier = inject(Notifier)
|
||||
private authService = inject(AuthService)
|
||||
private userService = inject(UserService)
|
||||
private serverService = inject(ServerService)
|
||||
|
||||
error: string
|
||||
user: User
|
||||
|
||||
ngOnInit () {
|
||||
const { minLength, maxLength } = this.serverService.getHTMLConfig().fieldsConstraints.users.password
|
||||
|
||||
this.buildForm({
|
||||
'current-password': USER_EXISTING_PASSWORD_VALIDATOR,
|
||||
'new-password': USER_PASSWORD_VALIDATOR,
|
||||
'new-password': getUserNewPasswordValidator(minLength, maxLength),
|
||||
'new-confirmed-password': USER_CONFIRM_PASSWORD_VALIDATOR
|
||||
})
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Component, OnInit, inject } from '@angular/core'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Notifier, UserService } from '@app/core'
|
||||
import { Notifier, ServerService, UserService } from '@app/core'
|
||||
import { RESET_PASSWORD_CONFIRM_VALIDATOR } from '@app/shared/form-validators/reset-password-validators'
|
||||
import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators'
|
||||
import { getUserNewPasswordValidator } from '@app/shared/form-validators/user-validators'
|
||||
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
|
||||
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
|
||||
import { InputTextComponent } from '../shared/shared-forms/input-text.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
|
||||
@Component({
|
||||
templateUrl: './reset-password.component.html',
|
||||
|
@ -16,6 +16,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
|||
export class ResetPasswordComponent extends FormReactive implements OnInit {
|
||||
protected formReactiveService = inject(FormReactiveService)
|
||||
private userService = inject(UserService)
|
||||
private serverService = inject(ServerService)
|
||||
private notifier = inject(Notifier)
|
||||
private router = inject(Router)
|
||||
private route = inject(ActivatedRoute)
|
||||
|
@ -24,8 +25,10 @@ export class ResetPasswordComponent extends FormReactive implements OnInit {
|
|||
private verificationString: string
|
||||
|
||||
ngOnInit () {
|
||||
const { minLength, maxLength } = this.serverService.getHTMLConfig().fieldsConstraints.users.password
|
||||
|
||||
this.buildForm({
|
||||
'password': USER_PASSWORD_VALIDATOR,
|
||||
'password': getUserNewPasswordValidator(minLength, maxLength),
|
||||
'password-confirm': RESET_PASSWORD_CONFIRM_VALIDATOR
|
||||
})
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<div class="col-md-12 col-xl-6 form-group">
|
||||
<label for="password" i18n>Password</label>
|
||||
|
||||
<div class="form-group-description">{{ getMinPasswordLengthMessage() }}</div>
|
||||
<div class="form-group-description">{{ minPasswordLengthMessage }}</div>
|
||||
|
||||
<my-input-text
|
||||
formControlName="password" inputId="password"
|
||||
|
|
|
@ -2,11 +2,12 @@ import { NgClass, NgIf } from '@angular/common'
|
|||
import { Component, OnInit, inject, input, output } from '@angular/core'
|
||||
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { SignupService } from '@app/+signup/shared/signup.service'
|
||||
import { ServerService } from '@app/core'
|
||||
import {
|
||||
USER_DISPLAY_NAME_REQUIRED_VALIDATOR,
|
||||
USER_EMAIL_VALIDATOR,
|
||||
USER_PASSWORD_VALIDATOR,
|
||||
USER_USERNAME_VALIDATOR
|
||||
USER_USERNAME_VALIDATOR,
|
||||
getUserNewPasswordValidator
|
||||
} from '@app/shared/form-validators/user-validators'
|
||||
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
|
||||
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
|
||||
|
@ -24,21 +25,29 @@ import { InputTextComponent } from '../../../shared/shared-forms/input-text.comp
|
|||
export class RegisterStepUserComponent extends FormReactive implements OnInit {
|
||||
protected formReactiveService = inject(FormReactiveService)
|
||||
private signupService = inject(SignupService)
|
||||
private serverService = inject(ServerService)
|
||||
|
||||
readonly videoUploadDisabled = input(false)
|
||||
readonly requiresEmailVerification = input(false)
|
||||
|
||||
readonly formBuilt = output<FormGroup>()
|
||||
|
||||
minPasswordLengthMessage: string
|
||||
|
||||
get instanceHost () {
|
||||
return window.location.host
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
const passwordConstraints = this.serverService.getHTMLConfig().fieldsConstraints.users.password
|
||||
const passwordValidator = getUserNewPasswordValidator(passwordConstraints.minLength, passwordConstraints.maxLength)
|
||||
|
||||
this.minPasswordLengthMessage = passwordValidator.MESSAGES.minlength
|
||||
|
||||
this.buildForm({
|
||||
displayName: USER_DISPLAY_NAME_REQUIRED_VALIDATOR,
|
||||
username: USER_USERNAME_VALIDATOR,
|
||||
password: USER_PASSWORD_VALIDATOR,
|
||||
password: passwordValidator,
|
||||
email: USER_EMAIL_VALIDATOR
|
||||
})
|
||||
|
||||
|
@ -51,10 +60,6 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
|
|||
.subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
|
||||
}
|
||||
|
||||
getMinPasswordLengthMessage () {
|
||||
return USER_PASSWORD_VALIDATOR.MESSAGES.minlength
|
||||
}
|
||||
|
||||
private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
|
||||
const username = this.form.value['username'] || ''
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Validators } from '@angular/forms'
|
||||
import { ValidatorFn, Validators } from '@angular/forms'
|
||||
import { BuildFormValidator } from './form-validator.model'
|
||||
|
||||
export const USER_USERNAME_REGEX_CHARACTERS = '[a-z0-9][a-z0-9._]'
|
||||
|
@ -70,27 +70,33 @@ export const USER_OTP_TOKEN_VALIDATOR: BuildFormValidator = {
|
|||
}
|
||||
}
|
||||
|
||||
export const USER_PASSWORD_VALIDATOR = {
|
||||
export function getUserNewPasswordValidator (minLength: number, maxLength: number) {
|
||||
const base = getUserNewPasswordOptionalValidator(minLength, maxLength)
|
||||
|
||||
return {
|
||||
VALIDATORS: [
|
||||
Validators.required,
|
||||
Validators.minLength(6),
|
||||
Validators.maxLength(255)
|
||||
],
|
||||
|
||||
...base.VALIDATORS
|
||||
] as ValidatorFn[],
|
||||
MESSAGES: {
|
||||
required: $localize`Password is required.`,
|
||||
minlength: $localize`Password must be at least 6 characters long.`,
|
||||
maxlength: $localize`Password cannot be more than 255 characters long.`
|
||||
|
||||
...base.MESSAGES
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const USER_PASSWORD_OPTIONAL_VALIDATOR: BuildFormValidator = {
|
||||
export function getUserNewPasswordOptionalValidator (minLength: number, maxLength: number) {
|
||||
return {
|
||||
VALIDATORS: [
|
||||
Validators.minLength(6),
|
||||
Validators.maxLength(255)
|
||||
],
|
||||
Validators.minLength(minLength),
|
||||
Validators.maxLength(maxLength)
|
||||
] as ValidatorFn[],
|
||||
MESSAGES: {
|
||||
minlength: $localize`Password must be at least 6 characters long.`,
|
||||
maxlength: $localize`Password cannot be more than 255 characters long.`
|
||||
minlength: $localize`Password must be at least ${minLength} characters long.`,
|
||||
maxlength: $localize`Password cannot be more than ${maxLength} characters long.`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -567,6 +567,9 @@ user:
|
|||
|
||||
default_channel_name: 'Main $1 channel' # The placeholder $1 is used to represent the user's username
|
||||
|
||||
password_constraints:
|
||||
min_length: 8
|
||||
|
||||
video_channels:
|
||||
max_per_user: 20 # Allows each user to create up to 20 video channels.
|
||||
|
||||
|
|
|
@ -141,3 +141,7 @@ video_studio:
|
|||
|
||||
transcoding:
|
||||
keep_original_file: false
|
||||
|
||||
user:
|
||||
password_constraints:
|
||||
min_length: 6
|
||||
|
|
|
@ -577,6 +577,9 @@ user:
|
|||
|
||||
default_channel_name: 'Main $1 channel' # The placeholder $1 is used to represent the user's username
|
||||
|
||||
password_constraints:
|
||||
min_length: 8
|
||||
|
||||
video_channels:
|
||||
max_per_user: 20 # Allows each user to create up to 20 video channels.
|
||||
|
||||
|
@ -1196,5 +1199,5 @@ email:
|
|||
|
||||
video_comments:
|
||||
# Accept or not comments from remote instances
|
||||
# This setting is not retroactive: current remote comments of your instance will not be affected
|
||||
# This setting is not retroactive: current comments from remote platforms will not be deleted
|
||||
accept_remote_comments: true
|
||||
|
|
|
@ -172,3 +172,7 @@ search:
|
|||
|
||||
video_transcription:
|
||||
model: 'tiny'
|
||||
|
||||
user:
|
||||
password_constraints:
|
||||
min_length: 6
|
||||
|
|
|
@ -439,6 +439,15 @@ export interface ServerConfig {
|
|||
nsfwFlagsSettings: {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
fieldsConstraints: {
|
||||
users: {
|
||||
password: {
|
||||
minLength: number
|
||||
maxLength: number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type HTMLServerConfig = Omit<ServerConfig, 'signup'>
|
||||
|
|
|
@ -27,10 +27,10 @@ describe('Test registrations API validators', function () {
|
|||
await setDefaultAccountAvatar([ server ])
|
||||
await setDefaultChannelAvatar([ server ])
|
||||
|
||||
await server.config.enableSignup(false);
|
||||
await server.config.enableSignup(false)
|
||||
|
||||
({ token: moderatorToken } = await server.users.generate('moderator', UserRole.MODERATOR));
|
||||
({ token: userToken } = await server.users.generate('user', UserRole.USER))
|
||||
;({ token: moderatorToken } = await server.users.generate('moderator', UserRole.MODERATOR))
|
||||
;({ token: userToken } = await server.users.generate('user', UserRole.USER))
|
||||
})
|
||||
|
||||
describe('Register', function () {
|
||||
|
@ -46,7 +46,6 @@ describe('Test registrations API validators', function () {
|
|||
}
|
||||
|
||||
describe('When registering a new user or requesting user registration', function () {
|
||||
|
||||
async function check (fields: any, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
|
||||
await server.config.enableSignup(false)
|
||||
await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus })
|
||||
|
@ -209,7 +208,6 @@ describe('Test registrations API validators', function () {
|
|||
})
|
||||
|
||||
describe('On direct registration', function () {
|
||||
|
||||
it('Should succeed with the correct params', async function () {
|
||||
await server.config.enableSignup(false)
|
||||
|
||||
|
@ -233,7 +231,6 @@ describe('Test registrations API validators', function () {
|
|||
})
|
||||
|
||||
describe('On registration request', function () {
|
||||
|
||||
before(async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
|
@ -321,10 +318,10 @@ describe('Test registrations API validators', function () {
|
|||
before(async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
await server.config.enableSignup(true);
|
||||
await server.config.enableSignup(true)
|
||||
|
||||
({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_2', registrationReason: 'toto' }));
|
||||
({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_3', registrationReason: 'toto' }))
|
||||
;({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_2', registrationReason: 'toto' }))
|
||||
;({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_3', registrationReason: 'toto' }))
|
||||
})
|
||||
|
||||
it('Should fail to accept/reject registration without token', async function () {
|
||||
|
@ -384,9 +381,9 @@ describe('Test registrations API validators', function () {
|
|||
let id3: number
|
||||
|
||||
before(async function () {
|
||||
({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_4', registrationReason: 'toto' }));
|
||||
({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_5', registrationReason: 'toto' }));
|
||||
({ id: id3 } = await server.registrations.requestRegistration({ username: 'request_6', registrationReason: 'toto' }))
|
||||
;({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_4', registrationReason: 'toto' }))
|
||||
;({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_5', registrationReason: 'toto' }))
|
||||
;({ id: id3 } = await server.registrations.requestRegistration({ username: 'request_6', registrationReason: 'toto' }))
|
||||
|
||||
await server.registrations.accept({ id: id2, moderationResponse: 'tt' })
|
||||
await server.registrations.reject({ id: id3, moderationResponse: 'tt' })
|
||||
|
|
|
@ -37,7 +37,6 @@ describe('Test abuses', function () {
|
|||
})
|
||||
|
||||
describe('Video abuses', function () {
|
||||
|
||||
before(async function () {
|
||||
this.timeout(50000)
|
||||
|
||||
|
@ -315,8 +314,8 @@ describe('Test abuses', function () {
|
|||
const abuse = body.data.find(a => a.id === createRes.abuse.id)
|
||||
expect(abuse.reason).to.equals(reason5)
|
||||
expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, 'predefined reasons do not match the one reported')
|
||||
expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported")
|
||||
expect(abuse.video.endAt).to.equal(5, "ending timestamp doesn't match the one reported")
|
||||
expect(abuse.video.startAt).to.equal(1, 'starting timestamp doesn\'t match the one reported')
|
||||
expect(abuse.video.endAt).to.equal(5, 'ending timestamp doesn\'t match the one reported')
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -374,7 +373,6 @@ describe('Test abuses', function () {
|
|||
})
|
||||
|
||||
describe('Comment abuses', function () {
|
||||
|
||||
async function getComment (server: PeerTubeServer, videoIdArg: number | string) {
|
||||
const videoId = typeof videoIdArg === 'string'
|
||||
? await server.videos.getId({ uuid: videoIdArg })
|
||||
|
@ -570,7 +568,6 @@ describe('Test abuses', function () {
|
|||
})
|
||||
|
||||
describe('Account abuses', function () {
|
||||
|
||||
function getAccountFromServer (server: PeerTubeServer, targetName: string, targetServer: PeerTubeServer) {
|
||||
return server.accounts.get({ accountName: targetName + '@' + targetServer.host })
|
||||
}
|
||||
|
@ -711,7 +708,6 @@ describe('Test abuses', function () {
|
|||
})
|
||||
|
||||
describe('Common actions on abuses', function () {
|
||||
|
||||
it('Should update the state of an abuse', async function () {
|
||||
await commands[0].update({ abuseId: abuseServer1.id, body: { state: AbuseState.REJECTED } })
|
||||
|
||||
|
|
|
@ -1022,3 +1022,66 @@ describe('Test config', function () {
|
|||
await cleanupTests([ server ])
|
||||
})
|
||||
})
|
||||
|
||||
describe('YAML config', function () {
|
||||
let server: PeerTubeServer
|
||||
let userToken: string
|
||||
|
||||
before(async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
server = await createSingleServer(1, {
|
||||
user: {
|
||||
password_constraints: {
|
||||
min_length: 10
|
||||
}
|
||||
}
|
||||
})
|
||||
await setAccessTokensToServers([ server ])
|
||||
})
|
||||
|
||||
it('Should update the minimum length of a password', async function () {
|
||||
await server.users.create({ username: 'user10', password: 'short', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||
|
||||
await server.users.create({ username: 'user10', password: 's'.repeat(10) })
|
||||
})
|
||||
|
||||
it('Should still be able to login with an old password', async function () {
|
||||
})
|
||||
|
||||
it('Should update the minimum length of a password but still allow to login', async function () {
|
||||
await server.kill()
|
||||
|
||||
await server.run({
|
||||
user: {
|
||||
password_constraints: {
|
||||
min_length: 12
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const res = await server.login.login({ user: { username: 'user10', password: 's'.repeat(10) } })
|
||||
userToken = res.access_token
|
||||
})
|
||||
|
||||
it('Should be able to change the password', async function () {
|
||||
await server.users.updateMe({
|
||||
token: userToken,
|
||||
currentPassword: 's'.repeat(10),
|
||||
password: 'password',
|
||||
expectedStatus: HttpStatusCode.BAD_REQUEST_400
|
||||
})
|
||||
|
||||
await server.users.updateMe({
|
||||
token: userToken,
|
||||
currentPassword: 's'.repeat(10),
|
||||
password: 's'.repeat(12)
|
||||
})
|
||||
|
||||
await server.login.login({ user: { username: 'user10', password: 's'.repeat(12) } })
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests([ server ])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
|
||||
import { UserRegistrationState, UserRole } from '@peertube/peertube-models'
|
||||
import {
|
||||
cleanupTests,
|
||||
|
@ -11,6 +9,8 @@ import {
|
|||
setAccessTokensToServers,
|
||||
waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
|
||||
import { expect } from 'chai'
|
||||
|
||||
describe('Test registrations', function () {
|
||||
let server: PeerTubeServer
|
||||
|
|
|
@ -20,6 +20,10 @@ function createPrivateAndPublicKeys () {
|
|||
async function comparePassword (plainPassword: string, hashPassword: string) {
|
||||
if (!plainPassword) return false
|
||||
|
||||
if (Buffer.byteLength(plainPassword, 'utf8') > 72) {
|
||||
throw new Error('Cannot compare more than 72 bytes with bcrypt')
|
||||
}
|
||||
|
||||
const { compare } = await import('bcrypt')
|
||||
|
||||
return compare(plainPassword, hashPassword)
|
||||
|
@ -110,7 +114,6 @@ export {
|
|||
comparePassword,
|
||||
createPrivateAndPublicKeys,
|
||||
cryptPassword,
|
||||
|
||||
encrypt,
|
||||
decrypt
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ export function checkMissedConfig () {
|
|||
'user.history.videos.enabled',
|
||||
'user.video_quota',
|
||||
'user.video_quota_daily',
|
||||
'user.password_constraints.min_length',
|
||||
'video_channels.max_per_user',
|
||||
'csp.enabled',
|
||||
'csp.report_only',
|
||||
|
|
|
@ -507,6 +507,7 @@ const CONFIG = {
|
|||
get MINIMUM_AGE () {
|
||||
return config.get<number>('signup.minimum_age')
|
||||
},
|
||||
|
||||
FILTERS: {
|
||||
CIDR: {
|
||||
get WHITELIST () {
|
||||
|
@ -534,6 +535,11 @@ const CONFIG = {
|
|||
},
|
||||
get DEFAULT_CHANNEL_NAME () {
|
||||
return config.get<string>('user.default_channel_name')
|
||||
},
|
||||
PASSWORD_CONSTRAINTS: {
|
||||
get MIN_LENGTH () {
|
||||
return config.get<number>('user.password_constraints.min_length')
|
||||
}
|
||||
}
|
||||
},
|
||||
VIDEO_CHANNELS: {
|
||||
|
|
|
@ -366,7 +366,7 @@ export const CONSTRAINTS_FIELDS = {
|
|||
NAME: { min: 1, max: 120 }, // Length
|
||||
DESCRIPTION: { min: 3, max: 1000 }, // Length
|
||||
USERNAME: { min: 1, max: 50 }, // Length
|
||||
PASSWORD: { min: 6, max: 255 }, // Length
|
||||
PASSWORD: { min: CONFIG.USER.PASSWORD_CONSTRAINTS.MIN_LENGTH, max: 50 }, // Length
|
||||
VIDEO_QUOTA: { min: -1 },
|
||||
VIDEO_QUOTA_DAILY: { min: -1 },
|
||||
VIDEO_LANGUAGES: { max: 500 }, // Array length
|
||||
|
|
|
@ -397,6 +397,15 @@ class ServerConfigManager {
|
|||
|
||||
nsfwFlagsSettings: {
|
||||
enabled: CONFIG.NSFW_FLAGS_SETTINGS.ENABLED
|
||||
},
|
||||
|
||||
fieldsConstraints: {
|
||||
users: {
|
||||
password: {
|
||||
minLength: CONSTRAINTS_FIELDS.USERS.PASSWORD.min,
|
||||
maxLength: CONSTRAINTS_FIELDS.USERS.PASSWORD.max
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -423,7 +432,7 @@ class ServerConfigManager {
|
|||
minimumAge: CONFIG.SIGNUP.MINIMUM_AGE,
|
||||
requiresApproval: CONFIG.SIGNUP.REQUIRES_APPROVAL,
|
||||
requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
|
||||
}
|
||||
} satisfies ServerConfig['signup']
|
||||
|
||||
const htmlConfig = await this.getHTMLServerConfig()
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||
import { UserRegistration, UserRegistrationState, type UserRegistrationStateType } from '@peertube/peertube-models'
|
||||
import {
|
||||
isRegistrationModerationResponseValid,
|
||||
|
@ -22,10 +23,9 @@ import {
|
|||
Table,
|
||||
UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { isUserDisplayNameValid, isUserEmailVerifiedValid, isUserPasswordValid } from '../../helpers/custom-validators/users.js'
|
||||
import { SequelizeModel, getSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
|
||||
import { isUserDisplayNameValid, isUserEmailVerifiedValid } from '../../helpers/custom-validators/users.js'
|
||||
import { getSort, parseAggregateResult, SequelizeModel, throwIfNotValid } from '../shared/index.js'
|
||||
import { UserModel } from './user.js'
|
||||
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||
|
||||
@Table({
|
||||
tableName: 'userRegistration',
|
||||
|
@ -65,7 +65,6 @@ export class UserRegistrationModel extends SequelizeModel<UserRegistrationModel>
|
|||
declare moderationResponse: string
|
||||
|
||||
@AllowNull(true)
|
||||
@Is('RegistrationPassword', value => throwIfNotValid(value, isUserPasswordValid, 'registration password', true))
|
||||
@Column
|
||||
declare password: string
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@ import {
|
|||
isUserNoModal,
|
||||
isUserNSFWPolicyValid,
|
||||
isUserP2PEnabledValid,
|
||||
isUserPasswordValid,
|
||||
isUserRoleValid,
|
||||
isUserVideoLanguages,
|
||||
isUserVideoQuotaDailyValid,
|
||||
|
@ -290,7 +289,6 @@ type WhereUserIdScopeOptions = { whereUserId?: '$userId' | '"UserModel"."id"' }
|
|||
})
|
||||
export class UserModel extends SequelizeModel<UserModel> {
|
||||
@AllowNull(true)
|
||||
@Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true))
|
||||
@Column
|
||||
declare password: string
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue