User Authentication System
This skill provides a complete authentication and authorization system for Greek accounting firm operations through OpenClaw. It manages user identities, role-based permissions, per-client access controls, and session security for multi-user accounting environments.
Setup
export OPENCLAW_DATA_DIR="/data"
which jq openssl || sudo apt install jq openssl
mkdir -p $OPENCLAW_DATA_DIR/auth
chmod 700 $OPENCLAW_DATA_DIR/auth
No external auth services. User credentials are stored as salted SHA-256 hashes locally. 2FA uses SHA-256 TOTP generated by openssl.
Core Philosophy
- Role-Based Access: Hierarchical permissions matching real accounting firm structures
- Per-Client Authorization: Granular control over which users access which client data
- Session Security: Secure session management with timeout and device tracking
- Audit Integration: Every authentication and authorization event logged
- OpenClaw Artifact Ready: File-based auth suitable for OpenClaw deployment
OpenClaw Commands
User Management
openclaw auth user-create --username "maria.g" --role assistant --full-name "Maria Georgiou" --email "maria@firm.gr"
openclaw auth user-update --username "maria.g" --role accountant --effective-date 2026-03-01
openclaw auth user-deactivate --username "maria.g" --reason "resignation" --revoke-sessions
openclaw auth user-list --active --role assistant --format table
openclaw auth password-reset --username "maria.g" --send-reset-link
openclaw auth password-policy --min-length 12 --require-special --max-age-days 90
Role & Permission Management
openclaw auth role-list --include-permissions
openclaw auth role-create --name "tax_specialist" --base-role accountant --add-permissions "tax_filing,tax_optimization"
openclaw auth assign-clients --username "maria.g" --clients EL123456789,EL987654321
openclaw auth assign-clients --username "maria.g" --all-clients
openclaw auth check-access --username "maria.g" --client EL123456789 --action "view_financials"
openclaw auth access-matrix --all-users --all-clients --format xlsx
Security & Audit
openclaw auth security-log --last-24h --include-failures
openclaw auth failed-logins --threshold 3 --lockout-duration 30m
openclaw auth audit-report --user "maria.g" --period last-30-days
openclaw auth audit-report --client EL123456789 --who-accessed --period last-week
openclaw auth 2fa-enable --username "maria.g" --method totp
openclaw auth sessions-list --active --format table
openclaw auth session-revoke --username "maria.g" --all-devices
File System Architecture
Auth_File_Structure:
user_data:
- /data/auth/users/{username}/profile.json
- /data/auth/users/{username}/credentials.json
- /data/auth/users/{username}/permissions.json
- /data/auth/users/{username}/sessions/
- /data/auth/users/{username}/2fa/
role_definitions:
- /data/auth/roles/senior_accountant.json
- /data/auth/roles/accountant.json
- /data/auth/roles/assistant.json
- /data/auth/roles/viewer.json
- /data/auth/roles/custom/
access_control:
- /data/auth/access/client_assignments.json
- /data/auth/access/policies.json
- /data/auth/access/ip_whitelist.json
security_logs:
- /data/auth/logs/logins/
- /data/auth/logs/access/
- /data/auth/logs/admin/
- /data/auth/logs/security/
Role Hierarchy & Permissions
Role Definitions
Roles:
senior_accountant:
description: "Senior accountant - full system access"
level: 4
inherits: "accountant"
permissions:
- all_client_access
- user_management
- role_assignment
- system_configuration
- data_export_all
- compliance_override
- audit_log_access
- gdpr_operations
- billing_management
- skill_configuration
client_access: "all"
accountant:
description: "Accountant - broad access to assigned clients"
level: 3
inherits: "assistant"
permissions:
- client_data_full_access
- tax_filing_submit
- tax_optimization
- compliance_management
- financial_reporting
- efka_submissions
- banking_reconciliation
- deadline_management
- client_communication
client_access: "assigned_only"
restrictions:
- cannot_manage_users
- cannot_change_system_config
assistant:
description: "Accountant assistant - operational access"
level: 2
inherits: "viewer"
permissions:
- document_upload
- document_processing
- data_entry
- email_processing
- dashboard_access
- basic_reporting
- client_data_edit_basic
- alert_acknowledgement
- ocr_processing
client_access: "assigned_only"
restrictions:
- cannot_submit_tax_filings
- cannot_export_sensitive_data
- cannot_modify_financial_records
viewer:
description: "Read-only access to assigned client data"
level: 1
permissions:
- dashboard_view
- client_data_view
- report_view
- deadline_view
- document_view
client_access: "assigned_only"
restrictions:
- read_only
- no_data_modification
- no_data_export
Permission Matrix
Permission_Matrix:
view_dashboard: "viewer"
configure_dashboard: "accountant"
view_client_profile: "viewer"
edit_client_profile: "assistant"
create_client: "accountant"
delete_client: "senior_accountant"
export_client_data: "accountant"
gdpr_operations: "senior_accountant"
view_documents: "viewer"
upload_documents: "assistant"
process_documents: "assistant"
delete_documents: "accountant"
view_financials: "viewer"
enter_financial_data: "assistant"
modify_financial_records: "accountant"
submit_tax_filings: "accountant"
view_compliance_status: "viewer"
manage_compliance: "accountant"
override_compliance: "senior_accountant"
view_employee_data: "viewer"
manage_employees: "accountant"
submit_efka: "accountant"
view_transactions: "viewer"
reconcile_transactions: "assistant"
configure_banking: "accountant"
manage_users: "senior_accountant"
manage_roles: "senior_accountant"
view_audit_logs: "senior_accountant"
system_configuration: "senior_accountant"
Authentication Engine
Core Authentication
class AuthenticationEngine:
"""Handles user authentication, sessions, and credential management."""
def __init__(self):
self.session_timeout = 30 * 60 # 30 minutes
self.idle_timeout = 15 * 60 # 15 minutes
self.max_failed_attempts = 5
self.lockout_duration = 30 * 60 # 30 minutes
def authenticate(self, username, password, device_info=None):
"""Authenticate user and create session."""
if self.is_account_locked(username):
self.log_auth_event(username, 'login_blocked', 'account_locked')
return {'success': False, 'error': 'Account is locked. Contact administrator.'}
user = self.load_user(username)
if not user:
self.log_auth_event(username, 'login_failed', 'user_not_found')
return {'success': False, 'error': 'Invalid credentials'}
if not self.verify_password(password, user['password_hash']):
self.record_failed_attempt(username)
self.log_auth_event(username, 'login_failed', 'wrong_password')
return {'success': False, 'error': 'Invalid credentials'}
if user['status'] != 'active':
self.log_auth_event(username, 'login_failed', f'account_{user["status"]}')
return {'success': False, 'error': 'Account is not active'}
if user.get('2fa_enabled', False):
return {'success': False, 'requires_2fa': True,
'session_pending': self.create_pending_session(username)}
session = self.create_session(username, device_info)
self.clear_failed_attempts(username)
self.log_auth_event(username, 'login_success', device_info=device_info)
return {'success': True, 'session': session, 'user': self.get_user_summary(username)}
def create_session(self, username, device_info=None):
"""Create a new authenticated session.
Security: The raw session token is returned to the user exactly once.
Only the salted SHA-256 hash is stored on disk. Validation compares
the hash of the incoming token against the stored hash.
"""
raw_token = generate_secure_token(64)
token_hash = hash_session_token(raw_token) # SHA-256 salted hash
session = {
'session_id': token_hash,
'username': username,
'created_at': current_timestamp(),
'expires_at': current_timestamp() + self.session_timeout,
'last_activity': current_timestamp(),
'device_info': device_info,
'ip_address': get_request_ip(),
'role': self.get_user_role(username),
'client_access': self.get_user_client_access(username)
}
session_path = f"/data/auth/users/{username}/sessions/{token_hash}.json"
write_json(session_path, session)
# Return raw token to user — this is the only time it exists in cleartext
session['bearer_token'] = raw_token
return session
def validate_session(self, session_id):
"""Validate an existing session."""
session = self.find_session(session_id)
if not session:
return {'valid': False, 'reason': 'session_not_found'}
if current_timestamp() > session['expires_at']:
self.destroy_session(session_id)
return {'valid': False, 'reason': 'session_expired'}
if current_timestamp() - session['last_activity'] > self.idle_timeout:
self.destroy_session(session_id)
return {'valid': False, 'reason': 'idle_timeout'}
session['last_activity'] = current_timestamp()
self.update_session(session)
return {'valid': True, 'session': session}
def hash_password(self, password):
"""Hash password using bcrypt with salt."""
return bcrypt_hash(password, rounds=12)
def validate_password_strength(self, password):
"""Check password meets policy requirements."""
errors = []
if len(password) < 12:
errors.append("Password must be at least 12 characters")
if not any(c.isupper() for c in password):
errors.append("Must contain uppercase letter")
if not any(c.islower() for c in password):
errors.append("Must contain lowercase letter")
if not any(c.isdigit() for c in password):
errors.append("Must contain digit")
if not any(c in '!@#$%^&*()_+-=' for c in password):
errors.append("Must contain special character")
return {'valid': len(errors) == 0, 'errors': errors}
Authorization Engine
class AuthorizationEngine:
"""Handles permission checks and access control decisions."""
def __init__(self):
self.roles = self.load_roles()
self.access_matrix = self.load_access_matrix()
def check_permission(self, session, action, client_vat=None):
"""Check if user has permission to perform an action."""
username = session['username']
user_role = session['role']
required_role = self.access_matrix.get(action)
if not required_role:
self.log_authorization(username, action, client_vat, 'denied', 'unknown_action')
return {'allowed': False, 'reason': f'Unknown action: {action}'}
user_level = self.roles[user_role]['level']
required_level = self.roles[required_role]['level']
if user_level < required_level:
self.log_authorization(username, action, client_vat, 'denied', 'insufficient_role')
return {'allowed': False,
'reason': f'Requires {required_role} role (you have {user_role})'}
# Check client-specific access
if client_vat:
client_access = session.get('client_access', [])
if 'all' not in client_access and client_vat not in client_access:
self.log_authorization(username, action, client_vat, 'denied', 'no_client_access')
return {'allowed': False,
'reason': f'Not authorized for client {AFM}'}
self.log_authorization(username, action, client_vat, 'allowed')
return {'allowed': True}
def resolve_permissions(self, role_name):
"""Resolve all permissions including inherited ones."""
role = self.roles.get(role_name, {})
permissions = set(role.get('permissions', []))
parent = role.get('inherits')
if parent:
permissions.update(self.resolve_permissions(parent))
return permissions
def get_accessible_clients(self, username):
"""Get list of clients this user can access."""
assignments = read_json("/data/auth/access/client_assignments.json")
user_entry = assignments.get(username, {})
if user_entry.get('all_clients', False):
return {'type': 'all', 'clients': 'all'}
return {'type': 'specific', 'clients': user_entry.get('clients', [])}
User Management
class UserManager:
"""Manages user lifecycle operations."""
def __init__(self):
self.auth_engine = AuthenticationEngine()
self.authz_engine = AuthorizationEngine()
def create_user(self, admin_session, user_data):
"""Create a new user account."""
# Verify admin has permission
perm_check = self.authz_engine.check_permission(admin_session, 'manage_users')
if not perm_check['allowed']:
return {'success': False, 'error': perm_check['reason']}
username = user_data['username']
# Check for duplicate
if self.user_exists(username):
return {'success': False, 'error': f'Username {username} already exists'}
# Validate role
if user_data['role'] not in self.authz_engine.roles:
return {'success': False, 'error': f'Invalid role: {user_data["role"]}'}
# Create user profile
profile = {
'username': username,
'full_name': user_data['full_name'],
'email': user_data.get('email'),
'role': user_data['role'],
'status': 'active',
'created_at': current_timestamp(),
'created_by': admin_session['username'],
'password_change_required': True,
'2fa_enabled': False
}
# Generate temporary password
temp_password = generate_secure_password()
credentials = {
'password_hash': self.auth_engine.hash_password(temp_password),
'password_set_at': current_timestamp(),
'password_change_required': True
}
# Create user directory and files
user_dir = f"/data/auth/users/{username}"
create_directory(user_dir)
create_directory(f"{user_dir}/sessions")
create_directory(f"{user_dir}/2fa")
write_json(f"{user_dir}/profile.json", profile)
write_json(f"{user_dir}/credentials.json", credentials)
write_json(f"{user_dir}/permissions.json", {'role': user_data['role'], 'custom': []})
# Audit log
self.log_admin_action(admin_session['username'], 'user_created', {
'new_user': username, 'role': user_data['role']
})
return {
'success': True,
'username': username,
'temporary_password': temp_password,
'message': f'User {username} created with role {user_data["role"]}. '
f'Password change required on first login.'
}
def assign_clients(self, admin_session, username, client_vats):
"""Assign client access to a user."""
perm_check = self.authz_engine.check_permission(admin_session, 'manage_users')
if not perm_check['allowed']:
return {'success': False, 'error': perm_check['reason']}
assignments_path = "/data/auth/access/client_assignments.json"
assignments = read_json(assignments_path) if file_exists(assignments_path) else {}
if client_vats == 'all':
assignments[username] = {'all_clients': True, 'clients': []}
else:
current = assignments.get(username, {'all_clients': False, 'clients': []})
current['clients'] = list(set(current.get('clients', []) + client_vats))
assignments[username] = current
write_json(assignments_path, assignments)
self.log_admin_action(admin_session['username'], 'clients_assigned', {
'user': username, 'clients': client_vats
})
return {'success': True, 'username': username, 'client_access': assignments[username]}
Security Features
File System Permissions (Production Hardening)
The /data/auth/ directory contains sensitive credential and session data. In production, OS-level file permissions must be hardened:
# Restrict the entire auth directory
chmod 700 /data/auth/
chown -R openclaw:openclaw /data/auth/
# Credential files must be read-only to the service user
chmod 600 /data/auth/users/*/credentials.json
# Session files
chmod 600 /data/auth/users/*/sessions/*.json
# Role definitions (read by all skills for auth checks, writable only by admin)
chmod 644 /data/auth/roles/*.json
# Audit logs (append-only in production if OS supports it)
chmod 600 /data/auth/logs/**/*.json
Note: The OpenClaw agent and all skills share the same file system context. Without OS-level permission restrictions, any skill could read credential hashes. These permissions ensure that only the authentication skill's process can access sensitive auth data.
Account Lockout
Lockout_Policy:
max_failed_attempts: 5
lockout_duration: "30 minutes"
lockout_escalation:
- "5 failures: 30 minute lockout"
- "10 failures: 2 hour lockout"
- "15 failures: account disabled, admin notification"
notification: "email admin on 3+ consecutive failures"
Two-Factor Authentication
2FA_Configuration:
methods: ["totp"]
mandatory_for: ["senior_accountant"]
optional_for: ["accountant", "assistant"]
recovery_codes: 10
totp_settings:
algorithm: "SHA256"
digits: 6
period: 30
Password Policy
Password_Policy:
min_length: 12
require_uppercase: true
require_lowercase: true
require_digit: true
require_special: true
max_age_days: 90
history_count: 5
common_password_check: true
IP & Device Security
Security_Controls:
ip_whitelist:
enabled: true
allowed_ranges: ["office_ip_range"]
action_on_violation: "block_and_alert"
device_tracking:
track_devices: true
alert_new_device: true
max_concurrent_sessions: 3
session_controls:
absolute_timeout: "8 hours"
idle_timeout: "15 minutes"
single_session_per_device: true
Integration with Other Skills
Skill_Integration:
dashboard_greek_accounting:
provides: ["user_session", "role_permissions", "accessible_clients"]
enforces: "dashboard view permissions per user role"
client_data_management:
provides: ["access_decisions", "user_identity"]
enforces: "per-client data access based on user assignments"
integration: "authorization check before every data operation"
greek_compliance_aade:
enforces: "only accountant+ can submit filings"
efka_api_integration:
enforces: "only accountant+ can submit EFKA data"
all_skills:
provides: "user context for audit trail entries"
enforces: "role-based action restrictions across all operations"
Audit & Compliance Reporting
Audit Log Structure
Audit_Events:
authentication:
- login_success
- login_failed
- login_blocked
- logout
- session_expired
- session_revoked
- 2fa_success
- 2fa_failed
- password_changed
- password_reset
authorization:
- access_granted
- access_denied
- client_access_checked
- permission_checked
administration:
- user_created
- user_updated
- user_deactivated
- role_changed
- clients_assigned
- clients_revoked
- policy_changed
Audit_Entry_Format:
timestamp: "ISO 8601 with timezone"
event_type: "category.action"
username: "acting user"
target: "affected resource"
client_vat: "if client-specific"
result: "success/failure"
details: "additional context"
ip_address: "source IP"
session_id: "active session"
Success Metrics
A successful authentication system deployment should achieve:
- ✅ Secure Authentication: bcrypt password hashing, optional 2FA, session management
- ✅ Role Hierarchy: Four-level role system matching accounting firm structures
- ✅ Per-Client Access: Granular client data access assignment per user
- ✅ Session Security: Timeout, idle detection, concurrent session limits
- ✅ Complete Audit Trail: Every auth/authz event logged with context
- ✅ Account Protection: Lockout policy, password requirements, brute-force prevention
- ✅ Cross-Skill Enforcement: Authorization integrated into all data operations
- ✅ Admin Tools: User management, access matrix, security reporting
- ✅ GDPR Compatible: Access controls support data protection requirements
- ✅ Scalable: Handle 50+ users across 500+ clients
Remember: The authentication system is the security foundation for the entire Greek accounting platform. Every data access must pass through authorization checks, and every action must leave an audit trail.