462 lines
17 KiB
Plaintext
462 lines
17 KiB
Plaintext
|
|
# Authentication Command Handlers
|
||
|
|
# Handles: auth login, auth logout, auth status, auth mfa, etc.
|
||
|
|
|
||
|
|
use ../flags.nu *
|
||
|
|
use ../../lib_provisioning/plugins/auth.nu *
|
||
|
|
|
||
|
|
# Main authentication command dispatcher
|
||
|
|
export def handle_authentication_command [
|
||
|
|
command: string
|
||
|
|
ops: string
|
||
|
|
flags: record
|
||
|
|
] {
|
||
|
|
match $command {
|
||
|
|
"auth" | "login" | "whoami" | "mfa" => {
|
||
|
|
let subcommand = if ($ops | is-not-empty) {
|
||
|
|
($ops | split row " " | get 0)
|
||
|
|
} else if $command == "login" {
|
||
|
|
"login"
|
||
|
|
} else if $command == "whoami" {
|
||
|
|
"status"
|
||
|
|
} else if $command == "mfa" {
|
||
|
|
"mfa"
|
||
|
|
} else {
|
||
|
|
"status"
|
||
|
|
}
|
||
|
|
|
||
|
|
let remaining_ops = if ($ops | is-not-empty) {
|
||
|
|
($ops | split row " " | skip 1 | str join " ")
|
||
|
|
} else {
|
||
|
|
""
|
||
|
|
}
|
||
|
|
|
||
|
|
match $subcommand {
|
||
|
|
"login" => { handle_auth_login $remaining_ops $flags }
|
||
|
|
"logout" => { handle_auth_logout $flags }
|
||
|
|
"status" | "whoami" => { handle_auth_status $flags }
|
||
|
|
"sessions" | "list" => { handle_auth_sessions $flags }
|
||
|
|
"refresh" => { handle_auth_refresh $flags }
|
||
|
|
"mfa" | "mfa-enroll" | "mfa-verify" => {
|
||
|
|
handle_auth_mfa $subcommand $remaining_ops $flags
|
||
|
|
}
|
||
|
|
"help" => { show_auth_help }
|
||
|
|
_ => {
|
||
|
|
print $"❌ Unknown auth subcommand: ($subcommand)"
|
||
|
|
print ""
|
||
|
|
print "Available auth subcommands:"
|
||
|
|
print " login - Login with credentials"
|
||
|
|
print " logout - Logout (revoke session)"
|
||
|
|
print " status - Show current authentication status"
|
||
|
|
print " whoami - Alias for status"
|
||
|
|
print " sessions - List active sessions"
|
||
|
|
print " list - Alias for sessions"
|
||
|
|
print " refresh - Refresh authentication token"
|
||
|
|
print " mfa - MFA operations"
|
||
|
|
print " mfa-enroll - Enroll in MFA"
|
||
|
|
print " mfa-verify - Verify MFA code"
|
||
|
|
print " help - Show this help message"
|
||
|
|
print ""
|
||
|
|
print "Use 'provisioning auth help' for more details"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
print $"❌ Unknown authentication command: ($command)"
|
||
|
|
print ""
|
||
|
|
print "Available authentication commands:"
|
||
|
|
print " login - Login with username and password"
|
||
|
|
print " logout - Logout (revoke session)"
|
||
|
|
print " status | whoami - Show current user and session status"
|
||
|
|
print " sessions | list - List active sessions"
|
||
|
|
print " refresh - Refresh authentication token"
|
||
|
|
print " mfa - Multi-factor authentication"
|
||
|
|
print " mfa-enroll - Enroll in MFA (TOTP or WebAuthn)"
|
||
|
|
print " mfa-verify - Verify MFA code"
|
||
|
|
print " help - Show auth help"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " provisioning auth login"
|
||
|
|
print " provisioning auth status"
|
||
|
|
print " provisioning mfa totp enroll"
|
||
|
|
print " provisioning mfa verify --code 123456"
|
||
|
|
print ""
|
||
|
|
print "Use 'provisioning help authentication' for more details"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Login with username and password
|
||
|
|
def handle_auth_login [ops: string, flags: record] {
|
||
|
|
let username = if ($ops | is-not-empty) {
|
||
|
|
($ops | split row " " | get 0)
|
||
|
|
} else {
|
||
|
|
input $"(_ansi cyan)Username:(_ansi reset) "
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($username | is-empty) {
|
||
|
|
print $"(_ansi red)❌ Username is required(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
# Prompt for password securely
|
||
|
|
print $"(_ansi cyan)Password for ($username):(_ansi reset) "
|
||
|
|
let password = (input --suppress-output)
|
||
|
|
|
||
|
|
if ($password | is-empty) {
|
||
|
|
print $"(_ansi red)❌ Password is required(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"\n(_ansi cyan)Authenticating...(_ansi reset)"
|
||
|
|
|
||
|
|
let result = (do -i {
|
||
|
|
plugin-login $username $password
|
||
|
|
})
|
||
|
|
|
||
|
|
if $result != null {
|
||
|
|
print $"\n(_ansi green_bold)✓ Login successful!(_ansi reset)\n"
|
||
|
|
print $"(_ansi cyan)User:(_ansi reset) ($result.user? | default $username)"
|
||
|
|
print $"(_ansi cyan)Role:(_ansi reset) ($result.role? | default 'N/A')"
|
||
|
|
print $"(_ansi cyan)Expires:(_ansi reset) ($result.expires_at? | default 'N/A')"
|
||
|
|
print $"(_ansi cyan)MFA:(_ansi reset) ($result.mfa_enabled? | default false)"
|
||
|
|
|
||
|
|
if ($result.mfa_required? | default false) {
|
||
|
|
print $"\n(_ansi yellow)⚠️ MFA verification required(_ansi reset)"
|
||
|
|
print $"(_ansi cyan)Use: provisioning auth mfa verify --code <6-digit-code>(_ansi reset)"
|
||
|
|
} else {
|
||
|
|
print $"\n(_ansi green)Session active and ready(_ansi reset)"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
print $"\n(_ansi red_bold)❌ Login failed(_ansi reset)"
|
||
|
|
print $"(_ansi red)Authentication error occurred(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Logout and clear tokens
|
||
|
|
def handle_auth_logout [flags: record] {
|
||
|
|
print $"(_ansi cyan)Logging out...(_ansi reset)"
|
||
|
|
|
||
|
|
let result = (do -i {
|
||
|
|
plugin-logout
|
||
|
|
})
|
||
|
|
|
||
|
|
if $result != null {
|
||
|
|
print $"\n(_ansi green_bold)✓ Logged out successfully(_ansi reset)"
|
||
|
|
print $"(_ansi default_dimmed)All tokens have been cleared(_ansi reset)"
|
||
|
|
} else {
|
||
|
|
print $"\n(_ansi yellow)⚠️ Logout completed with warnings(_ansi reset)"
|
||
|
|
print $"(_ansi default_dimmed)Token cleanup may have failed(_ansi reset)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show authentication status
|
||
|
|
def handle_auth_status [flags: record] {
|
||
|
|
print $"(_ansi cyan_bold)Authentication Status(_ansi reset)\n"
|
||
|
|
|
||
|
|
let status = (do -i {
|
||
|
|
plugin-auth-status
|
||
|
|
})
|
||
|
|
|
||
|
|
if $status != null {
|
||
|
|
if ($status.authenticated? | default false) {
|
||
|
|
print $"(_ansi green)Status:(_ansi reset) Authenticated ✓"
|
||
|
|
print $"(_ansi cyan)User:(_ansi reset) ($status.user? | default 'N/A')"
|
||
|
|
print $"(_ansi cyan)Role:(_ansi reset) ($status.role? | default 'N/A')"
|
||
|
|
print $"(_ansi cyan)Expires at:(_ansi reset) ($status.expires_at? | default 'N/A')"
|
||
|
|
print $"(_ansi cyan)MFA enabled:(_ansi reset) ($status.mfa_enabled? | default false)"
|
||
|
|
|
||
|
|
if ($status.permissions? | is-not-empty) {
|
||
|
|
print $"\n(_ansi cyan_bold)Permissions:(_ansi reset)"
|
||
|
|
for perm in $status.permissions {
|
||
|
|
print $" • ($perm)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($status.warning? | is-not-empty) {
|
||
|
|
print $"\n(_ansi yellow)⚠️ ($status.warning)(_ansi reset)"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
print $"(_ansi red)Status:(_ansi reset) Not authenticated ✗"
|
||
|
|
print $"(_ansi cyan)Message:(_ansi reset) ($status.message? | default 'No active session')"
|
||
|
|
print $"\n(_ansi default_dimmed)Use 'provisioning auth login' to authenticate(_ansi reset)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show plugin mode
|
||
|
|
print $"\n(_ansi default_dimmed)Mode: ($status.mode? | default 'unknown')(_ansi reset)"
|
||
|
|
} else {
|
||
|
|
print $"(_ansi red)❌ Failed to get auth status(_ansi reset)"
|
||
|
|
print $"(_ansi red)Status check error occurred(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# List active sessions
|
||
|
|
def handle_auth_sessions [flags: record] {
|
||
|
|
print $"(_ansi cyan_bold)Active Sessions(_ansi reset)\n"
|
||
|
|
|
||
|
|
let sessions = (do -i {
|
||
|
|
plugin-sessions
|
||
|
|
})
|
||
|
|
|
||
|
|
if $sessions != null {
|
||
|
|
if ($sessions | length) == 0 {
|
||
|
|
print $"(_ansi yellow)No active sessions found(_ansi reset)"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
$sessions | table -e
|
||
|
|
} else {
|
||
|
|
print $"(_ansi red)❌ Failed to list sessions(_ansi reset)"
|
||
|
|
print $"(_ansi red)Session listing error occurred(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Refresh access token
|
||
|
|
def handle_auth_refresh [flags: record] {
|
||
|
|
print $"(_ansi cyan)Refreshing access token...(_ansi reset)"
|
||
|
|
|
||
|
|
let result = (do -i {
|
||
|
|
plugin-verify
|
||
|
|
})
|
||
|
|
|
||
|
|
if $result != null {
|
||
|
|
if ($result.valid? | default false) {
|
||
|
|
print $"(_ansi green)✓ Token is still valid(_ansi reset)"
|
||
|
|
print $"(_ansi cyan)Expires:(_ansi reset) ($result.expires_at? | default 'N/A')"
|
||
|
|
} else {
|
||
|
|
print $"(_ansi yellow)Token expired or invalid, please login again(_ansi reset)"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
print $"(_ansi red)❌ Token verification failed(_ansi reset)"
|
||
|
|
print $"(_ansi red)Verification error occurred(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Handle MFA commands
|
||
|
|
def handle_auth_mfa [subcommand: string, ops: string, flags: record] {
|
||
|
|
let mfa_action = if $subcommand == "mfa" {
|
||
|
|
if ($ops | is-not-empty) {
|
||
|
|
($ops | split row " " | get 0)
|
||
|
|
} else {
|
||
|
|
"help"
|
||
|
|
}
|
||
|
|
} else if $subcommand == "mfa-enroll" {
|
||
|
|
"enroll"
|
||
|
|
} else if $subcommand == "mfa-verify" {
|
||
|
|
"verify"
|
||
|
|
} else {
|
||
|
|
"help"
|
||
|
|
}
|
||
|
|
|
||
|
|
let remaining_ops = if $subcommand == "mfa" and ($ops | is-not-empty) {
|
||
|
|
($ops | split row " " | skip 1 | str join " ")
|
||
|
|
} else {
|
||
|
|
$ops
|
||
|
|
}
|
||
|
|
|
||
|
|
match $mfa_action {
|
||
|
|
"enroll" | "enable" | "setup" => { handle_mfa_enroll $remaining_ops $flags }
|
||
|
|
"verify" | "check" => { handle_mfa_verify $remaining_ops $flags }
|
||
|
|
"help" => { show_mfa_help }
|
||
|
|
_ => {
|
||
|
|
print $"❌ Unknown MFA action: ($mfa_action)"
|
||
|
|
print "Use 'provisioning auth mfa help' for available commands"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Enroll in MFA
|
||
|
|
def handle_mfa_enroll [ops: string, flags: record] {
|
||
|
|
let mfa_type = if ($ops | is-not-empty) {
|
||
|
|
($ops | split row " " | get 0)
|
||
|
|
} else {
|
||
|
|
"totp"
|
||
|
|
}
|
||
|
|
|
||
|
|
if $mfa_type not-in ["totp" "webauthn"] {
|
||
|
|
print $"(_ansi red)❌ Invalid MFA type: ($mfa_type)(_ansi reset)"
|
||
|
|
print $"(_ansi cyan)Valid types: totp, webauthn(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"(_ansi cyan_bold)Enrolling in MFA ($mfa_type)...(_ansi reset)\n"
|
||
|
|
|
||
|
|
let result = (do -i {
|
||
|
|
plugin-mfa-enroll --type $mfa_type
|
||
|
|
})
|
||
|
|
|
||
|
|
if $result != null {
|
||
|
|
if $mfa_type == "totp" {
|
||
|
|
print $"(_ansi green_bold)✓ TOTP enrollment initiated(_ansi reset)\n"
|
||
|
|
print $"(_ansi cyan_bold)Secret:(_ansi reset) ($result.secret? | default 'N/A')\n"
|
||
|
|
|
||
|
|
if ($result.qr_code? | is-not-empty) {
|
||
|
|
print $"(_ansi cyan_bold)QR Code:(_ansi reset)"
|
||
|
|
print $result.qr_code
|
||
|
|
print ""
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"(_ansi yellow_bold)📱 Setup Instructions:(_ansi reset)"
|
||
|
|
print $" 1. Open your authenticator app (Google Authenticator, Authy, etc.)"
|
||
|
|
print $" 2. Scan the QR code above or manually enter the secret"
|
||
|
|
print $" 3. Verify with the 6-digit code:\n"
|
||
|
|
print $" (_ansi cyan)provisioning auth mfa verify --code <6-digit-code>(_ansi reset)\n"
|
||
|
|
} else {
|
||
|
|
print $"(_ansi green_bold)✓ WebAuthn enrollment initiated(_ansi reset)\n"
|
||
|
|
print $"(_ansi yellow_bold)🔑 Setup Instructions:(_ansi reset)"
|
||
|
|
print $" 1. Follow browser prompts to register your security key"
|
||
|
|
print $" 2. Touch your YubiKey, use Touch ID, or Windows Hello"
|
||
|
|
print $" 3. Complete registration in the web interface\n"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
print $"\n(_ansi red_bold)❌ MFA enrollment failed(_ansi reset)"
|
||
|
|
print $"(_ansi red)Enrollment error occurred(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Verify MFA code
|
||
|
|
def handle_mfa_verify [ops: string, flags: record] {
|
||
|
|
let code = if ($flags.code? | is-not-empty) {
|
||
|
|
$flags.code
|
||
|
|
} else if ($ops | is-not-empty) {
|
||
|
|
($ops | split row " " | get 0)
|
||
|
|
} else {
|
||
|
|
input $"(_ansi cyan)Enter 6-digit MFA code:(_ansi reset) "
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($code | is-empty) {
|
||
|
|
print $"(_ansi red)❌ MFA code is required(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"(_ansi cyan)Verifying MFA code...(_ansi reset)"
|
||
|
|
|
||
|
|
let result = (do -i {
|
||
|
|
plugin-mfa-verify $code
|
||
|
|
})
|
||
|
|
|
||
|
|
if $result != null {
|
||
|
|
print $"\n(_ansi green_bold)✓ MFA verified successfully!(_ansi reset)\n"
|
||
|
|
print $"(_ansi cyan)Status:(_ansi reset) ($result.status? | default 'Active')"
|
||
|
|
|
||
|
|
if ($result.backup_codes? | is-not-empty) {
|
||
|
|
print $"\n(_ansi yellow_bold)⚠️ Backup Codes \(save these securely\):(_ansi reset)"
|
||
|
|
for code in $result.backup_codes {
|
||
|
|
print $" • ($code)"
|
||
|
|
}
|
||
|
|
print $"\n(_ansi default_dimmed)Use these codes if you lose access to your MFA device(_ansi reset)"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
print $"\n(_ansi red_bold)❌ MFA verification failed(_ansi reset)"
|
||
|
|
print $"(_ansi red)Verification error occurred(_ansi reset)"
|
||
|
|
print $"\n(_ansi yellow)💡 Tip: Make sure the code is current (30-second window)(_ansi reset)"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show authentication help
|
||
|
|
def show_auth_help [] {
|
||
|
|
print $"
|
||
|
|
(_ansi cyan_bold)╔══════════════════════════════════════════════════╗(_ansi reset)
|
||
|
|
(_ansi cyan_bold)║(_ansi reset) 🔐 AUTHENTICATION COMMANDS (_ansi cyan_bold)║(_ansi reset)
|
||
|
|
(_ansi cyan_bold)╚══════════════════════════════════════════════════╝(_ansi reset)
|
||
|
|
|
||
|
|
(_ansi green_bold)[Session Management](_ansi reset)
|
||
|
|
(_ansi blue)auth login <username>(_ansi reset) Login and store JWT tokens
|
||
|
|
(_ansi blue)auth logout(_ansi reset) Logout and clear tokens
|
||
|
|
(_ansi blue)auth status(_ansi reset) Show current authentication status
|
||
|
|
(_ansi blue)auth sessions(_ansi reset) List active sessions
|
||
|
|
(_ansi blue)auth refresh(_ansi reset) Verify/refresh token
|
||
|
|
|
||
|
|
(_ansi green_bold)[Multi-Factor Authentication](_ansi reset)
|
||
|
|
(_ansi blue)auth mfa enroll <type>(_ansi reset) Enroll in MFA (totp or webauthn)
|
||
|
|
(_ansi blue)auth mfa verify --code <code>(_ansi reset) Verify MFA code
|
||
|
|
|
||
|
|
(_ansi green_bold)EXAMPLES(_ansi reset)
|
||
|
|
|
||
|
|
# Login interactively
|
||
|
|
provisioning auth login
|
||
|
|
|
||
|
|
# Login with username
|
||
|
|
provisioning auth login admin
|
||
|
|
|
||
|
|
# Check authentication status
|
||
|
|
provisioning auth status
|
||
|
|
provisioning whoami (_ansi default_dimmed)# shortcut(_ansi reset)
|
||
|
|
|
||
|
|
# Enroll in TOTP MFA
|
||
|
|
provisioning auth mfa enroll totp
|
||
|
|
|
||
|
|
# Verify MFA code
|
||
|
|
provisioning auth mfa verify --code 123456
|
||
|
|
|
||
|
|
# List active sessions
|
||
|
|
provisioning auth sessions
|
||
|
|
|
||
|
|
# Logout
|
||
|
|
provisioning auth logout
|
||
|
|
|
||
|
|
(_ansi green_bold)SHORTCUTS(_ansi reset)
|
||
|
|
|
||
|
|
login → auth login
|
||
|
|
logout → auth logout
|
||
|
|
whoami → auth status
|
||
|
|
mfa → auth mfa
|
||
|
|
mfa-enroll → auth mfa enroll
|
||
|
|
mfa-verify → auth mfa verify
|
||
|
|
|
||
|
|
(_ansi default_dimmed)💡 Authentication uses JWT tokens with RS256 signing
|
||
|
|
Tokens are stored securely and expire after 15 minutes
|
||
|
|
MFA is required for sensitive operations in production(_ansi reset)
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show MFA help
|
||
|
|
def show_mfa_help [] {
|
||
|
|
print $"
|
||
|
|
(_ansi cyan_bold)╔══════════════════════════════════════════════════╗(_ansi reset)
|
||
|
|
(_ansi cyan_bold)║(_ansi reset) 🔐 MULTI-FACTOR AUTHENTICATION (_ansi cyan_bold)║(_ansi reset)
|
||
|
|
(_ansi cyan_bold)╚══════════════════════════════════════════════════╝(_ansi reset)
|
||
|
|
|
||
|
|
(_ansi green_bold)[MFA Types](_ansi reset)
|
||
|
|
|
||
|
|
(_ansi blue)TOTP (Time-based One-Time Password)(_ansi reset)
|
||
|
|
• 6-digit codes that change every 30 seconds
|
||
|
|
• Works with Google Authenticator, Authy, etc.
|
||
|
|
• No internet required after setup
|
||
|
|
|
||
|
|
(_ansi blue)WebAuthn/FIDO2(_ansi reset)
|
||
|
|
• Hardware security keys (YubiKey)
|
||
|
|
• Biometric authentication (Touch ID, Windows Hello)
|
||
|
|
• Phishing-resistant
|
||
|
|
|
||
|
|
(_ansi green_bold)[Commands](_ansi reset)
|
||
|
|
|
||
|
|
(_ansi blue)auth mfa enroll totp(_ansi reset) Enroll in TOTP MFA
|
||
|
|
(_ansi blue)auth mfa enroll webauthn(_ansi reset) Enroll in WebAuthn MFA
|
||
|
|
(_ansi blue)auth mfa verify --code <code>(_ansi reset) Verify MFA code
|
||
|
|
|
||
|
|
(_ansi green_bold)EXAMPLES(_ansi reset)
|
||
|
|
|
||
|
|
# Enroll in TOTP
|
||
|
|
provisioning auth mfa enroll totp
|
||
|
|
|
||
|
|
# Scan QR code with authenticator app
|
||
|
|
# Then verify with 6-digit code
|
||
|
|
provisioning auth mfa verify --code 123456
|
||
|
|
|
||
|
|
# Enroll in WebAuthn
|
||
|
|
provisioning auth mfa enroll webauthn
|
||
|
|
|
||
|
|
(_ansi default_dimmed)💡 MFA enrollment requires active authentication session
|
||
|
|
Use 'provisioning auth login' first if not authenticated(_ansi reset)
|
||
|
|
"
|
||
|
|
}
|