@nixxie-cms/auth 1.0.1
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/LICENSE +21 -0
- package/README.md +6 -0
- package/components/Navigation/dist/nixxie-cms-auth-components-Navigation.cjs.d.ts +3 -0
- package/components/Navigation/dist/nixxie-cms-auth-components-Navigation.cjs.js +147 -0
- package/components/Navigation/dist/nixxie-cms-auth-components-Navigation.esm.js +143 -0
- package/components/Navigation/package.json +4 -0
- package/dist/declarations/src/components/Navigation.d.ts +6 -0
- package/dist/declarations/src/components/Navigation.d.ts.map +1 -0
- package/dist/declarations/src/index.d.ts +15 -0
- package/dist/declarations/src/index.d.ts.map +1 -0
- package/dist/declarations/src/pages/InitPage.d.ts +9 -0
- package/dist/declarations/src/pages/InitPage.d.ts.map +1 -0
- package/dist/declarations/src/pages/SigninPage.d.ts +9 -0
- package/dist/declarations/src/pages/SigninPage.d.ts.map +1 -0
- package/dist/declarations/src/types.d.ts +49 -0
- package/dist/declarations/src/types.d.ts.map +1 -0
- package/dist/nixxie-cms-auth.cjs.d.ts +2 -0
- package/dist/nixxie-cms-auth.cjs.js +552 -0
- package/dist/nixxie-cms-auth.esm.js +548 -0
- package/dist/useFromRedirect-2de239a9.cjs.js +26 -0
- package/dist/useFromRedirect-b3deee00.esm.js +24 -0
- package/package.json +56 -0
- package/pages/InitPage/dist/nixxie-cms-auth-pages-InitPage.cjs.d.ts +3 -0
- package/pages/InitPage/dist/nixxie-cms-auth-pages-InitPage.cjs.js +274 -0
- package/pages/InitPage/dist/nixxie-cms-auth-pages-InitPage.esm.js +266 -0
- package/pages/InitPage/package.json +4 -0
- package/pages/SigninPage/dist/nixxie-cms-auth-pages-SigninPage.cjs.d.ts +3 -0
- package/pages/SigninPage/dist/nixxie-cms-auth-pages-SigninPage.cjs.js +319 -0
- package/pages/SigninPage/dist/nixxie-cms-auth-pages-SigninPage.esm.js +311 -0
- package/pages/SigninPage/package.json +4 -0
- package/src/components/Navigation.tsx +182 -0
- package/src/gql/getBaseAuthSchema.ts +129 -0
- package/src/gql/getInitFirstItemSchema.ts +87 -0
- package/src/index.ts +291 -0
- package/src/lib/useFromRedirect.ts +23 -0
- package/src/pages/InitPage.tsx +292 -0
- package/src/pages/SigninPage.tsx +331 -0
- package/src/schema.ts +84 -0
- package/src/templates/config.ts +9 -0
- package/src/templates/init.ts +22 -0
- package/src/templates/signin.ts +20 -0
- package/src/types.ts +57 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var graphql = require('graphql');
|
|
6
|
+
var core = require('@nixxie-cms/core');
|
|
7
|
+
var password = require('@nixxie-cms/core/fields/types/password');
|
|
8
|
+
|
|
9
|
+
const AUTHENTICATION_FAILURE$1 = {
|
|
10
|
+
code: 'FAILURE',
|
|
11
|
+
message: 'Authentication failed.'
|
|
12
|
+
};
|
|
13
|
+
function getBaseAuthSchema({
|
|
14
|
+
authGqlNames,
|
|
15
|
+
listKey,
|
|
16
|
+
identityField,
|
|
17
|
+
secretField,
|
|
18
|
+
base
|
|
19
|
+
}) {
|
|
20
|
+
const kdf = password.getPasswordFieldKDF(base.schema, listKey, secretField);
|
|
21
|
+
if (!kdf) {
|
|
22
|
+
throw new Error(`${listKey}.${secretField} is not a valid password field.`);
|
|
23
|
+
}
|
|
24
|
+
const ItemAuthenticationWithPasswordSuccess = core.g.object()({
|
|
25
|
+
name: authGqlNames.ItemAuthenticationWithPasswordSuccess,
|
|
26
|
+
fields: {
|
|
27
|
+
sessionToken: core.g.field({
|
|
28
|
+
type: core.g.nonNull(core.g.String)
|
|
29
|
+
}),
|
|
30
|
+
item: core.g.field({
|
|
31
|
+
type: core.g.nonNull(base.object(listKey))
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const ItemAuthenticationWithPasswordFailure = core.g.object()({
|
|
36
|
+
name: authGqlNames.ItemAuthenticationWithPasswordFailure,
|
|
37
|
+
fields: {
|
|
38
|
+
message: core.g.field({
|
|
39
|
+
type: core.g.nonNull(core.g.String)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const AuthenticationResult = core.g.union({
|
|
44
|
+
name: authGqlNames.ItemAuthenticationWithPasswordResult,
|
|
45
|
+
types: [ItemAuthenticationWithPasswordSuccess, ItemAuthenticationWithPasswordFailure],
|
|
46
|
+
resolveType(val) {
|
|
47
|
+
if ('sessionToken' in val) return authGqlNames.ItemAuthenticationWithPasswordSuccess;
|
|
48
|
+
return authGqlNames.ItemAuthenticationWithPasswordFailure;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
const extension = {
|
|
52
|
+
query: {
|
|
53
|
+
authenticatedItem: core.g.field({
|
|
54
|
+
type: base.object(listKey),
|
|
55
|
+
resolve(rootVal, args, context) {
|
|
56
|
+
const {
|
|
57
|
+
session
|
|
58
|
+
} = context;
|
|
59
|
+
if (!(session !== null && session !== void 0 && session.itemId)) return null;
|
|
60
|
+
return context.db[listKey].findOne({
|
|
61
|
+
where: {
|
|
62
|
+
id: session.itemId
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
},
|
|
68
|
+
mutation: {
|
|
69
|
+
endSession: core.g.field({
|
|
70
|
+
type: core.g.nonNull(core.g.Boolean),
|
|
71
|
+
async resolve(rootVal, args, context) {
|
|
72
|
+
var _context$sessionStrat;
|
|
73
|
+
await ((_context$sessionStrat = context.sessionStrategy) === null || _context$sessionStrat === void 0 ? void 0 : _context$sessionStrat.end({
|
|
74
|
+
context
|
|
75
|
+
}));
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}),
|
|
79
|
+
[authGqlNames.authenticateItemWithPassword]: core.g.field({
|
|
80
|
+
type: AuthenticationResult,
|
|
81
|
+
args: {
|
|
82
|
+
[identityField]: core.g.arg({
|
|
83
|
+
type: core.g.nonNull(core.g.String)
|
|
84
|
+
}),
|
|
85
|
+
[secretField]: core.g.arg({
|
|
86
|
+
type: core.g.nonNull(core.g.String)
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
async resolve(rootVal, {
|
|
90
|
+
[identityField]: identity,
|
|
91
|
+
[secretField]: secret
|
|
92
|
+
}, context) {
|
|
93
|
+
if (!context.sessionStrategy) throw new Error('No session strategy on context');
|
|
94
|
+
const item = await context.sudo().db[listKey].findOne({
|
|
95
|
+
where: {
|
|
96
|
+
[identityField]: identity
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (typeof (item === null || item === void 0 ? void 0 : item[secretField]) !== 'string') {
|
|
100
|
+
await kdf.hash('simulated-password-to-counter-timing-attack');
|
|
101
|
+
return AUTHENTICATION_FAILURE$1;
|
|
102
|
+
}
|
|
103
|
+
const equal = await kdf.compare(secret, item[secretField]);
|
|
104
|
+
if (!equal) return AUTHENTICATION_FAILURE$1;
|
|
105
|
+
const sessionToken = await context.sessionStrategy.start({
|
|
106
|
+
data: {
|
|
107
|
+
listKey,
|
|
108
|
+
itemId: item.id
|
|
109
|
+
},
|
|
110
|
+
context
|
|
111
|
+
});
|
|
112
|
+
if (typeof sessionToken !== 'string' || sessionToken.length === 0) {
|
|
113
|
+
return AUTHENTICATION_FAILURE$1;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
sessionToken,
|
|
117
|
+
item
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
extension,
|
|
125
|
+
ItemAuthenticationWithPasswordSuccess
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const AUTHENTICATION_FAILURE = 'Authentication failed.';
|
|
130
|
+
function getInitFirstItemSchema({
|
|
131
|
+
authGqlNames,
|
|
132
|
+
listKey,
|
|
133
|
+
fields,
|
|
134
|
+
defaultItemData,
|
|
135
|
+
graphQLSchema,
|
|
136
|
+
ItemAuthenticationWithPasswordSuccess
|
|
137
|
+
}) {
|
|
138
|
+
const createInputConfig = graphql.assertInputObjectType(graphQLSchema.getType(`${listKey}CreateInput`)).toConfig();
|
|
139
|
+
const fieldsSet = new Set(fields);
|
|
140
|
+
const initialCreateInput = new graphql.GraphQLInputObjectType({
|
|
141
|
+
...createInputConfig,
|
|
142
|
+
fields: Object.fromEntries(Object.entries(createInputConfig.fields).filter(([fieldKey]) => fieldsSet.has(fieldKey))),
|
|
143
|
+
name: authGqlNames.CreateInitialInput
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
mutation: {
|
|
147
|
+
[authGqlNames.createInitialItem]: core.g.field({
|
|
148
|
+
type: core.g.nonNull(ItemAuthenticationWithPasswordSuccess),
|
|
149
|
+
args: {
|
|
150
|
+
data: core.g.arg({
|
|
151
|
+
type: core.g.nonNull(initialCreateInput)
|
|
152
|
+
})
|
|
153
|
+
},
|
|
154
|
+
async resolve(rootVal, {
|
|
155
|
+
data
|
|
156
|
+
}, context) {
|
|
157
|
+
if (!context.sessionStrategy) throw new Error('No session strategy on context');
|
|
158
|
+
const sudoContext = context.sudo();
|
|
159
|
+
|
|
160
|
+
// should approximate hasInitFirstItemConditions
|
|
161
|
+
const count = await sudoContext.db[listKey].count();
|
|
162
|
+
if (count !== 0) throw AUTHENTICATION_FAILURE;
|
|
163
|
+
|
|
164
|
+
// Update system state
|
|
165
|
+
// this is strictly speaking incorrect. the db API will do GraphQL coercion on a value which has already been coerced
|
|
166
|
+
// (this is also mostly fine, the chance that people are using things where
|
|
167
|
+
// the input value can't round-trip like the Upload scalar here is quite low)
|
|
168
|
+
const item = await sudoContext.db[listKey].createOne({
|
|
169
|
+
data: {
|
|
170
|
+
...defaultItemData,
|
|
171
|
+
...data
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
const sessionToken = await context.sessionStrategy.start({
|
|
175
|
+
data: {
|
|
176
|
+
listKey,
|
|
177
|
+
itemId: item.id
|
|
178
|
+
},
|
|
179
|
+
context
|
|
180
|
+
});
|
|
181
|
+
if (typeof sessionToken !== 'string' || sessionToken.length === 0) {
|
|
182
|
+
throw AUTHENTICATION_FAILURE;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
sessionToken,
|
|
186
|
+
item
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const getSchemaExtension = ({
|
|
195
|
+
authGqlNames,
|
|
196
|
+
listKey,
|
|
197
|
+
identityField,
|
|
198
|
+
secretField,
|
|
199
|
+
initFirstItem,
|
|
200
|
+
sessionData
|
|
201
|
+
}) => core.g.extend(base => {
|
|
202
|
+
const uniqueWhereInputType = graphql.assertInputObjectType(base.schema.getType(authGqlNames.whereUniqueInputName));
|
|
203
|
+
const identityFieldOnUniqueWhere = uniqueWhereInputType.getFields()[identityField];
|
|
204
|
+
if (base.schema.extensions.sudo && (identityFieldOnUniqueWhere === null || identityFieldOnUniqueWhere === void 0 ? void 0 : identityFieldOnUniqueWhere.type) !== graphql.GraphQLString && (identityFieldOnUniqueWhere === null || identityFieldOnUniqueWhere === void 0 ? void 0 : identityFieldOnUniqueWhere.type) !== graphql.GraphQLID) {
|
|
205
|
+
throw new Error(`createAuth was called with an identityField of ${identityField} on the list ${listKey} ` + `but that field doesn't allow being searched uniquely with a String or ID. ` + `You should likely add \`isIndexed: 'unique'\` ` + `to the field at ${listKey}.${identityField}`);
|
|
206
|
+
}
|
|
207
|
+
const baseSchema = getBaseAuthSchema({
|
|
208
|
+
authGqlNames: authGqlNames,
|
|
209
|
+
identityField,
|
|
210
|
+
listKey,
|
|
211
|
+
secretField,
|
|
212
|
+
base
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// technically this will incorrectly error if someone has a schema extension that adds a field to the list output type
|
|
216
|
+
// and then wants to fetch that field with `sessionData` but it's extremely unlikely someone will do that since if
|
|
217
|
+
// they want to add a GraphQL field, they'll probably use a virtual field
|
|
218
|
+
const query = `query($id: ID!) { ${authGqlNames.itemQueryName}(where: { id: $id }) { ${sessionData} } }`;
|
|
219
|
+
let ast;
|
|
220
|
+
try {
|
|
221
|
+
ast = graphql.parse(query);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
throw new Error(`The query to get session data has a syntax error, the sessionData option in your createAuth usage is likely incorrect\n${err}`);
|
|
224
|
+
}
|
|
225
|
+
const errors = graphql.validate(base.schema, ast);
|
|
226
|
+
if (errors.length) {
|
|
227
|
+
throw new Error(`The query to get session data has validation errors, the sessionData option in your createAuth usage is likely incorrect\n${errors.join('\n')}`);
|
|
228
|
+
}
|
|
229
|
+
return [baseSchema.extension, initFirstItem && getInitFirstItemSchema({
|
|
230
|
+
authGqlNames,
|
|
231
|
+
listKey,
|
|
232
|
+
fields: initFirstItem.fields,
|
|
233
|
+
defaultItemData: initFirstItem.itemData,
|
|
234
|
+
graphQLSchema: base.schema,
|
|
235
|
+
ItemAuthenticationWithPasswordSuccess: baseSchema.ItemAuthenticationWithPasswordSuccess
|
|
236
|
+
})].filter(x => x !== undefined);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
function configTemplate ({
|
|
240
|
+
labelField
|
|
241
|
+
}) {
|
|
242
|
+
return `import { type AdminConfig } from '@nixxie-cms/core/types'
|
|
243
|
+
import makeNavigation from '@nixxie-cms/auth/components/Navigation'
|
|
244
|
+
|
|
245
|
+
export const components: AdminConfig['components'] = {
|
|
246
|
+
Navigation: makeNavigation({ labelField: '${labelField}' }),
|
|
247
|
+
}
|
|
248
|
+
`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function signinTemplate ({
|
|
252
|
+
authGqlNames,
|
|
253
|
+
identityField,
|
|
254
|
+
secretField
|
|
255
|
+
}) {
|
|
256
|
+
return `import makeSigninPage from '@nixxie-cms/auth/pages/SigninPage'
|
|
257
|
+
|
|
258
|
+
export default makeSigninPage(${JSON.stringify({
|
|
259
|
+
authGqlNames,
|
|
260
|
+
identityField,
|
|
261
|
+
secretField
|
|
262
|
+
})})
|
|
263
|
+
`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function initTemplate ({
|
|
267
|
+
authGqlNames,
|
|
268
|
+
listKey,
|
|
269
|
+
initFirstItem
|
|
270
|
+
}) {
|
|
271
|
+
return `import makeInitPage from '@nixxie-cms/auth/pages/InitPage'
|
|
272
|
+
|
|
273
|
+
export default makeInitPage(${JSON.stringify({
|
|
274
|
+
listKey,
|
|
275
|
+
authGqlNames,
|
|
276
|
+
fieldPaths: initFirstItem.fields,
|
|
277
|
+
enableWelcome: !initFirstItem.skipNixxieWelcome
|
|
278
|
+
})})
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getAuthGqlNames(singular) {
|
|
283
|
+
const lowerSingularName = singular.charAt(0).toLowerCase() + singular.slice(1);
|
|
284
|
+
return {
|
|
285
|
+
itemQueryName: lowerSingularName,
|
|
286
|
+
whereUniqueInputName: `${singular}WhereUniqueInput`,
|
|
287
|
+
authenticateItemWithPassword: `authenticate${singular}WithPassword`,
|
|
288
|
+
ItemAuthenticationWithPasswordResult: `${singular}AuthenticationWithPasswordResult`,
|
|
289
|
+
ItemAuthenticationWithPasswordSuccess: `${singular}AuthenticationWithPasswordSuccess`,
|
|
290
|
+
ItemAuthenticationWithPasswordFailure: `${singular}AuthenticationWithPasswordFailure`,
|
|
291
|
+
CreateInitialInput: `CreateInitial${singular}Input`,
|
|
292
|
+
createInitialItem: `createInitial${singular}`
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// TODO: use TypeInfo and listKey for types
|
|
297
|
+
/**
|
|
298
|
+
* createAuth function
|
|
299
|
+
*
|
|
300
|
+
* Generates config for Nixxie to implement standard auth features.
|
|
301
|
+
*/
|
|
302
|
+
function createAuth({
|
|
303
|
+
listKey,
|
|
304
|
+
secretField,
|
|
305
|
+
initFirstItem,
|
|
306
|
+
identityField,
|
|
307
|
+
sessionData = 'id'
|
|
308
|
+
}) {
|
|
309
|
+
/**
|
|
310
|
+
* getAdditionalFiles
|
|
311
|
+
*
|
|
312
|
+
* This function adds files to be generated into the Admin UI build. Must be added to the
|
|
313
|
+
* ui.getAdditionalFiles config.
|
|
314
|
+
*
|
|
315
|
+
* The signin page is always included, and the init page is included when initFirstItem is set
|
|
316
|
+
*/
|
|
317
|
+
const authGetAdditionalFiles = config => {
|
|
318
|
+
var _listConfig$ui$labelF, _listConfig$ui, _listConfig$graphql$s, _listConfig$graphql;
|
|
319
|
+
// TODO: FIXME: this is a duplication of initialise-lists:747
|
|
320
|
+
const listConfig = config.lists[listKey];
|
|
321
|
+
const labelField = (_listConfig$ui$labelF = (_listConfig$ui = listConfig.ui) === null || _listConfig$ui === void 0 ? void 0 : _listConfig$ui.labelField) !== null && _listConfig$ui$labelF !== void 0 ? _listConfig$ui$labelF : listConfig.fields.label ? 'label' : listConfig.fields.name ? 'name' : listConfig.fields.title ? 'title' : 'id';
|
|
322
|
+
const authGqlNames = getAuthGqlNames((_listConfig$graphql$s = (_listConfig$graphql = listConfig.graphql) === null || _listConfig$graphql === void 0 ? void 0 : _listConfig$graphql.singular) !== null && _listConfig$graphql$s !== void 0 ? _listConfig$graphql$s : listKey);
|
|
323
|
+
const filesToWrite = [{
|
|
324
|
+
mode: 'write',
|
|
325
|
+
src: signinTemplate({
|
|
326
|
+
authGqlNames,
|
|
327
|
+
identityField,
|
|
328
|
+
secretField
|
|
329
|
+
}),
|
|
330
|
+
outputPath: 'pages/signin.js'
|
|
331
|
+
}, {
|
|
332
|
+
mode: 'write',
|
|
333
|
+
src: configTemplate({
|
|
334
|
+
labelField
|
|
335
|
+
}),
|
|
336
|
+
outputPath: 'config.ts'
|
|
337
|
+
}];
|
|
338
|
+
if (initFirstItem) {
|
|
339
|
+
filesToWrite.push({
|
|
340
|
+
mode: 'write',
|
|
341
|
+
src: initTemplate({
|
|
342
|
+
authGqlNames,
|
|
343
|
+
listKey,
|
|
344
|
+
initFirstItem
|
|
345
|
+
}),
|
|
346
|
+
outputPath: 'pages/init.js'
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return filesToWrite;
|
|
350
|
+
};
|
|
351
|
+
function throwIfInvalidConfig(config) {
|
|
352
|
+
if (!(listKey in config.lists)) {
|
|
353
|
+
throw new Error(`withAuth cannot find the list "${listKey}"`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// TODO: verify that the identity field is unique
|
|
357
|
+
// TODO: verify that the field is required
|
|
358
|
+
const list = config.lists[listKey];
|
|
359
|
+
if (!(identityField in list.fields)) {
|
|
360
|
+
throw new Error(`withAuth cannot find the identity field "${listKey}.${identityField}"`);
|
|
361
|
+
}
|
|
362
|
+
if (!(secretField in list.fields)) {
|
|
363
|
+
throw new Error(`withAuth cannot find the secret field "${listKey}.${secretField}"`);
|
|
364
|
+
}
|
|
365
|
+
for (const fieldKey of (initFirstItem === null || initFirstItem === void 0 ? void 0 : initFirstItem.fields) || []) {
|
|
366
|
+
if (fieldKey in list.fields) continue;
|
|
367
|
+
throw new Error(`initFirstItem.fields has unknown field "${listKey}.${fieldKey}"`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// this strategy wraps the existing session strategy,
|
|
372
|
+
// and injects the requested session.data before returning
|
|
373
|
+
function authSessionStrategy(_sessionStrategy) {
|
|
374
|
+
const {
|
|
375
|
+
get,
|
|
376
|
+
...sessionStrategy
|
|
377
|
+
} = _sessionStrategy;
|
|
378
|
+
return {
|
|
379
|
+
...sessionStrategy,
|
|
380
|
+
get: async ({
|
|
381
|
+
context
|
|
382
|
+
}) => {
|
|
383
|
+
const session = await get({
|
|
384
|
+
context
|
|
385
|
+
});
|
|
386
|
+
const sudoContext = context.sudo();
|
|
387
|
+
if (!(session !== null && session !== void 0 && session.itemId)) return;
|
|
388
|
+
|
|
389
|
+
// TODO: replace with SessionSecret: HMAC({ listKey, identityField, secretField }, SessionSecretVar)
|
|
390
|
+
// if (session.listKey !== listKey) return null
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
const data = await sudoContext.query[listKey].findOne({
|
|
394
|
+
where: {
|
|
395
|
+
id: session.itemId
|
|
396
|
+
},
|
|
397
|
+
query: sessionData
|
|
398
|
+
});
|
|
399
|
+
if (!data) return;
|
|
400
|
+
return {
|
|
401
|
+
...session,
|
|
402
|
+
itemId: session.itemId,
|
|
403
|
+
data
|
|
404
|
+
};
|
|
405
|
+
} catch (e) {
|
|
406
|
+
console.error(e);
|
|
407
|
+
// WARNING: this is probably an invalid configuration
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
async function hasInitFirstItemConditions(context) {
|
|
414
|
+
// do nothing if they aren't using this feature
|
|
415
|
+
if (!initFirstItem) return false;
|
|
416
|
+
|
|
417
|
+
// if they have a session, there is no initialisation necessary
|
|
418
|
+
if (context.session) return false;
|
|
419
|
+
const count = await context.sudo().db[listKey].count({});
|
|
420
|
+
return count === 0;
|
|
421
|
+
}
|
|
422
|
+
async function authMiddleware({
|
|
423
|
+
context,
|
|
424
|
+
wasAccessAllowed,
|
|
425
|
+
basePath
|
|
426
|
+
}) {
|
|
427
|
+
const {
|
|
428
|
+
req
|
|
429
|
+
} = context;
|
|
430
|
+
const {
|
|
431
|
+
pathname
|
|
432
|
+
} = new URL(req.url, 'http://_');
|
|
433
|
+
|
|
434
|
+
// redirect to init if initFirstItem conditions are met
|
|
435
|
+
if (pathname !== `${basePath}/init` && (await hasInitFirstItemConditions(context))) {
|
|
436
|
+
return {
|
|
437
|
+
kind: 'redirect',
|
|
438
|
+
to: `${basePath}/init`
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// redirect to / if attempting to /init and initFirstItem conditions are not met
|
|
443
|
+
if (pathname === `${basePath}/init` && !(await hasInitFirstItemConditions(context))) {
|
|
444
|
+
return {
|
|
445
|
+
kind: 'redirect',
|
|
446
|
+
to: basePath
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// don't redirect if we have access
|
|
451
|
+
if (wasAccessAllowed) return;
|
|
452
|
+
|
|
453
|
+
// otherwise, redirect to signin
|
|
454
|
+
return {
|
|
455
|
+
kind: 'redirect',
|
|
456
|
+
to: `${basePath}/signin`
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function defaultIsAccessAllowed({
|
|
460
|
+
session
|
|
461
|
+
}) {
|
|
462
|
+
return session !== undefined;
|
|
463
|
+
}
|
|
464
|
+
function defaultExtendGraphqlSchema(schema) {
|
|
465
|
+
return schema;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* withAuth
|
|
470
|
+
*
|
|
471
|
+
* Automatically extends your configuration with a prescriptive implementation.
|
|
472
|
+
*/
|
|
473
|
+
function withAuth(config) {
|
|
474
|
+
var _ui, _listConfig$graphql$s2, _listConfig$graphql2;
|
|
475
|
+
throwIfInvalidConfig(config);
|
|
476
|
+
let {
|
|
477
|
+
ui
|
|
478
|
+
} = config;
|
|
479
|
+
if (!((_ui = ui) !== null && _ui !== void 0 && _ui.isDisabled)) {
|
|
480
|
+
var _ui$basePath, _ui2;
|
|
481
|
+
const {
|
|
482
|
+
getAdditionalFiles = () => [],
|
|
483
|
+
isAccessAllowed = defaultIsAccessAllowed,
|
|
484
|
+
pageMiddleware,
|
|
485
|
+
publicPages = []
|
|
486
|
+
} = ui || {};
|
|
487
|
+
const authPublicPages = [`${(_ui$basePath = (_ui2 = ui) === null || _ui2 === void 0 ? void 0 : _ui2.basePath) !== null && _ui$basePath !== void 0 ? _ui$basePath : ''}/signin`];
|
|
488
|
+
ui = {
|
|
489
|
+
...ui,
|
|
490
|
+
publicPages: [...publicPages, ...authPublicPages],
|
|
491
|
+
getAdditionalFiles: async () => [...(await getAdditionalFiles()), ...authGetAdditionalFiles(config)],
|
|
492
|
+
isAccessAllowed: async context => {
|
|
493
|
+
if (await hasInitFirstItemConditions(context)) return true;
|
|
494
|
+
return isAccessAllowed(context);
|
|
495
|
+
},
|
|
496
|
+
pageMiddleware: async args => {
|
|
497
|
+
const shouldRedirect = await authMiddleware(args);
|
|
498
|
+
if (shouldRedirect) return shouldRedirect;
|
|
499
|
+
return pageMiddleware === null || pageMiddleware === void 0 ? void 0 : pageMiddleware(args);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (!config.session) throw new TypeError('Missing .session configuration');
|
|
504
|
+
const {
|
|
505
|
+
graphql
|
|
506
|
+
} = config;
|
|
507
|
+
const {
|
|
508
|
+
extendGraphqlSchema = defaultExtendGraphqlSchema
|
|
509
|
+
} = graphql !== null && graphql !== void 0 ? graphql : {};
|
|
510
|
+
const listConfig = config.lists[listKey];
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* extendGraphqlSchema
|
|
514
|
+
*
|
|
515
|
+
* Must be added to the extendGraphqlSchema config. Can be composed.
|
|
516
|
+
*/
|
|
517
|
+
const authGqlNames = getAuthGqlNames((_listConfig$graphql$s2 = (_listConfig$graphql2 = listConfig.graphql) === null || _listConfig$graphql2 === void 0 ? void 0 : _listConfig$graphql2.singular) !== null && _listConfig$graphql$s2 !== void 0 ? _listConfig$graphql$s2 : listKey);
|
|
518
|
+
const authExtendGraphqlSchema = getSchemaExtension({
|
|
519
|
+
authGqlNames,
|
|
520
|
+
listKey,
|
|
521
|
+
identityField,
|
|
522
|
+
secretField,
|
|
523
|
+
initFirstItem,
|
|
524
|
+
sessionData
|
|
525
|
+
});
|
|
526
|
+
return {
|
|
527
|
+
...config,
|
|
528
|
+
graphql: {
|
|
529
|
+
...config.graphql,
|
|
530
|
+
extendGraphqlSchema: schema => {
|
|
531
|
+
return extendGraphqlSchema(authExtendGraphqlSchema(schema));
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
ui,
|
|
535
|
+
session: authSessionStrategy(config.session),
|
|
536
|
+
lists: {
|
|
537
|
+
...config.lists,
|
|
538
|
+
[listKey]: {
|
|
539
|
+
...listConfig,
|
|
540
|
+
fields: {
|
|
541
|
+
...listConfig.fields
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
withAuth
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
exports.createAuth = createAuth;
|