aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornatsuoto <[email protected]>2026-07-11 11:43:53 +0530
committernatsuoto <[email protected]>2026-07-11 11:43:53 +0530
commitb86f5c04977b9e606ed2e25b1c02a869e4e4762d (patch)
tree14fe22a91e50f1b9da1dabe35f8dedab4ff43666
parent107ab2cf62b636889d867693c5678399426c6ecc (diff)
downloadedify-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__.py25
-rw-r--r--edify/library/auth/apikey.py14
-rw-r--r--edify/library/auth/bearer.py15
-rw-r--r--edify/library/auth/challenge.py16
-rw-r--r--edify/library/auth/csrf.py14
-rw-r--r--edify/library/auth/hmac.py13
-rw-r--r--edify/library/auth/jwt.py19
-rw-r--r--edify/library/auth/mfa.py13
-rw-r--r--edify/library/auth/mnemonic.py10
-rw-r--r--edify/library/auth/otp.py17
-rw-r--r--edify/library/auth/passkey.py16
-rw-r--r--edify/library/auth/password.py81
-rw-r--r--edify/library/auth/pin.py13
-rw-r--r--edify/library/auth/refresh.py16
-rw-r--r--edify/library/auth/secret.py14
-rw-r--r--edify/library/auth/session.py14
-rw-r--r--edify/library/auth/signing.py16
-rw-r--r--edify/library/auth/sso.py17
-rw-r--r--edify/library/auth/token.py14
-rw-r--r--edify/library/auth/webauthn.py14
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."""