paskia 0.7.1__py3-none-any.whl → 0.8.0__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 (40) hide show
  1. paskia/_version.py +2 -2
  2. paskia/authsession.py +12 -49
  3. paskia/bootstrap.py +30 -25
  4. paskia/db/__init__.py +163 -401
  5. paskia/db/background.py +128 -0
  6. paskia/db/jsonl.py +132 -0
  7. paskia/db/operations.py +1241 -0
  8. paskia/db/structs.py +148 -0
  9. paskia/fastapi/admin.py +456 -215
  10. paskia/fastapi/api.py +16 -15
  11. paskia/fastapi/authz.py +7 -2
  12. paskia/fastapi/mainapp.py +2 -1
  13. paskia/fastapi/remote.py +20 -20
  14. paskia/fastapi/reset.py +9 -10
  15. paskia/fastapi/user.py +10 -18
  16. paskia/fastapi/ws.py +22 -19
  17. paskia/frontend-build/auth/admin/index.html +3 -3
  18. paskia/frontend-build/auth/assets/AccessDenied-aTdCvz9k.js +8 -0
  19. paskia/frontend-build/auth/assets/admin-BeNu48FR.css +1 -0
  20. paskia/frontend-build/auth/assets/admin-tVs8oyLv.js +1 -0
  21. paskia/frontend-build/auth/assets/{auth-BU_O38k2.css → auth-BKX7shEe.css} +1 -1
  22. paskia/frontend-build/auth/assets/auth-Dk3q4pNS.js +1 -0
  23. paskia/frontend-build/auth/index.html +3 -3
  24. paskia/globals.py +7 -10
  25. paskia/migrate/__init__.py +274 -0
  26. paskia/migrate/sql.py +381 -0
  27. paskia/util/permutil.py +16 -5
  28. paskia/util/sessionutil.py +3 -2
  29. paskia/util/userinfo.py +12 -26
  30. paskia-0.8.0.dist-info/METADATA +94 -0
  31. {paskia-0.7.1.dist-info → paskia-0.8.0.dist-info}/RECORD +33 -29
  32. {paskia-0.7.1.dist-info → paskia-0.8.0.dist-info}/entry_points.txt +1 -0
  33. paskia/db/sql.py +0 -1424
  34. paskia/frontend-build/auth/assets/AccessDenied-C-lL9vbN.js +0 -8
  35. paskia/frontend-build/auth/assets/admin-Cs6Mg773.css +0 -1
  36. paskia/frontend-build/auth/assets/admin-Df5_Damp.js +0 -1
  37. paskia/frontend-build/auth/assets/auth-Df3pjeSS.js +0 -1
  38. paskia/util/tokens.py +0 -44
  39. paskia-0.7.1.dist-info/METADATA +0 -22
  40. {paskia-0.7.1.dist-info → paskia-0.8.0.dist-info}/WHEEL +0 -0
paskia/fastapi/api.py CHANGED
@@ -13,19 +13,17 @@ from fastapi import (
13
13
  from fastapi.responses import JSONResponse
14
14
  from fastapi.security import HTTPBearer
15
15
 
16
+ from paskia import db
16
17
  from paskia.authsession import (
17
18
  EXPIRES,
18
19
  get_reset,
19
20
  get_session,
20
21
  refresh_session_token,
21
- session_expiry,
22
22
  )
23
23
  from paskia.fastapi import authz, session, user
24
24
  from paskia.fastapi.session import AUTH_COOKIE, AUTH_COOKIE_NAME
25
- from paskia.globals import db
26
25
  from paskia.globals import passkey as global_passkey
27
26
  from paskia.util import frontend, hostutil, htmlutil, passphrase, userinfo
28
- from paskia.util.tokens import session_key
29
27
 
30
28
  bearer_auth = HTTPBearer(auto_error=True)
31
29
 
@@ -77,6 +75,7 @@ async def validate_token(
77
75
  request: Request,
78
76
  response: Response,
79
77
  perm: list[str] = Query([]),
78
+ max_age: str | None = Query(None),
80
79
  auth=AUTH_COOKIE,
81
80
  ):
82
81
  """Validate the current session and extend its expiry.
@@ -86,14 +85,18 @@ async def validate_token(
86
85
  refresh endpoint.
87
86
  """
88
87
  try:
89
- ctx = await authz.verify(auth, perm, host=request.headers.get("host"))
88
+ ctx = await authz.verify(
89
+ auth,
90
+ perm,
91
+ host=request.headers.get("host"),
92
+ max_age=max_age,
93
+ )
90
94
  except HTTPException:
91
95
  # Global handler will clear cookie if 401
92
96
  raise
93
97
  renewed = False
94
98
  if auth:
95
- current_expiry = session_expiry(ctx.session)
96
- consumed = EXPIRES - (current_expiry - datetime.now(timezone.utc))
99
+ consumed = EXPIRES - (ctx.session.expiry - datetime.now(timezone.utc))
97
100
  if not timedelta(0) < consumed < _REFRESH_INTERVAL:
98
101
  try:
99
102
  await refresh_session_token(
@@ -143,7 +146,7 @@ async def forward_authentication(
143
146
  )
144
147
  role_permissions = set(ctx.role.permissions or [])
145
148
  if ctx.permissions:
146
- role_permissions.update(permission.id for permission in ctx.permissions)
149
+ role_permissions.update(permission.scope for permission in ctx.permissions)
147
150
 
148
151
  remote_headers: dict[str, str] = {
149
152
  "Remote-User": str(ctx.user.uuid),
@@ -154,13 +157,11 @@ async def forward_authentication(
154
157
  "Remote-Role": str(ctx.role.uuid),
155
158
  "Remote-Role-Name": ctx.role.display_name,
156
159
  "Remote-Session-Expires": (
157
- session_expiry(ctx.session)
158
- .astimezone(timezone.utc)
160
+ ctx.session.expiry.astimezone(timezone.utc)
159
161
  .isoformat()
160
162
  .replace("+00:00", "Z")
161
- if session_expiry(ctx.session).tzinfo
162
- else session_expiry(ctx.session)
163
- .replace(tzinfo=timezone.utc)
163
+ if ctx.session.expiry.tzinfo
164
+ else ctx.session.expiry.replace(tzinfo=timezone.utc)
164
165
  .isoformat()
165
166
  .replace("+00:00", "Z")
166
167
  ),
@@ -221,7 +222,7 @@ async def api_token_info(token: str):
221
222
  # Check if this is a reset token
222
223
  try:
223
224
  reset_token = await get_reset(token)
224
- user = await db.instance.get_user_by_uuid(reset_token.user_uuid)
225
+ user = db.get_user_by_uuid(reset_token.user_uuid)
225
226
  return {
226
227
  "type": "reset",
227
228
  "user_name": user.display_name,
@@ -287,11 +288,11 @@ async def api_logout(request: Request, response: Response, auth=AUTH_COOKIE):
287
288
  if not auth:
288
289
  return {"message": "Already logged out"}
289
290
  try:
290
- await get_session(auth, host=request.headers.get("host"))
291
+ _s = await get_session(auth, host=request.headers.get("host"))
291
292
  except ValueError:
292
293
  return {"message": "Already logged out"}
293
294
  with suppress(Exception):
294
- await db.instance.delete_session(session_key(auth))
295
+ db.delete_session(auth)
295
296
  session.clear_session_cookie(response)
296
297
  return {"message": "Logged out successfully"}
297
298
 
paskia/fastapi/authz.py CHANGED
@@ -94,14 +94,19 @@ async def verify(
94
94
 
95
95
  if not match(ctx, perm):
96
96
  # Determine which permissions are missing for clearer diagnostics
97
- missing = sorted(set(perm) - set(ctx.role.permissions))
97
+ effective_scopes = (
98
+ {p.scope for p in (ctx.permissions or [])}
99
+ if ctx.permissions
100
+ else set(ctx.role.permissions or [])
101
+ )
102
+ missing = sorted(set(perm) - effective_scopes)
98
103
  logger.warning(
99
104
  "Permission denied: user=%s role=%s missing=%s required=%s granted=%s", # noqa: E501
100
105
  getattr(ctx.user, "uuid", "?"),
101
106
  getattr(ctx.role, "display_name", "?"),
102
107
  missing,
103
108
  perm,
104
- ctx.role.permissions,
109
+ list(effective_scopes),
105
110
  )
106
111
  raise AuthException(
107
112
  status_code=403, mode="forbidden", detail="Permission required"
paskia/fastapi/mainapp.py CHANGED
@@ -50,7 +50,7 @@ async def lifespan(app: FastAPI): # pragma: no cover - startup path
50
50
  yield
51
51
 
52
52
 
53
- app = FastAPI(lifespan=lifespan)
53
+ app = FastAPI(lifespan=lifespan, redirect_slashes=False)
54
54
 
55
55
  # Apply redirections to auth-host if configured (deny access to restricted endpoints, remove /auth/)
56
56
  app.middleware("http")(auth_host.redirect_middleware)
@@ -96,6 +96,7 @@ async def admin_root_redirect():
96
96
 
97
97
 
98
98
  @app.get("/admin/", include_in_schema=False)
99
+ @app.get("/auth/admin/", include_in_schema=False)
99
100
  async def admin_root(request: Request, auth=AUTH_COOKIE):
100
101
  return await admin.adminapp(request, auth) # Delegated to admin app
101
102
 
paskia/fastapi/remote.py CHANGED
@@ -15,11 +15,10 @@ from uuid import UUID
15
15
  import base64url
16
16
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
17
17
 
18
- from paskia import remoteauth
19
- from paskia.authsession import create_session
18
+ from paskia import db, remoteauth
20
19
  from paskia.fastapi.session import infodict
21
20
  from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
22
- from paskia.globals import db, passkey
21
+ from paskia.globals import passkey
23
22
  from paskia.util import passphrase, pow
24
23
 
25
24
  # Create a FastAPI subapp for remote auth WebSocket endpoints
@@ -323,9 +322,7 @@ async def websocket_remote_auth_permit(ws: WebSocket):
323
322
 
324
323
  # Fetch and verify credential
325
324
  try:
326
- stored_cred = await db.instance.get_credential_by_id(
327
- credential.raw_id
328
- )
325
+ stored_cred = db.get_credential_by_id(credential.raw_id)
329
326
  except ValueError:
330
327
  raise ValueError(
331
328
  f"This passkey is no longer registered with {passkey.instance.rp_name}"
@@ -336,9 +333,6 @@ async def websocket_remote_auth_permit(ws: WebSocket):
336
333
  credential, webauthn_challenge, stored_cred, origin
337
334
  )
338
335
 
339
- # Update credential last_used
340
- await db.instance.login(stored_cred.user_uuid, stored_cred)
341
-
342
336
  # Create a session for the REQUESTING device
343
337
  assert stored_cred.uuid is not None
344
338
 
@@ -348,34 +342,40 @@ async def websocket_remote_auth_permit(ws: WebSocket):
348
342
  if request.action == "register":
349
343
  # For registration, create a reset token for device addition
350
344
  from paskia.authsession import expires
351
- from paskia.util import tokens
345
+ from paskia.util import hostutil
352
346
 
353
347
  token_str = passphrase.generate()
354
348
  expiry = expires()
355
- await db.instance.create_reset_token(
349
+ db.create_reset_token(
356
350
  user_uuid=stored_cred.user_uuid,
357
- key=tokens.reset_key(token_str),
351
+ passphrase=token_str,
358
352
  expiry=expiry,
359
353
  token_type="device addition",
360
354
  )
361
355
  reset_token = token_str
362
- # Also create a session so the device is logged in?
363
- # User requested: "We can make the flow always create a new session, but make additional tokens for other possibilities."
364
- session_token = await create_session(
356
+ # Also create a session so the device is logged in
357
+ normalized_host = hostutil.normalize_host(request.host)
358
+ session_token = db.login(
365
359
  user_uuid=stored_cred.user_uuid,
366
- credential_uuid=stored_cred.uuid,
367
- host=request.host,
360
+ credential=stored_cred,
361
+ host=normalized_host,
368
362
  ip=request.ip,
369
363
  user_agent=request.user_agent,
364
+ expiry=expires(),
370
365
  )
371
366
  else:
372
367
  # Default login action
373
- session_token = await create_session(
368
+ from paskia.authsession import expires
369
+ from paskia.util import hostutil
370
+
371
+ normalized_host = hostutil.normalize_host(request.host)
372
+ session_token = db.login(
374
373
  user_uuid=stored_cred.user_uuid,
375
- credential_uuid=stored_cred.uuid,
376
- host=request.host,
374
+ credential=stored_cred,
375
+ host=normalized_host,
377
376
  ip=request.ip,
378
377
  user_agent=request.user_agent,
378
+ expiry=expires(),
379
379
  )
380
380
 
381
381
  # Complete the remote auth request (notifies the waiting device)
paskia/fastapi/reset.py CHANGED
@@ -16,9 +16,8 @@ import asyncio
16
16
  from uuid import UUID
17
17
 
18
18
  from paskia import authsession as _authsession
19
- from paskia import globals as _g
19
+ from paskia import db as _db
20
20
  from paskia.util import hostutil, passphrase
21
- from paskia.util import tokens as _tokens
22
21
 
23
22
 
24
23
  async def _resolve_targets(query: str | None):
@@ -27,9 +26,9 @@ async def _resolve_targets(query: str | None):
27
26
  targets: list[tuple] = []
28
27
  try:
29
28
  q_uuid = UUID(query)
30
- perm_orgs = await _g.db.instance.get_permission_organizations("auth:admin")
29
+ perm_orgs = _db.get_permission_organizations("auth:admin")
31
30
  for o in perm_orgs:
32
- users = await _g.db.instance.get_organization_users(str(o.uuid))
31
+ users = _db.get_organization_users(str(o.uuid))
33
32
  for u, role_name in users:
34
33
  if u.uuid == q_uuid:
35
34
  return [(u, role_name)]
@@ -38,9 +37,9 @@ async def _resolve_targets(query: str | None):
38
37
  pass
39
38
  # Substring search
40
39
  needle = query.lower()
41
- perm_orgs = await _g.db.instance.get_permission_organizations("auth:admin")
40
+ perm_orgs = _db.get_permission_organizations("auth:admin")
42
41
  for o in perm_orgs:
43
- users = await _g.db.instance.get_organization_users(str(o.uuid))
42
+ users = _db.get_organization_users(str(o.uuid))
44
43
  for u, role_name in users:
45
44
  if needle in (u.display_name or "").lower():
46
45
  targets.append((u, role_name))
@@ -53,10 +52,10 @@ async def _resolve_targets(query: str | None):
53
52
  deduped.append((u, role_name))
54
53
  return deduped
55
54
  # No query -> master admin
56
- perm_orgs = await _g.db.instance.get_permission_organizations("auth:admin")
55
+ perm_orgs = _db.get_permission_organizations("auth:admin")
57
56
  if not perm_orgs:
58
57
  return []
59
- users = await _g.db.instance.get_organization_users(str(perm_orgs[0].uuid))
58
+ users = _db.get_organization_users(str(perm_orgs[0].uuid))
60
59
  admin_users = [pair for pair in users if pair[1] == "Administration"]
61
60
  return admin_users[:1]
62
61
 
@@ -64,9 +63,9 @@ async def _resolve_targets(query: str | None):
64
63
  async def _create_reset(user, role_name: str):
65
64
  token = passphrase.generate()
66
65
  expiry = _authsession.reset_expires()
67
- await _g.db.instance.create_reset_token(
66
+ _db.create_reset_token(
67
+ passphrase=token,
68
68
  user_uuid=user.uuid,
69
- key=_tokens.reset_key(token),
70
69
  expiry=expiry,
71
70
  token_type="manual reset",
72
71
  )
paskia/fastapi/user.py CHANGED
@@ -10,6 +10,7 @@ from fastapi import (
10
10
  )
11
11
  from fastapi.responses import JSONResponse
12
12
 
13
+ from paskia import db
13
14
  from paskia.authsession import (
14
15
  delete_credential,
15
16
  expires,
@@ -17,9 +18,7 @@ from paskia.authsession import (
17
18
  )
18
19
  from paskia.fastapi import authz, session
19
20
  from paskia.fastapi.session import AUTH_COOKIE
20
- from paskia.globals import db
21
- from paskia.util import hostutil, passphrase, tokens
22
- from paskia.util.tokens import decode_session_key, session_key
21
+ from paskia.util import hostutil, passphrase
23
22
 
24
23
  app = FastAPI()
25
24
 
@@ -33,7 +32,7 @@ async def auth_exception_handler(_request, exc: authz.AuthException):
33
32
  )
34
33
 
35
34
 
36
- @app.put("/display-name")
35
+ @app.patch("/display-name")
37
36
  async def user_update_display_name(
38
37
  request: Request,
39
38
  response: Response,
@@ -55,7 +54,7 @@ async def user_update_display_name(
55
54
  raise HTTPException(status_code=400, detail="display_name required")
56
55
  if len(new_name) > 64:
57
56
  raise HTTPException(status_code=400, detail="display_name too long")
58
- await db.instance.update_user_display_name(s.user_uuid, new_name)
57
+ db.update_user_display_name(s.user_uuid, new_name)
59
58
  return {"status": "ok"}
60
59
 
61
60
 
@@ -69,7 +68,7 @@ async def api_logout_all(request: Request, response: Response, auth=AUTH_COOKIE)
69
68
  raise authz.AuthException(
70
69
  status_code=401, detail="Session expired", mode="login"
71
70
  )
72
- await db.instance.delete_sessions_for_user(s.user_uuid)
71
+ db.delete_sessions_for_user(s.user_uuid)
73
72
  session.clear_session_cookie(response)
74
73
  return {"message": "Logged out from all hosts"}
75
74
 
@@ -92,19 +91,12 @@ async def api_delete_session(
92
91
  status_code=401, detail="Session expired", mode="login"
93
92
  ) from exc
94
93
 
95
- try:
96
- target_key = decode_session_key(session_id)
97
- except ValueError as exc:
98
- raise HTTPException(
99
- status_code=400, detail="Invalid session identifier"
100
- ) from exc
101
-
102
- target_session = await db.instance.get_session(target_key)
94
+ target_session = db.get_session(session_id)
103
95
  if not target_session or target_session.user_uuid != current_session.user_uuid:
104
96
  raise HTTPException(status_code=404, detail="Session not found")
105
97
 
106
- await db.instance.delete_session(target_key)
107
- current_terminated = target_key == session_key(auth)
98
+ db.delete_session(session_id)
99
+ current_terminated = session_id == auth
108
100
  if current_terminated:
109
101
  session.clear_session_cookie(response) # explicit because 200
110
102
  return {"status": "ok", "current_session_terminated": current_terminated}
@@ -144,9 +136,9 @@ async def api_create_link(
144
136
  ) from e
145
137
  token = passphrase.generate()
146
138
  expiry = expires()
147
- await db.instance.create_reset_token(
139
+ db.create_reset_token(
148
140
  user_uuid=s.user_uuid,
149
- key=tokens.reset_key(token),
141
+ passphrase=token,
150
142
  expiry=expiry,
151
143
  token_type="device addition",
152
144
  )
paskia/fastapi/ws.py CHANGED
@@ -2,13 +2,13 @@ from uuid import UUID
2
2
 
3
3
  from fastapi import FastAPI, WebSocket
4
4
 
5
- from paskia.authsession import create_session, get_reset, get_session
5
+ from paskia import db
6
+ from paskia.authsession import expires, get_reset, get_session
6
7
  from paskia.fastapi import authz, remote
7
8
  from paskia.fastapi.session import AUTH_COOKIE, infodict
8
9
  from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
9
- from paskia.globals import db, passkey
10
- from paskia.util import passphrase
11
- from paskia.util.tokens import create_token, session_key
10
+ from paskia.globals import passkey
11
+ from paskia.util import hostutil, passphrase
12
12
 
13
13
  # Create a FastAPI subapp for WebSocket endpoints
14
14
  app = FastAPI()
@@ -65,25 +65,23 @@ async def websocket_register_add(
65
65
  s = ctx.session
66
66
 
67
67
  # Get user information and determine effective user_name for this registration
68
- user = await db.instance.get_user_by_uuid(user_uuid)
68
+ user = db.get_user_by_uuid(user_uuid)
69
69
  user_name = user.display_name
70
70
  if name is not None:
71
71
  stripped = name.strip()
72
72
  if stripped:
73
73
  user_name = stripped
74
- challenge_ids = await db.instance.get_credentials_by_user_uuid(user_uuid)
74
+ challenge_ids = db.get_credentials_by_user_uuid(user_uuid)
75
75
 
76
76
  # WebAuthn registration
77
77
  credential = await register_chat(ws, user_uuid, user_name, origin, challenge_ids)
78
78
 
79
79
  # Create a new session and store everything in database
80
- token = create_token()
81
80
  metadata = infodict(ws, "authenticated")
82
- await db.instance.create_credential_session( # type: ignore[attr-defined]
81
+ token = db.create_credential_session( # type: ignore[attr-defined]
83
82
  user_uuid=user_uuid,
84
83
  credential=credential,
85
84
  reset_key=(s.key if reset is not None else None),
86
- session_key=session_key(token),
87
85
  display_name=user_name,
88
86
  host=host,
89
87
  ip=metadata.get("ip"),
@@ -115,9 +113,7 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
115
113
  try:
116
114
  session = await get_session(auth, host=host)
117
115
  session_user_uuid = session.user_uuid
118
- credential_ids = await db.instance.get_credentials_by_user_uuid(
119
- session_user_uuid
120
- )
116
+ credential_ids = db.get_credentials_by_user_uuid(session_user_uuid)
121
117
  except ValueError:
122
118
  pass # Invalid/expired session - allow normal authentication
123
119
 
@@ -129,7 +125,7 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
129
125
  credential = passkey.instance.auth_parse(await ws.receive_json())
130
126
  # Fetch from the database by credential ID
131
127
  try:
132
- stored_cred = await db.instance.get_credential_by_id(credential.raw_id)
128
+ stored_cred = db.get_credential_by_id(credential.raw_id)
133
129
  except ValueError:
134
130
  raise ValueError(
135
131
  f"This passkey is no longer registered with {passkey.instance.rp_name}"
@@ -141,18 +137,25 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
141
137
 
142
138
  # Verify the credential matches the stored data
143
139
  passkey.instance.auth_verify(credential, challenge, stored_cred, origin)
144
- # Update both credential and user's last_seen timestamp
145
- await db.instance.login(stored_cred.user_uuid, stored_cred)
146
140
 
147
- # Create a session token for the authenticated user
141
+ # Create session and update user/credential in a single transaction
148
142
  assert stored_cred.uuid is not None
149
143
  metadata = infodict(ws, "auth")
150
- token = await create_session(
144
+ normalized_host = hostutil.normalize_host(host)
145
+ if not normalized_host:
146
+ raise ValueError("Host required for session creation")
147
+ hostname = normalized_host.split(":")[0]
148
+ rp_id = passkey.instance.rp_id
149
+ if not (hostname == rp_id or hostname.endswith(f".{rp_id}")):
150
+ raise ValueError(f"Host must be the same as or a subdomain of {rp_id}")
151
+
152
+ token = db.login(
151
153
  user_uuid=stored_cred.user_uuid,
152
- credential_uuid=stored_cred.uuid,
153
- host=host,
154
+ credential=stored_cred,
155
+ host=normalized_host,
154
156
  ip=metadata.get("ip") or "",
155
157
  user_agent=metadata.get("user_agent") or "",
158
+ expiry=expires(),
156
159
  )
157
160
 
158
161
  await ws.send_json(
@@ -4,13 +4,13 @@
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-Df5_Damp.js"></script>
7
+ <script type="module" crossorigin src="/auth/assets/admin-tVs8oyLv.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-rKFEraYH.js">
9
9
  <link rel="modulepreload" crossorigin href="/auth/assets/helpers-DzjFIx78.js">
10
- <link rel="modulepreload" crossorigin href="/auth/assets/AccessDenied-C-lL9vbN.js">
10
+ <link rel="modulepreload" crossorigin href="/auth/assets/AccessDenied-aTdCvz9k.js">
11
11
  <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-BTzJAQlS.css">
12
12
  <link rel="stylesheet" crossorigin href="/auth/assets/AccessDenied-Bc249ASC.css">
13
- <link rel="stylesheet" crossorigin href="/auth/assets/admin-Cs6Mg773.css">
13
+ <link rel="stylesheet" crossorigin href="/auth/assets/admin-BeNu48FR.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="admin-app"></div>
@@ -0,0 +1,8 @@
1
+ import{M as At,r as F,N as Rt,O as Yt,P as le,Q as Tt,R as Qt,S as Gt,T as Wt,n as ze,U as Zt,c as x,w as He,V as Xt,W as en,H as Q,m as tn,X as nn,Y as on,b as R,f as L,z as U,d as T,e as v,t as D,g as se,_ as Y,F as oe,i as ce,B as z,D as Mt,l as de,Z as Nt,$ as Pt,a0 as Dt,a1 as Lt,o as qt,a2 as rn,a as Ft,C as sn,k as xe,h as an,a3 as un,L as ln,x as cn}from"./_plugin-vue_export-helper-rKFEraYH.js";import{f as G,h as ae,g as We}from"./helpers-DzjFIx78.js";let Ut;const fe=e=>Ut=e,Kt=Symbol();function Oe(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 br(){const e=At(!0),s=e.run(()=>F({}));let r=[],t=[];const n=Rt({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&&Gt()&&Wt(n),n}function ee(e,...s){e.forEach(r=>{r(...s)})}const dn=e=>e(),Xe=Symbol(),pe=Symbol();function je(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];Oe(n)&&Oe(t)&&e.hasOwnProperty(r)&&!le(t)&&!Tt(t)?e[r]=je(n,t):e[r]=t}return e}const fn=Symbol();function hn(e){return!Oe(e)||!Object.prototype.hasOwnProperty.call(e,fn)}const{assign:J}=Object;function gn(e){return!!(le(e)&&e.effect)}function mn(e,s,r,t){const{state:n,actions:o,getters:i}=s,a=r.state.value[e];let u;function l(){a||(r.state.value[e]=n?n():{});const f=Zt(r.state.value[e]);return J(f,o,Object.keys(i||{}).reduce((w,b)=>(w[b]=Rt(x(()=>{fe(r);const h=r._s.get(e);return i[b].call(h,h)})),w),{}))}return u=Ht(e,l,s,r,t,!0),u}function Ht(e,s,r={},t,n,o){let i;const a=J({actions:{}},r),u={deep:!0};let l,f,w=new Set,b=new Set,h;const d=t.state.value[e];!o&&!d&&(t.state.value[e]={}),F({});let g;function N(y){let m;l=f=!1,typeof y=="function"?(y(t.state.value[e]),m={type:re.patchFunction,storeId:e,events:h}):(je(t.state.value[e],y),m={type:re.patchObject,payload:y,storeId:e,events:h});const _=g=Symbol();ze().then(()=>{g===_&&(l=!0)}),f=!0,ee(w,m,t.state.value[e])}const A=o?function(){const{state:m}=r,_=m?m():{};this.$patch(E=>{J(E,_)})}:Vt;function P(){i.stop(),w.clear(),b.clear(),t._s.delete(e)}const c=(y,m="")=>{if(Xe in y)return y[pe]=m,y;const _=function(){fe(t);const E=Array.from(arguments),k=new Set,B=new Set;function M(q){k.add(q)}function V(q){B.add(q)}ee(b,{args:E,name:_[pe],store:I,after:M,onError:V});let K;try{K=y.apply(this&&this.$id===e?this:I,E)}catch(q){throw ee(B,q),q}return K instanceof Promise?K.then(q=>(ee(k,q),q)).catch(q=>(ee(B,q),Promise.reject(q))):(ee(k,K),K)};return _[Xe]=!0,_[pe]=m,_},S={_p:t,$id:e,$onAction:Ze.bind(null,b),$patch:N,$reset:A,$subscribe(y,m={}){const _=Ze(w,y,m.detached,()=>E()),E=i.run(()=>He(()=>t.state.value[e],k=>{(m.flush==="sync"?f:l)&&y({storeId:e,type:re.direct,events:h},k)},J({},u,m)));return _},$dispose:P},I=Yt(S);t._s.set(e,I);const p=(t._a&&t._a.runWithContext||dn)(()=>t._e.run(()=>(i=At()).run(()=>s({action:c}))));for(const y in p){const m=p[y];if(le(m)&&!gn(m)||Tt(m))o||(d&&hn(m)&&(le(m)?m.value=d[y]:je(m,d[y])),t.state.value[e][y]=m);else if(typeof m=="function"){const _=c(m,y);p[y]=_,a.actions[y]=m}}return J(I,p),J(Qt(I),p),Object.defineProperty(I,"$state",{get:()=>t.state.value[e],set:y=>{N(m=>{J(m,y)})}}),t._p.forEach(y=>{J(I,i.run(()=>y({store:I,app:t._a,pinia:t,options:a})))}),d&&o&&r.hydrate&&r.hydrate(I.$state,d),l=!0,f=!0,I}function yn(e,s,r){let t;const n=typeof s=="function";t=n?r:s;function o(i,a){const u=en();return i=i||(u?Xt(Kt,null):null),i&&fe(i),i=Ut,i._s.has(e)||(n?Ht(e,s,t,i):mn(e,t,i)),i._s.get(e)}return o.$id=e,o}const $e=yn("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"}),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")}}}}),pn={key:0,class:"global-status",style:{display:"block"}},vr={__name:"StatusMessage",setup(e){const s=$e();return(r,t)=>U(s).status.show?(T(),R("div",pn,[v("div",{class:se(["status",U(s).status.type])},D(U(s).status.message),3)])):L("",!0)}},wn=["href"],bn={key:0,class:"sep"},vn={__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 l=window.location.hash||window.location.pathname;for(let f=n.value.length-1;f>=0;f--){const w=n.value[f].href;if(w===l||w&&l.startsWith(w))return f}return n.value.length-1});function i(l){if(l.target===t.value){const f=t.value.querySelectorAll("a"),w=Math.min(o.value,f.length-1);f[w]&&f[w].focus()}}function a(l){const f=z(l);f&&(f==="left"||f==="right")&&(l.preventDefault(),Mt(t.value,l.target,f,{itemSelector:"a"}))}function u(){const l=t.value?.querySelectorAll("a");if(l?.length){const f=Math.min(o.value,l.length-1);l[f]?.focus()}}return s({focusCurrent:u}),(l,f)=>n.value.length>1?(T(),R("nav",{key:0,ref_key:"navRef",ref:t,class:"breadcrumbs","aria-label":"Breadcrumb",tabindex:"0",onFocusin:i,onKeydown:a},[v("ol",null,[(T(!0),R(oe,null,ce(n.value,(w,b)=>(T(),R("li",{key:b},[v("a",{href:w.href,tabindex:"-1"},D(w.label),9,wn),b<n.value.length-1?(T(),R("span",bn," — ")):L("",!0)]))),128))])],544)):L("",!0)}},Cr=Y(vn,[["__scopeId","data-v-6344dbb8"]]),Cn={key:0},Sn={key:1},_n=["onFocusin","onKeydown"],En={class:"item-top"},kn={class:"item-icon"},Bn=["src","alt"],In={key:1,class:"auth-emoji"},An={class:"item-title"},Rn={class:"item-actions"},Tn={key:0,class:"badge badge-current"},Mn={key:1,class:"badge badge-current"},Nn={key:2,class:"badge badge-current"},Pn=["onClick","disabled","title"],Dn={class:"item-details"},Ln={class:"credential-dates"},qn={class:"date-value"},Fn={class:"date-value"},Un={class:"date-value"},Sr={__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=h=>{t("credentialHover",h)},o=h=>{h.currentTarget.contains(h.relatedTarget)||t("credentialHover",null)},i=h=>{h.currentTarget.matches(":focus")||(h.currentTarget.focus(),h.stopPropagation())},a=(h,d)=>{Pt(h,()=>{r.allowDelete&&!d.is_current_session&&t("delete",d)})},u=h=>{if(r.navigationDisabled)return;const d=h.currentTarget;if(h.target===d){const g=d.querySelector(".credential-item");g&&g.focus()}},l=h=>{r.navigationDisabled||Dt(h,d=>t("navigate-out",d))},f=(h,d)=>{if(a(h,d),h.defaultPrevented||r.navigationDisabled)return;const g=z(h);if(g){h.preventDefault();const N=h.currentTarget.closest(".credential-list");Nt(N,h.currentTarget,g,{itemSelector:".credential-item"})==="boundary"&&t("navigate-out",g)}},w=h=>{const d=r.aaguidInfo?.[h.aaguid];return d?d.name:"Unknown Authenticator"},b=h=>{const d=r.aaguidInfo?.[h.aaguid];if(!d)return null;const N=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"icon_dark":"icon_light";return d[N]||null};return(h,d)=>(T(),R("div",{class:"credential-list",tabindex:"0",onFocusin:u,onKeydown:l},[e.loading?(T(),R("div",Cn,[...d[2]||(d[2]=[v("p",null,"Loading credentials...",-1)])])):e.credentials?.length?(T(!0),R(oe,{key:2},ce(e.credentials,g=>(T(),R("div",{key:g.credential_uuid,class:se(["credential-item",{"current-session":g.is_current_session&&!e.hoveredCredentialUuid&&!e.hoveredSessionCredentialUuid,"is-hovered":e.hoveredCredentialUuid===g.credential_uuid,"is-linked-session":e.hoveredSessionCredentialUuid===g.credential_uuid}]),tabindex:"-1",onMousedown:d[0]||(d[0]=de(()=>{},["prevent"])),onClickCapture:i,onFocusin:N=>n(g.credential_uuid),onFocusout:d[1]||(d[1]=N=>o(N)),onKeydown:N=>f(N,g)},[v("div",En,[v("div",kn,[b(g)?(T(),R("img",{key:0,src:b(g),alt:w(g),class:"auth-icon",width:"32",height:"32"},null,8,Bn)):(T(),R("span",In,"🔑"))]),v("h4",An,D(w(g)),1),v("div",Rn,[g.is_current_session&&!e.hoveredCredentialUuid&&!e.hoveredSessionCredentialUuid?(T(),R("span",Tn,"Current")):e.hoveredCredentialUuid===g.credential_uuid?(T(),R("span",Mn,"Selected")):e.hoveredSessionCredentialUuid===g.credential_uuid?(T(),R("span",Nn,"Linked")):L("",!0),e.allowDelete?(T(),R("button",{key:3,onClick:N=>h.$emit("delete",g),class:"btn-card-delete",disabled:g.is_current_session,title:g.is_current_session?"Cannot delete current session credential":"Delete passkey and terminate any linked sessions.",tabindex:"-1"},"❌",8,Pn)):L("",!0)])]),v("div",Dn,[v("div",Ln,[d[4]||(d[4]=v("span",{class:"date-label"},"Created:",-1)),v("span",qn,D(U(G)(g.created_at)),1),d[5]||(d[5]=v("span",{class:"date-label"},"Last used:",-1)),v("span",Fn,D(U(G)(g.last_used)),1),d[6]||(d[6]=v("span",{class:"date-label"},"Last verified:",-1)),v("span",Un,D(U(G)(g.last_verified)),1)])])],42,_n))),128)):(T(),R("div",Sn,[...d[3]||(d[3]=[v("p",null,"No passkeys found.",-1)])]))],32))}},Kn={class:"user-name-heading"},Vn={class:"user-name-row"},Hn=["title"],On={key:0,class:"org-role-sub"},jn={key:0,class:"org-line"},zn={key:1,class:"role-line"},xn={class:"user-details"},$n={class:"date-value"},Jn={class:"date-value"},Yn={class:"date-value"},Qn={key:1,class:"user-info-extra"},Gn={__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;$e();const n=x(()=>!!r.name);return(o,i)=>n.value?(T(),R("div",{key:0,class:se(["user-info",{"has-extra":o.$slots.default}])},[v("h3",Kn,[i[1]||(i[1]=v("span",{class:"icon"},"👤",-1)),v("span",Vn,[v("span",{class:"display-name",title:e.name},D(e.name),9,Hn),e.canEdit&&e.updateEndpoint?(T(),R("button",{key:0,class:"mini-btn",onClick:i[0]||(i[0]=a=>t("editName")),title:"Edit name"},"✏️")):L("",!0)])]),e.orgDisplayName||e.roleName?(T(),R("div",On,[e.orgDisplayName?(T(),R("div",jn,D(e.orgDisplayName),1)):L("",!0),e.roleName?(T(),R("div",zn,D(e.roleName),1)):L("",!0)])):L("",!0),v("div",xn,[i[2]||(i[2]=v("span",{class:"date-label"},[v("strong",null,"Visits:")],-1)),v("span",$n,D(e.visits||0),1),i[3]||(i[3]=v("span",{class:"date-label"},[v("strong",null,"Registered:")],-1)),v("span",Jn,D(U(G)(e.createdAt)),1),i[4]||(i[4]=v("span",{class:"date-label"},[v("strong",null,"Last seen:")],-1)),v("span",Yn,D(U(G)(e.lastSeen)),1)]),o.$slots.default?(T(),R("div",Qn,[Lt(o.$slots,"default",{},void 0)])):L("",!0)],2)):L("",!0)}},_r=Y(Gn,[["__scopeId","data-v-ce373d6c"]]),Wn={__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 u=[s.focusFallback?.$el||s.focusFallback,i.closest("[data-nav-group]"),i.parentElement?.closest("section"),document.querySelector(".view-root")].filter(Boolean);for(const l of u){if(!l)continue;const f=l.querySelectorAll(s.focusSiblingSelector);if(f.length>0){const w=Math.min(s.focusIndex,f.length-1),b=f[w];if(b&&!b.disabled){b.focus();return}}}}const a=s.focusFallback?.$el||s.focusFallback;if(a&&document.body.contains(a)){const u=a.querySelector?.('button:not([disabled]), a, [tabindex="0"]')||a;if(u?.focus){u.focus();return}}},o=i=>{const a=z(i);if(!a)return;const u=i.target,l=u.closest(".modal-actions");if(l&&(a==="left"||a==="right"))i.preventDefault(),Mt(l,u,a,{itemSelector:"button"});else if(a==="up"&&l){i.preventDefault();const w=(l.closest("form")||l.closest(".modal-form"))?.querySelectorAll("input, textarea, select, button:not(.modal-actions button)");w&&w.length>0&&w[w.length-1].focus()}else if(a==="down"&&!l){const f=u.closest("form")||u.closest(".modal-form");if(f){i.preventDefault();const w=f.querySelector(".modal-actions");w&&sn(w,{primarySelector:".btn-primary",itemSelector:"button"})}}};return qt(()=>{t.value=document.activeElement,ze(()=>{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,a)=>(T(),R("dialog",{ref_key:"dialog",ref:r,onClose:a[0]||(a[0]=u=>i.$emit("close")),onKeydown:o},[Lt(i.$slots,"default",{},void 0)],544))}},Er=Y(Wn,[["__scopeId","data-v-2ebcbb0a"]]),Zn={class:"name-edit-form"},Xn=["for"],eo=["id","type","placeholder","disabled"],to={key:0,class:"error small"},no=["disabled"],oo=["disabled"],ro={__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:f=>t("update:modelValue",f)}),a=x(()=>r.inputId||o),u=f=>{if(z(f)==="up"){f.preventDefault(),n.value?.focus();return}};function l(){t("cancel")}return(f,w)=>(T(),R("div",Zn,[v("label",{for:a.value},[xe(D(e.label)+" ",1),an(v("input",{id:a.value,ref_key:"inputRef",ref:n,type:e.inputType,placeholder:e.placeholder,"onUpdate:modelValue":w[0]||(w[0]=b=>i.value=b),disabled:e.busy,required:""},null,8,eo),[[un,i.value]])],8,Xn),e.error?(T(),R("div",to,D(e.error),1)):L("",!0),v("div",{class:"modal-actions",onKeydown:u},[v("button",{type:"button",class:"btn-secondary",onClick:l,disabled:e.busy},D(e.cancelText),9,no),v("button",{type:"submit",class:"btn-primary",disabled:e.busy,"data-nav-primary":""},D(e.submitText),9,oo)],32)]))}},kr=Y(ro,[["__scopeId","data-v-b73321cf"]]),so={class:"section-block","data-component":"session-list-section"},io={class:"section-header"},ao={class:"section-description"},uo={class:"section-body"},lo=["onKeydown"],co=["href"],fo={class:"session-list"},ho=["onFocusin","onKeydown"],go={class:"item-top"},mo={class:"item-title"},yo={class:"item-actions"},po={key:0,class:"badge badge-current"},wo={key:1,class:"badge badge-current"},bo={key:2,class:"badge badge-current"},vo={key:3,class:"badge"},Co=["onClick","disabled","title"],So={class:"item-details"},_o={class:"session-dates"},Eo={class:"date-label"},ko=["onClick"],Bo={key:1,class:"empty-state"},Br={__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=$e(),o=F(null),i=F(null),a=c=>{i.value=c,o.value=c.ip||null,t("sessionHover",c)},u=c=>{c.currentTarget.contains(c.relatedTarget)||(i.value=null,o.value=null,t("sessionHover",null))},l=c=>{c.currentTarget.matches(":focus")||(c.currentTarget.focus(),c.stopPropagation())},f=c=>!!r.terminatingSessions[c],w=(c,S)=>{const I=c.currentTarget,p=I.querySelector(".session-list")?.querySelectorAll(".session-item"),y=Array.from(document.querySelectorAll(".session-group")),m=y.indexOf(I);if(c.key==="Enter"&&c.target===I){S&&I.querySelector("a")?.click();return}if(r.navigationDisabled)return;const _=z(c);if(["down","right"].includes(_)&&c.target===I){c.preventDefault(),p?.[0]?.focus();return}if(["up","left"].includes(_)&&c.target===I){c.preventDefault(),m>0?y[m-1].focus():t("navigate-out","up");return}Dt(c,E=>t("navigate-out",E))},b=(c,S)=>{if(Pt(c,()=>{f(S.id)||t("terminate",S)}),c.defaultPrevented||r.navigationDisabled)return;const I=z(c);if(I){c.preventDefault();const C=c.currentTarget.closest(".session-group"),p=C.querySelector(".session-list");if(Nt(p,c.currentTarget,I,{itemSelector:".session-item"})==="boundary"){if(I==="left"||I==="up")C?.focus();else if(I==="down"||I==="right"){const m=Array.from(document.querySelectorAll(".session-group")),_=m.indexOf(C);_<m.length-1?m[_+1].focus():t("navigate-out","down")}}}c.key==="Escape"&&(c.preventDefault(),c.currentTarget.closest(".session-group")?.focus())},h=c=>`${c.includes(":")?"http":"https"}://${c}`,d=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)}},g=c=>ae(c)??c,N=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=>N.value&&ae(c)===N.value,P=x(()=>{const c={};for(const p of r.sessions){const y=p.host||"";c[y]||(c[y]={sessions:[],isCurrentSite:!1}),c[y].sessions.push(p),p.is_current_host&&(c[y].isCurrentSite=!0)}for(const p in c)c[p].sessions.sort((y,m)=>new Date(m.last_renewed)-new Date(y.last_renewed));const S=new Intl.Collator(void 0,{numeric:!0,sensitivity:"base"}),I=Object.keys(c).sort(S.compare),C={};for(const p of I)C[p]=c[p];return C});return(c,S)=>(T(),R("section",so,[v("div",io,[S[2]||(S[2]=v("h2",null,"Active Sessions",-1)),v("p",ao,D(e.sectionDescription),1)]),v("div",uo,[v("div",null,[Array.isArray(e.sessions)&&e.sessions.length?(T(!0),R(oe,{key:0},ce(P.value,(I,C)=>(T(),R("div",{key:C,class:"session-group",tabindex:"0",onKeydown:p=>w(p,C)},[v("span",{class:se(["session-group-host",{"is-current-site":I.isCurrentSite}])},[S[3]||(S[3]=v("span",{class:"session-group-icon"},"🌐",-1)),C?(T(),R("a",{key:0,href:h(C),tabindex:"-1",target:"_blank",rel:"noopener noreferrer"},D(C),9,co)):(T(),R(oe,{key:1},[xe("Unbound host")],64))],2),v("div",fo,[(T(!0),R(oe,null,ce(I.sessions,p=>(T(),R("div",{key:p.id,class:se(["session-item",{"is-current":p.is_current&&!o.value&&!e.hoveredCredentialUuid,"is-hovered":i.value?.id===p.id,"is-linked-credential":e.hoveredCredentialUuid===p.credential_uuid}]),tabindex:"-1",onMousedown:S[0]||(S[0]=de(()=>{},["prevent"])),onClickCapture:l,onFocusin:y=>a(p),onFocusout:S[1]||(S[1]=y=>u(y)),onKeydown:y=>b(y,p)},[v("div",go,[v("h4",mo,D(p.user_agent),1),v("div",yo,[p.is_current&&!o.value&&!e.hoveredCredentialUuid?(T(),R("span",po,"Current")):i.value?.id===p.id?(T(),R("span",wo,"Selected")):e.hoveredCredentialUuid===p.credential_uuid?(T(),R("span",bo,"Linked")):!e.hoveredCredentialUuid&&A(p.ip)?(T(),R("span",vo,"Same IP")):L("",!0),v("button",{onClick:y=>c.$emit("terminate",p),class:"btn-card-delete",disabled:f(p.id),title:f(p.id)?"Terminating...":"Terminate session",tabindex:"-1"},"❌",8,Co)])]),v("div",So,[v("div",_o,[v("span",Eo,D(U(G)(p.last_renewed)),1),v("span",{class:"date-value",onClick:y=>d(p.ip),title:"Click to copy full IP"},D(g(p.ip)),9,ko)])])],42,ho))),128))])],40,lo))),128)):(T(),R("div",Bo,[v("p",null,D(e.emptyMessage),1)]))])])]))}};function Io(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var te={},we,et;function Ao(){return et||(et=1,we=function(){return typeof Promise=="function"&&Promise.prototype&&Promise.prototype.then}),we}var be={},$={},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 ve={},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}}})(ve)),ve}var Ce,ot;function Ro(){if(ot)return Ce;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++}},Ce=e,Ce}var Se,rt;function To(){if(rt)return Se;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]},Se=e,Se}var _e={},st;function Mo(){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,a=[o-7];for(let u=1;u<n-1;u++)a[u]=a[u-1]-i;return a.push(6),a.reverse()},e.getPositions=function(t){const n=[],o=e.getRowColCoords(t),i=o.length;for(let a=0;a<i;a++)for(let u=0;u<i;u++)a===0&&u===0||a===0&&u===i-1||a===i-1&&u===0||n.push([o[a],o[u]]);return n}})(_e)),_e}var Ee={},it;function No(){if(it)return Ee;it=1;const e=W().getSymbolSize,s=7;return Ee.getPositions=function(t){const n=e(t);return[[0,0],[n-s,0],[0,n-s]]},Ee}var ke={},at;function Po(){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,a=0,u=0,l=null,f=null;for(let w=0;w<o;w++){a=u=0,l=f=null;for(let b=0;b<o;b++){let h=n.get(w,b);h===l?a++:(a>=5&&(i+=s.N1+(a-5)),l=h,a=1),h=n.get(b,w),h===f?u++:(u>=5&&(i+=s.N1+(u-5)),f=h,u=1)}a>=5&&(i+=s.N1+(a-5)),u>=5&&(i+=s.N1+(u-5))}return i},e.getPenaltyN2=function(n){const o=n.size;let i=0;for(let a=0;a<o-1;a++)for(let u=0;u<o-1;u++){const l=n.get(a,u)+n.get(a,u+1)+n.get(a+1,u)+n.get(a+1,u+1);(l===4||l===0)&&i++}return i*s.N2},e.getPenaltyN3=function(n){const o=n.size;let i=0,a=0,u=0;for(let l=0;l<o;l++){a=u=0;for(let f=0;f<o;f++)a=a<<1&2047|n.get(l,f),f>=10&&(a===1488||a===93)&&i++,u=u<<1&2047|n.get(f,l),f>=10&&(u===1488||u===93)&&i++}return i*s.N3},e.getPenaltyN4=function(n){let o=0;const i=n.data.length;for(let u=0;u<i;u++)o+=n.data[u];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 a=0;a<i;a++)for(let u=0;u<i;u++)o.isReserved(u,a)||o.xor(u,a,r(n,u,a))},e.getBestMask=function(n,o){const i=Object.keys(e.Patterns).length;let a=0,u=1/0;for(let l=0;l<i;l++){o(l),e.applyMask(l,n);const f=e.getPenaltyN1(n)+e.getPenaltyN2(n)+e.getPenaltyN3(n)+e.getPenaltyN4(n);e.applyMask(l,n),f<u&&(u=f,a=l)}return a}})(ke)),ke}var ue={},ut;function Ot(){if(ut)return ue;ut=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 ue.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}},ue.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}},ue}var Be={},ne={},lt;function Do(){if(lt)return ne;lt=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 Lo(){return ct||(ct=1,(function(e){const s=Do();e.mul=function(t,n){const o=new Uint8Array(t.length+n.length-1);for(let i=0;i<t.length;i++)for(let a=0;a<n.length;a++)o[i+a]^=s.mul(t[i],n[a]);return o},e.mod=function(t,n){let o=new Uint8Array(t);for(;o.length-n.length>=0;){const i=o[0];for(let u=0;u<n.length;u++)o[u]^=s.mul(n[u],i);let a=0;for(;a<o.length&&o[a]===0;)a++;o=o.slice(a)}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}})(Be)),Be}var Ie,dt;function qo(){if(dt)return Ie;dt=1;const e=Lo();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 a=new Uint8Array(this.degree);return a.set(o,i),a}return o},Ie=s,Ie}var Ae={},Re={},Te={},ft;function jt(){return ft||(ft=1,Te.isValid=function(s){return!isNaN(s)&&s>=1&&s<=40}),Te}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(u){return n.test(u)},H.testNumeric=function(u){return o.test(u)},H.testAlphanumeric=function(u){return i.test(u)},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}}})(Re)),Re}var mt;function Fo(){return mt||(mt=1,(function(e){const s=W(),r=Ot(),t=Je(),n=Z(),o=jt(),i=7973,a=s.getBCHDigit(i);function u(b,h,d){for(let g=1;g<=40;g++)if(h<=e.getCapacity(g,d,b))return g}function l(b,h){return n.getCharCountIndicator(b,h)+4}function f(b,h){let d=0;return b.forEach(function(g){const N=l(g.mode,h);d+=N+g.getBitsLength()}),d}function w(b,h){for(let d=1;d<=40;d++)if(f(b,d)<=e.getCapacity(d,h,n.MIXED))return d}e.from=function(h,d){return o.isValid(h)?parseInt(h,10):d},e.getCapacity=function(h,d,g){if(!o.isValid(h))throw new Error("Invalid QR Code version");typeof g>"u"&&(g=n.BYTE);const N=s.getSymbolTotalCodewords(h),A=r.getTotalCodewordsCount(h,d),P=(N-A)*8;if(g===n.MIXED)return P;const c=P-l(g,h);switch(g){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(h,d){let g;const N=t.from(d,t.M);if(Array.isArray(h)){if(h.length>1)return w(h,N);if(h.length===0)return 1;g=h[0]}else g=h;return u(g.mode,g.getLength(),N)},e.getEncodedBits=function(h){if(!o.isValid(h)||h<7)throw new Error("Invalid QR Code version");let d=h<<12;for(;s.getBCHDigit(d)-a>=0;)d^=i<<s.getBCHDigit(d)-a;return h<<12|d}})(Ae)),Ae}var Me={},yt;function Uo(){if(yt)return Me;yt=1;const e=W(),s=1335,r=21522,t=e.getBCHDigit(s);return Me.getEncodedBits=function(o,i){const a=o.bit<<3|i;let u=a<<10;for(;e.getBCHDigit(u)-t>=0;)u^=s<<e.getBCHDigit(u)-t;return(a<<10|u)^r},Me}var Ne={},Pe,pt;function Ko(){if(pt)return Pe;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 a=this.data.length-n;a>0&&(o=this.data.substr(n),i=parseInt(o,10),t.put(i,a*3+1))},Pe=s,Pe}var De,wt;function Vo(){if(wt)return De;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)},De=r,De}var Le,bt;function Ho(){if(bt)return Le;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)},Le=s,Le}var qe,vt;function Oo(){if(vt)return qe;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)}},qe=r,qe}var Fe={exports:{}},Ct;function jo(){return Ct||(Ct=1,(function(e){var s={single_source_shortest_paths:function(r,t,n){var o={},i={};i[t]=0;var a=s.PriorityQueue.make();a.push(t,0);for(var u,l,f,w,b,h,d,g,N;!a.empty();){u=a.pop(),l=u.value,w=u.cost,b=r[l]||{};for(f in b)b.hasOwnProperty(f)&&(h=b[f],d=w+h,g=i[f],N=typeof i[f]>"u",(N||g>d)&&(i[f]=d,a.push(f,d),o[f]=l))}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})(Fe)),Fe.exports}var St;function zo(){return St||(St=1,(function(e){const s=Z(),r=Ko(),t=Vo(),n=Ho(),o=Oo(),i=zt(),a=W(),u=jo();function l(A){return unescape(encodeURIComponent(A)).length}function f(A,P,c){const S=[];let I;for(;(I=A.exec(c))!==null;)S.push({data:I[0],index:I.index,mode:P,length:I[0].length});return S}function w(A){const P=f(i.NUMERIC,s.NUMERIC,A),c=f(i.ALPHANUMERIC,s.ALPHANUMERIC,A);let S,I;return a.isKanjiModeEnabled()?(S=f(i.BYTE,s.BYTE,A),I=f(i.KANJI,s.KANJI,A)):(S=f(i.BYTE_KANJI,s.BYTE,A),I=[]),P.concat(c,S,I).sort(function(p,y){return p.index-y.index}).map(function(p){return{data:p.data,mode:p.mode,length:p.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 h(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 d(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:l(S.data)}]);break;case s.BYTE:P.push([{data:S.data,mode:s.BYTE,length:l(S.data)}])}}return P}function g(A,P){const c={},S={start:{}};let I=["start"];for(let C=0;C<A.length;C++){const p=A[C],y=[];for(let m=0;m<p.length;m++){const _=p[m],E=""+C+m;y.push(E),c[E]={node:_,lastCount:0},S[E]={};for(let k=0;k<I.length;k++){const B=I[k];c[B]&&c[B].node.mode===_.mode?(S[B][E]=b(c[B].lastCount+_.length,_.mode)-b(c[B].lastCount,_.mode),c[B].lastCount+=_.length):(c[B]&&(c[B].lastCount=_.length),S[B][E]=b(_.length,_.mode)+4+s.getCharCountIndicator(_.mode,P))}}I=y}for(let C=0;C<I.length;C++)S[I[C]].end=0;return{map:S,table:c}}function N(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&&!a.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(N(S,null)):S.data&&c.push(N(S.data,S.mode)),c},[])},e.fromString=function(P,c){const S=w(P,a.isKanjiModeEnabled()),I=d(S),C=g(I,c),p=u.find_path(C.map,"start","end"),y=[];for(let m=1;m<p.length-1;m++)y.push(C.table[p[m]].node);return e.fromArray(h(y))},e.rawSplit=function(P){return e.fromArray(w(P,a.isKanjiModeEnabled()))}})(Ne)),Ne}var _t;function xo(){if(_t)return be;_t=1;const e=W(),s=Je(),r=Ro(),t=To(),n=Mo(),o=No(),i=Po(),a=Ot(),u=qo(),l=Fo(),f=Uo(),w=Z(),b=zo();function h(C,p){const y=C.size,m=o.getPositions(p);for(let _=0;_<m.length;_++){const E=m[_][0],k=m[_][1];for(let B=-1;B<=7;B++)if(!(E+B<=-1||y<=E+B))for(let M=-1;M<=7;M++)k+M<=-1||y<=k+M||(B>=0&&B<=6&&(M===0||M===6)||M>=0&&M<=6&&(B===0||B===6)||B>=2&&B<=4&&M>=2&&M<=4?C.set(E+B,k+M,!0,!0):C.set(E+B,k+M,!1,!0))}}function d(C){const p=C.size;for(let y=8;y<p-8;y++){const m=y%2===0;C.set(y,6,m,!0),C.set(6,y,m,!0)}}function g(C,p){const y=n.getPositions(p);for(let m=0;m<y.length;m++){const _=y[m][0],E=y[m][1];for(let k=-2;k<=2;k++)for(let B=-2;B<=2;B++)k===-2||k===2||B===-2||B===2||k===0&&B===0?C.set(_+k,E+B,!0,!0):C.set(_+k,E+B,!1,!0)}}function N(C,p){const y=C.size,m=l.getEncodedBits(p);let _,E,k;for(let B=0;B<18;B++)_=Math.floor(B/3),E=B%3+y-8-3,k=(m>>B&1)===1,C.set(_,E,k,!0),C.set(E,_,k,!0)}function A(C,p,y){const m=C.size,_=f.getEncodedBits(p,y);let E,k;for(E=0;E<15;E++)k=(_>>E&1)===1,E<6?C.set(E,8,k,!0):E<8?C.set(E+1,8,k,!0):C.set(m-15+E,8,k,!0),E<8?C.set(8,m-E-1,k,!0):E<9?C.set(8,15-E-1+1,k,!0):C.set(8,15-E-1,k,!0);C.set(m-8,8,1,!0)}function P(C,p){const y=C.size;let m=-1,_=y-1,E=7,k=0;for(let B=y-1;B>0;B-=2)for(B===6&&B--;;){for(let M=0;M<2;M++)if(!C.isReserved(_,B-M)){let V=!1;k<p.length&&(V=(p[k]>>>E&1)===1),C.set(_,B-M,V),E--,E===-1&&(k++,E=7)}if(_+=m,_<0||y<=_){_-=m,m=-m;break}}}function c(C,p,y){const m=new r;y.forEach(function(M){m.put(M.mode.bit,4),m.put(M.getLength(),w.getCharCountIndicator(M.mode,C)),M.write(m)});const _=e.getSymbolTotalCodewords(C),E=a.getTotalCodewordsCount(C,p),k=(_-E)*8;for(m.getLengthInBits()+4<=k&&m.put(0,4);m.getLengthInBits()%8!==0;)m.putBit(0);const B=(k-m.getLengthInBits())/8;for(let M=0;M<B;M++)m.put(M%2?17:236,8);return S(m,C,p)}function S(C,p,y){const m=e.getSymbolTotalCodewords(p),_=a.getTotalCodewordsCount(p,y),E=m-_,k=a.getBlocksCount(p,y),B=m%k,M=k-B,V=Math.floor(m/k),K=Math.floor(E/k),q=K+1,Ye=V-K,$t=new u(Ye);let he=0;const ie=new Array(k),Qe=new Array(k);let ge=0;const Jt=new Uint8Array(C.buffer);for(let X=0;X<k;X++){const ye=X<M?K:q;ie[X]=Jt.slice(he,he+ye),Qe[X]=$t.encode(ie[X]),he+=ye,ge=Math.max(ge,ye)}const me=new Uint8Array(m);let Ge=0,O,j;for(O=0;O<ge;O++)for(j=0;j<k;j++)O<ie[j].length&&(me[Ge++]=ie[j][O]);for(O=0;O<Ye;O++)for(j=0;j<k;j++)me[Ge++]=Qe[j][O];return me}function I(C,p,y,m){let _;if(Array.isArray(C))_=b.fromArray(C);else if(typeof C=="string"){let V=p;if(!V){const K=b.rawSplit(C);V=l.getBestVersionForData(K,y)}_=b.fromString(C,V||40)}else throw new Error("Invalid data");const E=l.getBestVersionForData(_,y);if(!E)throw new Error("The amount of data is too big to be stored in a QR Code");if(!p)p=E;else if(p<E)throw new Error(`
5
+ The chosen QR Code version cannot contain this amount of data.
6
+ Minimum version required to store current data is: `+E+`.
7
+ `);const k=c(p,y,_),B=e.getSymbolSize(p),M=new t(B);return h(M,p),d(M),g(M,p),A(M,y,0),p>=7&&N(M,p),P(M,k),isNaN(m)&&(m=i.getBestMask(M,A.bind(null,M,y))),i.applyMask(m,M),A(M,y,m),{modules:M,version:p,errorCorrectionLevel:y,maskPattern:m,segments:_}}return be.create=function(p,y){if(typeof p>"u"||p==="")throw new Error("No input text");let m=s.M,_,E;return typeof y<"u"&&(m=s.from(y.errorCorrectionLevel,s.M),_=l.from(y.version),E=i.from(y.maskPattern),y.toSJISFunc&&e.setToSJISFunction(y.toSJISFunc)),I(p,_,m,E)},be}var Ue={},Ke={},Et;function xt(){return Et||(Et=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,a=n.modules.data,u=e.getScale(i,o),l=Math.floor((i+o.margin*2)*u),f=o.margin*u,w=[o.color.light,o.color.dark];for(let b=0;b<l;b++)for(let h=0;h<l;h++){let d=(b*l+h)*4,g=o.color.light;if(b>=f&&h>=f&&b<l-f&&h<l-f){const N=Math.floor((b-f)/u),A=Math.floor((h-f)/u);g=w[a[N*i+A]?1:0]}t[d++]=g.r,t[d++]=g.g,t[d++]=g.b,t[d]=g.a}}})(Ke)),Ke}var kt;function $o(){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,a){let u=a,l=i;typeof u>"u"&&(!i||!i.getContext)&&(u=i,i=void 0),i||(l=t()),u=s.getOptions(u);const f=s.getImageWidth(o.modules.size,u),w=l.getContext("2d"),b=w.createImageData(f,f);return s.qrToImageData(b.data,o,u),r(w,l,f),w.putImageData(b,0,0),l},e.renderToDataURL=function(o,i,a){let u=a;typeof u>"u"&&(!i||!i.getContext)&&(u=i,i=void 0),u||(u={});const l=e.render(o,i,u),f=u.type||"image/png",w=u.rendererOpts||{};return l.toDataURL(f,w.quality)}})(Ue)),Ue}var Ve={},Bt;function Jo(){if(Bt)return Ve;Bt=1;const e=xt();function s(n,o){const i=n.a/255,a=o+'="'+n.hex+'"';return i<1?a+" "+o+'-opacity="'+i.toFixed(2).slice(1)+'"':a}function r(n,o,i){let a=n+o;return typeof i<"u"&&(a+=" "+i),a}function t(n,o,i){let a="",u=0,l=!1,f=0;for(let w=0;w<n.length;w++){const b=Math.floor(w%o),h=Math.floor(w/o);!b&&!l&&(l=!0),n[w]?(f++,w>0&&b>0&&n[w-1]||(a+=l?r("M",b+i,.5+h+i):r("m",u,0),u=0,l=!1),b+1<o&&n[w+1]||(a+=r("h",f),f=0)):u++}return a}return Ve.render=function(o,i,a){const u=e.getOptions(i),l=o.modules.size,f=o.modules.data,w=l+u.margin*2,b=u.color.light.a?"<path "+s(u.color.light,"fill")+' d="M0 0h'+w+"v"+w+'H0z"/>':"",h="<path "+s(u.color.dark,"stroke")+' d="'+t(f,l,u.margin)+'"/>',d='viewBox="0 0 '+w+" "+w+'"',N='<svg xmlns="http://www.w3.org/2000/svg" '+(u.width?'width="'+u.width+'" height="'+u.width+'" ':"")+d+' shape-rendering="crispEdges">'+b+h+`</svg>
8
+ `;return typeof a=="function"&&a(null,N),N},Ve}var It;function Yo(){if(It)return te;It=1;const e=Ao(),s=xo(),r=$o(),t=Jo();function n(o,i,a,u,l){const f=[].slice.call(arguments,1),w=f.length,b=typeof f[w-1]=="function";if(!b&&!e())throw new Error("Callback required as last argument");if(b){if(w<2)throw new Error("Too few arguments provided");w===2?(l=a,a=i,i=u=void 0):w===3&&(i.getContext&&typeof l>"u"?(l=u,u=void 0):(l=u,u=a,a=i,i=void 0))}else{if(w<1)throw new Error("Too few arguments provided");return w===1?(a=i,i=u=void 0):w===2&&!i.getContext&&(u=a,a=i,i=void 0),new Promise(function(h,d){try{const g=s.create(a,u);h(o(g,i,u))}catch(g){d(g)}})}try{const h=s.create(a,u);l(null,o(h,i,u))}catch(h){l(h)}}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,a){return t.render(o,a)}),te}var Qo=Yo();const Go=Io(Qo),Wo={class:"qr-display"},Zo={class:"qr-section"},Xo=["href","onKeydown"],er={key:0,class:"link-text"},tr={__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),Go.toCanvas(n.value,r.url,{scale:6,margin:0,color:{dark:"#000000",light:"#FFFFFF"}}),n.value.removeAttribute("style")}catch(u){console.error("QR code generation failed:",u)}}async function a(){if(r.url)try{await navigator.clipboard.writeText(r.url),t("copied")}catch(u){console.error("Failed to copy link:",u)}}return He(()=>r.url,()=>{i()},{immediate:!0}),He(n,()=>{n.value&&r.url&&i()},{immediate:!0}),(u,l)=>(T(),R("div",Wo,[v("div",Zo,[v("a",{href:e.url,onClick:de(a,["prevent"]),class:"qr-link",title:"Click to copy link",tabindex:"0",onKeydown:ln(de(a,["prevent"]),["enter"])},[v("canvas",{ref_key:"qrCanvas",ref:n,class:"qr-code"},null,512),e.showLink&&e.url?(T(),R("div",er,D(o.value),1)):L("",!0)],40,Xo)])]))}},nr=Y(tr,[["__scopeId","data-v-727427c4"]]),or={class:"device-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"regTitle"},rr={class:"reg-header-row"},sr={id:"regTitle",class:"reg-title"},ir={key:0},ar={key:1},ur={class:"device-link-section"},lr={key:0,class:"expiry-note"},cr={__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=F(null),o=F(null),i=F(null),a=F(null),u=F(null);async function l(){try{const d=await Q(r.endpoint,{method:"POST"});if(d.url){if(o.value=d.url,i.value=d.expires?new Date(d.expires):null,await ze(),n.value){n.value.showModal();const g=a.value;(g?.querySelector(".btn-primary")||g?.querySelector("button"))?.focus()}}else t("close")}catch{t("close")}}function f(){t("copied")}const w=d=>{const g=z(d)},b=d=>{const g=z(d);g&&(d.preventDefault(),(g==="down"||g==="up")&&a.value?.querySelector("button")?.focus())},h=d=>{const g=z(d);g&&(d.preventDefault(),(g==="up"||g==="down")&&document.querySelector(".qr-link")?.focus())};return qt(()=>{u.value=document.activeElement,l()}),Ft(()=>{const d=u.value;d&&document.body.contains(d)&&!d.disabled&&d.focus()}),(d,g)=>(T(),R("dialog",{ref_key:"dialog",ref:n,onClose:g[2]||(g[2]=N=>d.$emit("close")),onKeydown:w},[v("div",or,[v("div",rr,[v("h2",sr,[g[3]||(g[3]=xe(" 📱 ",-1)),e.userName?(T(),R("span",ir,"Registration for "+D(e.userName),1)):(T(),R("span",ar,"Add Another Device"))]),v("button",{class:"icon-btn",onClick:g[0]||(g[0]=N=>d.$emit("close")),"aria-label":"Close",tabindex:"-1"},"✕")]),v("div",ur,[g[4]||(g[4]=v("p",{class:"reg-help"}," Scan this QR code on the new device, or copy the link and open it there. ",-1)),cn(nr,{url:o.value,"show-link":!0,onCopied:f,onKeydown:b},null,8,["url"]),i.value?(T(),R("p",lr," This link expires "+D(U(G)(i.value).toLowerCase())+". ",1)):L("",!0)]),v("div",{class:"reg-actions",ref_key:"actionsRow",ref:a,onKeydown:h},[v("button",{class:"btn-secondary",onClick:g[1]||(g[1]=N=>d.$emit("close"))},"Close")],544)])],544))}},Ir=Y(cr,[["__scopeId","data-v-e04dd463"]]),dr={class:"loading-container"},fr={__name:"LoadingView",props:{message:{type:String,default:"Loading..."}},setup(e){return(s,r)=>(T(),R("div",dr,[r[0]||(r[0]=v("div",{class:"loading-spinner"},null,-1)),v("p",null,D(e.message),1)]))}},Ar=Y(fr,[["__scopeId","data-v-130f5abf"]]),hr={class:"message-container"},gr={class:"message-content"},mr={class:"button-row"},yr={__name:"AccessDenied",emits:["reload"],setup(e){return(s,r)=>(T(),R("div",hr,[v("div",gr,[r[2]||(r[2]=v("h2",null,"🔒 Access Denied",-1)),v("div",mr,[v("button",{class:"btn-secondary",onClick:r[0]||(r[0]=(...t)=>U(We)&&U(We)(...t))},"Back"),v("button",{class:"btn-primary",onClick:r[1]||(r[1]=t=>s.$emit("reload"))},"Reload Page")])])]))}},Rr=Y(yr,[["__scopeId","data-v-744305d5"]]);export{Rr as A,Cr as B,Ar as L,Er as M,kr as N,Ir as R,_r as U,Sr as _,Br as a,vr as b,br as c,$e as u};