[feat] Remove Twitter Integration

This commit is contained in:
Pyrox 2023-11-27 18:17:34 -05:00 committed by Laura Hausmann
parent 042e8c552d
commit 60f7e2cf6a
22 changed files with 370 additions and 729 deletions

View File

@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class RemoveTwitterIntegration1701069578019 implements MigrationInterface {
name = "RemoveTwitterIntegration1701069578019";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN IF EXISTS "enableTwitterIntegration"`,
);
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN IF EXISTS "twitterConsumerKey"`,
);
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN IF EXISTS "twitterConsumerSecret"`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "enableTwitterIntegration" boolean NOT NULL DEFAULT false`,
);
await queryRunner.query(
`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "twitterConsumerKey" character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "twitterConsumerSecret" character varying(128)`,
);
}
}

View File

@ -310,23 +310,6 @@ export class Meta {
})
public swPrivateKey: string;
@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,
})

View File

@ -687,10 +687,6 @@ export async function resolvePerson(
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),
};

View File

@ -140,11 +140,6 @@ export const meta = {
optional: false,
nullable: false,
},
enableTwitterIntegration: {
type: "boolean",
optional: false,
nullable: false,
},
enableGithubIntegration: {
type: "boolean",
optional: false,
@ -260,36 +255,6 @@ export const meta = {
optional: true,
nullable: true,
},
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,
@ -483,9 +448,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,
translatorAvailable:
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
pinnedPages: instance.pinnedPages,
@ -504,12 +466,6 @@ export default define(meta, paramDef, async (ps, me) => {
secureMode: instance.secureMode,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
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,

View File

@ -124,9 +124,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 },
@ -363,17 +360,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;

View File

@ -251,11 +251,6 @@ export const meta = {
optional: false,
nullable: false,
},
enableTwitterIntegration: {
type: "boolean",
optional: false,
nullable: false,
},
enableGithubIntegration: {
type: "boolean",
optional: false,
@ -320,11 +315,6 @@ export const meta = {
optional: false,
nullable: false,
},
twitter: {
type: "boolean",
optional: false,
nullable: false,
},
github: {
type: "boolean",
optional: false,
@ -453,7 +443,6 @@ export default define(meta, paramDef, async (ps, me) => {
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,
enableGithubIntegration: instance.enableGithubIntegration,
enableDiscordIntegration: instance.enableDiscordIntegration,
@ -487,7 +476,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: true,

View File

@ -19,7 +19,6 @@ 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();
@ -112,8 +111,6 @@ 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,

View File

@ -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<any>((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<any>((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;

View File

@ -84,7 +84,6 @@ const nodeinfo2 = async () => {
enableRecaptcha: meta.enableRecaptcha,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
enableTwitterIntegration: meta.enableTwitterIntegration,
enableGithubIntegration: meta.enableGithubIntegration,
enableDiscordIntegration: meta.enableDiscordIntegration,
enableEmail: meta.enableEmail,

View File

@ -31,7 +31,3 @@ block meta
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
meta(name='misskey:clip-id' content=clip.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)

View File

@ -31,9 +31,5 @@ block meta
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
if !user.host
link(rel='alternate' href=url type='application/activity+json')

View File

@ -44,10 +44,6 @@ block meta
meta(name='misskey:user-id' content=user.id)
meta(name='misskey:note-id' content=note.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
if note.prev
link(rel='prev' href=`${config.url}/notes/${note.prev}`)
if note.next

View File

@ -31,7 +31,3 @@ block meta
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
meta(name='misskey:page-id' content=page.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)

View File

@ -31,9 +31,6 @@ block meta
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
if profile.twitter
meta(name='twitter:creator' content=`@${profile.twitter.screenName}`)
if !sub
if !user.host
link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json')

View File

@ -1,162 +1,153 @@
<template>
<form
class="eppvobhk _monolithic_"
:class="{ signing, totpLogin }"
@submit.prevent="onSubmit"
>
<div class="auth _section _formRoot">
<div
v-show="withAvatar"
class="avatar"
:style="{
backgroundImage: user ? `url('${user.avatarUrl}')` : null,
marginBottom: message ? '1.5em' : null,
}"
></div>
<MkInfo v-if="message">
{{ message }}
</MkInfo>
<div v-if="!totpLogin" class="normal-signin">
<MkInput
v-model="username"
class="_formBlock"
:placeholder="i18n.ts.username"
type="text"
pattern="^[a-zA-Z0-9_]+$"
:spellcheck="false"
autofocus
required
data-cy-signin-username
@update:modelValue="onUsernameChange"
>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
</MkInput>
<MkInput
v-if="!user || (user && !user.usePasswordLessLogin)"
v-model="password"
class="_formBlock"
:placeholder="i18n.ts.password"
type="password"
:with-password-toggle="true"
autocomplete="current-password"
required
data-cy-signin-password
>
<template #prefix
><i class="ph-lock ph-bold ph-lg"></i
></template>
<template #caption
><button
class="_textButton"
type="button"
@click="resetPassword"
>
{{ i18n.ts.forgotPassword }}
</button></template
>
</MkInput>
<MkButton
class="_formBlock"
type="submit"
primary
:disabled="signing"
style="margin: 1rem auto"
>{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton
>
</div>
<div
v-if="totpLogin"
class="2fa-signin"
:class="{ securityKeys: user && user.securityKeys }"
>
<div
v-if="user && user.securityKeys"
class="twofa-group tap-group"
>
<p>{{ i18n.ts.tapSecurityKey }}</p>
<MkButton v-if="!queryingKey" @click="queryKey">
{{ i18n.ts.retry }}
</MkButton>
</div>
<div v-if="user && user.securityKeys" class="or-hr">
<p class="or-msg">{{ i18n.ts.or }}</p>
</div>
<div class="twofa-group totp-group">
<p style="margin-bottom: 0">
{{ i18n.ts.twoStepAuthentication }}
</p>
<MkInput
v-if="user && user.usePasswordLessLogin"
v-model="password"
type="password"
:with-password-toggle="true"
autocomplete="current-password"
required
>
<template #label>{{ i18n.ts.password }}</template>
<template #prefix
><i class="ph-lock ph-bold ph-lg"></i
></template>
</MkInput>
<MkInput
v-model="token"
type="text"
autocomplete="one-time-code"
pattern="^[0-9]{6}$"
:spellcheck="false"
required
>
<template #label>{{ i18n.ts._2fa.token }}</template>
<template #prefix
><i class="ph-poker-chip ph-bold ph-lg"></i
></template>
</MkInput>
<MkButton
type="submit"
:disabled="signing"
primary
style="margin: 1rem auto auto"
>{{
signing ? i18n.ts.loggingIn : i18n.ts.login
}}</MkButton
>
</div>
</div>
</div>
<div class="social _section">
<a
v-if="meta && meta.enableTwitterIntegration"
class="_borderButton _gap"
:href="`${apiUrl}/signin/twitter`"
><i
class="ph-twitter-logo ph-bold ph-lg"
style="margin-right: 4px"
></i
>{{ i18n.t("signinWith", { x: "Twitter" }) }}</a
>
<a
v-if="meta && meta.enableGithubIntegration"
class="_borderButton _gap"
:href="`${apiUrl}/signin/github`"
><i
class="ph-github-logo ph-bold ph-lg"
style="margin-right: 4px"
></i
>{{ i18n.t("signinWith", { x: "GitHub" }) }}</a
>
<a
v-if="meta && meta.enableDiscordIntegration"
class="_borderButton _gap"
:href="`${apiUrl}/signin/discord`"
><i
class="ph-discord-logo ph-bold ph-lg"
style="margin-right: 4px"
></i
>{{ i18n.t("signinWith", { x: "Discord" }) }}</a
>
</div>
</form>
<form
class="eppvobhk _monolithic_"
:class="{ signing, totpLogin }"
@submit.prevent="onSubmit"
>
<div class="auth _section _formRoot">
<div
v-show="withAvatar"
class="avatar"
:style="{
backgroundImage: user ? `url('${user.avatarUrl}')` : null,
marginBottom: message ? '1.5em' : null,
}"
></div>
<MkInfo v-if="message">
{{ message }}
</MkInfo>
<div
v-if="!totpLogin"
class="normal-signin"
>
<MkInput
v-model="username"
class="_formBlock"
:placeholder="i18n.ts.username"
type="text"
pattern="^[a-zA-Z0-9_]+$"
:spellcheck="false"
autofocus
required
data-cy-signin-username
@update:modelValue="onUsernameChange"
>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
</MkInput>
<MkInput
v-if="!user || (user && !user.usePasswordLessLogin)"
v-model="password"
class="_formBlock"
:placeholder="i18n.ts.password"
type="password"
:with-password-toggle="true"
autocomplete="current-password"
required
data-cy-signin-password
>
<template #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
<template #caption><button
class="_textButton"
type="button"
@click="resetPassword"
>
{{ i18n.ts.forgotPassword }}
</button></template>
</MkInput>
<MkButton
class="_formBlock"
type="submit"
primary
:disabled="signing"
style="margin: 1rem auto"
>{{ signing
? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
</div>
<div
v-if="totpLogin"
class="2fa-signin"
:class="{ securityKeys: user && user.securityKeys }"
>
<div
v-if="user && user.securityKeys"
class="twofa-group tap-group"
>
<p>{{ i18n.ts.tapSecurityKey }}</p>
<MkButton
v-if="!queryingKey"
@click="queryKey"
>
{{ i18n.ts.retry }}
</MkButton>
</div>
<div
v-if="user && user.securityKeys"
class="or-hr"
>
<p class="or-msg">{{ i18n.ts.or }}</p>
</div>
<div class="twofa-group totp-group">
<p style="margin-bottom: 0">
{{ i18n.ts.twoStepAuthentication }}
</p>
<MkInput
v-if="user && user.usePasswordLessLogin"
v-model="password"
type="password"
:with-password-toggle="true"
autocomplete="current-password"
required
>
<template #label>{{ i18n.ts.password }}</template>
<template #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
</MkInput>
<MkInput
v-model="token"
type="text"
autocomplete="one-time-code"
pattern="^[0-9]{6}$"
:spellcheck="false"
required
>
<template #label>{{ i18n.ts._2fa.token }}</template>
<template #prefix><i class="ph-poker-chip ph-bold ph-lg"></i></template>
</MkInput>
<MkButton
type="submit"
:disabled="signing"
primary
style="margin: 1rem auto auto"
>{{
signing ? i18n.ts.loggingIn : i18n.ts.login
}}</MkButton>
</div>
</div>
</div>
<div class="social _section">
<a
v-if="meta && meta.enableGithubIntegration"
class="_borderButton _gap"
:href="`${apiUrl}/signin/github`"
><i
class="ph-github-logo ph-bold ph-lg"
style="margin-right: 4px"
></i>{{ i18n.t("signinWith", {
x: "GitHub"
}) }}</a>
<a
v-if="meta && meta.enableDiscordIntegration"
class="_borderButton _gap"
:href="`${apiUrl}/signin/discord`"
><i
class="ph-discord-logo ph-bold ph-lg"
style="margin-right: 4px"
></i>{{ i18n.t("signinWith", {
x:
"Discord"
}) }}</a>
</div>
</form>
</template>
<script lang="ts" setup>
@ -166,220 +157,220 @@ import { toUnicode } from "punycode/";
import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue";
import MkInfo from "@/components/MkInfo.vue";
import { apiUrl, host as configHost } from "@/config";
import { byteify, hexify } from "@/scripts/2fa";
import { apiUrl,host as configHost } from "@/config";
import { byteify,hexify } from "@/scripts/2fa";
import * as os from "@/os";
import { login } from "@/account";
import { instance } from "@/instance";
import { i18n } from "@/i18n";
let signing = $ref(false);
let user = $ref(null);
let username = $ref("");
let password = $ref("");
let token = $ref("");
let host = $ref(toUnicode(configHost));
let totpLogin = $ref(false);
let credential = $ref(null);
let challengeData = $ref(null);
let queryingKey = $ref(false);
let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null);
let signing=$ref(false);
let user=$ref(null);
let username=$ref("");
let password=$ref("");
let token=$ref("");
let host=$ref(toUnicode(configHost));
let totpLogin=$ref(false);
let credential=$ref(null);
let challengeData=$ref(null);
let queryingKey=$ref(false);
let hCaptchaResponse=$ref(null);
let reCaptchaResponse=$ref(null);
const updateToken = (value: string) => {
token = value.toString();
const updateToken=(value: string) => {
token=value.toString();
};
const meta = $computed(() => instance);
const meta=$computed(() => instance);
const emit = defineEmits<{
(ev: "login", v: any): void;
const emit=defineEmits<{
(ev: "login",v: any): void;
}>();
const props = defineProps({
withAvatar: {
type: Boolean,
required: false,
default: true,
},
autoSet: {
type: Boolean,
required: false,
default: false,
},
message: {
type: String,
required: false,
default: "",
},
const props=defineProps({
withAvatar: {
type: Boolean,
required: false,
default: true,
},
autoSet: {
type: Boolean,
required: false,
default: false,
},
message: {
type: String,
required: false,
default: "",
},
});
function onUsernameChange() {
os.api("users/show", {
username: username,
}).then(
(userResponse) => {
user = userResponse;
},
() => {
user = null;
},
);
os.api("users/show",{
username: username,
}).then(
(userResponse) => {
user=userResponse;
},
() => {
user=null;
},
);
}
function onLogin(res) {
if (props.autoSet) {
return login(res.i);
}
if(props.autoSet) {
return login(res.i);
}
}
function queryKey() {
queryingKey = true;
return navigator.credentials
.get({
publicKey: {
challenge: byteify(challengeData.challenge, "base64"),
allowCredentials: challengeData.securityKeys.map((key) => ({
id: byteify(key.id, "hex"),
type: "public-key",
transports: ["usb", "nfc", "ble", "internal"],
})),
timeout: 60 * 1000,
},
})
.catch(() => {
queryingKey = false;
return Promise.reject(null);
})
.then((credential) => {
queryingKey = false;
signing = true;
return os.api("signin", {
username,
password,
signature: hexify(credential.response.signature),
authenticatorData: hexify(
credential.response.authenticatorData,
),
clientDataJSON: hexify(credential.response.clientDataJSON),
credentialId: credential.id,
challengeId: challengeData.challengeId,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
});
})
.then((res) => {
emit("login", res);
return onLogin(res);
})
.catch((err) => {
if (err === null) return;
os.alert({
type: "error",
text: i18n.ts.signinFailed,
});
signing = false;
});
queryingKey=true;
return navigator.credentials
.get({
publicKey: {
challenge: byteify(challengeData.challenge,"base64"),
allowCredentials: challengeData.securityKeys.map((key) => ({
id: byteify(key.id,"hex"),
type: "public-key",
transports: ["usb","nfc","ble","internal"],
})),
timeout: 60*1000,
},
})
.catch(() => {
queryingKey=false;
return Promise.reject(null);
})
.then((credential) => {
queryingKey=false;
signing=true;
return os.api("signin",{
username,
password,
signature: hexify(credential.response.signature),
authenticatorData: hexify(
credential.response.authenticatorData,
),
clientDataJSON: hexify(credential.response.clientDataJSON),
credentialId: credential.id,
challengeId: challengeData.challengeId,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
});
})
.then((res) => {
emit("login",res);
return onLogin(res);
})
.catch((err) => {
if(err===null) return;
os.alert({
type: "error",
text: i18n.ts.signinFailed,
});
signing=false;
});
}
function onSubmit() {
signing = true;
console.log("submit");
if (!totpLogin && user && user.twoFactorEnabled) {
if (window.PublicKeyCredential && user.securityKeys) {
os.api("signin", {
username,
password,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
})
.then((res) => {
totpLogin = true;
signing = false;
challengeData = res;
return queryKey();
})
.catch(loginFailed);
} else {
totpLogin = true;
signing = false;
}
} else {
os.api("signin", {
username,
password,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
token: user && user.twoFactorEnabled ? token : undefined,
})
.then((res) => {
emit("login", res);
onLogin(res);
})
.catch(loginFailed);
}
signing=true;
console.log("submit");
if(!totpLogin&&user&&user.twoFactorEnabled) {
if(window.PublicKeyCredential&&user.securityKeys) {
os.api("signin",{
username,
password,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
})
.then((res) => {
totpLogin=true;
signing=false;
challengeData=res;
return queryKey();
})
.catch(loginFailed);
} else {
totpLogin=true;
signing=false;
}
} else {
os.api("signin",{
username,
password,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
token: user&&user.twoFactorEnabled? token:undefined,
})
.then((res) => {
emit("login",res);
onLogin(res);
})
.catch(loginFailed);
}
}
function loginFailed(err) {
switch (err.id) {
case "6cc579cc-885d-43d8-95c2-b8c7fc963280": {
os.alert({
type: "error",
title: i18n.ts.loginFailed,
text: i18n.ts.noSuchUser,
});
break;
}
case "932c904e-9460-45b7-9ce6-7ed33be7eb2c": {
os.alert({
type: "error",
title: i18n.ts.loginFailed,
text: i18n.ts.incorrectPassword,
});
break;
}
case "e03a5f46-d309-4865-9b69-56282d94e1eb": {
showSuspendedDialog();
break;
}
case "22d05606-fbcf-421a-a2db-b32610dcfd1b": {
os.alert({
type: "error",
title: i18n.ts.loginFailed,
text: i18n.ts.rateLimitExceeded,
});
break;
}
default: {
console.log(err);
os.alert({
type: "error",
title: i18n.ts.loginFailed,
text: JSON.stringify(err),
});
}
}
switch(err.id) {
case "6cc579cc-885d-43d8-95c2-b8c7fc963280": {
os.alert({
type: "error",
title: i18n.ts.loginFailed,
text: i18n.ts.noSuchUser,
});
break;
}
case "932c904e-9460-45b7-9ce6-7ed33be7eb2c": {
os.alert({
type: "error",
title: i18n.ts.loginFailed,
text: i18n.ts.incorrectPassword,
});
break;
}
case "e03a5f46-d309-4865-9b69-56282d94e1eb": {
showSuspendedDialog();
break;
}
case "22d05606-fbcf-421a-a2db-b32610dcfd1b": {
os.alert({
type: "error",
title: i18n.ts.loginFailed,
text: i18n.ts.rateLimitExceeded,
});
break;
}
default: {
console.log(err);
os.alert({
type: "error",
title: i18n.ts.loginFailed,
text: JSON.stringify(err),
});
}
}
challengeData = null;
totpLogin = false;
signing = false;
challengeData=null;
totpLogin=false;
signing=false;
}
function resetPassword() {
os.popup(
defineAsyncComponent(() => import("@/components/MkForgotPassword.vue")),
{},
{},
"closed",
);
os.popup(
defineAsyncComponent(() => import("@/components/MkForgotPassword.vue")),
{},
{},
"closed",
);
}
function showSuspendedDialog() {
os.alert({
type: "error",
title: i18n.ts.yourAccountSuspendedTitle,
text: i18n.ts.yourAccountSuspendedDescription,
});
os.alert({
type: "error",
title: i18n.ts.yourAccountSuspendedTitle,
text: i18n.ts.yourAccountSuspendedDescription,
});
}
</script>

View File

@ -47,13 +47,11 @@ 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;
}

View File

@ -1,50 +1,29 @@
<template>
<div class="_formRoot">
<FormSection v-if="instance.enableDiscordIntegration">
<template #label
><i class="ph-discord-logo ph-bold ph-lg"></i> Discord</template
>
<template #label><i class="ph-discord-logo ph-bold ph-lg"></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 }}#{{
<a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener"
target="_blank">@{{ integrations.discord.username }}#{{
integrations.discord.discriminator
}}</a
>
}}</a>
</p>
<MkButton
v-if="integrations.discord"
danger
@click="disconnectDiscord"
>{{ i18n.ts.disconnectService }}</MkButton
>
<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="ph-github-logo ph-bold ph-lg"></i> GitHub</template
>
<template #label><i class="ph-github-logo ph-bold ph-lg"></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
>
<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-if="integrations.github" danger @click="disconnectGithub">{{ i18n.ts.disconnectService }}</MkButton>
<MkButton v-else primary @click="connectGithub">{{
i18n.ts.connectService
}}</MkButton>
@ -62,7 +41,6 @@ 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);
@ -76,14 +54,6 @@ function openWindow(service: string, type: string) {
);
}
function connectTwitter() {
twitterForm.value = openWindow("twitter", "connect");
}
function disconnectTwitter() {
openWindow("twitter", "disconnect");
}
function connectDiscord() {
discordForm.value = openWindow("discord", "connect");
}
@ -107,9 +77,6 @@ onMounted(() => {
(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();
}

View File

@ -7799,7 +7799,7 @@
},
{
"kind": "Content",
"text": "{\n\tmaintainerName: string | null;\n\tmaintainerEmail: string | null;\n\tversion: string;\n\tname: string | null;\n\turi: string;\n\tdescription: string | null;\n\ttosUrl: string | null;\n\tdisableRegistration: boolean;\n\tdisableLocalTimeline: boolean;\n\tdisableRecommendedTimeline: boolean;\n\tdisableGlobalTimeline: boolean;\n\tdriveCapacityPerLocalUserMb: number;\n\tdriveCapacityPerRemoteUserMb: number;\n\tenableHcaptcha: boolean;\n\thcaptchaSiteKey: string | null;\n\tenableRecaptcha: boolean;\n\trecaptchaSiteKey: string | null;\n\tswPublickey: string | null;\n\tmaxNoteTextLength: number;\n\tenableEmail: boolean;\n\tenableTwitterIntegration: boolean;\n\tenableGithubIntegration: boolean;\n\tenableDiscordIntegration: boolean;\n\tenableServiceWorker: boolean;\n\temojis: "
"text": "{\n\tmaintainerName: string | null;\n\tmaintainerEmail: string | null;\n\tversion: string;\n\tname: string | null;\n\turi: string;\n\tdescription: string | null;\n\ttosUrl: string | null;\n\tdisableRegistration: boolean;\n\tdisableLocalTimeline: boolean;\n\tdisableRecommendedTimeline: boolean;\n\tdisableGlobalTimeline: boolean;\n\tdriveCapacityPerLocalUserMb: number;\n\tdriveCapacityPerRemoteUserMb: number;\n\tenableHcaptcha: boolean;\n\thcaptchaSiteKey: string | null;\n\tenableRecaptcha: boolean;\n\trecaptchaSiteKey: string | null;\n\tswPublickey: string | null;\n\tmaxNoteTextLength: number;\n\tenableEmail: boolean;\n\tenableGithubIntegration: boolean;\n\tenableDiscordIntegration: boolean;\n\tenableServiceWorker: boolean;\n\temojis: "
},
{
"kind": "Reference",

View File

@ -2397,7 +2397,6 @@ type LiteInstanceMetadata = {
swPublickey: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
enableServiceWorker: boolean;

View File

@ -2295,7 +2295,6 @@ type LiteInstanceMetadata = {
swPublickey: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
enableServiceWorker: boolean;

View File

@ -28,7 +28,6 @@ export declare type LiteInstanceMetadata = {
swPublickey: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
enableServiceWorker: boolean;

View File

@ -297,7 +297,6 @@ export type LiteInstanceMetadata = {
swPublickey: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
searchEngine: string;