mirror of
https://github.com/openwrt/luci.git
synced 2026-04-15 10:51:51 +00:00
Co-authored-by: Christian Marangi <ansuelsmth@gmail.com> Signed-off-by: Han Yiming <moebest@outlook.jp> luci-app-2fa: add priority option and QR code display This update adds a priority option and enables QR code display for 2FA. luci-app-2fa: native ubus IPvalid fsLOCK and log use native ubus IP validation instead of custom regex and parsing, use native fs lock instead of popen-call and add log for logging auth events. now, will clean stale rate limit entries on each check and log when entries are removed due to staleness. This prevents the rate limit file from growing indefinitely with old entries. luci-app-2fa: move dir and sync sysfixtime move to the new location. update the default time calibration threshold to sync sysfixtime. luci-app-2fa: native hex and more readable use native hex and base32 decoding functions Signed-off-by: Han Yiming <moebest@outlook.jp>
126 lines
3.5 KiB
Ucode
Executable File
126 lines
3.5 KiB
Ucode
Executable File
#!/usr/bin/ucode
|
|
|
|
// Copyright (c) 2024 Christian Marangi <ansuelsmth@gmail.com>
|
|
// Copyright (c) 2026 tokiskai galaxy <moebest@outlook.jp>
|
|
import { cursor } from 'uci';
|
|
import { sha1 } from 'digest';
|
|
import { pack } from 'struct';
|
|
|
|
const base32_decode_table = (function() {
|
|
let t = {};
|
|
for (let i = 0; i < 26; i++) { t[ord('A') + i] = i; t[ord('a') + i] = i; }
|
|
for (let i = 0; i < 6; i++) { t[ord('2') + i] = 26 + i; }
|
|
return t;
|
|
})();
|
|
|
|
function decode_base32_to_bin(string) {
|
|
let clean = replace(string, /[\s=]/g, "");
|
|
if (length(clean) == 0) return null;
|
|
|
|
let bin = "";
|
|
let buffer = 0;
|
|
let bits = 0;
|
|
|
|
for (let i = 0; i < length(clean); i++) {
|
|
let val = base32_decode_table[ord(clean, i)];
|
|
if (val === null || val === undefined) continue;
|
|
|
|
buffer = (buffer << 5) | val;
|
|
bits += 5;
|
|
|
|
if (bits >= 8) {
|
|
bits -= 8;
|
|
bin += chr((buffer >> bits) & 0xff);
|
|
}
|
|
}
|
|
return bin;
|
|
}
|
|
|
|
function calculate_hmac_sha1(key, message) {
|
|
const blocksize = 64;
|
|
if (length(key) > blocksize) key = hexdec(sha1(key));
|
|
while (length(key) < blocksize) key += chr(0);
|
|
|
|
let o_key_pad = "", i_key_pad = "";
|
|
for (let i = 0; i < blocksize; i++) {
|
|
let k = ord(key, i);
|
|
o_key_pad += chr(k ^ 0x5c);
|
|
i_key_pad += chr(k ^ 0x36);
|
|
}
|
|
let inner_hash = hexdec(sha1(i_key_pad + message));
|
|
return sha1(o_key_pad + inner_hash);
|
|
}
|
|
|
|
function calculate_otp(secret_base32, counter_int) {
|
|
let secret_bin = decode_base32_to_bin(secret_base32);
|
|
if (!secret_bin) return null;
|
|
|
|
let counter_bin = pack(">Q", counter_int);
|
|
|
|
let hmac_hex = calculate_hmac_sha1(secret_bin, counter_bin);
|
|
|
|
let offset = int(substr(hmac_hex, 38, 2), 16) & 0xf;
|
|
let binary_code = int(substr(hmac_hex, offset * 2, 8), 16) & 0x7fffffff;
|
|
|
|
return sprintf("%06d", binary_code % 1000000);
|
|
}
|
|
|
|
let username = ARGV[0];
|
|
let no_increment = false;
|
|
let custom_time = null;
|
|
let plugin_uuid = null;
|
|
|
|
for (let i = 1; i < length(ARGV); i++) {
|
|
let arg = ARGV[i];
|
|
if (arg == '--no-increment') {
|
|
no_increment = true;
|
|
} else if (substr(arg, 0, 7) == '--time=') {
|
|
let time_str = substr(arg, 7);
|
|
if (match(time_str, /^[0-9]+$/)) {
|
|
custom_time = int(time_str);
|
|
if (custom_time < 946684800 || custom_time > 4102444800) custom_time = null;
|
|
}
|
|
} else if (substr(arg, 0, 9) == '--plugin=') {
|
|
let uuid_str = substr(arg, 9);
|
|
if (match(uuid_str, /^[0-9a-fA-F]{32}$/)) plugin_uuid = uuid_str;
|
|
}
|
|
}
|
|
|
|
if (!username || username == '') exit(1);
|
|
|
|
let ctx = cursor();
|
|
let otp_type, secret, counter, step;
|
|
|
|
if (plugin_uuid) {
|
|
otp_type = ctx.get('luci_plugins', plugin_uuid, 'type_' + username) || 'totp';
|
|
secret = ctx.get('luci_plugins', plugin_uuid, 'key_' + username);
|
|
counter = int(ctx.get('luci_plugins', plugin_uuid, 'counter_' + username) || '0');
|
|
step = int(ctx.get('luci_plugins', plugin_uuid, 'step_' + username) || '30');
|
|
} else {
|
|
otp_type = ctx.get('2fa', username, 'type') || 'totp';
|
|
secret = ctx.get('2fa', username, 'key');
|
|
counter = int(ctx.get('2fa', username, 'counter') || '0');
|
|
step = int(ctx.get('2fa', username, 'step') || '30');
|
|
}
|
|
|
|
if (!secret) exit(1);
|
|
|
|
let otp;
|
|
if (otp_type == 'hotp') {
|
|
otp = calculate_otp(secret, counter);
|
|
if (!no_increment && otp) {
|
|
if (plugin_uuid) {
|
|
ctx.set('luci_plugins', plugin_uuid, 'counter_' + username, '' + (counter + 1));
|
|
ctx.commit('luci_plugins');
|
|
} else {
|
|
ctx.set('2fa', username, 'counter', '' + (counter + 1));
|
|
ctx.commit('2fa');
|
|
}
|
|
}
|
|
} else {
|
|
let timestamp = (custom_time != null) ? custom_time : time();
|
|
otp = calculate_otp(secret, int(timestamp / step));
|
|
}
|
|
|
|
if (otp) print(otp); else exit(1);
|