diff --git a/CONFIG.md b/CONFIG.md index ca6d92b..53c0706 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -131,6 +131,12 @@ via the terminal. - `username`: The username. (Optional) - `password`: The password. (Optional) +### The `tfa` authenticator + +This authenticator prompts the user on the console for a two-factor +authentication token. The token is provided as password and it is not cached. +This authenticator does not support usernames. + ## Transformation rules Transformation rules are rules for renaming and excluding files and directories. diff --git a/PFERD/authenticators/__init__.py b/PFERD/authenticators/__init__.py index d021d40..97ff03a 100644 --- a/PFERD/authenticators/__init__.py +++ b/PFERD/authenticators/__init__.py @@ -1,10 +1,11 @@ from configparser import SectionProxy from typing import Callable, Dict -from ..authenticator import Authenticator +from ..authenticator import Authenticator, AuthSection from ..conductor import TerminalConductor from ..config import Config from .simple import SimpleAuthenticator, SimpleAuthSection +from .tfa import TfaAuthenticator AuthConstructor = Callable[[ str, # Name (without the "auth:" prefix) @@ -16,4 +17,6 @@ AuthConstructor = Callable[[ AUTHENTICATORS: Dict[str, AuthConstructor] = { "simple": lambda n, s, c, t: SimpleAuthenticator(n, SimpleAuthSection(s), c, t), + "tfa": lambda n, s, c, t: + TfaAuthenticator(n, AuthSection(s), c, t), } diff --git a/PFERD/authenticators/tfa.py b/PFERD/authenticators/tfa.py new file mode 100644 index 0000000..3513d09 --- /dev/null +++ b/PFERD/authenticators/tfa.py @@ -0,0 +1,37 @@ +from typing import Tuple + +from ..authenticator import Authenticator, AuthException, AuthSection +from ..conductor import TerminalConductor +from ..config import Config +from ..utils import ainput + + +class TfaAuthenticator(Authenticator): + def __init__( + self, + name: str, + section: AuthSection, + config: Config, + conductor: TerminalConductor, + ) -> None: + super().__init__(name, section, config, conductor) + + async def username(self) -> str: + raise AuthException("TFA authenticator does not support usernames") + + async def password(self) -> str: + async with self.conductor.exclusive_output(): + code = await ainput("TFA code: ") + return code + + async def credentials(self) -> Tuple[str, str]: + raise AuthException("TFA authenticator does not support usernames") + + def invalidate_username(self) -> None: + raise AuthException("TFA authenticator does not support usernames") + + def invalidate_password(self) -> None: + pass + + def invalidate_credentials(self) -> None: + pass