mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
auth
This commit is contained in:
@@ -24,6 +24,7 @@ import BookingContractPage from "./pages/bookings/BookingContractPage";
|
||||
import BookingRequestDetailPage from "./pages/bookings/BookingRequestDetailPage";
|
||||
import BookingRequestsPage from "./pages/bookings/BookingRequestsPage";
|
||||
import NewBookingPage from "./pages/bookings/NewBookingPage";
|
||||
import UserManagementHostPage from "./pages/dashboard/user-management/UserManagementHostPage";
|
||||
import DemoUser1Page from "./pages/dashboard/demo/DemoUser1Page";
|
||||
import DemoUser2Page from "./pages/dashboard/demo/DemoUser2Page";
|
||||
import OverviewPage from "./pages/dashboard/OverviewPage";
|
||||
@@ -298,6 +299,10 @@ const App = () => {
|
||||
<Route path="containers" element={<FleetResourcePage />} />
|
||||
<Route path="cargoes" element={<FleetResourcePage />} />
|
||||
|
||||
{/* iframe-based user management module */}
|
||||
<Route path="um/*" element={<UserManagementHostPage />} />
|
||||
|
||||
{/* Legacy embedded user management routes */}
|
||||
<Route path="user-management" element={<UserManagementPage />} />
|
||||
<Route path="user-management/users" element={<UsersPage />} />
|
||||
<Route path="user-management/position-types" element={<PositionTypesPage />} />
|
||||
|
||||
117
apps/edr-freight-web/backoffice/src/auth/tokenManager.ts
Normal file
117
apps/edr-freight-web/backoffice/src/auth/tokenManager.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
export interface TokenMessage {
|
||||
type: 'UM_AUTH_TOKEN';
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages authentication tokens received from parent window (host)
|
||||
* Used by iframe modules to receive and store auth tokens
|
||||
*/
|
||||
class TokenManager {
|
||||
private token: string | null = null;
|
||||
private refreshToken: string | null = null;
|
||||
private tokenResolve: ((token: string) => void) | null = null;
|
||||
private tokenPromise: Promise<string>;
|
||||
private isFramed: boolean;
|
||||
|
||||
constructor() {
|
||||
// Check if we're running in an iframe
|
||||
this.isFramed = window.self !== window.top;
|
||||
|
||||
// Create a promise that resolves when token is received
|
||||
this.tokenPromise = new Promise((resolve) => {
|
||||
this.tokenResolve = resolve;
|
||||
});
|
||||
|
||||
if (this.isFramed) {
|
||||
this.setupMessageListener();
|
||||
|
||||
// Request token after 2 seconds if not received
|
||||
setTimeout(() => {
|
||||
if (!this.token) {
|
||||
this.requestTokenFromHost();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
private setupMessageListener() {
|
||||
window.addEventListener('message', (event) => {
|
||||
// Security: Only accept from parent window
|
||||
if (event.source !== window.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = event.data as TokenMessage | undefined;
|
||||
|
||||
if (data?.type === 'UM_AUTH_TOKEN') {
|
||||
this.token = data.token;
|
||||
this.refreshToken = data.refreshToken;
|
||||
|
||||
// Store in localStorage for persistence
|
||||
localStorage.setItem('um_auth_token', data.token);
|
||||
localStorage.setItem('um_refresh_token', data.refreshToken);
|
||||
|
||||
console.log('✅ Token received from host');
|
||||
|
||||
// Resolve the promise
|
||||
if (this.tokenResolve) {
|
||||
this.tokenResolve(data.token);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private requestTokenFromHost() {
|
||||
console.log('📤 Requesting token from host...');
|
||||
window.parent.postMessage(
|
||||
{ type: 'UM_REQUEST_AUTH' },
|
||||
window.location.origin
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token - waits for it if not yet received
|
||||
*/
|
||||
async getToken(): Promise<string> {
|
||||
if (this.token) {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
// Check localStorage as fallback
|
||||
const stored = localStorage.getItem('um_auth_token');
|
||||
if (stored) {
|
||||
this.token = stored;
|
||||
return stored;
|
||||
}
|
||||
|
||||
// Wait for token to arrive from parent
|
||||
return this.tokenPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token synchronously (returns null if not available)
|
||||
*/
|
||||
getTokenSync(): string | null {
|
||||
return this.token || localStorage.getItem('um_auth_token');
|
||||
}
|
||||
|
||||
getRefreshToken(): string | null {
|
||||
return this.refreshToken || localStorage.getItem('um_refresh_token');
|
||||
}
|
||||
|
||||
clearTokens() {
|
||||
this.token = null;
|
||||
this.refreshToken = null;
|
||||
localStorage.removeItem('um_auth_token');
|
||||
localStorage.removeItem('um_refresh_token');
|
||||
}
|
||||
|
||||
isInFrame(): boolean {
|
||||
return this.isFramed;
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const tokenManager = new TokenManager();
|
||||
@@ -0,0 +1,113 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { getCookie } from '@/auth/cookies';
|
||||
|
||||
function readToken(): string | null {
|
||||
return getCookie('auth-token') ?? null;
|
||||
}
|
||||
|
||||
function readRefreshToken(): string | null {
|
||||
return getCookie('refresh-token') ?? null;
|
||||
}
|
||||
|
||||
export default function UserManagementHostPage() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
|
||||
const mountBase = (
|
||||
(import.meta.env.VITE_USER_MANAGEMENT_BASE as string | undefined) ?? '/_um'
|
||||
).replace(/\/$/, '');
|
||||
|
||||
const moduleOrigin = window.location.origin;
|
||||
|
||||
const [iframeSrc] = useState(() => {
|
||||
const sub = location.pathname.replace(/^\/(?:dashboard\/)?um(?=\/|$)/, '');
|
||||
return mountBase + (sub || '/') + location.search;
|
||||
});
|
||||
|
||||
// ✅ Send token when iframe loads
|
||||
const handleIframeLoad = () => {
|
||||
const token = readToken();
|
||||
const refreshToken = readRefreshToken();
|
||||
const target = iframeRef.current?.contentWindow;
|
||||
|
||||
if (!token) {
|
||||
console.warn('⚠️ No authentication token found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
console.warn('⚠️ No iframe reference');
|
||||
return;
|
||||
}
|
||||
|
||||
target.postMessage(
|
||||
{
|
||||
type: 'UM_AUTH_TOKEN',
|
||||
token,
|
||||
refreshToken,
|
||||
},
|
||||
moduleOrigin
|
||||
);
|
||||
|
||||
console.log('✅ Token sent to iframe module');
|
||||
};
|
||||
|
||||
// ✅ Listen for messages from iframe
|
||||
useEffect(() => {
|
||||
const onMessage = (event: MessageEvent) => {
|
||||
// Security: Only accept from same origin
|
||||
if (event.origin !== moduleOrigin) {
|
||||
console.warn('🚫 Blocked message from different origin:', event.origin);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = event.data as { type?: string; path?: string } | undefined;
|
||||
if (!data) return;
|
||||
|
||||
// Handle auth request (if module asks for token again)
|
||||
if (data.type === 'UM_REQUEST_AUTH') {
|
||||
const token = readToken();
|
||||
const refreshToken = readRefreshToken();
|
||||
const target = iframeRef.current?.contentWindow;
|
||||
|
||||
if (token && target) {
|
||||
target.postMessage(
|
||||
{
|
||||
type: 'UM_AUTH_TOKEN',
|
||||
token,
|
||||
refreshToken,
|
||||
},
|
||||
moduleOrigin
|
||||
);
|
||||
console.log('✅ Token resent to iframe (on request)');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle route synchronization
|
||||
if (data.type === 'UM_ROUTE_CHANGED' && typeof data.path === 'string') {
|
||||
const target = '/dashboard/um' + data.path;
|
||||
if (window.location.pathname + window.location.search !== target) {
|
||||
navigate(target, { replace: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', onMessage);
|
||||
return () => window.removeEventListener('message', onMessage);
|
||||
}, [moduleOrigin, navigate]);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
title="User Management"
|
||||
src={iframeSrc}
|
||||
onLoad={handleIframeLoad}
|
||||
style={{ width: '100%', height: '100%', border: 0, display: 'block' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user