paskia 0.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. paskia/__init__.py +3 -0
  2. paskia/_version.py +34 -0
  3. paskia/aaguid/__init__.py +32 -0
  4. paskia/aaguid/combined_aaguid.json +1 -0
  5. paskia/authsession.py +112 -0
  6. paskia/bootstrap.py +190 -0
  7. paskia/config.py +25 -0
  8. paskia/db/__init__.py +415 -0
  9. paskia/db/sql.py +1424 -0
  10. paskia/fastapi/__init__.py +3 -0
  11. paskia/fastapi/__main__.py +335 -0
  12. paskia/fastapi/admin.py +850 -0
  13. paskia/fastapi/api.py +308 -0
  14. paskia/fastapi/auth_host.py +97 -0
  15. paskia/fastapi/authz.py +110 -0
  16. paskia/fastapi/mainapp.py +130 -0
  17. paskia/fastapi/remote.py +504 -0
  18. paskia/fastapi/reset.py +101 -0
  19. paskia/fastapi/session.py +52 -0
  20. paskia/fastapi/user.py +162 -0
  21. paskia/fastapi/ws.py +163 -0
  22. paskia/fastapi/wsutil.py +91 -0
  23. paskia/frontend-build/auth/admin/index.html +18 -0
  24. paskia/frontend-build/auth/assets/AccessDenied-Bc249ASC.css +1 -0
  25. paskia/frontend-build/auth/assets/AccessDenied-C-lL9vbN.js +8 -0
  26. paskia/frontend-build/auth/assets/RestrictedAuth-BLMK7-nL.js +1 -0
  27. paskia/frontend-build/auth/assets/RestrictedAuth-DgdJyscT.css +1 -0
  28. paskia/frontend-build/auth/assets/_plugin-vue_export-helper-BTzJAQlS.css +1 -0
  29. paskia/frontend-build/auth/assets/_plugin-vue_export-helper-rKFEraYH.js +2 -0
  30. paskia/frontend-build/auth/assets/admin-Cs6Mg773.css +1 -0
  31. paskia/frontend-build/auth/assets/admin-Df5_Damp.js +1 -0
  32. paskia/frontend-build/auth/assets/auth-BU_O38k2.css +1 -0
  33. paskia/frontend-build/auth/assets/auth-Df3pjeSS.js +1 -0
  34. paskia/frontend-build/auth/assets/forward-Dzg-aE1C.js +1 -0
  35. paskia/frontend-build/auth/assets/helpers-DzjFIx78.js +1 -0
  36. paskia/frontend-build/auth/assets/pow-2N9bxgAo.js +1 -0
  37. paskia/frontend-build/auth/assets/reset-BWF4cWKR.css +1 -0
  38. paskia/frontend-build/auth/assets/reset-C_Td1_jn.js +1 -0
  39. paskia/frontend-build/auth/assets/restricted-C0IQufuH.js +1 -0
  40. paskia/frontend-build/auth/index.html +19 -0
  41. paskia/frontend-build/auth/restricted/index.html +16 -0
  42. paskia/frontend-build/int/forward/index.html +18 -0
  43. paskia/frontend-build/int/reset/index.html +15 -0
  44. paskia/globals.py +71 -0
  45. paskia/remoteauth.py +359 -0
  46. paskia/sansio.py +263 -0
  47. paskia/util/frontend.py +75 -0
  48. paskia/util/hostutil.py +76 -0
  49. paskia/util/htmlutil.py +47 -0
  50. paskia/util/passphrase.py +20 -0
  51. paskia/util/permutil.py +32 -0
  52. paskia/util/pow.py +45 -0
  53. paskia/util/querysafe.py +11 -0
  54. paskia/util/sessionutil.py +37 -0
  55. paskia/util/startupbox.py +75 -0
  56. paskia/util/timeutil.py +47 -0
  57. paskia/util/tokens.py +44 -0
  58. paskia/util/useragent.py +10 -0
  59. paskia/util/userinfo.py +159 -0
  60. paskia/util/wordlist.py +54 -0
  61. paskia-0.7.1.dist-info/METADATA +22 -0
  62. paskia-0.7.1.dist-info/RECORD +64 -0
  63. paskia-0.7.1.dist-info/WHEEL +4 -0
  64. paskia-0.7.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,47 @@
1
+ """Utility functions for parsing time durations."""
2
+
3
+ import re
4
+ from datetime import timedelta
5
+
6
+
7
+ def parse_duration(duration_str: str) -> timedelta:
8
+ """Parse a duration string into a timedelta.
9
+
10
+ Supports units: s, m, min, h, d
11
+ Examples: "30s", "5m", "5min", "2h", "1d"
12
+
13
+ Args:
14
+ duration_str: A string like "30s", "5m", "2h"
15
+
16
+ Returns:
17
+ A timedelta object
18
+
19
+ Raises:
20
+ ValueError: If the format is invalid
21
+ """
22
+ duration_str = duration_str.strip().lower()
23
+
24
+ # Pattern matches: number + unit
25
+ # Units: s (seconds), m/min (minutes), h (hours), d (days)
26
+ pattern = r"^(\d+(?:\.\d+)?)(s|m|min|h|d)$"
27
+ match = re.match(pattern, duration_str)
28
+
29
+ if not match:
30
+ raise ValueError(
31
+ f"Invalid duration format: '{duration_str}'. "
32
+ "Expected format like '30s', '5m', '5min', '2h', or '1d'"
33
+ )
34
+
35
+ value = float(match.group(1))
36
+ unit = match.group(2)
37
+
38
+ if unit == "s":
39
+ return timedelta(seconds=value)
40
+ elif unit in ("m", "min"):
41
+ return timedelta(minutes=value)
42
+ elif unit == "h":
43
+ return timedelta(hours=value)
44
+ elif unit == "d":
45
+ return timedelta(days=value)
46
+ else:
47
+ raise ValueError(f"Unsupported time unit: {unit}")
paskia/util/tokens.py ADDED
@@ -0,0 +1,44 @@
1
+ import hashlib
2
+ import secrets
3
+
4
+ import base64url
5
+
6
+ from paskia.util.passphrase import is_well_formed
7
+
8
+
9
+ def create_token() -> str:
10
+ return secrets.token_urlsafe(12) # 16 characters Base64
11
+
12
+
13
+ def session_key(token: str) -> bytes:
14
+ if len(token) != 16:
15
+ raise ValueError("Session token must be exactly 16 characters long")
16
+ return b"sess" + base64url.dec(token)
17
+
18
+
19
+ def encode_session_key(key: bytes) -> str:
20
+ """Encode an opaque session key for external representation."""
21
+ return base64url.enc(key)
22
+
23
+
24
+ def decode_session_key(encoded: str) -> bytes:
25
+ """Decode an opaque session key from its public representation."""
26
+ if not encoded:
27
+ raise ValueError("Invalid session identifier")
28
+ try:
29
+ raw = base64url.dec(encoded)
30
+ except Exception as exc: # pragma: no cover - defensive
31
+ raise ValueError("Invalid session identifier") from exc
32
+ if not raw.startswith(b"sess"):
33
+ raise ValueError("Invalid session identifier")
34
+ return raw
35
+
36
+
37
+ def reset_key(passphrase: str) -> bytes:
38
+ if not is_well_formed(passphrase):
39
+ raise ValueError(
40
+ "Trying to reset with a session token in place of a passphrase"
41
+ if len(passphrase) == 16
42
+ else "Invalid passphrase format"
43
+ )
44
+ return b"rset" + hashlib.sha512(passphrase.encode()).digest()[:12]
@@ -0,0 +1,10 @@
1
+ import user_agents
2
+
3
+
4
+ def compact_user_agent(ua: str | None) -> str:
5
+ if not ua:
6
+ return "-"
7
+ u = user_agents.parse(ua)
8
+ ver = u.browser.version_string.split(".")[0]
9
+ dev = u.device.family if u.device.family not in ["Other", "Mac"] else ""
10
+ return f"{u.browser.family}/{ver} {u.os.family} {dev}".strip()
@@ -0,0 +1,159 @@
1
+ """User information formatting and retrieval logic."""
2
+
3
+ from datetime import timezone
4
+
5
+ from paskia import aaguid
6
+ from paskia.authsession import session_key
7
+ from paskia.globals import db
8
+ from paskia.util import hostutil, permutil, tokens, useragent
9
+
10
+
11
+ def _format_datetime(dt):
12
+ """Format a datetime object to ISO 8601 string with UTC timezone."""
13
+ if dt is None:
14
+ return None
15
+ if dt.tzinfo:
16
+ return dt.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
17
+ else:
18
+ return dt.replace(tzinfo=timezone.utc).isoformat().replace("+00:00", "Z")
19
+
20
+
21
+ async def format_user_info(
22
+ *,
23
+ user_uuid,
24
+ auth: str,
25
+ session_record,
26
+ request_host: str | None,
27
+ ) -> dict:
28
+ """Format complete user information for authenticated users.
29
+
30
+ Args:
31
+ user_uuid: UUID of the user to fetch information for
32
+ auth: Authentication token
33
+ session_record: Current session record
34
+ request_host: Host header from the request
35
+
36
+ Returns:
37
+ Dictionary containing formatted user information including:
38
+ - User details
39
+ - Organization and role information
40
+ - Credentials list
41
+ - Sessions list
42
+ - Permissions
43
+ """
44
+ u = await db.instance.get_user_by_uuid(user_uuid)
45
+ ctx = await permutil.session_context(auth, request_host)
46
+
47
+ # Fetch and format credentials
48
+ credential_ids = await db.instance.get_credentials_by_user_uuid(user_uuid)
49
+ credentials: list[dict] = []
50
+ user_aaguids: set[str] = set()
51
+
52
+ for cred_id in credential_ids:
53
+ try:
54
+ c = await db.instance.get_credential_by_id(cred_id)
55
+ except ValueError:
56
+ continue
57
+
58
+ aaguid_str = str(c.aaguid)
59
+ user_aaguids.add(aaguid_str)
60
+ credentials.append(
61
+ {
62
+ "credential_uuid": str(c.uuid),
63
+ "aaguid": aaguid_str,
64
+ "created_at": _format_datetime(c.created_at),
65
+ "last_used": _format_datetime(c.last_used),
66
+ "last_verified": _format_datetime(c.last_verified),
67
+ "sign_count": c.sign_count,
68
+ "is_current_session": session_record.credential_uuid == c.uuid,
69
+ }
70
+ )
71
+
72
+ credentials.sort(key=lambda cred: cred["created_at"])
73
+ aaguid_info = aaguid.filter(user_aaguids)
74
+
75
+ # Format role and org information
76
+ role_info = None
77
+ org_info = None
78
+ effective_permissions: list[str] = []
79
+ is_global_admin = False
80
+ is_org_admin = False
81
+
82
+ if ctx:
83
+ role_info = {
84
+ "uuid": str(ctx.role.uuid),
85
+ "display_name": ctx.role.display_name,
86
+ "permissions": ctx.role.permissions,
87
+ }
88
+ org_info = {
89
+ "uuid": str(ctx.org.uuid),
90
+ "display_name": ctx.org.display_name,
91
+ "permissions": ctx.org.permissions,
92
+ }
93
+ effective_permissions = [p.id for p in (ctx.permissions or [])]
94
+ is_global_admin = "auth:admin" in (role_info["permissions"] or [])
95
+ is_org_admin = any(
96
+ p.startswith("auth:org:") for p in (role_info["permissions"] or [])
97
+ )
98
+
99
+ # Format sessions
100
+ normalized_request_host = hostutil.normalize_host(request_host)
101
+ session_records = await db.instance.list_sessions_for_user(user_uuid)
102
+ current_session_key = session_key(auth)
103
+ sessions_payload: list[dict] = []
104
+
105
+ for entry in session_records:
106
+ sessions_payload.append(
107
+ {
108
+ "id": tokens.encode_session_key(entry.key),
109
+ "credential_uuid": str(entry.credential_uuid),
110
+ "host": entry.host,
111
+ "ip": entry.ip,
112
+ "user_agent": useragent.compact_user_agent(entry.user_agent),
113
+ "last_renewed": _format_datetime(entry.renewed),
114
+ "is_current": entry.key == current_session_key,
115
+ "is_current_host": bool(
116
+ normalized_request_host
117
+ and entry.host
118
+ and entry.host == normalized_request_host
119
+ ),
120
+ }
121
+ )
122
+
123
+ return {
124
+ "authenticated": True,
125
+ "user": {
126
+ "user_uuid": str(u.uuid),
127
+ "user_name": u.display_name,
128
+ "created_at": _format_datetime(u.created_at),
129
+ "last_seen": _format_datetime(u.last_seen),
130
+ "visits": u.visits,
131
+ },
132
+ "org": org_info,
133
+ "role": role_info,
134
+ "permissions": effective_permissions,
135
+ "is_global_admin": is_global_admin,
136
+ "is_org_admin": is_org_admin,
137
+ "credentials": credentials,
138
+ "aaguid_info": aaguid_info,
139
+ "sessions": sessions_payload,
140
+ }
141
+
142
+
143
+ async def format_reset_user_info(user_uuid, reset_token) -> dict:
144
+ """Format minimal user information for reset token requests.
145
+
146
+ Args:
147
+ user_uuid: UUID of the user
148
+ reset_token: Reset token record
149
+
150
+ Returns:
151
+ Dictionary with minimal user info for password reset flow
152
+ """
153
+ u = await db.instance.get_user_by_uuid(user_uuid)
154
+
155
+ return {
156
+ "authenticated": False,
157
+ "session_type": reset_token.token_type,
158
+ "user": {"user_uuid": str(u.uuid), "user_name": u.display_name},
159
+ }
@@ -0,0 +1,54 @@
1
+ # A custom list of 1024 common 3-6 letter words, with unique 3-prefixes and no prefix words, entropy 2.1b/letter 10b/word
2
+ words: list = """
3
+ able about absent abuse access acid across act adapt add adjust admit adult advice affair afraid again age agree ahead
4
+ aim air aisle alarm album alert alien all almost alone alpha also alter always amazed among amused anchor angle animal
5
+ ankle annual answer any apart appear april arch are argue army around array art ascent ash ask aspect assume asthma atom
6
+ attack audit august aunt author avoid away awful axis baby back bad bag ball bamboo bank bar base battle beach become
7
+ beef before begin behind below bench best better beyond bid bike bind bio birth bitter black bleak blind blood blue
8
+ board body boil bomb bone book border boss bottom bounce bowl box boy brain bread bring brown brush bubble buck budget
9
+ build bulk bundle burden bus but buyer buzz cable cache cage cake call came can car case catch cause cave celery cement
10
+ census cereal change check child choice chunk cigar circle city civil class clean client close club coast code coffee
11
+ coil cold come cool copy core cost cotton couch cover coyote craft cream crime cross cruel cry cube cue cult cup curve
12
+ custom cute cycle dad damage danger daring dash dawn day deal debate decide deer define degree deity delay demand denial
13
+ depth derive design detail device dial dice die differ dim dinner direct dish divert dizzy doctor dog dollar domain
14
+ donate door dose double dove draft dream drive drop drum dry duck dumb dune during dust dutch dwarf eager early east
15
+ echo eco edge edit effort egg eight either elbow elder elite else embark emerge emily employ enable end enemy engine
16
+ enjoy enlist enough enrich ensure entire envy equal era erode error erupt escape essay estate ethics evil evoke exact
17
+ excess exist exotic expect extent eye fabric face fade faith fall family fan far father fault feel female fence fetch
18
+ fever few fiber field figure file find first fish fit fix flat flesh flight float fluid fly foam focus fog foil follow
19
+ food force fossil found fox frame fresh friend frog fruit fuel fun fury future gadget gain galaxy game gap garden gas
20
+ gate gauge gaze genius ghost giant gift giggle ginger girl give glass glide globe glue goal god gold good gospel govern
21
+ gown grant great grid group grunt guard guess guide gulf gun gym habit hair half hammer hand happy hard hat have hawk
22
+ hay hazard head hedge height help hen hero hidden high hill hint hip hire hobby hockey hold home honey hood hope horse
23
+ host hotel hour hover how hub huge human hungry hurt hybrid ice icon idea idle ignore ill image immune impact income
24
+ index infant inhale inject inmate inner input inside into invest iron island issue italy item ivory jacket jaguar james
25
+ jar jazz jeans jelly jewel job joe joke joy judge juice july jump june just kansas kate keep kernel key kick kid kind
26
+ kiss kit kiwi knee knife know labor lady lag lake lamp laptop large later laugh lava law layer lazy leader left legal
27
+ lemon length lesson letter level liar libya lid life light like limit line lion liquid list little live lizard load
28
+ local logic long loop lost loud love low loyal lucky lumber lunch lust luxury lyrics mad magic main major make male
29
+ mammal man map market mass matter maze mccoy meadow media meet melt member men mercy mesh method middle milk mimic mind
30
+ mirror miss mix mobile model mom monkey moon more mother mouse move much muffin mule must mutual myself myth naive name
31
+ napkin narrow nasty nation near neck need nephew nerve nest net never news next nice night noble noise noodle normal
32
+ nose note novel now number nurse nut oak obey object oblige obtain occur ocean odor off often oil okay old olive omit
33
+ once one onion online open opium oppose option orange orbit order organ orient orphan other outer oval oven own oxygen
34
+ oyster ozone pact paddle page pair palace panel paper parade past path pause pave paw pay peace pen people pepper permit
35
+ pet philip phone phrase piano pick piece pig pilot pink pipe pistol pitch pizza place please pluck poem point polar pond
36
+ pool post pot pound powder praise prefer price profit public pull punch pupil purity push put puzzle qatar quasi queen
37
+ quite quoted rabbit race radio rail rally ramp range rapid rare rather raven raw razor real rebel recall red reform
38
+ region reject relief remain rent reopen report result return review reward rhythm rib rich ride rifle right ring riot
39
+ ripple risk ritual river road robot rocket room rose rotate round row royal rubber rude rug rule run rural sad safe sage
40
+ sail salad same santa sauce save say scale scene school scope screen scuba sea second seed self semi sense series settle
41
+ seven shadow she ship shock shrimp shy sick side siege sign silver simple since siren sister six size skate sketch ski
42
+ skull slab sleep slight slogan slush small smile smooth snake sniff snow soap soccer soda soft solid son soon sort south
43
+ space speak sphere spirit split spoil spring spy square state step still story strong stuff style submit such sudden
44
+ suffer sugar suit summer sun supply sure swamp sweet switch sword symbol syntax syria system table tackle tag tail talk
45
+ tank tape target task tattoo taxi team tell ten term test text that theme this three thumb tibet ticket tide tight tilt
46
+ time tiny tip tired tissue title toast today toe toilet token tomato tone tool top torch toss total toward toy trade
47
+ tree trial trophy true try tube tumble tunnel turn twenty twice two type ugly unable uncle under unfair unique unlock
48
+ until unveil update uphold upon upper upset urban urge usage use usual vacuum vague valid van vapor vast vault vein
49
+ velvet vendor very vessel viable video view villa violin virus visit vital vivid vocal voice volume vote voyage wage
50
+ wait wall want war wash water wave way wealth web weird were west wet what when whip wide wife will window wire wish
51
+ wolf woman wonder wood work wrap wreck write wrong xander xbox xerox xray yang yard year yellow yes yin york you zane
52
+ zara zebra zen zero zippo zone zoo zorro zulu
53
+ """.split()
54
+ assert len(words) == 1024 # Exactly 10 bits of entropy per word
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: paskia
3
+ Version: 0.7.1
4
+ Summary: Passkey Auth made easy: all sites and APIs can be guarded even without any changes on the protected site.
5
+ Author: Leo Vasanko
6
+ Keywords: FastAPI,auth_request,forward_auth
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: aiosqlite>=0.19.0
9
+ Requires-Dist: base64url>=1.0.0
10
+ Requires-Dist: fastapi[standard]>=0.104.1
11
+ Requires-Dist: pyjwt>=2.8.0
12
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.0
13
+ Requires-Dist: user-agents>=2.2.0
14
+ Requires-Dist: uuid7-standard>=1.0.0
15
+ Requires-Dist: webauthn>=1.11.1
16
+ Requires-Dist: websockets>=12.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: coverage[toml]>=7.0.0; extra == 'dev'
19
+ Requires-Dist: httpx>=0.27.0; extra == 'dev'
20
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
21
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
22
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
@@ -0,0 +1,64 @@
1
+ paskia/__init__.py,sha256=6eopO87IOFA2zfOuqt8Jj8Tdtp93HBMOgUBtTzMRweM,57
2
+ paskia/_version.py,sha256=az9tfX88VBVbBBILFx2zOlpMh1RBTXIwP6baQTMKtHc,704
3
+ paskia/authsession.py,sha256=H9C_lPBEp4AmpNjLYQFQkAveG9lFVQGliyq35RXONkk,3901
4
+ paskia/bootstrap.py,sha256=xT_Qx4FdTiA7_eMGzctja_BHkt36M4I3D3HBfq3vqp0,5799
5
+ paskia/config.py,sha256=U_HGzZab-HEVPuoMXxy2mrpoiZYW4jy_jiWDnhf0wBA,749
6
+ paskia/globals.py,sha256=zbuq3cTiSmXFKK66MwNwGGTzdBlfn-yPdlVYkAnTAbo,1784
7
+ paskia/remoteauth.py,sha256=peu_fcINwDW8YGzLtbMq7FX7oD05bWwNLmJaMYWaKR0,12481
8
+ paskia/sansio.py,sha256=O4TGgNrHAYtHR2IxRgyrB2P2Zesz7eGq-IMM1RGjvC8,9998
9
+ paskia/aaguid/__init__.py,sha256=Moy67wiJSYulL_whgEEBBKabKEOvlR-NRLyXprDdBO0,1007
10
+ paskia/aaguid/combined_aaguid.json,sha256=CaZ96AiwdAjBnyVZnJ1eolAHxUQMB2H6mDgZkorYg_A,4124722
11
+ paskia/db/__init__.py,sha256=CRSGh8nWW-rRFKtPY8i0TemYkjMawbbD4SyavYdWbbg,12379
12
+ paskia/db/sql.py,sha256=sYtkuDWymPaMPdmzQ83QXC7T8xBvWy5un49gC2Ya1Ds,56300
13
+ paskia/fastapi/__init__.py,sha256=NFsTX1qytoyZKiur7RDTa2fxiOWHrop5CAAx8rqK9E0,58
14
+ paskia/fastapi/__main__.py,sha256=YtkTWGLPrrjsgZeGUWoRENAgdqwQSufjHimWKuLZKDg,11322
15
+ paskia/fastapi/admin.py,sha256=if5oPeQMr4D9laYCAHRDAsOqZUPY7P_FOx5CmX_zc8Y,28809
16
+ paskia/fastapi/api.py,sha256=qLCrdwgmREJmozoAKdczZDYffspgwEHoQ1_ZIuCMvK4,10755
17
+ paskia/fastapi/auth_host.py,sha256=Y5w9Mz6jyq0hj7SX8LfwebaesUOLGcWzGW9lsmw5WOo,3242
18
+ paskia/fastapi/authz.py,sha256=khz5hoj_lG8HfNO_uWCq8-319ROA1WGCB5nWw5Xge1Q,3362
19
+ paskia/fastapi/mainapp.py,sha256=9zW2D556i-Qfi0qvqjYCTCJ7lDHTGxDKGHbURvzw6Qc,4392
20
+ paskia/fastapi/remote.py,sha256=xpCdX3YXSXMDT9-ePaf7PRYfGyetpL2ZHF0g6V0DnZo,19869
21
+ paskia/fastapi/reset.py,sha256=hU2IxbFN6IffM7eDKwWRqcmJGfYcMffdGBfFpVJUXaY,3518
22
+ paskia/fastapi/session.py,sha256=9n0NuK5NtocOb27ahIU2T9eHZUog1Og_i9vvaDf7Wo8,1489
23
+ paskia/fastapi/user.py,sha256=PrffmznW15K5n648xfLZNSCfmq-EkuaWbmLr32micvk,5205
24
+ paskia/fastapi/ws.py,sha256=MfIAP5wQprnTtX3bJ4xqy-zpKQ3JxTTIon4gZXh9O7g,5662
25
+ paskia/fastapi/wsutil.py,sha256=CJZOyy4e9jazgyvj6CQ_kXVlGtpHNwfjGOdmHL02Nec,2620
26
+ paskia/frontend-build/auth/index.html,sha256=CNhzCRFmKLgscMF16qcqldjgRagB-wuDeEkZXopBlso,936
27
+ paskia/frontend-build/auth/admin/index.html,sha256=9DdZWPN9gcDTKBiMUrFfKPFYaBC31tIvBmaDfOMH91o,862
28
+ paskia/frontend-build/auth/assets/AccessDenied-Bc249ASC.css,sha256=tXoiIdVA54gVkzpqtrSidcdLB_86bB1ND-RwJSio8c0,7847
29
+ paskia/frontend-build/auth/assets/AccessDenied-C-lL9vbN.js,sha256=Dsl-52F0yrraDooPrYQvHDnAEttwtNrCIPTXpJ5PjfM,51526
30
+ paskia/frontend-build/auth/assets/RestrictedAuth-BLMK7-nL.js,sha256=KAvDgEdN42Ycw3zyPONiFelfiV5xnUM9GXSnTpYfGX0,9816
31
+ paskia/frontend-build/auth/assets/RestrictedAuth-DgdJyscT.css,sha256=mh35oOAdymdWc0rbTIE4oBiPzMDaNBH-ERugDl0tkSk,5397
32
+ paskia/frontend-build/auth/assets/_plugin-vue_export-helper-BTzJAQlS.css,sha256=GpCu32ZoXHmL_8wVVa0Yja8dtKpaJSRyfitYAURx4Yc,12796
33
+ paskia/frontend-build/auth/assets/_plugin-vue_export-helper-rKFEraYH.js,sha256=tX2rJaQDkdoiFt6YDC4vVGpMlw8cN0KuLSGdZKc5qU0,84792
34
+ paskia/frontend-build/auth/assets/admin-Cs6Mg773.css,sha256=jkoXOfyZj7pI9AUijNb8qL2YusYll0kX1pgqaC8J_us,7377
35
+ paskia/frontend-build/auth/assets/admin-Df5_Damp.js,sha256=zV362iCLS1w9RC00AOu5-astKIPfz4h4KtnHSgpjmqY,39791
36
+ paskia/frontend-build/auth/assets/auth-BU_O38k2.css,sha256=auHlyCxBtNWTsSxjCEG7986Et0IxFkDdzMeKXllbYxQ,4333
37
+ paskia/frontend-build/auth/assets/auth-Df3pjeSS.js,sha256=A4kt2gzaW9w3Wa7cuaLNx68E1rkjuOlHkDRfVaOxEO8,25383
38
+ paskia/frontend-build/auth/assets/forward-Dzg-aE1C.js,sha256=XZfdNI43MqAKQkui-3r7tCW_GL2GDu_NXPMzovJMcOA,782
39
+ paskia/frontend-build/auth/assets/helpers-DzjFIx78.js,sha256=w_IsCBn3QwidsuwQhVRycd8Fa53lvbgRGGojTBXVlUc,940
40
+ paskia/frontend-build/auth/assets/pow-2N9bxgAo.js,sha256=7AfzW5lcTefPI6YGXrYao1b56L7v5Bon9Y9N40yHsaE,9447
41
+ paskia/frontend-build/auth/assets/reset-BWF4cWKR.css,sha256=8RZp0rx3cVPaxH2LjBzmyGzkrtQV6lqeAsBEG7eSetA,238
42
+ paskia/frontend-build/auth/assets/reset-C_Td1_jn.js,sha256=0EtawdYcLd14KKmtwMAuvaakEjpRrZA9am-QgrZc98Q,3983
43
+ paskia/frontend-build/auth/assets/restricted-C0IQufuH.js,sha256=fjCvfZTZBC7qkCKuvy-ppW3rzSD1ST9ZfE4VMR22r9w,1023
44
+ paskia/frontend-build/auth/restricted/index.html,sha256=xfFuO5qpZfx8CNz9kB7fC_OjPIsoZQ1b9W5xeFE1cJ4,785
45
+ paskia/frontend-build/int/forward/index.html,sha256=h4qLmAjIlGQ7ZjKV3yDsMD4SpPS2K3j4Z1BwW8DJf04,870
46
+ paskia/frontend-build/int/reset/index.html,sha256=t21RLLETyTHBnKe95V053_zmfB34Vavp0fp4G5fU7-U,612
47
+ paskia/util/frontend.py,sha256=NbCWi74lb6yqk-2WKJci4km-Kij0yCK1Vq_51yKfY3U,2505
48
+ paskia/util/hostutil.py,sha256=agZWjOawQxHshLGkFX-TxpExP1pjXEWCSrMCnOGajk8,2321
49
+ paskia/util/htmlutil.py,sha256=r0YR3AKIsIg5Gg45N92Lh7UYz2kwF1coreZahzLZIX8,1580
50
+ paskia/util/passphrase.py,sha256=oAfLJwaBvCmXY3CMv8KeAogE9V5SejnTLfXWIiQYCOo,562
51
+ paskia/util/permutil.py,sha256=zxcaeQ_vMBb9U20dZ40zvXxZ_upxIEVfJz59BP6aegY,1013
52
+ paskia/util/pow.py,sha256=u99Phs1DBiv9Ptm8agaA7ZSOnRPtDcpgkLgGzNTcJWo,1395
53
+ paskia/util/querysafe.py,sha256=iHfY2z5MX0Y6gso2oeq-SiHhg97vq35Jp1D16sXY0FE,294
54
+ paskia/util/sessionutil.py,sha256=3UVT28hOA3l-QmXrj7g5ceHyjLtQ8Mx9P34Ad2sXlps,1210
55
+ paskia/util/startupbox.py,sha256=SIg26g9EIdkm7uVWiDfAUuXn6Z05EAsYEQni4qkVEWI,2353
56
+ paskia/util/timeutil.py,sha256=1Zf8rXa76oLXDuZrGyuVDNhFjxl-28Z_Xb6pIH06ORk,1258
57
+ paskia/util/tokens.py,sha256=KGgVz7LXZYHz_7deX6qT7MtHmO3yify7O5ncv-8BYgE,1335
58
+ paskia/util/useragent.py,sha256=wOeueToxKHdJ91vT5jMVBoIhelNwxD5u7vgWQGSjImA,325
59
+ paskia/util/userinfo.py,sha256=CEV-IYFoiTqBeKnqm_SJlXowy1R6Jc6t2t9EDpKnhvM,5182
60
+ paskia/util/wordlist.py,sha256=EKwrABkqRO1f59--muegOoluPydPJAHlWJNXwV0IFyA,6069
61
+ paskia-0.7.1.dist-info/METADATA,sha256=Gh-LyfaqS81Mx3Cg2-VWXtdpv5ay0lcVWI1GvhuFSGY,825
62
+ paskia-0.7.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
63
+ paskia-0.7.1.dist-info/entry_points.txt,sha256=8jB3Hhf5N_HPouTcfdyTFTkM9bz5q7cOAz2PmkxKmuU,56
64
+ paskia-0.7.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ paskia = paskia.fastapi.__main__:main