wip
This commit is contained in:
parent
19d9fc56fa
commit
e1523706a3
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SystemAccounts31740133121105 {
|
||||||
|
name = 'SystemAccounts31740133121105'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "rootUserId" character varying(32)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_c80e4079d632f95eac06a9d28cc" FOREIGN KEY ("rootUserId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||||
|
|
||||||
|
const users = await queryRunner.query(`SELECT "id" FROM "user" WHERE "isRoot" = true LIMIT 1`);
|
||||||
|
if (users.length > 0) {
|
||||||
|
await queryRunner.query(`UPDATE "meta" SET "rootUserId" = $1`, [users[0].id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT "FK_c80e4079d632f95eac06a9d28cc"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "rootUserId"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Not, IsNull } from 'typeorm';
|
import { Not, IsNull } from 'typeorm';
|
||||||
import type { FollowingsRepository, MiUser, UsersRepository } from '@/models/_.js';
|
import type { FollowingsRepository, MiMeta, MiUser, UsersRepository } from '@/models/_.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
@ -18,6 +18,9 @@ import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteAccountService {
|
export class DeleteAccountService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private meta: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -38,8 +41,9 @@ export class DeleteAccountService {
|
||||||
id: string;
|
id: string;
|
||||||
host: string | null;
|
host: string | null;
|
||||||
}, moderator?: MiUser): Promise<void> {
|
}, moderator?: MiUser): Promise<void> {
|
||||||
|
if (this.meta.rootUserId === user.id) throw new Error('cannot delete a root account');
|
||||||
|
|
||||||
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
||||||
if (_user.isRoot) throw new Error('cannot delete a root account');
|
|
||||||
|
|
||||||
const systemAccounts = await this.systemAccountService.list();
|
const systemAccounts = await this.systemAccountService.list();
|
||||||
for (const systemAccount of systemAccounts) {
|
for (const systemAccount of systemAccounts) {
|
||||||
|
|
|
@ -406,15 +406,15 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async isModerator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise<boolean> {
|
public async isModerator(user: { id: MiUser['id'] } | null): Promise<boolean> {
|
||||||
if (user == null) return false;
|
if (user == null) return false;
|
||||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
|
return (this.meta.rootUserId === user.id) || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async isAdministrator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise<boolean> {
|
public async isAdministrator(user: { id: MiUser['id'] } | null): Promise<boolean> {
|
||||||
if (user == null) return false;
|
if (user == null) return false;
|
||||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
return (this.meta.rootUserId === user.id) || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -463,16 +463,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
.map(a => a.userId),
|
.map(a => a.userId),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (includeRoot) {
|
if (includeRoot && this.meta.rootUserId) {
|
||||||
const rootUserId = await this.rootUserIdCache.fetch(async () => {
|
resultSet.add(this.meta.rootUserId);
|
||||||
const it = await this.usersRepository.createQueryBuilder('users')
|
|
||||||
.select('id')
|
|
||||||
.where({ isRoot: true })
|
|
||||||
.getRawOne<{ id: string }>();
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
return it!.id;
|
|
||||||
});
|
|
||||||
resultSet.add(rootUserId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...resultSet].sort((x, y) => x.localeCompare(y));
|
return [...resultSet].sort((x, y) => x.localeCompare(y));
|
||||||
|
|
|
@ -21,6 +21,7 @@ import UsersChart from '@/core/chart/charts/users.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { UserService } from '@/core/UserService.js';
|
import { UserService } from '@/core/UserService.js';
|
||||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SignupService {
|
export class SignupService {
|
||||||
|
@ -42,6 +43,7 @@ export class SignupService {
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private systemAccountService: SystemAccountService,
|
private systemAccountService: SystemAccountService,
|
||||||
|
private metaService: MetaService,
|
||||||
private usersChart: UsersChart,
|
private usersChart: UsersChart,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -86,9 +88,7 @@ export class SignupService {
|
||||||
throw new Error('USED_USERNAME');
|
throw new Error('USED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent(); // TODO
|
if (!opts.ignorePreservedUsernames && this.meta.rootUserId != null) {
|
||||||
|
|
||||||
if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
|
|
||||||
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
|
||||||
if (isPreserved) {
|
if (isPreserved) {
|
||||||
throw new Error('USED_USERNAME');
|
throw new Error('USED_USERNAME');
|
||||||
|
@ -129,7 +129,6 @@ export class SignupService {
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: this.utilityService.toPunyNullable(host),
|
host: this.utilityService.toPunyNullable(host),
|
||||||
token: secret,
|
token: secret,
|
||||||
isRoot: isTheFirstUser,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await transactionalEntityManager.save(new MiUserKeypair({
|
await transactionalEntityManager.save(new MiUserKeypair({
|
||||||
|
@ -153,6 +152,10 @@ export class SignupService {
|
||||||
this.usersChart.update(account, true);
|
this.usersChart.update(account, true);
|
||||||
this.userService.notifySystemWebhook(account, 'userCreated');
|
this.userService.notifySystemWebhook(account, 'userCreated');
|
||||||
|
|
||||||
|
if (this.meta.rootUserId == null) {
|
||||||
|
await this.metaService.update({ rootUserId: account.id });
|
||||||
|
}
|
||||||
|
|
||||||
return { account, secret };
|
return { account, secret };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,6 @@ export class SystemAccountService {
|
||||||
usernameLower: extra.username.toLowerCase(),
|
usernameLower: extra.username.toLowerCase(),
|
||||||
host: null,
|
host: null,
|
||||||
token: secret,
|
token: secret,
|
||||||
isRoot: false,
|
|
||||||
isLocked: true,
|
isLocked: true,
|
||||||
isExplorable: false,
|
isExplorable: false,
|
||||||
isBot: true,
|
isBot: true,
|
||||||
|
|
|
@ -152,7 +152,7 @@ export class MetaEntityService {
|
||||||
...packed,
|
...packed,
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
||||||
requireSetup: !await this.instanceActorService.realLocalUsersPresent(), // TODO
|
requireSetup: this.meta.rootUserId == null,
|
||||||
proxyAccountName: proxyAccount.username,
|
proxyAccountName: proxyAccount.username,
|
||||||
features: {
|
features: {
|
||||||
localTimeline: instance.policies.ltlAvailable,
|
localTimeline: instance.policies.ltlAvailable,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
import { Entity, Column, PrimaryColumn, ManyToOne } from 'typeorm';
|
||||||
import { id } from './util/id.js';
|
import { id } from './util/id.js';
|
||||||
import { MiUser } from './User.js';
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
|
@ -15,6 +15,18 @@ export class MiMeta {
|
||||||
})
|
})
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public rootUserId: MiUser['id'] | null;
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public rootUser: MiUser | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 1024, nullable: true,
|
length: 1024, nullable: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -184,12 +184,6 @@ export class MiUser {
|
||||||
})
|
})
|
||||||
public isCat: boolean;
|
public isCat: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
|
||||||
default: false,
|
|
||||||
comment: 'Whether the User is the root.',
|
|
||||||
})
|
|
||||||
public isRoot: boolean;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: true,
|
default: true,
|
||||||
|
|
|
@ -371,7 +371,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
|
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && (this.meta.rootUserId !== user!.id)) {
|
||||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||||
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
||||||
throw new ApiError({
|
throw new ApiError({
|
||||||
|
@ -391,7 +391,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ep.meta.requireRolePolicy != null && !user!.isRoot) {
|
if (ep.meta.requireRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
|
||||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||||
const policies = await this.roleService.getUserPolicies(user!.id);
|
const policies = await this.roleService.getUserPolicies(user!.id);
|
||||||
if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
||||||
import { SignupService } from '@/core/SignupService.js';
|
import { SignupService } from '@/core/SignupService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
|
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
|
||||||
|
@ -61,6 +61,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -69,9 +72,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, _me, token) => {
|
super(meta, paramDef, async (ps, _me, token) => {
|
||||||
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
||||||
const realUsers = await this.instanceActorService.realLocalUsersPresent(); // TODO
|
|
||||||
|
|
||||||
if (!realUsers && me == null && token == null) {
|
if (this.serverSettings.rootUserId == null && me == null && token == null) {
|
||||||
// 初回セットアップの場合
|
// 初回セットアップの場合
|
||||||
if (this.config.setupPassword != null) {
|
if (this.config.setupPassword != null) {
|
||||||
// 初期パスワードが設定されている場合
|
// 初期パスワードが設定されている場合
|
||||||
|
@ -83,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
// 初期パスワードが設定されていないのに初期パスワードが入力された場合
|
// 初期パスワードが設定されていないのに初期パスワードが入力された場合
|
||||||
throw new ApiError(meta.errors.wrongInitialPassword);
|
throw new ApiError(meta.errors.wrongInitialPassword);
|
||||||
}
|
}
|
||||||
} else if ((realUsers && !me?.isRoot) || token !== null) {
|
} else if ((this.serverSettings.rootUserId != null && (this.serverSettings.rootUserId !== me?.id)) || token !== null) {
|
||||||
// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
|
// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
|
||||||
throw new ApiError(meta.errors.accessDenied);
|
throw new ApiError(meta.errors.accessDenied);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
import type { UsersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
@ -43,6 +43,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@ -58,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isRoot) {
|
if (this.serverSettings.rootUserId === user.id) {
|
||||||
throw new Error('cannot reset password of root');
|
throw new Error('cannot reset password of root');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
@ -19,6 +19,8 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { MiMeta } from '@/models/_.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
@ -81,6 +83,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
private remoteUserResolveService: RemoteUserResolveService,
|
private remoteUserResolveService: RemoteUserResolveService,
|
||||||
private apiLoggerService: ApiLoggerService,
|
private apiLoggerService: ApiLoggerService,
|
||||||
private accountMoveService: AccountMoveService,
|
private accountMoveService: AccountMoveService,
|
||||||
|
@ -92,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
// check parameter
|
// check parameter
|
||||||
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
|
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
|
||||||
// abort if user is the root
|
// abort if user is the root
|
||||||
if (me.isRoot) throw new ApiError(meta.errors.rootForbidden);
|
if (this.serverSettings.rootUserId === me.id) throw new ApiError(meta.errors.rootForbidden);
|
||||||
// abort if user has already moved
|
// abort if user has already moved
|
||||||
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue