@ti-engine/web-framework 1.14.1 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  This document will contain the list of changes made to the framework. The format is based on the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
4
4
 
5
+ ## Version 1.15.0
6
+
7
+ A dedicated health endpoint, a `TI_WEB_AUTH_METHODS` env override, and login-page gating for every auth method — completing the container-friendly auth/health story for the competence deployment (CA-90).
8
+
9
+ * feat(web-framework): add an unprotected `GET /health` endpoint (`healthHandler`) that returns `200` with `{ status, broker, uptime }` — a purpose-built liveness/readiness probe for container and orchestrator health checks, so probes no longer have to hit the user-facing login route; `broker` reports the Redis connection state
10
+ * feat(web-framework): add the `TI_WEB_AUTH_METHODS` env override (comma-separated) which REPLACES `auth.enabledMethods` — a clean, 12-factor way to select enabled auth methods per deployment (the config-file merge is by-index and cannot cleanly override an array)
11
+ * feat(web-framework): extend login-page auth gating from the OAuth buttons to every method — the `local` credentials form and the "or continue with" divider are now gated too, and a "no method configured" fallback is shown when nothing is enabled, so an SSO-only deployment presents no dead local form
12
+ * build(release): bump package version from `1.14.1` to `1.15.0`
13
+
5
14
  ## Version 1.14.1
6
15
 
7
16
  Security hardening for the web-server CodeQL findings raised after the scanner was modernized in CA-90 (CA-91).
@@ -14,6 +14,7 @@
14
14
  <!-- Error message -->
15
15
  <div id="ti-error" class="ti-login-error"></div>
16
16
 
17
+ <!--ti-auth-method:local-->
17
18
  <!-- Local auth form -->
18
19
  <form id="ti-login-form" action="/login/local" method="post" class="ti-login-form">
19
20
  <input type="hidden" name="_csrf" value='{ti-csrf-placeholder}'>
@@ -45,15 +46,18 @@
45
46
  <span x-text-label="interface.default.login.sign-in">Sign In</span>
46
47
  </button>
47
48
  </form>
49
+ <!--/ti-auth-method-->
48
50
 
49
- <!--ti-auth-oauth-section-->
51
+ <!--ti-auth-divider-->
50
52
  <!-- Divider -->
51
53
  <div class="ti-login-divider">
52
54
  <div class="ti-login-divider-line"></div>
53
55
  <span class="ti-login-divider-label">or continue with</span>
54
56
  <div class="ti-login-divider-line"></div>
55
57
  </div>
58
+ <!--/ti-auth-divider-->
56
59
 
60
+ <!--ti-auth-social-->
57
61
  <!-- Social auth -->
58
62
  <div class="ti-login-social">
59
63
  <!--ti-auth-method:openid-google-->
@@ -79,7 +83,12 @@
79
83
  </a>
80
84
  <!--/ti-auth-method-->
81
85
  </div>
82
- <!--/ti-auth-oauth-section-->
86
+ <!--/ti-auth-social-->
87
+
88
+ <!--ti-auth-none-->
89
+ <!-- Shown only when no authentication method is enabled/configured. -->
90
+ <div class="ti-login-empty">No sign-in method is configured. Please contact your administrator.</div>
91
+ <!--/ti-auth-none-->
83
92
  </div>
84
93
 
85
94
  <!-- TEMPORARY: Test user selector for augmentSession() experimentation. Remove once AD-driven identity is wired up. -->
@@ -21,7 +21,8 @@ const RE_HTMX_CONFIG = /\{ti-htmx-config-placeholder}/g;
21
21
  const RE_CSP_NONCE = /^[A-Za-z0-9+/=_-]{16,}$/;
22
22
  const TI_NESTED_FRAME_PLACEHOLDER = "ti-nested-frame-placeholder";
23
23
  const OAUTH_METHODS = [ "openid-google", "openid-azure" ];
24
- const RE_AUTH_MARKERS = /<!--\/?ti-auth-(?:oauth-section|method(?::[a-z-]+)?)-->/g;
24
+ const ALL_METHODS = [ "local", "openid-google", "openid-azure" ];
25
+ const RE_AUTH_MARKERS = /<!--\/?ti-auth-(?:divider|social|none|method(?::[a-z-]+)?)-->/g;
25
26
 
26
27
  /**
27
28
  * Removes every `<openMarker>…<closeMarker>` span (inclusive) from `html` in a single linear pass. The markers are
@@ -57,12 +58,15 @@ function stripMarkerSpans( html, openMarker, closeMarker ) {
57
58
  }
58
59
 
59
60
  /**
60
- * Removes login-page OpenID provider markup for any OAuth method that is not currently enabled, and removes the
61
- * entire "or continue with" section when no OAuth method is enabled at all. The login fragment delimits the
62
- * relevant blocks with HTML-comment markers: `<!--ti-auth-method:METHOD-->…<!--/ti-auth-method-->` around each
63
- * provider button, and `<!--ti-auth-oauth-section-->…<!--/ti-auth-oauth-section-->` around the divider + social
64
- * block. Any remaining markers are stripped so clean HTML ships. Fragments without these markers (every non-login
65
- * fragment) are returned unchanged.
61
+ * Gates the login-page authentication markup to the effective enabled methods. The login fragment delimits blocks
62
+ * with HTML-comment markers: `<!--ti-auth-method:METHOD-->…<!--/ti-auth-method-->` around each method's control
63
+ * (the `local` credentials form and each OpenID provider button), `<!--ti-auth-divider-->…<!--/ti-auth-divider-->`
64
+ * around the "or continue with" separator, `<!--ti-auth-social-->…<!--/ti-auth-social-->` around the SSO button
65
+ * group, and `<!--ti-auth-none-->…<!--/ti-auth-none-->` around a "no method configured" fallback. It removes the
66
+ * block for any method that is not enabled, drops the social group when no SSO provider is enabled, shows the
67
+ * divider only when a local form AND at least one SSO provider are both present, and shows the fallback only when
68
+ * nothing is enabled. Any remaining markers are stripped so clean HTML ships. Fragments without these markers
69
+ * (every non-login fragment) are returned unchanged.
66
70
  *
67
71
  * @param {string} html
68
72
  * @param {string[]} [enabledMethods] The effective enabled authentication methods.
@@ -71,18 +75,31 @@ function stripMarkerSpans( html, openMarker, closeMarker ) {
71
75
  function applyAuthMethodVisibility( html, enabledMethods ) {
72
76
  let result = String( html );
73
77
  const enabled = Array.isArray( enabledMethods ) ? enabledMethods : [];
78
+ const localEnabled = enabled.includes( "local" );
79
+ const anyOAuth = OAUTH_METHODS.some( ( method ) => enabled.includes( method ) );
74
80
 
75
- if ( !OAUTH_METHODS.some( ( method ) => enabled.includes( method ) ) ) {
76
- // No OAuth providers available — drop the whole "or continue with" section, then any stray markers.
77
- return stripMarkerSpans( result, "<!--ti-auth-oauth-section-->", "<!--/ti-auth-oauth-section-->" ).replace( RE_AUTH_MARKERS, "" );
78
- }
79
-
80
- // Drop the button block for each OAuth method that is not enabled, then strip the remaining markers.
81
- OAUTH_METHODS.forEach( ( method ) => {
81
+ // Drop the block for each authentication method that is not enabled.
82
+ ALL_METHODS.forEach( ( method ) => {
82
83
  if ( !enabled.includes( method ) ) {
83
84
  result = stripMarkerSpans( result, "<!--ti-auth-method:" + method + "-->", "<!--/ti-auth-method-->" );
84
85
  }
85
86
  } );
87
+
88
+ // Drop the SSO button group when no OpenID provider is enabled.
89
+ if ( !anyOAuth ) {
90
+ result = stripMarkerSpans( result, "<!--ti-auth-social-->", "<!--/ti-auth-social-->" );
91
+ }
92
+
93
+ // Show the "or continue with" divider only when BOTH a local form and at least one SSO provider are present.
94
+ if ( !( localEnabled && anyOAuth ) ) {
95
+ result = stripMarkerSpans( result, "<!--ti-auth-divider-->", "<!--/ti-auth-divider-->" );
96
+ }
97
+
98
+ // Show the "no method configured" fallback only when nothing is enabled.
99
+ if ( localEnabled || anyOAuth ) {
100
+ result = stripMarkerSpans( result, "<!--ti-auth-none-->", "<!--/ti-auth-none-->" );
101
+ }
102
+
86
103
  return result.replace( RE_AUTH_MARKERS, "" );
87
104
  }
88
105
 
package/bin/web-server.js CHANGED
@@ -528,6 +528,7 @@ class TiWebServer extends ServiceConsumer {
528
528
  this.#webServer.get( "/login/:method", webHandlers.authenticationHandler( this ) );
529
529
  this.#webServer.post( "/login/:method", webHandlers.authenticationHandler( this ) );
530
530
  this.#webServer.post( "/logout", webHandlers.logoutHandler() );
531
+ this.#webServer.get( "/health", webHandlers.healthHandler() );
531
532
  this.#webServer.get( "/me", webHandlers.userInformationHandler() );
532
533
  if ( this.#authManager.isAuthEnabled( authMethod.OPENID_GOOGLE ) ) {
533
534
  this.#webServer.get( this.#authManager.getOAuth2CallbackUrl( authMethod.OPENID_GOOGLE ), webHandlers.authorizedOAuth2CallbackHandler( this, authMethod.OPENID_GOOGLE ) );
@@ -568,6 +569,7 @@ class TiWebServer extends ServiceConsumer {
568
569
  this.#unprotectedRoutes.push( "/app/config" );
569
570
  this.#unprotectedRoutes.push( /^\/login\/[^/]+$/i );
570
571
  this.#unprotectedRoutes.push( "/logout" );
572
+ this.#unprotectedRoutes.push( "/health" );
571
573
  this.#unprotectedRoutes.push( RE_STATIC_UNPROTECTED );
572
574
  this.#unprotectedRoutes.push( RE_WELL_KNOWN_UNPROTECTED );
573
575
  }
@@ -14,7 +14,9 @@ const tools = require( "@ti-engine/core/tools" );
14
14
  * Applies TI_WEB_* environment-variable overrides onto an (already-merged) web server configuration object.
15
15
  * Each override is applied ONLY when its environment variable is defined, so an absent variable leaves the
16
16
  * configured/default value untouched (fully backward compatible). This gives ti-engine web servers 12-factor,
17
- * container-friendly control over network binding, TLS, and the session cookie secret without editing config files.
17
+ * container-friendly control over network binding, TLS, the session cookie secret, and the enabled authentication
18
+ * methods without editing config files. Note `TI_WEB_AUTH_METHODS` fully REPLACES `auth.enabledMethods` (a clean
19
+ * array replacement) rather than merging — the config-file merge is by-index and cannot cleanly override an array.
18
20
  *
19
21
  * @method
20
22
  * @param {Object} config The web server configuration to augment (mutated in place and returned).
@@ -48,6 +50,10 @@ function applyWebConfigEnvOverrides( config, env = process.env ) {
48
50
  config.cookies = config.cookies || {};
49
51
  config.cookies.secret = env.TI_WEB_COOKIE_SECRET;
50
52
  }
53
+ if ( env.TI_WEB_AUTH_METHODS !== undefined ) {
54
+ config.auth = config.auth || {};
55
+ config.auth.enabledMethods = env.TI_WEB_AUTH_METHODS.split( "," ).map( ( method ) => method.trim() ).filter( ( method ) => method.length > 0 );
56
+ }
51
57
  return config;
52
58
  }
53
59
 
@@ -13,6 +13,7 @@ const { randomBytes, timingSafeEqual } = require( "node:crypto" );
13
13
  const URL = require( "node:url" ).URL;
14
14
  const _ = require( "lodash" );
15
15
  const helmet = require( "helmet" );
16
+ const cache = require( "@ti-engine/core/cache" );
16
17
  const authMethod = require( "#auth-manager" ).authMethod;
17
18
  const authorization = require( "#authorization" );
18
19
 
@@ -372,6 +373,29 @@ module.exports.logoutHandler = () => {
372
373
  };
373
374
  };
374
375
 
376
+ /**
377
+ * Handler for a lightweight, unauthenticated health probe. Responds `200` whenever the web server is serving
378
+ * (a liveness signal for container/orchestrator probes), and reports the message-broker (Redis) connection state
379
+ * in the body so it can double as a readiness signal without hitting a user-facing route like the login page.
380
+ *
381
+ * @method
382
+ * @returns {ExpressHandler}
383
+ * @public
384
+ */
385
+ module.exports.healthHandler = () => {
386
+ return ( request, response ) => {
387
+ const broker = ( cache.instance && cache.instance.isOperational === true ) ? "connected" : "disconnected";
388
+ response.status( exceptions.httpCode.C_200 ).send( {
389
+ isSuccessful: true,
390
+ data: {
391
+ status: "ok",
392
+ broker: broker,
393
+ uptime: Math.round( process.uptime() )
394
+ }
395
+ } );
396
+ };
397
+ };
398
+
375
399
  /**
376
400
  * Handler for retrieving authenticated user information.
377
401
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ti-engine/web-framework",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "description": "A web-framework based on the ti-engine. It provides a customizable ready-to-use web-server microservice and a set of tools for creating web applications. NOTICE: This is still a work in progress and the full architecture, design, and functionality are not available!",
5
5
  "author": "Boris Kostadinov <kostadinov.boris@gmail.com>",
6
6
  "license": "GPL-3.0-or-later",