Skip to content

Commit

Permalink
Fix it
Browse files Browse the repository at this point in the history
  • Loading branch information
horia141 committed Feb 27, 2024
1 parent d5f11c9 commit adcd4c1
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 98 deletions.
56 changes: 30 additions & 26 deletions src/core/jupiter/core/domain/auth/password_new_plain.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""A new password in plain text, as received from a user."""
import re
from re import Pattern
from typing import Final, Optional
from typing import Final

from jupiter.core.framework.errors import InputValidationError
from jupiter.core.framework.realm import (
DatabaseRealm,
CliRealm,
RealmDecoder,
RealmEncoder,
RealmThing,
WebRealm,
only_in_realm,
)
from jupiter.core.framework.value import SecretValue, secret_value

Expand All @@ -17,51 +18,54 @@


@secret_value
@only_in_realm(CliRealm, WebRealm)
class PasswordNewPlain(SecretValue):
"""A new password in plain text, as received from a user."""

password_raw: str

@staticmethod
def from_raw(password_str: Optional[str]) -> "PasswordNewPlain":
"""Validate and clean a raw password."""
if not password_str:
raise InputValidationError("Expected password to be non null")

password_str = PasswordNewPlain._clean_password(password_str)
class PasswordNewPlainCliDecoder(RealmDecoder[PasswordNewPlain, CliRealm]):
"""Decode a password newplain from storage in the CLI."""

return PasswordNewPlain(password_str)
def decode(self, value: RealmThing) -> PasswordNewPlain:
"""Decode a password newplain from storage in the database."""
if not isinstance(value, str):
raise InputValidationError(
f"Expected password newplain to be a string, got {value}"
)

@staticmethod
def _clean_password(password_str_raw: str) -> str:
if not _PASSWORD_PLAIN_RE.match(password_str_raw):
if not _PASSWORD_PLAIN_RE.match(value):
raise InputValidationError(
"Expected password to not contain any white-space"
)

if len(password_str_raw) < _PASSWORD_MIN_LENGTH:
if len(value) < _PASSWORD_MIN_LENGTH:
raise InputValidationError(
f"Expected password to be longer than {_PASSWORD_MIN_LENGTH} characters"
)

return password_str_raw


class PasswordNewPlainDatabaseEncoder(RealmEncoder[PasswordNewPlain, DatabaseRealm]):
"""Encode a password newplain for storage in the database."""

def encode(self, value: PasswordNewPlain) -> RealmThing:
"""Encode a password newplain for storage in the database."""
return value.password_raw
return PasswordNewPlain(value)


class PasswordNewPlainDatabaseDecoder(RealmDecoder[PasswordNewPlain, DatabaseRealm]):
"""Decode a password newplain from storage in the database."""
class PasswordNewPlainWebDecoder(RealmDecoder[PasswordNewPlain, WebRealm]):
"""Decode a password newplain from storage in the Web."""

def decode(self, value: RealmThing) -> PasswordNewPlain:
"""Decode a password newplain from storage in the database."""
if not isinstance(value, str):
raise InputValidationError(
f"Expected password newplain to be a string, got {value}"
)
return PasswordNewPlain.from_raw(value)

if not _PASSWORD_PLAIN_RE.match(value):
raise InputValidationError(
"Expected password to not contain any white-space"
)

if len(value) < _PASSWORD_MIN_LENGTH:
raise InputValidationError(
f"Expected password to be longer than {_PASSWORD_MIN_LENGTH} characters"
)

return PasswordNewPlain(value)
69 changes: 42 additions & 27 deletions src/core/jupiter/core/domain/auth/password_plain.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,70 @@
"""A password in plain text, as received from a user."""
from typing import Optional
import re
from re import Pattern

from jupiter.core.framework.errors import InputValidationError
from jupiter.core.framework.realm import (
DatabaseRealm,
CliRealm,
RealmDecoder,
RealmEncoder,
RealmThing,
WebRealm,
only_in_realm,
)
from jupiter.core.framework.value import SecretValue, secret_value

_PASSWORD_PLAIN_RE: Pattern[str] = re.compile(r"^\S+$")
_PASSWORD_MIN_LENGTH: int = 10


@secret_value
@only_in_realm(CliRealm, WebRealm)
class PasswordPlain(SecretValue):
"""A new password in plain text, as received from a user."""

password_raw: str

@staticmethod
def from_raw(password_str: Optional[str]) -> "PasswordPlain":
"""Validate and clean a raw password."""
if not password_str:
raise InputValidationError("Expected password to be non null")

password_str = PasswordPlain._clean_password(password_str)

return PasswordPlain(password_str)

@staticmethod
def _clean_password(password_str_raw: str) -> str:
if len(password_str_raw) == 0:
raise InputValidationError("Expected password to be non-empty")
class PasswordPlainCliDecoder(RealmDecoder[PasswordPlain, CliRealm]):
"""Decode a password newplain from storage in the CLI."""

return password_str_raw
def decode(self, value: RealmThing) -> PasswordPlain:
"""Decode a password plain from storage in the database."""
if not isinstance(value, str):
raise InputValidationError(
f"Expected password newplain to be a string, got {value}"
)

if not _PASSWORD_PLAIN_RE.match(value):
raise InputValidationError(
"Expected password to not contain any white-space"
)

class PasswordPlainDatabaseEncoder(RealmEncoder[PasswordPlain, DatabaseRealm]):
"""Encode a password plain for storage in the database."""
if len(value) < _PASSWORD_MIN_LENGTH:
raise InputValidationError(
f"Expected password to be longer than {_PASSWORD_MIN_LENGTH} characters"
)

def encode(self, value: PasswordPlain) -> RealmThing:
"""Encode a password plain for storage in the database."""
return value.password_raw
return PasswordPlain(value)


class PasswordPlainDatabaseDecoder(RealmDecoder[PasswordPlain, DatabaseRealm]):
"""Decode a password plain from storage in the database."""
class PasswordPlainWebDecoder(RealmDecoder[PasswordPlain, WebRealm]):
"""Decode a password newplain from storage in the Web."""

def decode(self, value: RealmThing) -> PasswordPlain:
"""Decode a password plain from storage in the database."""
"""Decode a password newplain from storage in the database."""
if not isinstance(value, str):
raise InputValidationError(
f"Expected password plain to be a string, got {value}"
f"Expected password newplain to be a string, got {value}"
)

if not _PASSWORD_PLAIN_RE.match(value):
raise InputValidationError(
"Expected password to not contain any white-space"
)
return PasswordPlain.from_raw(value)

if len(value) < _PASSWORD_MIN_LENGTH:
raise InputValidationError(
f"Expected password to be longer than {_PASSWORD_MIN_LENGTH} characters"
)

return PasswordPlain(value)
38 changes: 14 additions & 24 deletions src/core/jupiter/core/domain/auth/recovery_token_hash.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""A hashed recovery token, suitable for storage."""
from typing import Optional

import argon2.profiles
from argon2 import PasswordHasher
Expand All @@ -25,28 +24,6 @@ class RecoveryTokenHash(SecretValue):

token_hash_raw: str

@staticmethod
def from_raw(recovery_token_hash_str: Optional[str]) -> "RecoveryTokenHash":
"""Validate and clean a raw hashed recovery token."""
if not recovery_token_hash_str:
raise InputValidationError("Expected hashed recovery token to be non-null")

try:
recovery_token_hash_params = argon2.extract_parameters(
recovery_token_hash_str
)
except argon2.exceptions.InvalidHash as err:
raise InputValidationError(
"Hashed recovery token does not match expected format"
) from err

if recovery_token_hash_params != _PROFILE:
raise InputValidationError(
"Hashed recovery token parameters do not match standard profile"
)

return RecoveryTokenHash(recovery_token_hash_str)

@staticmethod
def from_plain(plain: RecoveryTokenPlain) -> "RecoveryTokenHash":
"""Build a hashed recovery token from a plain recovery token."""
Expand Down Expand Up @@ -78,4 +55,17 @@ def decode(self, value: RealmThing) -> RecoveryTokenHash:
raise InputValidationError(
f"Expected password hash to be a string, got {value}"
)
return RecoveryTokenHash.from_raw(value)

try:
recovery_token_hash_params = argon2.extract_parameters(value)
except argon2.exceptions.InvalidHash as err:
raise InputValidationError(
"Hashed recovery token does not match expected format"
) from err

if recovery_token_hash_params != _PROFILE:
raise InputValidationError(
"Hashed recovery token parameters do not match standard profile"
)

return RecoveryTokenHash(value)
27 changes: 6 additions & 21 deletions src/core/jupiter/core/domain/auth/recovery_token_plain.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""A recovery token for auth systems."""

import uuid
from typing import Optional

from jupiter.core.framework.errors import InputValidationError
from jupiter.core.framework.realm import (
Expand All @@ -25,24 +23,6 @@ def new_recovery_token() -> "RecoveryTokenPlain":
token = str(uuid.uuid4())
return RecoveryTokenPlain(token)

@staticmethod
def from_raw(recovery_token_raw: Optional[str]) -> "RecoveryTokenPlain":
"""Validate and clean a raw recovery token."""
if recovery_token_raw is None:
raise InputValidationError("Expected recovery token to non-null")

token = RecoveryTokenPlain._clean_token(recovery_token_raw)

return RecoveryTokenPlain(token)

@staticmethod
def _clean_token(token_str: str) -> str:
try:
uuid.UUID(token_str, version=4)
except (ValueError, TypeError) as err:
raise InputValidationError("Recovery token has a bad format") from err
return token_str


class RecoveryTokenPlainDatabaseEncoder(
RealmEncoder[RecoveryTokenPlain, DatabaseRealm]
Expand All @@ -65,4 +45,9 @@ def decode(self, value: RealmThing) -> RecoveryTokenPlain:
raise InputValidationError(
f"Expected password hash to be a string, got {value}"
)
return RecoveryTokenPlain.from_raw(value)

try:
uuid.UUID(value, version=4)
except (ValueError, TypeError) as err:
raise InputValidationError("Recovery token has a bad format") from err
return RecoveryTokenPlain(value)

0 comments on commit adcd4c1

Please sign in to comment.