authentication-strategy.e2e-spec.ts 13 KB

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