diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Overview/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Overview/index.tsx index 97ae3d98f..94a820683 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Overview/index.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/Overview/index.tsx @@ -37,7 +37,7 @@ export default async function Overview({ return (
- {env.ENABLE_DTMC ? : null} + >C: Already authenticated (Curity) + U->>D: Click "Link Employment" → /[lang]/dtmc + D->>M: signIn("microsoft-entra-id") + M->>U: Microsoft login prompt + U->>M: Authenticate with corporate credentials + M->>J: Profile with user.employeeid + J->>J: Store employeeId directly in token + J->>S: Expose as session.employeeId + S->>H: Callback to /api/web/auth/dtmc + H->>H: Validate both Curity + DTMC sessions + H->>A: Call linkEmployeeToUser(employeeId) + A->>H: Success response + H->>U: Redirect to overview with success banner +``` + +### 3. Code Implementation Details + +#### DTMC Auth Configuration (`auth.dtmc.ts`) + +```typescript +// Separate session cookie to avoid conflicts +cookies: { + sessionToken: { + name: "dtmc.session-token", + }, +} + +// Short-lived session for security +session: { + strategy: "jwt", + maxAge: 10 * 60, // 10 minutes +} + +// Storage of employeeId +jwt({ account, profile, token }) { + if (account?.provider === "microsoft-entra-id") { + const employeeId = profile["user.employeeid"] + return { + access_token: "", // Empty to save cookie size + loginType: "dtmc", + employeeId, + } + } +} +``` + +#### Route Routing (`[...nextauth]/route.ts`) + +Routes Microsoft requests to DTMC handlers by checking if the pathname contains "microsoft-entra-id". + +#### Auth Initiation (`/dtmc/route.ts`) + +```typescript +// Starts Microsoft signin with callback redirect +const redirectUrl = await signIn( + "microsoft-entra-id", + { + redirectTo: `${env.PUBLIC_URL}${dtmcApiCallback}`, + redirect: false, + }, + { + prompt: "login", + } +) +``` + +#### Callback Handler (`/api/web/auth/dtmc/route.ts`) + +```typescript +// Validates dual sessions and processes linking +const dtmcSession = await dtmcAuth() // Microsoft session +const session = await auth() // Curity session + +// Both sessions required for security +if (!isValidSession(session) || !isValidSession(dtmcSession)) { + // Redirect to error page +} + +const employeeId = dtmcSession.employeeId +await linkEmployeeToUser(employeeId, accessToken) +``` + +## Routes & URLs + +### User-Facing Routes + +- `/[lang]/dtmc` - Microsoft auth initiation +- `/[lang]/link-employment-error` - Error handling page +- `/[lang]/employee-benefits` - Employee benefits overview + +### API Routes + +- `/api/web/auth/dtmc` - Employee linking callback handler +- `/api/web/auth/[...nextauth]` - NextAuth routing (handles Microsoft provider) + +## API Integration Details + +### TeamMemberCard Endpoint + +```typescript +// Added to packages/trpc/lib/api/endpoints.ts +export namespace v2 { + export namespace Profile { + export function teamMemberCard(employeeId: string) { + return `${profile}/${employeeId}/TeamMemberCard` + } + } +} +``` + +### Profile Schema Extension + +```typescript +// Added to server/routers/user/output.ts +export const employmentDetailsSchema = z + .object({ + employeeId: z.string(), + location: z.string(), + country: z.string(), + retired: z.boolean(), + }) + .optional() +``` + +### Employee Utility Functions + +```typescript +// Added to utils/user.ts +export function isEmployeeLinked(user: User): boolean +export function getEmployeeInfo(user: User) +export function canUseEmployeeBenefits(user: User): boolean +``` + +### Status Code Handling + +- **200/202**: Success → Continue to overview page +- **400/404**: Client errors → "unable_to_verify_employee_id" error page +- **500**: Server error → Default error message +- **Network errors**: → Default error message + +## Digital Team Member Card Behavior + +```typescript +// Current implementation +const hasEmploymentData = isEmployeeLinked(user) + +if (!hasEmploymentData) { + return null +} +return +``` + +## Testing Checklist + +### Happy Path + +- [x] User has valid Curity session +- [x] Microsoft auth extracts employee ID successfully +- [x] Both sessions validated in callback handler +- [x] Employee linking API call succeeds +- [x] User redirected to overview with success banner +- [x] Original Curity session remains functional +- [x] Digital Team Member Card displays with employment data + +### Error Scenarios + +- [x] Missing Curity session during flow +- [x] Microsoft auth failure/cancellation +- [x] Missing employee ID in Microsoft profile +- [x] Employee API failure scenarios +- [x] DTMC session expiration +- [x] Status code mapping (400/404/500/network errors) + +### Security Validation + +- [x] No employee ID in browser network logs +- [x] No employee ID in URL parameters +- [x] Dual session architecture working +- [x] Proper session validation in callback +- [x] Short-lived DTMC session (10 minutes) for security + +### UI/UX Validation + +- [x] Card shows real data when employment details exist +- [x] Proper fallbacks for missing employment data fields diff --git a/apps/scandic-web/env/server.ts b/apps/scandic-web/env/server.ts index 08c0f6f72..e452e5a4d 100644 --- a/apps/scandic-web/env/server.ts +++ b/apps/scandic-web/env/server.ts @@ -93,13 +93,6 @@ export const env = createEnv({ // transform to boolean .transform((s) => s === "true") .default("false"), - ENABLE_DTMC: z - .string() - // only allow "true" or "false" - .refine((s) => s === "true" || s === "false") - // transform to boolean - .transform((s) => s === "true") - .default("false"), SHOW_SITE_WIDE_ALERT: z .string() // only allow "true" or "false" @@ -228,7 +221,6 @@ export const env = createEnv({ GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID, USE_NEW_REWARD_MODEL: process.env.USE_NEW_REWARD_MODEL, ENABLE_SURPRISES: process.env.ENABLE_SURPRISES, - ENABLE_DTMC: process.env.ENABLE_DTMC, SHOW_SITE_WIDE_ALERT: process.env.SHOW_SITE_WIDE_ALERT, SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT, SENTRY_SERVER_SAMPLERATE: process.env.SENTRY_SERVER_SAMPLERATE, diff --git a/apps/scandic-web/middlewares/handleDTMC.ts b/apps/scandic-web/middlewares/handleDTMC.ts index 5bdf1a052..4cbe7282b 100644 --- a/apps/scandic-web/middlewares/handleDTMC.ts +++ b/apps/scandic-web/middlewares/handleDTMC.ts @@ -1,17 +1,10 @@ import { type NextMiddleware, NextResponse } from "next/server" import { handleDTMC } from "@/constants/routes/dtmc" -import { env } from "@/env/server" -import { notFound } from "@/server/errors/next" import type { MiddlewareMatcher } from "@/types/middleware" -export const middleware: NextMiddleware = (request) => { - if (!env.ENABLE_DTMC) { - throw notFound( - `ENABLE_DTMC is disabled, returning notFound for DTMC Route: ${request.nextUrl.pathname}` - ) - } +export const middleware: NextMiddleware = () => { return NextResponse.next() }