-
-
Notifications
You must be signed in to change notification settings - Fork 31.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Time-based Onetime Password Multi-factor Authentication Module #16129
Conversation
tests/auth/mfa_modules/test_totp.py
Outdated
|
||
provider = hass.auth.auth_providers[0] | ||
|
||
result = await hass.auth.login_flow.async_init((provider.type, provider.id)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (80 > 79 characters)
tests/auth/mfa_modules/test_totp.py
Outdated
"""Test the Time-based One Time Password (MFA) auth module.""" | ||
from unittest.mock import patch | ||
|
||
import pytest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'pytest' imported but unused
@@ -0,0 +1,142 @@ | |||
"""Time-based One Time Password auth module.""" | |||
import logging | |||
from typing import Any, Dict, List, Optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'typing.List' imported but unused
'typing.Optional' imported but unused
user[STORAGE_OTA_SECRET] = ota_secret | ||
return ota_secret | ||
|
||
self._users.append({ # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should users be a list of dicts? It looks like we're assuming that user ids are unique so then a dict (of dicts) makes more sense to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied code from auth provider, should change to dict
|
||
def _validate_2fa(self, user_id: str, code: str) -> bool: | ||
"""Validate two factor authentication code.""" | ||
if user_id is None or code is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When can this happen? Code input is validated by the input schema.
if ota_secret is None: | ||
# even we cannot find user, we still do verify | ||
# to make timing the same as if user was found. | ||
pyotp.TOTP(pyotp.random_base32()).verify(code) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the time for generating the ota_secret via random_base32
insignificant compared to the rest of the validation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to a fixed dummy code
homeassistant/auth/__init__.py
Outdated
"""List enabled mfa modules for user.""" | ||
module_ids = [] | ||
modules = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OrderedDict
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see anything related to setting up TOTP for a user in this PR besides a call to setup. Where is the schema?
See my PR on how this should look: https://github.com/home-assistant/home-assistant/pull/16141/files#r212261624
PR order problem, the setup flow part need wait #16141 merged first. |
Rebase on #16141, add setup flow, add QR code generation Need first merge #16141, then merge home-assistant/frontend#1590, then merge this PR, finally need add/fix translate to display QR code |
user: User) -> None: | ||
"""Initialize the setup flow.""" | ||
super().__init__(auth_module, user.id) | ||
self._auth_module = auth_module # type: TotpAuthModule |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already set in the parent class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For correct the stupid tying
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it looks like we don't need the parent class. Do we really think the default init step will not be overridden in other mfa flows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the inheritance is not needed as we're overriding everything.
|
||
if user_input: | ||
verified = await self.hass.async_add_executor_job( # type: ignore | ||
pyotp.TOTP(self._ota_secret).verify, user_input['code']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't self._auth_module.async_validation
be used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this is validate against a temp secret has not been saved yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
import pyqrcode | ||
|
||
qr_code = pyqrcode.create(data) | ||
return str(qr_code.png_as_base64_str(scale=4)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the svg method and we don't need to install pypng as dependency.
return str(qr_code.png_as_base64_str(scale=4)) | ||
|
||
|
||
@bind_hass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to add @bind_hass
for internal methods.
image = _generate_qr_code(url) | ||
return ota_secret, url, image | ||
|
||
return await hass.async_add_executor_job( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the whole method is about running a sync method, just make the method sync on the global scope and call with async_add_executor_job
directly.
"step": { | ||
"init": { | ||
"title": "Scan this QR code with your app", | ||
"description": "Scan the QR code with your authentication app, such as **Google Authenticator** or **Authy**. \nOr enter {code} instead. \n\n[QR Code]{url}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Markdown image syntax is ![alt text](url)
. I don't see that in the description?
Add TOTP setup flow, generate QR code
The only thing left is translation string. I am not familiar with the process, @balloob can you take care of it? |
I will but won't have time today. Will do it tomorrow. Here is an example of using placeholders btw |
Finally get this work. There is a change in front end need to be done. We are using So, I have to disable filterXSS to make my QR code displayed. I think the properly way to handle it (see front end PR: home-assistant/frontend#1599), maybe we should make My quick fix in front end to get following screenshot. diff --git a/src/components/ha-markdown.js b/src/components/ha-markdown.js
index 355507ec..e13f1b4b 100644
--- a/src/components/ha-markdown.js
+++ b/src/components/ha-markdown.js
@@ -46,11 +46,11 @@ class HaMarkdown extends EventsMixin(PolymerElement) {
this._renderScheduled = false;
if (this._scriptLoaded === 1) {
- this.innerHTML = this.filterXSS(this.marked(this.content, {
+ this.innerHTML = this.marked(this.content, {
gfm: true,
tables: true,
breaks: true
- }));
+ });
this._resize();
const walker = document.createTreeWalker(this, 1 /* SHOW_ELEMENT */, null, false); |
@@ -0,0 +1,213 @@ | |||
"""Time-based One Time Password auth module.""" | |||
import logging | |||
from base64 import b64encode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'base64.b64encode' imported but unused
…16129) * Add Time-based Onetime Password Multi-factor Auth Add TOTP setup flow, generate QR code * Resolve rebase issue * Use svg instead png for QR code * Lint and typing * Fix translation * Load totp auth module by default * use <svg> tag instead markdown image * Update strings * Cleanup
Description:
totp is Time-based One Time Password module which compatible with Google Authenticator and Authy.
See #15489 learn more about multi-factor authentication module
Edit: a totp MFA auth module named "Authenticator app" will be auto loaded if no auth_mfa_modules config section defined in configuration.yaml
Related issue (if applicable): fixes #
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#6089
Example entry for
configuration.yaml
(if applicable):Checklist:
tox
. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code does not interact with devices: