Update core components including CLI, Nushell libraries, plugins system, and utility scripts for the provisioning system. CLI Updates: - Command implementations - CLI utilities and dispatching - Help system improvements - Command validation Library Updates: - Configuration management system - Infrastructure validation - Extension system improvements - Secrets management - Workspace operations - Cache management system Plugin System: - Interactive form plugin (inquire) - KCL integration plugin - Performance optimization plugins - Plugin registration system Utilities: - Build and distribution scripts - Installation procedures - Testing utilities - Development tools Documentation: - Library module documentation - Extension API guides - Plugin usage guides - Service management documentation All changes are backward compatible. No breaking changes.
893 lines
27 KiB
Plaintext
893 lines
27 KiB
Plaintext
#!/usr/bin/env nu
|
|
# [command]
|
|
# name = "auth login"
|
|
# group = "authentication"
|
|
# tags = ["authentication", "jwt", "interactive", "login"]
|
|
# version = "2.1.0"
|
|
# requires = ["forminquire.nu:1.0.0", "nushell:0.109.0"]
|
|
# note = "Migrated to FormInquire interactive forms for login and MFA enrollment"
|
|
|
|
# Authentication Plugin Wrapper with HTTP Fallback
|
|
# Provides graceful degradation to HTTP API when nu_plugin_auth is unavailable
|
|
|
|
use ../config/accessor.nu *
|
|
use ../../../forminquire/nulib/forminquire.nu *
|
|
use ../commands/traits.nu *
|
|
|
|
# Check if auth plugin is available
|
|
def is-plugin-available []: nothing -> bool {
|
|
(which auth | length) > 0
|
|
}
|
|
|
|
# Check if auth plugin is enabled in config
|
|
def is-plugin-enabled []: nothing -> bool {
|
|
config-get "plugins.auth_enabled" true
|
|
}
|
|
|
|
# Get control center base URL
|
|
def get-control-center-url []: nothing -> string {
|
|
config-get "platform.control_center.url" "http://localhost:3000"
|
|
}
|
|
|
|
# Store token in OS keyring (requires plugin)
|
|
def store-token-keyring [
|
|
token: string
|
|
]: nothing -> nothing {
|
|
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 []: nothing -> string {
|
|
if (is-plugin-available) {
|
|
auth get-token
|
|
} else {
|
|
""
|
|
}
|
|
}
|
|
|
|
# Helper to safely execute a closure and return null on error
|
|
def try-plugin [callback: closure]: nothing -> any {
|
|
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 []: nothing -> record {
|
|
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")
|
|
]: nothing -> record {
|
|
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
|
|
]: nothing -> bool {
|
|
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
|
|
]: nothing -> bool {
|
|
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
|
|
]: nothing -> bool {
|
|
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)
|
|
]: nothing -> bool {
|
|
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
|
|
]: nothing -> bool {
|
|
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 []: nothing -> bool {
|
|
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 []: nothing -> bool {
|
|
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 []: nothing -> bool {
|
|
(config-get "security.require_mfa_for_destructive" true)
|
|
}
|
|
|
|
# Check if user is authenticated
|
|
export def is-authenticated []: nothing -> bool {
|
|
let result = (plugin-verify)
|
|
($result | get valid? | default false)
|
|
}
|
|
|
|
# Check if MFA is verified
|
|
export def is-mfa-verified []: nothing -> bool {
|
|
let result = (plugin-verify)
|
|
($result | get mfa_verified? | default false)
|
|
}
|
|
|
|
# Get current authenticated user
|
|
export def get-authenticated-user []: nothing -> string {
|
|
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
|
|
]: nothing -> bool {
|
|
# 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
|
|
]: nothing -> bool {
|
|
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
|
|
]: nothing -> bool {
|
|
# 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
|
|
]: nothing -> bool {
|
|
# 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]: nothing -> bool {
|
|
(($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]: nothing -> bool {
|
|
$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
|
|
]: nothing -> bool {
|
|
# 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 []: nothing -> record {
|
|
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
|
|
]: nothing -> nothing {
|
|
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 []: nothing -> nothing {
|
|
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)"
|
|
}
|
|
# ============================================================================
|
|
# INTERACTIVE FORM HANDLERS (FormInquire Integration)
|
|
# ============================================================================
|
|
|
|
# Interactive login with form
|
|
export def login-interactive [] : nothing -> record {
|
|
print "🔐 Interactive Authentication"
|
|
print ""
|
|
|
|
# Run the login form
|
|
let form_result = (run-forminquire-form "provisioning/core/shlib/forms/authentication/auth_login.toml")
|
|
|
|
if not $form_result.success {
|
|
return {
|
|
success: false
|
|
error: $form_result.error
|
|
}
|
|
}
|
|
|
|
let form_values = $form_result.values
|
|
|
|
# Check if user cancelled or didn't confirm
|
|
if not ($form_values.confirm_login // false) {
|
|
return {
|
|
success: false
|
|
error: "Login cancelled by user"
|
|
}
|
|
}
|
|
|
|
# Perform login with provided credentials
|
|
let username = ($form_values.username // "")
|
|
let password = ($form_values.password // "")
|
|
let mfa_code = (if ($form_values.has_mfa // false) {
|
|
$form_values.mfa_code // ""
|
|
} 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: ($form_values.has_mfa // false)
|
|
}
|
|
}
|
|
|
|
# Interactive MFA enrollment with form
|
|
export def mfa-enroll-interactive [] : 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
|
|
let form_result = (run-forminquire-form "provisioning/core/shlib/forms/authentication/mfa_enroll.toml")
|
|
|
|
if not $form_result.success {
|
|
return {
|
|
success: false
|
|
error: $form_result.error
|
|
}
|
|
}
|
|
|
|
let form_values = $form_result.values
|
|
|
|
# Check if user confirmed
|
|
if not ($form_values.confirm_enroll // false) {
|
|
return {
|
|
success: false
|
|
error: "MFA enrollment cancelled by user"
|
|
}
|
|
}
|
|
|
|
# Determine MFA type and parameters
|
|
let mfa_type = if ($form_values.mfa_type | str contains "TOTP") {
|
|
"totp"
|
|
} else {
|
|
"webauthn"
|
|
}
|
|
|
|
# Call the plugin MFA enrollment function
|
|
let enroll_result = (plugin-mfa-enroll --type $mfa_type)
|
|
|
|
{
|
|
success: true
|
|
result: $enroll_result
|
|
mfa_type: $mfa_type
|
|
backup_codes_saved: ($form_values.totp_backups // false)
|
|
}
|
|
}
|