From e364e31e25bd1d4b8d801c845a96d6be708f0a18 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 19 Jan 2023 09:27:16 +0100 Subject: Implement signup approval in server --- .../sql/user-notitication-list-query-builder.ts | 130 ++++++----- server/models/user/user-notification.ts | 26 +++ server/models/user/user-registration.ts | 259 +++++++++++++++++++++ server/models/user/user.ts | 17 +- 4 files changed, 361 insertions(+), 71 deletions(-) create mode 100644 server/models/user/user-registration.ts (limited to 'server/models') diff --git a/server/models/user/sql/user-notitication-list-query-builder.ts b/server/models/user/sql/user-notitication-list-query-builder.ts index d11546df0..7b29807a3 100644 --- a/server/models/user/sql/user-notitication-list-query-builder.ts +++ b/server/models/user/sql/user-notitication-list-query-builder.ts @@ -180,7 +180,9 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery { "Account->Actor->Avatars"."type" AS "Account.Actor.Avatars.type", "Account->Actor->Avatars"."filename" AS "Account.Actor.Avatars.filename", "Account->Actor->Server"."id" AS "Account.Actor.Server.id", - "Account->Actor->Server"."host" AS "Account.Actor.Server.host"` + "Account->Actor->Server"."host" AS "Account.Actor.Server.host", + "UserRegistration"."id" AS "UserRegistration.id", + "UserRegistration"."username" AS "UserRegistration.username"` } private getJoins () { @@ -196,74 +198,76 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery { ON "Video->VideoChannel->Actor"."serverId" = "Video->VideoChannel->Actor->Server"."id" ) ON "UserNotificationModel"."videoId" = "Video"."id" - LEFT JOIN ( - "videoComment" AS "VideoComment" - INNER JOIN "account" AS "VideoComment->Account" ON "VideoComment"."accountId" = "VideoComment->Account"."id" - INNER JOIN "actor" AS "VideoComment->Account->Actor" ON "VideoComment->Account"."actorId" = "VideoComment->Account->Actor"."id" - LEFT JOIN "actorImage" AS "VideoComment->Account->Actor->Avatars" - ON "VideoComment->Account->Actor"."id" = "VideoComment->Account->Actor->Avatars"."actorId" - AND "VideoComment->Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR} - LEFT JOIN "server" AS "VideoComment->Account->Actor->Server" - ON "VideoComment->Account->Actor"."serverId" = "VideoComment->Account->Actor->Server"."id" - INNER JOIN "video" AS "VideoComment->Video" ON "VideoComment"."videoId" = "VideoComment->Video"."id" - ) ON "UserNotificationModel"."commentId" = "VideoComment"."id" + LEFT JOIN ( + "videoComment" AS "VideoComment" + INNER JOIN "account" AS "VideoComment->Account" ON "VideoComment"."accountId" = "VideoComment->Account"."id" + INNER JOIN "actor" AS "VideoComment->Account->Actor" ON "VideoComment->Account"."actorId" = "VideoComment->Account->Actor"."id" + LEFT JOIN "actorImage" AS "VideoComment->Account->Actor->Avatars" + ON "VideoComment->Account->Actor"."id" = "VideoComment->Account->Actor->Avatars"."actorId" + AND "VideoComment->Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR} + LEFT JOIN "server" AS "VideoComment->Account->Actor->Server" + ON "VideoComment->Account->Actor"."serverId" = "VideoComment->Account->Actor->Server"."id" + INNER JOIN "video" AS "VideoComment->Video" ON "VideoComment"."videoId" = "VideoComment->Video"."id" + ) ON "UserNotificationModel"."commentId" = "VideoComment"."id" + + LEFT JOIN "abuse" AS "Abuse" ON "UserNotificationModel"."abuseId" = "Abuse"."id" + LEFT JOIN "videoAbuse" AS "Abuse->VideoAbuse" ON "Abuse"."id" = "Abuse->VideoAbuse"."abuseId" + LEFT JOIN "video" AS "Abuse->VideoAbuse->Video" ON "Abuse->VideoAbuse"."videoId" = "Abuse->VideoAbuse->Video"."id" + LEFT JOIN "commentAbuse" AS "Abuse->VideoCommentAbuse" ON "Abuse"."id" = "Abuse->VideoCommentAbuse"."abuseId" + LEFT JOIN "videoComment" AS "Abuse->VideoCommentAbuse->VideoComment" + ON "Abuse->VideoCommentAbuse"."videoCommentId" = "Abuse->VideoCommentAbuse->VideoComment"."id" + LEFT JOIN "video" AS "Abuse->VideoCommentAbuse->VideoComment->Video" + ON "Abuse->VideoCommentAbuse->VideoComment"."videoId" = "Abuse->VideoCommentAbuse->VideoComment->Video"."id" + LEFT JOIN ( + "account" AS "Abuse->FlaggedAccount" + INNER JOIN "actor" AS "Abuse->FlaggedAccount->Actor" ON "Abuse->FlaggedAccount"."actorId" = "Abuse->FlaggedAccount->Actor"."id" + LEFT JOIN "actorImage" AS "Abuse->FlaggedAccount->Actor->Avatars" + ON "Abuse->FlaggedAccount->Actor"."id" = "Abuse->FlaggedAccount->Actor->Avatars"."actorId" + AND "Abuse->FlaggedAccount->Actor->Avatars"."type" = ${ActorImageType.AVATAR} + LEFT JOIN "server" AS "Abuse->FlaggedAccount->Actor->Server" + ON "Abuse->FlaggedAccount->Actor"."serverId" = "Abuse->FlaggedAccount->Actor->Server"."id" + ) ON "Abuse"."flaggedAccountId" = "Abuse->FlaggedAccount"."id" - LEFT JOIN "abuse" AS "Abuse" ON "UserNotificationModel"."abuseId" = "Abuse"."id" - LEFT JOIN "videoAbuse" AS "Abuse->VideoAbuse" ON "Abuse"."id" = "Abuse->VideoAbuse"."abuseId" - LEFT JOIN "video" AS "Abuse->VideoAbuse->Video" ON "Abuse->VideoAbuse"."videoId" = "Abuse->VideoAbuse->Video"."id" - LEFT JOIN "commentAbuse" AS "Abuse->VideoCommentAbuse" ON "Abuse"."id" = "Abuse->VideoCommentAbuse"."abuseId" - LEFT JOIN "videoComment" AS "Abuse->VideoCommentAbuse->VideoComment" - ON "Abuse->VideoCommentAbuse"."videoCommentId" = "Abuse->VideoCommentAbuse->VideoComment"."id" - LEFT JOIN "video" AS "Abuse->VideoCommentAbuse->VideoComment->Video" - ON "Abuse->VideoCommentAbuse->VideoComment"."videoId" = "Abuse->VideoCommentAbuse->VideoComment->Video"."id" - LEFT JOIN ( - "account" AS "Abuse->FlaggedAccount" - INNER JOIN "actor" AS "Abuse->FlaggedAccount->Actor" ON "Abuse->FlaggedAccount"."actorId" = "Abuse->FlaggedAccount->Actor"."id" - LEFT JOIN "actorImage" AS "Abuse->FlaggedAccount->Actor->Avatars" - ON "Abuse->FlaggedAccount->Actor"."id" = "Abuse->FlaggedAccount->Actor->Avatars"."actorId" - AND "Abuse->FlaggedAccount->Actor->Avatars"."type" = ${ActorImageType.AVATAR} - LEFT JOIN "server" AS "Abuse->FlaggedAccount->Actor->Server" - ON "Abuse->FlaggedAccount->Actor"."serverId" = "Abuse->FlaggedAccount->Actor->Server"."id" - ) ON "Abuse"."flaggedAccountId" = "Abuse->FlaggedAccount"."id" + LEFT JOIN ( + "videoBlacklist" AS "VideoBlacklist" + INNER JOIN "video" AS "VideoBlacklist->Video" ON "VideoBlacklist"."videoId" = "VideoBlacklist->Video"."id" + ) ON "UserNotificationModel"."videoBlacklistId" = "VideoBlacklist"."id" - LEFT JOIN ( - "videoBlacklist" AS "VideoBlacklist" - INNER JOIN "video" AS "VideoBlacklist->Video" ON "VideoBlacklist"."videoId" = "VideoBlacklist->Video"."id" - ) ON "UserNotificationModel"."videoBlacklistId" = "VideoBlacklist"."id" + LEFT JOIN "videoImport" AS "VideoImport" ON "UserNotificationModel"."videoImportId" = "VideoImport"."id" + LEFT JOIN "video" AS "VideoImport->Video" ON "VideoImport"."videoId" = "VideoImport->Video"."id" - LEFT JOIN "videoImport" AS "VideoImport" ON "UserNotificationModel"."videoImportId" = "VideoImport"."id" - LEFT JOIN "video" AS "VideoImport->Video" ON "VideoImport"."videoId" = "VideoImport->Video"."id" + LEFT JOIN "plugin" AS "Plugin" ON "UserNotificationModel"."pluginId" = "Plugin"."id" - LEFT JOIN "plugin" AS "Plugin" ON "UserNotificationModel"."pluginId" = "Plugin"."id" + LEFT JOIN "application" AS "Application" ON "UserNotificationModel"."applicationId" = "Application"."id" - LEFT JOIN "application" AS "Application" ON "UserNotificationModel"."applicationId" = "Application"."id" + LEFT JOIN ( + "actorFollow" AS "ActorFollow" + INNER JOIN "actor" AS "ActorFollow->ActorFollower" ON "ActorFollow"."actorId" = "ActorFollow->ActorFollower"."id" + INNER JOIN "account" AS "ActorFollow->ActorFollower->Account" + ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Account"."actorId" + LEFT JOIN "actorImage" AS "ActorFollow->ActorFollower->Avatars" + ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Avatars"."actorId" + AND "ActorFollow->ActorFollower->Avatars"."type" = ${ActorImageType.AVATAR} + LEFT JOIN "server" AS "ActorFollow->ActorFollower->Server" + ON "ActorFollow->ActorFollower"."serverId" = "ActorFollow->ActorFollower->Server"."id" + INNER JOIN "actor" AS "ActorFollow->ActorFollowing" ON "ActorFollow"."targetActorId" = "ActorFollow->ActorFollowing"."id" + LEFT JOIN "videoChannel" AS "ActorFollow->ActorFollowing->VideoChannel" + ON "ActorFollow->ActorFollowing"."id" = "ActorFollow->ActorFollowing->VideoChannel"."actorId" + LEFT JOIN "account" AS "ActorFollow->ActorFollowing->Account" + ON "ActorFollow->ActorFollowing"."id" = "ActorFollow->ActorFollowing->Account"."actorId" + LEFT JOIN "server" AS "ActorFollow->ActorFollowing->Server" + ON "ActorFollow->ActorFollowing"."serverId" = "ActorFollow->ActorFollowing->Server"."id" + ) ON "UserNotificationModel"."actorFollowId" = "ActorFollow"."id" - LEFT JOIN ( - "actorFollow" AS "ActorFollow" - INNER JOIN "actor" AS "ActorFollow->ActorFollower" ON "ActorFollow"."actorId" = "ActorFollow->ActorFollower"."id" - INNER JOIN "account" AS "ActorFollow->ActorFollower->Account" - ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Account"."actorId" - LEFT JOIN "actorImage" AS "ActorFollow->ActorFollower->Avatars" - ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Avatars"."actorId" - AND "ActorFollow->ActorFollower->Avatars"."type" = ${ActorImageType.AVATAR} - LEFT JOIN "server" AS "ActorFollow->ActorFollower->Server" - ON "ActorFollow->ActorFollower"."serverId" = "ActorFollow->ActorFollower->Server"."id" - INNER JOIN "actor" AS "ActorFollow->ActorFollowing" ON "ActorFollow"."targetActorId" = "ActorFollow->ActorFollowing"."id" - LEFT JOIN "videoChannel" AS "ActorFollow->ActorFollowing->VideoChannel" - ON "ActorFollow->ActorFollowing"."id" = "ActorFollow->ActorFollowing->VideoChannel"."actorId" - LEFT JOIN "account" AS "ActorFollow->ActorFollowing->Account" - ON "ActorFollow->ActorFollowing"."id" = "ActorFollow->ActorFollowing->Account"."actorId" - LEFT JOIN "server" AS "ActorFollow->ActorFollowing->Server" - ON "ActorFollow->ActorFollowing"."serverId" = "ActorFollow->ActorFollowing->Server"."id" - ) ON "UserNotificationModel"."actorFollowId" = "ActorFollow"."id" + LEFT JOIN ( + "account" AS "Account" + INNER JOIN "actor" AS "Account->Actor" ON "Account"."actorId" = "Account->Actor"."id" + LEFT JOIN "actorImage" AS "Account->Actor->Avatars" + ON "Account->Actor"."id" = "Account->Actor->Avatars"."actorId" + AND "Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR} + LEFT JOIN "server" AS "Account->Actor->Server" ON "Account->Actor"."serverId" = "Account->Actor->Server"."id" + ) ON "UserNotificationModel"."accountId" = "Account"."id" - LEFT JOIN ( - "account" AS "Account" - INNER JOIN "actor" AS "Account->Actor" ON "Account"."actorId" = "Account->Actor"."id" - LEFT JOIN "actorImage" AS "Account->Actor->Avatars" - ON "Account->Actor"."id" = "Account->Actor->Avatars"."actorId" - AND "Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR} - LEFT JOIN "server" AS "Account->Actor->Server" ON "Account->Actor"."serverId" = "Account->Actor->Server"."id" - ) ON "UserNotificationModel"."accountId" = "Account"."id"` + LEFT JOIN "userRegistration" as "UserRegistration" ON "UserNotificationModel"."userRegistrationId" = "UserRegistration"."id"` } } diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts index 6e134158f..667ee7f5f 100644 --- a/server/models/user/user-notification.ts +++ b/server/models/user/user-notification.ts @@ -20,6 +20,7 @@ import { VideoCommentModel } from '../video/video-comment' import { VideoImportModel } from '../video/video-import' import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder' import { UserModel } from './user' +import { UserRegistrationModel } from './user-registration' @Table({ tableName: 'userNotification', @@ -98,6 +99,14 @@ import { UserModel } from './user' [Op.ne]: null } } + }, + { + fields: [ 'userRegistrationId' ], + where: { + userRegistrationId: { + [Op.ne]: null + } + } } ] as (ModelIndexesOptions & { where?: WhereOptions })[] }) @@ -241,6 +250,18 @@ export class UserNotificationModel extends Model UserRegistrationModel) + @Column + userRegistrationId: number + + @BelongsTo(() => UserRegistrationModel, { + foreignKey: { + allowNull: true + }, + onDelete: 'cascade' + }) + UserRegistration: UserRegistrationModel + static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { const where = { userId } @@ -416,6 +437,10 @@ export class UserNotificationModel extends Model>> { + + @AllowNull(false) + @Is('RegistrationState', value => throwIfNotValid(value, isRegistrationStateValid, 'state')) + @Column + state: UserRegistrationState + + @AllowNull(false) + @Is('RegistrationReason', value => throwIfNotValid(value, isRegistrationReasonValid, 'registration reason')) + @Column(DataType.TEXT) + registrationReason: string + + @AllowNull(true) + @Is('RegistrationModerationResponse', value => throwIfNotValid(value, isRegistrationModerationResponseValid, 'moderation response', true)) + @Column(DataType.TEXT) + moderationResponse: string + + @AllowNull(true) + @Is('RegistrationPassword', value => throwIfNotValid(value, isUserPasswordValid, 'registration password', true)) + @Column + password: string + + @AllowNull(false) + @Column + username: string + + @AllowNull(false) + @IsEmail + @Column(DataType.STRING(400)) + email: string + + @AllowNull(true) + @Is('RegistrationEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true)) + @Column + emailVerified: boolean + + @AllowNull(true) + @Is('RegistrationAccountDisplayName', value => throwIfNotValid(value, isUserDisplayNameValid, 'account display name', true)) + @Column + accountDisplayName: string + + @AllowNull(true) + @Is('ChannelHandle', value => throwIfNotValid(value, isVideoChannelDisplayNameValid, 'channel handle', true)) + @Column + channelHandle: string + + @AllowNull(true) + @Is('ChannelDisplayName', value => throwIfNotValid(value, isVideoChannelDisplayNameValid, 'channel display name', true)) + @Column + channelDisplayName: string + + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @ForeignKey(() => UserModel) + @Column + userId: number + + @BelongsTo(() => UserModel, { + foreignKey: { + allowNull: true + }, + onDelete: 'SET NULL' + }) + User: UserModel + + @BeforeCreate + static async cryptPasswordIfNeeded (instance: UserRegistrationModel) { + instance.password = await cryptPassword(instance.password) + } + + static load (id: number): Promise { + return UserRegistrationModel.findByPk(id) + } + + static loadByEmail (email: string): Promise { + const query = { + where: { email } + } + + return UserRegistrationModel.findOne(query) + } + + static loadByEmailOrUsername (emailOrUsername: string): Promise { + const query = { + where: { + [Op.or]: [ + { email: emailOrUsername }, + { username: emailOrUsername } + ] + } + } + + return UserRegistrationModel.findOne(query) + } + + static loadByEmailOrHandle (options: { + email: string + username: string + channelHandle?: string + }): Promise { + const { email, username, channelHandle } = options + + let or: WhereOptions = [ + { email }, + { channelHandle: username }, + { username } + ] + + if (channelHandle) { + or = or.concat([ + { username: channelHandle }, + { channelHandle } + ]) + } + + const query = { + where: { + [Op.or]: or + } + } + + return UserRegistrationModel.findOne(query) + } + + // --------------------------------------------------------------------------- + + static listForApi (options: { + start: number + count: number + sort: string + search?: string + }) { + const { start, count, sort, search } = options + + const where: WhereOptions = {} + + if (search) { + Object.assign(where, { + [Op.or]: [ + { + email: { + [Op.iLike]: '%' + search + '%' + } + }, + { + username: { + [Op.iLike]: '%' + search + '%' + } + } + ] + }) + } + + const query: FindOptions = { + offset: start, + limit: count, + order: getSort(sort), + where, + include: [ + { + model: UserModel.unscoped(), + required: false + } + ] + } + + return Promise.all([ + UserRegistrationModel.count(query), + UserRegistrationModel.findAll(query) + ]).then(([ total, data ]) => ({ total, data })) + } + + // --------------------------------------------------------------------------- + + toFormattedJSON (this: MRegistrationFormattable): UserRegistration { + return { + id: this.id, + + state: { + id: this.state, + label: USER_REGISTRATION_STATES[this.state] + }, + + registrationReason: this.registrationReason, + moderationResponse: this.moderationResponse, + + username: this.username, + email: this.email, + emailVerified: this.emailVerified, + + accountDisplayName: this.accountDisplayName, + + channelHandle: this.channelHandle, + channelDisplayName: this.channelDisplayName, + + createdAt: this.createdAt, + updatedAt: this.updatedAt, + + user: this.User + ? { id: this.User.id } + : null + } + } +} diff --git a/server/models/user/user.ts b/server/models/user/user.ts index 0932a367a..c5c8a1b30 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts @@ -441,16 +441,17 @@ export class UserModel extends Model>> { }) OAuthTokens: OAuthTokenModel[] + // Used if we already set an encrypted password in user model + skipPasswordEncryption = false + @BeforeCreate @BeforeUpdate - static cryptPasswordIfNeeded (instance: UserModel) { - if (instance.changed('password') && instance.password) { - return cryptPassword(instance.password) - .then(hash => { - instance.password = hash - return undefined - }) - } + static async cryptPasswordIfNeeded (instance: UserModel) { + if (instance.skipPasswordEncryption) return + if (!instance.changed('password')) return + if (!instance.password) return + + instance.password = await cryptPassword(instance.password) } @AfterUpdate -- cgit v1.2.3