Hey there @bbarnell welcome to the community!
We unfortunately don’t have a pre-existing example of the same functionality in an Action, but I was able to just test this code in my own environment and it seems to be functioning as expected:
const { ManagementClient } = require('auth0');
exports.onExecutePostLogin = async (event, api) => {
const fieldMapping = {
family_name: 'family_name',
given_name: 'given_name',
name: 'name',
nickname: 'nickname',
picture: 'picture'
};
if (needMigration(event.user)) {
const management = new ManagementClient({
domain: event.secrets.domain,
clientId: event.secrets.clientID,
clientSecret: event.secrets.clientSecret,
});
try {
const updatedUser = await management.updateUser(
{ id: event.user.user_id },
generateUserPayload(event.user)
);
updateActionUser(event.user, updatedUser);
} catch (err) {
console.error(err);
throw new Error('Failed to migrate user attributes.');
}
}
function needMigration(user) {
if (user.user_metadata) {
for (const key in fieldMapping) {
if (typeof user.user_metadata[fieldMapping[key]] === 'string') {
return true;
}
}
}
return false;
}
function generateUserPayload(user) {
const payload = { user_metadata: {} };
const userMetadata = user.user_metadata;
for (const key in fieldMapping) {
generateUserPayloadField(userMetadata, payload, key, fieldMapping[key]);
}
return payload;
}
function updateActionUser(user, updatedUser) {
for (const key in fieldMapping) {
if (typeof user.user_metadata[fieldMapping[key]] === 'string') {
user[key] = updatedUser[key];
delete user.user_metadata[fieldMapping[key]];
}
}
}
function generateUserPayloadField(userMetadata, payload, rootField, metadataField) {
if (typeof userMetadata[metadataField] === 'string') {
payload[rootField] = userMetadata[metadataField];
payload.user_metadata[metadataField] = null;
}
}
};
I attempted to mirror the Rule code as closely as possible - I will note that it might be worth looking into caching the Management API access token as well:
Hope this helps!