diff --git a/src/front/Api/Auth/IdNot/index.ts b/src/front/Api/Auth/IdNot/index.ts index 9b385477..77063188 100644 --- a/src/front/Api/Auth/IdNot/index.ts +++ b/src/front/Api/Auth/IdNot/index.ts @@ -33,13 +33,13 @@ export default class Auth extends BaseApiService { ? `${window.location.origin}/authorized-client` : `${variables.FRONT_APP_HOST}/authorized-client`; - // Resolve backend base for calling the state endpoint (prefer explicit BACK_BASE) - const backBase = variables.BACK_BASE || `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}${variables.BACK_API_PORT ? `:${variables.BACK_API_PORT}` : ''}`; - const stateEndpoint = new URL(`/api/v1/idnot/state`, backBase); + // Use same-origin API (proxied by dev4) to avoid CORS + const apiOrigin = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}${variables.BACK_API_PORT ? `:${variables.BACK_API_PORT}` : ''}`; + const stateEndpoint = new URL(`${variables.BACK_API_ROOT_URL || '/api'}/v1/idnot/state`, apiOrigin); try { // 1) Ask backend for a signed state that embeds next_url - const resp = await fetch(stateEndpoint.toString(), { + const resp = await fetch(stateEndpoint.toString(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ next_url: nextUrl }) @@ -56,7 +56,8 @@ export default class Auth extends BaseApiService { // 2) Build the IdNot authorization URL with fixed redirect_uri and the signed state const fixedRedirect = variables.IDNOT_REDIRECT_URI_FIXED || 'http://local.4nkweb.com:3000/authorized-client'; const authorizeBase = `${variables.IDNOT_BASE_URL}${variables.IDNOT_AUTHORIZE_ENDPOINT}`; - const authorizeUrl = `${authorizeBase}?client_id=${encodeURIComponent(variables.IDNOT_CLIENT_ID)}&redirect_uri=${encodeURIComponent(fixedRedirect)}&scope=openid,profile&response_type=code&state=${encodeURIComponent(state)}`; + const scopeParam = encodeURIComponent('openid profile'); + const authorizeUrl = `${authorizeBase}?client_id=${encodeURIComponent(variables.IDNOT_CLIENT_ID)}&redirect_uri=${encodeURIComponent(fixedRedirect)}&scope=${scopeParam}&response_type=code&state=${encodeURIComponent(state)}`; console.log('[IDNOT] authorizeUrl', authorizeUrl); console.log('[IDNOT] state', state); diff --git a/src/front/Components/Layouts/Login/StepEmail/index.tsx b/src/front/Components/Layouts/Login/StepEmail/index.tsx index 7c69564c..1e191f12 100644 --- a/src/front/Components/Layouts/Login/StepEmail/index.tsx +++ b/src/front/Components/Layouts/Login/StepEmail/index.tsx @@ -53,12 +53,9 @@ export default function StepEmail(props: IProps) { const variables = FrontendVariables.getInstance(); try { const nextUrl = typeof window !== 'undefined' ? `${window.location.origin}/authorized-client` : `${variables.FRONT_APP_HOST}/authorized-client`; - let backBase = variables.BACK_BASE || (process.env.NEXT_PUBLIC_BACK_BASE as string) || `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}${variables.BACK_API_PORT ? `:${variables.BACK_API_PORT}` : ''}`; - if (!backBase || !/^https?:\/\//i.test(backBase)) { - console.warn('[IDNOT] BACK_BASE invalid or missing, falling back to https://dev3.4nkweb.com'); - backBase = 'https://dev3.4nkweb.com'; - } - const stateEndpoint = `${backBase.replace(/\/$/, '')}/api/v1/idnot/state`; + // Always prefer same-origin API to avoid CORS: https://dev4.4nkweb.com/api + const apiBase = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}${variables.BACK_API_PORT ? `:${variables.BACK_API_PORT}` : ''}${variables.BACK_API_ROOT_URL || ''}`; + const stateEndpoint = `${apiBase.replace(/\/$/, '')}/v1/idnot/state`; const resp = await fetch(stateEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -75,7 +72,8 @@ export default function StepEmail(props: IProps) { } const fixedRedirect = variables.IDNOT_REDIRECT_URI_FIXED || 'http://local.4nkweb.com:3000/authorized-client'; const authorizeBase = `${variables.IDNOT_BASE_URL}${variables.IDNOT_AUTHORIZE_ENDPOINT}`; - const authorizeUrl = `${authorizeBase}?client_id=${encodeURIComponent(variables.IDNOT_CLIENT_ID)}&redirect_uri=${encodeURIComponent(fixedRedirect)}&scope=openid,profile&response_type=code&state=${encodeURIComponent(state)}`; + const scopeParam = encodeURIComponent('openid profile'); + const authorizeUrl = `${authorizeBase}?client_id=${encodeURIComponent(variables.IDNOT_CLIENT_ID)}&redirect_uri=${encodeURIComponent(fixedRedirect)}&scope=${scopeParam}&response_type=code&state=${encodeURIComponent(state)}`; console.log('[IDNOT] authorizeUrl', authorizeUrl); console.log('[IDNOT] state', state); router.push(authorizeUrl); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index c6f73f2d..8778466a 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -89,14 +89,18 @@ const MyApp = (({ // Configure iframe target and URL on client-side only useEffect(() => { setMounted(true); - try { - if (_4nkUrl && typeof _4nkUrl === 'string' && _4nkUrl.trim().length > 0) { + try { + const runtime4nkUrl = (_4nkUrl && _4nkUrl.trim().length > 0) + ? _4nkUrl + : ((process as any)?.env?.NEXT_PUBLIC_4NK_URL || (publicRuntimeConfig as any)?.NEXT_PUBLIC_4NK_URL || ''); + + if (runtime4nkUrl && typeof runtime4nkUrl === 'string' && runtime4nkUrl.trim().length > 0) { const origin = (() => { - try { return new URL(_4nkUrl).origin; } catch { return _4nkUrl; } + try { return new URL(runtime4nkUrl).origin; } catch { return runtime4nkUrl; } })(); IframeReference.setTargetOrigin(origin); - const candidate = (publicRuntimeConfig as any).NEXT_PUBLIC_4NK_IFRAME_URL ?? _4nkUrl; + const candidate = (publicRuntimeConfig as any).NEXT_PUBLIC_4NK_IFRAME_URL ?? runtime4nkUrl; const iframe = (() => { try { return new URL(candidate).toString(); } catch { return origin; } })(); @@ -147,25 +151,35 @@ const MyApp = (({ }) as AppType; MyApp.getInitialProps = async () => { - return { - backApiProtocol: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_PROTOCOL, - backApiHost: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_HOST, - backApiPort: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_PORT, - backApiRootUrl: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_ROOT_URL, - backApiVersion: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_VERSION, - frontAppHost: publicRuntimeConfig.NEXT_PUBLIC_FRONT_APP_HOST, - frontAppPort: publicRuntimeConfig.NEXT_PUBLIC_FRONT_APP_PORT, - idNotBaseUrl: publicRuntimeConfig.NEXT_PUBLIC_IDNOT_BASE_URL, - idNotAuthorizeEndpoint: publicRuntimeConfig.NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT, - idNotClientId: publicRuntimeConfig.NEXT_PUBLIC_IDNOT_CLIENT_ID, - idNotRedirectUri: publicRuntimeConfig.NEXT_PUBLIC_IDNOT_REDIRECT_URI, - fcAuthorizeEndpoint: publicRuntimeConfig.NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT, - fcClientId: publicRuntimeConfig.NEXT_PUBLIC_FC_CLIENT_ID, - docaposteApiUrl: publicRuntimeConfig.NEXT_PUBLIC_DOCAPOSTE_API_URL, - _4nkUrl: publicRuntimeConfig.NEXT_PUBLIC_4NK_URL, - _4nkIframeUrl: publicRuntimeConfig.NEXT_PUBLIC_4NK_IFRAME_URL, - apiUrl: publicRuntimeConfig.NEXT_PUBLIC_API_URL, - }; + // Fallback runtime: use process.env when runtimeConfig is empty (e.g., image built without build args) + const getEnv = (key: string): string => { + const fromRuntime = (publicRuntimeConfig as any)?.[key]; + if (typeof fromRuntime === 'string' && fromRuntime.trim().length > 0) { + return fromRuntime; + } + const fromProcess = (process as any)?.env?.[key]; + return typeof fromProcess === 'string' ? fromProcess : ''; + }; + + return { + backApiProtocol: getEnv('NEXT_PUBLIC_BACK_API_PROTOCOL'), + backApiHost: getEnv('NEXT_PUBLIC_BACK_API_HOST'), + backApiPort: getEnv('NEXT_PUBLIC_BACK_API_PORT'), + backApiRootUrl: getEnv('NEXT_PUBLIC_BACK_API_ROOT_URL'), + backApiVersion: getEnv('NEXT_PUBLIC_BACK_API_VERSION'), + frontAppHost: getEnv('NEXT_PUBLIC_FRONT_APP_HOST'), + frontAppPort: getEnv('NEXT_PUBLIC_FRONT_APP_PORT'), + idNotBaseUrl: getEnv('NEXT_PUBLIC_IDNOT_BASE_URL'), + idNotAuthorizeEndpoint: getEnv('NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT'), + idNotClientId: getEnv('NEXT_PUBLIC_IDNOT_CLIENT_ID'), + idNotRedirectUri: getEnv('NEXT_PUBLIC_IDNOT_REDIRECT_URI'), + fcAuthorizeEndpoint: getEnv('NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT'), + fcClientId: getEnv('NEXT_PUBLIC_FC_CLIENT_ID'), + docaposteApiUrl: getEnv('NEXT_PUBLIC_DOCAPOSTE_API_URL'), + _4nkUrl: getEnv('NEXT_PUBLIC_4NK_URL'), + _4nkIframeUrl: getEnv('NEXT_PUBLIC_4NK_IFRAME_URL'), + apiUrl: getEnv('NEXT_PUBLIC_API_URL'), + }; }; export default MyApp; diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx new file mode 100644 index 00000000..77632263 --- /dev/null +++ b/src/pages/_document.tsx @@ -0,0 +1,63 @@ +import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'; + +class MyDocument extends Document { + static async getInitialProps(ctx: DocumentContext) { + const initialProps = await Document.getInitialProps(ctx); + + // Collect public env vars + const entries = Object.entries(process.env || {}) + .filter(([k]) => k.startsWith('NEXT_PUBLIC_')) + .map(([k, v]) => `${k}=${String(v ?? '')}`) + .sort(); + + // App version from package.json + let appVersion = ''; + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const pkg = require('../../package.json'); + appVersion = pkg?.version || ''; + } catch { + appVersion = ''; + } + + const buildDate = new Date().toISOString(); + const dockerTag = process.env.DOCKER_TAG || process.env.IMAGE_TAG || 'ext'; + + const metaComment = [ + 'Build Meta:', + `version=${appVersion}`, + `build_date=${buildDate}`, + `docker_tag=${dockerTag}`, + 'env:', + ...entries, + ].join('\n'); + + return { ...initialProps, metaComment } as any; + } + + render() { + // @ts-expect-error injected by getInitialProps + const metaComment: string = (this.props as any).metaComment || ''; + return ( + + + + + + + + {metaComment && ( +
` }} + /> + )} +
+ + + + ); + } +} + +export default MyDocument; diff --git a/src/pages/api/env.ts b/src/pages/api/env.ts new file mode 100644 index 00000000..f692d661 --- /dev/null +++ b/src/pages/api/env.ts @@ -0,0 +1,33 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); + const pick = (keys: string[]) => keys.reduce>((acc, k) => { + acc[k] = process.env[k]; + return acc; + }, {}); + + const clientKeys = [ + 'NEXT_PUBLIC_4NK_URL', + 'NEXT_PUBLIC_4NK_IFRAME_URL', + 'NEXT_PUBLIC_BACK_BASE', + 'NEXT_PUBLIC_IDNOT_BASE_URL', + 'NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT', + 'NEXT_PUBLIC_IDNOT_CLIENT_ID', + 'NEXT_PUBLIC_IDNOT_REDIRECT_URI', + 'NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED', + 'NEXT_PUBLIC_BACK_API_PROTOCOL', + 'NEXT_PUBLIC_BACK_API_HOST', + 'NEXT_PUBLIC_BACK_API_PORT', + 'NEXT_PUBLIC_BACK_API_ROOT_URL', + 'NEXT_PUBLIC_BACK_API_VERSION', + 'NEXT_PUBLIC_API_URL', + 'NEXT_PUBLIC_DEFAULT_VALIDATOR_ID', + 'NEXT_PUBLIC_DEFAULT_STORAGE_URLS', + ]; + + res.status(200).json({ + time: new Date().toISOString(), + client: pick(clientKeys), + }); +} diff --git a/src/pages/env.tsx b/src/pages/env.tsx new file mode 100644 index 00000000..2413e8ca --- /dev/null +++ b/src/pages/env.tsx @@ -0,0 +1,47 @@ +import { GetServerSideProps } from 'next'; +import getConfig from 'next/config'; + +type Props = { + now: string; + env: Record; + runtime: Record; +}; + +export default function EnvPage({ now, env, runtime }: Props) { + return ( +
+

Runtime environment

+
Time: {now}
+

process.env (client-safe)

+
{JSON.stringify(env, null, 2)}
+

next/config publicRuntimeConfig

+
{JSON.stringify(runtime.publicRuntimeConfig ?? {}, null, 2)}
+
+ ); +} + +export const getServerSideProps: GetServerSideProps = async ({ res }) => { + res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); + const keys = [ + 'NEXT_PUBLIC_4NK_URL', + 'NEXT_PUBLIC_4NK_IFRAME_URL', + 'NEXT_PUBLIC_BACK_BASE', + 'NEXT_PUBLIC_IDNOT_BASE_URL', + 'NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT', + 'NEXT_PUBLIC_IDNOT_CLIENT_ID', + 'NEXT_PUBLIC_IDNOT_REDIRECT_URI', + 'NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED', + 'NEXT_PUBLIC_BACK_API_PROTOCOL', + 'NEXT_PUBLIC_BACK_API_HOST', + 'NEXT_PUBLIC_BACK_API_PORT', + 'NEXT_PUBLIC_BACK_API_ROOT_URL', + 'NEXT_PUBLIC_BACK_API_VERSION', + 'NEXT_PUBLIC_API_URL', + 'NEXT_PUBLIC_DEFAULT_VALIDATOR_ID', + 'NEXT_PUBLIC_DEFAULT_STORAGE_URLS', + ]; + const env: Record = {}; + for (const k of keys) env[k] = process.env[k]; + const runtime = getConfig(); + return { props: { now: new Date().toISOString(), env, runtime } }; +};