diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index 042c466abc..ded934bff9 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -16,6 +16,7 @@
"neighbor.py",
"nhrp.py",
"openconnect.py",
+"otp.py",
"openvpn.py",
"reset_vpn.py",
"reverseproxy.py",
diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in
index 85bfdcdbae..116c7460f8 100644
--- a/op-mode-definitions/show-system.xml.in
+++ b/op-mode-definitions/show-system.xml.in
@@ -102,6 +102,55 @@
Show user accounts
+
+
+ Show user account authentication information
+
+
+
+
+ Show configured user
+
+ system login user
+
+
+
+
+
+ Show OTP key information
+
+ ${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"
+
+
+
+ Show full settings, including QR code and commands for VyOS
+
+ ${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"
+
+
+
+ Show OTP authentication secret in Base32 (used in mobile apps)
+
+ ${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="key-b32"
+
+
+
+ Show OTP authentication QR code
+
+ ${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="qrcode"
+
+
+
+ Show OTP authentication otpauth URI
+
+ ${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="uri"
+
+
+
+
+
+
+
Show user account information
diff --git a/src/op_mode/otp.py b/src/op_mode/otp.py
new file mode 100755
index 0000000000..c472a1ed3d
--- /dev/null
+++ b/src/op_mode/otp.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+# Copyright 2017, 2022 VyOS maintainers and contributors
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see .
+
+
+import sys
+import os
+import vyos.opmode
+from jinja2 import Template
+from vyos.configquery import ConfigTreeQuery
+from vyos.xml import defaults
+from vyos.configdict import dict_merge
+from vyos.util import popen
+
+
+users_otp_template = Template("""
+{% if info == "full" %}
+# You can share it with the user, he just needs to scan the QR in his OTP app
+# username: {{username}}
+# OTP KEY: {{key_base32}}
+# OTP URL: {{otp_url}}
+{{qrcode}}
+# To add this OTP key to configuration, run the following commands:
+set system login user {{username}} authentication otp key '{{key_base32}}'
+{% if rate_limit != "3" %}
+set system login user {{username}} authentication otp rate-limit '{{rate_limit}}'
+{% endif %}
+{% if rate_time != "30" %}
+set system login user {{username}} authentication otp rate-time '{{rate_time}}'
+{% endif %}
+{% if window_size != "3" %}
+set system login user {{username}} authentication otp window-size '{{window_size}}'
+{% endif %}
+{% elif info == "key-b32" %}
+# OTP key in Base32 for system user {{username}}:
+{{key_base32}}
+{% elif info == "qrcode" %}
+# QR code for system user '{{username}}'
+{{qrcode}}
+{% elif info == "uri" %}
+# URI for system user '{{username}}'
+{{otp_url}}
+{% endif %}
+""", trim_blocks=True, lstrip_blocks=True)
+
+
+def _check_uname_otp(username:str):
+ """
+ Check if "username" exists and have an OTP key
+ """
+ config = ConfigTreeQuery()
+ base_key = ['system', 'login', 'user', username, 'authentication', 'otp', 'key']
+ if not config.exists(base_key):
+ return None
+ return True
+
+def _get_login_otp(username: str, info:str):
+ """
+ Retrieve user settings from configuration and set some defaults
+ """
+ config = ConfigTreeQuery()
+ base = ['system', 'login', 'user', username]
+ if not config.exists(base):
+ return None
+ user_otp = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(['system', 'login', 'user'])
+ user_otp = dict_merge(default_values, user_otp)
+ result = user_otp['authentication']['otp']
+ # Filling in the system and default options
+ result['info'] = info
+ result['hostname'] = os.uname()[1]
+ result['username'] = username
+ result['key_base32'] = result['key']
+ result['otp_length'] = '6'
+ result['interval'] = '30'
+ result['token_type'] = 'hotp-time'
+ if result['token_type'] == 'hotp-time':
+ token_type_acrn = 'totp'
+ result['otp_url'] = ''.join(["otpauth://",token_type_acrn,"/",username,"@",\
+ result['hostname'],"?secret=",result['key_base32'],"&digits=",\
+ result['otp_length'],"&period=",result['interval']])
+ result['qrcode'],err = popen('qrencode -t ansiutf8', input=result['otp_url'])
+ return result
+
+def show_login(raw: bool, username: str, info:str):
+ '''
+ Display OTP parameters for
+ '''
+ check_otp = _check_uname_otp(username)
+ if check_otp:
+ user_otp_params = _get_login_otp(username, info)
+ else:
+ print(f'There is no such user ("{username}") with an OTP key configured')
+ print('You can use the following command to generate a key for a user:\n')
+ print(f'generate system login username {username} otp-key hotp-time')
+ sys.exit(0)
+ if raw:
+ return user_otp_params
+ return users_otp_template.render(user_otp_params)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)