Improvements to oputil
* Major update to --help * '2fa' is now '2fa-otp' or just 'otp' * Better 2fa-otp output & handling
This commit is contained in:
parent
6953cdf159
commit
d215919bff
|
@ -209,7 +209,7 @@ function scanFileAreaForChanges(areaInfo, options, cb) {
|
|||
async.series(
|
||||
[
|
||||
function quickCheck(next) {
|
||||
if(!options.quick) {
|
||||
if(options['full-scan']) {
|
||||
return next(null);
|
||||
}
|
||||
|
||||
|
@ -476,8 +476,8 @@ function scanFileAreas() {
|
|||
options.tags = tags.split(',');
|
||||
}
|
||||
|
||||
options.descFile = argv['desc-file']; // --desc-file or --desc-file PATH
|
||||
options.quick = argv.quick;
|
||||
options.descFile = argv['desc-file']; // --desc-file or --desc-file PATH
|
||||
options['full-scan'] = argv['-full-scan'];
|
||||
|
||||
options.areaAndStorageInfo = getAreaAndStorage(argv._.slice(2));
|
||||
|
||||
|
|
|
@ -9,115 +9,169 @@ exports.getHelpFor = getHelpFor;
|
|||
const usageHelp = exports.USAGE_HELP = {
|
||||
General :
|
||||
`usage: oputil.js [--version] [--help]
|
||||
<command> [<args>]
|
||||
<command> [<arguments>]
|
||||
|
||||
global args:
|
||||
-c, --config PATH specify config path (${getDefaultConfigPath()})
|
||||
-n, --no-prompt assume defaults/don't prompt for input where possible
|
||||
global arguments:
|
||||
-c, --config PATH Specify config path (${getDefaultConfigPath()})
|
||||
-n, --no-prompt Assume defaults (don't prompt for input where possible)
|
||||
|
||||
commands:
|
||||
user user utilities
|
||||
config config file management
|
||||
fb file base management
|
||||
mb message base management
|
||||
user User management
|
||||
config Configuration management
|
||||
fb File base management
|
||||
mb Message base management
|
||||
`,
|
||||
User :
|
||||
`usage: oputil.js user <action> [<args>]
|
||||
`usage: oputil.js user <action> [<arguments>]
|
||||
|
||||
actions:
|
||||
info USERNAME display information about a user
|
||||
pw USERNAME PASSWORD set a user's password
|
||||
aliases: password, passwd
|
||||
rm USERNAME permanently removes user from system
|
||||
aliases: remove, delete, del
|
||||
rename USERNAME NEWNAME rename a user
|
||||
aliases: mv
|
||||
activate USERNAME set status to active
|
||||
deactivate USERNAME set status to inactive
|
||||
disable USERNAME set status to disabled
|
||||
lock USERNAME set status to locked
|
||||
group USERNAME [+|-]GROUP adds (+) or removes (-) user from a group
|
||||
Actions:
|
||||
info USERNAME Display information about a user
|
||||
|
||||
pw USERNAME PASSWORD Set a user's password
|
||||
(passwd|password)
|
||||
|
||||
rm USERNAME Permanently removes user from system
|
||||
(del|delete|remove)
|
||||
|
||||
rename USERNAME NEWNAME Rename a user
|
||||
(mv)
|
||||
|
||||
2fa-otp USERNAME SPEC Enable Two Factor Authentication (2FA)
|
||||
(otp)
|
||||
|
||||
Valid specs:
|
||||
totp : Time-Based One-Time Password Algorithm (RFC-6238)
|
||||
hotp : HMAC-Based One-Time Password Algorithm (RFC-4266)
|
||||
google : Google Authenticator
|
||||
|
||||
activate USERNAME Set a user's status to "active"
|
||||
|
||||
deactivate USERNAME Set a user's status to "inactive"
|
||||
|
||||
disable USERNAME Set a user's status to "disabled"
|
||||
|
||||
lock USERNAME Set a user's status to "locked"
|
||||
|
||||
group USERNAME [+|-]GROUP Adds (+) or removes (-) user from a group
|
||||
|
||||
info arguments:
|
||||
--security Include security information in output
|
||||
|
||||
2fa-otp arguments:
|
||||
--qr-type TYPE Specify QR code type
|
||||
|
||||
Valid QR types:
|
||||
ascii : Plain ASCII (default)
|
||||
data : HTML data URL
|
||||
img : HTML image tag
|
||||
svg : SVG image
|
||||
|
||||
--out PATH Path to write QR code to. defaults to stdout
|
||||
`,
|
||||
|
||||
Config :
|
||||
`usage: oputil.js config <action> [<args>]
|
||||
`usage: oputil.js config <action> [<arguments>]
|
||||
|
||||
actions:
|
||||
new generate a new/initial configuration
|
||||
cat cat current configuration to stdout
|
||||
Actions:
|
||||
new Generate a new / default configuration
|
||||
|
||||
cat args:
|
||||
--no-color disable color
|
||||
--no-comments strip any comments
|
||||
cat Write current configuration to stdout
|
||||
|
||||
cat arguments:
|
||||
--no-color Disable color
|
||||
--no-comments Strip any comments
|
||||
`,
|
||||
FileBase :
|
||||
`usage: oputil.js fb <action> [<args>]
|
||||
`usage: oputil.js fb <action> [<arguments>]
|
||||
|
||||
actions:
|
||||
scan AREA_TAG[@STORAGE_TAG] scan specified area
|
||||
may also contain optional GLOB as last parameter,
|
||||
for example: scan some_area *.zip
|
||||
Actions:
|
||||
scan AREA_TAG[@STORAGE_TAG] Scan specified area
|
||||
|
||||
info CRITERIA display information about areas and/or files
|
||||
matching CRITERIA.
|
||||
May contain optional GLOB as last parameter.
|
||||
Example: ./oputil.js fb scan d0pew4r3z *.zip
|
||||
|
||||
mv SRC [SRC...] DST move entry(s) from SRC to DST
|
||||
SRC: FILENAME_WC|SHA|FILE_ID|AREA_TAG[@STORAGE_TAG]
|
||||
DST: AREA_TAG[@STORAGE_TAG]
|
||||
info CRITERIA Display information about areas and/or files
|
||||
|
||||
rm SRC [SRC...] remove entry(s) from the system matching SRC
|
||||
SRC: FILENAME_WC|SHA|FILE_ID|AREA_TAG[@STORAGE_TAG]
|
||||
desc CRITERIA sets a new file description for file base entry
|
||||
matching CRITERIA. Launches an external editor using
|
||||
$VISUAL, $EDITOR, or vim/notepad.
|
||||
import-areas FILEGATE.ZXX import file base areas using FileGate RAID type format
|
||||
mv SRC [SRC...] DST Move matching entry(s)
|
||||
(move)
|
||||
|
||||
scan args:
|
||||
--tags TAG1,TAG2,... specify tag(s) to assign to discovered entries
|
||||
Source may be any of the following:
|
||||
- Filename including '*' wildcards
|
||||
- SHA-1
|
||||
- File ID
|
||||
- Area tag with optional @storageTag suffix
|
||||
Destination is area tag with optional @storageTag suffix
|
||||
|
||||
--desc-file [PATH] prefer file descriptions from supplied path over other
|
||||
other sources such as FILE_ID.DIZ. Path must point to
|
||||
a valid FILES.BBS or DESCRIPT.ION file.
|
||||
--update attempt to update information for existing entries
|
||||
--quick perform quick scan
|
||||
rm SRC [SRC...] Remove entry(s) from the system
|
||||
(del|delete|remove)
|
||||
|
||||
info args:
|
||||
--show-desc display short description, if any
|
||||
Source may be any of the following:
|
||||
- Filename including '*' wildcards
|
||||
- SHA-1
|
||||
- File ID
|
||||
- Area tag with optional @storageTag suffix
|
||||
|
||||
remove args:
|
||||
--phys-file also remove underlying physical file
|
||||
desc CRITERIA Updates an file base entry's description
|
||||
|
||||
import-areas args:
|
||||
--type TYPE sets import areas type. valid options are "zxx" or "na"
|
||||
--create-dirs create backing storage directories
|
||||
Launches an external editor using $VISUAL, $EDITOR, or vim/notepad.
|
||||
|
||||
import-areas FILEGATE.ZXX Import file base areas using FileGate RAID type format
|
||||
|
||||
scan arguments:
|
||||
--tags TAG1,TAG2,... Specify hashtag(s) to assign to discovered entries
|
||||
|
||||
--desc-file [PATH] Prefer file descriptions from supplied input file
|
||||
|
||||
If a file description can be found in the supplied input file, prefer that description
|
||||
over other sources such related FILE_ID.DIZ. Path must point to a valid FILES.BBS or
|
||||
DESCRIPT.ION file.
|
||||
|
||||
--update Attempt to update information for existing entries
|
||||
--full-scan Perform a full scan (default is quick)
|
||||
|
||||
info arguments:
|
||||
--show-desc Display short description, if any
|
||||
|
||||
remove arguments:
|
||||
--phys-file Also remove underlying physical file
|
||||
|
||||
import-areas arguments:
|
||||
--type TYPE Sets import areas type
|
||||
|
||||
Valid types are are "zxx" or "na".
|
||||
|
||||
--create-dirs Also create backing storage directories
|
||||
`,
|
||||
FileOpsInfo :
|
||||
`
|
||||
general information:
|
||||
AREA_TAG[@STORAGE_TAG] can specify an area tag and optionally, a storage specific tag
|
||||
example: retro@bbs
|
||||
General Information:
|
||||
Generally an area tag can also include an optional storage tag. For example, the
|
||||
area of 'bbswarez' stored using 'bbswarez_main': bbswarez@bbswarez_main
|
||||
|
||||
CRITERIA file base entry criteria. in general, can be AREA_TAG, SHA,
|
||||
FILE_ID, or FILENAME_WC.
|
||||
|
||||
FILENAME_WC filename with * and ? wildcard support. may match 0:n entries
|
||||
SHA full or partial SHA-256
|
||||
FILE_ID a file identifier. see file.sqlite3
|
||||
When performing an initial import of a large area or storage backing, --full-scan
|
||||
is the best option. If re-scanning an area for updates a standard / quick scan is
|
||||
generally good enough.
|
||||
|
||||
File ID's are those found in file.sqlite3.
|
||||
`,
|
||||
MessageBase :
|
||||
`usage: oputil.js mb <action> [<args>]
|
||||
`usage: oputil.js mb <action> [<arguments>]
|
||||
|
||||
actions:
|
||||
areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s)
|
||||
one or more commands may be supplied. commands that are multi
|
||||
part such as "%COMPRESS ZIP" should be quoted.
|
||||
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH
|
||||
areafix CMD1 CMD2 ... ADDR Sends an AreaFix NetMail
|
||||
|
||||
import-areas args:
|
||||
--conf CONF_TAG conference tag in which to import areas
|
||||
--network NETWORK network name/key to associate FTN areas
|
||||
--uplinks UL1,UL2,... one or more comma separated uplinks
|
||||
--type TYPE area import type. valid options are "bbs" and "na"
|
||||
NetMail is sent to supplied address with the supplied command(s). Multi-part commands
|
||||
such as "%COMPRESS ZIP" should be quoted.
|
||||
|
||||
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
|
||||
|
||||
import-areas arguments:
|
||||
--conf CONF_TAG Conference tag in which to import areas
|
||||
--network NETWORK Network name/key to associate FTN areas
|
||||
--uplinks UL1,UL2,... One or more uplinks (comma separated)
|
||||
--type TYPE Area import type
|
||||
|
||||
Valid types are "bbs" and "na".
|
||||
`
|
||||
};
|
||||
|
||||
|
|
|
@ -329,7 +329,7 @@ function showUserInfo(user) {
|
|||
return user.properties[p] || 'N/A';
|
||||
};
|
||||
|
||||
console.info(`User information:
|
||||
const stdInfo = `User information:
|
||||
Username : ${user.username}${user.isRoot() ? ' (root/SysOp)' : ''}
|
||||
Real name : ${propOrNA(UserProps.RealName)}
|
||||
ID : ${user.userId}
|
||||
|
@ -340,11 +340,29 @@ Last login : ${lastLogin()}
|
|||
Login count : ${propOrNA(UserProps.LoginCount)}
|
||||
Email : ${propOrNA(UserProps.EmailAddress)}
|
||||
Location : ${propOrNA(UserProps.Location)}
|
||||
Affiliations : ${propOrNA(UserProps.Affiliations)}
|
||||
`);
|
||||
Affiliations : ${propOrNA(UserProps.Affiliations)}`;
|
||||
let secInfo = '';
|
||||
if(argv.security) {
|
||||
const otp = user.getProperty(UserProps.AuthFactor2OTP);
|
||||
if(otp) {
|
||||
const backupCodesOrNa = () => {
|
||||
try
|
||||
{
|
||||
return JSON.parse(user.getProperty(UserProps.AuthFactor2OTPBackupCodes)).join(', ');
|
||||
} catch(e) {
|
||||
return 'N/A';
|
||||
}
|
||||
};
|
||||
secInfo = `\n2FA OTP : ${otp}
|
||||
OTP secret : ${user.getProperty(UserProps.AuthFactor2OTPSecret) || 'N/A'}
|
||||
OTP Backup : ${backupCodesOrNa()}`;
|
||||
}
|
||||
}
|
||||
|
||||
console.info(`${stdInfo}${secInfo}`);
|
||||
}
|
||||
|
||||
function twoFactorAuth(user) {
|
||||
function twoFactorAuthOTP(user) {
|
||||
if(argv._.length < 4) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
|
@ -359,8 +377,15 @@ function twoFactorAuth(user) {
|
|||
function validate(callback) {
|
||||
// :TODO: Prompt for if not supplied
|
||||
let otpType = argv._[argv._.length - 1];
|
||||
|
||||
// allow aliases for OTP types
|
||||
otpType = {
|
||||
google : OTPTypes.GoogleAuthenticator,
|
||||
hotp : OTPTypes.RFC4266_HOTP,
|
||||
totp : OTPTypes.RFC6238_TOTP,
|
||||
}[otpType] || otpType;
|
||||
otpType = _.find(OTPTypes, t => {
|
||||
return t.toLowerCase() === otpType;
|
||||
return t.toLowerCase() === otpType.toLowerCase();
|
||||
});
|
||||
if(!otpType) {
|
||||
return callback(Errors.Invalid('Invalid OTP type'));
|
||||
|
@ -377,7 +402,7 @@ function twoFactorAuth(user) {
|
|||
});
|
||||
},
|
||||
function storeOrDisplayQR(otpInfo, callback) {
|
||||
if(!argv.out) {
|
||||
if(!argv.out || !otpInfo.qr) {
|
||||
return callback(null, otpInfo);
|
||||
}
|
||||
|
||||
|
@ -400,15 +425,18 @@ function twoFactorAuth(user) {
|
|||
if(err) {
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info(`OTP enabled for ${user.username}.`);
|
||||
console.info(`Secret: ${otpInfo.secret}`);
|
||||
console.info(`Backup codes: ${otpInfo.backupCodes.join(', ')}`);
|
||||
console.info(`OTP enabled for : ${user.username}`);
|
||||
console.info(`Secret : ${otpInfo.secret}`);
|
||||
console.info(`Backup codes : ${otpInfo.backupCodes.join(', ')}`);
|
||||
|
||||
if(!argv.out) {
|
||||
console.info('QR code:');
|
||||
console.info(otpInfo.qr);
|
||||
} else {
|
||||
console.info(`QR code saved to ${argv.out}`);
|
||||
if(otpInfo.qr) {
|
||||
if(!argv.out) {
|
||||
console.info('--- Begin QR ---');
|
||||
console.info(otpInfo.qr);
|
||||
console.info('--- End QR ---');
|
||||
} else {
|
||||
console.info(`QR code saved to ${argv.out}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -429,7 +457,7 @@ function handleUserCommand() {
|
|||
'pw', 'pass', 'passwd', 'password',
|
||||
'group',
|
||||
'mv', 'rename',
|
||||
'2fa',
|
||||
'2fa-otp', 'otp'
|
||||
].includes(action) ? argv._.length - 2 : argv._.length - 1;
|
||||
const userName = argv._[usernameIdx];
|
||||
|
||||
|
@ -465,7 +493,8 @@ function handleUserCommand() {
|
|||
|
||||
info : showUserInfo,
|
||||
|
||||
'2fa' : twoFactorAuth,
|
||||
'2fa-otp' : twoFactorAuthOTP,
|
||||
otp : twoFactorAuthOTP,
|
||||
}[action] || errUsage)(user, action);
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue