Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MNTOR-2945: add del subscription api and unsub before deleting an user #4303

Merged
merged 13 commits into from
Mar 9, 2024
16 changes: 16 additions & 0 deletions src/app/functions/server/deleteAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "../../../db/tables/subscribers";
import { deactivateProfile } from "./onerep";
import { SerializedSubscriber } from "../../../next-auth";
import { deleteSubscription } from "../../../utils/fxa";

export async function deleteAccount(
subscriber: SubscriberRow | SerializedSubscriber,
Expand All @@ -21,6 +22,7 @@ export async function deleteAccount(
// get profile id
const oneRepProfileId = await getOnerepProfileId(subscriber.id);
if (oneRepProfileId) {
// try to deactivate onerep profile
try {
await deactivateProfile(oneRepProfileId);
} catch (ex) {
Expand All @@ -37,6 +39,20 @@ export async function deleteAccount(
logger.info("deactivated_onerep_profile", {
subscriber_id: subscriber.id,
});

// try to unsubscribe from subplat
mansaj marked this conversation as resolved.
Show resolved Hide resolved
try {
const isDeleted = await deleteSubscription(subscriber.fxa_access_token);
logger.info("unsubscribe_from_subplat", {
subscriber_id: subscriber.id,
success: isDeleted,
});
} catch (ex) {
logger.error("unsubscribe_from_subplat", {
mansaj marked this conversation as resolved.
Show resolved Hide resolved
subscriber_id: subscriber.id,
exception: ex,
});
}
}

// delete user events only have keys. Keys point to empty objects
Expand Down
72 changes: 60 additions & 12 deletions src/utils/fxa.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import AppConstants from '../appConstants.js'
// to abstract fetching API endpoints from the OAuth server (instead
// of specifying them in the environment) in the future.
const FxAOAuthUtils = {
get authorizationUri () { return AppConstants.OAUTH_AUTHORIZATION_URI },
get tokenUri () { return AppConstants.OAUTH_TOKEN_URI },
get authorizationUri() { return AppConstants.OAUTH_AUTHORIZATION_URI },
get tokenUri() { return AppConstants.OAUTH_TOKEN_URI },
// TODO: Add unit test when changing this code:
/* c8 ignore next */
get profileUri () { return AppConstants.OAUTH_PROFILE_URI }
get profileUri() { return AppConstants.OAUTH_PROFILE_URI }
}

const FxAOAuthClient = new ClientOAuth2({
Expand All @@ -35,7 +35,7 @@ const FxAOAuthClient = new ClientOAuth2({
*/
// TODO: Add unit test when changing this code:
/* c8 ignore start */
async function postTokenRequest (path, token) {
async function postTokenRequest(path, token) {
const fxaTokenOrigin = new URL(AppConstants.OAUTH_TOKEN_URI).origin
const tokenUrl = `${fxaTokenOrigin}${path}`
const tokenBody = (typeof token === 'object') ? token : { token }
Expand Down Expand Up @@ -65,7 +65,7 @@ async function postTokenRequest (path, token) {
*/
// TODO: Add unit test when changing this code:
/* c8 ignore start */
async function verifyOAuthToken (token) {
async function verifyOAuthToken(token) {
try {
const response = await postTokenRequest('/v1/verify', token)
return response
Expand All @@ -84,7 +84,7 @@ async function verifyOAuthToken (token) {
*/
// TODO: Add unit test when changing this code:
/* c8 ignore start */
async function destroyOAuthToken (token) {
async function destroyOAuthToken(token) {
const tokenBody = {
...token,
client_id: AppConstants.OAUTH_CLIENT_ID,
Expand All @@ -106,7 +106,7 @@ async function destroyOAuthToken (token) {
*/
// TODO: Add unit test when changing this code:
/* c8 ignore next 4 */
async function revokeOAuthTokens (subscriber) {
async function revokeOAuthTokens(subscriber) {
await destroyOAuthToken({ token: subscriber.fxa_access_token, token_type_hint: "access_token" })
await destroyOAuthToken({ token: subscriber.fxa_refresh_token, token_type_hint: "refresh_token" })
}
Expand All @@ -116,7 +116,7 @@ async function revokeOAuthTokens (subscriber) {
*/
// TODO: Add unit test when changing this code:
/* c8 ignore start */
async function getProfileData (accessToken) {
async function getProfileData(accessToken) {
try {
const response = await fetch(FxAOAuthUtils.profileUri, {
headers: { Authorization: `Bearer ${accessToken}` }
Expand All @@ -135,9 +135,9 @@ async function getProfileData (accessToken) {
/**
* @param {string} path
*/
// TODO: Add unit test when changing this code:
// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function sendMetricsFlowPing (path) {
async function sendMetricsFlowPing(path) {
const fxaMetricsFlowUrl = new URL(path, AppConstants.NEXT_PUBLIC_FXA_SETTINGS_URL)
try {
const response = await fetch(fxaMetricsFlowUrl, {
Expand All @@ -155,12 +155,59 @@ async function sendMetricsFlowPing (path) {
}
/* c8 ignore stop */

/**
* @param {string | null} bearerToken
*/
// TODO: Add unit test when changing this code:
mansaj marked this conversation as resolved.
Show resolved Hide resolved
/* c8 ignore start */
async function deleteSubscription(bearerToken) {
if (!bearerToken) {
console.error('delete_fxa_subscription: bearerToken cannot be empty')
return false
}
const subscriptionIdUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/subscriptions/active`
try {
const getResp = await fetch(subscriptionIdUrl, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${bearerToken}`
}
})

const subs = await getResp.json()
const subscriptionId = subs?.[0]?.subscriptionId
mansaj marked this conversation as resolved.
Show resolved Hide resolved
if (subscriptionId) {

const deleteUrl = `${subscriptionIdUrl}/${subscriptionId}`
const response = await fetch(deleteUrl, {
method: "DELETE",
headers: {
Accept: 'application/json',
Authorization: `Bearer ${bearerToken}`
}
})
if (!response.ok) {
throw new InternalServerError(`bad response: ${response.status}`)
} else {
console.info(`delete_fxa_subscription: success - ${JSON.stringify(await response.json())}`)
mansaj marked this conversation as resolved.
Show resolved Hide resolved
}
}
return true
} catch (e) {
if (e instanceof Error) {
console.error('delete_fxa_subscription', { stack: e.stack })
}
return false
}
}
/* c8 ignore stop */

/**
* @param {crypto.BinaryLike} email
*/
// TODO: Add unit test when changing this code:
/* c8 ignore next 3 */
function getSha1 (email) {
function getSha1(email) {
return crypto.createHash('sha1').update(email).digest('hex')
}

Expand All @@ -171,5 +218,6 @@ export {
revokeOAuthTokens,
getProfileData,
sendMetricsFlowPing,
getSha1
getSha1,
deleteSubscription
}
Loading