diff --git a/src/common/remote/activitypub/renderer/follow.ts b/src/common/remote/activitypub/renderer/follow.ts index 05c0ecca0..86a3f8ced 100644 --- a/src/common/remote/activitypub/renderer/follow.ts +++ b/src/common/remote/activitypub/renderer/follow.ts @@ -1,8 +1,8 @@ import config from '../../../../conf'; -import { IRemoteAccount } from '../../../../models/user'; +import { IRemoteUser } from '../../../../models/user'; -export default ({ username }, { account }) => ({ +export default ({ username }, followee: IRemoteUser) => ({ type: 'Follow', actor: `${config.url}/@${username}`, - object: (account as IRemoteAccount).uri + object: followee.account.uri }); diff --git a/src/common/remote/activitypub/renderer/key.ts b/src/common/remote/activitypub/renderer/key.ts index 692c71f88..3cac86b76 100644 --- a/src/common/remote/activitypub/renderer/key.ts +++ b/src/common/remote/activitypub/renderer/key.ts @@ -1,10 +1,10 @@ import config from '../../../../conf'; import { extractPublic } from '../../../../crypto_key'; -import { ILocalAccount } from '../../../../models/user'; +import { ILocalUser } from '../../../../models/user'; -export default ({ username, account }) => ({ - id: `${config.url}/@${username}/publickey`, +export default (user: ILocalUser) => ({ + id: `${config.url}/@${user.username}/publickey`, type: 'Key', - owner: `${config.url}/@${username}`, - publicKeyPem: extractPublic((account as ILocalAccount).keypair) + owner: `${config.url}/@${user.username}`, + publicKeyPem: extractPublic(user.account.keypair) }); diff --git a/src/common/user/get-summary.ts b/src/common/user/get-summary.ts index 47592c86b..2c71d3eae 100644 --- a/src/common/user/get-summary.ts +++ b/src/common/user/get-summary.ts @@ -1,4 +1,4 @@ -import { ILocalAccount, IUser } from '../../models/user'; +import { IUser, isLocalUser } from '../../models/user'; import getAcct from './get-acct'; /** @@ -9,8 +9,8 @@ export default function(user: IUser): string { let string = `${user.name} (@${getAcct(user)})\n` + `${user.postsCount}投稿、${user.followingCount}フォロー、${user.followersCount}フォロワー\n`; - if (user.host === null) { - const account = user.account as ILocalAccount; + if (isLocalUser(user)) { + const account = user.account; string += `場所: ${account.profile.location}、誕生日: ${account.profile.birthday}\n`; } diff --git a/src/models/user.ts b/src/models/user.ts index d9ac72b88..789b28b2f 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -39,7 +39,7 @@ export function isValidBirthday(birthday: string): boolean { return typeof birthday == 'string' && /^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/.test(birthday); } -export type ILocalAccount = { +type ILocalAccount = { keypair: string; email: string; links: string[]; @@ -69,7 +69,7 @@ export type ILocalAccount = { settings: any; }; -export type IRemoteAccount = { +type IRemoteAccount = { inbox: string; uri: string; publicKey: { @@ -78,7 +78,7 @@ export type IRemoteAccount = { }; }; -export type IUser = { +type IUserBase = { _id: mongo.ObjectID; createdAt: Date; deletedAt: Date; @@ -97,13 +97,19 @@ export type IUser = { pinnedPostId: mongo.ObjectID; isSuspended: boolean; keywords: string[]; - host: string; hostLower: string; - account: ILocalAccount | IRemoteAccount; }; -export type ILocalUser = IUser & { account: ILocalAccount }; -export type IRemoteUser = IUser & { account: IRemoteAccount }; +export type IUser = ILocalUser | IRemoteUser; + +export interface ILocalUser extends IUserBase { host: null; account: ILocalAccount; } +export interface IRemoteUser extends IUserBase { host: string; account: IRemoteAccount; } + +export const isLocalUser = (user: any): user is ILocalUser => + user.host === null; + +export const isRemoteUser = (user: any): user is IRemoteUser => + !isLocalUser(user); export function init(user): IUser { user._id = new mongo.ObjectID(user._id); diff --git a/src/processor/http/follow.ts b/src/processor/http/follow.ts index a8f7ba78c..9b8337f2e 100644 --- a/src/processor/http/follow.ts +++ b/src/processor/http/follow.ts @@ -1,7 +1,7 @@ import { request } from 'https'; import { sign } from 'http-signature'; import { URL } from 'url'; -import User, { ILocalAccount, IRemoteAccount, pack as packUser } from '../../models/user'; +import User, { isLocalUser, pack as packUser, ILocalUser } from '../../models/user'; import Following from '../../models/following'; import event from '../../common/event'; import notify from '../../common/notify'; @@ -10,7 +10,7 @@ import render from '../../common/remote/activitypub/renderer/follow'; import config from '../../conf'; export default ({ data }, done) => Following.findOne({ _id: data.following }).then(({ followerId, followeeId }) => { - const promisedFollower = User.findOne({ _id: followerId }); + const promisedFollower: Promise = User.findOne({ _id: followerId }); const promisedFollowee = User.findOne({ _id: followeeId }); return Promise.all([ @@ -38,7 +38,7 @@ export default ({ data }, done) => Following.findOne({ _id: data.following }).th .then(packed => event(follower._id, 'follow', packed)); let followeeEvent; - if (followee.host === null) { + if (isLocalUser(followee)) { followeeEvent = packUser(follower, followee) .then(packed => event(followee._id, 'followed', packed)); } else { @@ -49,7 +49,7 @@ export default ({ data }, done) => Following.findOne({ _id: data.following }).th port, pathname, search - } = new URL((followee.account as IRemoteAccount).inbox); + } = new URL(followee.account.inbox); const req = request({ protocol, @@ -72,7 +72,7 @@ export default ({ data }, done) => Following.findOne({ _id: data.following }).th sign(req, { authorizationHeaderName: 'Signature', - key: (follower.account as ILocalAccount).keypair, + key: follower.account.keypair, keyId: `acct:${follower.username}@${config.host}` }); diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index 6d092e66b..cb679dbf0 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -1,8 +1,9 @@ import * as bodyParser from 'body-parser'; import * as express from 'express'; import { parseRequest, verifySignature } from 'http-signature'; -import User, { IRemoteAccount } from '../../models/user'; +import User, { IRemoteUser } from '../../models/user'; import queue from '../../queue'; +import parseAcct from '../../common/user/parse-acct'; const app = express(); app.disable('x-powered-by'); @@ -36,13 +37,13 @@ app.post('/@:user/inbox', async (req, res) => { }; } - const user = await User.findOne(query); + const user = await User.findOne(query) as IRemoteUser; if (user === null) { return res.sendStatus(401); } - if (!verifySignature(parsed, (user.account as IRemoteAccount).publicKey.publicKeyPem)) { + if (!verifySignature(parsed, user.account.publicKey.publicKeyPem)) { return res.sendStatus(401); } diff --git a/src/server/api/bot/core.ts b/src/server/api/bot/core.ts index f84f1f5dc..d636cc26e 100644 --- a/src/server/api/bot/core.ts +++ b/src/server/api/bot/core.ts @@ -1,7 +1,7 @@ import * as EventEmitter from 'events'; import * as bcrypt from 'bcryptjs'; -import User, { ILocalAccount, IUser, init as initUser } from '../../../models/user'; +import User, { IUser, init as initUser, ILocalUser } from '../../../models/user'; import getPostSummary from '../../../common/get-post-summary'; import getUserSummary from '../../../common/user/get-summary'; @@ -198,7 +198,7 @@ abstract class Context extends EventEmitter { } class SigninContext extends Context { - private temporaryUser: IUser = null; + private temporaryUser: ILocalUser = null; public async greet(): Promise { return 'まずユーザー名を教えてください:'; @@ -207,14 +207,14 @@ class SigninContext extends Context { public async q(query: string): Promise { if (this.temporaryUser == null) { // Fetch user - const user: IUser = await User.findOne({ + const user = await User.findOne({ usernameLower: query.toLowerCase(), host: null }, { fields: { data: false } - }); + }) as ILocalUser; if (user === null) { return `${query}というユーザーは存在しませんでした... もう一度教えてください:`; @@ -225,7 +225,7 @@ class SigninContext extends Context { } } else { // Compare password - const same = await bcrypt.compare(query, (this.temporaryUser.account as ILocalAccount).password); + const same = await bcrypt.compare(query, this.temporaryUser.account.password); if (same) { this.bot.signin(this.temporaryUser); diff --git a/src/server/api/endpoints/posts/create.ts b/src/server/api/endpoints/posts/create.ts index 6e7d2329a..4de917694 100644 --- a/src/server/api/endpoints/posts/create.ts +++ b/src/server/api/endpoints/posts/create.ts @@ -5,9 +5,9 @@ import $ from 'cafy'; import deepEqual = require('deep-equal'); import html from '../../../../common/text/html'; import parse from '../../../../common/text/parse'; -import { default as Post, IPost, isValidText, isValidCw } from '../../../../models/post'; -import { default as User, ILocalAccount, IUser } from '../../../../models/user'; -import { default as Channel, IChannel } from '../../../../models/channel'; +import Post, { IPost, isValidText, isValidCw } from '../../../../models/post'; +import User, { ILocalUser } from '../../../../models/user'; +import Channel, { IChannel } from '../../../../models/channel'; import Following from '../../../../models/following'; import Mute from '../../../../models/mute'; import DriveFile from '../../../../models/drive-file'; @@ -29,7 +29,7 @@ import config from '../../../../conf'; * @param {any} app * @return {Promise} */ -module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { +module.exports = (params, user: ILocalUser, app) => new Promise(async (res, rej) => { // Get 'text' parameter const [text, textErr] = $(params.text).optional.string().pipe(isValidText).$; if (textErr) return rej('invalid text'); @@ -400,7 +400,7 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { }); // この投稿をWatchする - if ((user.account as ILocalAccount).settings.autoWatch !== false) { + if (user.account.settings.autoWatch !== false) { watch(user._id, reply); } diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index 4b7064491..4ad5097e5 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -1,7 +1,7 @@ import * as express from 'express'; import * as bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; -import { default as User, ILocalAccount, IUser } from '../../../models/user'; +import User, { ILocalUser } from '../../../models/user'; import Signin, { pack } from '../../../models/signin'; import event from '../../../common/event'; import signin from '../common/signin'; @@ -31,7 +31,7 @@ export default async (req: express.Request, res: express.Response) => { } // Fetch user - const user: IUser = await User.findOne({ + const user = await User.findOne({ usernameLower: username.toLowerCase(), host: null }, { @@ -39,7 +39,7 @@ export default async (req: express.Request, res: express.Response) => { data: false, 'account.profile': false } - }); + }) as ILocalUser; if (user === null) { res.status(404).send({ @@ -48,7 +48,7 @@ export default async (req: express.Request, res: express.Response) => { return; } - const account = user.account as ILocalAccount; + const account = user.account; // Compare password const same = await bcrypt.compare(password, account.password);