Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions blocks/header/header.css
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,39 @@ header nav .nav-tools a.button:any-link:not(.nav-tool) {
transition: all 0.3s;
}

header nav .nav-tools a.button.nav-auth-link:any-link {
padding: 10px 14px;
min-height: 36px;
line-height: 1;
white-space: nowrap;
}

header nav .nav-auth-link .nav-auth-info {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
margin-left: 8px;
border: 1px solid currentcolor;
border-radius: 50%;
font-size: 10px;
font-weight: 700;
line-height: 1;
text-transform: none;
}

header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a {
display: block;
color: inherit;
text-decoration: none;
}

header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a:hover,
header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a:focus-visible {
color: var(--nav-hover-color);
}

header nav .nav-tools a.button:any-link:not(.nav-tool):hover {
background: var(--color-rust);
color: var(--color-light);
Expand Down Expand Up @@ -735,6 +768,12 @@ header nav .nav-tools .nav-language-menu a:focus-visible {
width: 16px;
height: 16px;
}

header nav .nav-tools a.button.nav-auth-link:any-link {
padding: 8px 10px;
font-size: var(--body-font-size-xs);
letter-spacing: 1px;
}
}

/* very small phones */
Expand Down Expand Up @@ -1168,6 +1207,10 @@ header nav .nav-tools .nav-language-menu a:focus-visible {
width: 190px;
}

header nav .nav-tools a.button.nav-auth-link:any-link {
padding: 12px 18px;
}

header nav .nav-tools .nav-search-input:hover {
border-color: light-dark(rgb(0 0 0 / 24%), rgb(255 255 255 / 30%));
}
Expand All @@ -1177,6 +1220,57 @@ header nav .nav-tools .nav-language-menu a:focus-visible {
}
}

@media (width <= 899px) {
header nav .nav-tools .nav-auth-desktop {
display: none;
}

header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item {
border-bottom: 0;
padding: var(--space-m) 0;
text-align: center;
}

header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p {
margin: 0;
padding: 0;
justify-content: center;
}

header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a.button.nav-auth-mobile:any-link {
display: inline-flex;
align-items: center;
justify-content: center;
border: 1.5px solid var(--color-rust);
border-radius: 4px;
background: transparent;
color: var(--color-rust);
padding: 12px 24px;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
line-height: 1;
min-width: 120px;
text-decoration: none;
}

header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a.button.nav-auth-mobile:any-link:hover,
header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a.button.nav-auth-mobile:any-link:focus-visible {
background: var(--color-rust);
color: var(--color-light);
transform: none;
box-shadow: none;
text-decoration: none;
}
}

@media (width >= 900px) {
header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item {
display: none;
}
}

/* large screens: 1200px */
@media (width >= 1200px) {
header nav {
Expand Down
100 changes: 100 additions & 0 deletions blocks/header/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
import { getMetadata } from '../../scripts/aem.js';
import { loadFragment } from '../fragment/fragment.js';
import { getBlockContext } from '../../scripts/shared.js';
import {
getLoginUrl,
getLogoutUrl,
getDefaultAuthLabel,
getSessionState,
} from '../../scripts/shared/auth-api.js';

const DESKTOP = window.matchMedia('(min-width: 900px)');
const THEME_KEY = 'diyfire-theme';
Expand Down Expand Up @@ -231,6 +237,99 @@ function getCookie(name) {
return decodeURIComponent(cookie.split('=').slice(1).join('='));
}

function hasCookieStartingWith(prefix) {
return document.cookie
.split(';')
.map((entry) => decodeURIComponent(entry.split('=')[0] || '').trim())
.some((cookieName) => cookieName.startsWith(prefix));
}

function isLoggedIn() {
return hasCookieStartingWith('CF_Authorization');
}

async function resolveAuthState() {
try {
const session = await getSessionState();
return {
authenticated: Boolean(session?.authenticated),
email: session?.email || '',
};
} catch (e) {
return {
authenticated: isLoggedIn(),
email: '',
};
}
}

function setAuthUserInfo(link, email) {
link.querySelector('.nav-auth-info')?.remove();
link.removeAttribute('title');
link.removeAttribute('data-auth-email');

if (!email) return;

link.dataset.authEmail = email;
link.setAttribute('title', email);
const info = document.createElement('span');
info.className = 'nav-auth-info';
info.setAttribute('aria-hidden', 'true');
info.setAttribute('title', email);
info.textContent = 'i';
link.append(info);
}

async function initAuth(nav, tools) {
const loginLabel = getDefaultAuthLabel('login');
const logoutLabel = getDefaultAuthLabel('logout');

const loginCandidate = tools.querySelector('a[href*="login" i], a[data-auth-link]');
const shouldCreateLink = !loginCandidate;

const desktopLink = loginCandidate || document.createElement('a');
if (shouldCreateLink) {
desktopLink.href = getLoginUrl();
desktopLink.className = 'button nav-auth-link nav-auth-desktop';
tools.append(desktopLink);
}

desktopLink.dataset.authLink = 'true';
if (!desktopLink.classList.contains('button')) desktopLink.classList.add('button');
desktopLink.classList.add('nav-auth-link', 'nav-auth-desktop');

let mobileLink = nav.querySelector('.nav-auth-mobile-item a');
if (!mobileLink) {
const mobileList = nav.querySelector('.nav-sections .default-content-wrapper > ul');
if (mobileList) {
const li = document.createElement('li');
li.className = 'nav-auth-mobile-item';
const p = document.createElement('p');
mobileLink = document.createElement('a');
mobileLink.className = 'button nav-auth-link nav-auth-mobile';
mobileLink.dataset.authLink = 'true';
p.append(mobileLink);
li.append(p);
mobileList.append(li);
}
}
if (mobileLink && !mobileLink.classList.contains('button')) mobileLink.classList.add('button');

const authState = await resolveAuthState();
const loggedIn = authState.authenticated;
const loginHref = getLoginUrl();
const logoutHref = getLogoutUrl();
const targetHref = loggedIn ? logoutHref : loginHref;
const label = loggedIn ? logoutLabel : loginLabel;

[desktopLink, mobileLink].filter(Boolean).forEach((link) => {
link.setAttribute('href', targetHref);
link.textContent = label;
link.setAttribute('aria-label', label);
setAuthUserInfo(link, loggedIn ? authState.email : '');
});
}

function ensureGoogleTranslateScript() {
if (window.__googleTranslateScriptLoaded) return Promise.resolve();
if (window.__googleTranslateScriptPromise) return window.__googleTranslateScriptPromise;
Expand Down Expand Up @@ -437,6 +536,7 @@ export default async function decorate(block) {
if (tools) {
initTheme(tools);
initSearch(tools);
await initAuth(nav, tools);
initLanguage(tools, eventRoot);
hydrateTranslateFromCookie();
}
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"build:json:definitions": "merge-json-cli -i \"ue/models/component-definition.json\" -o \"component-definition.json\"",
"build:json:filters": "merge-json-cli -i \"ue/models/component-filters.json\" -o \"component-filters.json\"",
"prepare": "husky",
"dev:auth": "wrangler dev --config ./workers/auth/wrangler.toml",
"deploy:auth": "wrangler deploy --config ./workers/auth/wrangler.toml",
"tail:auth": "wrangler tail --config ./workers/auth/wrangler.toml",
"dev:contact-us": "wrangler dev --config ./workers/contact_us/wrangler.toml",
"deploy:contact-us": "wrangler deploy --config ./workers/contact_us/wrangler.toml",
"tail:contact-us": "wrangler tail --config ./workers/contact_us/wrangler.toml"
Expand Down
41 changes: 41 additions & 0 deletions scripts/shared/auth-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const AUTH_ORIGIN = 'https://demo-bbird-auth.aem-poc-lab.workers.dev';
const AUTH_PATHS = {
login: '/auth/login',
logout: '/auth/logout',
session: '/auth/session',
};

const AUTH_LABELS = {
login: 'Login',
logout: 'Logout',
};

function authUrl(path) {
return new URL(path, AUTH_ORIGIN).toString();
}

export function getDefaultAuthLabel(type) {
return AUTH_LABELS[type] || '';
}

export function getLoginUrl(returnTo = window.location.href) {
const target = new URL(AUTH_PATHS.login, AUTH_ORIGIN);
target.searchParams.set('returnTo', returnTo);
return target.toString();
}

export function getLogoutUrl() {
return authUrl(AUTH_PATHS.logout);
}

export async function getSessionState() {
const response = await fetch(authUrl(AUTH_PATHS.session), {
method: 'GET',
credentials: 'include',
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new Error(`Auth session request failed: ${response.status}`);
}
return response.json();
}
58 changes: 58 additions & 0 deletions workers/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Auth Test Worker

Lightweight Cloudflare Worker to drive login/logout/session state for the header auth button.

## Endpoints

- `GET /auth/login?returnTo=<url-or-path>` -> triggers Access and redirects to `returnTo`
- `GET /auth/logout` -> logs out via app domain `/cdn-cgi/access/logout` (logout success page)
- `GET /auth/session` -> returns `{ authenticated, email, hasJwtAssertion }`

## Setup

1. Make sure your Cloudflare Access application protects the same hostname and `/auth/*` path.
2. Use the configured worker name in `wrangler.toml` (`demo-bbird-auth`) or change it.
3. Deploy from the project root:

```bash
npm install
npm run deploy:auth
```

## Local dev

```bash
npm run dev:auth
```

## Test flow

1. Open an incognito window.
2. Visit the login endpoint, for example:

```text
https://demo-bbird-auth.aem-poc-lab.workers.dev/auth/login?returnTo=http://localhost:3000/
```

3. Enter an allowed email address on the Access screen.
4. Enter the one-time PIN.
5. Confirm your site loads and `/auth/session` shows `authenticated: true`.

## Logout

Visit:

```text
https://demo-bbird-auth.aem-poc-lab.workers.dev/auth/logout
```

## Site integration

Header auth URLs are centralized in:

`scripts/shared/auth-api.js`

Update `AUTH_ORIGIN` there if your worker hostname changes.

Logout uses app-domain endpoint: `https://demo-bbird-auth.aem-poc-lab.workers.dev/cdn-cgi/access/logout`
without additional redirect parameters for demo simplicity.
Loading