微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

bcrypt.hash 不是函数 Typescript 和 express 中的错误

如何解决bcrypt.hash 不是函数 Typescript 和 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 Web 令牌有效负载进行签名时,当我尝试在登录时比较 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
      },{
        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"
   }
}

解决方法

问题是您在 "module": "es2015" 文件中使用了 tsconfig.json 设置,在 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(...)
...
,

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

有些软件包没有开箱即用的打字稿支持,因此需要使用 Definetly Typed repository 安装相应的类型。

因此,在您的情况下,如果您尝试为 bcryptjs 和 jsonwebtoken 安装相应的类型,您的问题可能会得到解决。

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

由于这些仅在开发时需要,我们可以使用 -D 标记它们以仅将软件包安装为开发依赖项。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。