bcrypt.hash 不是函数。打字稿和表达错误

bcrypt.hash is not a function. Error in Typescript and express

我正在使用 Typescript、typeorm、express 和 JWT 创建身份验证 API

但是在尝试注册(注册新用户)时,我收到此错误:

TypeError: bcrypt.hash is not a function
at User.<anonymous> (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:25:42)
at Generator.next (<anonymous>)
at file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:16:71
at new Promise (<anonymous>)
at __awaiter (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:12:12)
at User.hashPassword (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:24:16)
at file:///home/e-wave/Desktop/junkies/typechat/public/controllers/control.js:81:23
at Generator.next (<anonymous>)
at fulfilled (file:///home/e-wave/Desktop/junkies/typechat/public/controllers/control.js:4:58)

同样的事情发生在我尝试签署一个 json 网络令牌负载时,当我尝试比较登录时的 bcrypt 哈希时(我分别得到 jwt.sign is not a functionbcrypt.compare is not a function

这是我的 index.ts 文件中的代码:

import "reflect-metadata";
import {createConnection} from "typeorm";
import User from "./entity/User.js";
import express from 'express';
import { router } from "./routes/routes.js";
import morgan from 'morgan';
import {config} from "dotenv"
config(
)

createConnection({
  type:"postgres",
  host: process.env.RDS_HOSTNAME,
  port: 5432,
  username: process.env.RDS_USERNAME,
  password: process.env.RDS_PASSWORD,
  // database: "database-1",  
  entities: [
      User
  ],
  migrations:["migration/*.js"],
  cli:{
    migrationsDir:"migration"

  },
  synchronize: false,
  logging: false
}).then(connection => {
  // here you can start to work with your entities
    const app = express() 
    const PORT:string|number = process.env.PORT||3000
    app.use(express.json())       
    app.use(morgan('dev'))

    app.use('/',router)

    app.listen(PORT, ():void=> console.log(` Database connected and this app is running on port ${PORT}`))
  }).catch(error => console.error(error));

我的 User.ts 文件:

import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, BaseEntity} from "typeorm";
import {isEmail} from "class-validator"
import * as bcrypt from "bcryptjs";

@Entity()
export default class User extends BaseEntity{
    
    @PrimaryGeneratedColumn("uuid")
    id: string;

    
    @Column({type:"varchar",unique:true,nullable:false})
    email: string;

    @Column({nullable:false})
    password: string;

    @CreateDateColumn()
    createdAt:Date

    @UpdateDateColumn()
    updatedAt:Date
    //hash password method before saving to db
    async hashPassword() {
        this.password = await bcrypt.hash(this.password,10);
        return this.password
    }
    
}

我的 control.ts 文件:

import {
  Request,
  Response
} from 'express';
import * as jwt from 'jsonwebtoken';
import {
  validate
} from "class-validator"
import User from '../entity/User.js'
import {
  getManager,
  getRepository
} from "typeorm"
import * as bcrypt from "bcryptjs"

//create an expiry date for our jwt token 1 day
const MAXAGE: number = 24 * 60 * 60 * 1000

class MainControllers {
  //sign a json web token
  static welcome = (req: Request, res: Response) => {
    res.send({
      "text": "message"
    })
  }

  //render login view
  static loginGet = (req: Request, res: Response) => {
    res.render('login')
  }
  //logining with credentials
  static loginPost = async(req: Request, res: Response) => {
    try {
      //login user
      let {
        email,
        password
      } = req.body
      if (!email || !password) return res.status(404).json({
        message: "Not found !"
      })
      let userLoginRepository = getRepository(User)
      //find the user attempting to login via the database
      const findUser = await userLoginRepository.findOne({
        where: {
          email
        }
      })
      if (!findUser) return res.json({
        code: 404,
        message: "user not found in our records"
      })
      //compare passwords
      let auth = await bcrypt.compare(password, findUser.password);
      if (!auth) return res.status(401).json({
        message: "invalid password"
      })
      //else login the user and create the token valid for one day
      const token: string = jwt.sign({
        email
      }, process.env.JWT_SECRET || "", {
        expiresIn: process.env.JWT_EXPIRES
      })
      return res.status(200).json({
        message: "login successfully",
        token
      })

    } catch (err) {
      console.error(err)
      res.status(500).send({
        message: err.message
      })
    }
  }


  //render signup view
  static signUpGet = (req: Request, res: Response) => {
    res.render('signup')
  }

  //signing up with credentials
  static signUpPost = async(req: Request, res: Response) => {
    try {
      let {
        email,
        password
      } = req.body
      const newUser = new User()
      newUser.email = email
      newUser.password = password

      //validate the input fields
      let errors = await validate(newUser)
      console.log("errors are:", errors)
      if (errors.length > 0) return res.status(500).json({
        message: errors
      })
      //generate the token
      const token: string = jwt.sign({
        email
      }, process.env.JWT_SECRET || "", {
        expiresIn: process.env.JWT_EXPIRES
      })
      res.cookie('jwtoken', token, {
        maxAge: MAXAGE,
        signed: true,
        httpOnly: true
      })
      await newUser.hashPassword()
      const dbRepository = getRepository(User)
      await dbRepository.save(newUser)
      return res.status(201).send({
        message: "user created",
        user: newUser,
        token: token
      })
    } catch (err) {
      console.error(err);
      return res.status(500).send({
        message: err.message
      });
    }
  }
}

export default MainControllers;

我的 routes.ts 文件:

import {Router} from 'express';
import MainControllers from '../controllers/control.js';
//import  * as cookieParser from "cookie-parser"
import cookieParser from 'cookie-parser';

export const router = Router()

router.use(cookieParser())

router.get('/welcome',MainControllers.welcome);
router.get('/login',MainControllers.loginGet)
router.post('/login',MainControllers.loginPost);
router.get('/signup',MainControllers.signUpGet)
router.post('/signup',MainControllers.signUpPost)

请问我可能做错了什么?

编辑

这是我的 tsconfig.ts 文件:

{
   "compilerOptions": {
     /* Visit https://aka.ms/tsconfig.json to read more about this file */
 
     /* Basic Options */
     // "incremental": true,                         /* Enable incremental compilation */
     "target": "ES2015",                                /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
     "module": "es2015",                           /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
      "lib": ["ES5","ES2015","ES2016","ES2017","ES2018"],                                   /* Specify library files to be included in the compilation. */
     // "allowJs": true,                             /* Allow javascript files to be compiled. */
     // "checkJs": true,                             /* Report errors in .js files. */
     // "jsx": "preserve",                           /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
     // "declaration": true,                         /* Generates corresponding '.d.ts' file. */
     // "declarationMap": true,                      /* Generates a sourcemap for each corresponding '.d.ts' file. */
     "sourceMap": true,                           /* Generates corresponding '.map' file. */
     // "outFile": "./",                             /* Concatenate and emit output to single file. */
     "outDir": "./public",                              /* Redirect output structure to the directory. */
     "rootDir": "./src",                             /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
     // "composite": true,                           /* Enable project compilation */
     // "tsBuildInfoFile": "./",                     /* Specify file to store incremental compilation information */
     // "removeComments": true,                      /* Do not emit comments to output. */
     // "noEmit": true,                              /* Do not emit outputs. */
     // "importHelpers": true,                       /* Import emit helpers from 'tslib'. */
     // "downlevelIteration": true,                  /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
     // "isolatedModules": true,                     /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
 
     /* Strict Type-Checking Options */
     "strict": true,                                 /* Enable all strict type-checking options. */
     // "noImplicitAny": true,                       /* Raise error on expressions and declarations with an implied 'any' type. */
     // "strictNullChecks": true,                    /* Enable strict null checks. */
     // "strictFunctionTypes": true,                 /* Enable strict checking of function types. */
     // "strictBindCallApply": true,                 /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
     "strictPropertyInitialization": false,        /* Enable strict checking of property initialization in classes. */
     // "noImplicitThis": true,                      /* Raise error on 'this' expressions with an implied 'any' type. */
     // "alwaysStrict": true,                        /* Parse in strict mode and emit "use strict" for each source file. */
 
     /* Additional Checks */
     // "noUnusedLocals": true,                      /* Report errors on unused locals. */
     // "noUnusedParameters": true,                  /* Report errors on unused parameters. */
     // "noImplicitReturns": true,                   /* Report error when not all code paths in function return a value. */
     // "noFallthroughCasesInSwitch": true,          /* Report errors for fallthrough cases in switch statement. */
     // "noUncheckedIndexedAccess": true,            /* Include 'undefined' in index signature results */
     // "noImplicitOverride": true,                  /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
     // "noPropertyAccessFromIndexSignature": true,  /* Require undeclared properties from index signatures to use element accesses. */
 
     /* Module Resolution Options */
     "moduleResolution": "node",                  /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
     "baseUrl": "./",                             /* Base directory to resolve non-absolute module names. */
     // "paths": {},                                 /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
     // "rootDirs": [],                              /* List of root folders whose combined content represents the structure of the project at runtime. */
     // "typeRoots": [],                             /* List of folders to include type definitions from. */
     // "types": [],                                 /* Type declaration files to be included in compilation. */
     "allowSyntheticDefaultImports": true,        /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
     "esModuleInterop": true,                        /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
     // "preserveSymlinks": true,                    /* Do not resolve the real path of symlinks. */
     // "allowUmdGlobalAccess": true,                /* Allow accessing UMD globals from modules. */
 
     /* Source Map Options */
     // "sourceRoot": "",                            /* Specify the location where debugger should locate TypeScript files instead of source locations. */
     // "mapRoot": "",                               /* Specify the location where debugger should locate map files instead of generated locations. */
     // "inlineSourceMap": true,                     /* Emit a single file with source maps instead of having a separate file. */
     // "inlineSources": true,                       /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
 
     /* Experimental Options */
     "experimentalDecorators": true,              /* Enables experimental support for ES7 decorators. */
     "emitDecoratorMetadata": true,               /* Enables experimental support for emitting type metadata for decorators. */
 
     /* Advanced Options */
     "skipLibCheck": true,                           /* Skip type checking of declaration files. */
     "forceConsistentCasingInFileNames": true        /* Disallow inconsistently-cased references to the same file. */
   },
   "include": ["src"]
 }

这也是我的 package.json 文件(以防有任何我应该安装的包):

{
   "name": "typechat",
   "version": "1.0.0",
   "description": "A  chat app built with typescript and mongodb",
   "main": "index.js",
   "author": "E-wave",
   "license": "MIT",
   "type": "module",
   "dependencies": {
      "aws-sdk": "^2.931.0",
      "bcryptjs": "^2.4.3",
      "class-validator": "^0.13.1",
      "cookie-parser": "^1.4.5",
      "dotenv": "^10.0.0",
      "express": "^4.17.1",
      "jsonwebtoken": "^8.5.1",
      "pg": "^8.6.0",
      "reflect-metadata": "^0.1.10",
      "typeorm": "0.2.34"
   },
   "devDependencies": {
      "@types/bcryptjs": "^2.4.2",
      "@types/cookie-parser": "^1.4.2",
      "@types/express": "^4.17.12",
      "@types/jsonwebtoken": "^8.5.2",
      "@types/morgan": "^1.9.2",
      "@types/mysql": "^2.15.18",
      "@types/node": "^8.0.29",
      "morgan": "^1.10.0",
      "ts-node": "3.3.0",
      "typescript": "^4.3.4"
   },
   "scripts": {
      "start": "src/index.ts",
      "dev": "tsc && nodemon public/index.js"
   }
}

这很可能是因为缺少类型或包。

有些包没有开箱即用的 typescript 支持,因此需要使用 Definetly Typed repository.

安装相应的类型

所以在你的情况下,如果你尝试为 bcryptjs 和 jsonwebtoken 安装各自的类型,你的问题可能会得到解决。

$ npm i -D @types/bcryptjs @types/jsonwebtoken

因为这些只在开发中需要,我们可以用 -D 标记它们以仅将包安装为开发 depedencies。

我还建议您查看 NestJS,这是一个用于 NodeJS 的打字稿框架,具有许多不错的功能和方法。

问题是您在 tsconfig.json 文件中使用 "module": "es2015" 设置,在 package.json 中使用 "type": "module"。但是 bcryptjs 是一个 commonjs 模块。因为它不支持 esm 兼容性选项。从 es2015 代码导入时唯一的导出是 default。由于您已经设置了 "allowSytheticDefaultImports": "true",您只需将导入重写为:

...
import bcrypt from 'bcryptjs'
...

bcrypt.hash(...)

...
import { default as bcrypt } from 'bcryptjs'
...

bcrypt.hash(...)
...