Last active
March 14, 2022 21:49
-
-
Save abbaspour/8972d27a13df7052b8fcacf07e736214 to your computer and use it in GitHub Desktop.
Auth0 Change Password Hook to Revoke (Rotating) Refresh Tokens
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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