Add Time-based Onetime Password Multi-factor Authentication Module#16129
Add Time-based Onetime Password Multi-factor Authentication Module#16129balloob merged 9 commits intohome-assistant:devfrom
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.
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 |
| @@ -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.
'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.
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.
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.
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.
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.
Change to a fixed dummy code
homeassistant/auth/__init__.py
Outdated
| async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]: | ||
| """List enabled mfa modules for user.""" | ||
| module_ids = [] | ||
| modules = {} |
There was a problem hiding this comment.
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.
This is already set in the parent class.
There was a problem hiding this comment.
For correct the stupid tying
There was a problem hiding this comment.
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.
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.
Shouldn't self._auth_module.async_validation be used here?
There was a problem hiding this comment.
No, this is validate against a temp secret has not been saved yet.
| import pyqrcode | ||
|
|
||
| qr_code = pyqrcode.create(data) | ||
| return str(qr_code.png_as_base64_str(scale=4)) |
There was a problem hiding this comment.
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.
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.
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.
Markdown image syntax is . 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.
'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
…ome-assistant#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: