diff --git a/src/db/postgre.ts b/src/db/postgre.ts index bc5ee4ce8..e5726e9c8 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -36,10 +36,10 @@ import { Emoji } from '../models/entities/emoji'; import { ReversiGame } from '../models/entities/games/reversi/game'; import { ReversiMatching } from '../models/entities/games/reversi/matching'; import { UserNotePining } from '../models/entities/user-note-pinings'; -import { UserServiceLinking } from '../models/entities/user-service-linking'; import { Poll } from '../models/entities/poll'; import { UserKeypair } from '../models/entities/user-keypair'; import { UserPublickey } from '../models/entities/user-publickey'; +import { UserProfile } from '../models/entities/user-profile'; const sqlLogger = dbLogger.createSubLogger('sql', 'white', false); @@ -101,12 +101,12 @@ export function initDb(justBorrow = false, sync = false, log = false) { AuthSession, AccessToken, User, + UserProfile, UserKeypair, UserPublickey, UserList, UserListJoining, UserNotePining, - UserServiceLinking, Following, FollowRequest, Muting, diff --git a/src/migrate.ts b/src/migrate.ts index b9d6eb339..f833ec180 100644 --- a/src/migrate.ts +++ b/src/migrate.ts @@ -26,6 +26,7 @@ import { UserKeypair } from './models/entities/user-keypair'; import { extractPublic } from './crypto_key'; import { Emoji } from './models/entities/emoji'; import { toPuny } from './misc/convert-host'; +import { UserProfile } from './models/entities/user-profile'; const u = (config as any).mongodb.user ? encodeURIComponent((config as any).mongodb.user) : null; const p = (config as any).mongodb.pass ? encodeURIComponent((config as any).mongodb.pass) : null; @@ -70,6 +71,7 @@ const getDriveFileBucket = async (): Promise => { async function main() { await initDb(); const Users = getRepository(User); + const UserProfiles = getRepository(UserProfile); const DriveFiles = getRepository(DriveFile); const DriveFolders = getRepository(DriveFolder); const Notes = getRepository(Note); @@ -90,17 +92,11 @@ async function main() { usernameLower: user.username.toLowerCase(), host: toPuny(user.host), token: generateUserToken(), - password: user.password, isAdmin: user.isAdmin, - autoAcceptFollowed: true, - autoWatch: false, name: user.name, - location: user.profile ? user.profile.location : null, - birthday: user.profile ? user.profile.birthday : null, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, - description: user.description, isBot: user.isBot, isCat: user.isCat, isVerified: user.isVerified, @@ -108,9 +104,18 @@ async function main() { sharedInbox: user.sharedInbox, uri: user.uri, }); + await UserProfiles.save({ + userId: user._id.toHexString(), + description: user.description, + userHost: toPuny(user.host), + autoAcceptFollowed: true, + autoWatch: false, + password: user.password, + location: user.profile ? user.profile.location : null, + birthday: user.profile ? user.profile.birthday : null, + }); if (user.publicKey) { await UserPublickeys.save({ - id: genId(), userId: user._id.toHexString(), keyId: user.publicKey.id, keyPem: user.publicKey.publicKeyPem @@ -118,7 +123,6 @@ async function main() { } if (user.keypair) { await UserKeypairs.save({ - id: genId(), userId: user._id.toHexString(), publicKey: extractPublic(user.keypair), privateKey: user.keypair, diff --git a/src/models/entities/user-keypair.ts b/src/models/entities/user-keypair.ts index be264641f..9181abf8c 100644 --- a/src/models/entities/user-keypair.ts +++ b/src/models/entities/user-keypair.ts @@ -4,11 +4,8 @@ import { id } from '../id'; @Entity() export class UserKeypair { - @PrimaryColumn(id()) - public id: string; - @Index({ unique: true }) - @Column(id()) + @PrimaryColumn(id()) public userId: User['id']; @OneToOne(type => User, { diff --git a/src/models/entities/user-service-linking.ts b/src/models/entities/user-profile.ts similarity index 52% rename from src/models/entities/user-service-linking.ts rename to src/models/entities/user-profile.ts index 3d99554e1..24b92231f 100644 --- a/src/models/entities/user-service-linking.ts +++ b/src/models/entities/user-profile.ts @@ -1,14 +1,11 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; -import { User } from './user'; +import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; import { id } from '../id'; +import { User } from './user'; @Entity() -export class UserServiceLinking { - @PrimaryColumn(id()) - public id: string; - +export class UserProfile { @Index({ unique: true }) - @Column(id()) + @PrimaryColumn(id()) public userId: User['id']; @OneToOne(type => User, { @@ -17,6 +14,96 @@ export class UserServiceLinking { @JoinColumn() public user: User | null; + @Column('varchar', { + length: 128, nullable: true, + comment: 'The location of the User.' + }) + public location: string | null; + + @Column('char', { + length: 10, nullable: true, + comment: 'The birthday (YYYY-MM-DD) of the User.' + }) + public birthday: string | null; + + @Column('varchar', { + length: 1024, nullable: true, + comment: 'The description (bio) of the User.' + }) + public description: string | null; + + @Column('jsonb', { + default: [], + }) + public fields: { + name: string; + value: string; + }[]; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The email address of the User.' + }) + public email: string | null; + + @Column('varchar', { + length: 128, nullable: true, + }) + public emailVerifyCode: string | null; + + @Column('boolean', { + default: false, + }) + public emailVerified: boolean; + + @Column('varchar', { + length: 128, nullable: true, + }) + public twoFactorTempSecret: string | null; + + @Column('varchar', { + length: 128, nullable: true, + }) + public twoFactorSecret: string | null; + + @Column('boolean', { + default: false, + }) + public twoFactorEnabled: boolean; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The password hash of the User. It will be null if the origin of the user is local.' + }) + public password: string | null; + + @Column('jsonb', { + default: {}, + comment: 'The client-specific data of the User.' + }) + public clientData: Record; + + @Column('boolean', { + default: false, + }) + public autoWatch: boolean; + + @Column('boolean', { + default: false, + }) + public autoAcceptFollowed: boolean; + + @Column('boolean', { + default: false, + }) + public alwaysMarkNsfw: boolean; + + @Column('boolean', { + default: false, + }) + public carefulBot: boolean; + + //#region Linking @Column('boolean', { default: false, }) @@ -96,6 +183,7 @@ export class UserServiceLinking { length: 64, nullable: true, default: null, }) public discordDiscriminator: string | null; + //#endregion //#region Denormalized fields @Index() diff --git a/src/models/entities/user-publickey.ts b/src/models/entities/user-publickey.ts index 6c019f331..81c42404f 100644 --- a/src/models/entities/user-publickey.ts +++ b/src/models/entities/user-publickey.ts @@ -4,11 +4,8 @@ import { id } from '../id'; @Entity() export class UserPublickey { - @PrimaryColumn(id()) - public id: string; - @Index({ unique: true }) - @Column(id()) + @PrimaryColumn(id()) public userId: User['id']; @OneToOne(type => User, { diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index 0a2878c0c..40d27a42b 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -45,18 +45,6 @@ export class User { }) public name: string | null; - @Column('varchar', { - length: 128, nullable: true, - comment: 'The location of the User.' - }) - public location: string | null; - - @Column('char', { - length: 10, nullable: true, - comment: 'The birthday (YYYY-MM-DD) of the User.' - }) - public birthday: string | null; - @Column('integer', { default: 0, comment: 'The count of followers.' @@ -101,44 +89,12 @@ export class User { @JoinColumn() public banner: DriveFile | null; - @Column('varchar', { - length: 1024, nullable: true, - comment: 'The description (bio) of the User.' - }) - public description: string | null; - @Index() @Column('varchar', { length: 128, array: true, default: '{}' }) public tags: string[]; - @Column('varchar', { - length: 128, nullable: true, - comment: 'The email address of the User.' - }) - public email: string | null; - - @Column('varchar', { - length: 128, nullable: true, - }) - public emailVerifyCode: string | null; - - @Column('boolean', { - default: false, - }) - public emailVerified: boolean; - - @Column('varchar', { - length: 128, nullable: true, - }) - public twoFactorTempSecret: string | null; - - @Column('varchar', { - length: 128, nullable: true, - }) - public twoFactorSecret: string | null; - @Column('varchar', { length: 256, nullable: true, }) @@ -206,11 +162,6 @@ export class User { }) public isVerified: boolean; - @Column('boolean', { - default: false, - }) - public twoFactorEnabled: boolean; - @Column('varchar', { length: 128, array: true, default: '{}' }) @@ -248,44 +199,12 @@ export class User { }) public uri: string | null; - @Column('varchar', { - length: 128, nullable: true, - comment: 'The password hash of the User. It will be null if the origin of the user is local.' - }) - public password: string | null; - @Index({ unique: true }) @Column('char', { length: 16, nullable: true, unique: true, comment: 'The native access token of the User. It will be null if the origin of the user is local.' }) public token: string | null; - - @Column('jsonb', { - default: {}, - comment: 'The client-specific data of the User.' - }) - public clientData: Record; - - @Column('boolean', { - default: false, - }) - public autoWatch: boolean; - - @Column('boolean', { - default: false, - }) - public autoAcceptFollowed: boolean; - - @Column('boolean', { - default: false, - }) - public alwaysMarkNsfw: boolean; - - @Column('boolean', { - default: false, - }) - public carefulBot: boolean; } export interface ILocalUser extends User { diff --git a/src/models/index.ts b/src/models/index.ts index f88bb8d63..d66e4e710 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -25,7 +25,6 @@ import { FollowRequestRepository } from './repositories/follow-request'; import { MutingRepository } from './repositories/muting'; import { BlockingRepository } from './repositories/blocking'; import { NoteReactionRepository } from './repositories/note-reaction'; -import { UserServiceLinking } from './entities/user-service-linking'; import { NotificationRepository } from './repositories/notification'; import { NoteFavoriteRepository } from './repositories/note-favorite'; import { ReversiMatchingRepository } from './repositories/games/reversi/matching'; @@ -35,6 +34,7 @@ import { AppRepository } from './repositories/app'; import { FollowingRepository } from './repositories/following'; import { AbuseUserReportRepository } from './repositories/abuse-user-report'; import { AuthSessionRepository } from './repositories/auth-session'; +import { UserProfile } from './entities/user-profile'; export const Apps = getCustomRepository(AppRepository); export const Notes = getCustomRepository(NoteRepository); @@ -45,12 +45,12 @@ export const NoteUnreads = getRepository(NoteUnread); export const Polls = getRepository(Poll); export const PollVotes = getRepository(PollVote); export const Users = getCustomRepository(UserRepository); +export const UserProfiles = getRepository(UserProfile); export const UserKeypairs = getRepository(UserKeypair); export const UserPublickeys = getRepository(UserPublickey); export const UserLists = getCustomRepository(UserListRepository); export const UserListJoinings = getRepository(UserListJoining); export const UserNotePinings = getRepository(UserNotePining); -export const UserServiceLinkings = getRepository(UserServiceLinking); export const Followings = getCustomRepository(FollowingRepository); export const FollowRequests = getCustomRepository(FollowRequestRepository); export const Instances = getRepository(Instance); diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 3939e3142..9e11b2aa8 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -1,6 +1,6 @@ import { EntityRepository, Repository, In } from 'typeorm'; import { User, ILocalUser, IRemoteUser } from '../entities/user'; -import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings } from '..'; +import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..'; import rap from '@prezzemolo/rap'; @EntityRepository(User) @@ -80,6 +80,7 @@ export class UserRepository extends Repository { const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : []; + const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }) : null; return await rap({ id: user.id, @@ -116,9 +117,9 @@ export class UserRepository extends Repository { } : {}), ...(opts.detail ? { - description: user.description, - location: user.location, - birthday: user.birthday, + description: profile.description, + location: profile.location, + birthday: profile.birthday, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, @@ -131,9 +132,9 @@ export class UserRepository extends Repository { ...(opts.detail && meId === user.id ? { avatarId: user.avatarId, bannerId: user.bannerId, - autoWatch: user.autoWatch, - alwaysMarkNsfw: user.alwaysMarkNsfw, - carefulBot: user.carefulBot, + autoWatch: profile.autoWatch, + alwaysMarkNsfw: profile.alwaysMarkNsfw, + carefulBot: profile.carefulBot, hasUnreadMessagingMessage: MessagingMessages.count({ where: { recipientId: user.id, diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 6129dad3d..a6f7482bb 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -14,16 +14,16 @@ import { IIdentifier } from './identifier'; import { apLogger } from '../logger'; import { Note } from '../../../models/entities/note'; import { updateHashtag } from '../../../services/update-hashtag'; -import { Users, UserNotePinings, Instances, DriveFiles, Followings, UserServiceLinkings, UserPublickeys } from '../../../models'; +import { Users, UserNotePinings, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '../../../models'; import { User, IRemoteUser } from '../../../models/entities/user'; import { Emoji } from '../../../models/entities/emoji'; import { UserNotePining } from '../../../models/entities/user-note-pinings'; import { genId } from '../../../misc/gen-id'; -import { UserServiceLinking } from '../../../models/entities/user-service-linking'; import { instanceChart, usersChart } from '../../../services/chart'; import { UserPublickey } from '../../../models/entities/user-publickey'; import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error'; import { toPuny } from '../../../misc/convert-host'; +import { UserProfile } from '../../../models/entities/user-profile'; const logger = apLogger; /** @@ -126,7 +126,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise tag.toLowerCase()); @@ -141,7 +141,6 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise); + await UserPublickeys.save({ - id: genId(), userId: user.id, keyId: person.publicKey.id, keyPem: person.publicKey.publicKeyPem } as UserPublickey); - await UserServiceLinkings.save({ - id: genId(), - userId: user.id - } as UserServiceLinking); - // Register host registerOrFetchInstanceDoc(host).then(i => { Instances.increment({ id: i.id }, 'usersCount', 1); @@ -347,7 +344,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje keyPem: person.publicKey.publicKeyPem }); - await UserServiceLinkings.update({ userId: exist.id }, { + await UserProfiles.update({ userId: exist.id }, { twitterUserId: services.twitter.userId, twitterScreenName: services.twitter.screenName, githubId: services.github.id, diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index 4c6b518eb..e561e47c6 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -8,15 +8,15 @@ import { getEmojis } from './note'; import renderEmoji from './emoji'; import { IIdentifier } from '../models/identifier'; import renderHashtag from './hashtag'; -import { DriveFiles, UserServiceLinkings, UserKeypairs } from '../../../models'; +import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models'; export async function renderPerson(user: ILocalUser) { const id = `${config.url}/users/${user.id}`; - const [avatar, banner, links] = await Promise.all([ + const [avatar, banner, profile] = await Promise.all([ DriveFiles.findOne(user.avatarId), DriveFiles.findOne(user.bannerId), - UserServiceLinkings.findOne({ userId: user.id }) + UserProfiles.findOne({ userId: user.id }) ]); const attachment: { @@ -27,41 +27,41 @@ export async function renderPerson(user: ILocalUser) { identifier?: IIdentifier }[] = []; - if (links.twitter) { + if (profile.twitter) { attachment.push({ type: 'PropertyValue', name: 'Twitter', - value: `@${links.twitterScreenName}`, + value: `@${profile.twitterScreenName}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:twitter', - value: `${links.twitterUserId}@${links.twitterScreenName}` + value: `${profile.twitterUserId}@${profile.twitterScreenName}` } }); } - if (links.github) { + if (profile.github) { attachment.push({ type: 'PropertyValue', name: 'GitHub', - value: `@${links.githubLogin}`, + value: `@${profile.githubLogin}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:github', - value: `${links.githubId}@${links.githubLogin}` + value: `${profile.githubId}@${profile.githubLogin}` } }); } - if (links.discord) { + if (profile.discord) { attachment.push({ type: 'PropertyValue', name: 'Discord', - value: `${links.discordUsername}#${links.discordDiscriminator}`, + value: `${profile.discordUsername}#${profile.discordDiscriminator}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:discord', - value: `${links.discordId}@${links.discordUsername}#${links.discordDiscriminator}` + value: `${profile.discordId}@${profile.discordUsername}#${profile.discordDiscriminator}` } }); } @@ -93,7 +93,7 @@ export async function renderPerson(user: ILocalUser) { url: `${config.url}/@${user.username}`, preferredUsername: user.username, name: user.name, - summary: toHtml(parse(user.description)), + summary: toHtml(parse(profile.description)), icon: user.avatarId && renderImage(avatar), image: user.bannerId && renderImage(banner), tag, diff --git a/src/server/api/endpoints/admin/reset-password.ts b/src/server/api/endpoints/admin/reset-password.ts index 07b8b6d93..42df66860 100644 --- a/src/server/api/endpoints/admin/reset-password.ts +++ b/src/server/api/endpoints/admin/reset-password.ts @@ -3,7 +3,7 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import * as bcrypt from 'bcryptjs'; import rndstr from 'rndstr'; -import { Users } from '../../../../models'; +import { Users, UserProfiles } from '../../../../models'; export const meta = { desc: { @@ -42,7 +42,9 @@ export default define(meta, async (ps) => { // Generate hash of password const hash = bcrypt.hashSync(passwd); - await Users.update(user.id, { + await UserProfiles.update({ + userId: user.id + }, { password: hash }); diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts index 8ccb09b8b..edc7cefd2 100644 --- a/src/server/api/endpoints/i/2fa/done.ts +++ b/src/server/api/endpoints/i/2fa/done.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as speakeasy from 'speakeasy'; import define from '../../../define'; -import { Users } from '../../../../../models'; +import { UserProfiles } from '../../../../../models'; export const meta = { requireCredential: true, @@ -16,24 +16,26 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const _token = ps.token.replace(/\s/g, ''); + const token = ps.token.replace(/\s/g, ''); - if (user.twoFactorTempSecret == null) { + const profile = await UserProfiles.findOne({ userId: user.id }); + + if (profile.twoFactorTempSecret == null) { throw new Error('二段階認証の設定が開始されていません'); } const verified = (speakeasy as any).totp.verify({ - secret: user.twoFactorTempSecret, + secret: profile.twoFactorTempSecret, encoding: 'base32', - token: _token + token: token }); if (!verified) { throw new Error('not verified'); } - await Users.update(user.id, { - twoFactorSecret: user.twoFactorTempSecret, + await UserProfiles.update({ userId: user.id }, { + twoFactorSecret: profile.twoFactorTempSecret, twoFactorEnabled: true }); }); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts index 5efe77900..db9a2fe94 100644 --- a/src/server/api/endpoints/i/2fa/register.ts +++ b/src/server/api/endpoints/i/2fa/register.ts @@ -4,7 +4,7 @@ import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; import config from '../../../../../config'; import define from '../../../define'; -import { Users } from '../../../../../models'; +import { UserProfiles } from '../../../../../models'; export const meta = { requireCredential: true, @@ -19,8 +19,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); @@ -31,7 +33,7 @@ export default define(meta, async (ps, user) => { length: 32 }); - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { twoFactorTempSecret: secret.base32 }); diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts index fb3ecd404..fa25b7439 100644 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ b/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../../define'; -import { Users } from '../../../../../models'; +import { UserProfiles } from '../../../../../models'; export const meta = { requireCredential: true, @@ -16,17 +16,17 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); } - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { twoFactorSecret: null, twoFactorEnabled: false }); - - return; }); diff --git a/src/server/api/endpoints/i/change-password.ts b/src/server/api/endpoints/i/change-password.ts index f8f977200..d0e0695e1 100644 --- a/src/server/api/endpoints/i/change-password.ts +++ b/src/server/api/endpoints/i/change-password.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; -import { Users } from '../../../../models'; +import { UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -20,8 +20,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.currentPassword, user.password); + const same = await bcrypt.compare(ps.currentPassword, profile.password); if (!same) { throw new Error('incorrect password'); @@ -31,7 +33,7 @@ export default define(meta, async (ps, user) => { const salt = await bcrypt.genSalt(8); const hash = await bcrypt.hash(ps.newPassword, salt); - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { password: hash }); }); diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index 5aff74e0c..7ef7aa5fa 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; -import { Users } from '../../../../models'; +import { Users, UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -16,8 +16,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/regenerate-token.ts b/src/server/api/endpoints/i/regenerate-token.ts index 729c1a300..ec53bca97 100644 --- a/src/server/api/endpoints/i/regenerate-token.ts +++ b/src/server/api/endpoints/i/regenerate-token.ts @@ -3,7 +3,7 @@ import * as bcrypt from 'bcryptjs'; import { publishMainStream } from '../../../../services/stream'; import generateUserToken from '../../common/generate-native-user-token'; import define from '../../define'; -import { Users } from '../../../../models'; +import { Users, UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -18,8 +18,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/update-client-setting.ts b/src/server/api/endpoints/i/update-client-setting.ts index edbfe28f3..49bcb35ae 100644 --- a/src/server/api/endpoints/i/update-client-setting.ts +++ b/src/server/api/endpoints/i/update-client-setting.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { publishMainStream } from '../../../../services/stream'; import define from '../../define'; -import { Users } from '../../../../models'; +import { UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -20,7 +20,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - await Users.createQueryBuilder().update() + await UserProfiles.createQueryBuilder().update() .set({ clientData: { [ps.name]: ps.value diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts index 253017535..d98f0d753 100644 --- a/src/server/api/endpoints/i/update-email.ts +++ b/src/server/api/endpoints/i/update-email.ts @@ -8,7 +8,7 @@ import config from '../../../../config'; import * as ms from 'ms'; import * as bcrypt from 'bcryptjs'; import { apiLogger } from '../../logger'; -import { Users } from '../../../../models'; +import { Users, UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -32,14 +32,16 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); } - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { email: ps.email, emailVerified: false, emailVerifyCode: null @@ -56,7 +58,7 @@ export default define(meta, async (ps, user) => { if (ps.email != null) { const code = rndstr('a-z0-9', 16); - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { emailVerifyCode: code }); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 54e7f33bd..ffc90b2f5 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -10,7 +10,9 @@ import extractHashtags from '../../../../misc/extract-hashtags'; import * as langmap from 'langmap'; import { updateHashtag } from '../../../../services/update-hashtag'; import { ApiError } from '../../error'; -import { Users, DriveFiles } from '../../../../models'; +import { Users, DriveFiles, UserProfiles } from '../../../../models'; +import { User } from '../../../../models/entities/user'; +import { UserProfile } from '../../../../models/entities/user-profile'; export const meta = { desc: { @@ -154,22 +156,23 @@ export const meta = { export default define(meta, async (ps, user, app) => { const isSecure = user != null && app == null; - const updates = {} as any; + const updates = {} as Partial; + const profile = {} as Partial; if (ps.name !== undefined) updates.name = ps.name; - if (ps.description !== undefined) updates.description = ps.description; - if (ps.lang !== undefined) updates.lang = ps.lang; - if (ps.location !== undefined) updates.location = ps.location; - if (ps.birthday !== undefined) updates.birthday = ps.birthday; + if (ps.description !== undefined) profile.description = ps.description; + //if (ps.lang !== undefined) updates.lang = ps.lang; + if (ps.location !== undefined) profile.location = ps.location; + if (ps.birthday !== undefined) profile.birthday = ps.birthday; if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked; if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; - if (typeof ps.carefulBot == 'boolean') updates.carefulBot = ps.carefulBot; - if (typeof ps.autoAcceptFollowed == 'boolean') updates.autoAcceptFollowed = ps.autoAcceptFollowed; + if (typeof ps.carefulBot == 'boolean') profile.carefulBot = ps.carefulBot; + if (typeof ps.autoAcceptFollowed == 'boolean') profile.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; - if (typeof ps.autoWatch == 'boolean') updates.autoWatch = ps.autoWatch; - if (typeof ps.alwaysMarkNsfw == 'boolean') updates.alwaysMarkNsfw = ps.alwaysMarkNsfw; + if (typeof ps.autoWatch == 'boolean') profile.autoWatch = ps.autoWatch; + if (typeof ps.alwaysMarkNsfw == 'boolean') profile.alwaysMarkNsfw = ps.alwaysMarkNsfw; if (ps.avatarId) { const avatar = await DriveFiles.findOne(ps.avatarId); @@ -206,8 +209,8 @@ export default define(meta, async (ps, user, app) => { emojis = emojis.concat(extractEmojis(tokens)); } - if (updates.description != null) { - const tokens = parse(updates.description); + if (profile.description != null) { + const tokens = parse(profile.description); emojis = emojis.concat(extractEmojis(tokens)); tags = extractHashtags(tokens).map(tag => tag.toLowerCase()); } @@ -221,6 +224,7 @@ export default define(meta, async (ps, user, app) => { //#endregion await Users.update(user.id, updates); + await UserProfiles.update({ userId: user.id }, profile); const iObj = await Users.pack(user.id, user, { detail: true, diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index 7d0ed6e4f..d868234dc 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -10,7 +10,7 @@ import { deliver } from '../../../../../queue'; import { renderActivity } from '../../../../../remote/activitypub/renderer'; import renderVote from '../../../../../remote/activitypub/renderer/vote'; import { deliverQuestionUpdate } from '../../../../../services/note/polls/update'; -import { PollVotes, NoteWatchings, Users, Polls } from '../../../../../models'; +import { PollVotes, NoteWatchings, Users, Polls, UserProfiles } from '../../../../../models'; import { Not } from 'typeorm'; import { IRemoteUser } from '../../../../../models/entities/user'; import { genId } from '../../../../../misc/gen-id'; @@ -149,8 +149,10 @@ export default define(meta, async (ps, user) => { } }); + const profile = await UserProfiles.findOne({ userId: user.id }); + // この投稿をWatchする - if (user.autoWatch !== false) { + if (profile.autoWatch !== false) { watch(user.id, note); } diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index c1fd908d8..fe2e5577c 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -4,7 +4,7 @@ import * as speakeasy from 'speakeasy'; import { publishMainStream } from '../../../services/stream'; import signin from '../common/signin'; import config from '../../../config'; -import { Users, Signins } from '../../../models'; +import { Users, Signins, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; import { genId } from '../../../misc/gen-id'; @@ -45,13 +45,15 @@ export default async (ctx: Koa.BaseContext) => { return; } + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(password, user.password); + const same = await bcrypt.compare(password, profile.password); if (same) { - if (user.twoFactorEnabled) { + if (profile.twoFactorEnabled) { const verified = (speakeasy as any).totp.verify({ - secret: user.twoFactorSecret, + secret: profile.twoFactorSecret, encoding: 'base32', token: token }); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 657e54dec..5ed25fa41 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -5,13 +5,13 @@ import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; import fetchMeta from '../../../misc/fetch-meta'; import * as recaptcha from 'recaptcha-promise'; -import { Users, RegistrationTickets, UserServiceLinkings, UserKeypairs } from '../../../models'; +import { Users, RegistrationTickets, UserProfiles, UserKeypairs } from '../../../models'; import { genId } from '../../../misc/gen-id'; import { usersChart } from '../../../services/chart'; -import { UserServiceLinking } from '../../../models/entities/user-service-linking'; import { User } from '../../../models/entities/user'; import { UserKeypair } from '../../../models/entities/user-keypair'; import { toPuny } from '../../../misc/convert-host'; +import { UserProfile } from '../../../models/entities/user-profile'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -106,23 +106,21 @@ export default async (ctx: Koa.BaseContext) => { usernameLower: username.toLowerCase(), host: toPuny(host), token: secret, - password: hash, isAdmin: config.autoAdmin && usersCount === 0, - autoAcceptFollowed: true, - autoWatch: false } as User); await UserKeypairs.save({ - id: genId(), publicKey: keyPair[0], privateKey: keyPair[1], userId: account.id } as UserKeypair); - await UserServiceLinkings.save({ - id: genId(), - userId: account.id - } as UserServiceLinking); + await UserProfiles.save({ + userId: account.id, + autoAcceptFollowed: true, + autoWatch: false, + password: hash, + } as Partial); usersChart.update(account, true); diff --git a/src/server/api/service/discord.ts b/src/server/api/service/discord.ts index 4290e1ff9..879b8b484 100644 --- a/src/server/api/service/discord.ts +++ b/src/server/api/service/discord.ts @@ -8,7 +8,7 @@ import redis from '../../../db/redis'; import * as uuid from 'uuid'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; -import { Users, UserServiceLinkings } from '../../../models'; +import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { @@ -45,7 +45,7 @@ router.get('/disconnect/discord', async ctx => { token: userToken }); - await UserServiceLinkings.update({ + await UserProfiles.update({ userId: user.id }, { discord: false, @@ -202,7 +202,7 @@ router.get('/dc/cb', async ctx => { return; } - const link = await UserServiceLinkings.createQueryBuilder() + const profile = await UserProfiles.createQueryBuilder() .where('discord @> :discord', { discord: { id: id, @@ -211,12 +211,12 @@ router.get('/dc/cb', async ctx => { .andWhere('userHost IS NULL') .getOne(); - if (link == null) { + if (profile == null) { ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); return; } - await UserServiceLinkings.update(link.id, { + await UserProfiles.update({ userId: profile.userId }, { discord: true, discordAccessToken: accessToken, discordRefreshToken: refreshToken, @@ -225,7 +225,7 @@ router.get('/dc/cb', async ctx => { discordDiscriminator: discriminator }); - signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); + signin(ctx, await Users.findOne(profile.userId) as ILocalUser, true); } else { const code = ctx.query.code; @@ -289,7 +289,7 @@ router.get('/dc/cb', async ctx => { token: userToken }); - await UserServiceLinkings.update({ userId: user.id }, { + await UserProfiles.update({ userId: user.id }, { discord: true, discordAccessToken: accessToken, discordRefreshToken: refreshToken, diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index e59b149d1..580947811 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -8,7 +8,7 @@ import redis from '../../../db/redis'; import * as uuid from 'uuid'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; -import { Users, UserServiceLinkings } from '../../../models'; +import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { @@ -45,7 +45,7 @@ router.get('/disconnect/github', async ctx => { token: userToken }); - await UserServiceLinkings.update({ + await UserProfiles.update({ userId: user.id }, { github: false, @@ -191,7 +191,7 @@ router.get('/gh/cb', async ctx => { return; } - const link = await UserServiceLinkings.createQueryBuilder() + const link = await UserProfiles.createQueryBuilder() .where('github @> :github', { github: { id: id, @@ -263,7 +263,7 @@ router.get('/gh/cb', async ctx => { token: userToken }); - await UserServiceLinkings.update({ userId: user.id }, { + await UserProfiles.update({ userId: user.id }, { github: true, githubAccessToken: accessToken, githubId: id, diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index 77cf71395..c0c762c6c 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -7,7 +7,7 @@ import { publishMainStream } from '../../../services/stream'; import config from '../../../config'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; -import { Users, UserServiceLinkings } from '../../../models'; +import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { @@ -44,7 +44,7 @@ router.get('/disconnect/twitter', async ctx => { token: userToken }); - await UserServiceLinkings.update({ + await UserProfiles.update({ userId: user.id }, { twitter: false, @@ -139,7 +139,7 @@ router.get('/tw/cb', async ctx => { const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier); - const link = await UserServiceLinkings.createQueryBuilder() + const link = await UserProfiles.createQueryBuilder() .where('twitter @> :twitter', { twitter: { userId: result.userId, @@ -177,7 +177,7 @@ router.get('/tw/cb', async ctx => { token: userToken }); - await UserServiceLinkings.update({ userId: user.id }, { + await UserProfiles.update({ userId: user.id }, { twitter: true, twitterAccessToken: result.accessToken, twitterAccessTokenSecret: result.accessTokenSecret, diff --git a/src/server/index.ts b/src/server/index.ts index 563117773..9c153f016 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -23,7 +23,7 @@ import apiServer from './api'; import { sum } from '../prelude/array'; import Logger from '../services/logger'; import { program } from '../argv'; -import { Users } from '../models'; +import { UserProfiles } from '../models'; import { networkChart } from '../services/chart'; export const serverLogger = new Logger('server', 'gray', false); @@ -73,15 +73,15 @@ router.use(nodeinfo.routes()); router.use(wellKnown.routes()); router.get('/verify-email/:code', async ctx => { - const user = await Users.findOne({ + const profile = await UserProfiles.findOne({ emailVerifyCode: ctx.params.code }); - if (user != null) { + if (profile != null) { ctx.body = 'Verify succeeded!'; ctx.status = 200; - Users.update(user.id, { + UserProfiles.update({ userId: profile.userId }, { emailVerified: true, emailVerifyCode: null }); diff --git a/src/server/web/feed.ts b/src/server/web/feed.ts index 4b4ea8797..94f0643f7 100644 --- a/src/server/web/feed.ts +++ b/src/server/web/feed.ts @@ -1,7 +1,7 @@ import { Feed } from 'feed'; import config from '../../config'; import { User } from '../../models/entities/user'; -import { Notes, DriveFiles } from '../../models'; +import { Notes, DriveFiles, UserProfiles } from '../../models'; import { In } from 'typeorm'; export default async function(user: User) { @@ -10,6 +10,8 @@ export default async function(user: User) { name: user.name || user.username }; + const profile = await UserProfiles.findOne({ userId: user.id }); + const notes = await Notes.find({ where: { userId: user.id, @@ -25,7 +27,7 @@ export default async function(user: User) { title: `${author.name} (@${user.username}@${config.host})`, updated: notes[0].createdAt, generator: 'Misskey', - description: `${user.notesCount} Notes, ${user.followingCount} Following, ${user.followersCount} Followers${user.description ? ` · ${user.description}` : ''}`, + description: `${user.notesCount} Notes, ${user.followingCount} Following, ${user.followersCount} Followers${profile.description ? ` · ${profile.description}` : ''}`, link: author.link, image: user.avatarUrl, feedLinks: { diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 0a84b88fb..b83c3558d 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -15,7 +15,7 @@ import { driveLogger } from './logger'; import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor'; import { contentDisposition } from '../../misc/content-disposition'; import { detectMine } from '../../misc/detect-mine'; -import { DriveFiles, DriveFolders, Users, Instances } from '../../models'; +import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '../../models'; import { InternalStorage } from './internal-storage'; import { DriveFile } from '../../models/entities/drive-file'; import { IRemoteUser, User } from '../../models/entities/user'; @@ -365,6 +365,8 @@ export default async function( propPromises = [calcWh(), calcAvg()]; } + const profile = await UserProfiles.findOne({ userId: user.id }); + const [folder] = await Promise.all([fetchFolder(), Promise.all(propPromises)]); let file = new DriveFile(); @@ -376,7 +378,7 @@ export default async function( file.comment = comment; file.properties = properties; file.isLink = isLink; - file.isSensitive = Users.isLocalUser(user) && user.alwaysMarkNsfw ? true : + file.isSensitive = Users.isLocalUser(user) && profile.alwaysMarkNsfw ? true : (sensitive !== null && sensitive !== undefined) ? sensitive : false; diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 28e4ba3c1..57bb61fd9 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -9,7 +9,7 @@ import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import Logger from '../logger'; import { IdentifiableError } from '../../misc/identifiable-error'; import { User } from '../../models/entities/user'; -import { Followings, Users, FollowRequests, Blockings, Instances } from '../../models'; +import { Followings, Users, FollowRequests, Blockings, Instances, UserProfiles } from '../../models'; import { instanceChart, perUserFollowingChart } from '../chart'; import { genId } from '../../misc/gen-id'; import { createNotification } from '../create-notification'; @@ -115,11 +115,13 @@ export default async function(follower: User, followee: User, requestId?: string if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); } + const followeeProfile = await UserProfiles.findOne({ userId: followee.id }); + // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく - if (followee.isLocked || (followee.carefulBot && follower.isBot) || (Users.isLocalUser(follower) && Users.isRemoteUser(followee))) { + if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (Users.isLocalUser(follower) && Users.isRemoteUser(followee))) { let autoAccept = false; // 鍵アカウントであっても、既にフォローされていた場合はスルー @@ -132,7 +134,7 @@ export default async function(follower: User, followee: User, requestId?: string } // フォローしているユーザーは自動承認オプション - if (!autoAccept && (Users.isLocalUser(followee) && followee.autoAcceptFollowed)) { + if (!autoAccept && (Users.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) { const followed = await Followings.findOne({ followerId: followee.id, followeeId: follower.id diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 05837a4da..6058fada1 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -17,7 +17,7 @@ import extractMentions from '../../misc/extract-mentions'; import extractEmojis from '../../misc/extract-emojis'; import extractHashtags from '../../misc/extract-hashtags'; import { Note } from '../../models/entities/note'; -import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, Polls } from '../../models'; +import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, Polls, UserProfiles } from '../../models'; import { DriveFile } from '../../models/entities/drive-file'; import { App } from '../../models/entities/app'; import { Not } from 'typeorm'; @@ -256,13 +256,15 @@ export default async (user: User, data: Option, silent = false) => new Promise new Promise new Promise(async (re } }); + const profile = await UserProfiles.findOne({ userId: user.id }); + // ローカルユーザーが投票した場合この投稿をWatchする - if (Users.isLocalUser(user) && user.autoWatch) { + if (Users.isLocalUser(user) && profile.autoWatch) { watch(user.id, note); } }); diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index 437b213de..1b026cc9c 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -8,7 +8,7 @@ import { toDbReaction } from '../../../misc/reaction-lib'; import fetchMeta from '../../../misc/fetch-meta'; import { User } from '../../../models/entities/user'; import { Note } from '../../../models/entities/note'; -import { NoteReactions, Users, NoteWatchings, Notes } from '../../../models'; +import { NoteReactions, Users, NoteWatchings, Notes, UserProfiles } from '../../../models'; import { Not } from 'typeorm'; import { perUserReactionsChart } from '../../chart'; import { genId } from '../../../misc/gen-id'; @@ -79,8 +79,10 @@ export default async (user: User, note: Note, reaction: string) => { } }); + const profile = await UserProfiles.findOne({ userId: user.id }); + // ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする - if (Users.isLocalUser(user) && user.autoWatch !== false) { + if (Users.isLocalUser(user) && profile.autoWatch) { watch(user.id, note); }