summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2024-10-05 12:03:47 +0900
committerGitHub <noreply@github.com>2024-10-05 12:03:47 +0900
commitae3c155490d9b5a574c45309744ba2a0cbe78932 (patch)
tree95425995a0c47d2861ab054e2a48ea1ab974b538 /packages
parent:art: (diff)
downloadsharkey-ae3c155490d9b5a574c45309744ba2a0cbe78932.tar.gz
sharkey-ae3c155490d9b5a574c45309744ba2a0cbe78932.tar.bz2
sharkey-ae3c155490d9b5a574c45309744ba2a0cbe78932.zip
fix: signin の資格情報が足りないだけの場合はエラーにせず200を返すように (#14700)
* fix: signin の資格情報が足りないだけの場合はエラーにせず200を返すように * run api extractor * fix * fix * fix test * /signin -> /signin-flow * fix * fix lint * rename * fix * fix
Diffstat (limited to 'packages')
-rw-r--r--packages/backend/src/server/api/ApiServerService.ts2
-rw-r--r--packages/backend/src/server/api/SigninApiService.ts66
-rw-r--r--packages/backend/src/server/api/SigninService.ts6
-rw-r--r--packages/backend/test/e2e/2fa.ts71
-rw-r--r--packages/backend/test/e2e/endpoints.ts8
-rw-r--r--packages/frontend/src/components/MkSignin.vue236
-rw-r--r--packages/frontend/src/components/MkSignupDialog.form.vue11
-rw-r--r--packages/frontend/src/components/MkSignupDialog.vue4
-rw-r--r--packages/misskey-js/etc/misskey-js.api.md24
-rw-r--r--packages/misskey-js/src/api.types.ts10
-rw-r--r--packages/misskey-js/src/entities.ts22
11 files changed, 228 insertions, 232 deletions
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 356e145681..6b760c258b 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -133,7 +133,7 @@ export class ApiServerService {
'turnstile-response'?: string;
'm-captcha-response'?: string;
};
- }>('/signin', (request, reply) => this.signinApiService.signin(request, reply));
+ }>('/signin-flow', (request, reply) => this.signinApiService.signin(request, reply));
fastify.post<{
Body: {
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index 81684beb3c..0d24ffa56a 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -5,8 +5,8 @@
import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs';
-import * as OTPAuth from 'otpauth';
import { IsNull } from 'typeorm';
+import * as Misskey from 'misskey-js';
import { DI } from '@/di-symbols.js';
import type {
MiMeta,
@@ -26,27 +26,9 @@ import { CaptchaService } from '@/core/CaptchaService.js';
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js';
-import type { AuthenticationResponseJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
+import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { FastifyReply, FastifyRequest } from 'fastify';
-/**
- * next を指定すると、次にクライアント側で行うべき処理を指定できる。
- *
- * - `captcha`: パスワードと、(有効になっている場合は)CAPTCHAを求める
- * - `password`: パスワードを求める
- * - `totp`: ワンタイムパスワードを求める
- * - `passkey`: WebAuthn認証を求める(WebAuthnに対応していないブラウザの場合はワンタイムパスワード)
- */
-
-type SigninErrorResponse = {
- id: string;
- next?: 'captcha' | 'password' | 'totp';
-} | {
- id: string;
- next: 'passkey';
- authRequest: PublicKeyCredentialRequestOptionsJSON;
-};
-
@Injectable()
export class SigninApiService {
constructor(
@@ -101,7 +83,7 @@ export class SigninApiService {
const password = body['password'];
const token = body['token'];
- function error(status: number, error: SigninErrorResponse) {
+ function error(status: number, error: { id: string }) {
reply.code(status);
return { error };
}
@@ -152,21 +134,17 @@ export class SigninApiService {
const securityKeysAvailable = await this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1);
if (password == null) {
- reply.code(403);
+ reply.code(200);
if (profile.twoFactorEnabled) {
return {
- error: {
- id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
- next: 'password',
- },
- } satisfies { error: SigninErrorResponse };
+ finished: false,
+ next: 'password',
+ } satisfies Misskey.entities.SigninFlowResponse;
} else {
return {
- error: {
- id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
- next: 'captcha',
- },
- } satisfies { error: SigninErrorResponse };
+ finished: false,
+ next: 'captcha',
+ } satisfies Misskey.entities.SigninFlowResponse;
}
}
@@ -178,7 +156,7 @@ export class SigninApiService {
// Compare password
const same = await bcrypt.compare(password, profile.password!);
- const fail = async (status?: number, failure?: SigninErrorResponse) => {
+ const fail = async (status?: number, failure?: { id: string; }) => {
// Append signin history
await this.signinsRepository.insert({
id: this.idService.gen(),
@@ -268,27 +246,23 @@ export class SigninApiService {
const authRequest = await this.webAuthnService.initiateAuthentication(user.id);
- reply.code(403);
+ reply.code(200);
return {
- error: {
- id: '06e661b9-8146-4ae3-bde5-47138c0ae0c4',
- next: 'passkey',
- authRequest,
- },
- } satisfies { error: SigninErrorResponse };
+ finished: false,
+ next: 'passkey',
+ authRequest,
+ } satisfies Misskey.entities.SigninFlowResponse;
} else {
if (!same || !profile.twoFactorEnabled) {
return await fail(403, {
id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
});
} else {
- reply.code(403);
+ reply.code(200);
return {
- error: {
- id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
- next: 'totp',
- },
- } satisfies { error: SigninErrorResponse };
+ finished: false,
+ next: 'totp',
+ } satisfies Misskey.entities.SigninFlowResponse;
}
}
// never get here
diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts
index 4b041f373f..640356b50c 100644
--- a/packages/backend/src/server/api/SigninService.ts
+++ b/packages/backend/src/server/api/SigninService.ts
@@ -4,6 +4,7 @@
*/
import { Inject, Injectable } from '@nestjs/common';
+import * as Misskey from 'misskey-js';
import { DI } from '@/di-symbols.js';
import type { SigninsRepository, UserProfilesRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
@@ -57,9 +58,10 @@ export class SigninService {
reply.code(200);
return {
+ finished: true,
id: user.id,
- i: user.token,
- };
+ i: user.token!,
+ } satisfies Misskey.entities.SigninFlowResponse;
}
}
diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts
index 88c32b4346..48e1bababb 100644
--- a/packages/backend/test/e2e/2fa.ts
+++ b/packages/backend/test/e2e/2fa.ts
@@ -136,7 +136,7 @@ describe('2要素認証', () => {
keyName: string,
credentialId: Buffer,
requestOptions: PublicKeyCredentialRequestOptionsJSON,
- }): misskey.entities.SigninRequest => {
+ }): misskey.entities.SigninFlowRequest => {
// AuthenticatorAssertionResponse.authenticatorData
// https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse/authenticatorData
const authenticatorData = Buffer.concat([
@@ -196,22 +196,21 @@ describe('2要素認証', () => {
}, alice);
assert.strictEqual(doneResponse.status, 200);
- const signinWithoutTokenResponse = await api('signin', {
+ const signinWithoutTokenResponse = await api('signin-flow', {
...signinParam(),
});
- assert.strictEqual(signinWithoutTokenResponse.status, 403);
+ assert.strictEqual(signinWithoutTokenResponse.status, 200);
assert.deepStrictEqual(signinWithoutTokenResponse.body, {
- error: {
- id: '144ff4f8-bd6c-41bc-82c3-b672eb09efbf',
- next: 'totp',
- },
+ finished: false,
+ next: 'totp',
});
- const signinResponse = await api('signin', {
+ const signinResponse = await api('signin-flow', {
...signinParam(),
token: otpToken(registerResponse.body.secret),
});
assert.strictEqual(signinResponse.status, 200);
+ assert.strictEqual(signinResponse.body.finished, true);
assert.notEqual(signinResponse.body.i, undefined);
// 後片付け
@@ -252,29 +251,23 @@ describe('2要素認証', () => {
assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url'));
assert.strictEqual(keyDoneResponse.body.name, keyName);
- const signinResponse = await api('signin', {
+ const signinResponse = await api('signin-flow', {
...signinParam(),
});
- const signinResponseBody = signinResponse.body as unknown as {
- error: {
- id: string;
- next: 'passkey';
- authRequest: PublicKeyCredentialRequestOptionsJSON;
- };
- };
- assert.strictEqual(signinResponse.status, 403);
- assert.strictEqual(signinResponseBody.error.id, '06e661b9-8146-4ae3-bde5-47138c0ae0c4');
- assert.strictEqual(signinResponseBody.error.next, 'passkey');
- assert.notEqual(signinResponseBody.error.authRequest.challenge, undefined);
- assert.notEqual(signinResponseBody.error.authRequest.allowCredentials, undefined);
- assert.strictEqual(signinResponseBody.error.authRequest.allowCredentials && signinResponseBody.error.authRequest.allowCredentials[0]?.id, credentialId.toString('base64url'));
+ assert.strictEqual(signinResponse.status, 200);
+ assert.strictEqual(signinResponse.body.finished, false);
+ assert.strictEqual(signinResponse.body.next, 'passkey');
+ assert.notEqual(signinResponse.body.authRequest.challenge, undefined);
+ assert.notEqual(signinResponse.body.authRequest.allowCredentials, undefined);
+ assert.strictEqual(signinResponse.body.authRequest.allowCredentials && signinResponse.body.authRequest.allowCredentials[0]?.id, credentialId.toString('base64url'));
- const signinResponse2 = await api('signin', signinWithSecurityKeyParam({
+ const signinResponse2 = await api('signin-flow', signinWithSecurityKeyParam({
keyName,
credentialId,
- requestOptions: signinResponseBody.error.authRequest,
+ requestOptions: signinResponse.body.authRequest,
}));
assert.strictEqual(signinResponse2.status, 200);
+ assert.strictEqual(signinResponse2.body.finished, true);
assert.notEqual(signinResponse2.body.i, undefined);
// 後片付け
@@ -320,32 +313,26 @@ describe('2要素認証', () => {
assert.strictEqual(iResponse.status, 200);
assert.strictEqual(iResponse.body.usePasswordLessLogin, true);
- const signinResponse = await api('signin', {
+ const signinResponse = await api('signin-flow', {
...signinParam(),
password: '',
});
- const signinResponseBody = signinResponse.body as unknown as {
- error: {
- id: string;
- next: 'passkey';
- authRequest: PublicKeyCredentialRequestOptionsJSON;
- };
- };
- assert.strictEqual(signinResponse.status, 403);
- assert.strictEqual(signinResponseBody.error.id, '06e661b9-8146-4ae3-bde5-47138c0ae0c4');
- assert.strictEqual(signinResponseBody.error.next, 'passkey');
- assert.notEqual(signinResponseBody.error.authRequest.challenge, undefined);
- assert.notEqual(signinResponseBody.error.authRequest.allowCredentials, undefined);
+ assert.strictEqual(signinResponse.status, 200);
+ assert.strictEqual(signinResponse.body.finished, false);
+ assert.strictEqual(signinResponse.body.next, 'passkey');
+ assert.notEqual(signinResponse.body.authRequest.challenge, undefined);
+ assert.notEqual(signinResponse.body.authRequest.allowCredentials, undefined);
- const signinResponse2 = await api('signin', {
+ const signinResponse2 = await api('signin-flow', {
...signinWithSecurityKeyParam({
keyName,
credentialId,
- requestOptions: signinResponseBody.error.authRequest,
+ requestOptions: signinResponse.body.authRequest,
} as any),
password: '',
});
assert.strictEqual(signinResponse2.status, 200);
+ assert.strictEqual(signinResponse2.body.finished, true);
assert.notEqual(signinResponse2.body.i, undefined);
// 後片付け
@@ -450,11 +437,12 @@ describe('2要素認証', () => {
assert.strictEqual(afterIResponse.status, 200);
assert.strictEqual(afterIResponse.body.securityKeys, false);
- const signinResponse = await api('signin', {
+ const signinResponse = await api('signin-flow', {
...signinParam(),
token: otpToken(registerResponse.body.secret),
});
assert.strictEqual(signinResponse.status, 200);
+ assert.strictEqual(signinResponse.body.finished, true);
assert.notEqual(signinResponse.body.i, undefined);
// 後片付け
@@ -485,10 +473,11 @@ describe('2要素認証', () => {
}, alice);
assert.strictEqual(unregisterResponse.status, 204);
- const signinResponse = await api('signin', {
+ const signinResponse = await api('signin-flow', {
...signinParam(),
});
assert.strictEqual(signinResponse.status, 200);
+ assert.strictEqual(signinResponse.body.finished, true);
assert.notEqual(signinResponse.body.i, undefined);
// 後片付け
diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts
index 5aaec7f6f9..b91d77c398 100644
--- a/packages/backend/test/e2e/endpoints.ts
+++ b/packages/backend/test/e2e/endpoints.ts
@@ -66,9 +66,9 @@ describe('Endpoints', () => {
});
});
- describe('signin', () => {
+ describe('signin-flow', () => {
test('間違ったパスワードでサインインできない', async () => {
- const res = await api('signin', {
+ const res = await api('signin-flow', {
username: 'test1',
password: 'bar',
});
@@ -77,7 +77,7 @@ describe('Endpoints', () => {
});
test('クエリをインジェクションできない', async () => {
- const res = await api('signin', {
+ const res = await api('signin-flow', {
username: 'test1',
// @ts-expect-error password must be string
password: {
@@ -89,7 +89,7 @@ describe('Endpoints', () => {
});
test('正しい情報でサインインできる', async () => {
- const res = await api('signin', {
+ const res = await api('signin-flow', {
username: 'test1',
password: 'test1',
});
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index 03dd61f6c6..26e1ac516c 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -83,7 +83,7 @@ import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/br
import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
const emit = defineEmits<{
- (ev: 'login', v: Misskey.entities.SigninResponse): void;
+ (ev: 'login', v: Misskey.entities.SigninFlowResponse): void;
}>();
const props = withDefaults(defineProps<{
@@ -212,23 +212,63 @@ async function onTotpSubmitted(token: string) {
}
}
-async function tryLogin(req: Partial<Misskey.entities.SigninRequest>): Promise<Misskey.entities.SigninResponse> {
+async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promise<Misskey.entities.SigninFlowResponse> {
const _req = {
username: req.username ?? userInfo.value?.username,
...req,
};
- function assertIsSigninRequest(x: Partial<Misskey.entities.SigninRequest>): x is Misskey.entities.SigninRequest {
+ function assertIsSigninFlowRequest(x: Partial<Misskey.entities.SigninFlowRequest>): x is Misskey.entities.SigninFlowRequest {
return x.username != null;
}
- if (!assertIsSigninRequest(_req)) {
+ if (!assertIsSigninFlowRequest(_req)) {
throw new Error('Invalid request');
}
- return await misskeyApi('signin', _req).then(async (res) => {
- emit('login', res);
- await onLoginSucceeded(res);
+ return await misskeyApi('signin-flow', _req).then(async (res) => {
+ if (res.finished) {
+ emit('login', res);
+ await onLoginSucceeded(res);
+ } else {
+ switch (res.next) {
+ case 'captcha': {
+ needCaptcha.value = true;
+ page.value = 'password';
+ break;
+ }
+ case 'password': {
+ needCaptcha.value = false;
+ page.value = 'password';
+ break;
+ }
+ case 'totp': {
+ page.value = 'totp';
+ break;
+ }
+ case 'passkey': {
+ if (webAuthnSupported()) {
+ credentialRequest.value = parseRequestOptionsFromJSON({
+ publicKey: res.authRequest,
+ });
+ page.value = 'passkey';
+ } else {
+ page.value = 'totp';
+ }
+ break;
+ }
+ }
+
+ if (doingPasskeyFromInputPage.value === true) {
+ doingPasskeyFromInputPage.value = false;
+ page.value = 'input';
+ password.value = '';
+ }
+ passwordPageEl.value?.resetCaptcha();
+ nextTick(() => {
+ waiting.value = false;
+ });
+ }
return res;
}).catch((err) => {
onSigninApiError(err);
@@ -236,7 +276,7 @@ async function tryLogin(req: Partial<Misskey.entities.SigninRequest>): Promise<M
});
}
-async function onLoginSucceeded(res: Misskey.entities.SigninResponse) {
+async function onLoginSucceeded(res: Misskey.entities.SigninFlowResponse & { finished: true; }) {
if (props.autoSet) {
await login(res.i);
}
@@ -245,112 +285,82 @@ async function onLoginSucceeded(res: Misskey.entities.SigninResponse) {
function onSigninApiError(err?: any): void {
const id = err?.id ?? null;
- if (typeof err === 'object' && 'next' in err) {
- switch (err.next) {
- case 'captcha': {
- needCaptcha.value = true;
- page.value = 'password';
- break;
- }
- case 'password': {
- needCaptcha.value = false;
- page.value = 'password';
- break;
- }
- case 'totp': {
- page.value = 'totp';
- break;
- }
- case 'passkey': {
- if (webAuthnSupported() && 'authRequest' in err) {
- credentialRequest.value = parseRequestOptionsFromJSON({
- publicKey: err.authRequest,
- });
- page.value = 'passkey';
- } else {
- page.value = 'totp';
- }
- break;
- }
+ switch (id) {
+ case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: i18n.ts.noSuchUser,
+ });
+ break;
}
- } else {
- switch (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;
- }
- case 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f': {
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: i18n.ts.incorrectTotp,
- });
- break;
- }
- case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': {
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: i18n.ts.unknownWebAuthnKey,
- });
- break;
- }
- case '93b86c4b-72f9-40eb-9815-798928603d1e': {
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: i18n.ts.passkeyVerificationFailed,
- });
- break;
- }
- case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: i18n.ts.passkeyVerificationFailed,
- });
- break;
- }
- case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': {
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: i18n.ts.passkeyVerificationSucceededButPasswordlessLoginDisabled,
- });
- break;
- }
- default: {
- console.error(err);
- os.alert({
- type: 'error',
- title: i18n.ts.loginFailed,
- text: JSON.stringify(err),
- });
- }
+ 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;
+ }
+ case 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f': {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: i18n.ts.incorrectTotp,
+ });
+ break;
+ }
+ case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: i18n.ts.unknownWebAuthnKey,
+ });
+ break;
+ }
+ case '93b86c4b-72f9-40eb-9815-798928603d1e': {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: i18n.ts.passkeyVerificationFailed,
+ });
+ break;
+ }
+ case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: i18n.ts.passkeyVerificationFailed,
+ });
+ break;
+ }
+ case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: i18n.ts.passkeyVerificationSucceededButPasswordlessLoginDisabled,
+ });
+ break;
+ }
+ default: {
+ console.error(err);
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: JSON.stringify(err),
+ });
}
}
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index 38cac7f644..ff096dc729 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -98,7 +98,7 @@ const props = withDefaults(defineProps<{
});
const emit = defineEmits<{
- (ev: 'signup', user: Misskey.entities.SigninResponse): void;
+ (ev: 'signup', user: Misskey.entities.SigninFlowResponse): void;
(ev: 'signupEmailPending'): void;
}>();
@@ -269,14 +269,19 @@ async function onSubmit(): Promise<void> {
});
emit('signupEmailPending');
} else {
- const res = await misskeyApi('signin', {
+ const res = await misskeyApi('signin-flow', {
username: username.value,
password: password.value,
});
emit('signup', res);
- if (props.autoSet) {
+ if (props.autoSet && res.finished) {
return login(res.i);
+ } else {
+ os.alert({
+ type: 'error',
+ text: i18n.ts.somethingHappened,
+ });
}
}
} catch {
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 97310d32a6..4cccd99492 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -47,7 +47,7 @@ const props = withDefaults(defineProps<{
});
const emit = defineEmits<{
- (ev: 'done', res: Misskey.entities.SigninResponse): void;
+ (ev: 'done', res: Misskey.entities.SigninFlowResponse): void;
(ev: 'closed'): void;
}>();
@@ -55,7 +55,7 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const isAcceptedServerRule = ref(false);
-function onSignup(res: Misskey.entities.SigninResponse) {
+function onSignup(res: Misskey.entities.SigninFlowResponse) {
emit('done', res);
dialog.value?.close();
}
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 9ad784c296..732352abd8 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -1158,9 +1158,9 @@ export type Endpoints = Overwrite<Endpoints_2, {
req: SignupPendingRequest;
res: SignupPendingResponse;
};
- 'signin': {
- req: SigninRequest;
- res: SigninResponse;
+ 'signin-flow': {
+ req: SigninFlowRequest;
+ res: SigninFlowResponse;
};
'signin-with-passkey': {
req: SigninWithPasskeyRequest;
@@ -1208,11 +1208,11 @@ declare namespace entities {
SignupResponse,
SignupPendingRequest,
SignupPendingResponse,
- SigninRequest,
+ SigninFlowRequest,
+ SigninFlowResponse,
SigninWithPasskeyRequest,
SigninWithPasskeyInitResponse,
SigninWithPasskeyResponse,
- SigninResponse,
PartialRolePolicyOverride,
EmptyRequest,
EmptyResponse,
@@ -3038,7 +3038,7 @@ type ServerStatsLog = ServerStats[];
type Signin = components['schemas']['Signin'];
// @public (undocumented)
-type SigninRequest = {
+type SigninFlowRequest = {
username: string;
password?: string;
token?: string;
@@ -3050,9 +3050,17 @@ type SigninRequest = {
};
// @public (undocumented)
-type SigninResponse = {
+type SigninFlowResponse = {
+ finished: true;
id: User['id'];
i: string;
+} | {
+ finished: false;
+ next: 'captcha' | 'password' | 'totp';
+} | {
+ finished: false;
+ next: 'passkey';
+ authRequest: PublicKeyCredentialRequestOptionsJSON;
};
// @public (undocumented)
@@ -3069,7 +3077,7 @@ type SigninWithPasskeyRequest = {
// @public (undocumented)
type SigninWithPasskeyResponse = {
- signinResponse: SigninResponse;
+ signinResponse: SigninFlowResponse;
};
// @public (undocumented)
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index cef5ab8861..838949f8e1 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -3,8 +3,8 @@ import { UserDetailed } from './autogen/models.js';
import { AdminRolesCreateRequest, AdminRolesCreateResponse, UsersShowRequest } from './autogen/entities.js';
import {
PartialRolePolicyOverride,
- SigninRequest,
- SigninResponse,
+ SigninFlowRequest,
+ SigninFlowResponse,
SigninWithPasskeyInitResponse,
SigninWithPasskeyRequest,
SigninWithPasskeyResponse,
@@ -81,9 +81,9 @@ export type Endpoints = Overwrite<
res: SignupPendingResponse;
},
// api.jsonには載せないものなのでここで定義
- 'signin': {
- req: SigninRequest;
- res: SigninResponse;
+ 'signin-flow': {
+ req: SigninFlowRequest;
+ res: SigninFlowResponse;
},
'signin-with-passkey': {
req: SigninWithPasskeyRequest;
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 98ac50e5a1..8bbc9c113b 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -267,7 +267,7 @@ export type SignupPendingResponse = {
i: string,
};
-export type SigninRequest = {
+export type SigninFlowRequest = {
username: string;
password?: string;
token?: string;
@@ -278,6 +278,19 @@ export type SigninRequest = {
'm-captcha-response'?: string | null;
};
+export type SigninFlowResponse = {
+ finished: true;
+ id: User['id'];
+ i: string;
+} | {
+ finished: false;
+ next: 'captcha' | 'password' | 'totp';
+} | {
+ finished: false;
+ next: 'passkey';
+ authRequest: PublicKeyCredentialRequestOptionsJSON;
+};
+
export type SigninWithPasskeyRequest = {
credential?: AuthenticationResponseJSON;
context?: string;
@@ -289,12 +302,7 @@ export type SigninWithPasskeyInitResponse = {
};
export type SigninWithPasskeyResponse = {
- signinResponse: SigninResponse;
-};
-
-export type SigninResponse = {
- id: User['id'],
- i: string,
+ signinResponse: SigninFlowResponse;
};
type Values<T extends Record<PropertyKey, unknown>> = T[keyof T];