1
0

auth.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import type { Account } from './account/types'
  2. import type { Context } from './types'
  3. import { ObjectId } from 'mongodb'
  4. import type { WithId } from 'mongodb'
  5. import { http } from '@edge/misc-utils'
  6. import jwt from 'jsonwebtoken'
  7. import type { NextFunction, Request, Response } from 'express'
  8. /**
  9. * Authentication context.
  10. * This provides functionality for signing and verifying JWTs using static configuration.
  11. *
  12. * @see https://jwt.io/introduction
  13. */
  14. export type Auth = ReturnType<typeof createAuth>
  15. /** AuthRequest is an Express Request with some additional properties. */
  16. export type AuthRequest = Request & {
  17. /**
  18. * Account document set by authentication middleware.
  19. * If this value exists, then the the request is authenticated with this account.
  20. *
  21. * @see Auth.verifyRequestMiddleware
  22. */
  23. account?: WithId<Account>
  24. }
  25. /**
  26. * An AuthRequestHandler is essentially the same as an Express RequestHandler, but has access to additional request
  27. * properties when `verifyRequest` is used earlier in request processing.
  28. *
  29. * @see AuthRequest
  30. */
  31. export type AuthRequestHandler = (req: AuthRequest, res: Response, next: NextFunction) => void | Promise<void>
  32. /**
  33. * Create an authentication context.
  34. * This provides functionality for signing and verifying JWTs using static configuration.
  35. *
  36. * @see https://jwt.io/introduction
  37. */
  38. function createAuth(ctx: Context) {
  39. const { expiresIn, secret } = ctx.config.auth.jwt
  40. /** Sign a JWT containing an account ID. */
  41. function sign(accountId: ObjectId | string): Promise<string> {
  42. return new Promise((res, rej) => {
  43. jwt.sign({ _id: accountId.toString() }, secret, { expiresIn }, (err, enc) => {
  44. if (err) return rej(err)
  45. if (enc) return res(enc)
  46. rej(new Error('no result'))
  47. })
  48. })
  49. }
  50. /** Verify and decode a JWT containing an account ID. */
  51. function verify(token: string): Promise<ObjectId> {
  52. return new Promise((res, rej) => {
  53. jwt.verify(token, secret, (err, dec) => {
  54. if (err) return rej(err)
  55. try {
  56. const id = new ObjectId((dec as { _id: string })._id)
  57. res(id)
  58. } catch (err) {
  59. rej(err)
  60. }
  61. })
  62. })
  63. }
  64. /**
  65. * Express middleware to verify and decode a JWT provided in an HTTP request's authorization header.
  66. *
  67. * If an account ID is successfully decoded from the JWT, this will attempt to load the account automatically before
  68. * further request processing.
  69. * The account is attached to the request and tacitly available to subsequent request handlers that implement the
  70. * AuthRequestHandler type.
  71. *
  72. * If an invalid token is provided or the account does not exist, an error will be passed along, blocking request
  73. * handling.
  74. */
  75. const verifyRequestMiddleware: AuthRequestHandler = async (req, res, next) => {
  76. // Read header and skip processing if bearer token missing or malformed
  77. const header = req.header('authorization')
  78. if (!header) return next()
  79. if (!/^[Bb]earer /.test(header)) return next()
  80. try {
  81. // Read and verify token
  82. const token = header.substring(7)
  83. const _id = await verify(token)
  84. // Load account
  85. const account = await ctx.model.account.collection.findOne({ _id })
  86. if (!account) return http.unauthorized(res, next)
  87. req.account = account
  88. next()
  89. } catch (err) {
  90. return next(err)
  91. }
  92. }
  93. return { sign, verify, verifyRequestMiddleware }
  94. }
  95. export default createAuth