diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index 3b814e59df..0cdc401c94 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -354,57 +354,6 @@ export class Meta { }) public swPrivateKey: string | null; - @Column("boolean", { - default: false, - }) - public enableTwitterIntegration: boolean; - - @Column("varchar", { - length: 128, - nullable: true, - }) - public twitterConsumerKey: string | null; - - @Column("varchar", { - length: 128, - nullable: true, - }) - public twitterConsumerSecret: string | null; - - @Column("boolean", { - default: false, - }) - public enableGithubIntegration: boolean; - - @Column("varchar", { - length: 128, - nullable: true, - }) - public githubClientId: string | null; - - @Column("varchar", { - length: 128, - nullable: true, - }) - public githubClientSecret: string | null; - - @Column("boolean", { - default: false, - }) - public enableDiscordIntegration: boolean; - - @Column("varchar", { - length: 128, - nullable: true, - }) - public discordClientId: string | null; - - @Column("varchar", { - length: 128, - nullable: true, - }) - public discordClientSecret: string | null; - @Column("varchar", { length: 128, nullable: true, diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index 119eecdc73..b2a2d945e2 100644 --- a/packages/backend/src/models/entities/user-profile.ts +++ b/packages/backend/src/models/entities/user-profile.ts @@ -1,15 +1,8 @@ -import { - Entity, - Column, - Index, - OneToOne, - JoinColumn, - PrimaryColumn, -} from "typeorm"; -import { ffVisibility, notificationTypes } from "@/types.js"; -import { id } from "../id.js"; -import { User } from "./user.js"; -import { Page } from "./page.js"; +import {Column, Entity, Index, JoinColumn, OneToOne, PrimaryColumn,} from "typeorm"; +import {ffVisibility, notificationTypes} from "@/types.js"; +import {id} from "../id.js"; +import {User} from "./user.js"; +import {Page} from "./page.js"; // TODO: このテーブルで管理している情報すべてレジストリで管理するようにしても良いかも // ただ、「emailVerified が true なユーザーを find する」のようなクエリは書けなくなるからウーン @@ -208,11 +201,6 @@ export class UserProfile { @JoinColumn() public pinnedPage: Page | null; - @Column("jsonb", { - default: {}, - }) - public integrations: Record; - @Index() @Column("boolean", { default: false, diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index a3c8a98aa8..8d553baabc 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -504,7 +504,6 @@ export const UserRepository = db.getRepository(User).extend({ hasUnreadNotification: this.getHasUnreadNotification(user.id), hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id), - integrations: profile!.integrations, mutedWords: profile!.mutedWords, mutedInstances: profile!.mutedInstances, mutingNotificationTypes: profile!.mutingNotificationTypes, diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts index 31fda6df9a..8c30b8c2ec 100644 --- a/packages/backend/src/models/schema/user.ts +++ b/packages/backend/src/models/schema/user.ts @@ -449,11 +449,6 @@ export const packedMeDetailedOnlySchema = { nullable: false, optional: false, }, - integrations: { - type: "object", - nullable: true, - optional: false, - }, mutedWords: { type: "array", nullable: false, diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index f8208e6d7b..52d542810c 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -1,53 +1,46 @@ -import { URL } from "node:url"; +import {URL} from "node:url"; import promiseLimit from "promise-limit"; import config from "@/config/index.js"; -import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; -import type { Note } from "@/models/entities/note.js"; -import { updateUsertags } from "@/services/update-hashtag.js"; +import {registerOrFetchInstanceDoc} from "@/services/register-or-fetch-instance-doc.js"; +import type {Note} from "@/models/entities/note.js"; +import {updateUsertags} from "@/services/update-hashtag.js"; +import {Followings, Instances, UserProfiles, UserPublickeys, Users,} from "@/models/index.js"; +import type {CacheableUser, IRemoteUser} from "@/models/entities/user.js"; +import {User} from "@/models/entities/user.js"; +import type {Emoji} from "@/models/entities/emoji.js"; +import {UserNotePining} from "@/models/entities/user-note-pining.js"; +import {genId} from "@/misc/gen-id.js"; +import {instanceChart, usersChart} from "@/services/chart/index.js"; +import {UserPublickey} from "@/models/entities/user-publickey.js"; +import {isDuplicateKeyValueError} from "@/misc/is-duplicate-key-value-error.js"; +import {toPuny} from "@/misc/convert-host.js"; +import {UserProfile} from "@/models/entities/user-profile.js"; +import {toArray} from "@/prelude/array.js"; +import {fetchInstanceMetadata} from "@/services/fetch-instance-metadata.js"; +import {normalizeForSearch} from "@/misc/normalize-for-search.js"; +import {truncate} from "@/misc/truncate.js"; +import {StatusError} from "@/misc/fetch.js"; +import {uriPersonCache} from "@/services/user-cache.js"; +import {publishInternalEvent} from "@/services/stream.js"; +import {db} from "@/db/postgre.js"; +import {apLogger} from "../logger.js"; +import {htmlToMfm} from "../misc/html-to-mfm.js"; +import {fromHtml} from "../../../mfm/from-html.js"; +import type {IActor, IObject} from "../type.js"; import { - Users, - Instances, - DriveFiles, - Followings, - UserProfiles, - UserPublickeys, -} from "@/models/index.js"; -import type { IRemoteUser, CacheableUser } from "@/models/entities/user.js"; -import { User } from "@/models/entities/user.js"; -import type { Emoji } from "@/models/entities/emoji.js"; -import { UserNotePining } from "@/models/entities/user-note-pining.js"; -import { genId } from "@/misc/gen-id.js"; -import { instanceChart, usersChart } from "@/services/chart/index.js"; -import { UserPublickey } from "@/models/entities/user-publickey.js"; -import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; -import { toPuny } from "@/misc/convert-host.js"; -import { UserProfile } from "@/models/entities/user-profile.js"; -import { toArray } from "@/prelude/array.js"; -import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js"; -import { normalizeForSearch } from "@/misc/normalize-for-search.js"; -import { truncate } from "@/misc/truncate.js"; -import { StatusError } from "@/misc/fetch.js"; -import { uriPersonCache } from "@/services/user-cache.js"; -import { publishInternalEvent } from "@/services/stream.js"; -import { db } from "@/db/postgre.js"; -import { apLogger } from "../logger.js"; -import { htmlToMfm } from "../misc/html-to-mfm.js"; -import { fromHtml } from "../../../mfm/from-html.js"; -import type { IActor, IObject, IApPropertyValue } from "../type.js"; -import { - isCollectionOrOrderedCollection, - isCollection, getApId, - getOneApHrefNullable, - isPropertyValue, getApType, + getOneApHrefNullable, isActor, + isCollection, + isCollectionOrOrderedCollection, + isPropertyValue, } from "../type.js"; import Resolver from "../resolver.js"; -import { extractApHashtags } from "./tag.js"; -import { resolveNote, extractEmojis } from "./note.js"; -import { resolveImage } from "./image.js"; +import {extractApHashtags} from "./tag.js"; +import {extractEmojis, resolveNote} from "./note.js"; +import {resolveImage} from "./image.js"; const logger = apLogger; @@ -588,39 +581,6 @@ export async function resolvePerson( return await createPerson(uri, resolver); } -const services: { - [x: string]: (id: string, username: string) => any; -} = { - "misskey:authentication:twitter": (userId, screenName) => ({ - userId, - screenName, - }), - "misskey:authentication:github": (id, login) => ({ id, login }), - "misskey:authentication:discord": (id, name) => $discord(id, name), -}; - -const $discord = (id: string, name: string) => { - if (typeof name !== "string") { - name = "unknown#0000"; - } - const [username, discriminator] = name.split("#"); - return { id, username, discriminator }; -}; - -function addService(target: { [x: string]: any }, source: IApPropertyValue) { - const service = services[source.name]; - - if (typeof source.value !== "string") { - source.value = "unknown"; - } - - const [id, username] = source.value.split("@"); - - if (service) { - target[source.name.split(":")[2]] = service(id, username); - } -} - export function analyzeAttachments( attachments: IObject | IObject[] | undefined, ) { @@ -628,13 +588,11 @@ export function analyzeAttachments( name: string; value: string; }[] = []; - const services: { [x: string]: any } = {}; - + if (Array.isArray(attachments)) { for (const attachment of attachments.filter(isPropertyValue)) { - if (isPropertyValue(attachment.identifier)) { - addService(services, attachment.identifier); - } else { + // TODO: This doesn't sound right + if (!isPropertyValue(attachment.identifier)) { fields.push({ name: attachment.name, value: fromHtml(attachment.value), @@ -643,7 +601,7 @@ export function analyzeAttachments( } } - return { fields, services }; + return { fields }; } export async function updateFeatured(userId: User["id"], resolver?: Resolver) { diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index ad70310633..b7359049ea 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -1,8 +1,7 @@ import config from "@/config/index.js"; -import { fetchMeta } from "@/misc/fetch-meta.js"; -import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; +import {fetchMeta} from "@/misc/fetch-meta.js"; +import {MAX_CAPTION_TEXT_LENGTH, MAX_NOTE_TEXT_LENGTH} from "@/const.js"; import define from "../../define.js"; -import { Exp } from "@tensorflow/tfjs"; export const meta = { tags: ["meta"], @@ -170,21 +169,6 @@ export const meta = { optional: false, nullable: false, }, - enableTwitterIntegration: { - type: "boolean", - optional: false, - nullable: false, - }, - enableGithubIntegration: { - type: "boolean", - optional: false, - nullable: false, - }, - enableDiscordIntegration: { - type: "boolean", - optional: false, - nullable: false, - }, enableServiceWorker: { type: "boolean", optional: false, @@ -326,36 +310,6 @@ export const meta = { nullable: true, format: "id", }, - twitterConsumerKey: { - type: "string", - optional: true, - nullable: true, - }, - twitterConsumerSecret: { - type: "string", - optional: true, - nullable: true, - }, - githubClientId: { - type: "string", - optional: true, - nullable: true, - }, - githubClientSecret: { - type: "string", - optional: true, - nullable: true, - }, - discordClientId: { - type: "string", - optional: true, - nullable: true, - }, - discordClientSecret: { - type: "string", - optional: true, - nullable: true, - }, summaryProxy: { type: "string", optional: true, @@ -532,9 +486,6 @@ export default define(meta, paramDef, async (ps, me) => { defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, enableEmail: instance.enableEmail, - enableTwitterIntegration: instance.enableTwitterIntegration, - enableGithubIntegration: instance.enableGithubIntegration, - enableDiscordIntegration: instance.enableDiscordIntegration, enableServiceWorker: instance.enableServiceWorker, translatorAvailable: instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null, @@ -561,12 +512,6 @@ export default define(meta, paramDef, async (ps, me) => { enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, proxyAccountId: instance.proxyAccountId, - twitterConsumerKey: instance.twitterConsumerKey, - twitterConsumerSecret: instance.twitterConsumerSecret, - githubClientId: instance.githubClientId, - githubClientSecret: instance.githubClientSecret, - discordClientId: instance.discordClientId, - discordClientSecret: instance.discordClientSecret, summalyProxy: instance.summalyProxy, email: instance.email, smtpSecure: instance.smtpSecure, diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 7a2bf23651..dd280e23e6 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -1,4 +1,4 @@ -import { Signins, UserProfiles, Users } from "@/models/index.js"; +import {Signins, UserProfiles, Users} from "@/models/index.js"; import define from "../../define.js"; export const meta = { @@ -46,13 +46,6 @@ export default define(meta, paramDef, async (ps, me) => { }; } - const maskedKeys = ["accessToken", "accessTokenSecret", "refreshToken"]; - Object.keys(profile.integrations).forEach((integration) => { - maskedKeys.forEach( - (key) => (profile.integrations[integration][key] = ""), - ); - }); - const signins = await Signins.findBy({ userId: user.id }); return { @@ -66,7 +59,6 @@ export default define(meta, paramDef, async (ps, me) => { carefulBot: profile.carefulBot, injectFeaturedNote: profile.injectFeaturedNote, receiveAnnouncementEmail: profile.receiveAnnouncementEmail, - integrations: profile.integrations, mutedWords: profile.mutedWords, mutedInstances: profile.mutedInstances, mutingNotificationTypes: profile.mutingNotificationTypes, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index b967112a5c..61ba0ea755 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -1,7 +1,6 @@ -import { Meta } from "@/models/entities/meta.js"; -import { insertModerationLog } from "@/services/insert-moderation-log.js"; -import { DB_MAX_NOTE_TEXT_LENGTH } from "@/misc/hard-limits.js"; -import { db } from "@/db/postgre.js"; +import {Meta} from "@/models/entities/meta.js"; +import {insertModerationLog} from "@/services/insert-moderation-log.js"; +import {db} from "@/db/postgre.js"; import define from "../../define.js"; export const meta = { @@ -133,15 +132,6 @@ export const paramDef = { deeplIsPro: { type: "boolean" }, libreTranslateApiUrl: { type: "string", nullable: true }, libreTranslateApiKey: { type: "string", nullable: true }, - enableTwitterIntegration: { type: "boolean" }, - twitterConsumerKey: { type: "string", nullable: true }, - twitterConsumerSecret: { type: "string", nullable: true }, - enableGithubIntegration: { type: "boolean" }, - githubClientId: { type: "string", nullable: true }, - githubClientSecret: { type: "string", nullable: true }, - enableDiscordIntegration: { type: "boolean" }, - discordClientId: { type: "string", nullable: true }, - discordClientSecret: { type: "string", nullable: true }, enableEmail: { type: "boolean" }, email: { type: "string", nullable: true }, smtpSecure: { type: "boolean" }, @@ -385,42 +375,6 @@ export default define(meta, paramDef, async (ps, me) => { set.summalyProxy = ps.summalyProxy; } - if (ps.enableTwitterIntegration !== undefined) { - set.enableTwitterIntegration = ps.enableTwitterIntegration; - } - - if (ps.twitterConsumerKey !== undefined) { - set.twitterConsumerKey = ps.twitterConsumerKey; - } - - if (ps.twitterConsumerSecret !== undefined) { - set.twitterConsumerSecret = ps.twitterConsumerSecret; - } - - if (ps.enableGithubIntegration !== undefined) { - set.enableGithubIntegration = ps.enableGithubIntegration; - } - - if (ps.githubClientId !== undefined) { - set.githubClientId = ps.githubClientId; - } - - if (ps.githubClientSecret !== undefined) { - set.githubClientSecret = ps.githubClientSecret; - } - - if (ps.enableDiscordIntegration !== undefined) { - set.enableDiscordIntegration = ps.enableDiscordIntegration; - } - - if (ps.discordClientId !== undefined) { - set.discordClientId = ps.discordClientId; - } - - if (ps.discordClientSecret !== undefined) { - set.discordClientSecret = ps.discordClientSecret; - } - if (ps.enableEmail !== undefined) { set.enableEmail = ps.enableEmail; } diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index f6c978b2de..924ce0385e 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,9 +1,9 @@ import JSON5 from "json5"; -import { IsNull, MoreThan } from "typeorm"; +import {IsNull, MoreThan} from "typeorm"; import config from "@/config/index.js"; -import { fetchMeta } from "@/misc/fetch-meta.js"; -import { Ads, Emojis, Users } from "@/models/index.js"; -import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; +import {fetchMeta} from "@/misc/fetch-meta.js"; +import {Ads, Emojis, Users} from "@/models/index.js"; +import {MAX_CAPTION_TEXT_LENGTH, MAX_NOTE_TEXT_LENGTH} from "@/const.js"; import define from "../define.js"; export const meta = { @@ -268,21 +268,6 @@ export const meta = { optional: false, nullable: false, }, - enableTwitterIntegration: { - type: "boolean", - optional: false, - nullable: false, - }, - enableGithubIntegration: { - type: "boolean", - optional: false, - nullable: false, - }, - enableDiscordIntegration: { - type: "boolean", - optional: false, - nullable: false, - }, enableServiceWorker: { type: "boolean", optional: false, @@ -343,21 +328,6 @@ export const meta = { optional: false, nullable: false, }, - twitter: { - type: "boolean", - optional: false, - nullable: false, - }, - github: { - type: "boolean", - optional: false, - nullable: false, - }, - discord: { - type: "boolean", - optional: false, - nullable: false, - }, serviceWorker: { type: "boolean", optional: false, @@ -482,10 +452,6 @@ export default define(meta, paramDef, async (ps, me) => { })), enableEmail: instance.enableEmail, - enableTwitterIntegration: instance.enableTwitterIntegration, - enableGithubIntegration: instance.enableGithubIntegration, - enableDiscordIntegration: instance.enableDiscordIntegration, - enableServiceWorker: instance.enableServiceWorker, translatorAvailable: @@ -525,9 +491,6 @@ export default define(meta, paramDef, async (ps, me) => { hcaptcha: instance.enableHcaptcha, recaptcha: instance.enableRecaptcha, objectStorage: instance.useObjectStorage, - twitter: instance.enableTwitterIntegration, - github: instance.enableGithubIntegration, - discord: instance.enableDiscordIntegration, serviceWorker: instance.enableServiceWorker, postEditing: instance.experimentalFeatures?.postEditing || false, postImports: instance.experimentalFeatures?.postImports || false, diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index a0a9559d93..16597929be 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -16,9 +16,6 @@ import signup from "./private/signup.js"; import signin from "./private/signin.js"; import signupPending from "./private/signup-pending.js"; import verifyEmail from "./private/verify-email.js"; -import discord from "./service/discord.js"; -import github from "./service/github.js"; -import twitter from "./service/twitter.js"; // Init app const app = new Koa(); @@ -105,10 +102,6 @@ router.post("/signin", signin); router.post("/signup-pending", signupPending); router.post("/verify-email", verifyEmail); -router.use(discord.routes()); -router.use(github.routes()); -router.use(twitter.routes()); - router.post("/miauth/:session/check", async (ctx) => { const token = await AccessTokens.findOneBy({ session: ctx.params.session, diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts deleted file mode 100644 index 9906d2f7ca..0000000000 --- a/packages/backend/src/server/api/service/discord.ts +++ /dev/null @@ -1,333 +0,0 @@ -import type Koa from "koa"; -import Router from "@koa/router"; -import { OAuth2 } from "oauth"; -import { v4 as uuid } from "uuid"; -import { IsNull } from "typeorm"; -import { getJson } from "@/misc/fetch.js"; -import config from "@/config/index.js"; -import { publishMainStream } from "@/services/stream.js"; -import { fetchMeta } from "@/misc/fetch-meta.js"; -import { Users, UserProfiles } from "@/models/index.js"; -import type { ILocalUser } from "@/models/entities/user.js"; -import { redisClient } from "../../../db/redis.js"; -import signin from "../common/signin.js"; - -function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1]; -} - -function compareOrigin(ctx: Koa.BaseContext): boolean { - function normalizeUrl(url?: string): string { - return url ? (url.endsWith("/") ? url.substr(0, url.length - 1) : url) : ""; - } - - const referer = ctx.headers["referer"]; - - return normalizeUrl(referer) === normalizeUrl(config.url); -} - -// Init router -const router = new Router(); - -router.get("/disconnect/discord", async (ctx) => { - if (!compareOrigin(ctx)) { - ctx.throw(400, "invalid origin"); - return; - } - - const userToken = getUserToken(ctx); - if (!userToken) { - ctx.throw(400, "signin required"); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - profile.integrations.discord = undefined; - - await UserProfiles.update(user.id, { - integrations: profile.integrations, - }); - - ctx.body = "Discordの連携を解除しました :v:"; - - // Publish i updated event - publishMainStream( - user.id, - "meUpdated", - await Users.pack(user, user, { - detail: true, - includeSecrets: true, - }), - ); -}); - -async function getOAuth2() { - const meta = await fetchMeta(true); - - if (meta.enableDiscordIntegration) { - return new OAuth2( - meta.discordClientId!, - meta.discordClientSecret!, - "https://discord.com/", - "api/oauth2/authorize", - "api/oauth2/token", - ); - } else { - return null; - } -} - -router.get("/connect/discord", async (ctx) => { - if (!compareOrigin(ctx)) { - ctx.throw(400, "invalid origin"); - return; - } - - const userToken = getUserToken(ctx); - if (!userToken) { - ctx.throw(400, "signin required"); - return; - } - - const params = { - redirect_uri: `${config.url}/api/dc/cb`, - scope: ["identify"], - state: uuid(), - response_type: "code", - }; - - redisClient.set(userToken, JSON.stringify(params)); - - const oauth2 = await getOAuth2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); -}); - -router.get("/signin/discord", async (ctx) => { - const sessid = uuid(); - - const params = { - redirect_uri: `${config.url}/api/dc/cb`, - scope: ["identify"], - state: uuid(), - response_type: "code", - }; - - ctx.cookies.set("signin_with_discord_sid", sessid, { - path: "/", - secure: config.url.startsWith("https"), - httpOnly: true, - }); - - redisClient.set(sessid, JSON.stringify(params)); - - const oauth2 = await getOAuth2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); -}); - -router.get("/dc/cb", async (ctx) => { - const userToken = getUserToken(ctx); - - const oauth2 = await getOAuth2(); - - if (!userToken) { - const sessid = ctx.cookies.get("signin_with_discord_sid"); - - if (!sessid) { - ctx.throw(400, "invalid session"); - return; - } - - const code = ctx.query.code; - - if (!code || typeof code !== "string") { - ctx.throw(400, "invalid session"); - return; - } - - const { redirect_uri, state } = await new Promise((res, rej) => { - redisClient.get(sessid, async (_, state) => { - res(JSON.parse(state)); - }); - }); - - if (ctx.query.state !== state) { - ctx.throw(400, "invalid session"); - return; - } - - const { accessToken, refreshToken, expiresDate } = await new Promise( - (res, rej) => - oauth2!.getOAuthAccessToken( - code, - { - grant_type: "authorization_code", - redirect_uri, - }, - (err, accessToken, refreshToken, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ - accessToken, - refreshToken, - expiresDate: Date.now() + Number(result.expires_in) * 1000, - }); - } - }, - ), - ); - - const { id, username, discriminator } = (await getJson( - "https://discord.com/api/users/@me", - "*/*", - 10 * 1000, - { - Authorization: `Bearer ${accessToken}`, - }, - )) as Record; - - if ( - typeof id !== "string" || - typeof username !== "string" || - typeof discriminator !== "string" - ) { - ctx.throw(400, "invalid session"); - return; - } - - const profile = await UserProfiles.createQueryBuilder() - .where("\"integrations\"->'discord'->>'id' = :id", { id: id }) - .andWhere('"userHost" IS NULL') - .getOne(); - - if (profile == null) { - ctx.throw( - 404, - `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`, - ); - return; - } - - await UserProfiles.update(profile.userId, { - integrations: { - ...profile.integrations, - discord: { - id: id, - accessToken: accessToken, - refreshToken: refreshToken, - expiresDate: expiresDate, - username: username, - discriminator: discriminator, - }, - }, - }); - - signin( - ctx, - (await Users.findOneBy({ id: profile.userId })) as ILocalUser, - true, - ); - } else { - const code = ctx.query.code; - - if (!code || typeof code !== "string") { - ctx.throw(400, "invalid session"); - return; - } - - const { redirect_uri, state } = await new Promise((res, rej) => { - redisClient.get(userToken, async (_, state) => { - res(JSON.parse(state)); - }); - }); - - if (ctx.query.state !== state) { - ctx.throw(400, "invalid session"); - return; - } - - const { accessToken, refreshToken, expiresDate } = await new Promise( - (res, rej) => - oauth2!.getOAuthAccessToken( - code, - { - grant_type: "authorization_code", - redirect_uri, - }, - (err, accessToken, refreshToken, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ - accessToken, - refreshToken, - expiresDate: Date.now() + Number(result.expires_in) * 1000, - }); - } - }, - ), - ); - - const { id, username, discriminator } = (await getJson( - "https://discord.com/api/users/@me", - "*/*", - 10 * 1000, - { - Authorization: `Bearer ${accessToken}`, - }, - )) as Record; - if ( - typeof id !== "string" || - typeof username !== "string" || - typeof discriminator !== "string" - ) { - ctx.throw(400, "invalid session"); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - await UserProfiles.update(user.id, { - integrations: { - ...profile.integrations, - discord: { - accessToken: accessToken, - refreshToken: refreshToken, - expiresDate: expiresDate, - id: id, - username: username, - discriminator: discriminator, - }, - }, - }); - - ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`; - - // Publish i updated event - publishMainStream( - user.id, - "meUpdated", - await Users.pack(user, user, { - detail: true, - includeSecrets: true, - }), - ); - } -}); - -export default router; diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts deleted file mode 100644 index f77c5f795d..0000000000 --- a/packages/backend/src/server/api/service/github.ts +++ /dev/null @@ -1,296 +0,0 @@ -import type Koa from "koa"; -import Router from "@koa/router"; -import { OAuth2 } from "oauth"; -import { v4 as uuid } from "uuid"; -import { IsNull } from "typeorm"; -import { getJson } from "@/misc/fetch.js"; -import config from "@/config/index.js"; -import { publishMainStream } from "@/services/stream.js"; -import { fetchMeta } from "@/misc/fetch-meta.js"; -import { Users, UserProfiles } from "@/models/index.js"; -import type { ILocalUser } from "@/models/entities/user.js"; -import { redisClient } from "../../../db/redis.js"; -import signin from "../common/signin.js"; - -function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1]; -} - -function compareOrigin(ctx: Koa.BaseContext): boolean { - function normalizeUrl(url?: string): string { - return url ? (url.endsWith("/") ? url.substr(0, url.length - 1) : url) : ""; - } - - const referer = ctx.headers["referer"]; - - return normalizeUrl(referer) === normalizeUrl(config.url); -} - -// Init router -const router = new Router(); - -router.get("/disconnect/github", async (ctx) => { - if (!compareOrigin(ctx)) { - ctx.throw(400, "invalid origin"); - return; - } - - const userToken = getUserToken(ctx); - if (!userToken) { - ctx.throw(400, "signin required"); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - profile.integrations.github = undefined; - - await UserProfiles.update(user.id, { - integrations: profile.integrations, - }); - - ctx.body = "GitHubの連携を解除しました :v:"; - - // Publish i updated event - publishMainStream( - user.id, - "meUpdated", - await Users.pack(user, user, { - detail: true, - includeSecrets: true, - }), - ); -}); - -async function getOath2() { - const meta = await fetchMeta(true); - - if ( - meta.enableGithubIntegration && - meta.githubClientId && - meta.githubClientSecret - ) { - return new OAuth2( - meta.githubClientId, - meta.githubClientSecret, - "https://github.com/", - "login/oauth/authorize", - "login/oauth/access_token", - ); - } else { - return null; - } -} - -router.get("/connect/github", async (ctx) => { - if (!compareOrigin(ctx)) { - ctx.throw(400, "invalid origin"); - return; - } - - const userToken = getUserToken(ctx); - if (!userToken) { - ctx.throw(400, "signin required"); - return; - } - - const params = { - redirect_uri: `${config.url}/api/gh/cb`, - scope: ["read:user"], - state: uuid(), - }; - - redisClient.set(userToken, JSON.stringify(params)); - - const oauth2 = await getOath2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); -}); - -router.get("/signin/github", async (ctx) => { - const sessid = uuid(); - - const params = { - redirect_uri: `${config.url}/api/gh/cb`, - scope: ["read:user"], - state: uuid(), - }; - - ctx.cookies.set("signin_with_github_sid", sessid, { - path: "/", - secure: config.url.startsWith("https"), - httpOnly: true, - }); - - redisClient.set(sessid, JSON.stringify(params)); - - const oauth2 = await getOath2(); - ctx.redirect(oauth2!.getAuthorizeUrl(params)); -}); - -router.get("/gh/cb", async (ctx) => { - const userToken = getUserToken(ctx); - - const oauth2 = await getOath2(); - - if (!userToken) { - const sessid = ctx.cookies.get("signin_with_github_sid"); - - if (!sessid) { - ctx.throw(400, "invalid session"); - return; - } - - const code = ctx.query.code; - - if (!code || typeof code !== "string") { - ctx.throw(400, "invalid session"); - return; - } - - const { redirect_uri, state } = await new Promise((res, rej) => { - redisClient.get(sessid, async (_, state) => { - res(JSON.parse(state)); - }); - }); - - if (ctx.query.state !== state) { - ctx.throw(400, "invalid session"); - return; - } - - const { accessToken } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken( - code, - { - redirect_uri, - }, - (err, accessToken, refresh, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ accessToken }); - } - }, - ), - ); - - const { login, id } = (await getJson( - "https://api.github.com/user", - "application/vnd.github.v3+json", - 10 * 1000, - { - Authorization: `bearer ${accessToken}`, - }, - )) as Record; - if (typeof login !== "string" || typeof id !== "string") { - ctx.throw(400, "invalid session"); - return; - } - - const link = await UserProfiles.createQueryBuilder() - .where("\"integrations\"->'github'->>'id' = :id", { id: id }) - .andWhere('"userHost" IS NULL') - .getOne(); - - if (link == null) { - ctx.throw( - 404, - `@${login}と連携しているMisskeyアカウントはありませんでした...`, - ); - return; - } - - signin( - ctx, - (await Users.findOneBy({ id: link.userId })) as ILocalUser, - true, - ); - } else { - const code = ctx.query.code; - - if (!code || typeof code !== "string") { - ctx.throw(400, "invalid session"); - return; - } - - const { redirect_uri, state } = await new Promise((res, rej) => { - redisClient.get(userToken, async (_, state) => { - res(JSON.parse(state)); - }); - }); - - if (ctx.query.state !== state) { - ctx.throw(400, "invalid session"); - return; - } - - const { accessToken } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken( - code, - { redirect_uri }, - (err, accessToken, refresh, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ accessToken }); - } - }, - ), - ); - - const { login, id } = (await getJson( - "https://api.github.com/user", - "application/vnd.github.v3+json", - 10 * 1000, - { - Authorization: `bearer ${accessToken}`, - }, - )) as Record; - - if (typeof login !== "string" || typeof id !== "string") { - ctx.throw(400, "invalid session"); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - await UserProfiles.update(user.id, { - integrations: { - ...profile.integrations, - github: { - accessToken: accessToken, - id: id, - login: login, - }, - }, - }); - - ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`; - - // Publish i updated event - publishMainStream( - user.id, - "meUpdated", - await Users.pack(user, user, { - detail: true, - includeSecrets: true, - }), - ); - } -}); - -export default router; diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts deleted file mode 100644 index 3695592410..0000000000 --- a/packages/backend/src/server/api/service/twitter.ts +++ /dev/null @@ -1,226 +0,0 @@ -import type Koa from "koa"; -import Router from "@koa/router"; -import { v4 as uuid } from "uuid"; -import autwh from "autwh"; -import { IsNull } from "typeorm"; -import { publishMainStream } from "@/services/stream.js"; -import config from "@/config/index.js"; -import { fetchMeta } from "@/misc/fetch-meta.js"; -import { Users, UserProfiles } from "@/models/index.js"; -import type { ILocalUser } from "@/models/entities/user.js"; -import signin from "../common/signin.js"; -import { redisClient } from "../../../db/redis.js"; - -function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1]; -} - -function compareOrigin(ctx: Koa.BaseContext): boolean { - function normalizeUrl(url?: string): string { - return url == null - ? "" - : url.endsWith("/") - ? url.substr(0, url.length - 1) - : url; - } - - const referer = ctx.headers["referer"]; - - return normalizeUrl(referer) === normalizeUrl(config.url); -} - -// Init router -const router = new Router(); - -router.get("/disconnect/twitter", async (ctx) => { - if (!compareOrigin(ctx)) { - ctx.throw(400, "invalid origin"); - return; - } - - const userToken = getUserToken(ctx); - if (userToken == null) { - ctx.throw(400, "signin required"); - return; - } - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - profile.integrations.twitter = undefined; - - await UserProfiles.update(user.id, { - integrations: profile.integrations, - }); - - ctx.body = "Twitterの連携を解除しました :v:"; - - // Publish i updated event - publishMainStream( - user.id, - "meUpdated", - await Users.pack(user, user, { - detail: true, - includeSecrets: true, - }), - ); -}); - -async function getTwAuth() { - const meta = await fetchMeta(true); - - if ( - meta.enableTwitterIntegration && - meta.twitterConsumerKey && - meta.twitterConsumerSecret - ) { - return autwh({ - consumerKey: meta.twitterConsumerKey, - consumerSecret: meta.twitterConsumerSecret, - callbackUrl: `${config.url}/api/tw/cb`, - }); - } else { - return null; - } -} - -router.get("/connect/twitter", async (ctx) => { - if (!compareOrigin(ctx)) { - ctx.throw(400, "invalid origin"); - return; - } - - const userToken = getUserToken(ctx); - if (userToken == null) { - ctx.throw(400, "signin required"); - return; - } - - const twAuth = await getTwAuth(); - const twCtx = await twAuth!.begin(); - redisClient.set(userToken, JSON.stringify(twCtx)); - ctx.redirect(twCtx.url); -}); - -router.get("/signin/twitter", async (ctx) => { - const twAuth = await getTwAuth(); - const twCtx = await twAuth!.begin(); - - const sessid = uuid(); - - redisClient.set(sessid, JSON.stringify(twCtx)); - - ctx.cookies.set("signin_with_twitter_sid", sessid, { - path: "/", - secure: config.url.startsWith("https"), - httpOnly: true, - }); - - ctx.redirect(twCtx.url); -}); - -router.get("/tw/cb", async (ctx) => { - const userToken = getUserToken(ctx); - - const twAuth = await getTwAuth(); - - if (userToken == null) { - const sessid = ctx.cookies.get("signin_with_twitter_sid"); - - if (sessid == null) { - ctx.throw(400, "invalid session"); - return; - } - - const get = new Promise((res, rej) => { - redisClient.get(sessid, async (_, twCtx) => { - res(twCtx); - }); - }); - - const twCtx = await get; - - const verifier = ctx.query.oauth_verifier; - if (!verifier || typeof verifier !== "string") { - ctx.throw(400, "invalid session"); - return; - } - - const result = await twAuth!.done(JSON.parse(twCtx), verifier); - - const link = await UserProfiles.createQueryBuilder() - .where("\"integrations\"->'twitter'->>'userId' = :id", { - id: result.userId, - }) - .andWhere('"userHost" IS NULL') - .getOne(); - - if (link == null) { - ctx.throw( - 404, - `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`, - ); - return; - } - - signin( - ctx, - (await Users.findOneBy({ id: link.userId })) as ILocalUser, - true, - ); - } else { - const verifier = ctx.query.oauth_verifier; - - if (!verifier || typeof verifier !== "string") { - ctx.throw(400, "invalid session"); - return; - } - - const get = new Promise((res, rej) => { - redisClient.get(userToken, async (_, twCtx) => { - res(twCtx); - }); - }); - - const twCtx = await get; - - const result = await twAuth!.done(JSON.parse(twCtx), verifier); - - const user = await Users.findOneByOrFail({ - host: IsNull(), - token: userToken, - }); - - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - await UserProfiles.update(user.id, { - integrations: { - ...profile.integrations, - twitter: { - accessToken: result.accessToken, - accessTokenSecret: result.accessTokenSecret, - userId: result.userId, - screenName: result.screenName, - }, - }, - }); - - ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; - - // Publish i updated event - publishMainStream( - user.id, - "meUpdated", - await Users.pack(user, user, { - detail: true, - includeSecrets: true, - }), - ); - } -}); - -export default router;