From d3be8ebffdad8b9d952e4be405c6df87c811d726 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Tue, 28 Oct 2025 13:22:14 +0300 Subject: [PATCH] server.ts updated --- .../files/server-builder/server.ts.template | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/npm/ng-packs/packages/schematics/src/commands/ssr-add/files/server-builder/server.ts.template b/npm/ng-packs/packages/schematics/src/commands/ssr-add/files/server-builder/server.ts.template index e20cc2e222..3e64ee03f8 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ssr-add/files/server-builder/server.ts.template +++ b/npm/ng-packs/packages/schematics/src/commands/ssr-add/files/server-builder/server.ts.template @@ -6,6 +6,17 @@ import express from 'express'; import { existsSync } from 'node:fs'; import { join } from 'node:path'; import <% if (isStandalone) { %>bootstrap<% } else { %>AppServerModule<% } %> from './main.server'; +import {environment} from './environments/environment'; +import * as oidc from 'openid-client'; + +const ISSUER = new URL(environment.oAuthConfig.issuer); +const CLIENT_ID = environment.oAuthConfig.clientId; +const REDIRECT_URI = environment.oAuthConfig.redirectUri; +const SCOPE = environment.oAuthConfig.scope; + +const config = await oidc.discovery(ISSUER, CLIENT_ID, /* client_secret */ undefined); +const secureCookie = { httpOnly: true, sameSite: 'lax' as const, secure: environment.production, path: '/' }; +const tokenCookie = { ...secureCookie, httpOnly: false }; // The Express app is exported so that it can be used by serverless Functions. export function app(): express.Express { @@ -20,6 +31,119 @@ export function app(): express.Express { server.set('view engine', 'html'); server.set('views', distFolder); + server.use(ServerCookieParser.middleware()); + + const sessions = new Map(); + + server.get('/authorize', async (_req, res) => { + const code_verifier = oidc.randomPKCECodeVerifier(); + const code_challenge = await oidc.calculatePKCECodeChallenge(code_verifier); + const state = oidc.randomState(); + + if (_req.query.returnUrl) { + const returnUrl = String(_req.query.returnUrl || null); + res.cookie('returnUrl', returnUrl, { ...secureCookie, maxAge: 5 * 60 * 1000 }); + } + + const sid = crypto.randomUUID(); + sessions.set(sid, { pkce: code_verifier, state }); + res.cookie('sid', sid, secureCookie); + + const url = oidc.buildAuthorizationUrl(config, { + redirect_uri: REDIRECT_URI, + scope: SCOPE, + code_challenge, + code_challenge_method: 'S256', + state, + }); + res.redirect(url.toString()); + }); + + server.get('/logout', async (req, res) => { + try { + const sid = req.cookies.sid; + + if (sid && sessions.has(sid)) { + sessions.delete(sid); + } + + res.clearCookie('sid', secureCookie); + res.clearCookie('access_token', tokenCookie); + res.clearCookie('refresh_token', secureCookie); + res.clearCookie('expires_at', tokenCookie); + res.clearCookie('returnUrl', secureCookie); + + const endSessionEndpoint = config.serverMetadata().end_session_endpoint; + if (endSessionEndpoint) { + const logoutUrl = new URL(endSessionEndpoint); + logoutUrl.searchParams.set('post_logout_redirect_uri', REDIRECT_URI); + logoutUrl.searchParams.set('client_id', CLIENT_ID); + + return res.redirect(logoutUrl.toString()); + } + res.redirect('/'); + + } catch (error) { + console.error('Logout error:', error); + res.status(500).send('Logout error'); + } + }); + + server.get('/', async (req, res, next) => { + try { + const { code, state } = req.query as any; + if (!code || !state) return next(); + + const sid = req.cookies.sid; + const sess = sid && sessions.get(sid); + if (!sess || state !== sess.state) return res.status(400).send('invalid state'); + + const tokenEndpoint = config.serverMetadata().token_endpoint!; + const body = new URLSearchParams({ + grant_type: 'authorization_code', + code: String(code), + redirect_uri: environment.oAuthConfig.redirectUri, + code_verifier: sess.pkce!, + client_id: CLIENT_ID + }); + + const resp = await fetch(tokenEndpoint, { + method: 'POST', + headers: { 'content-type': 'application/x-www-form-urlencoded' }, + body, + }); + + if (!resp.ok) { + const errTxt = await resp.text(); + console.error('token error:', resp.status, errTxt); + return res.status(500).send('token error'); + } + + const tokens = await resp.json(); + + const expiresInSec = + Number(tokens.expires_in ?? tokens.expiresIn ?? 3600); + const skewSec = 60; + const accessExpiresAt = new Date( + Date.now() + Math.max(0, expiresInSec - skewSec) * 1000 + ); + + sessions.set(sid, { ...sess, at: tokens.access_token, refresh: tokens.refresh_token }); + res.cookie('access_token', tokens.access_token, {...tokenCookie, maxAge: accessExpiresAt.getTime()}); + res.cookie('refresh_token', tokens.refresh_token, secureCookie); + res.cookie('expires_at', String(accessExpiresAt.getTime()), tokenCookie); + + const returnUrl = req.cookies?.returnUrl ?? '/'; + res.clearCookie('returnUrl', secureCookie); + + return res.redirect(returnUrl); + } catch (e) { + console.error('OIDC error:', e); + return res.status(500).send('oidc error'); + } + }); + + // Example Express Rest API endpoints // server.get('/api/{*splat}', (req, res) => { }); // Serve static files from /browser