authentication-strategy.e2e-spec.ts 15 KB

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