auth.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import type { Context } from './types'
  2. import { ObjectId } from 'mongodb'
  3. import jwt from 'jsonwebtoken'
  4. import type { NextFunction, Request, Response } from 'express'
  5. /**
  6. * Authentication context.
  7. * This provides functionality for signing and verifying JWTs using static configuration.
  8. *
  9. * @see https://jwt.io/introduction
  10. */
  11. export type Auth = ReturnType<typeof createAuth>
  12. /** AuthRequest is an Express Request with some additional properties. */
  13. export type AuthRequest = Request & {
  14. /** Account ID decoded from JWT. */
  15. accountId?: ObjectId
  16. /**
  17. * Indicates whether the request is verified with a JWT.
  18. * This does not necessarily mean the corresponding accountId is internally valid or applicable to the contents of
  19. * the request; only that the JWT was in itself valid.
  20. */
  21. verified?: boolean
  22. }
  23. /**
  24. * An AuthRequestHandler is essentially the same as an Express RequestHandler, but has access to additional request
  25. * properties when `verifyRequest` is used earlier in request processing.
  26. *
  27. * @see AuthRequest
  28. */
  29. export type AuthRequestHandler = (req: AuthRequest, res: Response, next: NextFunction) => void | Promise<void>
  30. /**
  31. * Create an authentication context.
  32. * This provides functionality for signing and verifying JWTs using static configuration.
  33. *
  34. * @see https://jwt.io/introduction
  35. */
  36. function createAuth(ctx: Context) {
  37. const { expiresIn, secret } = ctx.config.auth.jwt
  38. /** Sign a JWT containing an account ID. */
  39. function sign(accountId: ObjectId | string): Promise<string> {
  40. return new Promise((res, rej) => {
  41. jwt.sign({ _id: accountId.toString() }, secret, { expiresIn }, (err, enc) => {
  42. if (err) return rej(err)
  43. if (enc) return res(enc)
  44. rej(new Error('no result'))
  45. })
  46. })
  47. }
  48. /** Verify and decode a JWT containing an account ID. */
  49. function verify(token: string): Promise<ObjectId> {
  50. return new Promise((res, rej) => {
  51. jwt.verify(token, secret, (err, dec) => {
  52. if (err) return rej(err)
  53. try {
  54. const id = new ObjectId((dec as { _id: string })._id)
  55. res(id)
  56. } catch (err) {
  57. rej(err)
  58. }
  59. })
  60. })
  61. }
  62. /**
  63. * Verify and decode a JWT provided in an HTTP request's authorization header.
  64. *
  65. * If the JWT is verified, some additional properties are set on the request object as a side effect.
  66. * These properties are then available to other, subsequent request handlers that use the `AuthRequestHandler` type.
  67. * This function is thus suitable for use in Express middleware.
  68. */
  69. async function verifyRequest(req: Request): Promise<ObjectId | undefined> {
  70. const header = req.header('authorization')
  71. if (!header) return
  72. if (!/^[Bb]earer /.test(header)) return
  73. const token = header.substring(7)
  74. const accountId = await verify(token);
  75. (req as AuthRequest).accountId = accountId;
  76. (req as AuthRequest).verified = true
  77. return accountId
  78. }
  79. return { sign, verify, verifyRequest }
  80. }
  81. export default createAuth