Skip to content

Commit

Permalink
feat(auth): improve cleeng session stablity
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed May 30, 2023
1 parent 79b6fc7 commit 245c0f8
Show file tree
Hide file tree
Showing 24 changed files with 906 additions and 341 deletions.
8 changes: 4 additions & 4 deletions src/containers/AccountModal/AccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const AccountModal = () => {
const viewParam = useQueryParam('u');
const [view, setView] = useState(viewParam);
const message = useQueryParam('message');
const { loading, auth } = useAccountStore(({ loading, auth }) => ({ loading, auth }), shallow);
const { loading, user } = useAccountStore(({ loading, user }) => ({ loading, user }), shallow);
const config = useConfigStore((s) => s.config);
const {
assets: { banner },
Expand All @@ -47,17 +47,17 @@ const AccountModal = () => {
}, [viewParam]);

useEffect(() => {
if (!!viewParam && !loading && !auth && !isPublicView) {
if (!!viewParam && !loading && !user && !isPublicView) {
navigate(addQueryParam(location, 'u', 'login'));
}
}, [viewParam, navigate, location, loading, auth, isPublicView]);
}, [viewParam, navigate, location, loading, user, isPublicView]);

const closeHandler = () => {
navigate(removeQueryParam(location, 'u'));
};

const renderForm = () => {
if (!auth && loading && !isPublicView) {
if (!user && loading && !isPublicView) {
return (
<div style={{ height: 300 }}>
<LoadingOverlay inline />
Expand Down
4 changes: 2 additions & 2 deletions src/containers/StartWatchingButton/StartWatchingButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const StartWatchingButton: React.VFC<Props> = ({ item, playUrl, disabled = false
const breakpoint = useBreakpoint();

// account
const auth = useAccountStore((state) => state.auth);
const isLoggedIn = !!auth;
const user = useAccountStore((state) => state.user);
const isLoggedIn = !!user;

// watch history
const watchHistoryItem = useWatchHistoryStore((state) => item && state.getItem(item));
Expand Down
10 changes: 5 additions & 5 deletions src/hooks/useAccount.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useAccountStore } from '#src/stores/AccountStore';
import type { AuthData, Customer, CustomerConsent } from '#types/account';
import type { Customer, CustomerConsent } from '#types/account';

function useAccount<T>(callback: (args: { customerId: string; customer: Customer; customerConsents: CustomerConsent[] | null; auth: AuthData }) => T): T {
const { user, auth, customerConsents } = useAccountStore.getState();
function useAccount<T>(callback: (args: { customerId: string; customer: Customer; customerConsents: CustomerConsent[] | null }) => T): T {
const { user, customerConsents } = useAccountStore.getState();

if (!user?.id || !auth?.jwt) throw new Error('user not logged in');
if (!user?.id) throw new Error('user not logged in');

return callback({ customerId: user.id, customer: user, auth, customerConsents });
return callback({ customerId: user.id, customer: user, customerConsents });
}

export default useAccount;
9 changes: 5 additions & 4 deletions src/hooks/useContentProtection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getJWPMediaToken, getMediaToken } from '#src/services/entitlement.servi
import { useConfigStore } from '#src/stores/ConfigStore';
import type { GetPlaylistParams } from '#types/playlist';
import type { GetMediaParams } from '#types/media';
import { useAccountStore } from '#src/stores/AccountStore';
import useService from '#src/hooks/useService';

const useContentProtection = <T>(
type: EntitlementType,
Expand All @@ -14,7 +14,7 @@ const useContentProtection = <T>(
enabled: boolean = true,
placeholderData?: T,
) => {
const jwt = useAccountStore((store) => store.auth?.jwt);
const accountService = useService(({ accountService }) => accountService);
const { configId, signingConfig, contentProtection, jwp, urlSigning } = useConfigStore(({ config }) => ({
configId: config.id,
signingConfig: config.contentSigningService,
Expand All @@ -28,11 +28,12 @@ const useContentProtection = <T>(

const { data: token, isLoading } = useQuery(
['token', type, id, params],
() => {
async () => {
// if provider is not JWP
if (!!id && !!host) {
const authData = await accountService.getAuthData();
const { host, drmPolicyId } = signingConfig;
return getMediaToken(host, id, jwt, params, drmPolicyId);
return getMediaToken(host, id, authData?.jwt, params, drmPolicyId);
}
// if provider is JWP
if (jwp && configId && !!id && signingEnabled) {
Expand Down
11 changes: 5 additions & 6 deletions src/hooks/useEntitlement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,10 @@ const useEntitlement: UseEntitlement = (playlistItem) => {

const { sandbox } = useClientIntegration();
const { accessModel } = useConfigStore();
const { user, subscription, auth } = useAccountStore(
({ user, subscription, auth }) => ({
const { user, subscription } = useAccountStore(
({ user, subscription }) => ({
user,
subscription,
auth,
}),
shallow,
);
Expand All @@ -53,14 +52,14 @@ const useEntitlement: UseEntitlement = (playlistItem) => {
const mediaEntitlementQueries = useQueries(
mediaOffers.map(({ offerId }) => ({
queryKey: ['entitlements', offerId],
queryFn: () => checkoutService?.getEntitlements({ offerId }, sandbox, auth?.jwt || ''),
enabled: !!playlistItem && !!auth?.jwt && !!offerId && !isPreEntitled,
queryFn: () => checkoutService?.getEntitlements({ offerId }, sandbox),
enabled: !!playlistItem && !!user && !!offerId && !isPreEntitled,
refetchOnMount: 'always' as const,
})),
);

// when the user is logged out the useQueries will be disabled but could potentially return its cached data
const isMediaEntitled = !!auth?.jwt && mediaEntitlementQueries.some((item) => item.isSuccess && (item.data as QueryResult)?.responseData?.accessGranted);
const isMediaEntitled = !!user && mediaEntitlementQueries.some((item) => item.isSuccess && (item.data as QueryResult)?.responseData?.accessGranted);
const isMediaEntitlementLoading = !isMediaEntitled && mediaEntitlementQueries.some((item) => item.isLoading);

const isEntitled = !!playlistItem && (isPreEntitled || isMediaEntitled);
Expand Down
96 changes: 57 additions & 39 deletions src/services/cleeng.account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import type {
GetCaptureStatusResponse,
Capture,
GetLocales,
RefreshToken,
LoginPayload,
RegisterPayload,
UpdateCaptureAnswersPayload,
Expand All @@ -31,8 +30,27 @@ import type {
ChangePasswordWithOldPassword,
UpdatePersonalShelves,
} from '#types/account';
import cleengAuthService from '#src/services/cleeng.auth.service';

export const setEnvironment = () => true;
const getCustomerIdFromAuthData = (auth: AuthData) => {
const decodedToken: JwtDetails = jwtDecode(auth.jwt);
return decodedToken.customerId;
};

export const initialize = async (config: Config, logoutCallback: () => Promise<void>) => {
await cleengAuthService.initialize(!!config.integrations.cleeng?.useSandbox, logoutCallback);
};

export const getAuthData = async () => {
if (cleengAuthService.tokens) {
return {
jwt: cleengAuthService.tokens.accessToken,
refreshToken: cleengAuthService.tokens.refreshToken,
} as AuthData;
}

return null;
};

export const login: Login = async ({ config, email, password }) => {
const payload: LoginPayload = {
Expand All @@ -45,11 +63,13 @@ export const login: Login = async ({ config, email, password }) => {
const { responseData: auth, errors }: ServiceResponse<AuthData> = await post(!!config.integrations.cleeng?.useSandbox, '/auths', JSON.stringify(payload));
handleErrors(errors);

const { user, customerConsents } = await getUser({ config, auth });
await cleengAuthService.setTokens({ accessToken: auth.jwt, refreshToken: auth.refreshToken });

const { user, customerConsents } = await getUser({ config });

return {
auth,
user,
auth,
customerConsents,
};
};
Expand All @@ -72,26 +92,34 @@ export const register: Register = async ({ config, email, password }) => {
const { responseData: auth, errors }: ServiceResponse<AuthData> = await post(!!config.integrations.cleeng?.useSandbox, '/customers', JSON.stringify(payload));
handleErrors(errors);

const { user, customerConsents } = await getUser({ config, auth });
await cleengAuthService.setTokens({ accessToken: auth.jwt, refreshToken: auth.refreshToken });

const { user, customerConsents } = await getUser({ config });

return {
auth,
user,
auth,
customerConsents,
};
};

export const logout = async () => true;
export const logout = async () => {
// clear the persisted access tokens
await cleengAuthService.clearTokens();
};

export async function getUser({ config }: { config: Config }) {
const authData = await getAuthData();

if (!authData) throw new Error('Not logged in');

const customerId = getCustomerIdFromAuthData(authData);
const { responseData: user, errors } = await getCustomer({ customerId }, !!config.integrations.cleeng?.useSandbox);

export async function getUser({ config, auth }: { config: Config; auth: AuthData }) {
const decodedToken: JwtDetails = jwtDecode(auth.jwt);
const customerId = decodedToken.customerId;
const { responseData: user, errors } = await getCustomer({ customerId }, !!config.integrations.cleeng?.useSandbox, auth.jwt);
handleErrors(errors);

const consentsPayload = {
config,
jwt: auth.jwt,
customer: user,
};

Expand All @@ -103,14 +131,6 @@ export async function getUser({ config, auth }: { config: Config; auth: AuthData
};
}

export const getFreshJwtToken = async ({ config, auth }: { config: Config; auth: AuthData }) => {
const response = await refreshToken({ refreshToken: auth.refreshToken }, !!config.integrations.cleeng?.useSandbox);

handleErrors(response.errors);

return response?.responseData;
};

export const getPublisherConsents: GetPublisherConsents = async (config) => {
const { cleeng } = config.integrations;
const response = await get(!!cleeng?.useSandbox, `/publishers/${cleeng?.id}/consents`);
Expand All @@ -123,10 +143,10 @@ export const getPublisherConsents: GetPublisherConsents = async (config) => {
};

export const getCustomerConsents: GetCustomerConsents = async (payload) => {
const { config, customer, jwt } = payload;
const { config, customer } = payload;
const { cleeng } = config.integrations;

const response: ServiceResponse<GetCustomerConsentsResponse> = await get(!!cleeng?.useSandbox, `/customers/${customer?.id}/consents`, jwt);
const response: ServiceResponse<GetCustomerConsentsResponse> = await get(!!cleeng?.useSandbox, `/customers/${customer?.id}/consents`, { authenticate: true });
handleErrors(response.errors);

return {
Expand All @@ -135,38 +155,40 @@ export const getCustomerConsents: GetCustomerConsents = async (payload) => {
};

export const updateCustomerConsents: UpdateCustomerConsents = async (payload) => {
const { config, customer, jwt } = payload;
const { config, customer } = payload;
const { cleeng } = config.integrations;

const params: UpdateCustomerConsentsPayload = {
id: customer.id,
consents: payload.consents,
};

const response: ServiceResponse<never> = await put(!!cleeng?.useSandbox, `/customers/${customer?.id}/consents`, JSON.stringify(params), jwt);
const response: ServiceResponse<never> = await put(!!cleeng?.useSandbox, `/customers/${customer?.id}/consents`, JSON.stringify(params), {
authenticate: true,
});
handleErrors(response.errors);

return await getCustomerConsents(payload);
};

export const getCaptureStatus: GetCaptureStatus = async ({ customer }, sandbox, jwt) => {
const response: ServiceResponse<GetCaptureStatusResponse> = await get(sandbox, `/customers/${customer?.id}/capture/status`, jwt);
export const getCaptureStatus: GetCaptureStatus = async ({ customer }, sandbox) => {
const response: ServiceResponse<GetCaptureStatusResponse> = await get(sandbox, `/customers/${customer?.id}/capture/status`, { authenticate: true });

handleErrors(response.errors);

return response;
};

export const updateCaptureAnswers: UpdateCaptureAnswers = async ({ customer, ...payload }, sandbox, jwt) => {
export const updateCaptureAnswers: UpdateCaptureAnswers = async ({ customer, ...payload }, sandbox) => {
const params: UpdateCaptureAnswersPayload = {
customerId: customer.id,
...payload,
};

const response: ServiceResponse<Capture> = await put(sandbox, `/customers/${customer.id}/capture`, JSON.stringify(params), jwt);
const response: ServiceResponse<Capture> = await put(sandbox, `/customers/${customer.id}/capture`, JSON.stringify(params), { authenticate: true });
handleErrors(response.errors);

const { responseData, errors } = await getCustomer({ customerId: customer.id }, sandbox, jwt);
const { responseData, errors } = await getCustomer({ customerId: customer.id }, sandbox);
handleErrors(errors);

return {
Expand All @@ -190,21 +212,17 @@ export const changePasswordWithOldPassword: ChangePasswordWithOldPassword = asyn
};
};

export const updateCustomer: UpdateCustomer = async (payload, sandbox, jwt) => {
export const updateCustomer: UpdateCustomer = async (payload, sandbox) => {
const { id, metadata, fullName, ...rest } = payload;
const params: UpdateCustomerPayload = {
id,
...rest,
};
return patch(sandbox, `/customers/${id}`, JSON.stringify(params), jwt);
};

export const getCustomer: GetCustomer = async (payload, sandbox, jwt) => {
return get(sandbox, `/customers/${payload.customerId}`, jwt);
return patch(sandbox, `/customers/${id}`, JSON.stringify(params), { authenticate: true });
};

export const refreshToken: RefreshToken = async (payload, sandbox) => {
return post(sandbox, '/auths/refresh_token', JSON.stringify(payload));
export const getCustomer: GetCustomer = async (payload, sandbox) => {
return get(sandbox, `/customers/${payload.customerId}`, { authenticate: true });
};

export const getLocales: GetLocales = async (sandbox) => {
Expand All @@ -217,8 +235,8 @@ const handleErrors = (errors: ApiResponse['errors']) => {
}
};

export const updatePersonalShelves: UpdatePersonalShelves = async (payload, sandbox, jwt) => {
return await updateCustomer(payload, sandbox, jwt);
export const updatePersonalShelves: UpdatePersonalShelves = async (payload, sandbox) => {
return await updateCustomer(payload, sandbox);
};
export const exportAccountData = () => null;

Expand Down
Loading

0 comments on commit 245c0f8

Please sign in to comment.