user.service.ts 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import { BadRequestException, ConflictException, Injectable, NotFoundException, OnModuleInit, ForbiddenException, Logger } from '@nestjs/common';
  2. import { InjectRepository } from '@nestjs/typeorm';
  3. import { Repository } from 'typeorm';
  4. import { User } from './user.entity';
  5. import { UserRole } from './user-role.enum';
  6. import { TenantMember } from '../tenant/tenant-member.entity';
  7. import { ApiKey } from '../auth/entities/api-key.entity';
  8. import * as bcrypt from 'bcrypt';
  9. import { CreateUserDto } from './dto/create-user.dto';
  10. import * as crypto from 'crypto';
  11. import { I18nService } from '../i18n/i18n.service';
  12. import { TenantService } from '../tenant/tenant.service';
  13. @Injectable()
  14. export class UserService implements OnModuleInit {
  15. private readonly logger = new Logger(UserService.name);
  16. constructor(
  17. @InjectRepository(User)
  18. private usersRepository: Repository<User>,
  19. @InjectRepository(ApiKey)
  20. private apiKeyRepository: Repository<ApiKey>,
  21. @InjectRepository(TenantMember)
  22. private tenantMemberRepository: Repository<TenantMember>,
  23. private i18nService: I18nService,
  24. private tenantService: TenantService,
  25. ) { }
  26. async findOneByUsername(username: string): Promise<User | null> {
  27. return this.usersRepository.findOne({ where: { username } });
  28. }
  29. async create(createUserDto: CreateUserDto): Promise<User> {
  30. const user = this.usersRepository.create(createUserDto as any);
  31. return this.usersRepository.save(user as any);
  32. }
  33. async onModuleInit() {
  34. await this.createAdminIfNotExists();
  35. }
  36. async findAll(): Promise<User[]> {
  37. return this.usersRepository.find({
  38. select: ['id', 'username', 'isAdmin', 'role', 'createdAt', 'tenantId'],
  39. relations: ['tenantMembers', 'tenantMembers.tenant'],
  40. order: { createdAt: 'DESC' },
  41. });
  42. }
  43. async isAdmin(userId: string): Promise<boolean> {
  44. const user = await this.usersRepository.findOne({
  45. where: { id: userId },
  46. select: ['isAdmin'],
  47. });
  48. return user?.isAdmin || false;
  49. }
  50. async changePassword(
  51. userId: string,
  52. currentPassword: string,
  53. newPassword: string,
  54. ): Promise<{ message: string }> {
  55. const user = await this.usersRepository.findOne({ where: { id: userId } });
  56. if (!user) {
  57. throw new NotFoundException(this.i18nService.getMessage('userNotFound'));
  58. }
  59. const isCurrentPasswordValid = await bcrypt.compare(
  60. currentPassword,
  61. user.password,
  62. );
  63. if (!isCurrentPasswordValid) {
  64. throw new BadRequestException(this.i18nService.getMessage('incorrectCurrentPassword'));
  65. }
  66. const hashedNewPassword = await bcrypt.hash(newPassword, 10);
  67. await this.usersRepository.update(userId, { password: hashedNewPassword });
  68. return { message: this.i18nService.getMessage('passwordChanged') };
  69. }
  70. async createUser(
  71. username: string,
  72. password: string,
  73. isAdmin: boolean = false,
  74. tenantId?: string,
  75. role?: UserRole,
  76. ): Promise<{ message: string; user: { id: string; username: string; isAdmin: boolean } }> {
  77. const existingUser = await this.findOneByUsername(username);
  78. if (existingUser) {
  79. throw new ConflictException(this.i18nService.getMessage('usernameExists'));
  80. }
  81. const hashedPassword = await bcrypt.hash(password, 10);
  82. const targetRoleValue = role ?? (isAdmin ? UserRole.TENANT_ADMIN : UserRole.USER);
  83. console.log(`[UserService] Creating user: ${username}, isAdmin: ${isAdmin}, role: ${targetRoleValue}`);
  84. const user = await this.usersRepository.save({
  85. username,
  86. password: hashedPassword,
  87. isAdmin,
  88. tenantId: tenantId ?? undefined,
  89. role: targetRoleValue,
  90. } as any);
  91. return {
  92. message: this.i18nService.getMessage('userCreated'),
  93. user: { id: user.id, username: user.username, isAdmin: user.isAdmin },
  94. };
  95. }
  96. async findOneById(userId: string): Promise<User | null> {
  97. return this.usersRepository.findOne({
  98. where: { id: userId },
  99. relations: ['tenantMembers', 'tenantMembers.tenant']
  100. });
  101. }
  102. async findByApiKey(apiKeyValue: string): Promise<User | null> {
  103. const apiKey = await this.apiKeyRepository.findOne({
  104. where: { key: apiKeyValue },
  105. relations: ['user']
  106. });
  107. return apiKey ? apiKey.user : null;
  108. }
  109. async findByTenantId(tenantId: string): Promise<User[]> {
  110. const members = await this.tenantMemberRepository.find({
  111. where: { tenantId },
  112. relations: ['user']
  113. });
  114. return members.map(m => m.user);
  115. }
  116. async getUserTenants(userId: string): Promise<(TenantMember & { features?: { isNotebookEnabled: boolean } })[]> {
  117. const user = await this.usersRepository.findOne({ where: { id: userId }, select: ['role'] });
  118. if (user?.role === UserRole.SUPER_ADMIN) {
  119. const allTenants = await this.tenantService.findAll();
  120. const results = await Promise.all(allTenants.map(async t => {
  121. const settings = await this.tenantService.getSettings(t.id);
  122. return {
  123. tenantId: t.id,
  124. tenant: t,
  125. role: UserRole.SUPER_ADMIN,
  126. userId: userId,
  127. features: {
  128. isNotebookEnabled: settings?.isNotebookEnabled ?? true,
  129. },
  130. } as TenantMember & { features: { isNotebookEnabled: boolean } };
  131. }));
  132. return results;
  133. }
  134. const members = await this.tenantMemberRepository.find({
  135. where: { userId },
  136. relations: ['tenant']
  137. });
  138. // Filter out the "Default" tenant for non-super admins
  139. const filtered = members.filter(m => m.tenant?.name !== TenantService.DEFAULT_TENANT_NAME);
  140. // Attach per-tenant feature flags
  141. return Promise.all(filtered.map(async m => {
  142. const settings = await this.tenantService.getSettings(m.tenantId);
  143. return {
  144. ...m,
  145. features: {
  146. isNotebookEnabled: settings?.isNotebookEnabled ?? true,
  147. },
  148. };
  149. }));
  150. }
  151. /**
  152. * Generates a new API key for the user, or returns the existing one (first one).
  153. */
  154. async getOrCreateApiKey(userId: string): Promise<string> {
  155. const user = await this.usersRepository.findOne({
  156. where: { id: userId },
  157. relations: ['apiKeys']
  158. });
  159. if (!user) throw new NotFoundException(this.i18nService.getMessage('userNotFound'));
  160. if (user.apiKeys && user.apiKeys.length > 0) {
  161. return user.apiKeys[0].key;
  162. }
  163. const keyString = 'kb_' + crypto.randomBytes(32).toString('hex');
  164. const newApiKey = this.apiKeyRepository.create({
  165. userId: user.id,
  166. key: keyString
  167. });
  168. await this.apiKeyRepository.save(newApiKey);
  169. return keyString;
  170. }
  171. /**
  172. * Regenerates (rotates) the API key for the user.
  173. * This clears existing keys and creates a new one.
  174. */
  175. async regenerateApiKey(userId: string): Promise<string> {
  176. const user = await this.usersRepository.findOne({ where: { id: userId } });
  177. if (!user) throw new NotFoundException(this.i18nService.getMessage('userNotFound'));
  178. // Delete existing keys
  179. await this.apiKeyRepository.delete({ userId: user.id });
  180. // Create new key
  181. const keyString = 'kb_' + crypto.randomBytes(32).toString('hex');
  182. const newApiKey = this.apiKeyRepository.create({
  183. userId: user.id,
  184. key: keyString
  185. });
  186. await this.apiKeyRepository.save(newApiKey);
  187. return keyString;
  188. }
  189. async updateUser(
  190. userId: string,
  191. updateData: { isAdmin?: boolean; role?: string; password?: string; tenantId?: string },
  192. ): Promise<{ message: string; user: { id: string; username: string; isAdmin: boolean } }> {
  193. const user = await this.usersRepository.findOne({ where: { id: userId } });
  194. if (!user) {
  195. throw new NotFoundException(this.i18nService.getMessage('userNotFound'));
  196. }
  197. // パスワードの更新が必要な場合は、まずハッシュ化する
  198. if (updateData.password) {
  199. const hashedPassword = await bcrypt.hash(updateData.password, 10);
  200. updateData.password = hashedPassword;
  201. }
  202. // ユーザー名 "admin" のユーザーに対するいかなる変更も阻止
  203. if (user.username === 'admin') {
  204. throw new ForbiddenException(this.i18nService.getMessage('cannotModifyBuiltinAdmin'));
  205. }
  206. // Apply role update logic
  207. if (updateData.role) {
  208. if (updateData.role === UserRole.TENANT_ADMIN || updateData.role === UserRole.SUPER_ADMIN) {
  209. updateData.isAdmin = true;
  210. } else {
  211. updateData.isAdmin = false;
  212. }
  213. }
  214. await this.usersRepository.update(userId, updateData as any);
  215. const updatedUser = await this.usersRepository.findOne({
  216. where: { id: userId },
  217. select: ['id', 'username', 'isAdmin'],
  218. });
  219. return {
  220. message: this.i18nService.getMessage('userInfoUpdated'),
  221. user: {
  222. id: updatedUser!.id,
  223. username: updatedUser!.username,
  224. isAdmin: updatedUser!.isAdmin
  225. },
  226. };
  227. }
  228. async deleteUser(userId: string): Promise<{ message: string }> {
  229. const user = await this.usersRepository.findOne({ where: { id: userId } });
  230. if (!user) {
  231. throw new NotFoundException(this.i18nService.getMessage('userNotFound'));
  232. }
  233. // ユーザー名 "admin" のユーザーの削除を阻止
  234. if (user.username === 'admin') {
  235. throw new ForbiddenException(this.i18nService.getMessage('cannotDeleteBuiltinAdmin'));
  236. }
  237. await this.usersRepository.delete(userId);
  238. return {
  239. message: this.i18nService.getMessage('userDeleted'),
  240. };
  241. }
  242. async getTenantSettings(tenantId: string) {
  243. return this.tenantService.getSettings(tenantId);
  244. }
  245. private async createAdminIfNotExists() {
  246. const adminUser = await this.findOneByUsername('admin');
  247. if (!adminUser) {
  248. const randomPassword = Math.random().toString(36).slice(-8);
  249. const hashedPassword = await bcrypt.hash(randomPassword, 10);
  250. await this.usersRepository.save({
  251. username: 'admin',
  252. password: hashedPassword,
  253. isAdmin: true,
  254. role: UserRole.SUPER_ADMIN,
  255. });
  256. console.log('\n=== Administrator Account Created ===');
  257. console.log('Username: admin');
  258. console.log('パスワード:', randomPassword);
  259. console.log('========================================\n');
  260. }
  261. }
  262. }