keycloak-authentication-strategy.ts 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { HttpService } from '@nestjs/axios';
  2. import {
  3. AuthenticationStrategy,
  4. ExternalAuthenticationService,
  5. Injector,
  6. Logger,
  7. RequestContext,
  8. Role,
  9. TransactionalConnection,
  10. User,
  11. } from '@vendure/core';
  12. import { DocumentNode } from 'graphql';
  13. import gql from 'graphql-tag';
  14. export type KeycloakAuthData = {
  15. token: string;
  16. };
  17. export type OpenIdUserInfo = {
  18. name: string;
  19. sub: string;
  20. email?: string;
  21. email_verified: boolean;
  22. preferred_username: string;
  23. given_name?: string;
  24. family_name?: string;
  25. };
  26. export class KeycloakAuthenticationStrategy implements AuthenticationStrategy<KeycloakAuthData> {
  27. readonly name = 'keycloak';
  28. private externalAuthenticationService: ExternalAuthenticationService;
  29. private httpService: HttpService;
  30. private connection: TransactionalConnection;
  31. private bearerToken: string;
  32. init(injector: Injector) {
  33. this.externalAuthenticationService = injector.get(ExternalAuthenticationService);
  34. this.httpService = injector.get(HttpService);
  35. this.connection = injector.get(TransactionalConnection);
  36. }
  37. defineInputType(): DocumentNode {
  38. return gql`
  39. input KeycloakAuthInput {
  40. token: String!
  41. }
  42. `;
  43. }
  44. async authenticate(ctx: RequestContext, data: KeycloakAuthData): Promise<User | false> {
  45. let userInfo: OpenIdUserInfo;
  46. this.bearerToken = data.token;
  47. try {
  48. const response = await this.httpService
  49. .get('http://localhost:9000/realms/myrealm/protocol/openid-connect/userinfo', {
  50. headers: {
  51. Authorization: `Bearer ${this.bearerToken}`,
  52. },
  53. })
  54. .toPromise();
  55. userInfo = response?.data;
  56. } catch (e: any) {
  57. Logger.error(e);
  58. return false;
  59. }
  60. if (!userInfo) {
  61. return false;
  62. }
  63. const user = await this.externalAuthenticationService.findAdministratorUser(
  64. ctx,
  65. this.name,
  66. userInfo.sub,
  67. );
  68. if (user) {
  69. return user;
  70. }
  71. const merchantRole = await this.connection.getRepository(ctx, Role).findOne({
  72. where: { code: 'merchant' },
  73. });
  74. if (!merchantRole) {
  75. Logger.error(`Could not find "merchant" role`);
  76. return false;
  77. }
  78. return this.externalAuthenticationService.createAdministratorAndUser(ctx, {
  79. strategy: this.name,
  80. externalIdentifier: userInfo.sub,
  81. identifier: userInfo.preferred_username,
  82. emailAddress: userInfo.email,
  83. firstName: userInfo.given_name ?? userInfo.preferred_username,
  84. lastName: userInfo.family_name ?? userInfo.preferred_username,
  85. roles: [merchantRole],
  86. });
  87. }
  88. }