Skip to content

Instantly share code, notes, and snippets.

@abbaspour
Last active March 14, 2022 21:49
Show Gist options
  • Save abbaspour/8972d27a13df7052b8fcacf07e736214 to your computer and use it in GitHub Desktop.
Save abbaspour/8972d27a13df7052b8fcacf07e736214 to your computer and use it in GitHub Desktop.
Auth0 Change Password Hook to Revoke (Rotating) Refresh Tokens
module.exports = function (user, context, cb) {
const ManagementClient = require("[email protected]").ManagementClient;
const auth0 = new ManagementClient({
domain: context.connection.tenant + '.eu.auth0.com',
clientId: context.webtask.secrets.delete_refresh_token_client_id,
clientSecret: context.webtask.secrets.delete_refresh_token_client_secret,
scope: 'read:device_credentials delete:device_credentials'
});
const getRefreshTokens = type => auth0.deviceCredentials.getAll({ user_id: user.id, type: type });
const deleteRefreshToken = id => auth0.deviceCredentials.delete({ id: id })
.then(() => console.log("Deleted " + id));
const deleteAllRefreshTokensOfType = type => Promise.resolve()
.then(() => console.log(">>> Deleting " + type))
.then(() => getRefreshTokens(type))
.then(tokens => { console.log("Got tokens: " + tokens.length); return tokens; })
.then(tokens => tokens.map(x => deleteRefreshToken(x.id)))
.then(deletePromises => Promise.all(deletePromises))
.then(() => console.log("done"));
Promise.resolve()
.then(() => deleteAllRefreshTokensOfType("refresh_token"))
.then(() => deleteAllRefreshTokensOfType("rotating_refresh_token"))
.catch(e => console.log(e))
.then(() => cb());
};
const rp = require('request-promise');
const logScope = 'post-user-change-password-hook';
let webtaskId;
function trace(message, userId, email) {
console.log(
JSON.stringify({
date: new Date().toISOString(),
requestId: webtaskId,
message: `[${logScope}][${message}]`,
userId: userId,
email: email,
})
);
}
async function getDeviceCredentials(auth0Domain, user_id, tokenV2) {
try {
const result = await rp({
method: 'GET',
url: `https://${auth0Domain}/api/v2/device-credentials?user_id=${user_id}&type=refresh_token`, // OR type=rotating_refresh_token
headers: {
Authorization: 'Bearer ' + tokenV2,
},
json: true,
});
if (result && result.length === 0) {
trace(`getDeviceCredentials][No devices found for user: ${user_id}`, user_id, '');
return false;
}
return result;
} catch (e) {
trace(`getDeviceCredentials][FAILED for user: ${user_id}`, user_id, '');
return false;
}
}
async function removeTokenForDevices(devices, auth0Domain, tokenV2, user_id) {
const successList = devices.map(async device => {
trace(`removeTokenForDevices][Deleting device: ${device.id} for user: ${user_id}`, user_id, '');
const isSuccess = await removeRefreshToken(auth0Domain, tokenV2, user_id, device);
return isSuccess;
});
return successList;
}
async function removeRefreshToken(auth0Domain, tokenV2, user_id, device) {
try {
const result = await rp({
method: 'DELETE',
url: `https://${auth0Domain}/api/v2/device-credentials/${device.id}`,
headers: {
Authorization: `Bearer ${tokenV2}`,
},
resolveWithFullResponse: true,
});
trace(`removeRefreshToken][Removed refresh Token for user ${user_id} device: ${device.id}`, user_id, '');
return true;
} catch (e) {
trace(`removeRefreshToken][Remove refresh Token failed for user ${user_id} device: ${device.id}`, user_id, '');
return false;
}
}
function getAuth0URL(tenant) {
return `${tenant}.au.auth0.com`;
}
async function getTokenFromAuth0(auth0Config) {
try {
const { access_token } = await rp({
method: 'POST',
url: `https://${auth0Config.domain}/oauth/token`,
headers: { 'content-type': 'application/json' },
body: {
grant_type: 'client_credentials',
client_id: auth0Config.client_id,
client_secret: auth0Config.client_secret,
audience: `https://${auth0Config.domain}/api/v2/`,
},
json: true,
});
if (!access_token) {
trace(`getTokenFromAuth0][Result does not contain body or access_token`, '', '');
throw new Error('Cannot get Token from Auth0');
}
trace(`getTokenFromAuth0][Token: ${access_token}`, '', '');
return access_token;
} catch (e) {
trace(`getTokenFromAuth0][Error: ${e}`, '', '');
throw new Error('Cannot get Token from Auth0');
}
}
async function searchAuth0(auth0Domain, user_identifier, tokenV2) {
try {
const queryString = `identities.user_id:"${user_identifier}"`;
// Search for the user
const result = await rp({
method: 'GET',
url: `https://${auth0Domain}/api/v2/users`,
headers: {
Authorization: `Bearer ${tokenV2}`,
},
json: true,
qs: {
search_engine: 'v3',
q: queryString,
},
});
if (result.statusCode > 200) {
trace(`searchAuth0][FAILED for user_id: ${user_identifier}`, user_identifier, '');
throw new Error('Cannot get user(s) Auth0');
}
return result;
} catch (e) {
trace(`searchAuth0][FAILED for user_id: ${user_identifier}`, user_identifier, '');
throw new Error('Cannot get user(s) Auth0');
}
}
const changePasswordHook = async function(user, context, cb) {
const { email, id } = user;
if (context && context.webtask) {
webtaskId = context.webtask.id;
}
trace(`Called from hook: Starting Processing for user ${email}`, '', email);
const auth0Domain = getAuth0URL(context.webtask.body.context.connection.tenant);
const clientId = context.webtask.secrets.AUTH0_CLIENT_ID;
const clientSecret = context.webtask.secrets.AUTH0_CLIENT_SECRET;
const auth0Config = {
client_id: clientId,
client_secret: clientSecret,
domain: auth0Domain,
};
// Get API Token
try {
const tokenV2 = await getTokenFromAuth0(auth0Config);
const users = await searchAuth0(auth0Domain, id, tokenV2);
if (!users || users.length === 0) {
trace(`Search Auth0 users failed for email: ${email}, user_id: ${id}`, id, email);
}
trace(`Search Auth0 success for email: ${email}, user_id: ${id} ][${users.length} users found`, id, email);
users.map(async user => {
const devices = await getDeviceCredentials(auth0Domain, user.user_id, tokenV2);
if (devices) {
await removeTokenForDevices(devices, auth0Domain, tokenV2, user.user_id);
}
});
} catch (e) {
return cb(e);
}
cb(null, {});
};
changePasswordHook.methods = {
getDeviceCredentials,
removeTokenForDevices,
removeRefreshToken,
getAuth0URL,
getTokenFromAuth0,
searchAuth0,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment