Skip to content

Commit

Permalink
Merge pull request #26311 from guardian/gb/okta-sign-in-status
Browse files Browse the repository at this point in the history
Okta: Header sign in status
  • Loading branch information
georgeblahblah authored Jul 18, 2023
2 parents 3143484 + dcbe222 commit 08466a3
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 40 deletions.
3 changes: 3 additions & 0 deletions common/app/views/support/JavaScriptPage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ object JavaScriptPage {
javascriptConfig ++ config ++ commercialMetaData ++ journalismMetaData ++ Map(
("edition", JsString(edition.id)),
("ajaxUrl", JsString(Configuration.ajax.url)),
// TODO: decide whether the value for `isDev` should be
// `environment.isDev` instead
("isDev", JsBoolean(!environment.isProd)),
("isProd", JsBoolean(Configuration.environment.isProd)),
("idUrl", JsString(Configuration.id.url)),
Expand All @@ -93,6 +95,7 @@ object JavaScriptPage {
("ipsosTag", JsString(ipsos)),
("isAdFree", JsBoolean(isAdFree(request))),
("commercialBundleUrl", commercialBundleUrl),
("stage", JsString(Configuration.environment.stage)),
)
}.toMap
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@guardian/commercial": "10.5.0",
"@guardian/consent-management-platform": "^13.5.0",
"@guardian/core-web-vitals": "^4.0.0",
"@guardian/identity-auth": "^0.2.0",
"@guardian/libs": "^14.0.0",
"@guardian/shimport": "^1.0.2",
"@guardian/source-foundations": "^10.0.0",
Expand Down
65 changes: 65 additions & 0 deletions static/src/javascripts/projects/common/modules/identity/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { AccessToken, IDToken } from '@guardian/identity-auth';
import { getCookie, storage } from '@guardian/libs';
import { mergeCalls } from 'common/modules/async-call-merger';
import { fetchJson } from '../../../../lib/fetch-json';
import { mediator } from '../../../../lib/mediator';
import { getUrlVars } from '../../../../lib/url';
import { createAuthenticationComponentEventParams } from './auth-component-event-params';
import type { CustomIdTokenClaims } from './okta';

// Types info coming from https://github.com/guardian/discussion-rendering/blob/fc14c26db73bfec8a04ff7a503ed9f90f1a1a8ad/src/types.ts

Expand Down Expand Up @@ -168,7 +170,70 @@ export const buildNewsletterUpdatePayload = (
return newsletter;
};

type SignedOutWithCookies = { kind: 'SignedOutWithCookies' };
export type SignedInWithCookies = { kind: 'SignedInWithCookies' };
type SignedOutWithOkta = { kind: 'SignedOutWithOkta' };
export type SignedInWithOkta = {
kind: 'SignedInWithOkta';
accessToken: AccessToken<never>;
idToken: IDToken<CustomIdTokenClaims>;
};

export type AuthStatus =
| SignedOutWithCookies
| SignedInWithCookies
| SignedOutWithOkta
| SignedInWithOkta;

// We want to be in the experiment if in the development environment
// or if we have opted in to the Okta server side experiment
const isInOktaExperiment =
window.guardian.config.page.stage === 'DEV' ||
window.guardian.config.tests?.oktaVariant === 'variant';

const getAuthStatus = async (): Promise<AuthStatus> => {
if (isInOktaExperiment) {
const { isSignedInWithOktaAuthState } = await import('./okta');
const authState = await isSignedInWithOktaAuthState();
if (authState.isAuthenticated) {
return {
kind: 'SignedInWithOkta',
accessToken: authState.accessToken,
idToken: authState.idToken,
};
} else {
return {
kind: 'SignedOutWithOkta',
};
}
} else {
if (isUserLoggedIn()) {
return {
kind: 'SignedInWithCookies',
};
} else {
return {
kind: 'SignedOutWithCookies',
};
}
}
};

export const isUserLoggedIn = (): boolean => getUserFromCookie() !== null;
export const isUserLoggedInOktaRefactor = (): Promise<boolean> => {
return new Promise((resolve) => {
void getAuthStatus().then((authStatus) => {
if (
authStatus.kind === 'SignedInWithCookies' ||
authStatus.kind === 'SignedInWithOkta'
) {
resolve(true);
} else {
resolve(false);
}
});
});
};

export const getUserFromApi = mergeCalls(
(mergingCallback: (u: IdentityUser | null) => void) => {
Expand Down
64 changes: 64 additions & 0 deletions static/src/javascripts/projects/common/modules/identity/okta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { CustomClaims, IdentityAuthState } from '@guardian/identity-auth';
import { IdentityAuth } from '@guardian/identity-auth';

// the `id_token.profile.theguardian` scope is used to get custom claims
export type CustomIdTokenClaims = CustomClaims & {
email: string;
braze_uuid: string;
};

const getStage = () => {
return window.guardian.config.page.stage;
};

const getIssuer = (stage: Stage) =>
stage === 'PROD'
? 'https://profile.theguardian.com/oauth2/aus3xgj525jYQRowl417'
: 'https://profile.code.dev-theguardian.com/oauth2/aus3v9gla95Toj0EE0x7';

const getClientId = (stage: Stage) =>
stage === 'PROD' ? '0oa79m1fmgzrtaHc1417' : '0oa53x6k5wGYXOGzm0x7';

const getRedirectUri = (stage: Stage) => {
switch (stage) {
case 'PROD':
return 'https://www.theguardian.com/';
case 'CODE':
return 'https://m.code.dev-theguardian.com/';
case 'DEV':
default:
return 'http://localhost:9000/';
}
};

let identityAuth: IdentityAuth<never, CustomIdTokenClaims> | undefined;

function getIdentityAuth() {
if (identityAuth === undefined) {
const stage = getStage();

identityAuth = new IdentityAuth<never, CustomIdTokenClaims>({
issuer: getIssuer(stage),
clientId: getClientId(stage),
redirectUri: getRedirectUri(stage),
scopes: [
'openid', // required for open id connect, returns an id token
'profile', // populates the id token with basic profile information
'email', // populates the id token with the user's email address
'guardian.discussion-api.private-profile.read.self', // allows the access token to be used to make requests to the discussion api to read the user's profile
'guardian.discussion-api.update.secure', // allows the access token to be used to make requests to the discussion api to post comments, upvote etc
'guardian.identity-api.newsletters.read.self', // allows the access token to be used to make requests to the identity api to read the user's newsletter subscriptions
'guardian.identity-api.newsletters.update.self', // allows the access token to be used to make requests to the identity api to update the user's newsletter subscriptions
'guardian.members-data-api.read.self', // allows the access token to be used to make requests to the members data api to read the user's membership status
'id_token.profile.theguardian', // populates the id token with application specific profile information
],
});
}
return identityAuth;
}

export async function isSignedInWithOktaAuthState(): Promise<
IdentityAuthState<never, CustomIdTokenClaims>
> {
return getIdentityAuth().isSignedInWithAuthState();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { OphanComponent, OphanComponentType } from '@guardian/libs';
import { getUserFromCookie, isUserLoggedIn } from 'common/modules/identity/api';
import {
getUserFromCookie,
isUserLoggedInOktaRefactor,
} from 'common/modules/identity/api';
import fastdom from 'lib/fastdom-promise';
import { bufferedNotificationListener } from '../bufferedNotificationListener';
import {
Expand Down Expand Up @@ -198,48 +201,49 @@ const addNotifications = (notifications: HeaderNotification[]): void => {
};

const showMyAccountIfNecessary = (): void => {
if (!isUserLoggedIn()) {
return;
}

void fastdom
.measure(() => ({
signIns: Array.from(
document.querySelectorAll('.js-navigation-sign-in'),
),
accountActionsLists: Array.from(
document.querySelectorAll('.js-navigation-account-actions'),
),
commentItems: Array.from(
document.querySelectorAll('.js-show-comment-activity'),
),
}))
.then((els) => {
const { signIns, accountActionsLists, commentItems } = els;
return fastdom
.mutate(() => {
signIns.forEach((signIn) => {
signIn.remove();
});
accountActionsLists.forEach((accountActions) => {
accountActions.classList.remove('is-hidden');
});
void isUserLoggedInOktaRefactor().then((isLoggedIn) => {
if (!isLoggedIn) return;
void fastdom
.measure(() => ({
signIns: Array.from(
document.querySelectorAll('.js-navigation-sign-in'),
),
accountActionsLists: Array.from(
document.querySelectorAll('.js-navigation-account-actions'),
),
commentItems: Array.from(
document.querySelectorAll('.js-show-comment-activity'),
),
}))
.then((els) => {
const { signIns, accountActionsLists, commentItems } = els;
return fastdom
.mutate(() => {
signIns.forEach((signIn) => {
signIn.remove();
});
accountActionsLists.forEach((accountActions) => {
accountActions.classList.remove('is-hidden');
});

Array.from(
document.querySelectorAll('.js-user-account-trigger'),
).forEach((accountTrigger) => {
accountTrigger.classList.remove('is-hidden');
});
})
.then(() => {
updateCommentLink(commentItems);
Array.from(
document.querySelectorAll(
'.js-user-account-trigger',
),
).forEach((accountTrigger) => {
accountTrigger.classList.remove('is-hidden');
});
})
.then(() => {
updateCommentLink(commentItems);

bufferedNotificationListener.on((event) => {
const notifications = event.detail;
addNotifications(notifications);
bufferedNotificationListener.on((event) => {
const notifications = event.detail;
addNotifications(notifications);
});
});
});
});
});
});
};

export { showMyAccountIfNecessary };
2 changes: 2 additions & 0 deletions static/src/javascripts/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ interface LightboxImages {
images: Array<{ src: string }>;
}

type Stage = 'DEV' | 'CODE' | 'PROD';
interface PageConfig extends CommercialPageConfig {
ajaxUrl?: string; // https://github.com/guardian/frontend/blob/33db7bbd/common/app/views/support/JavaScriptPage.scala#L72
assetsPath: string;
Expand Down Expand Up @@ -170,6 +171,7 @@ interface PageConfig extends CommercialPageConfig {
videoDuration: number;
webPublicationDate: number;
userAttributesApiUrl?: string;
stage: Stage;
}

interface Ophan {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1618,6 +1618,11 @@
eslint-plugin-import "2.24.0"
eslint-plugin-prettier "3.4.0"

"@guardian/identity-auth@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@guardian/identity-auth/-/identity-auth-0.2.0.tgz#aaad1667095c5ba4bc7610e1a119ec007c249307"
integrity sha512-4bKanidGEH+VKaYQNNcS2y8ubmxfzMicr+wiIDsdosxicn3mLA1Hw0kHiaZm1vroo3JKp4442qVba3Z8Ot5vPA==

"@guardian/libs@^10.0.0":
version "10.1.1"
resolved "https://registry.yarnpkg.com/@guardian/libs/-/libs-10.1.1.tgz#7048c365a68dda068f707d46c78979f430221963"
Expand Down

0 comments on commit 08466a3

Please sign in to comment.