auth-tenant-isolation.test.ts 6.1 KB

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