mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-26 14:53:09 +02:00
parent
65ed702d87
commit
562b02310f
24 changed files with 6 additions and 1426 deletions
|
@ -29,6 +29,7 @@ import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
|||
import { StatusError } from '@/misc/status-error.js';
|
||||
import type { UtilityService } from '@/core/UtilityService.js';
|
||||
import type { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
||||
import { extractApHashtags } from './tag.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
@ -43,37 +44,6 @@ import type { IActor, IObject, IApPropertyValue } from '../type.js';
|
|||
const nameLength = 128;
|
||||
const summaryLength = 2048;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApPersonService implements OnModuleInit {
|
||||
private utilityService: UtilityService;
|
||||
|
@ -540,22 +510,16 @@ export class ApPersonService implements OnModuleInit {
|
|||
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 {
|
||||
fields.push({
|
||||
name: attachment.name,
|
||||
value: this.mfmService.fromHtml(attachment.value),
|
||||
});
|
||||
}
|
||||
fields.push({
|
||||
name: attachment.name,
|
||||
value: this.mfmService.fromHtml(attachment.value),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { fields, services };
|
||||
return { fields };
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
@ -489,7 +489,6 @@ export class UserEntityService implements OnModuleInit {
|
|||
hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id),
|
||||
hasUnreadNotification: this.getHasUnreadNotification(user.id),
|
||||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||
integrations: profile!.integrations,
|
||||
mutedWords: profile!.mutedWords,
|
||||
mutedInstances: profile!.mutedInstances,
|
||||
mutingNotificationTypes: profile!.mutingNotificationTypes,
|
||||
|
|
|
@ -279,57 +279,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,
|
||||
|
|
|
@ -184,11 +184,6 @@ export class UserProfile {
|
|||
@JoinColumn()
|
||||
public pinnedPage: Page | null;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: {},
|
||||
})
|
||||
public integrations: Record<string, any>;
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false, select: false,
|
||||
|
|
|
@ -323,10 +323,6 @@ export const packedMeDetailedOnlySchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
integrations: {
|
||||
type: 'object',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
mutedWords: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
|
|
|
@ -111,9 +111,6 @@ export class NodeinfoServerService {
|
|||
enableHcaptcha: meta.enableHcaptcha,
|
||||
enableRecaptcha: meta.enableRecaptcha,
|
||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
||||
enableTwitterIntegration: meta.enableTwitterIntegration,
|
||||
enableGithubIntegration: meta.enableGithubIntegration,
|
||||
enableDiscordIntegration: meta.enableDiscordIntegration,
|
||||
enableEmail: meta.enableEmail,
|
||||
enableServiceWorker: meta.enableServiceWorker,
|
||||
proxyAccountName: proxyAccount ? proxyAccount.username : null,
|
||||
|
|
|
@ -7,9 +7,6 @@ import { NodeinfoServerService } from './NodeinfoServerService.js';
|
|||
import { ServerService } from './ServerService.js';
|
||||
import { WellKnownServerService } from './WellKnownServerService.js';
|
||||
import { GetterService } from './api/GetterService.js';
|
||||
import { DiscordServerService } from './api/integration/DiscordServerService.js';
|
||||
import { GithubServerService } from './api/integration/GithubServerService.js';
|
||||
import { TwitterServerService } from './api/integration/TwitterServerService.js';
|
||||
import { ChannelsService } from './api/stream/ChannelsService.js';
|
||||
import { ActivityPubServerService } from './ActivityPubServerService.js';
|
||||
import { ApiLoggerService } from './api/ApiLoggerService.js';
|
||||
|
@ -54,9 +51,6 @@ import { UserListChannelService } from './api/stream/channels/user-list.js';
|
|||
ServerService,
|
||||
WellKnownServerService,
|
||||
GetterService,
|
||||
DiscordServerService,
|
||||
GithubServerService,
|
||||
TwitterServerService,
|
||||
ChannelsService,
|
||||
ApiCallService,
|
||||
ApiLoggerService,
|
||||
|
|
|
@ -12,9 +12,6 @@ import endpoints, { IEndpoint } from './endpoints.js';
|
|||
import { ApiCallService } from './ApiCallService.js';
|
||||
import { SignupApiService } from './SignupApiService.js';
|
||||
import { SigninApiService } from './SigninApiService.js';
|
||||
import { GithubServerService } from './integration/GithubServerService.js';
|
||||
import { DiscordServerService } from './integration/DiscordServerService.js';
|
||||
import { TwitterServerService } from './integration/TwitterServerService.js';
|
||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
@Injectable()
|
||||
|
@ -38,9 +35,6 @@ export class ApiServerService {
|
|||
private apiCallService: ApiCallService,
|
||||
private signupApiService: SignupApiService,
|
||||
private signinApiService: SigninApiService,
|
||||
private githubServerService: GithubServerService,
|
||||
private discordServerService: DiscordServerService,
|
||||
private twitterServerService: TwitterServerService,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
}
|
||||
|
@ -133,10 +127,6 @@ export class ApiServerService {
|
|||
|
||||
fastify.post<{ Body: { code: string; } }>('/signup-pending', (request, reply) => this.signupApiService.signupPending(request, reply));
|
||||
|
||||
fastify.register(this.discordServerService.create);
|
||||
fastify.register(this.githubServerService.create);
|
||||
fastify.register(this.twitterServerService.create);
|
||||
|
||||
fastify.get('/v1/instance/peers', async (request, reply) => {
|
||||
const instances = await this.instancesRepository.find({
|
||||
select: ['host'],
|
||||
|
|
|
@ -138,18 +138,6 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
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, nullable: false,
|
||||
|
@ -223,30 +211,6 @@ export const meta = {
|
|||
optional: true, 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, nullable: true,
|
||||
|
@ -389,9 +353,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
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,
|
||||
pinnedPages: instance.pinnedPages,
|
||||
|
@ -409,12 +370,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
||||
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,
|
||||
|
|
|
@ -65,11 +65,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
};
|
||||
}
|
||||
|
||||
const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken'];
|
||||
Object.keys(profile.integrations).forEach(integration => {
|
||||
maskedKeys.forEach(key => profile.integrations[integration][key] = '<MASKED>');
|
||||
});
|
||||
|
||||
const signins = await this.signinsRepository.findBy({ userId: user.id });
|
||||
|
||||
const roles = await this.roleService.getUserRoles(user.id);
|
||||
|
@ -84,7 +79,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
carefulBot: profile.carefulBot,
|
||||
injectFeaturedNote: profile.injectFeaturedNote,
|
||||
receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
|
||||
integrations: profile.integrations,
|
||||
mutedWords: profile.mutedWords,
|
||||
mutedInstances: profile.mutedInstances,
|
||||
mutingNotificationTypes: profile.mutingNotificationTypes,
|
||||
|
|
|
@ -68,15 +68,6 @@ export const paramDef = {
|
|||
summalyProxy: { type: 'string', nullable: true },
|
||||
deeplAuthKey: { type: 'string', nullable: true },
|
||||
deeplIsPro: { type: 'boolean' },
|
||||
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' },
|
||||
|
@ -270,42 +261,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -169,18 +169,6 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
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, nullable: false,
|
||||
|
@ -225,18 +213,6 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
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, nullable: false,
|
||||
|
@ -325,11 +301,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
imageUrl: ad.imageUrl,
|
||||
})),
|
||||
enableEmail: instance.enableEmail,
|
||||
|
||||
enableTwitterIntegration: instance.enableTwitterIntegration,
|
||||
enableGithubIntegration: instance.enableGithubIntegration,
|
||||
enableDiscordIntegration: instance.enableDiscordIntegration,
|
||||
|
||||
enableServiceWorker: instance.enableServiceWorker,
|
||||
|
||||
translatorAvailable: instance.deeplAuthKey != null,
|
||||
|
@ -358,9 +329,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
recaptcha: instance.enableRecaptcha,
|
||||
turnstile: instance.enableTurnstile,
|
||||
objectStorage: instance.useObjectStorage,
|
||||
twitter: instance.enableTwitterIntegration,
|
||||
github: instance.enableGithubIntegration,
|
||||
discord: instance.enableDiscordIntegration,
|
||||
serviceWorker: instance.enableServiceWorker,
|
||||
miauth: true,
|
||||
};
|
||||
|
|
|
@ -1,308 +0,0 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { OAuth2 } from 'oauth';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IsNull } from 'typeorm';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import type { ILocalUser } from '@/models/entities/User.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { SigninService } from '../SigninService.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
@Injectable()
|
||||
export class DiscordServerService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private httpRequestService: HttpRequestService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private metaService: MetaService,
|
||||
private signinService: SigninService,
|
||||
) {
|
||||
//this.create = this.create.bind(this);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
fastify.get('/disconnect/discord', async (request, reply) => {
|
||||
if (!this.compareOrigin(request)) {
|
||||
throw new FastifyReplyError(400, 'invalid origin');
|
||||
}
|
||||
|
||||
const userToken = this.getUserToken(request);
|
||||
if (!userToken) {
|
||||
throw new FastifyReplyError(400, 'signin required');
|
||||
}
|
||||
|
||||
const user = await this.usersRepository.findOneByOrFail({
|
||||
host: IsNull(),
|
||||
token: userToken,
|
||||
});
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
delete profile.integrations.discord;
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
integrations: profile.integrations,
|
||||
});
|
||||
|
||||
// Publish i updated event
|
||||
this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
|
||||
return 'Discordの連携を解除しました :v:';
|
||||
});
|
||||
|
||||
const getOAuth2 = async () => {
|
||||
const meta = await this.metaService.fetch(true);
|
||||
|
||||
if (meta.enableDiscordIntegration) {
|
||||
return new OAuth2(
|
||||
meta.discordClientId!,
|
||||
meta.discordClientSecret!,
|
||||
'https://discord.com/',
|
||||
'api/oauth2/authorize',
|
||||
'api/oauth2/token');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
fastify.get('/connect/discord', async (request, reply) => {
|
||||
if (!this.compareOrigin(request)) {
|
||||
throw new FastifyReplyError(400, 'invalid origin');
|
||||
}
|
||||
|
||||
const userToken = this.getUserToken(request);
|
||||
if (!userToken) {
|
||||
throw new FastifyReplyError(400, 'signin required');
|
||||
}
|
||||
|
||||
const params = {
|
||||
redirect_uri: `${this.config.url}/api/dc/cb`,
|
||||
scope: ['identify'],
|
||||
state: uuid(),
|
||||
response_type: 'code',
|
||||
};
|
||||
|
||||
this.redisClient.set(userToken, JSON.stringify(params));
|
||||
|
||||
const oauth2 = await getOAuth2();
|
||||
reply.redirect(oauth2!.getAuthorizeUrl(params));
|
||||
});
|
||||
|
||||
fastify.get('/signin/discord', async (request, reply) => {
|
||||
const sessid = uuid();
|
||||
|
||||
const params = {
|
||||
redirect_uri: `${this.config.url}/api/dc/cb`,
|
||||
scope: ['identify'],
|
||||
state: uuid(),
|
||||
response_type: 'code',
|
||||
};
|
||||
|
||||
reply.setCookie('signin_with_discord_sid', sessid, {
|
||||
path: '/',
|
||||
secure: this.config.url.startsWith('https'),
|
||||
httpOnly: true,
|
||||
});
|
||||
|
||||
this.redisClient.set(sessid, JSON.stringify(params));
|
||||
|
||||
const oauth2 = await getOAuth2();
|
||||
reply.redirect(oauth2!.getAuthorizeUrl(params));
|
||||
});
|
||||
|
||||
fastify.get<{ Querystring: { code: string; state: string; } }>('/dc/cb', async (request, reply) => {
|
||||
const userToken = this.getUserToken(request);
|
||||
|
||||
const oauth2 = await getOAuth2();
|
||||
|
||||
if (!userToken) {
|
||||
const sessid = request.cookies['signin_with_discord_sid'];
|
||||
|
||||
if (!sessid) {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const code = request.query.code;
|
||||
|
||||
if (!code || typeof code !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
||||
this.redisClient.get(sessid, async (_, state) => {
|
||||
if (state == null) throw new Error('empty state');
|
||||
res(JSON.parse(state));
|
||||
});
|
||||
});
|
||||
|
||||
if (request.query.state !== state) {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const { accessToken, refreshToken, expiresDate } = await new Promise<any>((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 this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
})) as Record<string, unknown>;
|
||||
|
||||
if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const profile = await this.userProfilesRepository.createQueryBuilder()
|
||||
.where('"integrations"->\'discord\'->>\'id\' = :id', { id: id })
|
||||
.andWhere('"userHost" IS NULL')
|
||||
.getOne();
|
||||
|
||||
if (profile == null) {
|
||||
throw new FastifyReplyError(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`);
|
||||
}
|
||||
|
||||
await this.userProfilesRepository.update(profile.userId, {
|
||||
integrations: {
|
||||
...profile.integrations,
|
||||
discord: {
|
||||
id: id,
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
expiresDate: expiresDate,
|
||||
username: username,
|
||||
discriminator: discriminator,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: profile.userId }) as ILocalUser, true);
|
||||
} else {
|
||||
const code = request.query.code;
|
||||
|
||||
if (!code || typeof code !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
||||
this.redisClient.get(userToken, async (_, state) => {
|
||||
if (state == null) throw new Error('empty state');
|
||||
res(JSON.parse(state));
|
||||
});
|
||||
});
|
||||
|
||||
if (request.query.state !== state) {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const { accessToken, refreshToken, expiresDate } = await new Promise<any>((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 this.httpRequestService.getJson('https://discord.com/api/users/@me', '*/*', {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
})) as Record<string, unknown>;
|
||||
if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const user = await this.usersRepository.findOneByOrFail({
|
||||
host: IsNull(),
|
||||
token: userToken,
|
||||
});
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
integrations: {
|
||||
...profile.integrations,
|
||||
discord: {
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
expiresDate: expiresDate,
|
||||
id: id,
|
||||
username: username,
|
||||
discriminator: discriminator,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Publish i updated event
|
||||
this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
|
||||
return `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`;
|
||||
}
|
||||
});
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private getUserToken(request: FastifyRequest): string | null {
|
||||
return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private compareOrigin(request: FastifyRequest): boolean {
|
||||
function normalizeUrl(url?: string): string {
|
||||
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
||||
}
|
||||
|
||||
const referer = request.headers['referer'];
|
||||
|
||||
return (normalizeUrl(referer) === normalizeUrl(this.config.url));
|
||||
}
|
||||
}
|
|
@ -1,280 +0,0 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { OAuth2 } from 'oauth';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IsNull } from 'typeorm';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import type { ILocalUser } from '@/models/entities/User.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { SigninService } from '../SigninService.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
@Injectable()
|
||||
export class GithubServerService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private httpRequestService: HttpRequestService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private metaService: MetaService,
|
||||
private signinService: SigninService,
|
||||
) {
|
||||
//this.create = this.create.bind(this);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
fastify.get('/disconnect/github', async (request, reply) => {
|
||||
if (!this.compareOrigin(request)) {
|
||||
throw new FastifyReplyError(400, 'invalid origin');
|
||||
}
|
||||
|
||||
const userToken = this.getUserToken(request);
|
||||
if (!userToken) {
|
||||
throw new FastifyReplyError(400, 'signin required');
|
||||
}
|
||||
|
||||
const user = await this.usersRepository.findOneByOrFail({
|
||||
host: IsNull(),
|
||||
token: userToken,
|
||||
});
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
delete profile.integrations.github;
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
integrations: profile.integrations,
|
||||
});
|
||||
|
||||
// Publish i updated event
|
||||
this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
|
||||
return 'GitHubの連携を解除しました :v:';
|
||||
});
|
||||
|
||||
const getOath2 = async () => {
|
||||
const meta = await this.metaService.fetch(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;
|
||||
}
|
||||
};
|
||||
|
||||
fastify.get('/connect/github', async (request, reply) => {
|
||||
if (!this.compareOrigin(request)) {
|
||||
throw new FastifyReplyError(400, 'invalid origin');
|
||||
}
|
||||
|
||||
const userToken = this.getUserToken(request);
|
||||
if (!userToken) {
|
||||
throw new FastifyReplyError(400, 'signin required');
|
||||
}
|
||||
|
||||
const params = {
|
||||
redirect_uri: `${this.config.url}/api/gh/cb`,
|
||||
scope: ['read:user'],
|
||||
state: uuid(),
|
||||
};
|
||||
|
||||
this.redisClient.set(userToken, JSON.stringify(params));
|
||||
|
||||
const oauth2 = await getOath2();
|
||||
reply.redirect(oauth2!.getAuthorizeUrl(params));
|
||||
});
|
||||
|
||||
fastify.get('/signin/github', async (request, reply) => {
|
||||
const sessid = uuid();
|
||||
|
||||
const params = {
|
||||
redirect_uri: `${this.config.url}/api/gh/cb`,
|
||||
scope: ['read:user'],
|
||||
state: uuid(),
|
||||
};
|
||||
|
||||
reply.setCookie('signin_with_github_sid', sessid, {
|
||||
path: '/',
|
||||
secure: this.config.url.startsWith('https'),
|
||||
httpOnly: true,
|
||||
});
|
||||
|
||||
this.redisClient.set(sessid, JSON.stringify(params));
|
||||
|
||||
const oauth2 = await getOath2();
|
||||
reply.redirect(oauth2!.getAuthorizeUrl(params));
|
||||
});
|
||||
|
||||
fastify.get<{ Querystring: { code: string; state: string; } }>('/gh/cb', async (request, reply) => {
|
||||
const userToken = this.getUserToken(request);
|
||||
|
||||
const oauth2 = await getOath2();
|
||||
|
||||
if (!userToken) {
|
||||
const sessid = request.cookies['signin_with_github_sid'];
|
||||
|
||||
if (!sessid) {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const code = request.query.code;
|
||||
|
||||
if (!code || typeof code !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
||||
this.redisClient.get(sessid, async (_, state) => {
|
||||
if (state == null) throw new Error('empty state');
|
||||
res(JSON.parse(state));
|
||||
});
|
||||
});
|
||||
|
||||
if (request.query.state !== state) {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const { accessToken } = await new Promise<{ accessToken: string }>((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 this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', {
|
||||
'Authorization': `bearer ${accessToken}`,
|
||||
})) as Record<string, unknown>;
|
||||
if (typeof login !== 'string' || typeof id !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const link = await this.userProfilesRepository.createQueryBuilder()
|
||||
.where('"integrations"->\'github\'->>\'id\' = :id', { id: id })
|
||||
.andWhere('"userHost" IS NULL')
|
||||
.getOne();
|
||||
|
||||
if (link == null) {
|
||||
throw new FastifyReplyError(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`);
|
||||
}
|
||||
|
||||
return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true);
|
||||
} else {
|
||||
const code = request.query.code;
|
||||
|
||||
if (!code || typeof code !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
||||
this.redisClient.get(userToken, async (_, state) => {
|
||||
if (state == null) throw new Error('empty state');
|
||||
res(JSON.parse(state));
|
||||
});
|
||||
});
|
||||
|
||||
if (request.query.state !== state) {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const { accessToken } = await new Promise<{ accessToken: string }>((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 this.httpRequestService.getJson('https://api.github.com/user', 'application/vnd.github.v3+json', {
|
||||
'Authorization': `bearer ${accessToken}`,
|
||||
})) as Record<string, unknown>;
|
||||
|
||||
if (typeof login !== 'string' || typeof id !== 'number') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const user = await this.usersRepository.findOneByOrFail({
|
||||
host: IsNull(),
|
||||
token: userToken,
|
||||
});
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
integrations: {
|
||||
...profile.integrations,
|
||||
github: {
|
||||
accessToken: accessToken,
|
||||
id: id,
|
||||
login: login,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Publish i updated event
|
||||
this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
|
||||
return `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`;
|
||||
}
|
||||
});
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private getUserToken(request: FastifyRequest): string | null {
|
||||
return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private compareOrigin(request: FastifyRequest): boolean {
|
||||
function normalizeUrl(url?: string): string {
|
||||
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
||||
}
|
||||
|
||||
const referer = request.headers['referer'];
|
||||
|
||||
return (normalizeUrl(referer) === normalizeUrl(this.config.url));
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IsNull } from 'typeorm';
|
||||
import * as autwh from 'autwh';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import type { ILocalUser } from '@/models/entities/User.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { SigninService } from '../SigninService.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
@Injectable()
|
||||
export class TwitterServerService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private httpRequestService: HttpRequestService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private metaService: MetaService,
|
||||
private signinService: SigninService,
|
||||
) {
|
||||
//this.create = this.create.bind(this);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public create(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
fastify.get('/disconnect/twitter', async (request, reply) => {
|
||||
if (!this.compareOrigin(request)) {
|
||||
throw new FastifyReplyError(400, 'invalid origin');
|
||||
}
|
||||
|
||||
const userToken = this.getUserToken(request);
|
||||
if (userToken == null) {
|
||||
throw new FastifyReplyError(400, 'signin required');
|
||||
}
|
||||
|
||||
const user = await this.usersRepository.findOneByOrFail({
|
||||
host: IsNull(),
|
||||
token: userToken,
|
||||
});
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
delete profile.integrations.twitter;
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
integrations: profile.integrations,
|
||||
});
|
||||
|
||||
// Publish i updated event
|
||||
this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
|
||||
return 'Twitterの連携を解除しました :v:';
|
||||
});
|
||||
|
||||
const getTwAuth = async () => {
|
||||
const meta = await this.metaService.fetch(true);
|
||||
|
||||
if (meta.enableTwitterIntegration && meta.twitterConsumerKey && meta.twitterConsumerSecret) {
|
||||
return autwh({
|
||||
consumerKey: meta.twitterConsumerKey,
|
||||
consumerSecret: meta.twitterConsumerSecret,
|
||||
callbackUrl: `${this.config.url}/api/tw/cb`,
|
||||
});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
fastify.get('/connect/twitter', async (request, reply) => {
|
||||
if (!this.compareOrigin(request)) {
|
||||
throw new FastifyReplyError(400, 'invalid origin');
|
||||
}
|
||||
|
||||
const userToken = this.getUserToken(request);
|
||||
if (userToken == null) {
|
||||
throw new FastifyReplyError(400, 'signin required');
|
||||
}
|
||||
|
||||
const twAuth = await getTwAuth();
|
||||
const twCtx = await twAuth!.begin();
|
||||
this.redisClient.set(userToken, JSON.stringify(twCtx));
|
||||
reply.redirect(twCtx.url);
|
||||
});
|
||||
|
||||
fastify.get('/signin/twitter', async (request, reply) => {
|
||||
const twAuth = await getTwAuth();
|
||||
const twCtx = await twAuth!.begin();
|
||||
|
||||
const sessid = uuid();
|
||||
|
||||
this.redisClient.set(sessid, JSON.stringify(twCtx));
|
||||
|
||||
reply.setCookie('signin_with_twitter_sid', sessid, {
|
||||
path: '/',
|
||||
secure: this.config.url.startsWith('https'),
|
||||
httpOnly: true,
|
||||
});
|
||||
|
||||
reply.redirect(twCtx.url);
|
||||
});
|
||||
|
||||
fastify.get('/tw/cb', async (request, reply) => {
|
||||
const userToken = this.getUserToken(request);
|
||||
|
||||
const twAuth = await getTwAuth();
|
||||
|
||||
if (userToken == null) {
|
||||
const sessid = request.cookies['signin_with_twitter_sid'];
|
||||
|
||||
if (sessid == null) {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const get = new Promise<any>((res, rej) => {
|
||||
this.redisClient.get(sessid, async (_, twCtx) => {
|
||||
res(twCtx);
|
||||
});
|
||||
});
|
||||
|
||||
const twCtx = await get;
|
||||
|
||||
const verifier = request.query.oauth_verifier;
|
||||
if (!verifier || typeof verifier !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const result = await twAuth!.done(JSON.parse(twCtx), verifier);
|
||||
|
||||
const link = await this.userProfilesRepository.createQueryBuilder()
|
||||
.where('"integrations"->\'twitter\'->>\'userId\' = :id', { id: result.userId })
|
||||
.andWhere('"userHost" IS NULL')
|
||||
.getOne();
|
||||
|
||||
if (link == null) {
|
||||
throw new FastifyReplyError(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`);
|
||||
}
|
||||
|
||||
return this.signinService.signin(request, reply, await this.usersRepository.findOneBy({ id: link.userId }) as ILocalUser, true);
|
||||
} else {
|
||||
const verifier = request.query.oauth_verifier;
|
||||
|
||||
if (!verifier || typeof verifier !== 'string') {
|
||||
throw new FastifyReplyError(400, 'invalid session');
|
||||
}
|
||||
|
||||
const get = new Promise<any>((res, rej) => {
|
||||
this.redisClient.get(userToken, async (_, twCtx) => {
|
||||
res(twCtx);
|
||||
});
|
||||
});
|
||||
|
||||
const twCtx = await get;
|
||||
|
||||
const result = await twAuth!.done(JSON.parse(twCtx), verifier);
|
||||
|
||||
const user = await this.usersRepository.findOneByOrFail({
|
||||
host: IsNull(),
|
||||
token: userToken,
|
||||
});
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
integrations: {
|
||||
...profile.integrations,
|
||||
twitter: {
|
||||
accessToken: result.accessToken,
|
||||
accessTokenSecret: result.accessTokenSecret,
|
||||
userId: result.userId,
|
||||
screenName: result.screenName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Publish i updated event
|
||||
this.globalEventService.publishMainStream(user.id, 'meUpdated', await this.userEntityService.pack(user, user, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}));
|
||||
|
||||
return `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`;
|
||||
}
|
||||
});
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private getUserToken(request: FastifyRequest): string | null {
|
||||
return ((request.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private compareOrigin(request: FastifyRequest): boolean {
|
||||
function normalizeUrl(url?: string): string {
|
||||
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
||||
}
|
||||
|
||||
const referer = request.headers['referer'];
|
||||
|
||||
return (normalizeUrl(referer) === normalizeUrl(this.config.url));
|
||||
}
|
||||
}
|
|
@ -40,11 +40,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="social">
|
||||
<a v-if="meta && meta.enableTwitterIntegration" class="_borderButton _margin" :href="`${apiUrl}/signin/twitter`"><i class="ti ti-brand-twitter" style="margin-right: 4px;"></i>{{ $t('signinWith', { x: 'Twitter' }) }}</a>
|
||||
<a v-if="meta && meta.enableGithubIntegration" class="_borderButton _margin" :href="`${apiUrl}/signin/github`"><i class="ti ti-brand-github" style="margin-right: 4px;"></i>{{ $t('signinWith', { x: 'GitHub' }) }}</a>
|
||||
<a v-if="meta && meta.enableDiscordIntegration" class="_borderButton _margin" :href="`${apiUrl}/signin/discord`"><i class="ti ti-brand-discord" style="margin-right: 4px;"></i>{{ $t('signinWith', { x: 'Discord' }) }}</a>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -164,11 +164,6 @@ const menuDef = $computed(() => [{
|
|||
text: i18n.ts.relays,
|
||||
to: '/admin/relays',
|
||||
active: currentPage?.route.name === 'relays',
|
||||
}, {
|
||||
icon: 'ti ti-share',
|
||||
text: i18n.ts.integration,
|
||||
to: '/admin/integrations',
|
||||
active: currentPage?.route.name === 'integrations',
|
||||
}, {
|
||||
icon: 'ti ti-ban',
|
||||
text: i18n.ts.instanceBlocking,
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
<template>
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="enableDiscordIntegration">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="enableDiscordIntegration">
|
||||
<FormInfo>Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo>
|
||||
|
||||
<MkInput v-model="discordClientId">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Client ID</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="discordClientSecret">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Client Secret</template>
|
||||
</MkInput>
|
||||
</template>
|
||||
|
||||
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
let uri: string = $ref('');
|
||||
let enableDiscordIntegration: boolean = $ref(false);
|
||||
let discordClientId: string | null = $ref(null);
|
||||
let discordClientSecret: string | null = $ref(null);
|
||||
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
uri = meta.uri;
|
||||
enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
discordClientId = meta.discordClientId;
|
||||
discordClientSecret = meta.discordClientSecret;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableDiscordIntegration,
|
||||
discordClientId,
|
||||
discordClientSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -1,60 +0,0 @@
|
|||
<template>
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="enableGithubIntegration">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="enableGithubIntegration">
|
||||
<FormInfo>Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo>
|
||||
|
||||
<MkInput v-model="githubClientId">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Client ID</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="githubClientSecret">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Client Secret</template>
|
||||
</MkInput>
|
||||
</template>
|
||||
|
||||
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
let uri: string = $ref('');
|
||||
let enableGithubIntegration: boolean = $ref(false);
|
||||
let githubClientId: string | null = $ref(null);
|
||||
let githubClientSecret: string | null = $ref(null);
|
||||
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
uri = meta.uri;
|
||||
enableGithubIntegration = meta.enableGithubIntegration;
|
||||
githubClientId = meta.githubClientId;
|
||||
githubClientSecret = meta.githubClientSecret;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableGithubIntegration,
|
||||
githubClientId,
|
||||
githubClientSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -1,60 +0,0 @@
|
|||
<template>
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="enableTwitterIntegration">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="enableTwitterIntegration">
|
||||
<FormInfo>Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo>
|
||||
|
||||
<MkInput v-model="twitterConsumerKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Consumer Key</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="twitterConsumerSecret">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Consumer Secret</template>
|
||||
</MkInput>
|
||||
</template>
|
||||
|
||||
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
let uri: string = $ref('');
|
||||
let enableTwitterIntegration: boolean = $ref(false);
|
||||
let twitterConsumerKey: string | null = $ref(null);
|
||||
let twitterConsumerSecret: string | null = $ref(null);
|
||||
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
uri = meta.uri;
|
||||
enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
twitterConsumerKey = meta.twitterConsumerKey;
|
||||
twitterConsumerSecret = meta.twitterConsumerSecret;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableTwitterIntegration,
|
||||
twitterConsumerKey,
|
||||
twitterConsumerSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -1,61 +0,0 @@
|
|||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-brand-twitter"></i></template>
|
||||
<template #label>Twitter</template>
|
||||
<template #suffix>{{ enableTwitterIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
|
||||
<XTwitter/>
|
||||
</MkFolder>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-brand-github"></i></template>
|
||||
<template #label>GitHub</template>
|
||||
<template #suffix>{{ enableGithubIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
|
||||
<XGithub/>
|
||||
</MkFolder>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-brand-discord"></i></template>
|
||||
<template #label>Discord</template>
|
||||
<template #suffix>{{ enableDiscordIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
|
||||
<XDiscord/>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import XTwitter from './integrations.twitter.vue';
|
||||
import XGithub from './integrations.github.vue';
|
||||
import XDiscord from './integrations.discord.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
let enableTwitterIntegration: boolean = $ref(false);
|
||||
let enableGithubIntegration: boolean = $ref(false);
|
||||
let enableDiscordIntegration: boolean = $ref(false);
|
||||
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
enableGithubIntegration = meta.enableGithubIntegration;
|
||||
enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.integration,
|
||||
icon: 'ti ti-share',
|
||||
});
|
||||
</script>
|
|
@ -89,11 +89,6 @@ const menuDef = computed(() => [{
|
|||
text: i18n.ts.email,
|
||||
to: '/settings/email',
|
||||
active: currentPage?.route.name === 'email',
|
||||
}, {
|
||||
icon: 'ti ti-share',
|
||||
text: i18n.ts.integration,
|
||||
to: '/settings/integration',
|
||||
active: currentPage?.route.name === 'integration',
|
||||
}, {
|
||||
icon: 'ti ti-lock',
|
||||
text: i18n.ts.security,
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
<template>
|
||||
<div class="_gaps_m">
|
||||
<FormSection v-if="instance.enableTwitterIntegration">
|
||||
<template #label><i class="ti ti-brand-twitter"></i> Twitter</template>
|
||||
<p v-if="integrations.twitter">{{ i18n.ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
|
||||
<MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ i18n.ts.disconnectService }}</MkButton>
|
||||
<MkButton v-else primary @click="connectTwitter">{{ i18n.ts.connectService }}</MkButton>
|
||||
</FormSection>
|
||||
|
||||
<FormSection v-if="instance.enableDiscordIntegration">
|
||||
<template #label><i class="ti ti-brand-discord"></i> Discord</template>
|
||||
<p v-if="integrations.discord">{{ i18n.ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p>
|
||||
<MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ i18n.ts.disconnectService }}</MkButton>
|
||||
<MkButton v-else primary @click="connectDiscord">{{ i18n.ts.connectService }}</MkButton>
|
||||
</FormSection>
|
||||
|
||||
<FormSection v-if="instance.enableGithubIntegration">
|
||||
<template #label><i class="ti ti-brand-github"></i> GitHub</template>
|
||||
<p v-if="integrations.github">{{ i18n.ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p>
|
||||
<MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ i18n.ts.disconnectService }}</MkButton>
|
||||
<MkButton v-else primary @click="connectGithub">{{ i18n.ts.connectService }}</MkButton>
|
||||
</FormSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { apiUrl } from '@/config';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { $i } from '@/account';
|
||||
import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const twitterForm = ref<Window | null>(null);
|
||||
const discordForm = ref<Window | null>(null);
|
||||
const githubForm = ref<Window | null>(null);
|
||||
|
||||
const integrations = computed(() => $i!.integrations);
|
||||
|
||||
function openWindow(service: string, type: string) {
|
||||
return window.open(`${apiUrl}/${type}/${service}`,
|
||||
`${service}_${type}_window`,
|
||||
'height=570, width=520',
|
||||
);
|
||||
}
|
||||
|
||||
function connectTwitter() {
|
||||
twitterForm.value = openWindow('twitter', 'connect');
|
||||
}
|
||||
|
||||
function disconnectTwitter() {
|
||||
openWindow('twitter', 'disconnect');
|
||||
}
|
||||
|
||||
function connectDiscord() {
|
||||
discordForm.value = openWindow('discord', 'connect');
|
||||
}
|
||||
|
||||
function disconnectDiscord() {
|
||||
openWindow('discord', 'disconnect');
|
||||
}
|
||||
|
||||
function connectGithub() {
|
||||
githubForm.value = openWindow('github', 'connect');
|
||||
}
|
||||
|
||||
function disconnectGithub() {
|
||||
openWindow('github', 'disconnect');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.cookie = `igi=${$i!.token}; path=/;` +
|
||||
' max-age=31536000;' +
|
||||
(document.location.protocol.startsWith('https') ? ' secure' : '');
|
||||
|
||||
watch(integrations, () => {
|
||||
if (integrations.value.twitter) {
|
||||
if (twitterForm.value) twitterForm.value.close();
|
||||
}
|
||||
if (integrations.value.discord) {
|
||||
if (discordForm.value) discordForm.value.close();
|
||||
}
|
||||
if (integrations.value.github) {
|
||||
if (githubForm.value) githubForm.value.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.integration,
|
||||
icon: 'ti ti-share',
|
||||
});
|
||||
</script>
|
|
@ -70,10 +70,6 @@ export const routes = [{
|
|||
path: '/email',
|
||||
name: 'email',
|
||||
component: page(() => import('./pages/settings/email.vue')),
|
||||
}, {
|
||||
path: '/integration',
|
||||
name: 'integration',
|
||||
component: page(() => import('./pages/settings/integration.vue')),
|
||||
}, {
|
||||
path: '/security',
|
||||
name: 'security',
|
||||
|
@ -399,10 +395,6 @@ export const routes = [{
|
|||
path: '/relays',
|
||||
name: 'relays',
|
||||
component: page(() => import('./pages/admin/relays.vue')),
|
||||
}, {
|
||||
path: '/integrations',
|
||||
name: 'integrations',
|
||||
component: page(() => import('./pages/admin/integrations.vue')),
|
||||
}, {
|
||||
path: '/instance-block',
|
||||
name: 'instance-block',
|
||||
|
|
Loading…
Reference in a new issue