Browse Source

feat(core): Make password hashing strategy configurable

Closes #1063
Michael Bromley 4 years ago
parent
commit
e5abab0351

+ 30 - 0
packages/core/src/config/auth/bcrypt-password-hashing-strategy.ts

@@ -0,0 +1,30 @@
+import { PasswordHashingStrategy } from './password-hashing-strategy';
+
+const SALT_ROUNDS = 12;
+
+/**
+ * @description
+ * A hashing strategy which uses bcrypt (https://en.wikipedia.org/wiki/Bcrypt) to hash plaintext password strings.
+ *
+ * @docsCategory auth
+ * @since 1.3.0
+ */
+export class BcryptPasswordHashingStrategy implements PasswordHashingStrategy {
+    private bcrypt: any;
+
+    async init() {
+        // The bcrypt lib is lazily loaded so that if we want to run Vendure
+        // in an environment that does not support native Node modules
+        // (such as an online sandbox like Stackblitz) the bcrypt dependency
+        // does not get loaded when linking the source files on startup.
+        this.bcrypt = await import('bcrypt');
+    }
+
+    hash(plaintext: string): Promise<string> {
+        return this.bcrypt.hash(plaintext, SALT_ROUNDS);
+    }
+
+    check(plaintext: string, hash: string): Promise<boolean> {
+        return this.bcrypt.compare(plaintext, hash);
+    }
+}

+ 13 - 0
packages/core/src/config/auth/password-hashing-strategy.ts

@@ -0,0 +1,13 @@
+import { InjectableStrategy } from '../../common/types/injectable-strategy';
+
+/**
+ * @description
+ * Defines how user passwords get hashed when using the {@link NativeAuthenticationStrategy}.
+ *
+ * @docsCategory auth
+ * @since 1.3.0
+ */
+export interface PasswordHashingStrategy extends InjectableStrategy {
+    hash(plaintext: string): Promise<string>;
+    check(plaintext: string, hash: string): Promise<boolean>;
+}

+ 7 - 2
packages/core/src/config/config.module.ts

@@ -60,8 +60,12 @@ export class ConfigModule implements OnApplicationBootstrap, OnApplicationShutdo
             this.configService.assetOptions;
         const { productVariantPriceCalculationStrategy, stockDisplayStrategy } =
             this.configService.catalogOptions;
-        const { adminAuthenticationStrategy, shopAuthenticationStrategy, sessionCacheStrategy } =
-            this.configService.authOptions;
+        const {
+            adminAuthenticationStrategy,
+            shopAuthenticationStrategy,
+            sessionCacheStrategy,
+            passwordHashingStrategy,
+        } = this.configService.authOptions;
         const { taxZoneStrategy } = this.configService.taxOptions;
         const { jobQueueStrategy } = this.configService.jobQueueOptions;
         const {
@@ -81,6 +85,7 @@ export class ConfigModule implements OnApplicationBootstrap, OnApplicationShutdo
             ...adminAuthenticationStrategy,
             ...shopAuthenticationStrategy,
             sessionCacheStrategy,
+            passwordHashingStrategy,
             assetNamingStrategy,
             assetPreviewStrategy,
             assetStorageStrategy,

+ 2 - 0
packages/core/src/config/default-config.ts

@@ -10,6 +10,7 @@ import { InMemoryJobQueueStrategy } from '../job-queue/in-memory-job-queue-strat
 import { DefaultAssetNamingStrategy } from './asset-naming-strategy/default-asset-naming-strategy';
 import { NoAssetPreviewStrategy } from './asset-preview-strategy/no-asset-preview-strategy';
 import { NoAssetStorageStrategy } from './asset-storage-strategy/no-asset-storage-strategy';
+import { BcryptPasswordHashingStrategy } from './auth/bcrypt-password-hashing-strategy';
 import { NativeAuthenticationStrategy } from './auth/native-authentication-strategy';
 import { defaultCollectionFilters } from './catalog/default-collection-filters';
 import { DefaultProductVariantPriceCalculationStrategy } from './catalog/default-product-variant-price-calculation-strategy';
@@ -84,6 +85,7 @@ export const defaultConfig: RuntimeVendureConfig = {
         shopAuthenticationStrategy: [new NativeAuthenticationStrategy()],
         adminAuthenticationStrategy: [new NativeAuthenticationStrategy()],
         customPermissions: [],
+        passwordHashingStrategy: new BcryptPasswordHashingStrategy(),
     },
     catalogOptions: {
         collectionFilters: defaultCollectionFilters,

+ 10 - 1
packages/core/src/config/vendure-config.ts

@@ -13,6 +13,7 @@ import { AssetNamingStrategy } from './asset-naming-strategy/asset-naming-strate
 import { AssetPreviewStrategy } from './asset-preview-strategy/asset-preview-strategy';
 import { AssetStorageStrategy } from './asset-storage-strategy/asset-storage-strategy';
 import { AuthenticationStrategy } from './auth/authentication-strategy';
+import { PasswordHashingStrategy } from './auth/password-hashing-strategy';
 import { CollectionFilter } from './catalog/collection-filter';
 import { ProductVariantPriceCalculationStrategy } from './catalog/product-variant-price-calculation-strategy';
 import { StockDisplayStrategy } from './catalog/stock-display-strategy';
@@ -296,7 +297,7 @@ export interface AuthOptions {
      * `authTokenHeaderKey` in the server's CORS configuration (adding `Access-Control-Expose-Headers: vendure-auth-token`
      * by default).
      *
-     * From v1.2.0 is is possible to specify both methods as a tuple: `['cookie', 'bearer']`.
+     * From v1.2.0 it is possible to specify both methods as a tuple: `['cookie', 'bearer']`.
      *
      * @default 'cookie'
      */
@@ -390,6 +391,14 @@ export interface AuthOptions {
      * @default []
      */
     customPermissions?: PermissionDefinition[];
+    /**
+     * @description
+     * Allows you to customize the way passwords are hashed when using the {@link NativeAuthenticationStrategy}.
+     *
+     * @default BcryptPasswordHashingStrategy
+     * @since 1.3.0
+     */
+    passwordHashingStrategy?: PasswordHashingStrategy;
 }
 
 /**

+ 1 - 1
packages/core/src/connection/transactional-connection.ts

@@ -120,7 +120,7 @@ export class TransactionalConnection {
      * }
      * ```
      *
-     * @since v1.3.0
+     * @since 1.3.0
      */
     async withTransaction<T>(work: (ctx: RequestContext) => Promise<T>): Promise<T>;
     async withTransaction<T>(ctx: RequestContext, work: (ctx: RequestContext) => Promise<T>): Promise<T>;

+ 6 - 5
packages/core/src/service/helpers/password-cipher/password-cipher.ts

@@ -1,18 +1,19 @@
 import { Injectable } from '@nestjs/common';
-import bcrypt from 'bcrypt';
 
-const SALT_ROUNDS = 12;
+import { ConfigService } from '../../../config/config.service';
 
 /**
- * A cipher which uses bcrypt (https://en.wikipedia.org/wiki/Bcrypt) to hash plaintext password strings.
+ * @description
+ * Used in the {@link NativeAuthenticationStrategy} when hashing and checking user passwords.
  */
 @Injectable()
 export class PasswordCipher {
+    constructor(private configService: ConfigService) {}
     hash(plaintext: string): Promise<string> {
-        return bcrypt.hash(plaintext, SALT_ROUNDS);
+        return this.configService.authOptions.passwordHashingStrategy.hash(plaintext);
     }
 
     check(plaintext: string, hash: string): Promise<boolean> {
-        return bcrypt.compare(plaintext, hash);
+        return this.configService.authOptions.passwordHashingStrategy.check(plaintext, hash);
     }
 }