summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-09-19 02:23:12 +0900
committerGitHub <noreply@github.com>2021-09-19 02:23:12 +0900
commit54e0a7f8a8d977c7befc255cc4950a86ac2e72fb (patch)
tree037bf47ed77a983ec51e6bc85134974e4433fd88
parentfix style (diff)
downloadsharkey-54e0a7f8a8d977c7befc255cc4950a86ac2e72fb.tar.gz
sharkey-54e0a7f8a8d977c7befc255cc4950a86ac2e72fb.tar.bz2
sharkey-54e0a7f8a8d977c7befc255cc4950a86ac2e72fb.zip
feat: 凍結された場合のダイアログを実装 (#7811)
* feat: 凍結された場合のダイアログを実装 * Update CHANGELOG.md * Update basic.js * improve error handling * cypressなんもわからん * Update basic.js
-rw-r--r--CHANGELOG.md2
-rw-r--r--cypress/integration/basic.js136
-rw-r--r--locales/ja-JP.yml2
-rw-r--r--src/client/account.ts22
-rwxr-xr-xsrc/client/components/signin.vue41
-rw-r--r--src/client/scripts/show-suspended-dialog.ts10
-rw-r--r--src/server/api/endpoints/reset-db.ts2
-rw-r--r--src/server/api/private/signin.ts37
8 files changed, 186 insertions, 66 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dce25340f9..8a15faf6a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@
### Improvements
- ActivityPub: リモートユーザーのDeleteアクティビティに対応
- ActivityPub: add resolver check for blocked instance
+- アカウントが凍結された場合に、凍結された旨を表示してからログアウトするように
+- 凍結されたアカウントにログインしようとしたときに、凍結されている旨を表示するように
- UIの改善
### Bugfixes
diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js
index 69d59bc2c6..52bcdb58d0 100644
--- a/cypress/integration/basic.js
+++ b/cypress/integration/basic.js
@@ -1,12 +1,18 @@
describe('Basic', () => {
- before(() => {
- cy.request('POST', '/api/reset-db');
- });
-
beforeEach(() => {
+ cy.request('POST', '/api/reset-db').as('reset');
+ cy.get('@reset').its('status').should('equal', 204);
+ cy.clearLocalStorage();
+ cy.clearCookies();
cy.reload(true);
});
+ afterEach(() => {
+ // テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
+ // waitを入れることでそれを防止できる
+ cy.wait(1000);
+ });
+
it('successfully loads', () => {
cy.visit('/');
});
@@ -14,56 +20,130 @@ describe('Basic', () => {
it('setup instance', () => {
cy.visit('/');
+ cy.intercept('POST', '/api/admin/accounts/create').as('signup');
+
cy.get('[data-cy-admin-username] input').type('admin');
-
cy.get('[data-cy-admin-password] input').type('admin1234');
-
cy.get('[data-cy-admin-ok]').click();
+
+ // なぜか動かない
+ //cy.wait('@signup').should('have.property', 'response.statusCode');
+ cy.wait('@signup');
});
it('signup', () => {
- cy.visit('/');
+ // インスタンス初期セットアップ
+ cy.request('POST', '/api/admin/accounts/create', {
+ username: 'admin',
+ password: 'pass',
+ }).as('setup');
- cy.get('[data-cy-signup]').click();
+ cy.get('@setup').then(() => {
+ cy.visit('/');
- cy.get('[data-cy-signup-username] input').type('alice');
+ cy.intercept('POST', '/api/signup').as('signup');
- cy.get('[data-cy-signup-password] input').type('alice1234');
-
- cy.get('[data-cy-signup-password-retype] input').type('alice1234');
+ cy.get('[data-cy-signup]').click();
+ cy.get('[data-cy-signup-username] input').type('alice');
+ cy.get('[data-cy-signup-password] input').type('alice1234');
+ cy.get('[data-cy-signup-password-retype] input').type('alice1234');
+ cy.get('[data-cy-signup-submit]').click();
- cy.get('[data-cy-signup-submit]').click();
+ cy.wait('@signup');
+ });
});
it('signin', () => {
- cy.visit('/');
+ // インスタンス初期セットアップ
+ cy.request('POST', '/api/admin/accounts/create', {
+ username: 'admin',
+ password: 'pass',
+ }).as('setup');
- cy.get('[data-cy-signin]').click();
+ cy.get('@setup').then(() => {
+ // ユーザー作成
+ cy.request('POST', '/api/signup', {
+ username: 'alice',
+ password: 'alice1234',
+ }).as('signup');
+ });
- cy.get('[data-cy-signin-username] input').type('alice');
+ cy.get('@signup').then(() => {
+ cy.visit('/');
- // Enterキーでサインインできるかの確認も兼ねる
- cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
+ cy.intercept('POST', '/api/signin').as('signin');
+
+ cy.get('[data-cy-signin]').click();
+ cy.get('[data-cy-signin-username] input').type('alice');
+ // Enterキーでサインインできるかの確認も兼ねる
+ cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
+
+ cy.wait('@signin');
+ });
});
it('note', () => {
cy.visit('/');
- //#region TODO: この辺はUI操作ではなくAPI操作でログインする
- cy.get('[data-cy-signin]').click();
+ // インスタンス初期セットアップ
+ cy.request('POST', '/api/admin/accounts/create', {
+ username: 'admin',
+ password: 'pass',
+ }).as('setup');
+
+ cy.get('@setup').then(() => {
+ // ユーザー作成
+ cy.request('POST', '/api/signup', {
+ username: 'alice',
+ password: 'alice1234',
+ }).as('signup');
+ });
- cy.get('[data-cy-signin-username] input').type('alice');
+ cy.get('@signup').then(() => {
+ cy.visit('/');
- // Enterキーでサインインできるかの確認も兼ねる
- cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
- //#endregion
+ cy.intercept('POST', '/api/signin').as('signin');
- cy.get('[data-cy-open-post-form]').click();
+ cy.get('[data-cy-signin]').click();
+ cy.get('[data-cy-signin-username] input').type('alice');
+ cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
- cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
+ cy.wait('@signin').as('signinEnd');
+ });
- cy.get('[data-cy-open-post-form-submit]').click();
+ cy.get('@signinEnd').then(() => {
+ cy.get('[data-cy-open-post-form]').click();
+ cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
+ cy.get('[data-cy-open-post-form-submit]').click();
- // TODO: 投稿した文字列が画面内にあるか(=タイムラインに流れてきたか)のテスト
+ cy.contains('Hello, Misskey!');
+ });
});
+
+ it('suspend', function() {
+ cy.request('POST', '/api/admin/accounts/create', {
+ username: 'admin',
+ password: 'pass',
+ }).its('body').as('admin');
+
+ cy.request('POST', '/api/signup', {
+ username: 'alice',
+ password: 'pass',
+ }).its('body').as('alice');
+
+ cy.then(() => {
+ cy.request('POST', '/api/admin/suspend-user', {
+ i: this.admin.token,
+ userId: this.alice.id,
+ });
+
+ cy.visit('/');
+
+ cy.get('[data-cy-signin]').click();
+ cy.get('[data-cy-signin-username] input').type('alice');
+ cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
+
+ cy.contains('アカウントが凍結されています');
+ });
+ });
});
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index b9623ef0d0..2c0663cf87 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -529,6 +529,8 @@ removeAllFollowing: "フォローを全解除"
removeAllFollowingDescription: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
userSuspended: "このユーザーは凍結されています。"
userSilenced: "このユーザーはサイレンスされています。"
+yourAccountSuspendedTitle: "アカウントが凍結されています"
+yourAccountSuspendedDescription: "このアカウントは、サーバーの利用規約に違反したなどの理由により、凍結されています。詳細については管理者までお問い合わせください。新しいアカウントを作らないでください。"
menu: "メニュー"
divider: "分割線"
addItem: "項目を追加"
diff --git a/src/client/account.ts b/src/client/account.ts
index e469bae5a2..6e26ac1f7d 100644
--- a/src/client/account.ts
+++ b/src/client/account.ts
@@ -3,6 +3,7 @@ import { reactive } from 'vue';
import { apiUrl } from '@client/config';
import { waiting } from '@client/os';
import { unisonReload, reloadChannel } from '@client/scripts/unison-reload';
+import { showSuspendedDialog } from './scripts/show-suspended-dialog';
// TODO: 他のタブと永続化されたstateを同期
@@ -82,17 +83,20 @@ function fetchAccount(token): Promise<Account> {
i: token
})
})
+ .then(res => res.json())
.then(res => {
- // When failed to authenticate user
- if (res.status !== 200 && res.status < 500) {
- return signout();
+ if (res.error) {
+ if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
+ showSuspendedDialog().then(() => {
+ signout();
+ });
+ } else {
+ signout();
+ }
+ } else {
+ res.token = token;
+ done(res);
}
-
- // Parse response
- res.json().then(i => {
- i.token = token;
- done(i);
- });
})
.catch(fail);
});
diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue
index c051288d0a..69f527b7d6 100755
--- a/src/client/components/signin.vue
+++ b/src/client/components/signin.vue
@@ -54,6 +54,7 @@ import { apiUrl, host } from '@client/config';
import { byteify, hexify } from '@client/scripts/2fa';
import * as os from '@client/os';
import { login } from '@client/account';
+import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
export default defineComponent({
components: {
@@ -169,15 +170,7 @@ export default defineComponent({
this.signing = false;
this.challengeData = res;
return this.queryKey();
- }).catch(() => {
- os.dialog({
- type: 'error',
- text: this.$ts.signinFailed
- });
- this.challengeData = null;
- this.totpLogin = false;
- this.signing = false;
- });
+ }).catch(this.loginFailed);
} else {
this.totpLogin = true;
this.signing = false;
@@ -190,14 +183,36 @@ export default defineComponent({
}).then(res => {
this.$emit('login', res);
this.onLogin(res);
- }).catch(() => {
+ }).catch(this.loginFailed);
+ }
+ },
+
+ loginFailed(err) {
+ switch (err.id) {
+ case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
os.dialog({
type: 'error',
- text: this.$ts.loginFailed
+ title: this.$ts.loginFailed,
+ text: this.$ts.noSuchUser
});
- this.signing = false;
- });
+ break;
+ }
+ case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
+ showSuspendedDialog();
+ break;
+ }
+ default: {
+ os.dialog({
+ type: 'error',
+ title: this.$ts.loginFailed,
+ text: JSON.stringify(err)
+ });
+ }
}
+
+ this.challengeData = null;
+ this.totpLogin = false;
+ this.signing = false;
},
resetPassword() {
diff --git a/src/client/scripts/show-suspended-dialog.ts b/src/client/scripts/show-suspended-dialog.ts
new file mode 100644
index 0000000000..dde829cdae
--- /dev/null
+++ b/src/client/scripts/show-suspended-dialog.ts
@@ -0,0 +1,10 @@
+import * as os from '@client/os';
+import { i18n } from '@client/i18n';
+
+export function showSuspendedDialog() {
+ return os.dialog({
+ type: 'error',
+ title: i18n.locale.yourAccountSuspendedTitle,
+ text: i18n.locale.yourAccountSuspendedDescription
+ });
+}
diff --git a/src/server/api/endpoints/reset-db.ts b/src/server/api/endpoints/reset-db.ts
index f430869302..f0a9dae4ff 100644
--- a/src/server/api/endpoints/reset-db.ts
+++ b/src/server/api/endpoints/reset-db.ts
@@ -18,4 +18,6 @@ export default define(meta, async (ps, user) => {
if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
await resetDb();
+
+ await new Promise(resolve => setTimeout(resolve, 1000));
});
diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts
index fff1037ff9..83c3dfee94 100644
--- a/src/server/api/private/signin.ts
+++ b/src/server/api/private/signin.ts
@@ -18,6 +18,11 @@ export default async (ctx: Koa.Context) => {
const password = body['password'];
const token = body['token'];
+ function error(status: number, error: { id: string }) {
+ ctx.status = status;
+ ctx.body = { error };
+ }
+
if (typeof username != 'string') {
ctx.status = 400;
return;
@@ -40,15 +45,15 @@ export default async (ctx: Koa.Context) => {
}) as ILocalUser;
if (user == null) {
- ctx.throw(404, {
- error: 'user not found'
+ error(404, {
+ id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
});
return;
}
if (user.isSuspended) {
- ctx.throw(403, {
- error: 'user is suspended'
+ error(403, {
+ id: 'e03a5f46-d309-4865-9b69-56282d94e1eb',
});
return;
}
@@ -58,7 +63,7 @@ export default async (ctx: Koa.Context) => {
// Compare password
const same = await bcrypt.compare(password, profile.password!);
- async function fail(status?: number, failure?: { error: string }) {
+ async function fail(status?: number, failure?: { id: string }) {
// Append signin history
await Signins.insert({
id: genId(),
@@ -69,7 +74,7 @@ export default async (ctx: Koa.Context) => {
success: false
});
- ctx.throw(status || 500, failure || { error: 'someting happened' });
+ error(status || 500, failure || { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
}
if (!profile.twoFactorEnabled) {
@@ -78,7 +83,7 @@ export default async (ctx: Koa.Context) => {
return;
} else {
await fail(403, {
- error: 'incorrect password'
+ id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c'
});
return;
}
@@ -87,7 +92,7 @@ export default async (ctx: Koa.Context) => {
if (token) {
if (!same) {
await fail(403, {
- error: 'incorrect password'
+ id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c'
});
return;
}
@@ -104,14 +109,14 @@ export default async (ctx: Koa.Context) => {
return;
} else {
await fail(403, {
- error: 'invalid token'
+ id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f'
});
return;
}
} else if (body.credentialId) {
if (!same && !profile.usePasswordLessLogin) {
await fail(403, {
- error: 'incorrect password'
+ id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c'
});
return;
}
@@ -127,7 +132,7 @@ export default async (ctx: Koa.Context) => {
if (!challenge) {
await fail(403, {
- error: 'non-existent challenge'
+ id: '2715a88a-2125-4013-932f-aa6fe72792da'
});
return;
}
@@ -139,7 +144,7 @@ export default async (ctx: Koa.Context) => {
if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) {
await fail(403, {
- error: 'non-existent challenge'
+ id: '2715a88a-2125-4013-932f-aa6fe72792da'
});
return;
}
@@ -155,7 +160,7 @@ export default async (ctx: Koa.Context) => {
if (!securityKey) {
await fail(403, {
- error: 'invalid credentialId'
+ id: '66269679-aeaf-4474-862b-eb761197e046'
});
return;
}
@@ -174,14 +179,14 @@ export default async (ctx: Koa.Context) => {
return;
} else {
await fail(403, {
- error: 'invalid challenge data'
+ id: '93b86c4b-72f9-40eb-9815-798928603d1e'
});
return;
}
} else {
if (!same && !profile.usePasswordLessLogin) {
await fail(403, {
- error: 'incorrect password'
+ id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c'
});
return;
}
@@ -192,7 +197,7 @@ export default async (ctx: Koa.Context) => {
if (keys.length === 0) {
await fail(403, {
- error: 'no keys found'
+ id: 'f27fd449-9af4-4841-9249-1f989b9fa4a4'
});
return;
}