auth.e2e-spec.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /* tslint:disable:no-non-null-assertion */
  2. import { SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD } from '@vendure/common/lib/shared-constants';
  3. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  4. import { DocumentNode } from 'graphql';
  5. import gql from 'graphql-tag';
  6. import path from 'path';
  7. import { initialData } from '../../../e2e-common/e2e-initial-data';
  8. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  9. import { ProtectedFieldsPlugin, transactions } from './fixtures/test-plugins/with-protected-field-resolver';
  10. import {
  11. AttemptLogin,
  12. CreateAdministrator,
  13. CreateCustomer,
  14. CreateCustomerGroup,
  15. CreateRole,
  16. CurrentUserFragment,
  17. ErrorCode,
  18. GetCustomerList,
  19. GetTaxRates,
  20. Me,
  21. MutationCreateProductArgs,
  22. MutationLoginArgs,
  23. MutationUpdateProductArgs,
  24. Permission,
  25. UpdateTaxRate,
  26. } from './graphql/generated-e2e-admin-types';
  27. import {
  28. ATTEMPT_LOGIN,
  29. CREATE_ADMINISTRATOR,
  30. CREATE_CUSTOMER,
  31. CREATE_CUSTOMER_GROUP,
  32. CREATE_PRODUCT,
  33. CREATE_ROLE,
  34. GET_CUSTOMER_LIST,
  35. GET_PRODUCT_LIST,
  36. GET_TAX_RATES_LIST,
  37. ME,
  38. UPDATE_PRODUCT,
  39. UPDATE_TAX_RATE,
  40. } from './graphql/shared-definitions';
  41. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  42. describe('Authorization & permissions', () => {
  43. const { server, adminClient, shopClient } = createTestEnvironment({
  44. ...testConfig(),
  45. plugins: [ProtectedFieldsPlugin],
  46. });
  47. beforeAll(async () => {
  48. await server.init({
  49. initialData,
  50. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  51. customerCount: 5,
  52. });
  53. await adminClient.asSuperAdmin();
  54. }, TEST_SETUP_TIMEOUT_MS);
  55. afterAll(async () => {
  56. await server.destroy();
  57. });
  58. describe('admin permissions', () => {
  59. describe('Anonymous user', () => {
  60. beforeAll(async () => {
  61. await adminClient.asAnonymousUser();
  62. });
  63. it(
  64. 'me is not permitted',
  65. assertThrowsWithMessage(async () => {
  66. await adminClient.query<Me.Query>(ME);
  67. }, 'You are not currently authorized to perform this action'),
  68. );
  69. it('can attempt login', async () => {
  70. await assertRequestAllowed<MutationLoginArgs>(ATTEMPT_LOGIN, {
  71. username: SUPER_ADMIN_USER_IDENTIFIER,
  72. password: SUPER_ADMIN_USER_PASSWORD,
  73. rememberMe: false,
  74. });
  75. });
  76. });
  77. describe('Customer user', () => {
  78. let customerEmailAddress: string;
  79. beforeAll(async () => {
  80. await adminClient.asSuperAdmin();
  81. const { customers } = await adminClient.query<GetCustomerList.Query>(GET_CUSTOMER_LIST);
  82. customerEmailAddress = customers.items[0].emailAddress;
  83. });
  84. it('cannot login', async () => {
  85. const result = await adminClient.asUserWithCredentials(customerEmailAddress, 'test');
  86. expect(result.errorCode).toBe(ErrorCode.INVALID_CREDENTIALS_ERROR);
  87. });
  88. });
  89. describe('ReadCatalog permission', () => {
  90. beforeAll(async () => {
  91. await adminClient.asSuperAdmin();
  92. const { identifier, password } = await createAdministratorWithPermissions('ReadCatalog', [
  93. Permission.ReadCatalog,
  94. ]);
  95. await adminClient.asUserWithCredentials(identifier, password);
  96. });
  97. it('me returns correct permissions', async () => {
  98. const { me } = await adminClient.query<Me.Query>(ME);
  99. expect(me!.channels[0].permissions).toEqual([
  100. Permission.Authenticated,
  101. Permission.ReadCatalog,
  102. ]);
  103. });
  104. it('can read', async () => {
  105. await assertRequestAllowed(GET_PRODUCT_LIST);
  106. });
  107. it('cannot update', async () => {
  108. await assertRequestForbidden<MutationUpdateProductArgs>(UPDATE_PRODUCT, {
  109. input: {
  110. id: '1',
  111. translations: [],
  112. },
  113. });
  114. });
  115. it('cannot create', async () => {
  116. await assertRequestForbidden<MutationCreateProductArgs>(CREATE_PRODUCT, {
  117. input: {
  118. translations: [],
  119. },
  120. });
  121. });
  122. });
  123. describe('CRUD on Customers permissions', () => {
  124. beforeAll(async () => {
  125. await adminClient.asSuperAdmin();
  126. const { identifier, password } = await createAdministratorWithPermissions('CRUDCustomer', [
  127. Permission.CreateCustomer,
  128. Permission.ReadCustomer,
  129. Permission.UpdateCustomer,
  130. Permission.DeleteCustomer,
  131. ]);
  132. await adminClient.asUserWithCredentials(identifier, password);
  133. });
  134. it('me returns correct permissions', async () => {
  135. const { me } = await adminClient.query<Me.Query>(ME);
  136. expect(me!.channels[0].permissions).toEqual([
  137. Permission.Authenticated,
  138. Permission.CreateCustomer,
  139. Permission.ReadCustomer,
  140. Permission.UpdateCustomer,
  141. Permission.DeleteCustomer,
  142. ]);
  143. });
  144. it('can create', async () => {
  145. await assertRequestAllowed(
  146. gql(`mutation CanCreateCustomer($input: CreateCustomerInput!) {
  147. createCustomer(input: $input) {
  148. ... on Customer {
  149. id
  150. }
  151. }
  152. }
  153. `),
  154. { input: { emailAddress: '', firstName: '', lastName: '' } },
  155. );
  156. });
  157. it('can read', async () => {
  158. await assertRequestAllowed(gql`
  159. query GetCustomerCount {
  160. customers {
  161. totalItems
  162. }
  163. }
  164. `);
  165. });
  166. });
  167. });
  168. describe('administrator and customer users with the same email address', () => {
  169. const emailAddress = 'same-email@test.com';
  170. const adminPassword = 'admin-password';
  171. const customerPassword = 'customer-password';
  172. const loginErrorGuard: ErrorResultGuard<CurrentUserFragment> = createErrorResultGuard(
  173. input => !!input.identifier,
  174. );
  175. beforeAll(async () => {
  176. await adminClient.asSuperAdmin();
  177. await adminClient.query<CreateAdministrator.Mutation, CreateAdministrator.Variables>(
  178. CREATE_ADMINISTRATOR,
  179. {
  180. input: {
  181. emailAddress,
  182. firstName: 'First',
  183. lastName: 'Last',
  184. password: adminPassword,
  185. roleIds: ['1'],
  186. },
  187. },
  188. );
  189. await adminClient.query<CreateCustomer.Mutation, CreateCustomer.Variables>(CREATE_CUSTOMER, {
  190. input: {
  191. emailAddress,
  192. firstName: 'First',
  193. lastName: 'Last',
  194. },
  195. password: customerPassword,
  196. });
  197. });
  198. beforeEach(async () => {
  199. await adminClient.asAnonymousUser();
  200. await shopClient.asAnonymousUser();
  201. });
  202. it('can log in as an administrator', async () => {
  203. const loginResult = await adminClient.query<AttemptLogin.Mutation, AttemptLogin.Variables>(
  204. ATTEMPT_LOGIN,
  205. {
  206. username: emailAddress,
  207. password: adminPassword,
  208. },
  209. );
  210. loginErrorGuard.assertSuccess(loginResult.login);
  211. expect(loginResult.login.identifier).toEqual(emailAddress);
  212. });
  213. it('can log in as a customer', async () => {
  214. const loginResult = await shopClient.query<AttemptLogin.Mutation, AttemptLogin.Variables>(
  215. ATTEMPT_LOGIN,
  216. {
  217. username: emailAddress,
  218. password: customerPassword,
  219. },
  220. );
  221. loginErrorGuard.assertSuccess(loginResult.login);
  222. expect(loginResult.login.identifier).toEqual(emailAddress);
  223. });
  224. it('cannot log in as an administrator using a customer password', async () => {
  225. const loginResult = await adminClient.query<AttemptLogin.Mutation, AttemptLogin.Variables>(
  226. ATTEMPT_LOGIN,
  227. {
  228. username: emailAddress,
  229. password: customerPassword,
  230. },
  231. );
  232. loginErrorGuard.assertErrorResult(loginResult.login);
  233. expect(loginResult.login.errorCode).toEqual(ErrorCode.INVALID_CREDENTIALS_ERROR);
  234. });
  235. it('cannot log in as a customer using an administrator password', async () => {
  236. const loginResult = await shopClient.query<AttemptLogin.Mutation, AttemptLogin.Variables>(
  237. ATTEMPT_LOGIN,
  238. {
  239. username: emailAddress,
  240. password: adminPassword,
  241. },
  242. );
  243. loginErrorGuard.assertErrorResult(loginResult.login);
  244. expect(loginResult.login.errorCode).toEqual(ErrorCode.INVALID_CREDENTIALS_ERROR);
  245. });
  246. });
  247. describe('protected field resolvers', () => {
  248. let readCatalogAdmin: { identifier: string; password: string };
  249. let transactionsAdmin: { identifier: string; password: string };
  250. const GET_PRODUCT_WITH_TRANSACTIONS = `
  251. query GetProductWithTransactions($id: ID!) {
  252. product(id: $id) {
  253. id
  254. transactions {
  255. id
  256. amount
  257. description
  258. }
  259. }
  260. }
  261. `;
  262. beforeAll(async () => {
  263. await adminClient.asSuperAdmin();
  264. transactionsAdmin = await createAdministratorWithPermissions('Transactions', [
  265. Permission.ReadCatalog,
  266. transactions.Permission,
  267. ]);
  268. readCatalogAdmin = await createAdministratorWithPermissions('ReadCatalog', [
  269. Permission.ReadCatalog,
  270. ]);
  271. });
  272. it('protected field not resolved without permissions', async () => {
  273. await adminClient.asUserWithCredentials(readCatalogAdmin.identifier, readCatalogAdmin.password);
  274. try {
  275. const status = await adminClient.query(gql(GET_PRODUCT_WITH_TRANSACTIONS), { id: 'T_1' });
  276. fail(`Should have thrown`);
  277. } catch (e) {
  278. expect(getErrorCode(e)).toBe('FORBIDDEN');
  279. }
  280. });
  281. it('protected field is resolved with permissions', async () => {
  282. await adminClient.asUserWithCredentials(transactionsAdmin.identifier, transactionsAdmin.password);
  283. const { product } = await adminClient.query(gql(GET_PRODUCT_WITH_TRANSACTIONS), { id: 'T_1' });
  284. expect(product.id).toBe('T_1');
  285. expect(product.transactions).toEqual([
  286. { id: 'T_1', amount: 100, description: 'credit' },
  287. { id: 'T_2', amount: -50, description: 'debit' },
  288. ]);
  289. });
  290. // https://github.com/vendure-ecommerce/vendure/issues/730
  291. it('protects against deep query data leakage', async () => {
  292. await adminClient.asSuperAdmin();
  293. const { createCustomerGroup } = await adminClient.query<
  294. CreateCustomerGroup.Mutation,
  295. CreateCustomerGroup.Variables
  296. >(CREATE_CUSTOMER_GROUP, {
  297. input: {
  298. name: 'Test group',
  299. customerIds: ['T_1', 'T_2', 'T_3', 'T_4'],
  300. },
  301. });
  302. const taxRateName = `Standard Tax ${initialData.defaultZone}`;
  303. const { taxRates } = await adminClient.query<GetTaxRates.Query, GetTaxRates.Variables>(
  304. GET_TAX_RATES_LIST,
  305. {
  306. options: {
  307. filter: {
  308. name: { eq: taxRateName },
  309. },
  310. },
  311. },
  312. );
  313. const standardTax = taxRates.items[0];
  314. expect(standardTax.name).toBe(taxRateName);
  315. await adminClient.query<UpdateTaxRate.Mutation, UpdateTaxRate.Variables>(UPDATE_TAX_RATE, {
  316. input: {
  317. id: standardTax.id,
  318. customerGroupId: createCustomerGroup.id,
  319. },
  320. });
  321. try {
  322. const status = await shopClient.query(
  323. gql(`
  324. query DeepFieldResolutionTestQuery{
  325. product(id: "T_1") {
  326. variants {
  327. taxRateApplied {
  328. customerGroup {
  329. customers {
  330. items {
  331. id
  332. emailAddress
  333. }
  334. }
  335. }
  336. }
  337. }
  338. }
  339. }`),
  340. { id: 'T_1' },
  341. );
  342. fail(`Should have thrown`);
  343. } catch (e) {
  344. expect(getErrorCode(e)).toBe('FORBIDDEN');
  345. }
  346. });
  347. });
  348. async function assertRequestAllowed<V>(operation: DocumentNode, variables?: V) {
  349. try {
  350. const status = await adminClient.queryStatus(operation, variables);
  351. expect(status).toBe(200);
  352. } catch (e) {
  353. const errorCode = getErrorCode(e);
  354. if (!errorCode) {
  355. fail(`Unexpected failure: ${e}`);
  356. } else {
  357. fail(`Operation should be allowed, got status ${getErrorCode(e)}`);
  358. }
  359. }
  360. }
  361. async function assertRequestForbidden<V>(operation: DocumentNode, variables: V) {
  362. try {
  363. const status = await adminClient.query(operation, variables);
  364. fail(`Should have thrown`);
  365. } catch (e) {
  366. expect(getErrorCode(e)).toBe('FORBIDDEN');
  367. }
  368. }
  369. function getErrorCode(err: any): string {
  370. return err.response.errors[0].extensions.code;
  371. }
  372. async function createAdministratorWithPermissions(
  373. code: string,
  374. permissions: Permission[],
  375. ): Promise<{ identifier: string; password: string }> {
  376. const roleResult = await adminClient.query<CreateRole.Mutation, CreateRole.Variables>(CREATE_ROLE, {
  377. input: {
  378. code,
  379. description: '',
  380. permissions,
  381. },
  382. });
  383. const role = roleResult.createRole;
  384. const identifier = `${code}@${Math.random().toString(16).substr(2, 8)}`;
  385. const password = `test`;
  386. const adminResult = await adminClient.query<
  387. CreateAdministrator.Mutation,
  388. CreateAdministrator.Variables
  389. >(CREATE_ADMINISTRATOR, {
  390. input: {
  391. emailAddress: identifier,
  392. firstName: code,
  393. lastName: 'Admin',
  394. password,
  395. roleIds: [role.id],
  396. },
  397. });
  398. const admin = adminResult.createAdministrator;
  399. return {
  400. identifier,
  401. password,
  402. };
  403. }
  404. });