diff --git a/.config/example.yml b/.config/example.yml index 7080159117..d66c00221c 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -59,6 +59,19 @@ # # publishTarballInsteadOfProvideRepositoryUrl: true +# ┌───────────────┐ +#───┘ Initial Setup Password └───────────────────────────────────────────────────── + +# Password to initiate setting up admin account. +# It will not be used after the initial setup is complete. +# +# Be sure to change this when you set up Misskey via the Internet. +# +# The provider of the service who sets up Misskey on behalf of the customer should +# set this value to something unique when generating the Misskey config file, +# and provide it to the customer. +initialPassword: example_password_please_change_this_or_you_will_get_hacked + # ┌─────┐ #───┘ URL └───────────────────────────────────────────────────── diff --git a/cypress/e2e/basic.cy.ts b/cypress/e2e/basic.cy.ts index d2525e0a7d..e4baeacbf3 100644 --- a/cypress/e2e/basic.cy.ts +++ b/cypress/e2e/basic.cy.ts @@ -23,6 +23,7 @@ describe('Before setup instance', () => { cy.intercept('POST', '/api/admin/accounts/create').as('signup'); + cy.get('[data-cy-admin-initial-password] input').type('example_password_please_change_this_or_you_will_get_hacked'); 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(); diff --git a/locales/index.d.ts b/locales/index.d.ts index 2a27eb3e15..50b14defa4 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -48,6 +48,16 @@ export interface Locale extends ILocale { * パスワード */ "password": string; + /** + * 初期設定開始用パスワード + */ + "initialPasswordForSetup": string; + /** + * Misskeyの動作環境を自分で構築した場合は、コンフィグファイルにパスワードが記載されています。 + * Misskeyの構築を自動で行うサービスなどを使用している場合は、(おそらく)その提供者から初期設定用のパスワードを入手できるはずです。 + * このパスワードをコンフィグファイルに設定していない場合は、空欄にしたまま続行してください。 + */ + "initialPasswordForSetupDescription": string; /** * パスワードを忘れた */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 80cd8dc7cc..a576d3496a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -8,6 +8,9 @@ search: "検索" notifications: "通知" username: "ユーザー名" password: "パスワード" +initialPasswordForSetup: "初期設定開始用パスワード" +initialPasswordIsIncorrect: "初期設定開始用のパスワードが違います。" +initialPasswordForSetupDescription: "Misskeyの動作環境を自分で構築した場合は、コンフィグファイルにパスワードが記載されています。\nMisskeyの構築を自動で行うサービスなどを使用している場合は、(おそらく)その提供者から初期設定用のパスワードを入手できるはずです。\nこのパスワードをコンフィグファイルに設定していない場合は、空欄にしたまま続行してください。" forgotPassword: "パスワードを忘れた" fetchingAsApObject: "連合に照会中" ok: "OK" diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 97ba79c574..b320ce5403 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -63,6 +63,8 @@ type Source = { publishTarballInsteadOfProvideRepositoryUrl?: boolean; + initialPassword?: string; + proxy?: string; proxySmtp?: string; proxyBypassHosts?: string[]; @@ -152,6 +154,7 @@ export type Config = { version: string; publishTarballInsteadOfProvideRepositoryUrl: boolean; + initialPassword: string | undefined; host: string; hostname: string; scheme: string; @@ -232,6 +235,7 @@ export function loadConfig(): Config { return { version, publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl, + initialPassword: config.initialPassword, url: url.origin, port: config.port ?? parseInt(process.env.PORT ?? '', 10), socket: config.socket, diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index a7e8a3b018..a9cc399225 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -12,11 +12,27 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; import { localUsernameSchema, passwordSchema } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { ApiError } from '@/server/api/error.js'; import { Packed } from '@/misc/json-schema.js'; export const meta = { tags: ['admin'], + errors: { + accessDenied: { + message: 'Access denied.', + code: 'ACCESS_DENIED', + id: '1fb7cb09-d46a-4fff-b8df-057708cce513', + }, + + wrongInitialPassword: { + message: 'Initial password is wrong.', + code: 'WRONG_INITIAL_PASSWORD', + id: '1fb7cb09-d46a-4fff-b8df-057708cce514', + }, + }, + res: { type: 'object', optional: false, nullable: false, @@ -35,6 +51,7 @@ export const paramDef = { properties: { username: localUsernameSchema, password: passwordSchema, + initialPassword: { type: 'string', nullable: true }, }, required: ['username', 'password'], } as const; @@ -42,6 +59,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.config) + private config: Config, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -50,9 +70,17 @@ export default class extends Endpoint { // eslint- private instanceActorService: InstanceActorService, ) { super(meta, paramDef, async (ps, _me, token) => { + if (ps.initialPassword != null && this.config.initialPassword != null) { + if (ps.initialPassword !== this.config.initialPassword) { + throw new ApiError(meta.errors.wrongInitialPassword); + } + } + const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null; const realUsers = await this.instanceActorService.realLocalUsersPresent(); - if ((realUsers && !me?.isRoot) || token !== null) throw new Error('access denied'); + if ((realUsers && !me?.isRoot) || token !== null) { + throw new ApiError(meta.errors.accessDenied); + } const { account, secret } = await this.signupService.signup({ username: ps.username, diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue index a227c7c4bc..bbf1391a1e 100644 --- a/packages/frontend/src/pages/welcome.setup.vue +++ b/packages/frontend/src/pages/welcome.setup.vue @@ -14,6 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.intro }}
+ + + + @@ -47,6 +51,7 @@ import MkAnimBg from '@/components/MkAnimBg.vue'; const username = ref(''); const password = ref(''); +const initialPassword = ref(''); const submitting = ref(false); function submit() { @@ -56,14 +61,27 @@ function submit() { misskeyApi('admin/accounts/create', { username: username.value, password: password.value, + initialPassword: initialPassword.value === '' ? null : initialPassword.value, }).then(res => { return login(res.token); - }).catch(() => { + }).catch((err) => { submitting.value = false; + let title = i18n.ts.somethingHappened; + let text = err.message + '\n' + err.id; + + if (err.code === 'ACCESS_DENIED') { + title = i18n.ts.permissionDeniedError; + text = i18n.ts.operationForbidden; + } else if (err.code === 'WRONG_INITIAL_PASSWORD') { + title = i18n.ts.permissionDeniedError; + text = i18n.ts.incorrectPassword; + } + os.alert({ type: 'error', - text: i18n.ts.somethingHappened, + title, + text, }); }); } @@ -74,8 +92,8 @@ function submit() { min-height: 100svh; padding: 32px 32px 64px 32px; box-sizing: border-box; -display: grid; -place-content: center; + display: grid; + place-content: center; } .form { diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 5d5bc52956..7f388f7943 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5588,6 +5588,7 @@ export type operations = { 'application/json': { username: string; password: string; + initialPassword?: string | null; }; }; };