luci-base: add generic authentication plugin mechanism#8281
luci-base: add generic authentication plugin mechanism#8281Tokisaki-Galaxy wants to merge 6 commits intoopenwrt:masterfrom
Conversation
|
I tried to inject /hook the login process directly, but it didn't work. This PR modifies We attempted to implement 2FA by wrapping the main 1. Atomicity of the Login Process
2. Inaccessibility of Internal State 3. Client-Side Compatibility (Bootstrap Theme) To implement secure 2FA, we must hook directly into
👆 Click show raw logNow let me also remove the bootstrap-specific view file since the template is no longer needed (we're rendering our own HTML now):View luci-app-2fa/htdocs/luci-static/resources/view/bootstrap/sysauth2fa.js Remove unused bootstrap view directory View luci-app-2fa List all package files Show final installed file paths Progress update: Refactor 2FA to use uhttpd wrapper instead of overwriting system files Check for existing OpenWrt containers Start OpenWrt container Install LuCI in container Start OpenWrt services Check uhttpd config Deploy 2FA package files to container Configure uhttpd to use 2FA handler Restart uhttpd with new handler Restart uhttpd with 2FA handler Find uhttpd PID Restart uhttpd Check uhttpd status Navigate Playwright web browser to a URL Call to Fill form Click element in Playwright web browser Take screenshot of Playwright web browser Click element in Playwright web browser Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Restart rpcd to load 2FA plugin Find rpcd PID Restart rpcd Navigate Playwright web browser to a URL Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Call to Fill form Click element in Playwright web browser Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Take screenshot of Playwright web browser Click element in Playwright web browser Click element in Playwright web browser Click element in Playwright web browser Wait for text to appear/disappear in Playwright web browser Check saved 2FA config Take screenshot of Playwright web browser Click element in Playwright web browser Call to Fill form Click element in Playwright web browser Check uhttpd config and handler Looking at the code, I realize the issue - the bootstrap theme uses JavaScript to handle login via AJAX/RPC rather than traditional form submission, so our server-side check in uhttpd-2fa.uc isn't being triggered. The 2FA needs to be integrated differently. Let me look at how LuCI's bootstrap sysauth works: Call to subagent explore
The files they want me to look at are: themes/luci-theme-bootstrap/htdocs/luci-static/resources/view/bootstrap/sysauth.js POST vs JavaScript submission View 2 files themes/luci-theme-bootstrap/htdocs/luci-static/resources/view/bootstrap/sysauth.js themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/sysauth.ut Find files /home/runner/work/luci-app-2fa/luci-app-2fa/themes/luci-theme-bootstrap/**/sysauth Find files /home/runner/work/luci-app-2fa/luci-app-2fa/**/sysauth* View repository View luci Find files /home/runner/work/luci-app-2fa/luci-app-2fa/luci/**/sysauth* Find files /home/runner/work/luci-app-2fa/luci-app-2fa/luci/themes/**/bootstrap Search sysauth View luci/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/sysauth.ut Find files /home/runner/work/luci-app-2fa/luci-app-2fa/luci/themes/luci-theme-bootstrap/**/sysauth Find files /home/runner/work/luci-app-2fa/luci-app-2fa/luci/themes/luci-theme-bootstrap/**/*.js Search sysauth View 6 files luci/themes/luci-theme-bootstrap luci/themes/luci-theme-bootstrap/htdocs luci/themes/luci-theme-bootstrap/htdocs/luci-static luci/themes/luci-theme-bootstrap/htdocs/luci-static/resources luci/themes/luci-theme-bootstrap/htdocs/luci-static/resources/view luci/themes/luci-theme-bootstrap/htdocs/luci-static/resources/view/bootstrap View luci/themes/luci-theme-bootstrap/htdocs/luci-static/resources/view/bootstrap/sysauth.js Bootstrap Theme Login Flow Analysis
The template (sysauth.ut line 9) defines: The JavaScript (sysauth.js line 29) calls: form.submit() This performs a standard HTML form POST submission, not an AJAX call 2. Authentication Flow Template renders (sysauth.ut): Hidden form with POST method containing two fields: luci_username and luci_password Extracts the form and button from the template Authenticates the user and redirects to the main UI ⏹️ End subagent: explore Let me check if our handler is actually being executed: Check logs for 2FA messages Stop Bash session 20 Generate test OTP View 2 files luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:90-120 luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:155-220 Edit luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc View luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:210-260 Edit luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc Now let me re-deploy the updated handler and test: Update and restart uhttpd Find uhttpd PID Update and restart uhttpd Navigate Playwright web browser to a URL Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Call to Fill form Click element in Playwright web browser Let me understand the problem better - the issue is that the bootstrap theme's JavaScript-based login form interacts with the normal dispatcher flow, and our interception at the is_login_request() check might not be matching how the form is submitted. Let me add some debug output to understand what's happening: Test login via curl Test login via wget Test login and see response View response body View luci/modules/luci-base/ucode/http.uc:1-60 Search write|output|send Looking at the http.uc code: req.status() sets the status code/message View luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:118-180 Edit luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc Update handler and test Find uhttpd PID Update and test Check handler for syntax errors View luci/modules/luci-base/ucode/uhttpd.uc Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Call to Fill form Click element in Playwright web browser Our error handler is catching an exception and falling back to normal dispatch View luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:80-95 The problem with my approach is that the login flow works like this: User submits username/password I need to reconsider the approach. Given the complexity of hooking into the dispatcher without modifying it, let me take a simpler approach for now: New approach: Instead of a complex wrapper, we can use a simpler method - we acknowledge that we can't properly intercept the login at this layer without modifying dispatcher.uc. Given the problem statement says we should cannot directly overwrite system files, but we need to inject into the login flow, the cleanest solution would be: Keep the basic file structure |
|
I have updated the templates for luci-base and luci-theme-bootstrap (the default theme) to demonstrate and verify this mechanism. Note: Other in-tree themes (e.g., luci-theme-material, luci-theme-openwrt-2020) currently override sysauth.ut completely and will need similar updates to render the plugin fields correctly. I suggest finalizing the core logic and API design in this PR first. Once the architecture is approved, I (or the theme maintainers) can proceed with updating the other standard themes. |
This comment has been minimized.
This comment has been minimized.
d95deef to
db92b4a
Compare
This comment has been minimized.
This comment has been minimized.
|
@Tokisaki-Galaxy: Nice, good job! Do not forget to solve: |
|
Any Auth plugins mechanism will need a configuration mechanism to completely disable or enable them. System -> Administration seems like a good place. |
This commit introduces a generic authentication plugin mechanism to the LuCI dispatcher, enabling multi-factor authentication (MFA/2FA) and other custom verification methods without modifying core files. Currently, LuCI's authentication logic is hardcoded to username/password verification against the system shadow file or UCI. There is no hook to intercept the login process for secondary verification (e.g., TOTP, SMS, Hardware Token) after the initial password check succeeds but before the session is granted. Key changes: 1. Plugin Architecture: - Plugins are loaded from `/usr/share/luci/auth.d/*.uc`. - Each plugin exports a `check(http, user)` and `verify(http, user)` function. - Plugins are sorted by priority to support multiple verification steps. 2. Dispatcher Logic (`dispatcher.uc`): - `load_auth_plugins()`: Discovers and loads .uc plugins. - `check_auth_plugins()`: Iterates plugins to see if any require user interaction. - `verify_auth_plugins()`: Validates the user's input (e.g., OTP code) against the plugin. - The login flow is intercepted: if a plugin requires auth, the session is provisionally withheld or destroyed until the plugin returns success. 3. Template Support (`sysauth.ut`): - Updated base and bootstrap `sysauth` templates to dynamically render additional form fields (e.g., OTP) provided by the plugin. - Supports displaying plugin-specific error messages. This mechanism is backward compatible. If no plugins are installed, the login process behaves exactly as before. Signed-off-by: Han Yiming <moebest@outlook.jp>
db92b4a to
a84fc50
Compare
in system password settings page, add option to enable or disable external authentication plugins, such as 2FA. Signed-off-by: Han Yiming <moebest@outlook.jp>
|
I don't quite understand how to add the i18n of the newly added option, and I can't find the corresponding pot file. seem do not work |
This allows authentication plugins to inject custom HTML content (e.g., `<img>` for CAPTCHA, `<script>` for Passkey/WebAuthn) into the login page. It provides the necessary flexibility for advanced verification methods that go beyond simple input fields. Signed-off-by: Han Yiming <moebest@outlook.jp>
|
Any config mechanism should be able to disable globally (as it looks like from your screen shot) or individually. you're implementing a framework, so if TOTP is disabled, perhaps that other scheme shouldn't also be disabled.
you need to provide -b if you make changes in the base or modules. |
enforce individual plugin disabling at the framework level for robustness and security. Signed-off-by: Han Yiming <moebest@outlook.jp>
|
That's a very valid point of view. Because this is a framework, we should not force an all-or-nothing approach. I agree that management complexity is very important. I will not build a complicated UI management system in luci-base, but implement a back-end mechanism in dispatcher.uc to respect the status of each plug-in. I didn't take this into account when designing the framework before. What I wanted at that time was to let each app manage its own status, and I will pay attention next time. |
Make a new tab (file).
Leave them, and we'll pick them up on the next i18n pass. |
This comment has been minimized.
This comment has been minimized.
76c62c8 to
1760083
Compare
Revert "luci-mod-system: add extra auth plugin config" add extra auth plugin config page in luci-mod-system Signed-off-by: Han Yiming <moebest@outlook.jp>
1760083 to
c439c3f
Compare
now, user use the login flow only need input password once Signed-off-by: Han Yiming <moebest@outlook.jp>
This comment has been minimized.
This comment has been minimized.
beeedf4 to
7d73932
Compare
Add a video in the previous description sectionHi, I have updated the code according to your suggestions. Please let me know if there is anything else that needs to be changed. Can I mark this as Ready now?" |
|
OK - this is starting to look better. Some feedback. Thanks for taking this on - it's no trivial task. Move new functions (listAuthPlugins) out of luci uc, into a new file, luci.auth.uc. Export it as luci.auth. Ensure all functions shall be usable/callable from the command line to ease debugging. ( Check how I did it in the dockerman JS conversion ). Unique plugins shall have a UUID - you can use the kernel Plugins shall be toggled (enabled or disabled) based on their UUID, and not 'disabled'. Enabling AAA shall be a conscious decision by an administrator. If the UUID doesn't match, the plugin is not enabled. Once enabled, a plugin shall reveal its configurables (e.g. TOTP, time window) in its own tab. So the tab shall dynamically determine what the plugin can configure, and display fields based on that. Everything self-contained in each plugin. Move new functions from dispatcher.uc to a separate file called auth.plugin.dispatcher.uc or something. The change delta in dispatcher shall be as minimal as possible. Just a few lines. Pass things like http, env, to functions in auth.plugin.dispatcher as necessary. On authentication results, it should return a boolean, and the boolean dictates that the session is destroyed or not, or how that moment is supposed to work. |
| return auth_plugins; | ||
| } | ||
|
|
||
| function get_auth_challenge(user) { |
There was a problem hiding this comment.
This should be called authenticate_user or authenticate_user_via_plugins - something describing what it is and or does.
This function should call verify_auth_challenge itself. dispatcher should make one call, passing user, http and whatever else is necessary.
This comment has been minimized.
This comment has been minimized.
Failed checksIssues marked with an ❌ are failing checks. Commit 7d73932
For more details, see the full job log. Something broken? Consider providing feedback. |
|
Please wait before making any changes while I come up with a good structure for these plugins. |
|
copy that. Thank you |
|
@stokito do you have any good ideas how to handle this?Kinda like how luci-app-statistics (rrdtool) handles its plugins. I've come up with two different UI systems to reveal config options for plugins like this and neither feels quite right (yet). |
|
Right... Who will set up the 2FA? Most users are too lazy to even change the default password :) But maybe for corporate networks this may be useful. I can't answer right now, but in general it's totally fine (and maybe more reliable) to not have plugins but just have all in the code. P.S. We may also add a complete "Login with Google/Github/Your company AD" button https://github.com/yurt-page/cgi-oauth/. |
|
Hi @systemcrash , |



Signed-off-by: <my@email.address>row (viagit commit --signoff)<package name>: titlefirst line subject for packagesPKG_VERSIONin the Makefileexist a PR depend this openwrt/luci luci-app-2fa: init checkin #8280
2026-02-04.180055.mp4
It allows any package to add additional authentication factors without modifying core LuCI files.
/usr/share/luci/auth.d/.uc) that exports a standard interfaceInterface:
Template Updates: The
sysauth.uttemplates are updated to:Authentication Settings UI: A new "Authentication" menu item is added under System > Administration:
/www/luci-static/resources/view/system/exauth.jslistAuthPlugins RPC Method: Added to the
luciubus object:external_authsetting status