@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-
|
|
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-
|
|
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. -->
|
package/bin/web-app-manager.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
-
|
|
76
|
-
|
|
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,
|
|
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.
|
|
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",
|