@nixxie-cms/auth 1.0.1 → 1.1.0

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.
package/src/schema.ts CHANGED
@@ -1,84 +1,85 @@
1
- import { assertInputObjectType, GraphQLString, GraphQLID, parse, validate } from 'graphql'
2
-
3
- import { g } from '@nixxie-cms/core'
4
- import type { AuthGqlNames, AuthTokenTypeConfig, InitFirstItemConfig } from './types'
5
- import { getBaseAuthSchema } from './gql/getBaseAuthSchema'
6
- import { getInitFirstItemSchema } from './gql/getInitFirstItemSchema'
7
-
8
- export const getSchemaExtension = ({
9
- authGqlNames,
10
- listKey,
11
- identityField,
12
- secretField,
13
- initFirstItem,
14
- sessionData,
15
- }: {
16
- authGqlNames: AuthGqlNames
17
- listKey: string
18
- identityField: string
19
- secretField: string
20
- initFirstItem?: InitFirstItemConfig<any>
21
- passwordResetLink?: AuthTokenTypeConfig
22
- magicAuthLink?: AuthTokenTypeConfig
23
- sessionData: string
24
- }) =>
25
- g.extend(base => {
26
- const uniqueWhereInputType = assertInputObjectType(
27
- base.schema.getType(authGqlNames.whereUniqueInputName)
28
- )
29
- const identityFieldOnUniqueWhere = uniqueWhereInputType.getFields()[identityField]
30
- if (
31
- base.schema.extensions.sudo &&
32
- identityFieldOnUniqueWhere?.type !== GraphQLString &&
33
- identityFieldOnUniqueWhere?.type !== GraphQLID
34
- ) {
35
- throw new Error(
36
- `createAuth was called with an identityField of ${identityField} on the list ${listKey} ` +
37
- `but that field doesn't allow being searched uniquely with a String or ID. ` +
38
- `You should likely add \`isIndexed: 'unique'\` ` +
39
- `to the field at ${listKey}.${identityField}`
40
- )
41
- }
42
-
43
- const baseSchema = getBaseAuthSchema({
44
- authGqlNames: authGqlNames,
45
- identityField,
46
- listKey,
47
- secretField,
48
- base,
49
- })
50
-
51
- // technically this will incorrectly error if someone has a schema extension that adds a field to the list output type
52
- // and then wants to fetch that field with `sessionData` but it's extremely unlikely someone will do that since if
53
- // they want to add a GraphQL field, they'll probably use a virtual field
54
- const query = `query($id: ID!) { ${authGqlNames.itemQueryName}(where: { id: $id }) { ${sessionData} } }`
55
-
56
- let ast
57
- try {
58
- ast = parse(query)
59
- } catch (err) {
60
- throw new Error(
61
- `The query to get session data has a syntax error, the sessionData option in your createAuth usage is likely incorrect\n${err}`
62
- )
63
- }
64
-
65
- const errors = validate(base.schema, ast)
66
- if (errors.length) {
67
- throw new Error(
68
- `The query to get session data has validation errors, the sessionData option in your createAuth usage is likely incorrect\n${errors.join('\n')}`
69
- )
70
- }
71
-
72
- return [
73
- baseSchema.extension,
74
- initFirstItem &&
75
- getInitFirstItemSchema({
76
- authGqlNames,
77
- listKey,
78
- fields: initFirstItem.fields,
79
- defaultItemData: initFirstItem.itemData,
80
- graphQLSchema: base.schema,
81
- ItemAuthenticationWithPasswordSuccess: baseSchema.ItemAuthenticationWithPasswordSuccess,
82
- }),
83
- ].filter((x): x is Exclude<typeof x, undefined> => x !== undefined)
84
- })
1
+ import { assertInputObjectType, GraphQLString, GraphQLID, parse, validate } from 'graphql'
2
+
3
+ import { g } from '@nixxie-cms/core'
4
+ import type { AuthGqlNames, AuthTokenTypeConfig, InitFirstItemConfig } from './types'
5
+ import { getBaseAuthSchema } from './gql/getBaseAuthSchema'
6
+ import { getInitFirstItemSchema } from './gql/getInitFirstItemSchema'
7
+
8
+ export const getSchemaExtension = ({
9
+ authGqlNames,
10
+ listKey,
11
+ identityField,
12
+ secretField,
13
+ initFirstItem,
14
+ sessionData,
15
+ }: {
16
+ authGqlNames: AuthGqlNames
17
+ listKey: string
18
+ identityField: string
19
+ secretField: string
20
+ initFirstItem?: InitFirstItemConfig<any>
21
+ passwordResetLink?: AuthTokenTypeConfig
22
+ magicAuthLink?: AuthTokenTypeConfig
23
+ sessionData: string
24
+ }) =>
25
+ g.extend(base => {
26
+ const uniqueWhereInputType = assertInputObjectType(
27
+ base.schema.getType(authGqlNames.whereUniqueInputName)
28
+ )
29
+ const identityFieldOnUniqueWhere = uniqueWhereInputType.getFields()[identityField]
30
+ if (
31
+ base.schema.extensions.sudo &&
32
+ identityFieldOnUniqueWhere?.type !== GraphQLString &&
33
+ identityFieldOnUniqueWhere?.type !== GraphQLID
34
+ ) {
35
+ throw new Error(
36
+ `createAuth was called with an identityField of ${identityField} on the list ${listKey} ` +
37
+ `but that field doesn't allow being searched uniquely with a String or ID. ` +
38
+ `You should likely add \`isIndexed: 'unique'\` ` +
39
+ `to the field at ${listKey}.${identityField}`
40
+ )
41
+ }
42
+
43
+ const baseSchema = getBaseAuthSchema({
44
+ authGqlNames: authGqlNames,
45
+ identityField,
46
+ listKey,
47
+ secretField,
48
+ base,
49
+ })
50
+
51
+ // technically this will incorrectly error if someone has a schema extension that adds a field to the list output type
52
+ // and then wants to fetch that field with `sessionData` but it's extremely unlikely someone will do that since if
53
+ // they want to add a GraphQL field, they'll probably use a virtual field
54
+ const query = `query($id: ID!) { ${authGqlNames.itemQueryName}(where: { id: $id }) { ${sessionData} } }`
55
+
56
+ let ast
57
+ try {
58
+ ast = parse(query)
59
+ } catch (err) {
60
+ throw new Error(
61
+ `The query to get session data has a syntax error, the sessionData option in your createAuth usage is likely incorrect\n${err}`,
62
+ { cause: err }
63
+ )
64
+ }
65
+
66
+ const errors = validate(base.schema, ast)
67
+ if (errors.length) {
68
+ throw new Error(
69
+ `The query to get session data has validation errors, the sessionData option in your createAuth usage is likely incorrect\n${errors.join('\n')}`
70
+ )
71
+ }
72
+
73
+ return [
74
+ baseSchema.extension,
75
+ initFirstItem &&
76
+ getInitFirstItemSchema({
77
+ authGqlNames,
78
+ listKey,
79
+ fields: initFirstItem.fields,
80
+ defaultItemData: initFirstItem.itemData,
81
+ graphQLSchema: base.schema,
82
+ ItemAuthenticationWithPasswordSuccess: baseSchema.ItemAuthenticationWithPasswordSuccess,
83
+ }),
84
+ ].filter((x): x is Exclude<typeof x, undefined> => x !== undefined)
85
+ })
@@ -1,9 +1,9 @@
1
- export default function ({ labelField }: { labelField: string }) {
2
- return `import { type AdminConfig } from '@nixxie-cms/core/types'
3
- import makeNavigation from '@nixxie-cms/auth/components/Navigation'
4
-
5
- export const components: AdminConfig['components'] = {
6
- Navigation: makeNavigation({ labelField: '${labelField}' }),
7
- }
8
- `
9
- }
1
+ export default function ({ labelField }: { labelField: string }) {
2
+ return `import { type AdminConfig } from '@nixxie-cms/core/types'
3
+ import makeNavigation from '@nixxie-cms/auth/components/Navigation'
4
+
5
+ export const components: AdminConfig['components'] = {
6
+ Navigation: makeNavigation({ labelField: '${labelField}' }),
7
+ }
8
+ `
9
+ }
@@ -1,22 +1,22 @@
1
- import type { BaseListTypeInfo } from '@nixxie-cms/core/types'
2
- import type { AuthGqlNames, AuthConfig } from '../types'
3
-
4
- export default function ({
5
- authGqlNames,
6
- listKey,
7
- initFirstItem,
8
- }: {
9
- authGqlNames: AuthGqlNames
10
- listKey: string
11
- initFirstItem: NonNullable<AuthConfig<BaseListTypeInfo>['initFirstItem']>
12
- }) {
13
- return `import makeInitPage from '@nixxie-cms/auth/pages/InitPage'
14
-
15
- export default makeInitPage(${JSON.stringify({
16
- listKey,
17
- authGqlNames,
18
- fieldPaths: initFirstItem.fields,
19
- enableWelcome: !initFirstItem.skipNixxieWelcome,
20
- })})
21
- `
22
- }
1
+ import type { BaseCollectionTypeInfo } from '@nixxie-cms/core/types'
2
+ import type { AuthGqlNames, AuthConfig } from '../types'
3
+
4
+ export default function ({
5
+ authGqlNames,
6
+ listKey,
7
+ initFirstItem,
8
+ }: {
9
+ authGqlNames: AuthGqlNames
10
+ listKey: string
11
+ initFirstItem: NonNullable<AuthConfig<BaseCollectionTypeInfo>['initFirstItem']>
12
+ }) {
13
+ return `import makeInitPage from '@nixxie-cms/auth/pages/InitPage'
14
+
15
+ export default makeInitPage(${JSON.stringify({
16
+ listKey,
17
+ authGqlNames,
18
+ fieldPaths: initFirstItem.fields,
19
+ enableWelcome: !initFirstItem.skipNixxieWelcome,
20
+ })})
21
+ `
22
+ }
@@ -1,20 +1,20 @@
1
- import type { AuthGqlNames } from '../types'
2
-
3
- export default function ({
4
- authGqlNames,
5
- identityField,
6
- secretField,
7
- }: {
8
- authGqlNames: AuthGqlNames
9
- identityField: string
10
- secretField: string
11
- }) {
12
- return `import makeSigninPage from '@nixxie-cms/auth/pages/SigninPage'
13
-
14
- export default makeSigninPage(${JSON.stringify({
15
- authGqlNames,
16
- identityField,
17
- secretField,
18
- })})
19
- `
20
- }
1
+ import type { AuthGqlNames } from '../types'
2
+
3
+ export default function ({
4
+ authGqlNames,
5
+ identityField,
6
+ secretField,
7
+ }: {
8
+ authGqlNames: AuthGqlNames
9
+ identityField: string
10
+ secretField: string
11
+ }) {
12
+ return `import makeSigninPage from '@nixxie-cms/auth/pages/SigninPage'
13
+
14
+ export default makeSigninPage(${JSON.stringify({
15
+ authGqlNames,
16
+ identityField,
17
+ secretField,
18
+ })})
19
+ `
20
+ }
package/src/types.ts CHANGED
@@ -1,57 +1,57 @@
1
- import type { BaseListTypeInfo, NixxieContext } from '@nixxie-cms/core/types'
2
-
3
- export type AuthGqlNames = {
4
- itemQueryName: string
5
- whereUniqueInputName: string
6
-
7
- authenticateItemWithPassword: string
8
- ItemAuthenticationWithPasswordResult: string
9
- ItemAuthenticationWithPasswordSuccess: string
10
- ItemAuthenticationWithPasswordFailure: string
11
-
12
- CreateInitialInput: string
13
- createInitialItem: string
14
- }
15
-
16
- export type SendTokenFn = (args: {
17
- itemId: string | number | bigint
18
- identity: string
19
- token: string
20
- context: NixxieContext
21
- }) => Promise<void> | void
22
-
23
- export type AuthTokenTypeConfig = {
24
- /** Called when a user should be sent the magic signin token they requested */
25
- sendToken: SendTokenFn
26
- /** How long do tokens stay valid for from time of issue, in minutes **/
27
- tokensValidForMins?: number
28
- }
29
-
30
- export type AuthConfig<ListTypeInfo extends BaseListTypeInfo> = {
31
- /** The key of the list to authenticate users with */
32
- listKey: ListTypeInfo['key']
33
- /** The path of the field the identity is stored in; must be text-ish */
34
- identityField: ListTypeInfo['fields']
35
- /** The path of the field the secret is stored in; must be password-ish */
36
- secretField: ListTypeInfo['fields']
37
- /** The initial user/db seeding functionality */
38
- initFirstItem?: InitFirstItemConfig<ListTypeInfo>
39
- /** Session data population */
40
- sessionData?: string
41
- }
42
-
43
- export type InitFirstItemConfig<ListTypeInfo extends BaseListTypeInfo> = {
44
- /** Array of fields to collect, e.g ['name', 'email', 'password'] */
45
- fields: readonly ListTypeInfo['fields'][]
46
- /** Suppresses the second screen where we ask people to subscribe and follow Nixxie */
47
- skipNixxieWelcome?: boolean
48
- /** Extra input to add for the create mutation */
49
- itemData?: Partial<ListTypeInfo['inputs']['create']>
50
- }
51
-
52
- export type AuthTokenRedemptionErrorCode = 'FAILURE' | 'TOKEN_EXPIRED' | 'TOKEN_REDEEMED'
53
-
54
- export type SecretFieldImpl = {
55
- generateHash: (secret: string) => Promise<string>
56
- compare: (secret: string, hash: string) => Promise<boolean>
57
- }
1
+ import type { BaseCollectionTypeInfo, NixxieContext } from '@nixxie-cms/core/types'
2
+
3
+ export type AuthGqlNames = {
4
+ itemQueryName: string
5
+ whereUniqueInputName: string
6
+
7
+ authenticateItemWithPassword: string
8
+ ItemAuthenticationWithPasswordResult: string
9
+ ItemAuthenticationWithPasswordSuccess: string
10
+ ItemAuthenticationWithPasswordFailure: string
11
+
12
+ CreateInitialInput: string
13
+ createInitialItem: string
14
+ }
15
+
16
+ export type SendTokenFn = (args: {
17
+ itemId: string | number | bigint
18
+ identity: string
19
+ token: string
20
+ context: NixxieContext
21
+ }) => Promise<void> | void
22
+
23
+ export type AuthTokenTypeConfig = {
24
+ /** Called when a user should be sent the magic signin token they requested */
25
+ sendToken: SendTokenFn
26
+ /** How long do tokens stay valid for from time of issue, in minutes **/
27
+ tokensValidForMins?: number
28
+ }
29
+
30
+ export type AuthConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = {
31
+ /** The key of the list to authenticate users with */
32
+ listKey: CollectionTypeInfo['key']
33
+ /** The path of the field the identity is stored in; must be text-ish */
34
+ identityField: CollectionTypeInfo['fields']
35
+ /** The path of the field the secret is stored in; must be password-ish */
36
+ secretField: CollectionTypeInfo['fields']
37
+ /** The initial user/db seeding functionality */
38
+ initFirstItem?: InitFirstItemConfig<CollectionTypeInfo>
39
+ /** Session data population */
40
+ sessionData?: string
41
+ }
42
+
43
+ export type InitFirstItemConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = {
44
+ /** Array of fields to collect, e.g ['name', 'email', 'password'] */
45
+ fields: readonly CollectionTypeInfo['fields'][]
46
+ /** Suppresses the second screen where we ask people to subscribe and follow Nixxie */
47
+ skipNixxieWelcome?: boolean
48
+ /** Extra input to add for the create mutation */
49
+ itemData?: Partial<CollectionTypeInfo['inputs']['create']>
50
+ }
51
+
52
+ export type AuthTokenRedemptionErrorCode = 'FAILURE' | 'TOKEN_EXPIRED' | 'TOKEN_REDEEMED'
53
+
54
+ export type SecretFieldImpl = {
55
+ generateHash: (secret: string) => Promise<string>
56
+ compare: (secret: string, hash: string) => Promise<boolean>
57
+ }