Skip to content

Commit

Permalink
Merge pull request #4303 from mozilla/MNTOR-2945
Browse files Browse the repository at this point in the history
MNTOR-2945: add del subscription api and unsub before deleting an user
  • Loading branch information
mansaj authored Mar 9, 2024
2 parents d9dd0b6 + 62149ac commit 2590fdf
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 13 deletions.
18 changes: 18 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,22 @@ export async function deleteAccount(
logger.info("deactivated_onerep_profile", {
subscriber_id: subscriber.id,
});

// try to unsubscribe from subplat
if (subscriber.fxa_access_token) {
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", {
subscriber_id: subscriber.id,
exception: ex,
});
}
}
}

// delete user events only have keys. Keys point to empty objects
Expand Down
3 changes: 2 additions & 1 deletion src/appConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ const optionalEnvVars = [
'RECRUITMENT_BANNER_LINK',
'RECRUITMENT_BANNER_TEXT',
'SENTRY_DSN_LEGACY',
'FALSE_DOOR_TEST_LINK_PHASE_ONE'
'FALSE_DOOR_TEST_LINK_PHASE_ONE',
'PREMIUM_PRODUCT_ID'
]

/** @type {Record<string, string>} */
Expand Down
97 changes: 85 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,83 @@ async function sendMetricsFlowPing (path) {
}
/* c8 ignore stop */

/**
* @param {string} bearerToken
* @returns {Promise<Array<any> | null>}
*/
// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function getSubscriptions(bearerToken) {
const subscriptionIdUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/subscriptions/active`
try {
const getResp = await fetch(subscriptionIdUrl, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${bearerToken}`
}
})

if (!getResp.ok) {
throw new InternalServerError(`bad response: ${getResp.status}`)
} else {
console.info(`get_fxa_subscriptions: success`)
return await getResp.json()
}
} catch (e) {
if (e instanceof Error) {
console.error('get_fxa_subscriptions', { stack: e.stack })
}
return null
}
}
/* c8 ignore stop */

/**
* @param {string} bearerToken
* @returns {Promise<boolean>}
*/
// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function deleteSubscription(bearerToken) {
try {
const subs = await getSubscriptions(bearerToken) ?? []
let subscriptionId;
for (const sub of subs) {
if (sub && sub.productId && sub.productId === AppConstants.PREMIUM_PRODUCT_ID) {
subscriptionId = sub.subscriptionId
}
}
if (subscriptionId) {
const deleteUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/subscriptions/active/${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())}`)
}
}
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 +242,7 @@ export {
revokeOAuthTokens,
getProfileData,
sendMetricsFlowPing,
getSha1
getSha1,
getSubscriptions,
deleteSubscription
}

0 comments on commit 2590fdf

Please sign in to comment.