feat: Improve authentication adapter interface to support multi-factor authentication (MFA), authentication challenges, and provide a more powerful interface for writing custom authentication adapters (#8156)

This commit is contained in:
dblythy
2022-11-11 03:35:39 +11:00
committed by GitHub
parent 4eb5f28b04
commit 5bbf9cade9
20 changed files with 2391 additions and 264 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable indent */
import {
GraphQLID,
GraphQLObjectType,
@@ -140,11 +141,7 @@ const load = (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseGraphQLCla
...fields,
[field]: {
description: `This is the object ${field}.`,
type:
(className === '_User' && (field === 'username' || field === 'password')) ||
parseClass.fields[field].required
? new GraphQLNonNull(type)
: type,
type: parseClass.fields[field].required ? new GraphQLNonNull(type) : type,
},
};
} else {
@@ -352,6 +349,14 @@ const load = (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseGraphQLCla
const parseObjectFields = {
id: globalIdField(className, obj => obj.objectId),
...defaultGraphQLTypes.PARSE_OBJECT_FIELDS,
...(className === '_User'
? {
authDataResponse: {
description: `auth provider response when triggered on signUp/logIn.`,
type: defaultGraphQLTypes.OBJECT,
},
}
: {}),
};
const outputFields = () => {
return classOutputFields.reduce((fields, field) => {

View File

@@ -41,7 +41,7 @@ const load = parseGraphQLSchema => {
req: { config, auth, info },
});
const { sessionToken, objectId } = await objectsMutations.createObject(
const { sessionToken, objectId, authDataResponse } = await objectsMutations.createObject(
'_User',
parseFields,
config,
@@ -50,9 +50,15 @@ const load = parseGraphQLSchema => {
);
context.info.sessionToken = sessionToken;
const viewer = await getUserFromSessionToken(
context,
mutationInfo,
'viewer.user.',
objectId
);
if (authDataResponse && viewer.user) viewer.user.authDataResponse = authDataResponse;
return {
viewer: await getUserFromSessionToken(context, mutationInfo, 'viewer.user.', objectId),
viewer,
};
} catch (e) {
parseGraphQLSchema.handleError(e);
@@ -111,7 +117,7 @@ const load = parseGraphQLSchema => {
req: { config, auth, info },
});
const { sessionToken, objectId } = await objectsMutations.createObject(
const { sessionToken, objectId, authDataResponse } = await objectsMutations.createObject(
'_User',
{ ...parseFields, authData },
config,
@@ -120,9 +126,15 @@ const load = parseGraphQLSchema => {
);
context.info.sessionToken = sessionToken;
const viewer = await getUserFromSessionToken(
context,
mutationInfo,
'viewer.user.',
objectId
);
if (authDataResponse && viewer.user) viewer.user.authDataResponse = authDataResponse;
return {
viewer: await getUserFromSessionToken(context, mutationInfo, 'viewer.user.', objectId),
viewer,
};
} catch (e) {
parseGraphQLSchema.handleError(e);
@@ -146,6 +158,10 @@ const load = parseGraphQLSchema => {
description: 'This is the password used to log in the user.',
type: new GraphQLNonNull(GraphQLString),
},
authData: {
description: 'Auth data payload, needed if some required auth adapters are configured.',
type: OBJECT,
},
},
outputFields: {
viewer: {
@@ -155,14 +171,15 @@ const load = parseGraphQLSchema => {
},
mutateAndGetPayload: async (args, context, mutationInfo) => {
try {
const { username, password } = deepcopy(args);
const { username, password, authData } = deepcopy(args);
const { config, auth, info } = context;
const { sessionToken, objectId } = (
const { sessionToken, objectId, authDataResponse } = (
await usersRouter.handleLogIn({
body: {
username,
password,
authData,
},
query: {},
config,
@@ -173,8 +190,15 @@ const load = parseGraphQLSchema => {
context.info.sessionToken = sessionToken;
const viewer = await getUserFromSessionToken(
context,
mutationInfo,
'viewer.user.',
objectId
);
if (authDataResponse && viewer.user) viewer.user.authDataResponse = authDataResponse;
return {
viewer: await getUserFromSessionToken(context, mutationInfo, 'viewer.user.', objectId),
viewer,
};
} catch (e) {
parseGraphQLSchema.handleError(e);
@@ -355,6 +379,57 @@ const load = parseGraphQLSchema => {
true,
true
);
const challengeMutation = mutationWithClientMutationId({
name: 'Challenge',
description:
'The challenge mutation can be used to initiate an authentication challenge when an auth adapter needs it.',
inputFields: {
username: {
description: 'This is the username used to log in the user.',
type: GraphQLString,
},
password: {
description: 'This is the password used to log in the user.',
type: GraphQLString,
},
authData: {
description:
'Auth data allow to preidentify the user if the auth adapter needs preidentification.',
type: OBJECT,
},
challengeData: {
description:
'Challenge data payload, can be used to post data to auth providers to auth providers if they need data for the response.',
type: OBJECT,
},
},
outputFields: {
challengeData: {
description: 'Challenge response from configured auth adapters.',
type: OBJECT,
},
},
mutateAndGetPayload: async (input, context) => {
try {
const { config, auth, info } = context;
const { response } = await usersRouter.handleChallenge({
body: input,
config,
auth,
info,
});
return response;
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
});
parseGraphQLSchema.addGraphQLType(challengeMutation.args.input.type.ofType, true, true);
parseGraphQLSchema.addGraphQLType(challengeMutation.type, true, true);
parseGraphQLSchema.addGraphQLMutation('challenge', challengeMutation, true, true);
};
export { load };