paskia 0.10.0__tar.gz → 0.10.2__tar.gz

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 (87) hide show
  1. {paskia-0.10.0 → paskia-0.10.2}/PKG-INFO +14 -11
  2. {paskia-0.10.0 → paskia-0.10.2}/README.md +13 -10
  3. {paskia-0.10.0 → paskia-0.10.2}/paskia/_version.py +2 -2
  4. {paskia-0.10.0 → paskia-0.10.2}/paskia/bootstrap.py +8 -7
  5. {paskia-0.10.0 → paskia-0.10.2}/paskia/db/__init__.py +2 -0
  6. {paskia-0.10.0 → paskia-0.10.2}/paskia/db/background.py +5 -8
  7. {paskia-0.10.0 → paskia-0.10.2}/paskia/db/operations.py +17 -0
  8. {paskia-0.10.0 → paskia-0.10.2}/paskia/db/structs.py +3 -2
  9. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/__main__.py +21 -13
  10. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/authz.py +8 -1
  11. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/mainapp.py +6 -4
  12. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/user.py +22 -0
  13. {paskia-0.10.0 → paskia-0.10.2}/paskia/frontend-build/auth/admin/index.html +7 -6
  14. paskia-0.10.2/paskia/frontend-build/auth/assets/AccessDenied-CVQZxSIL.css +1 -0
  15. paskia-0.10.2/paskia/frontend-build/auth/assets/AccessDenied-Licr0tqA.js +8 -0
  16. paskia-0.10.0/paskia/frontend-build/auth/assets/RestrictedAuth-BOdNrlQB.css → paskia-0.10.2/paskia/frontend-build/auth/assets/RestrictedAuth-0MFeNWS2.css +1 -1
  17. paskia-0.10.0/paskia/frontend-build/auth/assets/RestrictedAuth-BSusdAfp.js → paskia-0.10.2/paskia/frontend-build/auth/assets/RestrictedAuth-DWKMTEV3.js +1 -1
  18. paskia-0.10.2/paskia/frontend-build/auth/assets/_plugin-vue_export-helper-DJsHCwvl.js +33 -0
  19. paskia-0.10.2/paskia/frontend-build/auth/assets/_plugin-vue_export-helper-DUBf8-iM.css +1 -0
  20. paskia-0.10.0/paskia/frontend-build/auth/assets/admin-CmNtuH3s.css → paskia-0.10.2/paskia/frontend-build/auth/assets/admin-B1H4YqM_.css +1 -1
  21. paskia-0.10.2/paskia/frontend-build/auth/assets/admin-CZKsX1OI.js +1 -0
  22. paskia-0.10.0/paskia/frontend-build/auth/assets/auth-BKq4T2K2.css → paskia-0.10.2/paskia/frontend-build/auth/assets/auth-B4EpDxom.css +1 -1
  23. paskia-0.10.2/paskia/frontend-build/auth/assets/auth-Pe-PKe8b.js +1 -0
  24. paskia-0.10.2/paskia/frontend-build/auth/assets/forward-BC0p23CH.js +1 -0
  25. paskia-0.10.0/paskia/frontend-build/auth/assets/pow-2N9bxgAo.js → paskia-0.10.2/paskia/frontend-build/auth/assets/pow-DUr-T9XX.js +1 -1
  26. paskia-0.10.2/paskia/frontend-build/auth/assets/reset-CkY9h28U.js +1 -0
  27. paskia-0.10.2/paskia/frontend-build/auth/assets/restricted-C9cJlHkd.js +1 -0
  28. paskia-0.10.2/paskia/frontend-build/auth/assets/theme-C2WysaSw.js +1 -0
  29. {paskia-0.10.0 → paskia-0.10.2}/paskia/frontend-build/auth/index.html +8 -7
  30. {paskia-0.10.0 → paskia-0.10.2}/paskia/frontend-build/auth/restricted/index.html +7 -6
  31. {paskia-0.10.0 → paskia-0.10.2}/paskia/frontend-build/int/forward/index.html +6 -6
  32. {paskia-0.10.0 → paskia-0.10.2}/paskia/frontend-build/int/reset/index.html +3 -3
  33. paskia-0.10.2/paskia/frontend-build/paskia.webp +0 -0
  34. paskia-0.10.2/paskia/util/__init__.py +0 -0
  35. paskia-0.10.2/paskia/util/apistructs.py +110 -0
  36. paskia-0.10.2/paskia/util/frontend.py +75 -0
  37. paskia-0.10.2/paskia/util/hostutil.py +75 -0
  38. paskia-0.10.2/paskia/util/htmlutil.py +47 -0
  39. paskia-0.10.2/paskia/util/passphrase.py +20 -0
  40. paskia-0.10.2/paskia/util/permutil.py +43 -0
  41. paskia-0.10.2/paskia/util/pow.py +45 -0
  42. paskia-0.10.2/paskia/util/querysafe.py +11 -0
  43. paskia-0.10.2/paskia/util/sessionutil.py +38 -0
  44. paskia-0.10.2/paskia/util/startupbox.py +103 -0
  45. paskia-0.10.2/paskia/util/timeutil.py +47 -0
  46. paskia-0.10.2/paskia/util/useragent.py +10 -0
  47. paskia-0.10.2/paskia/util/userinfo.py +63 -0
  48. paskia-0.10.2/paskia/util/vitedev.py +71 -0
  49. paskia-0.10.2/paskia/util/wordlist.py +54 -0
  50. paskia-0.10.0/paskia/frontend-build/auth/assets/AccessDenied-C29NZI95.css +0 -1
  51. paskia-0.10.0/paskia/frontend-build/auth/assets/AccessDenied-DAdzg_MJ.js +0 -12
  52. paskia-0.10.0/paskia/frontend-build/auth/assets/_plugin-vue_export-helper-D2l53SUz.js +0 -49
  53. paskia-0.10.0/paskia/frontend-build/auth/assets/_plugin-vue_export-helper-DYJ24FZK.css +0 -1
  54. paskia-0.10.0/paskia/frontend-build/auth/assets/admin-BeFvGyD6.js +0 -1
  55. paskia-0.10.0/paskia/frontend-build/auth/assets/auth-DvHf8hgy.js +0 -1
  56. paskia-0.10.0/paskia/frontend-build/auth/assets/forward-C86Jm_Uq.js +0 -1
  57. paskia-0.10.0/paskia/frontend-build/auth/assets/reset-D71FG0VL.js +0 -1
  58. paskia-0.10.0/paskia/frontend-build/auth/assets/restricted-CW0drE_k.js +0 -1
  59. {paskia-0.10.0 → paskia-0.10.2}/.gitignore +0 -0
  60. {paskia-0.10.0 → paskia-0.10.2}/paskia/__init__.py +0 -0
  61. {paskia-0.10.0 → paskia-0.10.2}/paskia/aaguid/__init__.py +0 -0
  62. {paskia-0.10.0 → paskia-0.10.2}/paskia/aaguid/combined_aaguid.json +0 -0
  63. {paskia-0.10.0 → paskia-0.10.2}/paskia/authsession.py +0 -0
  64. {paskia-0.10.0 → paskia-0.10.2}/paskia/config.py +0 -0
  65. {paskia-0.10.0 → paskia-0.10.2}/paskia/db/jsonl.py +0 -0
  66. {paskia-0.10.0 → paskia-0.10.2}/paskia/db/logging.py +0 -0
  67. {paskia-0.10.0 → paskia-0.10.2}/paskia/db/migrations.py +0 -0
  68. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/__init__.py +0 -0
  69. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/admin.py +0 -0
  70. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/api.py +0 -0
  71. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/auth_host.py +0 -0
  72. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/logging.py +0 -0
  73. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/remote.py +0 -0
  74. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/reset.py +0 -0
  75. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/response.py +0 -0
  76. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/session.py +0 -0
  77. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/ws.py +0 -0
  78. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/wschat.py +0 -0
  79. {paskia-0.10.0 → paskia-0.10.2}/paskia/fastapi/wsutil.py +0 -0
  80. {paskia-0.10.0 → paskia-0.10.2}/paskia/frontend-build/auth/assets/helpers-DzjFIx78.js +0 -0
  81. {paskia-0.10.0 → paskia-0.10.2}/paskia/frontend-build/auth/assets/reset-B8PlNXuP.css +0 -0
  82. {paskia-0.10.0 → paskia-0.10.2}/paskia/globals.py +0 -0
  83. {paskia-0.10.0 → paskia-0.10.2}/paskia/migrate/__init__.py +0 -0
  84. {paskia-0.10.0 → paskia-0.10.2}/paskia/migrate/sql.py +0 -0
  85. {paskia-0.10.0 → paskia-0.10.2}/paskia/remoteauth.py +0 -0
  86. {paskia-0.10.0 → paskia-0.10.2}/paskia/sansio.py +0 -0
  87. {paskia-0.10.0 → paskia-0.10.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paskia
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: Passkey Auth made easy: all sites and APIs can be guarded even without any changes on the protected site.
5
5
  Project-URL: Homepage, https://git.zi.fi/LeoVasanko/paskia
6
6
  Project-URL: Repository, https://github.com/LeoVasanko/paskia
@@ -31,6 +31,8 @@ Description-Content-Type: text/markdown
31
31
 
32
32
  # Paskia
33
33
 
34
+ ![Screenshot](https://git.zi.fi/leovasanko/paskia/raw/main/docs/screenshots/forbidden-light.webp)
35
+
34
36
  An easy to install passkey-based authentication service that protects any web application with strong passwordless login.
35
37
 
36
38
  ## What is Paskia?
@@ -58,12 +60,12 @@ Single Sign-On (SSO): Users register once and authenticate across all applicatio
58
60
  Install [UV](https://docs.astral.sh/uv/getting-started/installation/) and run:
59
61
 
60
62
  ```fish
61
- uvx paskia serve --rp-id example.com
63
+ uvx paskia --rp-id example.com
62
64
  ```
63
65
 
64
- On the first run it downloads the software and prints a registration link for the Admin. The server will start up on [localhost:4401](http://localhost:4401) *for authentication required*, serving for `*.example.com`. If you are going to be connecting `localhost` directly, for testing, leave out the rp-id.
66
+ On the first run it downloads the software and prints a registration link for the Admin. The server starts on [localhost:4401](http://localhost:4401), serving authentication for `*.example.com`. For local testing, leave out `--rp-id`.
65
67
 
66
- Otherwise you will need a web server such as [Caddy](https://caddyserver.com/) to serve HTTPS on your actual domain names and proxy requests to Paskia and your backend apps (see documentation below).
68
+ For production you need a web server such as [Caddy](https://caddyserver.com/) to serve HTTPS on your actual domain names and proxy requests to Paskia and your backend apps (see documentation below).
67
69
 
68
70
  For a permanent install of `paskia` CLI command, not needing `uvx`:
69
71
 
@@ -73,19 +75,20 @@ uv tool install paskia
73
75
 
74
76
  ## Configuration
75
77
 
76
- There is no config file. Pass only the options on CLI:
78
+ There is no config file. All settings are passed as CLI options:
77
79
 
78
80
  ```text
79
- paskia serve [options]
81
+ paskia [options]
82
+ paskia reset [user] # Generate passkey reset link
80
83
  ```
81
84
 
82
85
  | Option | Description | Default |
83
86
  |--------|-------------|---------|
84
- | Listen address | One of *host***:***port* (default all hosts, port 4401) or **unix:***path***/paskia.socket** (Unix socket) | **localhost:4401** |
85
- | --rp-id *domain* | Main/top domain | **localhost** |
86
- | --rp-name *"text"* | Name of your company or site | Same as rp-id |
87
- | --origin *url* | Explicitly list the domain names served | **https://**_rp-id_ |
88
- | --auth-host *domain* | Dedicated authentication site (e.g., **auth.example.com**) | **Unspecified:** we use **/auth/** on **every** site under rp-id.|
87
+ | -l, --listen *endpoint* | Listen address: *host*:*port*, :*port* (all interfaces), or */path.sock* | **localhost:4401** |
88
+ | --rp-id *domain* | Main/top domain for passkeys | **localhost** |
89
+ | --rp-name *"text"* | Name shown during passkey registration | Same as rp-id |
90
+ | --origin *url* | Restrict allowed origins for WebSocket auth (repeatable) | All under rp-id |
91
+ | --auth-host *url* | Dedicated authentication site, e.g. **auth.example.com** | Use **/auth/** path on each site |
89
92
 
90
93
  ## Further Documentation
91
94
 
@@ -1,5 +1,7 @@
1
1
  # Paskia
2
2
 
3
+ ![Screenshot](https://git.zi.fi/leovasanko/paskia/raw/main/docs/screenshots/forbidden-light.webp)
4
+
3
5
  An easy to install passkey-based authentication service that protects any web application with strong passwordless login.
4
6
 
5
7
  ## What is Paskia?
@@ -27,12 +29,12 @@ Single Sign-On (SSO): Users register once and authenticate across all applicatio
27
29
  Install [UV](https://docs.astral.sh/uv/getting-started/installation/) and run:
28
30
 
29
31
  ```fish
30
- uvx paskia serve --rp-id example.com
32
+ uvx paskia --rp-id example.com
31
33
  ```
32
34
 
33
- On the first run it downloads the software and prints a registration link for the Admin. The server will start up on [localhost:4401](http://localhost:4401) *for authentication required*, serving for `*.example.com`. If you are going to be connecting `localhost` directly, for testing, leave out the rp-id.
35
+ On the first run it downloads the software and prints a registration link for the Admin. The server starts on [localhost:4401](http://localhost:4401), serving authentication for `*.example.com`. For local testing, leave out `--rp-id`.
34
36
 
35
- Otherwise you will need a web server such as [Caddy](https://caddyserver.com/) to serve HTTPS on your actual domain names and proxy requests to Paskia and your backend apps (see documentation below).
37
+ For production you need a web server such as [Caddy](https://caddyserver.com/) to serve HTTPS on your actual domain names and proxy requests to Paskia and your backend apps (see documentation below).
36
38
 
37
39
  For a permanent install of `paskia` CLI command, not needing `uvx`:
38
40
 
@@ -42,19 +44,20 @@ uv tool install paskia
42
44
 
43
45
  ## Configuration
44
46
 
45
- There is no config file. Pass only the options on CLI:
47
+ There is no config file. All settings are passed as CLI options:
46
48
 
47
49
  ```text
48
- paskia serve [options]
50
+ paskia [options]
51
+ paskia reset [user] # Generate passkey reset link
49
52
  ```
50
53
 
51
54
  | Option | Description | Default |
52
55
  |--------|-------------|---------|
53
- | Listen address | One of *host***:***port* (default all hosts, port 4401) or **unix:***path***/paskia.socket** (Unix socket) | **localhost:4401** |
54
- | --rp-id *domain* | Main/top domain | **localhost** |
55
- | --rp-name *"text"* | Name of your company or site | Same as rp-id |
56
- | --origin *url* | Explicitly list the domain names served | **https://**_rp-id_ |
57
- | --auth-host *domain* | Dedicated authentication site (e.g., **auth.example.com**) | **Unspecified:** we use **/auth/** on **every** site under rp-id.|
56
+ | -l, --listen *endpoint* | Listen address: *host*:*port*, :*port* (all interfaces), or */path.sock* | **localhost:4401** |
57
+ | --rp-id *domain* | Main/top domain for passkeys | **localhost** |
58
+ | --rp-name *"text"* | Name shown during passkey registration | Same as rp-id |
59
+ | --origin *url* | Restrict allowed origins for WebSocket auth (repeatable) | All under rp-id |
60
+ | --auth-host *url* | Dedicated authentication site, e.g. **auth.example.com** | Use **/auth/** path on each site |
58
61
 
59
62
  ## Further Documentation
60
63
 
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.10.0'
32
- __version_tuple__ = version_tuple = (0, 10, 0)
31
+ __version__ = version = '0.10.2'
32
+ __version_tuple__ = version_tuple = (0, 10, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -15,18 +15,18 @@ from paskia.util import hostutil, passphrase
15
15
  logger = logging.getLogger(__name__)
16
16
 
17
17
  # Shared log message template for admin reset links
18
- ADMIN_RESET_MESSAGE = """\
19
- %s
20
-
18
+ ADMIN_RESET_MESSAGE = """
21
19
  👤 Admin %s
22
20
  - Use this link to register a Passkey for the admin user!
23
21
  """
24
22
 
25
23
 
26
- def _log_reset_link(message: str, passphrase: str) -> str:
24
+ def _log_reset_link(passphrase: str, message: str | None = None) -> str:
27
25
  """Log a reset link message and return the URL."""
28
26
  reset_link = hostutil.reset_link_url(passphrase)
29
- logger.info(ADMIN_RESET_MESSAGE, message, reset_link)
27
+ if message:
28
+ logger.info(message)
29
+ logger.info(ADMIN_RESET_MESSAGE, reset_link)
30
30
  return reset_link
31
31
 
32
32
 
@@ -41,7 +41,7 @@ async def bootstrap_system() -> None:
41
41
  reset_passphrase = db.bootstrap()
42
42
 
43
43
  # Log the reset link (this is separate from the transaction log)
44
- _log_reset_link("✅ Bootstrap completed!", reset_passphrase)
44
+ _log_reset_link(reset_passphrase, "✅ Bootstrap completed!")
45
45
 
46
46
 
47
47
  async def check_admin_credentials() -> bool:
@@ -72,6 +72,7 @@ async def check_admin_credentials() -> bool:
72
72
 
73
73
  if not db.get_user_credential_ids(admin_user.uuid):
74
74
  # Admin exists but has no credentials, create reset link
75
+ logger.info("⚠️ Admin user has no credentials!")
75
76
 
76
77
  token = passphrase.generate()
77
78
  expiry = authsession.reset_expires()
@@ -81,7 +82,7 @@ async def check_admin_credentials() -> bool:
81
82
  expiry=expiry,
82
83
  token_type="admin registration",
83
84
  )
84
- _log_reset_link("⚠️ Admin user has no credentials!", token)
85
+ _log_reset_link(token)
85
86
  return True
86
87
 
87
88
  return False
@@ -64,6 +64,7 @@ from paskia.db.operations import (
64
64
  update_user_display_name,
65
65
  update_user_role,
66
66
  update_user_role_in_organization,
67
+ update_user_theme,
67
68
  )
68
69
  from paskia.db.structs import (
69
70
  DB,
@@ -147,4 +148,5 @@ __all__ = [
147
148
  "update_user_display_name",
148
149
  "update_user_role",
149
150
  "update_user_role_in_organization",
151
+ "update_user_theme",
150
152
  ]
@@ -74,21 +74,18 @@ async def start_background():
74
74
  _logger.debug("Background task in different event loop, restarting")
75
75
  _background_task = None
76
76
  else:
77
- # Task is running in the same event loop - this is an error
78
- raise RuntimeError(
79
- "Background task is already running. "
80
- "start_background() must not be called multiple times in the same event loop."
77
+ # Task is already running in same loop - idempotent, just return
78
+ # This happens with dual IPv4+IPv6 endpoints sharing the same process
79
+ _logger.debug(
80
+ "Background task already running in same loop, skipping"
81
81
  )
82
- except RuntimeError:
83
- raise # Re-raise RuntimeError from above
82
+ return
84
83
  except Exception as e:
85
84
  _logger.debug("Error checking background task loop: %s, restarting", e)
86
85
  _background_task = None
87
86
 
88
87
  if _background_task is None:
89
88
  _background_task = asyncio.create_task(_background_loop())
90
- else:
91
- _logger.debug("Background task already running: %s", _background_task)
92
89
 
93
90
 
94
91
  async def stop_background():
@@ -352,6 +352,23 @@ def update_user_display_name(
352
352
  _db.users[uuid].display_name = display_name
353
353
 
354
354
 
355
+ def update_user_theme(
356
+ uuid: UUID,
357
+ theme: str,
358
+ *,
359
+ ctx: SessionContext | None = None,
360
+ ) -> None:
361
+ """Update user theme preference ('' for auto, 'light', 'dark')."""
362
+ if isinstance(uuid, str):
363
+ uuid = UUID(uuid)
364
+ if uuid not in _db.users:
365
+ raise ValueError(f"User {uuid} not found")
366
+ if theme not in ("", "light", "dark"):
367
+ raise ValueError(f"Invalid theme: {theme}")
368
+ with _db.transaction("update_user_theme", ctx):
369
+ _db.users[uuid].theme = theme
370
+
371
+
355
372
  def update_user_role(
356
373
  uuid: UUID,
357
374
  role_uuid: UUID,
@@ -147,10 +147,10 @@ class Role(msgspec.Struct, dict=True, omit_defaults=True):
147
147
  return role
148
148
 
149
149
 
150
- class User(msgspec.Struct, dict=True):
150
+ class User(msgspec.Struct, dict=True, omit_defaults=True):
151
151
  """User data structure.
152
152
 
153
- Mutable fields: display_name, role_uuid, last_seen, visits
153
+ Mutable fields: display_name, role_uuid, last_seen, visits, theme
154
154
  Immutable fields: created_at (set at creation, never modified)
155
155
  uuid is derived from created_at using uuid7.
156
156
  """
@@ -160,6 +160,7 @@ class User(msgspec.Struct, dict=True):
160
160
  created_at: datetime
161
161
  last_seen: datetime | None = None
162
162
  visits: int = 0
163
+ theme: str = "" # "" or "auto" = OS default, "light", "dark"
163
164
 
164
165
  def __post_init__(self):
165
166
  if not hasattr(self, "uuid"):
@@ -21,10 +21,10 @@ DEFAULT_PORT = 4401
21
21
 
22
22
  EPILOG = """\
23
23
  Examples:
24
- paskia # localhost:4401
25
- paskia :8080 # All interfaces, port 8080
26
- paskia unix:/tmp/paskia.sock
27
- paskia reset [user] # Generate passkey reset link
24
+ paskia # localhost:4401
25
+ paskia -l :8080 # All interfaces, port 8080
26
+ paskia -l /tmp/paskia.sock # Unix socket
27
+ paskia reset [user] # Generate passkey reset link
28
28
  """
29
29
 
30
30
 
@@ -81,32 +81,40 @@ def main():
81
81
  epilog=EPILOG,
82
82
  )
83
83
 
84
- # Primary argument: either host:port or "reset" subcommand
84
+ # Subcommand for reset
85
85
  parser.add_argument(
86
- "hostport",
86
+ "command",
87
87
  nargs="?",
88
- help=(
89
- "Endpoint (default: localhost:4401). Forms: host[:port] | :port | "
90
- "[ipv6][:port] | ipv6 | unix:/path.sock | 'reset' for credential reset"
91
- ),
88
+ help="Command: 'reset' for credential reset, or omit to run server",
92
89
  )
93
90
  parser.add_argument(
94
91
  "reset_query",
95
92
  nargs="?",
96
93
  help="For 'reset' command: user UUID or substring of display name",
97
94
  )
95
+ parser.add_argument(
96
+ "-l",
97
+ "--listen",
98
+ metavar="LISTEN",
99
+ help=(
100
+ "Endpoint to listen on (default: localhost:4401). "
101
+ "Forms: host:port port :port [ipv6]:port unix:path /path.sock"
102
+ ),
103
+ )
98
104
  add_common_options(parser)
99
105
 
100
106
  args = parser.parse_args()
101
107
 
102
- # Detect "reset" subcommand (first positional is "reset")
103
- is_reset = args.hostport == "reset"
108
+ # Detect "reset" subcommand
109
+ is_reset = args.command == "reset"
104
110
 
105
111
  if is_reset:
106
112
  endpoints = []
107
113
  else:
114
+ if args.command is not None:
115
+ raise SystemExit(f"Unknown command: {args.command}")
108
116
  # Parse endpoint using fastapi_vue.hostutil
109
- endpoints = parse_endpoint(args.hostport, DEFAULT_PORT)
117
+ endpoints = parse_endpoint(args.listen, DEFAULT_PORT)
110
118
 
111
119
  # Extract host/port/uds from first endpoint for config display and site_url
112
120
  ep = endpoints[0] if endpoints else {}
@@ -80,6 +80,9 @@ async def verify(
80
80
  mode="login",
81
81
  clear_session=True,
82
82
  )
83
+ # User's theme preference for iframe (only if explicitly set)
84
+ user_theme = ctx.user.theme if ctx.user.theme else None
85
+
83
86
  # Check max_age requirement if specified
84
87
  if max_age:
85
88
  try:
@@ -88,6 +91,7 @@ async def verify(
88
91
  status_code=401,
89
92
  detail="Additional authentication required",
90
93
  mode="reauth",
94
+ theme=user_theme,
91
95
  )
92
96
  except ValueError as e:
93
97
  # Invalid max_age format - log but don't fail the request
@@ -104,7 +108,10 @@ async def verify(
104
108
  ctx, perm, missing, require_all=(match == permutil.has_all)
105
109
  )
106
110
  raise AuthException(
107
- status_code=403, mode="forbidden", detail="Permission required"
111
+ status_code=403,
112
+ mode="forbidden",
113
+ detail="Permission required",
114
+ theme=user_theme,
108
115
  )
109
116
 
110
117
  return ctx
@@ -26,6 +26,7 @@ _access_logger = logging.getLogger("paskia.access")
26
26
  frontend = Frontend(
27
27
  Path(__file__).parent.parent / "frontend-build",
28
28
  cached=["/auth/assets/"],
29
+ favicon="/paskia.webp",
29
30
  )
30
31
 
31
32
 
@@ -135,6 +136,11 @@ async def examples_page():
135
136
  return FileResponse(index_file, media_type="text/html")
136
137
 
137
138
 
139
+ # Frontend static files - must be before /{token} catch-all routes
140
+ # (actual routes registered during lifespan after frontend.load())
141
+ frontend.route(app, "/")
142
+
143
+
138
144
  # Note: this catch-all handler must be the last route defined
139
145
  @app.get("/{token}")
140
146
  @app.get("/auth/{token}")
@@ -147,7 +153,3 @@ async def token_link(token: str):
147
153
  raise HTTPException(status_code=404)
148
154
 
149
155
  return Response(*await vitedev.read("/int/reset/index.html"))
150
-
151
-
152
- # Final catch-all route for frontend files (keep at end of file)
153
- frontend.route(app, "/")
@@ -57,6 +57,28 @@ async def user_update_display_name(
57
57
  return {"status": "ok"}
58
58
 
59
59
 
60
+ @app.patch("/theme")
61
+ async def user_update_theme(
62
+ request: Request,
63
+ payload: dict = Body(...),
64
+ auth=AUTH_COOKIE,
65
+ ):
66
+ if not auth:
67
+ raise authz.AuthException(
68
+ status_code=401, detail="Authentication Required", mode="login"
69
+ )
70
+ ctx = db.data().session_ctx(auth, request.headers.get("host"))
71
+ if not ctx:
72
+ raise authz.AuthException(
73
+ status_code=401, detail="Session expired", mode="login"
74
+ )
75
+ theme = payload.get("theme", "")
76
+ if theme not in ("", "light", "dark"):
77
+ raise HTTPException(status_code=400, detail="Invalid theme")
78
+ db.update_user_theme(ctx.user.uuid, theme, ctx=ctx)
79
+ return {"status": "ok"}
80
+
81
+
60
82
  @app.post("/logout-all")
61
83
  async def api_logout_all(request: Request, response: Response, auth=AUTH_COOKIE):
62
84
  if not auth:
@@ -4,13 +4,14 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Admin</title>
7
- <script type="module" crossorigin src="/auth/assets/admin-BeFvGyD6.js"></script>
8
- <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-D2l53SUz.js">
7
+ <script type="module" crossorigin src="/auth/assets/admin-CZKsX1OI.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-DJsHCwvl.js">
9
+ <link rel="modulepreload" crossorigin href="/auth/assets/theme-C2WysaSw.js">
9
10
  <link rel="modulepreload" crossorigin href="/auth/assets/helpers-DzjFIx78.js">
10
- <link rel="modulepreload" crossorigin href="/auth/assets/AccessDenied-DAdzg_MJ.js">
11
- <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-DYJ24FZK.css">
12
- <link rel="stylesheet" crossorigin href="/auth/assets/AccessDenied-C29NZI95.css">
13
- <link rel="stylesheet" crossorigin href="/auth/assets/admin-CmNtuH3s.css">
11
+ <link rel="modulepreload" crossorigin href="/auth/assets/AccessDenied-Licr0tqA.js">
12
+ <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-DUBf8-iM.css">
13
+ <link rel="stylesheet" crossorigin href="/auth/assets/AccessDenied-CVQZxSIL.css">
14
+ <link rel="stylesheet" crossorigin href="/auth/assets/admin-B1H4YqM_.css">
14
15
  </head>
15
16
  <body>
16
17
  <div id="admin-app"></div>
@@ -0,0 +1 @@
1
+ .breadcrumbs[data-v-6344dbb8]{margin:.25rem 0 .5rem;line-height:1.2;color:var(--color-text-muted)}.breadcrumbs ol[data-v-6344dbb8]{list-style:none;padding:0;margin:0;display:flex;flex-wrap:wrap;align-items:center;gap:.25rem}.breadcrumbs li[data-v-6344dbb8]{display:inline-flex;align-items:center;gap:.25rem;font-size:.9rem}.breadcrumbs a[data-v-6344dbb8]{text-decoration:none;color:var(--color-link);padding:0 .25rem;border-radius:4px;transition:color .2s ease,background .2s ease}.breadcrumbs .sep[data-v-6344dbb8]{color:var(--color-text-muted);margin:0}.btn-card-delete{display:none}.credential-item:focus .btn-card-delete{display:block}.user-info.has-extra[data-v-ce373d6c]{grid-template-columns:auto 1fr 2fr;grid-template-areas:"heading heading extra" "org org extra" "label1 value1 extra" "label2 value2 extra" "label3 value3 extra"}.user-info[data-v-ce373d6c]:not(.has-extra){grid-template-columns:auto 1fr;grid-template-areas:"heading heading" "org org" "label1 value1" "label2 value2" "label3 value3"}@media(max-width:720px){.user-info.has-extra[data-v-ce373d6c]{grid-template-columns:auto 1fr;grid-template-areas:"heading heading" "org org" "label1 value1" "label2 value2" "label3 value3" "extra extra"}}.user-name-heading[data-v-ce373d6c]{grid-area:heading;display:flex;align-items:center;flex-wrap:wrap;margin:0 0 .25rem}.org-role-sub[data-v-ce373d6c]{grid-area:org;display:flex;flex-direction:column;margin:-.15rem 0 .25rem}.org-line[data-v-ce373d6c]{font-size:.7rem;font-weight:600;line-height:1.1;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.05em}.role-line[data-v-ce373d6c]{font-size:.65rem;color:var(--color-text-muted);line-height:1.1}.info-label[data-v-ce373d6c]:nth-of-type(1){grid-area:label1}.info-value[data-v-ce373d6c]:nth-of-type(2){grid-area:value1}.info-label[data-v-ce373d6c]:nth-of-type(3){grid-area:label2}.info-value[data-v-ce373d6c]:nth-of-type(4){grid-area:value2}.info-label[data-v-ce373d6c]:nth-of-type(5){grid-area:label3}.info-value[data-v-ce373d6c]:nth-of-type(6){grid-area:value3}.user-info-extra[data-v-ce373d6c]{grid-area:extra;padding-left:2rem;border-left:1px solid var(--color-border)}.user-name-row[data-v-ce373d6c]{display:inline-flex;align-items:center;gap:.35rem;max-width:100%}.user-name-row.editing[data-v-ce373d6c]{flex:1 1 auto}.display-name[data-v-ce373d6c]{font-weight:600;font-size:1.05em;line-height:1.2;max-width:14ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.name-input[data-v-ce373d6c]{width:auto;flex:1 1 140px;min-width:120px;padding:6px 8px;font-size:.9em;border:1px solid var(--color-border-strong);border-radius:6px;background:var(--color-surface);color:var(--color-text)}.user-name-heading .name-input[data-v-ce373d6c]{width:auto}.name-input[data-v-ce373d6c]:focus{outline:none;border-color:var(--color-accent);box-shadow:var(--focus-ring)}.mini-btn[data-v-ce373d6c]{width:auto;padding:4px 6px;margin:0;font-size:.75em;line-height:1;cursor:pointer}.mini-btn[data-v-ce373d6c]:hover:not(:disabled){background:var(--color-accent-soft);color:var(--color-accent)}.mini-btn[data-v-ce373d6c]:active:not(:disabled){transform:translateY(1px)}.mini-btn[data-v-ce373d6c]:disabled{opacity:.5;cursor:not-allowed}@media(max-width:720px){.user-info-extra[data-v-ce373d6c]{padding-left:0;padding-top:1rem;margin-top:1rem;border-left:none;border-top:1px solid var(--color-border)}}dialog[data-v-2ebcbb0a]{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-xl);padding:calc(var(--space-lg) - var(--space-xs));max-width:500px;width:min(500px,90vw);max-height:90vh;overflow-y:auto;position:fixed;inset:0;margin:auto;height:fit-content}dialog[data-v-2ebcbb0a]::backdrop{background:transparent;backdrop-filter:blur(.1rem) brightness(.7);-webkit-backdrop-filter:blur(.1rem) brightness(.7)}dialog[data-v-2ebcbb0a] .modal-title,dialog[data-v-2ebcbb0a] h3{margin:0 0 var(--space-md);font-size:1.25rem;font-weight:600;color:var(--color-heading)}dialog[data-v-2ebcbb0a] form{display:flex;flex-direction:column;gap:var(--space-md)}dialog[data-v-2ebcbb0a] .modal-form{display:flex;flex-direction:column;gap:var(--space-md)}dialog[data-v-2ebcbb0a] .modal-form label{display:flex;flex-direction:column;gap:var(--space-xs);font-weight:500}dialog[data-v-2ebcbb0a] .modal-form input,dialog[data-v-2ebcbb0a] .modal-form textarea{padding:var(--space-md);border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);font-size:1rem;line-height:1.4;min-height:2.5rem}dialog[data-v-2ebcbb0a] .modal-form input:focus,dialog[data-v-2ebcbb0a] .modal-form textarea:focus{outline:none;border-color:var(--color-accent);box-shadow:0 0 0 2px #c7d2fe}dialog[data-v-2ebcbb0a] .modal-actions{display:flex;justify-content:flex-end;gap:var(--space-sm);margin-top:var(--space-md);margin-bottom:var(--space-xs)}.name-edit-form[data-v-b73321cf]{display:flex;flex-direction:column;gap:var(--space-md)}.error[data-v-b73321cf]{color:var(--color-danger-text)}.small[data-v-b73321cf]{font-size:.9rem}.qr-display[data-v-727427c4]{display:flex;flex-direction:column;align-items:center;gap:.75rem}.qr-section[data-v-727427c4]{display:flex;flex-direction:column;align-items:center;gap:.5rem}.qr-link[data-v-727427c4]{display:flex;flex-direction:column;align-items:center;text-decoration:none;color:inherit;border-radius:var(--radius-sm, 6px);overflow:hidden}.qr-code[data-v-727427c4]{display:block;width:200px;height:200px;max-width:100%;object-fit:contain;border-radius:var(--radius-sm, 6px);background:#fff;cursor:pointer}.link-text[data-v-727427c4]{padding:.5rem;font-size:.75rem;color:var(--color-text-muted);font-family:monospace;word-break:break-all;line-height:1.2;transition:color .2s ease}.qr-link:hover .link-text[data-v-727427c4]{color:var(--color-text)}dialog[data-v-26360b56]{border:none;background:transparent;padding:0;max-width:none;width:fit-content;height:fit-content;position:fixed;inset:0;margin:auto}dialog[data-v-26360b56]::backdrop{-webkit-backdrop-filter:blur(.2rem) brightness(.5);backdrop-filter:blur(.2rem) brightness(.5)}.icon-btn[data-v-26360b56]{background:none;border:none;cursor:pointer;font-size:1rem;opacity:.6}.icon-btn[data-v-26360b56]:hover{opacity:1}.reg-header-row[data-v-26360b56]{display:flex;justify-content:space-between;align-items:center;gap:.75rem;margin-bottom:.75rem}.reg-title[data-v-26360b56]{margin:0;font-size:1.25rem;font-weight:600}.device-dialog[data-v-26360b56]{background:var(--color-surface);padding:1.25rem 1.25rem 1rem;border-radius:var(--radius-md);max-width:480px;width:100%;box-shadow:0 6px 28px #00000040}.reg-help[data-v-26360b56]{margin:.5rem 0 .75rem;font-size:.85rem;line-height:1.4;text-align:center;color:var(--color-text-muted)}.reg-actions[data-v-26360b56]{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.expiry-note[data-v-26360b56]{font-size:.75rem;color:var(--color-text-muted);text-align:center;margin-top:.75rem}.loading-container[data-v-130f5abf]{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;gap:1rem}.loading-spinner[data-v-130f5abf]{width:40px;height:40px;border:4px solid var(--color-border);border-top:4px solid var(--color-primary);border-radius:50%;animation:spin-130f5abf 1s linear infinite}@keyframes spin-130f5abf{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-container p[data-v-130f5abf]{color:var(--color-text-muted);margin:0}.message-container[data-v-a7b258e7]{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;padding:2rem}.message-content[data-v-a7b258e7]{text-align:center;max-width:480px}.message-content h2[data-v-a7b258e7]{margin:0 0 1rem;color:var(--color-heading)}.message-content .error-detail[data-v-a7b258e7]{margin:0 0 1.5rem;color:var(--color-text-muted)}.message-content .button-row[data-v-a7b258e7]{display:flex;gap:.75rem;justify-content:center}
@@ -0,0 +1,8 @@
1
+ import{k as F,M as At,P as Rt,Q as Yt,R as Qt,S as ue,T as Tt,U as Gt,V as Wt,W as Zt,X as Xt,n as xe,Y as en,m as x,w as Oe,z as Q,l as tn,Z as nn,$ as on,u as U,b as T,c as M,d as v,t as D,e as se,g as L,_ as Y,F as oe,r as ce,A as z,E as Mt,j as de,a0 as Nt,a1 as Pt,a2 as Dt,a3 as Lt,o as qt,a4 as rn,a as Ft,B as sn,h as $e,i as an,a5 as ln,L as un,q as cn}from"./_plugin-vue_export-helper-DJsHCwvl.js";import{f as G,h as ae,g as We}from"./helpers-DzjFIx78.js";import{u as dn}from"./theme-C2WysaSw.js";let Ut;const fe=e=>Ut=e,Kt=Symbol();function je(e){return e&&typeof e=="object"&&Object.prototype.toString.call(e)==="[object Object]"&&typeof e.toJSON!="function"}var re;(function(e){e.direct="direct",e.patchObject="patch object",e.patchFunction="patch function"})(re||(re={}));function Sr(){const e=Rt(!0),s=e.run(()=>F({}));let r=[],t=[];const n=At({install(o){fe(n),n._a=o,o.provide(Kt,n),o.config.globalProperties.$pinia=n,t.forEach(i=>r.push(i)),t=[]},use(o){return this._a?r.push(o):t.push(o),this},_p:r,_a:null,_e:e,_s:new Map,state:s});return n}const Vt=()=>{};function Ze(e,s,r,t=Vt){e.add(s);const n=()=>{e.delete(s)&&t()};return!r&&Wt()&&Zt(n),n}function ee(e,...s){e.forEach(r=>{r(...s)})}const fn=e=>e(),Xe=Symbol(),we=Symbol();function ze(e,s){e instanceof Map&&s instanceof Map?s.forEach((r,t)=>e.set(t,r)):e instanceof Set&&s instanceof Set&&s.forEach(e.add,e);for(const r in s){if(!s.hasOwnProperty(r))continue;const t=s[r],n=e[r];je(n)&&je(t)&&e.hasOwnProperty(r)&&!ue(t)&&!Tt(t)?e[r]=ze(n,t):e[r]=t}return e}const hn=Symbol();function gn(e){return!je(e)||!Object.prototype.hasOwnProperty.call(e,hn)}const{assign:J}=Object;function mn(e){return!!(ue(e)&&e.effect)}function yn(e,s,r,t){const{state:n,actions:o,getters:i}=s,l=r.state.value[e];let a;function u(){l||(r.state.value[e]=n?n():{});const d=en(r.state.value[e]);return J(d,o,Object.keys(i||{}).reduce((p,b)=>(p[b]=At(x(()=>{fe(r);const f=r._s.get(e);return i[b].call(f,f)})),p),{}))}return a=Ht(e,u,s,r,t,!0),a}function Ht(e,s,r={},t,n,o){let i;const l=J({actions:{}},r),a={deep:!0};let u,d,p=new Set,b=new Set,f;const w=t.state.value[e];!o&&!w&&(t.state.value[e]={});let h;function _(m){let g;u=d=!1,typeof m=="function"?(m(t.state.value[e]),g={type:re.patchFunction,storeId:e,events:f}):(ze(t.state.value[e],m),g={type:re.patchObject,payload:m,storeId:e,events:f});const E=h=Symbol();xe().then(()=>{h===E&&(u=!0)}),d=!0,ee(p,g,t.state.value[e])}const A=o?function(){const{state:g}=r,E=g?g():{};this.$patch(k=>{J(k,E)})}:Vt;function P(){i.stop(),p.clear(),b.clear(),t._s.delete(e)}const c=(m,g="")=>{if(Xe in m)return m[we]=g,m;const E=function(){fe(t);const k=Array.from(arguments),B=new Set,I=new Set;function N(q){B.add(q)}function V(q){I.add(q)}ee(b,{args:k,name:E[we],store:R,after:N,onError:V});let K;try{K=m.apply(this&&this.$id===e?this:R,k)}catch(q){throw ee(I,q),q}return K instanceof Promise?K.then(q=>(ee(B,q),q)).catch(q=>(ee(I,q),Promise.reject(q))):(ee(B,K),K)};return E[Xe]=!0,E[we]=g,E},S={_p:t,$id:e,$onAction:Ze.bind(null,b),$patch:_,$reset:A,$subscribe(m,g={}){const E=Ze(p,m,g.detached,()=>k()),k=i.run(()=>Oe(()=>t.state.value[e],B=>{(g.flush==="sync"?d:u)&&m({storeId:e,type:re.direct,events:f},B)},J({},a,g)));return E},$dispose:P},R=Xt(S);t._s.set(e,R);const y=(t._a&&t._a.runWithContext||fn)(()=>t._e.run(()=>(i=Rt()).run(()=>s({action:c}))));for(const m in y){const g=y[m];if(ue(g)&&!mn(g)||Tt(g))o||(w&&gn(g)&&(ue(g)?g.value=w[m]:ze(g,w[m])),t.state.value[e][m]=g);else if(typeof g=="function"){const E=c(g,m);y[m]=E,l.actions[m]=g}}return J(R,y),J(Gt(R),y),Object.defineProperty(R,"$state",{get:()=>t.state.value[e],set:m=>{_(g=>{J(g,m)})}}),t._p.forEach(m=>{J(R,i.run(()=>m({store:R,app:t._a,pinia:t,options:l})))}),w&&o&&r.hydrate&&r.hydrate(R.$state,w),u=!0,d=!0,R}function pn(e,s,r){let t;const n=typeof s=="function";t=n?r:s;function o(i,l){const a=Qt();return i=i||(a?Yt(Kt,null):null),i&&fe(i),i=Ut,i._s.has(e)||(n?Ht(e,s,t,i):yn(e,t,i)),i._s.get(e)}return o.$id=e,o}const he=pn("auth",{state:()=>({userInfo:null,isLoading:!1,settings:null,currentView:"login",status:{message:"",type:"info",show:!1}}),getters:{},actions:{setLoading(e){this.isLoading=!!e},showMessage(e,s="info",r=null){const t=r??(s==="error"?5e3:3e3);this.status={message:e,type:s,show:!0},t>0&&setTimeout(()=>{this.status.show=!1},t)},async setSessionCookie(e){if(!e?.session_token)throw console.error("setSessionCookie called with missing session_token:",e),new Error("Authentication response missing session_token");return await Q("/auth/api/set-session",{method:"POST",headers:{Authorization:`Bearer ${e.session_token}`}})},async register(){this.isLoading=!0;try{const e=await on();return await this.setSessionCookie(e),await this.loadUserInfo(),this.selectView(),e}finally{this.isLoading=!1}},async authenticate(){this.isLoading=!0;try{const e=await nn();return await this.setSessionCookie(e),await this.loadUserInfo(),this.selectView(),e}finally{this.isLoading=!1}},selectView(){this.userInfo?this.currentView="profile":this.currentView="login"},async loadSettings(){this.settings=await tn()},async loadUserInfo(){try{this.userInfo=await Q("/auth/api/user-info",{method:"POST"}),dn(this.userInfo?.ctx),console.log("User info loaded:",this.userInfo)}catch(e){throw e.status===401||e.status===403?console.log("Authentication required:",e.message):this.showMessage(e.message||"Failed to load user info","error",5e3),e}},async deleteCredential(e){await Q(`/auth/api/user/credential/${e}`,{method:"DELETE"}),await this.loadUserInfo()},async terminateSession(e){try{if((await Q(`/auth/api/user/session/${e}`,{method:"DELETE"}))?.current_session_terminated){sessionStorage.clear(),location.reload();return}await this.loadUserInfo(),this.showMessage("Session terminated","success",2500)}catch(s){throw console.error("Terminate session error:",s),s}},async logout(){try{await Q("/auth/api/logout",{method:"POST"}),sessionStorage.clear(),location.reload()}catch(e){console.error("Logout error:",e),e.status!==401&&e.status!==403&&this.showMessage(e.message,"error")}},async logoutEverywhere(){try{await Q("/auth/api/user/logout-all",{method:"POST"}),sessionStorage.clear(),location.reload()}catch(e){console.error("Logout-all error:",e),e.status!==401&&e.status!==403&&this.showMessage(e.message,"error")}}}}),wn={key:0,class:"global-status",style:{display:"block"}},Er={__name:"StatusMessage",setup(e){const s=he();return(r,t)=>U(s).status.show?(T(),M("div",wn,[v("div",{class:se(["status",U(s).status.type])},D(U(s).status.message),3)])):L("",!0)}},bn=["href"],vn={key:0,class:"sep"},Cn={__name:"Breadcrumbs",props:{entries:{type:Array,default:()=>[]},showHome:{type:Boolean,default:!0},homeHref:{type:String,default:"/"}},setup(e,{expose:s}){const r=e,t=F(null),n=x(()=>r.showHome&&r.entries.length>0&&r.entries[0].href===r.homeHref?[{label:"🏠 "+r.entries[0].label,href:r.homeHref},...r.entries.slice(1)]:[...r.showHome?[{label:"🏠",href:r.homeHref}]:[],...r.entries]),o=x(()=>{const u=window.location.hash||window.location.pathname;for(let d=n.value.length-1;d>=0;d--){const p=n.value[d].href;if(p===u||p&&u.startsWith(p))return d}return n.value.length-1});function i(u){if(u.target===t.value){const d=t.value.querySelectorAll("a"),p=Math.min(o.value,d.length-1);d[p]&&d[p].focus()}}function l(u){const d=z(u);d&&(d==="left"||d==="right")&&(u.preventDefault(),Mt(t.value,u.target,d,{itemSelector:"a"}))}function a(){const u=t.value?.querySelectorAll("a");if(u?.length){const d=Math.min(o.value,u.length-1);u[d]?.focus()}}return s({focusCurrent:a}),(u,d)=>n.value.length>1?(T(),M("nav",{key:0,ref_key:"navRef",ref:t,class:"breadcrumbs","aria-label":"Breadcrumb",tabindex:"0",onFocusin:i,onKeydown:l},[v("ol",null,[(T(!0),M(oe,null,ce(n.value,(p,b)=>(T(),M("li",{key:b},[v("a",{href:p.href,tabindex:"-1"},D(p.label),9,bn),b<n.value.length-1?(T(),M("span",vn," — ")):L("",!0)]))),128))])],544)):L("",!0)}},_r=Y(Cn,[["__scopeId","data-v-6344dbb8"]]),Sn={key:0},En={key:1},_n=["onFocusin","onKeydown"],kn={class:"item-top"},Bn={class:"item-icon"},In=["src","alt"],An={key:1,class:"auth-emoji"},Rn={class:"item-title"},Tn={class:"item-actions"},Mn={key:0,class:"badge badge-current"},Nn={key:1,class:"badge badge-current"},Pn={key:2,class:"badge badge-current"},Dn=["onClick","disabled","title"],Ln={class:"item-details"},qn={class:"credential-dates"},Fn={class:"date-value"},Un={class:"date-value"},Kn={class:"date-value"},kr={__name:"CredentialList",props:{credentials:{type:Array,default:()=>[]},aaguidInfo:{type:Object,default:()=>({})},loading:{type:Boolean,default:!1},allowDelete:{type:Boolean,default:!1},hoveredCredentialUuid:{type:String,default:null},hoveredSessionCredentialUuid:{type:String,default:null},navigationDisabled:{type:Boolean,default:!1}},emits:["delete","credentialHover","navigate-out"],setup(e,{emit:s}){const r=e,t=s,n=f=>{t("credentialHover",f)},o=f=>{f.currentTarget.contains(f.relatedTarget)||t("credentialHover",null)},i=f=>{f.currentTarget.matches(":focus")||(f.currentTarget.focus(),f.stopPropagation())},l=(f,w)=>{Pt(f,()=>{r.allowDelete&&!w.is_current_session&&t("delete",w)})},a=f=>{if(r.navigationDisabled)return;const w=f.currentTarget;if(f.target===w){const h=w.querySelector(".credential-item");h&&h.focus()}},u=f=>{r.navigationDisabled||Dt(f,w=>t("navigate-out",w))},d=(f,w)=>{if(l(f,w),f.defaultPrevented||r.navigationDisabled)return;const h=z(f);if(h){f.preventDefault();const _=f.currentTarget.closest(".credential-list");Nt(_,f.currentTarget,h,{itemSelector:".credential-item"})==="boundary"&&t("navigate-out",h)}},p=f=>{const w=r.aaguidInfo?.[f.aaguid];return w?w.name:"Unknown Authenticator"},b=f=>{const w=r.aaguidInfo?.[f.aaguid];if(!w)return null;const _=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"icon_dark":"icon_light";return w[_]||null};return(f,w)=>(T(),M("div",{class:"credential-list",tabindex:"0",onFocusin:a,onKeydown:u},[e.loading?(T(),M("div",Sn,[...w[2]||(w[2]=[v("p",null,"Loading credentials...",-1)])])):e.credentials?.length?(T(!0),M(oe,{key:2},ce(e.credentials,h=>(T(),M("div",{key:h.credential,class:se(["credential-item",{"current-session":h.is_current_session&&!e.hoveredCredentialUuid&&!e.hoveredSessionCredentialUuid,"is-hovered":e.hoveredCredentialUuid===h.credential,"is-linked-session":e.hoveredSessionCredentialUuid===h.credential}]),tabindex:"-1",onMousedown:w[0]||(w[0]=de(()=>{},["prevent"])),onClickCapture:i,onFocusin:_=>n(h.credential),onFocusout:w[1]||(w[1]=_=>o(_)),onKeydown:_=>d(_,h)},[v("div",kn,[v("div",Bn,[b(h)?(T(),M("img",{key:0,src:b(h),alt:p(h),class:"auth-icon",width:"32",height:"32"},null,8,In)):(T(),M("span",An,"🔑"))]),v("h4",Rn,D(p(h)),1),v("div",Tn,[h.is_current_session&&!e.hoveredCredentialUuid&&!e.hoveredSessionCredentialUuid?(T(),M("span",Mn,"Current")):e.hoveredCredentialUuid===h.credential?(T(),M("span",Nn,"Selected")):e.hoveredSessionCredentialUuid===h.credential?(T(),M("span",Pn,"Linked")):L("",!0),e.allowDelete?(T(),M("button",{key:3,onClick:_=>f.$emit("delete",h),class:"btn-card-delete",disabled:h.is_current_session,title:h.is_current_session?"Cannot delete current session credential":"Delete passkey and terminate any linked sessions.",tabindex:"-1"},"❌",8,Dn)):L("",!0)])]),v("div",Ln,[v("div",qn,[w[4]||(w[4]=v("span",{class:"date-label"},"Created:",-1)),v("span",Fn,D(U(G)(h.created_at)),1),w[5]||(w[5]=v("span",{class:"date-label"},"Last used:",-1)),v("span",Un,D(U(G)(h.last_used)),1),w[6]||(w[6]=v("span",{class:"date-label"},"Last verified:",-1)),v("span",Kn,D(U(G)(h.last_verified)),1)])])],42,_n))),128)):(T(),M("div",En,[...w[3]||(w[3]=[v("p",null,"No passkeys found.",-1)])]))],32))}},Vn={class:"user-name-heading"},Hn={class:"user-name-row"},On=["title"],jn={key:0,class:"org-role-sub"},zn={key:0,class:"org-line"},xn={key:1,class:"role-line"},$n={class:"user-details"},Jn={class:"date-value"},Yn={class:"date-value"},Qn={class:"date-value"},Gn={key:1,class:"user-info-extra"},Wn={__name:"UserBasicInfo",props:{name:{type:String,required:!0},visits:{type:[Number,String],default:0},createdAt:{type:[String,Number,Date],default:null},lastSeen:{type:[String,Number,Date],default:null},updateEndpoint:{type:String,default:null},canEdit:{type:Boolean,default:!0},loading:{type:Boolean,default:!1},orgDisplayName:{type:String,default:""},roleName:{type:String,default:""}},emits:["saved","editName"],setup(e,{emit:s}){const r=e,t=s;he();const n=x(()=>!!r.name);return(o,i)=>n.value?(T(),M("div",{key:0,class:se(["user-info",{"has-extra":o.$slots.default}])},[v("h3",Vn,[i[1]||(i[1]=v("span",{class:"icon"},"👤",-1)),v("span",Hn,[v("span",{class:"display-name",title:e.name},D(e.name),9,On),e.canEdit&&e.updateEndpoint?(T(),M("button",{key:0,class:"mini-btn",onClick:i[0]||(i[0]=l=>t("editName")),title:"Edit name"},"✏️")):L("",!0)])]),e.orgDisplayName||e.roleName?(T(),M("div",jn,[e.orgDisplayName?(T(),M("div",zn,D(e.orgDisplayName),1)):L("",!0),e.roleName?(T(),M("div",xn,D(e.roleName),1)):L("",!0)])):L("",!0),v("div",$n,[i[2]||(i[2]=v("span",{class:"date-label"},[v("strong",null,"Visits:")],-1)),v("span",Jn,D(e.visits||0),1),i[3]||(i[3]=v("span",{class:"date-label"},[v("strong",null,"Registered:")],-1)),v("span",Yn,D(U(G)(e.createdAt)),1),i[4]||(i[4]=v("span",{class:"date-label"},[v("strong",null,"Last seen:")],-1)),v("span",Qn,D(U(G)(e.lastSeen)),1)]),o.$slots.default?(T(),M("div",Gn,[Lt(o.$slots,"default",{},void 0)])):L("",!0)],2)):L("",!0)}},Br=Y(Wn,[["__scopeId","data-v-ce373d6c"]]),Zn={__name:"Modal",props:{focusFallback:{type:[HTMLElement,Object],default:null},focusIndex:{type:Number,default:-1},focusSiblingSelector:{type:String,default:""}},emits:["close"],setup(e){const s=e,r=F(null),t=F(null),n=()=>{const i=t.value;if(!i)return;if(document.body.contains(i)&&!i.disabled){i.focus();return}if(s.focusSiblingSelector&&s.focusIndex>=0){const a=[s.focusFallback?.$el||s.focusFallback,i.closest("[data-nav-group]"),i.parentElement?.closest("section"),document.querySelector(".view-root")].filter(Boolean);for(const u of a){if(!u)continue;const d=u.querySelectorAll(s.focusSiblingSelector);if(d.length>0){const p=Math.min(s.focusIndex,d.length-1),b=d[p];if(b&&!b.disabled){b.focus();return}}}}const l=s.focusFallback?.$el||s.focusFallback;if(l&&document.body.contains(l)){const a=l.querySelector?.('button:not([disabled]), a, [tabindex="0"]')||l;if(a?.focus){a.focus();return}}},o=i=>{const l=z(i);if(!l)return;const a=i.target,u=a.closest(".modal-actions");if(u&&(l==="left"||l==="right"))i.preventDefault(),Mt(u,a,l,{itemSelector:"button"});else if(l==="up"&&u){i.preventDefault();const p=(u.closest("form")||u.closest(".modal-form"))?.querySelectorAll("input, textarea, select, button:not(.modal-actions button)");p&&p.length>0&&p[p.length-1].focus()}else if(l==="down"&&!u){const d=a.closest("form")||a.closest(".modal-form");if(d){i.preventDefault();const p=d.querySelector(".modal-actions");p&&sn(p,{primarySelector:".btn-primary",itemSelector:"button"})}}};return qt(()=>{t.value=document.activeElement,xe(()=>{if(r.value){r.value.showModal();const i=r.value.querySelector(".modal-actions .btn-primary");i&&i.setAttribute("data-nav-primary",""),rn(r.value)}})}),Ft(()=>{n()}),(i,l)=>(T(),M("dialog",{ref_key:"dialog",ref:r,onClose:l[0]||(l[0]=a=>i.$emit("close")),onKeydown:o},[Lt(i.$slots,"default",{},void 0)],544))}},Ir=Y(Zn,[["__scopeId","data-v-2ebcbb0a"]]),Xn={class:"name-edit-form"},eo=["for"],to=["id","type","placeholder","disabled"],no={key:0,class:"error small"},oo=["disabled"],ro=["disabled"],so={__name:"NameEditForm",props:{modelValue:{type:String,default:""},label:{type:String,default:"Name"},placeholder:{type:String,default:""},submitText:{type:String,default:"Save"},cancelText:{type:String,default:"Cancel"},busy:{type:Boolean,default:!1},error:{type:String,default:""},autoFocus:{type:Boolean,default:!0},autoSelect:{type:Boolean,default:!0},inputId:{type:String,default:null},inputType:{type:String,default:"text"}},emits:["update:modelValue","cancel"],setup(e,{emit:s}){const r=e,t=s,n=F(null),o=`name-edit-${Math.random().toString(36).slice(2,10)}`,i=x({get:()=>r.modelValue,set:d=>t("update:modelValue",d)}),l=x(()=>r.inputId||o),a=d=>{if(z(d)==="up"){d.preventDefault(),n.value?.focus();return}};function u(){t("cancel")}return(d,p)=>(T(),M("div",Xn,[v("label",{for:l.value},[$e(D(e.label)+" ",1),an(v("input",{id:l.value,ref_key:"inputRef",ref:n,type:e.inputType,placeholder:e.placeholder,"onUpdate:modelValue":p[0]||(p[0]=b=>i.value=b),disabled:e.busy,required:""},null,8,to),[[ln,i.value]])],8,eo),e.error?(T(),M("div",no,D(e.error),1)):L("",!0),v("div",{class:"modal-actions",onKeydown:a},[v("button",{type:"button",class:"btn-secondary",onClick:u,disabled:e.busy},D(e.cancelText),9,oo),v("button",{type:"submit",class:"btn-primary",disabled:e.busy,"data-nav-primary":""},D(e.submitText),9,ro)],32)]))}},Ar=Y(so,[["__scopeId","data-v-b73321cf"]]),io={class:"section-block","data-component":"session-list-section"},ao={class:"section-header"},lo={class:"section-description"},uo={class:"section-body"},co=["onKeydown"],fo=["href"],ho={class:"session-list"},go=["onFocusin","onKeydown"],mo={class:"item-top"},yo={class:"item-title"},po={class:"item-actions"},wo={key:0,class:"badge badge-current"},bo={key:1,class:"badge badge-current"},vo={key:2,class:"badge badge-current"},Co={key:3,class:"badge"},So=["onClick","disabled","title"],Eo={class:"item-details"},_o={class:"session-dates"},ko={class:"date-label"},Bo=["onClick"],Io={key:1,class:"empty-state"},Rr={__name:"SessionList",props:{sessions:{type:Array,default:()=>[]},emptyMessage:{type:String,default:"You currently have no other active sessions."},sectionDescription:{type:String,default:"Review where you're signed in and end any sessions you no longer recognize."},terminatingSessions:{type:Object,default:()=>({})},hoveredCredentialUuid:{type:String,default:null},navigationDisabled:{type:Boolean,default:!1}},emits:["terminate","sessionHover","navigate-out"],setup(e,{emit:s}){const r=e,t=s,n=he(),o=F(null),i=F(null),l=c=>{i.value=c,o.value=c.ip||null,t("sessionHover",c)},a=c=>{c.currentTarget.contains(c.relatedTarget)||(i.value=null,o.value=null,t("sessionHover",null))},u=c=>{c.currentTarget.matches(":focus")||(c.currentTarget.focus(),c.stopPropagation())},d=c=>!!r.terminatingSessions[c],p=(c,S)=>{const R=c.currentTarget,y=R.querySelector(".session-list")?.querySelectorAll(".session-item"),m=Array.from(document.querySelectorAll(".session-group")),g=m.indexOf(R);if(c.key==="Enter"&&c.target===R){S&&R.querySelector("a")?.click();return}if(r.navigationDisabled)return;const E=z(c);if(["down","right"].includes(E)&&c.target===R){c.preventDefault(),y?.[0]?.focus();return}if(["up","left"].includes(E)&&c.target===R){c.preventDefault(),g>0?m[g-1].focus():t("navigate-out","up");return}Dt(c,k=>t("navigate-out",k))},b=(c,S)=>{if(Pt(c,()=>{d(S.id)||t("terminate",S)}),c.defaultPrevented||r.navigationDisabled)return;const R=z(c);if(R){c.preventDefault();const C=c.currentTarget.closest(".session-group"),y=C.querySelector(".session-list");if(Nt(y,c.currentTarget,R,{itemSelector:".session-item"})==="boundary"){if(R==="left"||R==="up")C?.focus();else if(R==="down"||R==="right"){const g=Array.from(document.querySelectorAll(".session-group")),E=g.indexOf(C);E<g.length-1?g[E+1].focus():t("navigate-out","down")}}}c.key==="Escape"&&(c.preventDefault(),c.currentTarget.closest(".session-group")?.focus())},f=c=>`${c.includes(":")?"http":"https"}://${c}`,w=async c=>{if(c)try{await navigator.clipboard.writeText(c),n.showMessage("Full IP copied to clipboard!","success",2e3)}catch(S){console.error("Failed to copy IP:",S),n.showMessage("Failed to copy IP","error",3e3)}},h=c=>ae(c)??c,_=x(()=>{if(o.value)return ae(o.value);const c=r.sessions.find(S=>S.is_current);return c?ae(c.ip):null}),A=c=>_.value&&ae(c)===_.value,P=x(()=>{const c={};for(const y of r.sessions){const m=y.host||"";c[m]||(c[m]={sessions:[],isCurrentSite:!1}),c[m].sessions.push(y),y.is_current_host&&(c[m].isCurrentSite=!0)}for(const y in c)c[y].sessions.sort((m,g)=>new Date(g.last_renewed)-new Date(m.last_renewed));const S=new Intl.Collator(void 0,{numeric:!0,sensitivity:"base"}),R=Object.keys(c).sort(S.compare),C={};for(const y of R)C[y]=c[y];return C});return(c,S)=>(T(),M("section",io,[v("div",ao,[S[2]||(S[2]=v("h2",null,"Active Sessions",-1)),v("p",lo,D(e.sectionDescription),1)]),v("div",uo,[v("div",null,[Array.isArray(e.sessions)&&e.sessions.length?(T(!0),M(oe,{key:0},ce(P.value,(R,C)=>(T(),M("div",{key:C,class:"session-group",tabindex:"0",onKeydown:y=>p(y,C)},[v("span",{class:se(["session-group-host",{"is-current-site":R.isCurrentSite}])},[S[3]||(S[3]=v("span",{class:"session-group-icon"},"🌐",-1)),C?(T(),M("a",{key:0,href:f(C),tabindex:"-1",target:"_blank",rel:"noopener noreferrer"},D(C),9,fo)):(T(),M(oe,{key:1},[$e("Unbound host")],64))],2),v("div",ho,[(T(!0),M(oe,null,ce(R.sessions,y=>(T(),M("div",{key:y.id,class:se(["session-item",{"is-current":y.is_current&&!o.value&&!e.hoveredCredentialUuid,"is-hovered":i.value?.id===y.id,"is-linked-credential":e.hoveredCredentialUuid===y.credential}]),tabindex:"-1",onMousedown:S[0]||(S[0]=de(()=>{},["prevent"])),onClickCapture:u,onFocusin:m=>l(y),onFocusout:S[1]||(S[1]=m=>a(m)),onKeydown:m=>b(m,y)},[v("div",mo,[v("h4",yo,D(y.user_agent),1),v("div",po,[y.is_current&&!o.value&&!e.hoveredCredentialUuid?(T(),M("span",wo,"Current")):i.value?.id===y.id?(T(),M("span",bo,"Selected")):e.hoveredCredentialUuid===y.credential?(T(),M("span",vo,"Linked")):!e.hoveredCredentialUuid&&A(y.ip)?(T(),M("span",Co,"Same IP")):L("",!0),v("button",{onClick:m=>c.$emit("terminate",y),class:"btn-card-delete",disabled:d(y.id),title:d(y.id)?"Terminating...":"Terminate session",tabindex:"-1"},"❌",8,So)])]),v("div",Eo,[v("div",_o,[v("span",ko,D(U(G)(y.last_renewed)),1),v("span",{class:"date-value",onClick:m=>w(y.ip),title:"Click to copy full IP"},D(h(y.ip)),9,Bo)])])],42,go))),128))])],40,co))),128)):(T(),M("div",Io,[v("p",null,D(e.emptyMessage),1)]))])])]))}};function Ao(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var te={},be,et;function Ro(){return et||(et=1,be=function(){return typeof Promise=="function"&&Promise.prototype&&Promise.prototype.then}),be}var ve={},$={},tt;function W(){if(tt)return $;tt=1;let e;const s=[0,26,44,70,100,134,172,196,242,292,346,404,466,532,581,655,733,815,901,991,1085,1156,1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465,2611,2761,2876,3034,3196,3362,3532,3706];return $.getSymbolSize=function(t){if(!t)throw new Error('"version" cannot be null or undefined');if(t<1||t>40)throw new Error('"version" should be in range from 1 to 40');return t*4+17},$.getSymbolTotalCodewords=function(t){return s[t]},$.getBCHDigit=function(r){let t=0;for(;r!==0;)t++,r>>>=1;return t},$.setToSJISFunction=function(t){if(typeof t!="function")throw new Error('"toSJISFunc" is not a valid function.');e=t},$.isKanjiModeEnabled=function(){return typeof e<"u"},$.toSJIS=function(t){return e(t)},$}var Ce={},nt;function Je(){return nt||(nt=1,(function(e){e.L={bit:1},e.M={bit:0},e.Q={bit:3},e.H={bit:2};function s(r){if(typeof r!="string")throw new Error("Param is not a string");switch(r.toLowerCase()){case"l":case"low":return e.L;case"m":case"medium":return e.M;case"q":case"quartile":return e.Q;case"h":case"high":return e.H;default:throw new Error("Unknown EC Level: "+r)}}e.isValid=function(t){return t&&typeof t.bit<"u"&&t.bit>=0&&t.bit<4},e.from=function(t,n){if(e.isValid(t))return t;try{return s(t)}catch{return n}}})(Ce)),Ce}var Se,ot;function To(){if(ot)return Se;ot=1;function e(){this.buffer=[],this.length=0}return e.prototype={get:function(s){const r=Math.floor(s/8);return(this.buffer[r]>>>7-s%8&1)===1},put:function(s,r){for(let t=0;t<r;t++)this.putBit((s>>>r-t-1&1)===1)},getLengthInBits:function(){return this.length},putBit:function(s){const r=Math.floor(this.length/8);this.buffer.length<=r&&this.buffer.push(0),s&&(this.buffer[r]|=128>>>this.length%8),this.length++}},Se=e,Se}var Ee,rt;function Mo(){if(rt)return Ee;rt=1;function e(s){if(!s||s<1)throw new Error("BitMatrix size must be defined and greater than 0");this.size=s,this.data=new Uint8Array(s*s),this.reservedBit=new Uint8Array(s*s)}return e.prototype.set=function(s,r,t,n){const o=s*this.size+r;this.data[o]=t,n&&(this.reservedBit[o]=!0)},e.prototype.get=function(s,r){return this.data[s*this.size+r]},e.prototype.xor=function(s,r,t){this.data[s*this.size+r]^=t},e.prototype.isReserved=function(s,r){return this.reservedBit[s*this.size+r]},Ee=e,Ee}var _e={},st;function No(){return st||(st=1,(function(e){const s=W().getSymbolSize;e.getRowColCoords=function(t){if(t===1)return[];const n=Math.floor(t/7)+2,o=s(t),i=o===145?26:Math.ceil((o-13)/(2*n-2))*2,l=[o-7];for(let a=1;a<n-1;a++)l[a]=l[a-1]-i;return l.push(6),l.reverse()},e.getPositions=function(t){const n=[],o=e.getRowColCoords(t),i=o.length;for(let l=0;l<i;l++)for(let a=0;a<i;a++)l===0&&a===0||l===0&&a===i-1||l===i-1&&a===0||n.push([o[l],o[a]]);return n}})(_e)),_e}var ke={},it;function Po(){if(it)return ke;it=1;const e=W().getSymbolSize,s=7;return ke.getPositions=function(t){const n=e(t);return[[0,0],[n-s,0],[0,n-s]]},ke}var Be={},at;function Do(){return at||(at=1,(function(e){e.Patterns={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};const s={N1:3,N2:3,N3:40,N4:10};e.isValid=function(n){return n!=null&&n!==""&&!isNaN(n)&&n>=0&&n<=7},e.from=function(n){return e.isValid(n)?parseInt(n,10):void 0},e.getPenaltyN1=function(n){const o=n.size;let i=0,l=0,a=0,u=null,d=null;for(let p=0;p<o;p++){l=a=0,u=d=null;for(let b=0;b<o;b++){let f=n.get(p,b);f===u?l++:(l>=5&&(i+=s.N1+(l-5)),u=f,l=1),f=n.get(b,p),f===d?a++:(a>=5&&(i+=s.N1+(a-5)),d=f,a=1)}l>=5&&(i+=s.N1+(l-5)),a>=5&&(i+=s.N1+(a-5))}return i},e.getPenaltyN2=function(n){const o=n.size;let i=0;for(let l=0;l<o-1;l++)for(let a=0;a<o-1;a++){const u=n.get(l,a)+n.get(l,a+1)+n.get(l+1,a)+n.get(l+1,a+1);(u===4||u===0)&&i++}return i*s.N2},e.getPenaltyN3=function(n){const o=n.size;let i=0,l=0,a=0;for(let u=0;u<o;u++){l=a=0;for(let d=0;d<o;d++)l=l<<1&2047|n.get(u,d),d>=10&&(l===1488||l===93)&&i++,a=a<<1&2047|n.get(d,u),d>=10&&(a===1488||a===93)&&i++}return i*s.N3},e.getPenaltyN4=function(n){let o=0;const i=n.data.length;for(let a=0;a<i;a++)o+=n.data[a];return Math.abs(Math.ceil(o*100/i/5)-10)*s.N4};function r(t,n,o){switch(t){case e.Patterns.PATTERN000:return(n+o)%2===0;case e.Patterns.PATTERN001:return n%2===0;case e.Patterns.PATTERN010:return o%3===0;case e.Patterns.PATTERN011:return(n+o)%3===0;case e.Patterns.PATTERN100:return(Math.floor(n/2)+Math.floor(o/3))%2===0;case e.Patterns.PATTERN101:return n*o%2+n*o%3===0;case e.Patterns.PATTERN110:return(n*o%2+n*o%3)%2===0;case e.Patterns.PATTERN111:return(n*o%3+(n+o)%2)%2===0;default:throw new Error("bad maskPattern:"+t)}}e.applyMask=function(n,o){const i=o.size;for(let l=0;l<i;l++)for(let a=0;a<i;a++)o.isReserved(a,l)||o.xor(a,l,r(n,a,l))},e.getBestMask=function(n,o){const i=Object.keys(e.Patterns).length;let l=0,a=1/0;for(let u=0;u<i;u++){o(u),e.applyMask(u,n);const d=e.getPenaltyN1(n)+e.getPenaltyN2(n)+e.getPenaltyN3(n)+e.getPenaltyN4(n);e.applyMask(u,n),d<a&&(a=d,l=u)}return l}})(Be)),Be}var le={},lt;function Ot(){if(lt)return le;lt=1;const e=Je(),s=[1,1,1,1,1,1,1,1,1,1,2,2,1,2,2,4,1,2,4,4,2,4,4,4,2,4,6,5,2,4,6,6,2,5,8,8,4,5,8,8,4,5,8,11,4,8,10,11,4,9,12,16,4,9,16,16,6,10,12,18,6,10,17,16,6,11,16,19,6,13,18,21,7,14,21,25,8,16,20,25,8,17,23,25,9,17,23,34,9,18,25,30,10,20,27,32,12,21,29,35,12,23,34,37,12,25,34,40,13,26,35,42,14,28,38,45,15,29,40,48,16,31,43,51,17,33,45,54,18,35,48,57,19,37,51,60,19,38,53,63,20,40,56,66,21,43,59,70,22,45,62,74,24,47,65,77,25,49,68,81],r=[7,10,13,17,10,16,22,28,15,26,36,44,20,36,52,64,26,48,72,88,36,64,96,112,40,72,108,130,48,88,132,156,60,110,160,192,72,130,192,224,80,150,224,264,96,176,260,308,104,198,288,352,120,216,320,384,132,240,360,432,144,280,408,480,168,308,448,532,180,338,504,588,196,364,546,650,224,416,600,700,224,442,644,750,252,476,690,816,270,504,750,900,300,560,810,960,312,588,870,1050,336,644,952,1110,360,700,1020,1200,390,728,1050,1260,420,784,1140,1350,450,812,1200,1440,480,868,1290,1530,510,924,1350,1620,540,980,1440,1710,570,1036,1530,1800,570,1064,1590,1890,600,1120,1680,1980,630,1204,1770,2100,660,1260,1860,2220,720,1316,1950,2310,750,1372,2040,2430];return le.getBlocksCount=function(n,o){switch(o){case e.L:return s[(n-1)*4+0];case e.M:return s[(n-1)*4+1];case e.Q:return s[(n-1)*4+2];case e.H:return s[(n-1)*4+3];default:return}},le.getTotalCodewordsCount=function(n,o){switch(o){case e.L:return r[(n-1)*4+0];case e.M:return r[(n-1)*4+1];case e.Q:return r[(n-1)*4+2];case e.H:return r[(n-1)*4+3];default:return}},le}var Ie={},ne={},ut;function Lo(){if(ut)return ne;ut=1;const e=new Uint8Array(512),s=new Uint8Array(256);return(function(){let t=1;for(let n=0;n<255;n++)e[n]=t,s[t]=n,t<<=1,t&256&&(t^=285);for(let n=255;n<512;n++)e[n]=e[n-255]})(),ne.log=function(t){if(t<1)throw new Error("log("+t+")");return s[t]},ne.exp=function(t){return e[t]},ne.mul=function(t,n){return t===0||n===0?0:e[s[t]+s[n]]},ne}var ct;function qo(){return ct||(ct=1,(function(e){const s=Lo();e.mul=function(t,n){const o=new Uint8Array(t.length+n.length-1);for(let i=0;i<t.length;i++)for(let l=0;l<n.length;l++)o[i+l]^=s.mul(t[i],n[l]);return o},e.mod=function(t,n){let o=new Uint8Array(t);for(;o.length-n.length>=0;){const i=o[0];for(let a=0;a<n.length;a++)o[a]^=s.mul(n[a],i);let l=0;for(;l<o.length&&o[l]===0;)l++;o=o.slice(l)}return o},e.generateECPolynomial=function(t){let n=new Uint8Array([1]);for(let o=0;o<t;o++)n=e.mul(n,new Uint8Array([1,s.exp(o)]));return n}})(Ie)),Ie}var Ae,dt;function Fo(){if(dt)return Ae;dt=1;const e=qo();function s(r){this.genPoly=void 0,this.degree=r,this.degree&&this.initialize(this.degree)}return s.prototype.initialize=function(t){this.degree=t,this.genPoly=e.generateECPolynomial(this.degree)},s.prototype.encode=function(t){if(!this.genPoly)throw new Error("Encoder not initialized");const n=new Uint8Array(t.length+this.degree);n.set(t);const o=e.mod(n,this.genPoly),i=this.degree-o.length;if(i>0){const l=new Uint8Array(this.degree);return l.set(o,i),l}return o},Ae=s,Ae}var Re={},Te={},Me={},ft;function jt(){return ft||(ft=1,Me.isValid=function(s){return!isNaN(s)&&s>=1&&s<=40}),Me}var H={},ht;function zt(){if(ht)return H;ht=1;const e="[0-9]+",s="[A-Z $%*+\\-./:]+";let r="(?:[u3000-u303F]|[u3040-u309F]|[u30A0-u30FF]|[uFF00-uFFEF]|[u4E00-u9FAF]|[u2605-u2606]|[u2190-u2195]|u203B|[u2010u2015u2018u2019u2025u2026u201Cu201Du2225u2260]|[u0391-u0451]|[u00A7u00A8u00B1u00B4u00D7u00F7])+";r=r.replace(/u/g,"\\u");const t="(?:(?![A-Z0-9 $%*+\\-./:]|"+r+`)(?:.|[\r
2
+ ]))+`;H.KANJI=new RegExp(r,"g"),H.BYTE_KANJI=new RegExp("[^A-Z0-9 $%*+\\-./:]+","g"),H.BYTE=new RegExp(t,"g"),H.NUMERIC=new RegExp(e,"g"),H.ALPHANUMERIC=new RegExp(s,"g");const n=new RegExp("^"+r+"$"),o=new RegExp("^"+e+"$"),i=new RegExp("^[A-Z0-9 $%*+\\-./:]+$");return H.testKanji=function(a){return n.test(a)},H.testNumeric=function(a){return o.test(a)},H.testAlphanumeric=function(a){return i.test(a)},H}var gt;function Z(){return gt||(gt=1,(function(e){const s=jt(),r=zt();e.NUMERIC={id:"Numeric",bit:1,ccBits:[10,12,14]},e.ALPHANUMERIC={id:"Alphanumeric",bit:2,ccBits:[9,11,13]},e.BYTE={id:"Byte",bit:4,ccBits:[8,16,16]},e.KANJI={id:"Kanji",bit:8,ccBits:[8,10,12]},e.MIXED={bit:-1},e.getCharCountIndicator=function(o,i){if(!o.ccBits)throw new Error("Invalid mode: "+o);if(!s.isValid(i))throw new Error("Invalid version: "+i);return i>=1&&i<10?o.ccBits[0]:i<27?o.ccBits[1]:o.ccBits[2]},e.getBestModeForData=function(o){return r.testNumeric(o)?e.NUMERIC:r.testAlphanumeric(o)?e.ALPHANUMERIC:r.testKanji(o)?e.KANJI:e.BYTE},e.toString=function(o){if(o&&o.id)return o.id;throw new Error("Invalid mode")},e.isValid=function(o){return o&&o.bit&&o.ccBits};function t(n){if(typeof n!="string")throw new Error("Param is not a string");switch(n.toLowerCase()){case"numeric":return e.NUMERIC;case"alphanumeric":return e.ALPHANUMERIC;case"kanji":return e.KANJI;case"byte":return e.BYTE;default:throw new Error("Unknown mode: "+n)}}e.from=function(o,i){if(e.isValid(o))return o;try{return t(o)}catch{return i}}})(Te)),Te}var mt;function Uo(){return mt||(mt=1,(function(e){const s=W(),r=Ot(),t=Je(),n=Z(),o=jt(),i=7973,l=s.getBCHDigit(i);function a(b,f,w){for(let h=1;h<=40;h++)if(f<=e.getCapacity(h,w,b))return h}function u(b,f){return n.getCharCountIndicator(b,f)+4}function d(b,f){let w=0;return b.forEach(function(h){const _=u(h.mode,f);w+=_+h.getBitsLength()}),w}function p(b,f){for(let w=1;w<=40;w++)if(d(b,w)<=e.getCapacity(w,f,n.MIXED))return w}e.from=function(f,w){return o.isValid(f)?parseInt(f,10):w},e.getCapacity=function(f,w,h){if(!o.isValid(f))throw new Error("Invalid QR Code version");typeof h>"u"&&(h=n.BYTE);const _=s.getSymbolTotalCodewords(f),A=r.getTotalCodewordsCount(f,w),P=(_-A)*8;if(h===n.MIXED)return P;const c=P-u(h,f);switch(h){case n.NUMERIC:return Math.floor(c/10*3);case n.ALPHANUMERIC:return Math.floor(c/11*2);case n.KANJI:return Math.floor(c/13);case n.BYTE:default:return Math.floor(c/8)}},e.getBestVersionForData=function(f,w){let h;const _=t.from(w,t.M);if(Array.isArray(f)){if(f.length>1)return p(f,_);if(f.length===0)return 1;h=f[0]}else h=f;return a(h.mode,h.getLength(),_)},e.getEncodedBits=function(f){if(!o.isValid(f)||f<7)throw new Error("Invalid QR Code version");let w=f<<12;for(;s.getBCHDigit(w)-l>=0;)w^=i<<s.getBCHDigit(w)-l;return f<<12|w}})(Re)),Re}var Ne={},yt;function Ko(){if(yt)return Ne;yt=1;const e=W(),s=1335,r=21522,t=e.getBCHDigit(s);return Ne.getEncodedBits=function(o,i){const l=o.bit<<3|i;let a=l<<10;for(;e.getBCHDigit(a)-t>=0;)a^=s<<e.getBCHDigit(a)-t;return(l<<10|a)^r},Ne}var Pe={},De,pt;function Vo(){if(pt)return De;pt=1;const e=Z();function s(r){this.mode=e.NUMERIC,this.data=r.toString()}return s.getBitsLength=function(t){return 10*Math.floor(t/3)+(t%3?t%3*3+1:0)},s.prototype.getLength=function(){return this.data.length},s.prototype.getBitsLength=function(){return s.getBitsLength(this.data.length)},s.prototype.write=function(t){let n,o,i;for(n=0;n+3<=this.data.length;n+=3)o=this.data.substr(n,3),i=parseInt(o,10),t.put(i,10);const l=this.data.length-n;l>0&&(o=this.data.substr(n),i=parseInt(o,10),t.put(i,l*3+1))},De=s,De}var Le,wt;function Ho(){if(wt)return Le;wt=1;const e=Z(),s=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"," ","$","%","*","+","-",".","/",":"];function r(t){this.mode=e.ALPHANUMERIC,this.data=t}return r.getBitsLength=function(n){return 11*Math.floor(n/2)+6*(n%2)},r.prototype.getLength=function(){return this.data.length},r.prototype.getBitsLength=function(){return r.getBitsLength(this.data.length)},r.prototype.write=function(n){let o;for(o=0;o+2<=this.data.length;o+=2){let i=s.indexOf(this.data[o])*45;i+=s.indexOf(this.data[o+1]),n.put(i,11)}this.data.length%2&&n.put(s.indexOf(this.data[o]),6)},Le=r,Le}var qe,bt;function Oo(){if(bt)return qe;bt=1;const e=Z();function s(r){this.mode=e.BYTE,typeof r=="string"?this.data=new TextEncoder().encode(r):this.data=new Uint8Array(r)}return s.getBitsLength=function(t){return t*8},s.prototype.getLength=function(){return this.data.length},s.prototype.getBitsLength=function(){return s.getBitsLength(this.data.length)},s.prototype.write=function(r){for(let t=0,n=this.data.length;t<n;t++)r.put(this.data[t],8)},qe=s,qe}var Fe,vt;function jo(){if(vt)return Fe;vt=1;const e=Z(),s=W();function r(t){this.mode=e.KANJI,this.data=t}return r.getBitsLength=function(n){return n*13},r.prototype.getLength=function(){return this.data.length},r.prototype.getBitsLength=function(){return r.getBitsLength(this.data.length)},r.prototype.write=function(t){let n;for(n=0;n<this.data.length;n++){let o=s.toSJIS(this.data[n]);if(o>=33088&&o<=40956)o-=33088;else if(o>=57408&&o<=60351)o-=49472;else throw new Error("Invalid SJIS character: "+this.data[n]+`
3
+ Make sure your charset is UTF-8`);o=(o>>>8&255)*192+(o&255),t.put(o,13)}},Fe=r,Fe}var Ue={exports:{}},Ct;function zo(){return Ct||(Ct=1,(function(e){var s={single_source_shortest_paths:function(r,t,n){var o={},i={};i[t]=0;var l=s.PriorityQueue.make();l.push(t,0);for(var a,u,d,p,b,f,w,h,_;!l.empty();){a=l.pop(),u=a.value,p=a.cost,b=r[u]||{};for(d in b)b.hasOwnProperty(d)&&(f=b[d],w=p+f,h=i[d],_=typeof i[d]>"u",(_||h>w)&&(i[d]=w,l.push(d,w),o[d]=u))}if(typeof n<"u"&&typeof i[n]>"u"){var A=["Could not find a path from ",t," to ",n,"."].join("");throw new Error(A)}return o},extract_shortest_path_from_predecessor_list:function(r,t){for(var n=[],o=t;o;)n.push(o),r[o],o=r[o];return n.reverse(),n},find_path:function(r,t,n){var o=s.single_source_shortest_paths(r,t,n);return s.extract_shortest_path_from_predecessor_list(o,n)},PriorityQueue:{make:function(r){var t=s.PriorityQueue,n={},o;r=r||{};for(o in t)t.hasOwnProperty(o)&&(n[o]=t[o]);return n.queue=[],n.sorter=r.sorter||t.default_sorter,n},default_sorter:function(r,t){return r.cost-t.cost},push:function(r,t){var n={value:r,cost:t};this.queue.push(n),this.queue.sort(this.sorter)},pop:function(){return this.queue.shift()},empty:function(){return this.queue.length===0}}};e.exports=s})(Ue)),Ue.exports}var St;function xo(){return St||(St=1,(function(e){const s=Z(),r=Vo(),t=Ho(),n=Oo(),o=jo(),i=zt(),l=W(),a=zo();function u(A){return unescape(encodeURIComponent(A)).length}function d(A,P,c){const S=[];let R;for(;(R=A.exec(c))!==null;)S.push({data:R[0],index:R.index,mode:P,length:R[0].length});return S}function p(A){const P=d(i.NUMERIC,s.NUMERIC,A),c=d(i.ALPHANUMERIC,s.ALPHANUMERIC,A);let S,R;return l.isKanjiModeEnabled()?(S=d(i.BYTE,s.BYTE,A),R=d(i.KANJI,s.KANJI,A)):(S=d(i.BYTE_KANJI,s.BYTE,A),R=[]),P.concat(c,S,R).sort(function(y,m){return y.index-m.index}).map(function(y){return{data:y.data,mode:y.mode,length:y.length}})}function b(A,P){switch(P){case s.NUMERIC:return r.getBitsLength(A);case s.ALPHANUMERIC:return t.getBitsLength(A);case s.KANJI:return o.getBitsLength(A);case s.BYTE:return n.getBitsLength(A)}}function f(A){return A.reduce(function(P,c){const S=P.length-1>=0?P[P.length-1]:null;return S&&S.mode===c.mode?(P[P.length-1].data+=c.data,P):(P.push(c),P)},[])}function w(A){const P=[];for(let c=0;c<A.length;c++){const S=A[c];switch(S.mode){case s.NUMERIC:P.push([S,{data:S.data,mode:s.ALPHANUMERIC,length:S.length},{data:S.data,mode:s.BYTE,length:S.length}]);break;case s.ALPHANUMERIC:P.push([S,{data:S.data,mode:s.BYTE,length:S.length}]);break;case s.KANJI:P.push([S,{data:S.data,mode:s.BYTE,length:u(S.data)}]);break;case s.BYTE:P.push([{data:S.data,mode:s.BYTE,length:u(S.data)}])}}return P}function h(A,P){const c={},S={start:{}};let R=["start"];for(let C=0;C<A.length;C++){const y=A[C],m=[];for(let g=0;g<y.length;g++){const E=y[g],k=""+C+g;m.push(k),c[k]={node:E,lastCount:0},S[k]={};for(let B=0;B<R.length;B++){const I=R[B];c[I]&&c[I].node.mode===E.mode?(S[I][k]=b(c[I].lastCount+E.length,E.mode)-b(c[I].lastCount,E.mode),c[I].lastCount+=E.length):(c[I]&&(c[I].lastCount=E.length),S[I][k]=b(E.length,E.mode)+4+s.getCharCountIndicator(E.mode,P))}}R=m}for(let C=0;C<R.length;C++)S[R[C]].end=0;return{map:S,table:c}}function _(A,P){let c;const S=s.getBestModeForData(A);if(c=s.from(P,S),c!==s.BYTE&&c.bit<S.bit)throw new Error('"'+A+'" cannot be encoded with mode '+s.toString(c)+`.
4
+ Suggested mode is: `+s.toString(S));switch(c===s.KANJI&&!l.isKanjiModeEnabled()&&(c=s.BYTE),c){case s.NUMERIC:return new r(A);case s.ALPHANUMERIC:return new t(A);case s.KANJI:return new o(A);case s.BYTE:return new n(A)}}e.fromArray=function(P){return P.reduce(function(c,S){return typeof S=="string"?c.push(_(S,null)):S.data&&c.push(_(S.data,S.mode)),c},[])},e.fromString=function(P,c){const S=p(P,l.isKanjiModeEnabled()),R=w(S),C=h(R,c),y=a.find_path(C.map,"start","end"),m=[];for(let g=1;g<y.length-1;g++)m.push(C.table[y[g]].node);return e.fromArray(f(m))},e.rawSplit=function(P){return e.fromArray(p(P,l.isKanjiModeEnabled()))}})(Pe)),Pe}var Et;function $o(){if(Et)return ve;Et=1;const e=W(),s=Je(),r=To(),t=Mo(),n=No(),o=Po(),i=Do(),l=Ot(),a=Fo(),u=Uo(),d=Ko(),p=Z(),b=xo();function f(C,y){const m=C.size,g=o.getPositions(y);for(let E=0;E<g.length;E++){const k=g[E][0],B=g[E][1];for(let I=-1;I<=7;I++)if(!(k+I<=-1||m<=k+I))for(let N=-1;N<=7;N++)B+N<=-1||m<=B+N||(I>=0&&I<=6&&(N===0||N===6)||N>=0&&N<=6&&(I===0||I===6)||I>=2&&I<=4&&N>=2&&N<=4?C.set(k+I,B+N,!0,!0):C.set(k+I,B+N,!1,!0))}}function w(C){const y=C.size;for(let m=8;m<y-8;m++){const g=m%2===0;C.set(m,6,g,!0),C.set(6,m,g,!0)}}function h(C,y){const m=n.getPositions(y);for(let g=0;g<m.length;g++){const E=m[g][0],k=m[g][1];for(let B=-2;B<=2;B++)for(let I=-2;I<=2;I++)B===-2||B===2||I===-2||I===2||B===0&&I===0?C.set(E+B,k+I,!0,!0):C.set(E+B,k+I,!1,!0)}}function _(C,y){const m=C.size,g=u.getEncodedBits(y);let E,k,B;for(let I=0;I<18;I++)E=Math.floor(I/3),k=I%3+m-8-3,B=(g>>I&1)===1,C.set(E,k,B,!0),C.set(k,E,B,!0)}function A(C,y,m){const g=C.size,E=d.getEncodedBits(y,m);let k,B;for(k=0;k<15;k++)B=(E>>k&1)===1,k<6?C.set(k,8,B,!0):k<8?C.set(k+1,8,B,!0):C.set(g-15+k,8,B,!0),k<8?C.set(8,g-k-1,B,!0):k<9?C.set(8,15-k-1+1,B,!0):C.set(8,15-k-1,B,!0);C.set(g-8,8,1,!0)}function P(C,y){const m=C.size;let g=-1,E=m-1,k=7,B=0;for(let I=m-1;I>0;I-=2)for(I===6&&I--;;){for(let N=0;N<2;N++)if(!C.isReserved(E,I-N)){let V=!1;B<y.length&&(V=(y[B]>>>k&1)===1),C.set(E,I-N,V),k--,k===-1&&(B++,k=7)}if(E+=g,E<0||m<=E){E-=g,g=-g;break}}}function c(C,y,m){const g=new r;m.forEach(function(N){g.put(N.mode.bit,4),g.put(N.getLength(),p.getCharCountIndicator(N.mode,C)),N.write(g)});const E=e.getSymbolTotalCodewords(C),k=l.getTotalCodewordsCount(C,y),B=(E-k)*8;for(g.getLengthInBits()+4<=B&&g.put(0,4);g.getLengthInBits()%8!==0;)g.putBit(0);const I=(B-g.getLengthInBits())/8;for(let N=0;N<I;N++)g.put(N%2?17:236,8);return S(g,C,y)}function S(C,y,m){const g=e.getSymbolTotalCodewords(y),E=l.getTotalCodewordsCount(y,m),k=g-E,B=l.getBlocksCount(y,m),I=g%B,N=B-I,V=Math.floor(g/B),K=Math.floor(k/B),q=K+1,Ye=V-K,$t=new a(Ye);let ge=0;const ie=new Array(B),Qe=new Array(B);let me=0;const Jt=new Uint8Array(C.buffer);for(let X=0;X<B;X++){const pe=X<N?K:q;ie[X]=Jt.slice(ge,ge+pe),Qe[X]=$t.encode(ie[X]),ge+=pe,me=Math.max(me,pe)}const ye=new Uint8Array(g);let Ge=0,O,j;for(O=0;O<me;O++)for(j=0;j<B;j++)O<ie[j].length&&(ye[Ge++]=ie[j][O]);for(O=0;O<Ye;O++)for(j=0;j<B;j++)ye[Ge++]=Qe[j][O];return ye}function R(C,y,m,g){let E;if(Array.isArray(C))E=b.fromArray(C);else if(typeof C=="string"){let V=y;if(!V){const K=b.rawSplit(C);V=u.getBestVersionForData(K,m)}E=b.fromString(C,V||40)}else throw new Error("Invalid data");const k=u.getBestVersionForData(E,m);if(!k)throw new Error("The amount of data is too big to be stored in a QR Code");if(!y)y=k;else if(y<k)throw new Error(`
5
+ The chosen QR Code version cannot contain this amount of data.
6
+ Minimum version required to store current data is: `+k+`.
7
+ `);const B=c(y,m,E),I=e.getSymbolSize(y),N=new t(I);return f(N,y),w(N),h(N,y),A(N,m,0),y>=7&&_(N,y),P(N,B),isNaN(g)&&(g=i.getBestMask(N,A.bind(null,N,m))),i.applyMask(g,N),A(N,m,g),{modules:N,version:y,errorCorrectionLevel:m,maskPattern:g,segments:E}}return ve.create=function(y,m){if(typeof y>"u"||y==="")throw new Error("No input text");let g=s.M,E,k;return typeof m<"u"&&(g=s.from(m.errorCorrectionLevel,s.M),E=u.from(m.version),k=i.from(m.maskPattern),m.toSJISFunc&&e.setToSJISFunction(m.toSJISFunc)),R(y,E,g,k)},ve}var Ke={},Ve={},_t;function xt(){return _t||(_t=1,(function(e){function s(r){if(typeof r=="number"&&(r=r.toString()),typeof r!="string")throw new Error("Color should be defined as hex string");let t=r.slice().replace("#","").split("");if(t.length<3||t.length===5||t.length>8)throw new Error("Invalid hex color: "+r);(t.length===3||t.length===4)&&(t=Array.prototype.concat.apply([],t.map(function(o){return[o,o]}))),t.length===6&&t.push("F","F");const n=parseInt(t.join(""),16);return{r:n>>24&255,g:n>>16&255,b:n>>8&255,a:n&255,hex:"#"+t.slice(0,6).join("")}}e.getOptions=function(t){t||(t={}),t.color||(t.color={});const n=typeof t.margin>"u"||t.margin===null||t.margin<0?4:t.margin,o=t.width&&t.width>=21?t.width:void 0,i=t.scale||4;return{width:o,scale:o?4:i,margin:n,color:{dark:s(t.color.dark||"#000000ff"),light:s(t.color.light||"#ffffffff")},type:t.type,rendererOpts:t.rendererOpts||{}}},e.getScale=function(t,n){return n.width&&n.width>=t+n.margin*2?n.width/(t+n.margin*2):n.scale},e.getImageWidth=function(t,n){const o=e.getScale(t,n);return Math.floor((t+n.margin*2)*o)},e.qrToImageData=function(t,n,o){const i=n.modules.size,l=n.modules.data,a=e.getScale(i,o),u=Math.floor((i+o.margin*2)*a),d=o.margin*a,p=[o.color.light,o.color.dark];for(let b=0;b<u;b++)for(let f=0;f<u;f++){let w=(b*u+f)*4,h=o.color.light;if(b>=d&&f>=d&&b<u-d&&f<u-d){const _=Math.floor((b-d)/a),A=Math.floor((f-d)/a);h=p[l[_*i+A]?1:0]}t[w++]=h.r,t[w++]=h.g,t[w++]=h.b,t[w]=h.a}}})(Ve)),Ve}var kt;function Jo(){return kt||(kt=1,(function(e){const s=xt();function r(n,o,i){n.clearRect(0,0,o.width,o.height),o.style||(o.style={}),o.height=i,o.width=i,o.style.height=i+"px",o.style.width=i+"px"}function t(){try{return document.createElement("canvas")}catch{throw new Error("You need to specify a canvas element")}}e.render=function(o,i,l){let a=l,u=i;typeof a>"u"&&(!i||!i.getContext)&&(a=i,i=void 0),i||(u=t()),a=s.getOptions(a);const d=s.getImageWidth(o.modules.size,a),p=u.getContext("2d"),b=p.createImageData(d,d);return s.qrToImageData(b.data,o,a),r(p,u,d),p.putImageData(b,0,0),u},e.renderToDataURL=function(o,i,l){let a=l;typeof a>"u"&&(!i||!i.getContext)&&(a=i,i=void 0),a||(a={});const u=e.render(o,i,a),d=a.type||"image/png",p=a.rendererOpts||{};return u.toDataURL(d,p.quality)}})(Ke)),Ke}var He={},Bt;function Yo(){if(Bt)return He;Bt=1;const e=xt();function s(n,o){const i=n.a/255,l=o+'="'+n.hex+'"';return i<1?l+" "+o+'-opacity="'+i.toFixed(2).slice(1)+'"':l}function r(n,o,i){let l=n+o;return typeof i<"u"&&(l+=" "+i),l}function t(n,o,i){let l="",a=0,u=!1,d=0;for(let p=0;p<n.length;p++){const b=Math.floor(p%o),f=Math.floor(p/o);!b&&!u&&(u=!0),n[p]?(d++,p>0&&b>0&&n[p-1]||(l+=u?r("M",b+i,.5+f+i):r("m",a,0),a=0,u=!1),b+1<o&&n[p+1]||(l+=r("h",d),d=0)):a++}return l}return He.render=function(o,i,l){const a=e.getOptions(i),u=o.modules.size,d=o.modules.data,p=u+a.margin*2,b=a.color.light.a?"<path "+s(a.color.light,"fill")+' d="M0 0h'+p+"v"+p+'H0z"/>':"",f="<path "+s(a.color.dark,"stroke")+' d="'+t(d,u,a.margin)+'"/>',w='viewBox="0 0 '+p+" "+p+'"',_='<svg xmlns="http://www.w3.org/2000/svg" '+(a.width?'width="'+a.width+'" height="'+a.width+'" ':"")+w+' shape-rendering="crispEdges">'+b+f+`</svg>
8
+ `;return typeof l=="function"&&l(null,_),_},He}var It;function Qo(){if(It)return te;It=1;const e=Ro(),s=$o(),r=Jo(),t=Yo();function n(o,i,l,a,u){const d=[].slice.call(arguments,1),p=d.length,b=typeof d[p-1]=="function";if(!b&&!e())throw new Error("Callback required as last argument");if(b){if(p<2)throw new Error("Too few arguments provided");p===2?(u=l,l=i,i=a=void 0):p===3&&(i.getContext&&typeof u>"u"?(u=a,a=void 0):(u=a,a=l,l=i,i=void 0))}else{if(p<1)throw new Error("Too few arguments provided");return p===1?(l=i,i=a=void 0):p===2&&!i.getContext&&(a=l,l=i,i=void 0),new Promise(function(f,w){try{const h=s.create(l,a);f(o(h,i,a))}catch(h){w(h)}})}try{const f=s.create(l,a);u(null,o(f,i,a))}catch(f){u(f)}}return te.create=s.create,te.toCanvas=n.bind(null,r.render),te.toDataURL=n.bind(null,r.renderToDataURL),te.toString=n.bind(null,function(o,i,l){return t.render(o,l)}),te}var Go=Qo();const Wo=Ao(Go),Zo={class:"qr-display"},Xo={class:"qr-section"},er=["href","onKeydown"],tr={key:0,class:"link-text"},nr={__name:"QRCodeDisplay",props:{url:{type:String,required:!0},showLink:{type:Boolean,default:!1}},emits:["copied"],setup(e,{emit:s}){const r=e,t=s,n=F(null),o=x(()=>r.url?r.url.replace(/^https?:\/\//,""):"");function i(){if(!(!r.url||!n.value))try{n.value.getContext("2d").clearRect(0,0,n.value.width,n.value.height),Wo.toCanvas(n.value,r.url,{scale:6,margin:0,color:{dark:"#000000",light:"#FFFFFF"}}),n.value.removeAttribute("style")}catch(a){console.error("QR code generation failed:",a)}}async function l(){if(r.url)try{await navigator.clipboard.writeText(r.url),t("copied")}catch(a){console.error("Failed to copy link:",a)}}return Oe(()=>r.url,()=>{i()},{immediate:!0}),Oe(n,()=>{n.value&&r.url&&i()},{immediate:!0}),(a,u)=>(T(),M("div",Zo,[v("div",Xo,[v("a",{href:e.url,onClick:de(l,["prevent"]),class:"qr-link",title:"Click to copy link",tabindex:"0",onKeydown:un(de(l,["prevent"]),["enter"])},[v("canvas",{ref_key:"qrCanvas",ref:n,class:"qr-code"},null,512),e.showLink&&e.url?(T(),M("div",tr,D(o.value),1)):L("",!0)],40,er)])]))}},or=Y(nr,[["__scopeId","data-v-727427c4"]]),rr={class:"device-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"regTitle"},sr={class:"reg-header-row"},ir={id:"regTitle",class:"reg-title"},ar={key:0},lr={key:1},ur={class:"device-link-section"},cr={key:0,class:"expiry-note"},dr={__name:"RegistrationLinkModal",props:{endpoint:{type:String,required:!0},userName:{type:String,default:""}},emits:["close","copied"],setup(e,{emit:s}){const r=e,t=s,n=he(),o=F(null),i=F(null),l=F(null),a=F(null),u=F(null);async function d(){try{const h=await Q(r.endpoint,{method:"POST"});if(h.url){if(i.value=h.url,l.value=h.expires?new Date(h.expires):null,await xe(),o.value){o.value.showModal();const _=a.value;(_?.querySelector(".btn-primary")||_?.querySelector("button"))?.focus()}}else t("close")}catch(h){n.showMessage(h.message||"Failed to generate link","error"),t("close")}}function p(){t("copied")}const b=h=>{const _=z(h)},f=h=>{const _=z(h);_&&(h.preventDefault(),(_==="down"||_==="up")&&a.value?.querySelector("button")?.focus())},w=h=>{const _=z(h);_&&(h.preventDefault(),(_==="up"||_==="down")&&document.querySelector(".qr-link")?.focus())};return qt(()=>{u.value=document.activeElement,d()}),Ft(()=>{const h=u.value;h&&document.body.contains(h)&&!h.disabled&&h.focus()}),(h,_)=>(T(),M("dialog",{ref_key:"dialog",ref:o,onClose:_[2]||(_[2]=A=>h.$emit("close")),onKeydown:b},[v("div",rr,[v("div",sr,[v("h2",ir,[_[3]||(_[3]=$e(" 📱 ",-1)),e.userName?(T(),M("span",ar,"Registration for "+D(e.userName),1)):(T(),M("span",lr,"Add Another Device"))]),v("button",{class:"icon-btn",onClick:_[0]||(_[0]=A=>h.$emit("close")),"aria-label":"Close",tabindex:"-1"},"✕")]),v("div",ur,[_[4]||(_[4]=v("p",{class:"reg-help"}," Scan this QR code on the new device, or copy the link and open it there. ",-1)),cn(or,{url:i.value,"show-link":!0,onCopied:p,onKeydown:f},null,8,["url"]),l.value?(T(),M("p",cr," This link expires "+D(U(G)(l.value).toLowerCase())+". ",1)):L("",!0)]),v("div",{class:"reg-actions",ref_key:"actionsRow",ref:a,onKeydown:w},[v("button",{class:"btn-secondary",onClick:_[1]||(_[1]=A=>h.$emit("close"))},"Close")],544)])],544))}},Tr=Y(dr,[["__scopeId","data-v-26360b56"]]),fr={class:"loading-container"},hr={__name:"LoadingView",props:{message:{type:String,default:"Loading..."}},setup(e){return(s,r)=>(T(),M("div",fr,[r[0]||(r[0]=v("div",{class:"loading-spinner"},null,-1)),v("p",null,D(e.message),1)]))}},Mr=Y(hr,[["__scopeId","data-v-130f5abf"]]),gr={class:"message-container"},mr={class:"message-content"},yr={key:0,class:"error-detail"},pr={class:"button-row"},wr={__name:"AccessDenied",props:{title:{type:String,default:"Access Denied"},icon:{type:String,default:"🔒"},message:{type:String,default:null}},setup(e){function s(){window.location.reload()}return(r,t)=>(T(),M("div",gr,[v("div",mr,[v("h2",null,D(e.icon)+" "+D(e.title),1),e.message?(T(),M("p",yr,D(e.message),1)):L("",!0),v("div",pr,[v("button",{class:"btn-secondary",onClick:t[0]||(t[0]=(...n)=>U(We)&&U(We)(...n))},"Back"),v("button",{class:"btn-primary",onClick:s},"Reload Page")])])]))}},Nr=Y(wr,[["__scopeId","data-v-a7b258e7"]]);export{Nr as A,_r as B,Mr as L,Ir as M,Ar as N,Tr as R,Br as U,kr as _,Rr as a,Er as b,Sr as c,he as u};