Jesús Pérez eb20fec7de
chore: release 1.0.11 - nu script cleanup & refactoring + i18n fluentd
- Documented Fluent-based i18n system with locale detection
  - Bumped version from 1.0.10 to 1.0.11
2026-01-14 02:00:23 +00:00

1067 lines
31 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env nu
# [command]
# name = "auth login"
# group = "authentication"
# tags = ["authentication", "jwt", "interactive", "login"]
# version = "3.0.0"
# requires = ["nushell:0.109.0"]
# Authentication Plugin Wrapper with HTTP Fallback
# Provides graceful degradation to HTTP API when nu_plugin_auth is unavailable
use ../config/accessor.nu *
use ../commands/traits.nu *
# Check if auth plugin is available
def is-plugin-available [] {
(which auth | length) > 0
}
# Check if auth plugin is enabled in config
def is-plugin-enabled [] {
config-get "plugins.auth_enabled" true
}
# Get control center base URL
def get-control-center-url [] {
config-get "platform.control_center.url" "http://localhost:3000"
}
# Store token in OS keyring (requires plugin)
def store-token-keyring [
token: string
] {
if (is-plugin-available) {
auth store-token $token
} else {
print "⚠️ Keyring storage unavailable (plugin not loaded)"
}
}
# Retrieve token from OS keyring (requires plugin)
def get-token-keyring [] {
if (is-plugin-available) {
auth get-token
} else {
""
}
}
# Helper to safely execute a closure and return null on error
def try-plugin [callback: closure] {
do -i $callback
}
# Login with username and password
export def plugin-login [
username: string
password: string
--mfa-code: string = "" # Optional MFA code
] {
let enabled = is-plugin-enabled
let available = is-plugin-available
if $enabled and $available {
let plugin_result = (try-plugin {
# Note: Plugin login command may not support MFA code directly
# If MFA is required, it should be handled separately via mfa-verify
let result = (auth login $username $password)
store-token-keyring $result.access_token
# If MFA code provided, verify it after login
if not ($mfa_code | is-empty) {
let mfa_result = (try-plugin {
auth mfa-verify $mfa_code
})
if $mfa_result == null {
print "⚠️ MFA verification failed, but login succeeded"
}
}
$result
})
if $plugin_result != null {
return $plugin_result
}
print "⚠️ Plugin login failed, falling back to HTTP"
}
# HTTP fallback
print "⚠️ Using HTTP fallback (plugin not available)"
let url = $"(get-control-center-url)/api/auth/login"
let body = if ($mfa_code | is-empty) {
{username: $username, password: $password}
} else {
{username: $username, password: $password, mfa_code: $mfa_code}
}
let result = (do -i {
http post $url $body
})
if $result != null {
return $result
}
error make {
msg: "Login failed"
label: {
text: "HTTP request failed"
span: (metadata $username).span
}
}
}
# Logout and revoke tokens
export def plugin-logout [] {
let enabled = is-plugin-enabled
let available = is-plugin-available
let token = get-token-keyring
if $enabled and $available {
let plugin_result = (try-plugin {
auth logout
})
if $plugin_result != null {
return $plugin_result
}
print "⚠️ Plugin logout failed, falling back to HTTP"
}
# HTTP fallback
print "⚠️ Using HTTP fallback (plugin not available)"
let url = $"(get-control-center-url)/api/auth/logout"
let result = (do -i {
if ($token | is-empty) {
http post $url
} else {
http post $url --headers {Authorization: $"Bearer ($token)"}
}
})
if $result != null {
return {success: true, message: "Logged out successfully"}
}
{success: false, message: "Logout failed"}
}
# Verify current authentication token
export def plugin-verify [] {
let enabled = is-plugin-enabled
let available = is-plugin-available
if $enabled and $available {
let plugin_result = (try-plugin {
auth verify
})
if $plugin_result != null {
return $plugin_result
}
print "⚠️ Plugin verify failed, falling back to HTTP"
}
# HTTP fallback
print "⚠️ Using HTTP fallback (plugin not available)"
let token = get-token-keyring
if ($token | is-empty) {
return {valid: false, message: "No token found"}
}
let url = $"(get-control-center-url)/api/auth/verify"
let result = (do -i {
http get $url --headers {Authorization: $"Bearer ($token)"}
})
if $result != null {
return $result
}
{valid: false, message: "Token verification failed"}
}
# List active sessions
export def plugin-sessions [] {
let enabled = is-plugin-enabled
let available = is-plugin-available
if $enabled and $available {
let plugin_result = (try-plugin {
auth sessions
})
if $plugin_result != null {
return $plugin_result
}
print "⚠️ Plugin sessions failed, falling back to HTTP"
}
# HTTP fallback
print "⚠️ Using HTTP fallback (plugin not available)"
let token = get-token-keyring
if ($token | is-empty) {
return []
}
let url = $"(get-control-center-url)/api/auth/sessions"
let response = (do -i {
http get $url --headers {Authorization: $"Bearer ($token)"}
})
if $response != null {
return ($response | get sessions? | default [])
}
[]
}
# Enroll MFA device (TOTP)
export def plugin-mfa-enroll [
--type: string = "totp" # totp or webauthn
] {
let enabled = is-plugin-enabled
let available = is-plugin-available
if $enabled and $available {
let plugin_result = (try-plugin {
auth mfa-enroll --type $type
})
if $plugin_result != null {
return $plugin_result
}
print "⚠️ Plugin MFA enroll failed, falling back to HTTP"
}
# HTTP fallback
print "⚠️ Using HTTP fallback (plugin not available)"
let token = get-token-keyring
if ($token | is-empty) {
error make {
msg: "Authentication required"
label: {text: "No valid token found"}
}
}
let url = $"(get-control-center-url)/api/mfa/enroll"
let result = (do -i {
http post $url {type: $type} --headers {Authorization: $"Bearer ($token)"}
})
if $result != null {
return $result
}
error make {
msg: "MFA enrollment failed"
label: {text: "HTTP request failed"}
}
}
# Verify MFA code
export def plugin-mfa-verify [
code: string
--type: string = "totp" # totp or webauthn
] {
let enabled = is-plugin-enabled
let available = is-plugin-available
if $enabled and $available {
let plugin_result = (try-plugin {
auth mfa-verify $code --type $type
})
if $plugin_result != null {
return $plugin_result
}
print "⚠️ Plugin MFA verify failed, falling back to HTTP"
}
# HTTP fallback
print "⚠️ Using HTTP fallback (plugin not available)"
let token = get-token-keyring
if ($token | is-empty) {
error make {
msg: "Authentication required"
label: {text: "No valid token found"}
}
}
let url = $"(get-control-center-url)/api/mfa/verify"
let result = (do -i {
http post $url {code: $code, type: $type} --headers {Authorization: $"Bearer ($token)"}
})
if $result != null {
return $result
}
error make {
msg: "MFA verification failed"
label: {
text: "HTTP request failed"
span: (metadata $code).span
}
}
}
# Get current authentication status
export def plugin-auth-status [] {
let plugin_available = is-plugin-available
let plugin_enabled = is-plugin-enabled
let token = get-token-keyring
let has_token = not ($token | is-empty)
{
plugin_available: $plugin_available
plugin_enabled: $plugin_enabled
has_token: $has_token
mode: (if ($plugin_enabled and $plugin_available) { "plugin" } else { "http" })
}
}
# ============================================================================
# Metadata-Driven Authentication Helpers
# ============================================================================
# Get auth requirements from metadata for a specific command
def get-metadata-auth-requirements [
command_name: string # Command to check (e.g., "server create", "cluster delete")
] {
let metadata = (get-command-metadata $command_name)
if ($metadata | type) == "record" {
let requirements = ($metadata | get requirements? | default {})
{
requires_auth: ($requirements | get requires_auth? | default false)
auth_type: ($requirements | get auth_type? | default "none")
requires_confirmation: ($requirements | get requires_confirmation? | default false)
min_permission: ($requirements | get min_permission? | default "read")
side_effect_type: ($requirements | get side_effect_type? | default "none")
}
} else {
{
requires_auth: false
auth_type: "none"
requires_confirmation: false
min_permission: "read"
side_effect_type: "none"
}
}
}
# Determine if MFA is required based on metadata auth_type
def requires-mfa-from-metadata [
command_name: string # Command to check
] {
let auth_reqs = (get-metadata-auth-requirements $command_name)
$auth_reqs.auth_type == "mfa" or $auth_reqs.auth_type == "cedar"
}
# Determine if operation is destructive based on metadata
def is-destructive-from-metadata [
command_name: string # Command to check
] {
let auth_reqs = (get-metadata-auth-requirements $command_name)
$auth_reqs.side_effect_type == "delete"
}
# Check if metadata indicates this is a production operation
def is-production-from-metadata [
command_name: string # Command to check
] {
let metadata = (get-command-metadata $command_name)
if ($metadata | type) == "record" {
let tags = ($metadata | get tags? | default [])
($tags | any { |tag| $tag == "production" or $tag == "deploy" })
} else {
false
}
}
# Validate minimum permission level required by metadata
def validate-permission-level [
command_name: string # Command to check
user_level: string # User's permission level (read, write, admin, superadmin)
] {
let auth_reqs = (get-metadata-auth-requirements $command_name)
let required_level = $auth_reqs.min_permission
# Permission level hierarchy (lower index = lower permission)
let level_map = {
read: 0
write: 1
admin: 2
superadmin: 3
}
# Get required permission level index
let req_level = (
if $required_level == "read" { 0 }
else if $required_level == "write" { 1 }
else if $required_level == "admin" { 2 }
else if $required_level == "superadmin" { 3 }
else { -1 }
)
# Get user permission level index
let usr_level = (
if $user_level == "read" { 0 }
else if $user_level == "write" { 1 }
else if $user_level == "admin" { 2 }
else if $user_level == "superadmin" { 3 }
else { -1 }
)
# User must have equal or higher permission level
if $req_level < 0 or $usr_level < 0 {
return false
}
$usr_level >= $req_level
}
# Determine auth enforcement based on metadata
export def should-enforce-auth-from-metadata [
command_name: string # Command to check
] {
let auth_reqs = (get-metadata-auth-requirements $command_name)
# If metadata explicitly requires auth, enforce it
if $auth_reqs.requires_auth {
return true
}
# If side effects, enforce auth
if $auth_reqs.side_effect_type != "none" {
return true
}
# Otherwise check configuration
(should-require-auth)
}
# ============================================================================
# Security Policy Enforcement Functions
# ============================================================================
# Check if authentication is required based on configuration
export def should-require-auth [] {
let config_required = (config-get "security.require_auth" false)
let env_bypass = ($env.PROVISIONING_SKIP_AUTH? | default "false") == "true"
let allow_bypass = (config-get "security.bypass.allow_skip_auth" false)
$config_required and not ($env_bypass and $allow_bypass)
}
# Check if MFA is required for production operations
export def should-require-mfa-prod [] {
let environment = (config-get "environment" "dev")
let require_mfa = (config-get "security.require_mfa_for_production" true)
($environment == "prod") and $require_mfa
}
# Check if MFA is required for destructive operations
export def should-require-mfa-destructive [] {
(config-get "security.require_mfa_for_destructive" true)
}
# Check if user is authenticated
export def is-authenticated [] {
let result = (plugin-verify)
($result | get valid? | default false)
}
# Check if MFA is verified
export def is-mfa-verified [] {
let result = (plugin-verify)
($result | get mfa_verified? | default false)
}
# Get current authenticated user
export def get-authenticated-user [] {
let result = (plugin-verify)
($result | get username? | default "")
}
# Require authentication with clear error messages
export def require-auth [
operation: string # Operation name for error messages
--allow-skip # Allow skip-auth flag bypass
] {
# Check if authentication is required
if not (should-require-auth) {
return true
}
# Check if skip is allowed
if $allow_skip and (($env.PROVISIONING_SKIP_AUTH? | default "false") == "true") {
print $"⚠️ Authentication bypassed with PROVISIONING_SKIP_AUTH flag"
print $" (ansi yellow_bold)WARNING: This should only be used in development/testing!(ansi reset)"
return true
}
# Verify authentication
let auth_status = (plugin-verify)
if not ($auth_status | get valid? | default false) {
print $"(ansi red_bold)❌ Authentication Required(ansi reset)"
print ""
print $"Operation: (ansi cyan_bold)($operation)(ansi reset)"
print $"You must be logged in to perform this operation."
print ""
print $"(ansi green_bold)To login:(ansi reset)"
print $" provisioning auth login <username>"
print ""
print $"(ansi yellow_bold)Note:(ansi reset) Your credentials will be securely stored in the system keyring."
if ($auth_status | get message? | default null | is-not-empty) {
print ""
print $"(ansi red)Error:(ansi reset) ($auth_status.message)"
}
exit 1
}
let username = ($auth_status | get username? | default "unknown")
print $"(ansi green)✓(ansi reset) Authenticated as: (ansi cyan_bold)($username)(ansi reset)"
true
}
# Require MFA verification with clear error messages
export def require-mfa [
operation: string # Operation name for error messages
reason: string # Reason MFA is required
] {
let auth_status = (plugin-verify)
if not ($auth_status | get mfa_verified? | default false) {
print $"(ansi red_bold)❌ MFA Verification Required(ansi reset)"
print ""
print $"Operation: (ansi cyan_bold)($operation)(ansi reset)"
print $"Reason: (ansi yellow)($reason)(ansi reset)"
print ""
print $"(ansi green_bold)To verify MFA:(ansi reset)"
print $" 1. Get code from your authenticator app"
print $" 2. Run: provisioning auth mfa verify --code <6-digit-code>"
print ""
print $"(ansi yellow_bold)Don't have MFA set up?(ansi reset)"
print $" Run: provisioning auth mfa enroll totp"
exit 1
}
print $"(ansi green)✓(ansi reset) MFA verified"
true
}
# Check authentication and MFA for production operations (enhanced with metadata)
export def check-auth-for-production [
operation: string # Operation name
--allow-skip # Allow skip-auth flag bypass
] {
# First check if this command is actually production-related via metadata
if (is-production-from-metadata $operation) {
# Require authentication first
require-auth $operation --allow-skip=$allow_skip
# Check if MFA is required based on metadata or config
let requires_mfa_metadata = (requires-mfa-from-metadata $operation)
if $requires_mfa_metadata or (should-require-mfa-prod) {
require-mfa $operation "production environment operation"
}
return true
}
# Fallback to configuration-based check if not in metadata
if (should-require-mfa-prod) {
require-auth $operation --allow-skip=$allow_skip
require-mfa $operation "production environment operation"
}
true
}
# Check authentication and MFA for destructive operations (enhanced with metadata)
export def check-auth-for-destructive [
operation: string # Operation name
--allow-skip # Allow skip-auth flag bypass
] {
# Check if this is a destructive operation via metadata
if (is-destructive-from-metadata $operation) {
# Always require authentication for destructive ops
require-auth $operation --allow-skip=$allow_skip
# Check if MFA is required based on metadata or config
let requires_mfa_metadata = (requires-mfa-from-metadata $operation)
if $requires_mfa_metadata or (should-require-mfa-destructive) {
require-mfa $operation "destructive operation (delete/destroy)"
}
return true
}
# Fallback to configuration-based check
if (should-require-mfa-destructive) {
require-auth $operation --allow-skip=$allow_skip
require-mfa $operation "destructive operation (delete/destroy)"
}
true
}
# Helper: Check if operation is in check mode (should skip auth)
export def is-check-mode [flags: record] {
(($flags | get check? | default false) or
($flags | get check_mode? | default false) or
($flags | get c? | default false))
}
# Helper: Determine if operation is destructive
export def is-destructive-operation [operation_type: string] {
$operation_type in ["delete" "destroy" "remove"]
}
# Main authentication check for any operation (enhanced with metadata)
export def check-operation-auth [
operation_name: string # Name of operation
operation_type: string # Type: create, delete, modify, read
flags?: record # Command flags
] {
# Skip in check mode
if ($flags | is-not-empty) and (is-check-mode $flags) {
print $"(ansi dim)Skipping authentication check (check mode)(ansi reset)"
return true
}
# Check metadata-driven auth enforcement first
if (should-enforce-auth-from-metadata $operation_name) {
let auth_reqs = (get-metadata-auth-requirements $operation_name)
# Require authentication
let allow_skip = (config-get "security.bypass.allow_skip_auth" false)
require-auth $operation_name --allow-skip=$allow_skip
# Check MFA based on auth_type from metadata
if $auth_reqs.auth_type == "mfa" {
require-mfa $operation_name $"MFA required for ($operation_name)"
} else if $auth_reqs.auth_type == "cedar" {
# Cedar policy evaluation would go here
require-mfa $operation_name "Cedar policy verification required"
}
# Validate permission level if set
let user_level = (config-get "security.user_permission_level" "read")
if not (validate-permission-level $operation_name $user_level) {
print $"(ansi red_bold)❌ Insufficient Permissions(ansi reset)"
print $"Operation: (ansi cyan)($operation_name)(ansi reset)"
print $"Required: (ansi yellow)($auth_reqs.min_permission)(ansi reset)"
print $"Your level: (ansi yellow)($user_level)(ansi reset)"
exit 1
}
return true
}
# Skip if auth not required by configuration
if not (should-require-auth) {
return true
}
# Fallback to configuration-based checks
let allow_skip = (config-get "security.bypass.allow_skip_auth" false)
require-auth $operation_name --allow-skip=$allow_skip
# Get environment
let environment = (config-get "environment" "dev")
# Check MFA requirements based on environment and operation type
if $environment == "prod" and (should-require-mfa-prod) {
require-mfa $operation_name "production environment"
} else if (is-destructive-operation $operation_type) and (should-require-mfa-destructive) {
require-mfa $operation_name "destructive operation"
}
true
}
# Get authentication metadata for audit logging
export def get-auth-metadata [] {
let auth_status = (plugin-verify)
{
authenticated: ($auth_status | get valid? | default false)
mfa_verified: ($auth_status | get mfa_verified? | default false)
username: ($auth_status | get username? | default "anonymous")
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
}
}
# Log authenticated operation for audit trail
export def log-authenticated-operation [
operation: string # Operation performed
details: record # Operation details
] {
let auth_metadata = (get-auth-metadata)
let log_entry = {
timestamp: $auth_metadata.timestamp
user: $auth_metadata.username
operation: $operation
details: $details
mfa_verified: $auth_metadata.mfa_verified
}
# Log to file if configured
let log_path = (config-get "security.audit_log_path" "")
if ($log_path | is-not-empty) {
let log_dir = ($log_path | path dirname)
if ($log_dir | path exists) {
$log_entry | to json | save --append $log_path
}
}
}
# Print current authentication status (user-friendly)
export def print-auth-status [] {
let auth_status = (plugin-verify)
let is_valid = ($auth_status | get valid? | default false)
print $"(ansi blue_bold)Authentication Status(ansi reset)"
print $"━━━━━━━━━━━━━━━━━━━━━━━━"
if $is_valid {
let username = ($auth_status | get username? | default "unknown")
let mfa_verified = ($auth_status | get mfa_verified? | default false)
print $"Status: (ansi green_bold)✓ Authenticated(ansi reset)"
print $"User: (ansi cyan)($username)(ansi reset)"
if $mfa_verified {
print $"MFA: (ansi green_bold)✓ Verified(ansi reset)"
} else {
print $"MFA: (ansi yellow)Not verified(ansi reset)"
}
} else {
print $"Status: (ansi red)✗ Not authenticated(ansi reset)"
print ""
print $"Run: (ansi green)provisioning auth login <username>(ansi reset)"
}
print ""
print $"(ansi dim)Authentication required:(ansi reset) (should-require-auth)"
print $"(ansi dim)MFA for production:(ansi reset) (should-require-mfa-prod)"
print $"(ansi dim)MFA for destructive:(ansi reset) (should-require-mfa-destructive)"
}
# ============================================================================
# TYPEDIALOG HELPER FUNCTIONS
# ============================================================================
# Run TypeDialog form via bash wrapper for authentication
# This pattern avoids TTY/input issues in Nushell's execution stack
def run-typedialog-auth-form [
wrapper_script: string
--backend: string = "tui"
] {
# Check if the wrapper script exists
if not ($wrapper_script | path exists) {
return {
success: false
error: "TypeDialog wrapper not available"
use_fallback: true
}
}
# Set backend environment variable
$env.TYPEDIALOG_BACKEND = $backend
# Run bash wrapper (handles TTY input properly)
let result = (do { bash $wrapper_script } | complete)
if $result.exit_code != 0 {
return {
success: false
error: $result.stderr
use_fallback: true
}
}
# Read the generated JSON file
let json_output = ($wrapper_script | path dirname | path join "generated" | path join ($wrapper_script | path basename | str replace ".sh" "-result.json"))
if not ($json_output | path exists) {
return {
success: false
error: "Output file not found"
use_fallback: true
}
}
# Parse JSON output
let result = do {
open $json_output | from json
} | complete
if $result.exit_code == 0 {
let values = $result.stdout
{
success: true
values: $values
use_fallback: false
}
} else {
return {
success: false
error: "Failed to parse TypeDialog output"
use_fallback: true
}
}
}
# ============================================================================
# INTERACTIVE FORM HANDLERS (TypeDialog Integration)
# ============================================================================
# Interactive login with form
export def login-interactive [
--backend: string = "tui"
] : nothing -> record {
print "🔐 Interactive Authentication"
print ""
# Run the login form via bash wrapper
let wrapper_script = "provisioning/core/shlib/auth-login-tty.sh"
let form_result = (run-typedialog-auth-form $wrapper_script --backend $backend)
# Fallback to basic prompts if TypeDialog not available
if not $form_result.success or $form_result.use_fallback {
print " TypeDialog not available. Using basic prompts..."
print ""
print "Username: "
let username = (input)
print "Password: "
let password = (input --suppress-output)
print "Do you have MFA enabled? (y/n): "
let has_mfa_input = (input)
let has_mfa = ($has_mfa_input == "y" or $has_mfa_input == "Y")
let mfa_code = if $has_mfa {
print "MFA Code (6 digits): "
input
} else {
""
}
if ($username | is-empty) or ($password | is-empty) {
return {
success: false
error: "Username and password are required"
}
}
let login_result = (plugin-login $username $password --mfa-code $mfa_code)
return {
success: true
result: $login_result
username: $username
mfa_enabled: $has_mfa
}
}
let form_values = $form_result.values
# Check if user cancelled or didn't confirm
if not ($form_values.auth?.confirm_login? | default false) {
return {
success: false
error: "Login cancelled by user"
}
}
# Perform login with provided credentials
let username = ($form_values.auth?.username? | default "")
let password = ($form_values.auth?.password? | default "")
let has_mfa = ($form_values.auth?.has_mfa? | default false)
let mfa_code = if $has_mfa {
$form_values.auth?.mfa_code? | default ""
} else {
""
}
if ($username | is-empty) or ($password | is-empty) {
return {
success: false
error: "Username and password are required"
}
}
# Call the plugin login function
let login_result = (plugin-login $username $password --mfa-code $mfa_code)
{
success: true
result: $login_result
username: $username
mfa_enabled: $has_mfa
}
}
# Interactive MFA enrollment with form
export def mfa-enroll-interactive [
--backend: string = "tui"
] : nothing -> record {
print "🔐 Multi-Factor Authentication Setup"
print ""
# Check if user is already authenticated
let auth_status = (plugin-verify)
let is_authenticated = ($auth_status.valid // false)
if not $is_authenticated {
return {
success: false
error: "Must be authenticated to enroll in MFA. Please login first."
}
}
# Run the MFA enrollment form via bash wrapper
let wrapper_script = "provisioning/core/shlib/mfa-enroll-tty.sh"
let form_result = (run-typedialog-auth-form $wrapper_script --backend $backend)
# Fallback to basic prompts if TypeDialog not available
if not $form_result.success or $form_result.use_fallback {
print " TypeDialog not available. Using basic prompts..."
print ""
print "MFA Type (totp/webauthn/sms): "
let mfa_type = (input)
let device_name = if ($mfa_type == "totp" or $mfa_type == "webauthn") {
print "Device name: "
input
} else if $mfa_type == "sms" {
""
} else {
""
}
let phone_number = if $mfa_type == "sms" {
print "Phone number (international format, e.g., +1234567890): "
input
} else {
""
}
let verification_code = if ($mfa_type == "totp" or $mfa_type == "sms") {
print "Verification code (6 digits): "
input
} else {
""
}
print "Generate backup codes? (y/n): "
let generate_backup_input = (input)
let generate_backup = ($generate_backup_input == "y" or $generate_backup_input == "Y")
let backup_count = if $generate_backup {
print "Number of backup codes (5-20): "
let count_str = (input)
$count_str | into int | default 10
} else {
0
}
return {
success: true
mfa_type: $mfa_type
device_name: $device_name
phone_number: $phone_number
verification_code: $verification_code
generate_backup_codes: $generate_backup
backup_codes_count: $backup_count
}
}
let form_values = $form_result.values
# Check if user confirmed
if not ($form_values.mfa?.confirm_enroll? | default false) {
return {
success: false
error: "MFA enrollment cancelled by user"
}
}
# Extract MFA type and parameters from form values
let mfa_type = ($form_values.mfa?.type? | default "totp")
let device_name = if $mfa_type == "totp" {
$form_values.mfa?.totp?.device_name? | default "Authenticator App"
} else if $mfa_type == "webauthn" {
$form_values.mfa?.webauthn?.device_name? | default "Security Key"
} else if $mfa_type == "sms" {
""
} else {
""
}
let phone_number = if $mfa_type == "sms" {
$form_values.mfa?.sms?.phone_number? | default ""
} else {
""
}
let verification_code = if $mfa_type == "totp" {
$form_values.mfa?.totp?.verification_code? | default ""
} else if $mfa_type == "sms" {
$form_values.mfa?.sms?.verification_code? | default ""
} else {
""
}
let generate_backup = ($form_values.mfa?.generate_backup_codes? | default true)
let backup_count = ($form_values.mfa?.backup_codes_count? | default 10)
# Call the plugin MFA enrollment function
let enroll_result = (plugin-mfa-enroll --type $mfa_type)
{
success: true
result: $enroll_result
mfa_type: $mfa_type
device_name: $device_name
phone_number: $phone_number
verification_code: $verification_code
generate_backup_codes: $generate_backup
backup_codes_count: $backup_count
}
}