@nixxie-cms/core 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +2 -2
  2. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.cjs.js +4 -4
  3. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.esm.js +4 -4
  4. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.cjs.js +2 -2
  5. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.esm.js +2 -2
  6. package/context/dist/nixxie-cms-core-context.cjs.js +2 -2
  7. package/context/dist/nixxie-cms-core-context.esm.js +2 -2
  8. package/dist/{CreateItemDialog-33335548.esm.js → CreateItemDialog-7008b050.esm.js} +1 -1
  9. package/dist/{CreateItemDialog-56cf59b7.cjs.js → CreateItemDialog-a0cab315.cjs.js} +1 -1
  10. package/dist/{PageContainer-7db73317.esm.js → PageContainer-5ae731cc.esm.js} +25 -18
  11. package/dist/{PageContainer-27c27f10.cjs.js → PageContainer-abd7159f.cjs.js} +25 -18
  12. package/dist/{admin-meta-graphql-6f7f5331.esm.js → admin-meta-graphql-0e6e606e.esm.js} +1 -1
  13. package/dist/{admin-meta-graphql-c8f926e9.cjs.js → admin-meta-graphql-306c224a.cjs.js} +1 -1
  14. package/dist/{context-3132c3ed.esm.js → context-af9957ed.esm.js} +2 -2
  15. package/dist/{context-e7a45152.cjs.js → context-b5204629.cjs.js} +2 -2
  16. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  17. package/dist/declarations/src/admin-ui/components/PageContainer.d.ts.map +1 -1
  18. package/dist/declarations/src/helpers.d.ts.map +1 -1
  19. package/dist/declarations/src/index.d.ts +1 -0
  20. package/dist/declarations/src/index.d.ts.map +1 -1
  21. package/dist/declarations/src/internal-unstable/admin-ui/id-field-view.d.ts.map +1 -0
  22. package/dist/declarations/src/internal-unstable/admin-ui/pages/App/index.d.ts.map +1 -0
  23. package/dist/declarations/src/internal-unstable/admin-ui/pages/CreateItemPage/index.d.ts.map +1 -0
  24. package/dist/declarations/src/internal-unstable/admin-ui/pages/HomePage/index.d.ts.map +1 -0
  25. package/dist/declarations/src/internal-unstable/admin-ui/pages/ItemPage/index.d.ts.map +1 -0
  26. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -0
  27. package/dist/declarations/src/internal-unstable/admin-ui/pages/NoAccessPage/index.d.ts.map +1 -0
  28. package/dist/declarations/src/internal-unstable/artifacts.d.ts.map +1 -0
  29. package/dist/declarations/src/lib/core/initialise-lists.d.ts +1 -1
  30. package/dist/declarations/src/schema.d.ts.map +1 -1
  31. package/dist/declarations/src/types/config/index.d.ts +60 -1
  32. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  33. package/dist/declarations/src/types/config/lists.d.ts +4 -4
  34. package/dist/declarations/src/types/context.d.ts +150 -0
  35. package/dist/declarations/src/types/context.d.ts.map +1 -1
  36. package/dist/declarations/src/types/next-fields.d.ts +1 -1
  37. package/dist/{express-e9ed9a7d.cjs.js → express-455ae20c.cjs.js} +1 -1
  38. package/dist/{express-6743b918.esm.js → express-7559ca2d.esm.js} +1 -1
  39. package/dist/{index-ac01583b.cjs.js → index-89635494.cjs.js} +4 -4
  40. package/dist/{index-24b78415.esm.js → index-baa799e0.esm.js} +4 -4
  41. package/dist/nixxie-cms-core.cjs.js +104 -77
  42. package/dist/nixxie-cms-core.esm.js +104 -77
  43. package/dist/{non-null-graphql-5315718c.esm.js → non-null-graphql-a84ed64d.esm.js} +1 -1
  44. package/dist/{non-null-graphql-17b83ddc.cjs.js → non-null-graphql-add6bb3d.cjs.js} +1 -1
  45. package/dist/{resolve-hooks-66fe8a8e.cjs.js → resolve-hooks-165a9ce2.cjs.js} +1 -1
  46. package/dist/{resolve-hooks-17aafd37.esm.js → resolve-hooks-6813a045.esm.js} +2 -2
  47. package/dist/{system-dfec2f0a.esm.js → system-03e49e4f.esm.js} +8 -4
  48. package/dist/{system-48c5f6df.cjs.js → system-a321642d.cjs.js} +8 -4
  49. package/dist/{useFilter-0b5a1ee6.esm.js → useFilter-9b6db1f9.esm.js} +1 -1
  50. package/dist/{useFilter-1a4e6900.cjs.js → useFilter-acc9d413.cjs.js} +1 -1
  51. package/fields/dist/nixxie-cms-core-fields.cjs.js +16 -16
  52. package/fields/dist/nixxie-cms-core-fields.esm.js +17 -17
  53. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +3 -3
  54. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +3 -3
  55. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.cjs.js +1 -1
  56. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.esm.js +1 -1
  57. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +3 -3
  58. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +3 -3
  59. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.cjs.js +4 -4
  60. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.esm.js +4 -4
  61. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.cjs.js +1 -1
  62. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.esm.js +1 -1
  63. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.cjs.js +1 -1
  64. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.esm.js +1 -1
  65. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.d.ts +2 -0
  66. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.js +244 -0
  67. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.esm.js +235 -0
  68. package/internal-unstable/admin-ui/id-field-view/package.json +4 -0
  69. package/internal-unstable/admin-ui/next-config/package.json +4 -0
  70. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.d.ts +2 -0
  71. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.js +59 -0
  72. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.esm.js +55 -0
  73. package/internal-unstable/admin-ui/pages/App/package.json +4 -0
  74. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.d.ts +2 -0
  75. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.js +116 -0
  76. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.esm.js +112 -0
  77. package/internal-unstable/admin-ui/pages/CreateItemPage/package.json +4 -0
  78. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.d.ts +2 -0
  79. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.js +336 -0
  80. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.esm.js +332 -0
  81. package/internal-unstable/admin-ui/pages/HomePage/package.json +4 -0
  82. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.d.ts +2 -0
  83. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.js +463 -0
  84. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.esm.js +455 -0
  85. package/internal-unstable/admin-ui/pages/ItemPage/package.json +4 -0
  86. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.d.ts +2 -0
  87. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.js +1195 -0
  88. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.esm.js +1187 -0
  89. package/internal-unstable/admin-ui/pages/ListPage/package.json +4 -0
  90. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.d.ts +2 -0
  91. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.js +40 -0
  92. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.esm.js +35 -0
  93. package/internal-unstable/admin-ui/pages/NoAccessPage/package.json +4 -0
  94. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.d.ts +2 -0
  95. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +51 -0
  96. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +38 -0
  97. package/internal-unstable/artifacts/package.json +4 -0
  98. package/package.json +44 -44
  99. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +44 -15
  100. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +44 -15
  101. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +3 -3
  102. package/scripts/dist/nixxie-cms-core-scripts.esm.js +3 -3
  103. package/src/admin-ui/admin-meta-graphql.ts +168 -168
  104. package/src/admin-ui/components/CommandPalette.tsx +433 -431
  105. package/src/admin-ui/components/Navigation.tsx +389 -385
  106. package/src/admin-ui/components/PageContainer.tsx +311 -310
  107. package/src/admin-ui/components/WelcomeDialog.tsx +1 -1
  108. package/src/admin-ui/context.tsx +338 -338
  109. package/src/admin-ui/templates/app.ts +60 -60
  110. package/src/admin-ui/templates/create-item.ts +5 -5
  111. package/src/admin-ui/templates/home.ts +2 -2
  112. package/src/admin-ui/templates/item.tsx +5 -5
  113. package/src/admin-ui/templates/list.tsx +5 -5
  114. package/src/admin-ui/templates/next-config.ts +29 -0
  115. package/src/admin-ui/templates/no-access.ts +7 -7
  116. package/src/fields/types/bigInt/index.ts +181 -181
  117. package/src/fields/types/bytes/index.ts +275 -275
  118. package/src/fields/types/calendarDay/index.ts +194 -194
  119. package/src/fields/types/checkbox/index.ts +76 -76
  120. package/src/fields/types/decimal/index.ts +182 -182
  121. package/src/fields/types/file/index.ts +168 -168
  122. package/src/fields/types/float/index.ts +133 -133
  123. package/src/fields/types/image/index.ts +244 -244
  124. package/src/fields/types/integer/index.ts +156 -156
  125. package/src/fields/types/json/index.ts +77 -77
  126. package/src/fields/types/multiselect/index.ts +212 -212
  127. package/src/fields/types/password/index.ts +241 -241
  128. package/src/fields/types/relationship/index.ts +381 -381
  129. package/src/fields/types/relationship/views/RelationshipTable.tsx +190 -190
  130. package/src/fields/types/select/index.ts +226 -226
  131. package/src/fields/types/text/index.ts +207 -207
  132. package/src/fields/types/timestamp/index.ts +116 -116
  133. package/src/fields/types/virtual/index.ts +108 -108
  134. package/src/helpers.ts +342 -316
  135. package/src/index.ts +4 -0
  136. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.tsx +167 -167
  137. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.tsx +22 -22
  138. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.tsx +71 -71
  139. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.tsx +333 -333
  140. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/common.tsx +358 -358
  141. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.tsx +483 -483
  142. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/FilterAdd.tsx +221 -221
  143. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/PaginationControls.tsx +170 -170
  144. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/Tag.tsx +72 -72
  145. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.tsx +1006 -1006
  146. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.tsx +24 -24
  147. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.ts +5 -5
  148. package/src/lib/context/createContext.ts +165 -161
  149. package/src/lib/core/initialise-lists.ts +1097 -1097
  150. package/src/lib/id-field.ts +214 -214
  151. package/src/lib/telemetry.ts +342 -342
  152. package/src/schema.ts +237 -233
  153. package/src/scripts/telemetry.ts +1 -1
  154. package/src/types/config/index.ts +400 -333
  155. package/src/types/config/lists.ts +4 -4
  156. package/src/types/context.ts +700 -530
  157. package/src/types/next-fields.ts +499 -499
  158. package/src/types/telemetry.ts +51 -51
  159. package/tests/telemetry.test.ts +361 -361
  160. package/CHANGELOG.md +0 -3158
  161. package/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view/package.json +0 -4
  162. package/___internal-do-not-use-will-break-in-patch/admin-ui/next-config/package.json +0 -4
  163. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/package.json +0 -4
  164. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/package.json +0 -4
  165. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/package.json +0 -4
  166. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/package.json +0 -4
  167. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/package.json +0 -4
  168. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/package.json +0 -4
  169. package/___internal-do-not-use-will-break-in-patch/artifacts/package.json +0 -4
  170. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.d.ts.map +0 -1
  171. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/index.d.ts.map +0 -1
  172. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/index.d.ts.map +0 -1
  173. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/index.d.ts.map +0 -1
  174. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.d.ts.map +0 -1
  175. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.d.ts.map +0 -1
  176. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/index.d.ts.map +0 -1
  177. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/artifacts.d.ts.map +0 -1
  178. /package/dist/{common-1a350e11.cjs.js → common-5933f758.cjs.js} +0 -0
  179. /package/dist/{common-29fc82e6.esm.js → common-ea5c441a.esm.js} +0 -0
  180. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.d.ts +0 -0
  181. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.d.ts +0 -0
  182. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.d.ts +0 -0
  183. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.d.ts +0 -0
  184. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.d.ts +0 -0
  185. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.d.ts +0 -0
  186. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.d.ts +0 -0
  187. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.d.ts +0 -0
@@ -1,241 +1,241 @@
1
- import bcryptjs from 'bcryptjs'
2
- // @ts-expect-error
3
- import dumbPasswords from 'dumb-passwords'
4
- import { userInputError } from '../../../lib/core/graphql-errors'
5
- import {
6
- type BaseListTypeInfo,
7
- type CommonFieldConfig,
8
- type FieldTypeFunc,
9
- fieldType,
10
- } from '../../../types'
11
- import { g } from '../../..'
12
- import { makeValidateHook, defaultIsRequired } from '../../non-null-graphql'
13
- import { isObjectType, type GraphQLSchema } from 'graphql'
14
- import type { InferValueFromInputType } from '@graphql-ts/schema'
15
- import type { controller } from './views'
16
-
17
- type FieldTypeInfo = {
18
- item: string | null
19
- inputs: {
20
- where: InferValueFromInputType<typeof PasswordFilter> | undefined
21
- create: string | null | undefined
22
- update: string | null | undefined
23
- uniqueWhere: never
24
- orderBy: never
25
- }
26
- prisma: {
27
- create: string | null | undefined
28
- update: string | null | undefined
29
- }
30
- }
31
-
32
- export type PasswordFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
33
- ListTypeInfo,
34
- FieldTypeInfo
35
- > & {
36
- validation?: {
37
- isRequired?: boolean
38
- rejectCommon?: boolean
39
- match?: { regex: RegExp; explanation?: string }
40
- length?: {
41
- /** @default 8 */
42
- min?: number
43
- max?: number
44
- }
45
- }
46
- db?: {
47
- isNullable?: boolean
48
- map?: string
49
- extendPrismaSchema?: (field: string) => string
50
- }
51
- kdf?: KDF
52
- }
53
-
54
- type KDF = {
55
- compare(preImage: string, hash: string): Promise<boolean>
56
- hash(preImage: string): Promise<string>
57
- }
58
-
59
- const PasswordState = g.object<{ isSet: boolean }>()({
60
- name: 'PasswordState',
61
- fields: {
62
- isSet: g.field({ type: g.nonNull(g.Boolean) }),
63
- },
64
- })
65
-
66
- const PasswordFilter = g.inputObject({
67
- name: 'PasswordFilter',
68
- fields: {
69
- isSet: g.arg({ type: g.nonNull(g.Boolean) }),
70
- },
71
- })
72
-
73
- export function password<ListTypeInfo extends BaseListTypeInfo>(
74
- config: PasswordFieldConfig<ListTypeInfo> = {}
75
- ): FieldTypeFunc<ListTypeInfo> {
76
- const {
77
- kdf = {
78
- hash: secret => {
79
- // note this is slightly different to checking .length > 72 because
80
- // bcrypt will truncate to 72 bytes after utf8 encoding
81
- // not JS string length which may be different since that's the utf16 length
82
- // (though using characters in the error message makes sense since users shouldn't have to think about bytes and it aligns with the validation messages below)
83
- if (bcryptjs.truncates(secret))
84
- throw new Error('value must be no longer than 72 characters')
85
- return bcryptjs.hash(secret, 10)
86
- },
87
- compare: (secret, hash) => bcryptjs.compare(secret, hash),
88
- },
89
- validation = {},
90
- } = config
91
- const { isRequired = false, rejectCommon = false, match, length: { max } = {} } = validation
92
- const min = isRequired ? (validation.length?.min ?? 8) : validation.length?.min
93
-
94
- return meta => {
95
- if ((config as any).isIndexed === 'unique') {
96
- throw Error("isIndexed: 'unique' is not a supported option for field type password")
97
- }
98
- if (min !== undefined && (!Number.isInteger(min) || min < 0)) {
99
- throw new Error(
100
- `${meta.listKey}.${meta.fieldKey} specifies validation.length.min: ${min} but it must be a positive integer`
101
- )
102
- }
103
- if (max !== undefined && (!Number.isInteger(max) || max < 0)) {
104
- throw new Error(
105
- `${meta.listKey}.${meta.fieldKey} specifies validation.length.max: ${max} but it must be a positive integer`
106
- )
107
- }
108
- if (isRequired && min !== undefined && min === 0) {
109
- throw new Error(
110
- `${meta.listKey}.${meta.fieldKey} specifies validation.isRequired: true and validation.length.min: 0, this is not allowed because validation.isRequired implies at least a min length of 1`
111
- )
112
- }
113
- if (isRequired && max !== undefined && max === 0) {
114
- throw new Error(
115
- `${meta.listKey}.${meta.fieldKey} specifies validation.isRequired: true and validation.length.max: 0, this is not allowed because validation.isRequired implies at least a max length of 1`
116
- )
117
- }
118
- if (min !== undefined && max !== undefined && min > max) {
119
- throw new Error(
120
- `${meta.listKey}.${meta.fieldKey} specifies a validation.length.max that is less than the validation.length.min, and therefore has no valid options`
121
- )
122
- }
123
-
124
- function inputResolver(val: string | null | undefined) {
125
- if (val == null) return val
126
- return kdf.hash(val)
127
- }
128
-
129
- const hasAdditionalValidation = match || rejectCommon || min !== undefined || max !== undefined
130
- const { mode, validate } = makeValidateHook(
131
- meta,
132
- config,
133
- hasAdditionalValidation
134
- ? ({ inputData, operation, addValidationError }) => {
135
- if (operation === 'delete') return
136
-
137
- const value = inputData[meta.fieldKey] // we use inputData, as resolveData is hashed
138
- if (value != null) {
139
- if (min !== undefined && value.length < min) {
140
- if (min === 1) {
141
- addValidationError(`value must not be empty`)
142
- } else {
143
- addValidationError(`value must be at least ${min} characters long`)
144
- }
145
- }
146
- if (max !== undefined && value.length > max) {
147
- addValidationError(`value must be no longer than ${max} characters`)
148
- }
149
- if (match && !match.regex.test(value)) {
150
- addValidationError(match.explanation ?? `value must match ${match.regex}`)
151
- }
152
- if (rejectCommon && dumbPasswords.check(value)) {
153
- addValidationError(`value is too common and is not allowed`)
154
- }
155
- }
156
- }
157
- : undefined
158
- )
159
-
160
- return fieldType({
161
- kind: 'scalar',
162
- scalar: 'String',
163
- mode,
164
- map: config.db?.map,
165
- extendPrismaSchema: config.db?.extendPrismaSchema,
166
- })({
167
- ...config,
168
- ...defaultIsRequired(config, isRequired),
169
- hooks: {
170
- ...config.hooks,
171
- validate,
172
- },
173
- input: {
174
- where:
175
- mode === 'required'
176
- ? undefined
177
- : {
178
- arg: g.arg({ type: PasswordFilter }),
179
- resolve(val) {
180
- if (val === null) throw userInputError('Password filters cannot be set to null')
181
- if (val.isSet) return { not: null }
182
- return null
183
- },
184
- },
185
- create: {
186
- arg: g.arg({ type: g.String }),
187
- resolve(val) {
188
- if (val === undefined) return null
189
- return inputResolver(val)
190
- },
191
- },
192
- update: {
193
- arg: g.arg({ type: g.String }),
194
- resolve: inputResolver,
195
- },
196
- },
197
- __ksTelemetryFieldTypeName: '@nixxie-cms/password',
198
- views: '@nixxie-cms/core/fields/types/password/views',
199
- getAdminMeta: (): Parameters<typeof controller>[0]['fieldMeta'] => ({
200
- isNullable: mode === 'optional',
201
- validation: {
202
- rejectCommon,
203
- match: match
204
- ? {
205
- regex: {
206
- source: match.regex.source,
207
- flags: match.regex.flags,
208
- },
209
- explanation: match.explanation ?? `value must match ${match.regex}`,
210
- }
211
- : null,
212
- length: {
213
- max: max ?? null,
214
- min: min ?? 8,
215
- },
216
- },
217
- }),
218
- output: g.field({
219
- type: PasswordState,
220
- resolve(val) {
221
- return { isSet: val.value !== null }
222
- },
223
- extensions: {
224
- nixxieKDF: kdf,
225
- },
226
- }),
227
- })
228
- }
229
- }
230
-
231
- export function getPasswordFieldKDF(
232
- schema: GraphQLSchema,
233
- listKey: string,
234
- fieldKey: string
235
- ): KDF | null {
236
- const gqlOutputType = schema.getType(listKey)
237
- if (!isObjectType(gqlOutputType)) return null
238
- const passwordField = gqlOutputType.getFields()[fieldKey]
239
- if (!passwordField?.extensions.nixxieKDF) return null
240
- return passwordField.extensions.nixxieKDF as KDF
241
- }
1
+ import bcryptjs from 'bcryptjs'
2
+ // @ts-expect-error
3
+ import dumbPasswords from 'dumb-passwords'
4
+ import { userInputError } from '../../../lib/core/graphql-errors'
5
+ import {
6
+ type BaseListTypeInfo,
7
+ type CommonFieldConfig,
8
+ type FieldTypeFunc,
9
+ fieldType,
10
+ } from '../../../types'
11
+ import { g } from '../../..'
12
+ import { makeValidateHook, defaultIsRequired } from '../../non-null-graphql'
13
+ import { isObjectType, type GraphQLSchema } from 'graphql'
14
+ import type { InferValueFromInputType } from '@graphql-ts/schema'
15
+ import type { controller } from './views'
16
+
17
+ type FieldTypeInfo = {
18
+ item: string | null
19
+ inputs: {
20
+ where: InferValueFromInputType<typeof PasswordFilter> | undefined
21
+ create: string | null | undefined
22
+ update: string | null | undefined
23
+ uniqueWhere: never
24
+ orderBy: never
25
+ }
26
+ prisma: {
27
+ create: string | null | undefined
28
+ update: string | null | undefined
29
+ }
30
+ }
31
+
32
+ export type PasswordFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
33
+ ListTypeInfo,
34
+ FieldTypeInfo
35
+ > & {
36
+ validation?: {
37
+ isRequired?: boolean
38
+ rejectCommon?: boolean
39
+ match?: { regex: RegExp; explanation?: string }
40
+ length?: {
41
+ /** @default 8 */
42
+ min?: number
43
+ max?: number
44
+ }
45
+ }
46
+ db?: {
47
+ isNullable?: boolean
48
+ map?: string
49
+ extendPrismaSchema?: (field: string) => string
50
+ }
51
+ kdf?: KDF
52
+ }
53
+
54
+ type KDF = {
55
+ compare(preImage: string, hash: string): Promise<boolean>
56
+ hash(preImage: string): Promise<string>
57
+ }
58
+
59
+ const PasswordState = g.object<{ isSet: boolean }>()({
60
+ name: 'PasswordState',
61
+ fields: {
62
+ isSet: g.field({ type: g.nonNull(g.Boolean) }),
63
+ },
64
+ })
65
+
66
+ const PasswordFilter = g.inputObject({
67
+ name: 'PasswordFilter',
68
+ fields: {
69
+ isSet: g.arg({ type: g.nonNull(g.Boolean) }),
70
+ },
71
+ })
72
+
73
+ export function password<ListTypeInfo extends BaseListTypeInfo>(
74
+ config: PasswordFieldConfig<ListTypeInfo> = {}
75
+ ): FieldTypeFunc<ListTypeInfo> {
76
+ const {
77
+ kdf = {
78
+ hash: secret => {
79
+ // note this is slightly different to checking .length > 72 because
80
+ // bcrypt will truncate to 72 bytes after utf8 encoding
81
+ // not JS string length which may be different since that's the utf16 length
82
+ // (though using characters in the error message makes sense since users shouldn't have to think about bytes and it aligns with the validation messages below)
83
+ if (bcryptjs.truncates(secret))
84
+ throw new Error('value must be no longer than 72 characters')
85
+ return bcryptjs.hash(secret, 10)
86
+ },
87
+ compare: (secret, hash) => bcryptjs.compare(secret, hash),
88
+ },
89
+ validation = {},
90
+ } = config
91
+ const { isRequired = false, rejectCommon = false, match, length: { max } = {} } = validation
92
+ const min = isRequired ? (validation.length?.min ?? 8) : validation.length?.min
93
+
94
+ return meta => {
95
+ if ((config as any).isIndexed === 'unique') {
96
+ throw Error("isIndexed: 'unique' is not a supported option for field type password")
97
+ }
98
+ if (min !== undefined && (!Number.isInteger(min) || min < 0)) {
99
+ throw new Error(
100
+ `${meta.listKey}.${meta.fieldKey} specifies validation.length.min: ${min} but it must be a positive integer`
101
+ )
102
+ }
103
+ if (max !== undefined && (!Number.isInteger(max) || max < 0)) {
104
+ throw new Error(
105
+ `${meta.listKey}.${meta.fieldKey} specifies validation.length.max: ${max} but it must be a positive integer`
106
+ )
107
+ }
108
+ if (isRequired && min !== undefined && min === 0) {
109
+ throw new Error(
110
+ `${meta.listKey}.${meta.fieldKey} specifies validation.isRequired: true and validation.length.min: 0, this is not allowed because validation.isRequired implies at least a min length of 1`
111
+ )
112
+ }
113
+ if (isRequired && max !== undefined && max === 0) {
114
+ throw new Error(
115
+ `${meta.listKey}.${meta.fieldKey} specifies validation.isRequired: true and validation.length.max: 0, this is not allowed because validation.isRequired implies at least a max length of 1`
116
+ )
117
+ }
118
+ if (min !== undefined && max !== undefined && min > max) {
119
+ throw new Error(
120
+ `${meta.listKey}.${meta.fieldKey} specifies a validation.length.max that is less than the validation.length.min, and therefore has no valid options`
121
+ )
122
+ }
123
+
124
+ function inputResolver(val: string | null | undefined) {
125
+ if (val == null) return val
126
+ return kdf.hash(val)
127
+ }
128
+
129
+ const hasAdditionalValidation = match || rejectCommon || min !== undefined || max !== undefined
130
+ const { mode, validate } = makeValidateHook(
131
+ meta,
132
+ config,
133
+ hasAdditionalValidation
134
+ ? ({ inputData, operation, addValidationError }) => {
135
+ if (operation === 'delete') return
136
+
137
+ const value = inputData[meta.fieldKey] // we use inputData, as resolveData is hashed
138
+ if (value != null) {
139
+ if (min !== undefined && value.length < min) {
140
+ if (min === 1) {
141
+ addValidationError(`value must not be empty`)
142
+ } else {
143
+ addValidationError(`value must be at least ${min} characters long`)
144
+ }
145
+ }
146
+ if (max !== undefined && value.length > max) {
147
+ addValidationError(`value must be no longer than ${max} characters`)
148
+ }
149
+ if (match && !match.regex.test(value)) {
150
+ addValidationError(match.explanation ?? `value must match ${match.regex}`)
151
+ }
152
+ if (rejectCommon && dumbPasswords.check(value)) {
153
+ addValidationError(`value is too common and is not allowed`)
154
+ }
155
+ }
156
+ }
157
+ : undefined
158
+ )
159
+
160
+ return fieldType({
161
+ kind: 'scalar',
162
+ scalar: 'String',
163
+ mode,
164
+ map: config.db?.map,
165
+ extendPrismaSchema: config.db?.extendPrismaSchema,
166
+ })({
167
+ ...config,
168
+ ...defaultIsRequired(config, isRequired),
169
+ hooks: {
170
+ ...config.hooks,
171
+ validate,
172
+ },
173
+ input: {
174
+ where:
175
+ mode === 'required'
176
+ ? undefined
177
+ : {
178
+ arg: g.arg({ type: PasswordFilter }),
179
+ resolve(val) {
180
+ if (val === null) throw userInputError('Password filters cannot be set to null')
181
+ if (val.isSet) return { not: null }
182
+ return null
183
+ },
184
+ },
185
+ create: {
186
+ arg: g.arg({ type: g.String }),
187
+ resolve(val) {
188
+ if (val === undefined) return null
189
+ return inputResolver(val)
190
+ },
191
+ },
192
+ update: {
193
+ arg: g.arg({ type: g.String }),
194
+ resolve: inputResolver,
195
+ },
196
+ },
197
+ __nxTelemetryFieldTypeName: '@nixxie-cms/password',
198
+ views: '@nixxie-cms/core/fields/types/password/views',
199
+ getAdminMeta: (): Parameters<typeof controller>[0]['fieldMeta'] => ({
200
+ isNullable: mode === 'optional',
201
+ validation: {
202
+ rejectCommon,
203
+ match: match
204
+ ? {
205
+ regex: {
206
+ source: match.regex.source,
207
+ flags: match.regex.flags,
208
+ },
209
+ explanation: match.explanation ?? `value must match ${match.regex}`,
210
+ }
211
+ : null,
212
+ length: {
213
+ max: max ?? null,
214
+ min: min ?? 8,
215
+ },
216
+ },
217
+ }),
218
+ output: g.field({
219
+ type: PasswordState,
220
+ resolve(val) {
221
+ return { isSet: val.value !== null }
222
+ },
223
+ extensions: {
224
+ nixxieKDF: kdf,
225
+ },
226
+ }),
227
+ })
228
+ }
229
+ }
230
+
231
+ export function getPasswordFieldKDF(
232
+ schema: GraphQLSchema,
233
+ listKey: string,
234
+ fieldKey: string
235
+ ): KDF | null {
236
+ const gqlOutputType = schema.getType(listKey)
237
+ if (!isObjectType(gqlOutputType)) return null
238
+ const passwordField = gqlOutputType.getFields()[fieldKey]
239
+ if (!passwordField?.extensions.nixxieKDF) return null
240
+ return passwordField.extensions.nixxieKDF as KDF
241
+ }