settings-store-rw-permissions.e2e-spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. import { DefaultLogger, LogLevel, mergeConfig, Permission } from '@vendure/core';
  2. import { createTestEnvironment, type SimpleGraphQLClient } from '@vendure/testing';
  3. import { graphql } from 'gql.tada';
  4. import path from 'node:path';
  5. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  6. import { initialData } from '../../../e2e-common/e2e-initial-data';
  7. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  8. import {
  9. dashboardSavedViewsPermission,
  10. SettingsStoreRwPermissionsPlugin,
  11. } from './fixtures/test-plugins/settings-store-rw-permissions-plugin';
  12. import { createAdministratorDocument, createRoleDocument } from './graphql/shared-definitions';
  13. const getSettingsStoreValueDocument = graphql(`
  14. query GetSettingsStoreValue($key: String!) {
  15. getSettingsStoreValue(key: $key)
  16. }
  17. `);
  18. const setSettingsStoreValueDocument = graphql(`
  19. mutation SetSettingsStoreValue($input: SettingsStoreInput!) {
  20. setSettingsStoreValue(input: $input) {
  21. key
  22. result
  23. error
  24. }
  25. }
  26. `);
  27. describe('Settings Store Read/Write Permissions', () => {
  28. const { server, adminClient } = createTestEnvironment(
  29. mergeConfig(testConfig(), {
  30. logger: new DefaultLogger({ level: LogLevel.Warn }),
  31. plugins: [SettingsStoreRwPermissionsPlugin],
  32. }),
  33. );
  34. let admins: Awaited<ReturnType<typeof setupTestAdmins>>;
  35. beforeAll(async () => {
  36. await server.init({
  37. initialData,
  38. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  39. customerCount: 1,
  40. });
  41. await adminClient.asSuperAdmin();
  42. admins = await setupTestAdmins(adminClient);
  43. // Set up initial test data as super admin
  44. await adminClient.asSuperAdmin();
  45. await adminClient.query(setSettingsStoreValueDocument, {
  46. input: { key: 'rwtest.separateReadWrite', value: 'initial-separate-value' },
  47. });
  48. await adminClient.query(setSettingsStoreValueDocument, {
  49. input: { key: 'rwtest.dashboardSavedViews', value: { viewName: 'Test View', filters: [] } },
  50. });
  51. await adminClient.query(setSettingsStoreValueDocument, {
  52. input: { key: 'rwtest.multipleReadPermissions', value: 'multi-read-value' },
  53. });
  54. await adminClient.query(setSettingsStoreValueDocument, {
  55. input: { key: 'rwtest.backwardCompatible', value: 'backward-compatible-value' },
  56. });
  57. await adminClient.query(setSettingsStoreValueDocument, {
  58. input: { key: 'rwtest.publicRead', value: 'public-read-value' },
  59. });
  60. }, TEST_SETUP_TIMEOUT_MS);
  61. afterAll(async () => {
  62. await server.destroy();
  63. });
  64. describe('Separate read/write permissions (object syntax)', () => {
  65. it('user with read permission can read but not write', async () => {
  66. await adminClient.asUserWithCredentials(admins.readCatalogAdmin.emailAddress, 'test');
  67. // Should be able to read
  68. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  69. key: 'rwtest.separateReadWrite',
  70. });
  71. expect(getSettingsStoreValue).toBe('initial-separate-value');
  72. // Should not be able to write
  73. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  74. input: {
  75. key: 'rwtest.separateReadWrite',
  76. value: 'test-value',
  77. },
  78. });
  79. expect(setSettingsStoreValue.result).toBe(false);
  80. expect(setSettingsStoreValue.error).toContain('permissions');
  81. });
  82. it('user with write permission can write but not read', async () => {
  83. await adminClient.asUserWithCredentials(admins.updateCatalogAdmin.emailAddress, 'test');
  84. // Should not be able to read
  85. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  86. key: 'rwtest.separateReadWrite',
  87. });
  88. expect(getSettingsStoreValue).toBeNull();
  89. // Should be able to write
  90. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  91. input: {
  92. key: 'rwtest.separateReadWrite',
  93. value: 'write-only-value',
  94. },
  95. });
  96. expect(setSettingsStoreValue.result).toBe(true);
  97. expect(setSettingsStoreValue.error).toBeNull();
  98. });
  99. it('user with both permissions can read and write', async () => {
  100. await adminClient.asUserWithCredentials(admins.readWriteCatalogAdmin.emailAddress, 'test');
  101. // Should be able to write
  102. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  103. input: {
  104. key: 'rwtest.separateReadWrite',
  105. value: 'read-write-value',
  106. },
  107. });
  108. expect(setSettingsStoreValue.result).toBe(true);
  109. // Should be able to read
  110. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  111. key: 'rwtest.separateReadWrite',
  112. });
  113. expect(getSettingsStoreValue).toBe('read-write-value');
  114. });
  115. });
  116. describe('Custom RwPermissionDefinition', () => {
  117. it('user with custom read permission can only read', async () => {
  118. await adminClient.asUserWithCredentials(admins.customReadAdmin.emailAddress, 'test');
  119. // Should be able to read
  120. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  121. key: 'rwtest.dashboardSavedViews',
  122. });
  123. expect(getSettingsStoreValue).toEqual({ viewName: 'Test View', filters: [] });
  124. // Should not be able to write
  125. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  126. input: {
  127. key: 'rwtest.dashboardSavedViews',
  128. value: { viewName: 'Modified View', filters: [] },
  129. },
  130. });
  131. expect(setSettingsStoreValue.result).toBe(false);
  132. expect(setSettingsStoreValue.error).toContain('permissions');
  133. });
  134. it('user with custom write permission can only write', async () => {
  135. await adminClient.asUserWithCredentials(admins.customWriteAdmin.emailAddress, 'test');
  136. // Should not be able to read
  137. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  138. key: 'rwtest.dashboardSavedViews',
  139. });
  140. expect(getSettingsStoreValue).toBeNull();
  141. // Should be able to write
  142. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  143. input: {
  144. key: 'rwtest.dashboardSavedViews',
  145. value: { viewName: 'Write-Only View', filters: [] },
  146. },
  147. });
  148. expect(setSettingsStoreValue.result).toBe(true);
  149. });
  150. it('user with both custom permissions can read and write', async () => {
  151. await adminClient.asUserWithCredentials(admins.customReadWriteAdmin.emailAddress, 'test');
  152. // Should be able to write
  153. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  154. input: {
  155. key: 'rwtest.dashboardSavedViews',
  156. value: { viewName: 'Custom RW View', filters: ['filter1'] },
  157. },
  158. });
  159. expect(setSettingsStoreValue.result).toBe(true);
  160. // Should be able to read
  161. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  162. key: 'rwtest.dashboardSavedViews',
  163. });
  164. expect(getSettingsStoreValue).toEqual({ viewName: 'Custom RW View', filters: ['filter1'] });
  165. });
  166. it('user without custom permissions cannot access', async () => {
  167. await adminClient.asUserWithCredentials(admins.authenticatedOnlyAdmin.emailAddress, 'test');
  168. // Should not be able to read
  169. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  170. key: 'rwtest.dashboardSavedViews',
  171. });
  172. expect(getSettingsStoreValue).toBeNull();
  173. // Should not be able to write
  174. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  175. input: {
  176. key: 'rwtest.dashboardSavedViews',
  177. value: { viewName: 'Unauthorized View', filters: [] },
  178. },
  179. });
  180. expect(setSettingsStoreValue.result).toBe(false);
  181. expect(setSettingsStoreValue.error).toContain('permissions');
  182. });
  183. it('demonstrates that RwPermissionDefinition generates correct permission names', () => {
  184. // This test documents how to use the new RwPermissionDefinition
  185. expect(dashboardSavedViewsPermission.Read).toBe('ReadDashboardSavedViews');
  186. expect(dashboardSavedViewsPermission.Write).toBe('WriteDashboardSavedViews');
  187. });
  188. });
  189. describe('Multiple read permissions (OR logic)', () => {
  190. it('user with one of the read permissions can read', async () => {
  191. await adminClient.asUserWithCredentials(admins.readSettingsAdmin.emailAddress, 'test');
  192. // Can read with ReadSettings permission (one of the allowed)
  193. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  194. key: 'rwtest.multipleReadPermissions',
  195. });
  196. expect(getSettingsStoreValue).toBe('multi-read-value');
  197. // Cannot write (needs UpdateSettings)
  198. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  199. input: {
  200. key: 'rwtest.multipleReadPermissions',
  201. value: 'unauthorized-write',
  202. },
  203. });
  204. expect(setSettingsStoreValue.result).toBe(false);
  205. expect(setSettingsStoreValue.error).toContain('permissions');
  206. });
  207. it('user with the other read permission can also read', async () => {
  208. await adminClient.asUserWithCredentials(admins.readCatalogAdmin.emailAddress, 'test');
  209. // Can read with ReadCatalog permission (the other allowed)
  210. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  211. key: 'rwtest.multipleReadPermissions',
  212. });
  213. expect(getSettingsStoreValue).toBe('multi-read-value');
  214. });
  215. it('user with write permission can write', async () => {
  216. await adminClient.asUserWithCredentials(admins.updateSettingsAdmin.emailAddress, 'test');
  217. // Should not be able to read (doesn't have ReadCatalog or ReadSettings)
  218. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  219. key: 'rwtest.multipleReadPermissions',
  220. });
  221. expect(getSettingsStoreValue).toBeNull();
  222. // Should be able to write
  223. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  224. input: {
  225. key: 'rwtest.multipleReadPermissions',
  226. value: 'write-authorized-value',
  227. },
  228. });
  229. expect(setSettingsStoreValue.result).toBe(true);
  230. });
  231. });
  232. describe('Backward compatibility', () => {
  233. it('user without required permission cannot read or write', async () => {
  234. await adminClient.asUserWithCredentials(admins.readCatalogAdmin.emailAddress, 'test');
  235. // Cannot read (needs UpdateSettings)
  236. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  237. key: 'rwtest.backwardCompatible',
  238. });
  239. expect(getSettingsStoreValue).toBeNull();
  240. // Cannot write (needs UpdateSettings)
  241. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  242. input: {
  243. key: 'rwtest.backwardCompatible',
  244. value: 'unauthorized-value',
  245. },
  246. });
  247. expect(setSettingsStoreValue.result).toBe(false);
  248. expect(setSettingsStoreValue.error).toContain('permissions');
  249. });
  250. it('user with required permission can read and write', async () => {
  251. await adminClient.asUserWithCredentials(admins.updateSettingsAdmin.emailAddress, 'test');
  252. // Should be able to read
  253. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  254. key: 'rwtest.backwardCompatible',
  255. });
  256. expect(getSettingsStoreValue).toBe('backward-compatible-value');
  257. // Should be able to write
  258. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  259. input: {
  260. key: 'rwtest.backwardCompatible',
  261. value: 'authorized-backward-value',
  262. },
  263. });
  264. expect(setSettingsStoreValue.result).toBe(true);
  265. });
  266. });
  267. describe('Public read with restricted write', () => {
  268. it('authenticated user can read but not write', async () => {
  269. await adminClient.asUserWithCredentials(admins.authenticatedOnlyAdmin.emailAddress, 'test');
  270. // Should be able to read (only requires Authenticated)
  271. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  272. key: 'rwtest.publicRead',
  273. });
  274. expect(getSettingsStoreValue).toBe('public-read-value');
  275. // Should not be able to write (requires CreateAdministrator)
  276. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  277. input: {
  278. key: 'rwtest.publicRead',
  279. value: 'unauthorized-public-write',
  280. },
  281. });
  282. expect(setSettingsStoreValue.result).toBe(false);
  283. expect(setSettingsStoreValue.error).toContain('permissions');
  284. });
  285. it('super admin can write', async () => {
  286. await adminClient.asSuperAdmin();
  287. // Should be able to write
  288. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  289. input: {
  290. key: 'rwtest.publicRead',
  291. value: 'super-admin-write-value',
  292. },
  293. });
  294. expect(setSettingsStoreValue.result).toBe(true);
  295. });
  296. });
  297. describe('Read-only fields with permissions', () => {
  298. it('read-only field prevents writes even with correct permissions', async () => {
  299. await adminClient.asUserWithCredentials(admins.readSettingsAdmin.emailAddress, 'test');
  300. // Should be able to read
  301. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  302. key: 'rwtest.readOnlyAccess',
  303. });
  304. expect(getSettingsStoreValue).toBeNull(); // No initial value set for this field
  305. // Should fail to write because field is readonly
  306. const { setSettingsStoreValue } = await adminClient.query(setSettingsStoreValueDocument, {
  307. input: {
  308. key: 'rwtest.readOnlyAccess',
  309. value: 'readonly-attempt',
  310. },
  311. });
  312. expect(setSettingsStoreValue.result).toBe(false);
  313. expect(setSettingsStoreValue.error).toContain('readonly');
  314. });
  315. it('user without read permission cannot read readonly field', async () => {
  316. await adminClient.asUserWithCredentials(admins.authenticatedOnlyAdmin.emailAddress, 'test');
  317. // Should not be able to read (needs ReadSettings)
  318. const { getSettingsStoreValue } = await adminClient.query(getSettingsStoreValueDocument, {
  319. key: 'rwtest.readOnlyAccess',
  320. });
  321. expect(getSettingsStoreValue).toBeNull();
  322. });
  323. });
  324. });
  325. async function createAdminWithPermissions(input: {
  326. adminClient: SimpleGraphQLClient;
  327. name: string;
  328. permissions: Array<Permission | string>;
  329. }) {
  330. const { adminClient, name, permissions } = input;
  331. // All permissions are standard - use the typed mutation
  332. const { createRole } = await adminClient.query(createRoleDocument, {
  333. input: {
  334. code: name,
  335. description: name,
  336. permissions: permissions as Permission[],
  337. },
  338. });
  339. const { createAdministrator } = await adminClient.query(createAdministratorDocument, {
  340. input: {
  341. firstName: name,
  342. lastName: 'LastName',
  343. emailAddress: `${name}@test.com`,
  344. roleIds: [createRole.id],
  345. password: 'test',
  346. },
  347. });
  348. return createAdministrator;
  349. }
  350. async function setupTestAdmins(adminClient: SimpleGraphQLClient) {
  351. const readCatalogAdmin = await createAdminWithPermissions({
  352. adminClient,
  353. name: 'ReadCatalog',
  354. permissions: [Permission.ReadCatalog],
  355. });
  356. const updateCatalogAdmin = await createAdminWithPermissions({
  357. adminClient,
  358. name: 'UpdateCatalog',
  359. permissions: [Permission.UpdateCatalog],
  360. });
  361. const readWriteCatalogAdmin = await createAdminWithPermissions({
  362. adminClient,
  363. name: 'ReadWriteCatalog',
  364. permissions: [Permission.ReadCatalog, Permission.UpdateCatalog],
  365. });
  366. const customReadAdmin = await createAdminWithPermissions({
  367. adminClient,
  368. name: 'CustomRead',
  369. permissions: ['ReadDashboardSavedViews'],
  370. });
  371. const customWriteAdmin = await createAdminWithPermissions({
  372. adminClient,
  373. name: 'CustomWrite',
  374. permissions: ['WriteDashboardSavedViews'],
  375. });
  376. const customReadWriteAdmin = await createAdminWithPermissions({
  377. adminClient,
  378. name: 'CustomReadWrite',
  379. permissions: ['ReadDashboardSavedViews', 'WriteDashboardSavedViews'],
  380. });
  381. const readSettingsAdmin = await createAdminWithPermissions({
  382. adminClient,
  383. name: 'ReadSettings',
  384. permissions: [Permission.ReadSettings],
  385. });
  386. const updateSettingsAdmin = await createAdminWithPermissions({
  387. adminClient,
  388. name: 'UpdateSettings',
  389. permissions: [Permission.UpdateSettings],
  390. });
  391. const authenticatedOnlyAdmin = await createAdminWithPermissions({
  392. adminClient,
  393. name: 'AuthenticatedOnly',
  394. permissions: [Permission.Authenticated],
  395. });
  396. return {
  397. readCatalogAdmin,
  398. updateCatalogAdmin,
  399. readWriteCatalogAdmin,
  400. customReadAdmin,
  401. customWriteAdmin,
  402. customReadWriteAdmin,
  403. readSettingsAdmin,
  404. updateSettingsAdmin,
  405. authenticatedOnlyAdmin,
  406. };
  407. }