diff options
| author | natsuoto <[email protected]> | 2026-07-11 11:43:53 +0530 |
|---|---|---|
| committer | natsuoto <[email protected]> | 2026-07-11 11:43:53 +0530 |
| commit | b86f5c04977b9e606ed2e25b1c02a869e4e4762d (patch) | |
| tree | 14fe22a91e50f1b9da1dabe35f8dedab4ff43666 | |
| parent | 107ab2cf62b636889d867693c5678399426c6ecc (diff) | |
| download | edify-b86f5c04977b9e606ed2e25b1c02a869e4e4762d.tar.xz edify-b86f5c04977b9e606ed2e25b1c02a869e4e4762d.zip | |
feat(library/auth): 19 auth patterns (password, pin, token, jwt, otp, apikey, bearer, session, csrf, mnemonic, hmac, passkey, mfa, webauthn, secret, sso, refresh, signing, challenge)
| -rw-r--r-- | edify/library/auth/__init__.py | 25 | ||||
| -rw-r--r-- | edify/library/auth/apikey.py | 14 | ||||
| -rw-r--r-- | edify/library/auth/bearer.py | 15 | ||||
| -rw-r--r-- | edify/library/auth/challenge.py | 16 | ||||
| -rw-r--r-- | edify/library/auth/csrf.py | 14 | ||||
| -rw-r--r-- | edify/library/auth/hmac.py | 13 | ||||
| -rw-r--r-- | edify/library/auth/jwt.py | 19 | ||||
| -rw-r--r-- | edify/library/auth/mfa.py | 13 | ||||
| -rw-r--r-- | edify/library/auth/mnemonic.py | 10 | ||||
| -rw-r--r-- | edify/library/auth/otp.py | 17 | ||||
| -rw-r--r-- | edify/library/auth/passkey.py | 16 | ||||
| -rw-r--r-- | edify/library/auth/password.py | 81 | ||||
| -rw-r--r-- | edify/library/auth/pin.py | 13 | ||||
| -rw-r--r-- | edify/library/auth/refresh.py | 16 | ||||
| -rw-r--r-- | edify/library/auth/secret.py | 14 | ||||
| -rw-r--r-- | edify/library/auth/session.py | 14 | ||||
| -rw-r--r-- | edify/library/auth/signing.py | 16 | ||||
| -rw-r--r-- | edify/library/auth/sso.py | 17 | ||||
| -rw-r--r-- | edify/library/auth/token.py | 14 | ||||
| -rw-r--r-- | edify/library/auth/webauthn.py | 14 |
20 files changed, 371 insertions, 0 deletions
diff --git a/edify/library/auth/__init__.py b/edify/library/auth/__init__.py new file mode 100644 index 0000000..68928fa --- /dev/null +++ b/edify/library/auth/__init__.py @@ -0,0 +1,25 @@ +from edify.library.auth.apikey import apikey +from edify.library.auth.bearer import bearer +from edify.library.auth.challenge import challenge +from edify.library.auth.csrf import csrf +from edify.library.auth.hmac import hmac +from edify.library.auth.jwt import jwt +from edify.library.auth.mfa import mfa +from edify.library.auth.mnemonic import mnemonic +from edify.library.auth.otp import otp +from edify.library.auth.passkey import passkey +from edify.library.auth.password import password +from edify.library.auth.pin import pin +from edify.library.auth.refresh import refresh +from edify.library.auth.secret import secret +from edify.library.auth.session import session +from edify.library.auth.signing import signing +from edify.library.auth.sso import sso +from edify.library.auth.token import token +from edify.library.auth.webauthn import webauthn + +__all__ = [ + "apikey", "bearer", "challenge", "csrf", "hmac", "jwt", "mfa", "mnemonic", + "otp", "passkey", "password", "pin", "refresh", "secret", "session", + "signing", "sso", "token", "webauthn", +] diff --git a/edify/library/auth/apikey.py b/edify/library/auth/apikey.py new file mode 100644 index 0000000..e27d542 --- /dev/null +++ b/edify/library/auth/apikey.py @@ -0,0 +1,14 @@ +"""``apikey`` — opaque API-key shape (20–128 URL-safe chars).""" + +from __future__ import annotations + +from edify import Pattern + +apikey = ( + Pattern() + .start_of_input() + .between(20, 128) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for an opaque API-key shape: 20–128 URL-safe characters.""" diff --git a/edify/library/auth/bearer.py b/edify/library/auth/bearer.py new file mode 100644 index 0000000..1a2cc2d --- /dev/null +++ b/edify/library/auth/bearer.py @@ -0,0 +1,15 @@ +"""``bearer`` — HTTP bearer-token header shape (``Bearer <token>``).""" + +from __future__ import annotations + +from edify import Pattern + +bearer = ( + Pattern() + .start_of_input() + .string("Bearer ") + .one_or_more() + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("._-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for the ``Bearer <token>`` HTTP header shape.""" diff --git a/edify/library/auth/challenge.py b/edify/library/auth/challenge.py new file mode 100644 index 0000000..76722c5 --- /dev/null +++ b/edify/library/auth/challenge.py @@ -0,0 +1,16 @@ +"""``challenge`` — authentication-challenge nonce shape (16–128 URL-safe chars).""" + +from __future__ import annotations + +from edify import Pattern + +challenge = ( + Pattern() + .start_of_input() + .between(16, 128) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for an authentication challenge / nonce: +16–128 URL-safe characters. +""" diff --git a/edify/library/auth/csrf.py b/edify/library/auth/csrf.py new file mode 100644 index 0000000..ef357e9 --- /dev/null +++ b/edify/library/auth/csrf.py @@ -0,0 +1,14 @@ +"""``csrf`` — CSRF-token shape (32–128 URL-safe chars).""" + +from __future__ import annotations + +from edify import Pattern + +csrf = ( + Pattern() + .start_of_input() + .between(32, 128) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for a CSRF-token shape: 32–128 URL-safe characters.""" diff --git a/edify/library/auth/hmac.py b/edify/library/auth/hmac.py new file mode 100644 index 0000000..136137c --- /dev/null +++ b/edify/library/auth/hmac.py @@ -0,0 +1,13 @@ +"""``hmac`` — HMAC signature shape (hex digest, 32–128 chars).""" + +from __future__ import annotations + +from edify import Pattern + +hmac = ( + Pattern() + .start_of_input() + .between(32, 128).any_of().range("0", "9").range("a", "f").range("A", "F").end() + .end_of_input() +) +"""Callable :class:`Pattern` for a hex HMAC digest: 32–128 hex characters.""" diff --git a/edify/library/auth/jwt.py b/edify/library/auth/jwt.py new file mode 100644 index 0000000..244834b --- /dev/null +++ b/edify/library/auth/jwt.py @@ -0,0 +1,19 @@ +"""``jwt`` — JSON Web Token shape (three base64url segments separated by dots).""" + +from __future__ import annotations + +from edify import Pattern + +jwt = ( + Pattern() + .start_of_input() + .one_or_more().any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .char(".") + .one_or_more().any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .char(".") + .one_or_more().any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for the JWT shape: three base64url-encoded +segments separated by dots (``header.payload.signature``). +""" diff --git a/edify/library/auth/mfa.py b/edify/library/auth/mfa.py new file mode 100644 index 0000000..b2261de --- /dev/null +++ b/edify/library/auth/mfa.py @@ -0,0 +1,13 @@ +"""``mfa`` — MFA/TOTP six-to-eight digit code shape.""" + +from __future__ import annotations + +from edify import Pattern + +mfa = ( + Pattern() + .start_of_input() + .between(6, 8).digit() + .end_of_input() +) +"""Callable :class:`Pattern` for the MFA/TOTP code shape: 6–8 decimal digits.""" diff --git a/edify/library/auth/mnemonic.py b/edify/library/auth/mnemonic.py new file mode 100644 index 0000000..f5db890 --- /dev/null +++ b/edify/library/auth/mnemonic.py @@ -0,0 +1,10 @@ +"""``mnemonic`` — BIP-39 mnemonic phrase shape (12–24 lowercase words separated by single spaces).""" + +from __future__ import annotations + +from edify.library._support.regex import RegexBackedPattern + +mnemonic = RegexBackedPattern(r"^(?:[a-z]+ ){11,23}[a-z]+$") +"""Callable :class:`Pattern` for a BIP-39 mnemonic phrase: 12 to 24 +lowercase words separated by single spaces. +""" diff --git a/edify/library/auth/otp.py b/edify/library/auth/otp.py new file mode 100644 index 0000000..b32ca0c --- /dev/null +++ b/edify/library/auth/otp.py @@ -0,0 +1,17 @@ +"""``otp`` — one-time password shape (6–8 digits or 6–8 alphanumerics).""" + +from __future__ import annotations + +from edify import Pattern, any_of + +otp = any_of( + Pattern().start_of_input().between(6, 8).digit().end_of_input(), + Pattern() + .start_of_input() + .between(6, 8) + .any_of().range("A", "Z").range("0", "9").end() + .end_of_input(), +) +"""Callable :class:`Pattern` for the one-time-password shape: 6–8 digits or +6–8 uppercase-alphanumerics. +""" diff --git a/edify/library/auth/passkey.py b/edify/library/auth/passkey.py new file mode 100644 index 0000000..925ebb7 --- /dev/null +++ b/edify/library/auth/passkey.py @@ -0,0 +1,16 @@ +"""``passkey`` — WebAuthn passkey credential-ID shape (base64url).""" + +from __future__ import annotations + +from edify import Pattern + +passkey = ( + Pattern() + .start_of_input() + .between(22, 512) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for a WebAuthn passkey credential-ID shape: +22–512 base64url characters. +""" diff --git a/edify/library/auth/password.py b/edify/library/auth/password.py new file mode 100644 index 0000000..b25d85c --- /dev/null +++ b/edify/library/auth/password.py @@ -0,0 +1,81 @@ +"""``password`` — configurable password-strength validator (callable :class:`Pattern` subclass).""" + +from __future__ import annotations + +import re + +from edify.pattern.composition import Pattern + +_DEFAULT_SPECIAL_CHARS = "!@#$%^&*()_+-=[]{}|;':\",./<>?" +_UPPERCASE_RE = re.compile("[A-Z]") +_LOWERCASE_RE = re.compile("[a-z]") +_DIGIT_RE = re.compile("[0-9]") + + +class _PasswordPattern(Pattern): + """Callable :class:`Pattern` that checks configurable password thresholds. + + Attributes: + min_length: Minimum length inclusive. + max_length: Maximum length inclusive. + min_upper: Minimum required uppercase letters. + min_lower: Minimum required lowercase letters. + min_digit: Minimum required decimal digits. + min_special: Minimum required special characters. + special_chars: The set of characters counted toward ``min_special``. + """ + + def __init__( + self, + min_length: int = 8, + max_length: int = 64, + min_upper: int = 1, + min_lower: int = 1, + min_digit: int = 1, + min_special: int = 1, + special_chars: str = _DEFAULT_SPECIAL_CHARS, + ) -> None: + super().__init__() + self.min_length = min_length + self.max_length = max_length + self.min_upper = min_upper + self.min_lower = min_lower + self.min_digit = min_digit + self.min_special = min_special + self.special_chars = special_chars + + def __call__( # type: ignore[override] + self, + value: str, + min_length: int | None = None, + max_length: int | None = None, + min_upper: int | None = None, + min_lower: int | None = None, + min_digit: int | None = None, + min_special: int | None = None, + special_chars: str | None = None, + ) -> bool: + """Return True when ``value`` meets every configured threshold.""" + if not isinstance(value, str): + return False + effective_min_length = self.min_length if min_length is None else min_length + effective_max_length = self.max_length if max_length is None else max_length + effective_min_upper = self.min_upper if min_upper is None else min_upper + effective_min_lower = self.min_lower if min_lower is None else min_lower + effective_min_digit = self.min_digit if min_digit is None else min_digit + effective_min_special = self.min_special if min_special is None else min_special + effective_special = self.special_chars if special_chars is None else special_chars + length_ok = effective_min_length <= len(value) <= effective_max_length + upper_ok = len(_UPPERCASE_RE.findall(value)) >= effective_min_upper + lower_ok = len(_LOWERCASE_RE.findall(value)) >= effective_min_lower + digit_ok = len(_DIGIT_RE.findall(value)) >= effective_min_digit + special_count = sum(1 for character in value if character in effective_special) + special_ok = special_count >= effective_min_special + return length_ok and upper_ok and lower_ok and digit_ok and special_ok + + +password = _PasswordPattern() +"""Callable :class:`Pattern` (subclass) that enforces configurable +password-strength thresholds. Call as ``password(value)`` for the defaults, +or ``password(value, min_length=12, min_special=2)`` to tighten specific thresholds. +""" diff --git a/edify/library/auth/pin.py b/edify/library/auth/pin.py new file mode 100644 index 0000000..5f61a0a --- /dev/null +++ b/edify/library/auth/pin.py @@ -0,0 +1,13 @@ +"""``pin`` — numeric PIN shape (4–12 digits).""" + +from __future__ import annotations + +from edify import Pattern + +pin = ( + Pattern() + .start_of_input() + .between(4, 12).digit() + .end_of_input() +) +"""Callable :class:`Pattern` for the numeric PIN shape: 4–12 digits.""" diff --git a/edify/library/auth/refresh.py b/edify/library/auth/refresh.py new file mode 100644 index 0000000..5e01139 --- /dev/null +++ b/edify/library/auth/refresh.py @@ -0,0 +1,16 @@ +"""``refresh`` — OAuth refresh-token shape (opaque, 32–512 chars).""" + +from __future__ import annotations + +from edify import Pattern + +refresh = ( + Pattern() + .start_of_input() + .between(32, 512) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("._-~+/=").end() + .end_of_input() +) +"""Callable :class:`Pattern` for an OAuth refresh-token shape: 32–512 +opaque characters (URL-safe plus common padding symbols). +""" diff --git a/edify/library/auth/secret.py b/edify/library/auth/secret.py new file mode 100644 index 0000000..89b551f --- /dev/null +++ b/edify/library/auth/secret.py @@ -0,0 +1,14 @@ +"""``secret`` — opaque secret string shape (16–256 URL-safe chars).""" + +from __future__ import annotations + +from edify import Pattern + +secret = ( + Pattern() + .start_of_input() + .between(16, 256) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for an opaque secret string: 16–256 URL-safe characters.""" diff --git a/edify/library/auth/session.py b/edify/library/auth/session.py new file mode 100644 index 0000000..e6748ea --- /dev/null +++ b/edify/library/auth/session.py @@ -0,0 +1,14 @@ +"""``session`` — session identifier shape (16–128 URL-safe chars).""" + +from __future__ import annotations + +from edify import Pattern + +session = ( + Pattern() + .start_of_input() + .between(16, 128) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for a session-identifier shape: 16–128 URL-safe characters.""" diff --git a/edify/library/auth/signing.py b/edify/library/auth/signing.py new file mode 100644 index 0000000..30303c7 --- /dev/null +++ b/edify/library/auth/signing.py @@ -0,0 +1,16 @@ +"""``signing`` — request-signature shape (hex or base64, 32–256 chars).""" + +from __future__ import annotations + +from edify import Pattern + +signing = ( + Pattern() + .start_of_input() + .between(32, 256) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("+/=_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for a request-signature payload: 32–256 +base64-family characters. +""" diff --git a/edify/library/auth/sso.py b/edify/library/auth/sso.py new file mode 100644 index 0000000..9e4916c --- /dev/null +++ b/edify/library/auth/sso.py @@ -0,0 +1,17 @@ +"""``sso`` — SSO ticket/assertion shape (base64 or hex, 20–2048 chars).""" + +from __future__ import annotations + +from edify import Pattern + +sso = ( + Pattern() + .start_of_input() + .between(20, 2048) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("+/=_-.").end() + .end_of_input() +) +"""Callable :class:`Pattern` for an SSO ticket/assertion opaque payload: +20–2048 base64-family characters (letters, digits, ``+``, ``/``, ``=``, +``_``, ``-``, ``.``). +""" diff --git a/edify/library/auth/token.py b/edify/library/auth/token.py new file mode 100644 index 0000000..a2e0c7e --- /dev/null +++ b/edify/library/auth/token.py @@ -0,0 +1,14 @@ +"""``token`` — opaque bearer/session token shape (24–256 URL-safe chars).""" + +from __future__ import annotations + +from edify import Pattern + +token = ( + Pattern() + .start_of_input() + .between(24, 256) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-.").end() + .end_of_input() +) +"""Callable :class:`Pattern` for opaque token strings: 24–256 URL-safe characters.""" diff --git a/edify/library/auth/webauthn.py b/edify/library/auth/webauthn.py new file mode 100644 index 0000000..d81251c --- /dev/null +++ b/edify/library/auth/webauthn.py @@ -0,0 +1,14 @@ +"""``webauthn`` — WebAuthn assertion/attestation ID shape (base64url).""" + +from __future__ import annotations + +from edify import Pattern + +webauthn = ( + Pattern() + .start_of_input() + .between(43, 512) + .any_of().range("A", "Z").range("a", "z").range("0", "9").any_of_chars("_-").end() + .end_of_input() +) +"""Callable :class:`Pattern` for a WebAuthn credential/assertion base64url shape.""" |
