authentication-strategy.e2e-spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { ErrorCode, HistoryEntryType } from '@vendure/common/lib/generated-types';
  3. import { pick } from '@vendure/common/lib/pick';
  4. import { mergeConfig, NativeAuthenticationStrategy } from '@vendure/core';
  5. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  6. import path from 'path';
  7. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  8. import { initialData } from '../../../e2e-common/e2e-initial-data';
  9. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  10. import {
  11. TestAuthenticationStrategy,
  12. TestAuthenticationStrategy2,
  13. TestSSOStrategyAdmin,
  14. TestSSOStrategyShop,
  15. VALID_AUTH_TOKEN,
  16. } from './fixtures/test-authentication-strategies';
  17. import { currentUserFragment, customerFragment } from './graphql/fragments-admin';
  18. import { FragmentOf } from './graphql/graphql-admin';
  19. import {
  20. attemptLoginDocument,
  21. authenticateDocument,
  22. createCustomerDocument,
  23. deleteCustomerDocument,
  24. getCustomerHistoryDocument,
  25. getCustomersDocument,
  26. getCustomerUserAuthDocument,
  27. MeDocument,
  28. } from './graphql/shared-definitions';
  29. import { registerAccountDocument } from './graphql/shop-definitions';
  30. type CurrentUserFragmentType = FragmentOf<typeof currentUserFragment>;
  31. const currentUserGuard: ErrorResultGuard<CurrentUserFragmentType> = createErrorResultGuard(
  32. input => input.identifier != null,
  33. );
  34. type CustomerFragmentType = FragmentOf<typeof customerFragment>;
  35. const customerGuard: ErrorResultGuard<CustomerFragmentType> = createErrorResultGuard(
  36. input => input.emailAddress != null,
  37. );
  38. describe('AuthenticationStrategy', () => {
  39. const { server, adminClient, shopClient } = createTestEnvironment(
  40. mergeConfig(testConfig(), {
  41. authOptions: {
  42. shopAuthenticationStrategy: [
  43. new NativeAuthenticationStrategy(),
  44. new TestAuthenticationStrategy(),
  45. new TestAuthenticationStrategy2(),
  46. new TestSSOStrategyShop(),
  47. ],
  48. adminAuthenticationStrategy: [new NativeAuthenticationStrategy(), new TestSSOStrategyAdmin()],
  49. },
  50. }),
  51. );
  52. beforeAll(async () => {
  53. await server.init({
  54. initialData,
  55. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  56. customerCount: 1,
  57. });
  58. await adminClient.asSuperAdmin();
  59. }, TEST_SETUP_TIMEOUT_MS);
  60. afterAll(async () => {
  61. await server.destroy();
  62. });
  63. describe('external auth', () => {
  64. const userData = {
  65. email: 'test@email.com',
  66. firstName: 'Cixin',
  67. lastName: 'Liu',
  68. };
  69. let newCustomerId: string;
  70. it('fails with a bad token', async () => {
  71. const { authenticate } = await shopClient.query(authenticateDocument, {
  72. input: {
  73. test_strategy: {
  74. token: 'bad-token',
  75. },
  76. },
  77. });
  78. expect(authenticate.message).toBe('The provided credentials are invalid');
  79. expect(authenticate.errorCode).toBe(ErrorCode.INVALID_CREDENTIALS_ERROR);
  80. expect(authenticate.authenticationError).toBe('');
  81. });
  82. it('fails with an expired token', async () => {
  83. const { authenticate } = await shopClient.query(authenticateDocument, {
  84. input: {
  85. test_strategy: {
  86. token: 'expired-token',
  87. },
  88. },
  89. });
  90. expect(authenticate.message).toBe('The provided credentials are invalid');
  91. expect(authenticate.errorCode).toBe(ErrorCode.INVALID_CREDENTIALS_ERROR);
  92. expect(authenticate.authenticationError).toBe('Expired token');
  93. });
  94. it('creates a new Customer with valid token', async () => {
  95. const { customers: before } = await adminClient.query(getCustomersDocument);
  96. expect(before.totalItems).toBe(1);
  97. const { authenticate } = await shopClient.query(authenticateDocument, {
  98. input: {
  99. test_strategy: {
  100. token: VALID_AUTH_TOKEN,
  101. userData,
  102. },
  103. },
  104. });
  105. currentUserGuard.assertSuccess(authenticate);
  106. expect(authenticate.identifier).toEqual(userData.email);
  107. const { customers: after } = await adminClient.query(getCustomersDocument);
  108. expect(after.totalItems).toBe(2);
  109. expect(after.items.map(i => i.emailAddress)).toEqual([
  110. 'hayden.zieme12@hotmail.com',
  111. userData.email,
  112. ]);
  113. newCustomerId = after.items[1].id;
  114. });
  115. it('creates customer history entry', async () => {
  116. const { customer } = await adminClient.query(getCustomerHistoryDocument, {
  117. id: newCustomerId,
  118. });
  119. expect(
  120. customer?.history.items.sort((a, b) => (a.id > b.id ? 1 : -1)).map(pick(['type', 'data'])),
  121. ).toEqual([
  122. {
  123. type: HistoryEntryType.CUSTOMER_REGISTERED,
  124. data: {
  125. strategy: 'test_strategy',
  126. },
  127. },
  128. {
  129. type: HistoryEntryType.CUSTOMER_VERIFIED,
  130. data: {
  131. strategy: 'test_strategy',
  132. },
  133. },
  134. ]);
  135. });
  136. it('user authenticationMethod populated', async () => {
  137. const { customer } = await adminClient.query(getCustomerUserAuthDocument, {
  138. id: newCustomerId,
  139. });
  140. expect(customer?.user?.authenticationMethods.length).toBe(1);
  141. expect(customer?.user?.authenticationMethods[0].strategy).toBe('test_strategy');
  142. });
  143. it('creates authenticated session', async () => {
  144. const { me } = await shopClient.query(MeDocument);
  145. expect(me?.identifier).toBe(userData.email);
  146. });
  147. it('log out', async () => {
  148. await shopClient.asAnonymousUser();
  149. });
  150. it('logging in again re-uses created User & Customer', async () => {
  151. const { authenticate } = await shopClient.query(authenticateDocument, {
  152. input: {
  153. test_strategy: {
  154. token: VALID_AUTH_TOKEN,
  155. userData,
  156. },
  157. },
  158. });
  159. currentUserGuard.assertSuccess(authenticate);
  160. expect(authenticate.identifier).toEqual(userData.email);
  161. const { customers: after } = await adminClient.query(getCustomersDocument);
  162. expect(after.totalItems).toBe(2);
  163. expect(after.items.map(i => i.emailAddress)).toEqual([
  164. 'hayden.zieme12@hotmail.com',
  165. userData.email,
  166. ]);
  167. });
  168. // https://github.com/vendure-ecommerce/vendure/issues/695
  169. it('multiple external auth strategies to not interfere with one another', async () => {
  170. const EXPECTED_CUSTOMERS = [
  171. {
  172. emailAddress: 'hayden.zieme12@hotmail.com',
  173. id: 'T_1',
  174. },
  175. {
  176. emailAddress: 'test@email.com',
  177. id: 'T_2',
  178. },
  179. ];
  180. const { customers: customers1 } = await adminClient.query(getCustomersDocument);
  181. expect(customers1.items).toEqual(EXPECTED_CUSTOMERS);
  182. const { authenticate: auth1 } = await shopClient.query(authenticateDocument, {
  183. input: {
  184. test_strategy2: {
  185. token: VALID_AUTH_TOKEN,
  186. email: userData.email,
  187. },
  188. },
  189. });
  190. currentUserGuard.assertSuccess(auth1);
  191. expect(auth1.identifier).toBe(userData.email);
  192. const { customers: customers2 } = await adminClient.query(getCustomersDocument);
  193. expect(customers2.items).toEqual(EXPECTED_CUSTOMERS);
  194. await shopClient.asAnonymousUser();
  195. const { authenticate: auth2 } = await shopClient.query(authenticateDocument, {
  196. input: {
  197. test_strategy: {
  198. token: VALID_AUTH_TOKEN,
  199. userData,
  200. },
  201. },
  202. });
  203. currentUserGuard.assertSuccess(auth2);
  204. expect(auth2.identifier).toBe(userData.email);
  205. const { customers: customers3 } = await adminClient.query(getCustomersDocument);
  206. expect(customers3.items).toEqual(EXPECTED_CUSTOMERS);
  207. });
  208. it('registerCustomerAccount with external email', async () => {
  209. const successErrorGuard: ErrorResultGuard<{ success: boolean }> = createErrorResultGuard(
  210. input => input.success != null,
  211. );
  212. const { registerCustomerAccount } = await shopClient.query(registerAccountDocument, {
  213. input: {
  214. emailAddress: userData.email,
  215. },
  216. });
  217. successErrorGuard.assertSuccess(registerCustomerAccount);
  218. expect(registerCustomerAccount.success).toBe(true);
  219. const { customer } = await adminClient.query(getCustomerUserAuthDocument, {
  220. id: newCustomerId,
  221. });
  222. expect(customer?.user?.authenticationMethods.length).toBe(3);
  223. expect(customer?.user?.authenticationMethods.map(m => m.strategy)).toEqual([
  224. 'test_strategy',
  225. 'test_strategy2',
  226. 'native',
  227. ]);
  228. const { customer: customer2 } = await adminClient.query(getCustomerHistoryDocument, {
  229. id: newCustomerId,
  230. options: {
  231. skip: 4,
  232. },
  233. });
  234. expect(customer2?.history.items.map(pick(['type', 'data']))).toEqual([
  235. {
  236. type: HistoryEntryType.CUSTOMER_REGISTERED,
  237. data: {
  238. strategy: 'native',
  239. },
  240. },
  241. ]);
  242. });
  243. // https://github.com/vendure-ecommerce/vendure/issues/926
  244. it('Customer and Admin external auth does not reuse same User for different strategies', async () => {
  245. const emailAddress = 'hello@test-domain.com';
  246. await adminClient.asAnonymousUser();
  247. const { authenticate: adminAuth } = await adminClient.query(authenticateDocument, {
  248. input: {
  249. test_sso_strategy_admin: {
  250. email: emailAddress,
  251. },
  252. },
  253. });
  254. currentUserGuard.assertSuccess(adminAuth);
  255. const { authenticate: shopAuth } = await shopClient.query(authenticateDocument, {
  256. input: {
  257. test_sso_strategy_shop: {
  258. email: emailAddress,
  259. },
  260. },
  261. });
  262. currentUserGuard.assertSuccess(shopAuth);
  263. expect(adminAuth.id).not.toBe(shopAuth.id);
  264. });
  265. });
  266. describe('native auth', () => {
  267. const testEmailAddress = 'test-person@testdomain.com';
  268. // https://github.com/vendure-ecommerce/vendure/issues/486#issuecomment-704991768
  269. it('allows login for an email address which is shared by a previously-deleted Customer', async () => {
  270. const { createCustomer: result1 } = await adminClient.query(createCustomerDocument, {
  271. input: {
  272. firstName: 'Test',
  273. lastName: 'Person',
  274. emailAddress: testEmailAddress,
  275. },
  276. password: 'password1',
  277. });
  278. customerGuard.assertSuccess(result1);
  279. await adminClient.query(deleteCustomerDocument, {
  280. id: result1.id,
  281. });
  282. const { createCustomer: result2 } = await adminClient.query(createCustomerDocument, {
  283. input: {
  284. firstName: 'Test',
  285. lastName: 'Person',
  286. emailAddress: testEmailAddress,
  287. },
  288. password: 'password2',
  289. });
  290. customerGuard.assertSuccess(result2);
  291. const { authenticate } = await shopClient.query(authenticateDocument, {
  292. input: {
  293. native: {
  294. username: testEmailAddress,
  295. password: 'password2',
  296. },
  297. },
  298. });
  299. currentUserGuard.assertSuccess(authenticate);
  300. expect(pick(authenticate, ['id', 'identifier'])).toEqual({
  301. id: result2.user!.id,
  302. identifier: result2.emailAddress,
  303. });
  304. });
  305. });
  306. });
  307. describe('No NativeAuthStrategy on Shop API', () => {
  308. const { server, adminClient, shopClient } = createTestEnvironment(
  309. mergeConfig(testConfig(), {
  310. authOptions: {
  311. shopAuthenticationStrategy: [new TestAuthenticationStrategy()],
  312. },
  313. }),
  314. );
  315. beforeAll(async () => {
  316. await server.init({
  317. initialData,
  318. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  319. customerCount: 1,
  320. });
  321. await adminClient.asSuperAdmin();
  322. }, TEST_SETUP_TIMEOUT_MS);
  323. afterAll(async () => {
  324. await server.destroy();
  325. });
  326. // https://github.com/vendure-ecommerce/vendure/issues/2282
  327. it('can log in to Admin API', async () => {
  328. const { login } = await adminClient.query(attemptLoginDocument, {
  329. username: 'superadmin',
  330. password: 'superadmin',
  331. });
  332. currentUserGuard.assertSuccess(login);
  333. expect(login.identifier).toBe('superadmin');
  334. });
  335. });