auth-tenant-isolation.test.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import { describe, it, expect, beforeEach } from 'vitest';
  2. import { OpenAPIHono } from '@hono/zod-openapi';
  3. import { setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
  4. import { UserEntityMt } from '@d8d/user-module-mt';
  5. import { FileMt } from '@d8d/file-module-mt';
  6. import { authRoutes } from '../../src/routes';
  7. import { AppDataSource } from '@d8d/shared-utils';
  8. import { UserService } from '@d8d/user-module-mt';
  9. // 设置数据库钩子
  10. setupIntegrationDatabaseHooksWithEntities([UserEntityMt, FileMt]);
  11. describe('租户认证隔离测试', () => {
  12. let testApp: OpenAPIHono;
  13. let userService: UserService;
  14. beforeEach(async () => {
  15. testApp = authRoutes;
  16. userService = new UserService(AppDataSource);
  17. });
  18. describe('用户注册和登录', () => {
  19. it('应该成功注册不同租户的用户', async () => {
  20. // 租户1的用户注册
  21. const tenant1Response = await testApp.request('/register', {
  22. method: 'POST',
  23. headers: {
  24. 'Content-Type': 'application/json',
  25. 'X-Tenant-Id': '1'
  26. },
  27. body: JSON.stringify({
  28. username: 'user1',
  29. password: 'password123',
  30. email: 'user1@example.com'
  31. })
  32. });
  33. expect(tenant1Response.status).toBe(201);
  34. const tenant1Data = await tenant1Response.json();
  35. expect(tenant1Data.token).toBeDefined();
  36. expect(tenant1Data.user.username).toBe('user1');
  37. // 租户2的用户注册
  38. const tenant2Response = await testApp.request('/register', {
  39. method: 'POST',
  40. headers: {
  41. 'Content-Type': 'application/json',
  42. 'X-Tenant-Id': '2'
  43. },
  44. body: JSON.stringify({
  45. username: 'user2',
  46. password: 'password123',
  47. email: 'user2@example.com'
  48. })
  49. });
  50. expect(tenant2Response.status).toBe(201);
  51. const tenant2Data = await tenant2Response.json();
  52. expect(tenant2Data.token).toBeDefined();
  53. expect(tenant2Data.user.username).toBe('user2');
  54. });
  55. it('应该成功登录到正确的租户', async () => {
  56. // 租户1的用户登录
  57. const tenant1Response = await testApp.request('/login', {
  58. method: 'POST',
  59. headers: {
  60. 'Content-Type': 'application/json',
  61. 'X-Tenant-Id': '1'
  62. },
  63. body: JSON.stringify({
  64. username: 'user1',
  65. password: 'password123'
  66. })
  67. });
  68. expect(tenant1Response.status).toBe(200);
  69. const tenant1Data = await tenant1Response.json();
  70. expect(tenant1Data.token).toBeDefined();
  71. expect(tenant1Data.user.username).toBe('user1');
  72. // 租户2的用户登录
  73. const tenant2Response = await testApp.request('/login', {
  74. method: 'POST',
  75. headers: {
  76. 'Content-Type': 'application/json',
  77. 'X-Tenant-Id': '2'
  78. },
  79. body: JSON.stringify({
  80. username: 'user2',
  81. password: 'password123'
  82. })
  83. });
  84. expect(tenant2Response.status).toBe(200);
  85. const tenant2Data = await tenant2Response.json();
  86. expect(tenant2Data.token).toBeDefined();
  87. expect(tenant2Data.user.username).toBe('user2');
  88. });
  89. it('应该拒绝跨租户登录', async () => {
  90. // 尝试用租户1的用户登录到租户2
  91. const crossTenantResponse = await testApp.request('/login', {
  92. method: 'POST',
  93. headers: {
  94. 'Content-Type': 'application/json',
  95. 'X-Tenant-Id': '2'
  96. },
  97. body: JSON.stringify({
  98. username: 'user1',
  99. password: 'password123'
  100. })
  101. });
  102. expect(crossTenantResponse.status).toBe(401);
  103. const errorData = await crossTenantResponse.json();
  104. expect(errorData.message).toBe('用户不属于该租户');
  105. });
  106. it('应该允许无租户ID的登录(向后兼容)', async () => {
  107. // 创建无租户的用户
  108. const noTenantUser = await userService.createUser({
  109. username: 'notenant',
  110. password: 'password123',
  111. email: 'notenant@example.com'
  112. });
  113. // 无租户ID登录
  114. const response = await testApp.request('/login', {
  115. method: 'POST',
  116. headers: {
  117. 'Content-Type': 'application/json'
  118. },
  119. body: JSON.stringify({
  120. username: 'notenant',
  121. password: 'password123'
  122. })
  123. });
  124. expect(response.status).toBe(200);
  125. const data = await response.json();
  126. expect(data.token).toBeDefined();
  127. expect(data.user.username).toBe('notenant');
  128. });
  129. });
  130. describe('认证中间件租户上下文', () => {
  131. it('应该在认证后设置租户上下文', async () => {
  132. // 先登录获取token
  133. const loginResponse = await testApp.request('/login', {
  134. method: 'POST',
  135. headers: {
  136. 'Content-Type': 'application/json',
  137. 'X-Tenant-Id': '1'
  138. },
  139. body: JSON.stringify({
  140. username: 'user1',
  141. password: 'password123'
  142. })
  143. });
  144. const loginData = await loginResponse.json();
  145. const token = loginData.token;
  146. // 使用token访问需要认证的端点
  147. const meResponse = await testApp.request('/me', {
  148. method: 'GET',
  149. headers: {
  150. 'Authorization': `Bearer ${token}`
  151. }
  152. });
  153. expect(meResponse.status).toBe(200);
  154. const meData = await meResponse.json();
  155. expect(meData.tenantId).toBe(1); // 应该包含租户ID
  156. });
  157. });
  158. describe('JWT Token租户信息', () => {
  159. it('应该在JWT token中包含租户ID', async () => {
  160. const response = await testApp.request('/login', {
  161. method: 'POST',
  162. headers: {
  163. 'Content-Type': 'application/json',
  164. 'X-Tenant-Id': '1'
  165. },
  166. body: JSON.stringify({
  167. username: 'user1',
  168. password: 'password123'
  169. })
  170. });
  171. const data = await response.json();
  172. const token = data.token;
  173. // 解码token验证租户ID
  174. const { JWTUtil } = await import('@d8d/shared-utils');
  175. const decoded = JWTUtil.decodeToken(token);
  176. expect(decoded?.tenantId).toBe(1);
  177. });
  178. });
  179. });