Source

adminjs-passwords/src/passwords.feature.ts

  1. import AdminJS, { buildFeature, Before, ActionResponse, After, FeatureType } from 'adminjs'
  2. /**
  3. * Hashing function used to convert the password
  4. *
  5. * @alias HashingFunction
  6. * @memberof module:@adminjs/passwords
  7. * @returns {Promise<string> | string}
  8. */
  9. export type HashingFunction = (
  10. /**
  11. * Password which should be hashed
  12. */
  13. password: string
  14. ) => (Promise<string> | string)
  15. /**
  16. * Options passed to {@link module:@adminjs/passwords PasswordsFeature}
  17. *
  18. * @alias PasswordsOptions
  19. * @memberof module:@adminjs/passwords
  20. */
  21. export type PasswordsOptions = {
  22. /**
  23. * Names of the properties used by the feature
  24. */
  25. properties?: {
  26. /**
  27. * Virtual property which will be seen by end user. Its value is not stored in the database.
  28. * Default to `password`
  29. */
  30. password?: string,
  31. /**
  32. * Property where encrypted password will be stored. Default to `encryptedPassword`
  33. */
  34. encryptedPassword?: string,
  35. },
  36. /**
  37. * Function used to hash the password. You can pass function from the external library
  38. * Example using [Argon2](https://www.npmjs.com/package/argon2).: `hash: argon2.hash`
  39. *
  40. */
  41. hash: HashingFunction
  42. }
  43. export type Custom = {
  44. [T in keyof NonNullable<PasswordsOptions['properties']>]: NonNullable<T>
  45. }
  46. const editComponent = AdminJS.bundle('../components/edit')
  47. const passwordsFeature = (options?: PasswordsOptions): FeatureType => {
  48. const passwordProperty = options?.properties?.password || 'password'
  49. const encryptedPasswordProperty = options?.properties?.encryptedPassword || 'encryptedPassword'
  50. const { hash } = options || {}
  51. if (!hash) {
  52. throw new Error('You have to pass "hash" option in "PasswordOptions" of "passwordsFeature"')
  53. }
  54. const encryptPassword: Before = async (request) => {
  55. const { method } = request
  56. const { [passwordProperty]: newPassword, ...rest } = request.payload || {}
  57. if (method === 'post' && newPassword) {
  58. return {
  59. ...request,
  60. payload: {
  61. ...rest,
  62. [encryptedPasswordProperty]: await hash(newPassword),
  63. },
  64. }
  65. }
  66. return request
  67. }
  68. const movePasswordErrors: After<ActionResponse> = async (response) => {
  69. if (response.record
  70. && response.record.errors
  71. && response.record.errors[encryptedPasswordProperty]) {
  72. response.record.errors[passwordProperty] = response.record.errors[encryptedPasswordProperty]
  73. }
  74. return response
  75. }
  76. return buildFeature({
  77. properties: {
  78. [passwordProperty]: {
  79. custom: {
  80. password: passwordProperty,
  81. encryptedPassword: encryptedPasswordProperty,
  82. } as Custom,
  83. isVisible: { filter: false, show: false, edit: true, list: false },
  84. components: {
  85. edit: editComponent,
  86. },
  87. },
  88. },
  89. actions: {
  90. edit: {
  91. before: [encryptPassword],
  92. after: [movePasswordErrors],
  93. },
  94. new: {
  95. before: [encryptPassword],
  96. after: [movePasswordErrors],
  97. },
  98. },
  99. })
  100. }
  101. export default passwordsFeature