@jarvis-security/sdk 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +422 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.mts +161 -0
- package/dist/index.d.ts +161 -0
- package/dist/index.mjs +10 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jarvis Security Suite Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ J.A.R.V.I.S. Security Suite
|
|
4
|
+
|
|
5
|
+
**Iron Man–style pluggable multi-biometric authentication — drop into any React / Next.js app**
|
|
6
|
+
|
|
7
|
+
<a href="https://github.com/theaaqibjavaid/JARVIS-AUTH/actions"><img src="https://github.com/theaaqibjavaid/JARVIS-AUTH/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
8
|
+
[](#-testing)
|
|
9
|
+
[](https://www.npmjs.com/package/@jarvis-security/sdk)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](./tsconfig.json)
|
|
12
|
+
|
|
13
|
+
[Features](#-features) · [Quick Start](#-quick-start) · [SDK Usage](#-sdk-usage) · [Auth Adapters](#-auth-adapters) · [Backend](#-fastapi-backend) · [Testing](#-testing) · [Contributing](#-contributing)
|
|
14
|
+
|
|
15
|
+
<br/>
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ✨ Features
|
|
23
|
+
|
|
24
|
+
| Layer | What you get |
|
|
25
|
+
|---|---|
|
|
26
|
+
| 🔐 **4 Biometric Methods** | Passkey (password) · Facial scan (real camera) · Voice print (real mic) · Fingerprint (press & hold) |
|
|
27
|
+
| 🗝️ **WebAuthn Passkeys** | Real device passkey sign-in via `@simplewebauthn/browser` with feature detection |
|
|
28
|
+
| 🔌 **Pluggable Backends** | **Strategy / Adapter Pattern** — Mock (demo) · FastAPI REST · Firebase (skeleton) · write your own for Supabase, Auth0, Clerk... |
|
|
29
|
+
| 🛡️ **Hardened Backend** | FastAPI with bcrypt hashing, JWT access + refresh tokens, CORS, rate limiting (slowapi), SQLModel/SQLite persistence |
|
|
30
|
+
| 🎨 **Sci-Fi Visuals** | Arc Reactor MK VII HUD, animated starfield + grid canvas, laser scan beams, CRT scanlines, neon corner-frames, telemetry badges |
|
|
31
|
+
| 🔊 **Zero-file Audio** | Web Audio–synthesized beeps, success triads, error descents — *no MP3 / WAV files required* |
|
|
32
|
+
| 🏠 **Persistence** | Session survives reload via `localStorage` (`jarvis_auth_user`) + `onAuthStateChanged` observable subscription pattern |
|
|
33
|
+
| ♿ **Accessibility** | `aria-*` labels, `htmlFor`/`id` bindings, keyboard support (Space/Enter), `prefers-reduced-motion` support |
|
|
34
|
+
| 📱 **Responsive** | 12-col Tailwind grid collapses gracefully on tablet + mobile |
|
|
35
|
+
| ✅ **Fully Tested** | 64 unit tests (Vitest + Testing Library) + 10 FastAPI backend tests (pytest) |
|
|
36
|
+
| 🔧 **Zero config demo** | `npm run dev` → works out of the box with `MockAuthAdapter` (no backends needed) |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ⚡ Quick Start
|
|
41
|
+
|
|
42
|
+
> 🚨 **Node.js 18.17+ required** (Next.js 14 requirement)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 1. Install
|
|
46
|
+
npm install
|
|
47
|
+
|
|
48
|
+
# 2. Run dev server
|
|
49
|
+
npm run dev
|
|
50
|
+
|
|
51
|
+
# 3. Open → http://localhost:3000
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
That's it. **No API keys, no backends.** The default `MockAuthAdapter` accepts:
|
|
55
|
+
- Any valid email format
|
|
56
|
+
- Any passkey with 6+ characters
|
|
57
|
+
|
|
58
|
+
Try these:
|
|
59
|
+
|
|
60
|
+
| What | Input |
|
|
61
|
+
|---|---|
|
|
62
|
+
| Email | `stark@avengers.io` |
|
|
63
|
+
| Passkey | `iamironman` |
|
|
64
|
+
| Or click **FACIAL/EYE** / **VOICE** / **FINGERPRINT SCAN** buttons | — |
|
|
65
|
+
|
|
66
|
+
### Production build
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run build # ✓ 0 type errors, outputs statically-rendered pages
|
|
70
|
+
npm run start # serves production build on :3000
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 📦 SDK Usage
|
|
76
|
+
|
|
77
|
+
Install the package into any React / Next.js project:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install @jarvis-security/sdk
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Drop-in auth page
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
"use client";
|
|
87
|
+
import {
|
|
88
|
+
AuthProvider,
|
|
89
|
+
AuthPortal,
|
|
90
|
+
CanvasBackground,
|
|
91
|
+
createAuthAdapter,
|
|
92
|
+
} from "@jarvis-security/sdk";
|
|
93
|
+
|
|
94
|
+
const adapter = createAuthAdapter("mock");
|
|
95
|
+
|
|
96
|
+
export default function AuthPage() {
|
|
97
|
+
return (
|
|
98
|
+
<AuthProvider adapter={adapter}>
|
|
99
|
+
<CanvasBackground />
|
|
100
|
+
<AuthPortal />
|
|
101
|
+
</AuthProvider>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Protect any route (3 lines)
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
"use client";
|
|
110
|
+
import { useAuth } from "@jarvis-security/sdk";
|
|
111
|
+
import { redirect } from "next/navigation";
|
|
112
|
+
|
|
113
|
+
export default function Dashboard() {
|
|
114
|
+
const { user, status } = useAuth();
|
|
115
|
+
if (typeof window !== "undefined" && status === "unauthenticated") redirect("/auth");
|
|
116
|
+
return <div>Welcome, {user?.fullName}</div>;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Build the SDK locally
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm run build:sdk # → dist/index.cjs, dist/index.mjs, dist/index.d.ts
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Dual CJS + ESM output with full TypeScript declarations, built with [tsup](https://tsup.egoist.dev).
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 🧩 Manual Integration (Copy Source)
|
|
131
|
+
|
|
132
|
+
Prefer copying source over installing the package? Copy the `app/` folder contents into your Next.js App Router project, then merge the Tailwind theme.
|
|
133
|
+
|
|
134
|
+
### Step 1 · Copy files
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
your-app/
|
|
138
|
+
├── app/
|
|
139
|
+
│ ├── (auth)/
|
|
140
|
+
│ │ └── jarvis/ # ← COPY components/, context/, lib/, types/, globals.css, page.tsx
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Step 2 · Merge Tailwind theme
|
|
144
|
+
|
|
145
|
+
Copy the `theme.extend` block from [tailwind.config.ts](./tailwind.config.ts) (colors, fontFamily, boxShadow, keyframes, animations) into your Tailwind config.
|
|
146
|
+
|
|
147
|
+
### Step 3 · Use the `<AuthProvider>` shell
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
// app/(auth)/jarvis/page.tsx
|
|
151
|
+
"use client";
|
|
152
|
+
|
|
153
|
+
import { AuthProvider } from "./context/AuthContext";
|
|
154
|
+
import { createAuthAdapter } from "./lib/auth-adapter";
|
|
155
|
+
import { CanvasBackground } from "./components/CanvasBackground";
|
|
156
|
+
import { AuthPortal } from "./components/AuthPortal";
|
|
157
|
+
|
|
158
|
+
export default function JarvisAuthPage() {
|
|
159
|
+
const adapter = createAuthAdapter(
|
|
160
|
+
(process.env.NEXT_PUBLIC_AUTH_ADAPTER as "mock" | "backend" | "firebase") || "mock",
|
|
161
|
+
{ baseUrl: process.env.NEXT_PUBLIC_AUTH_API_URL }
|
|
162
|
+
);
|
|
163
|
+
return (
|
|
164
|
+
<AuthProvider adapter={adapter}>
|
|
165
|
+
<CanvasBackground />
|
|
166
|
+
<AuthPortal />
|
|
167
|
+
</AuthProvider>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
✅ **Done.** J.A.R.V.I.S. auth is now live at `/jarvis` in your app.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 🔌 Auth Adapters
|
|
177
|
+
|
|
178
|
+
Pick an adapter by setting an env var. Everything else (UI, flow, persistence, events) stays 100% identical.
|
|
179
|
+
|
|
180
|
+
| Adapter | `NEXT_PUBLIC_AUTH_ADAPTER=` | Use-case | Needs backend? |
|
|
181
|
+
|---|---|---|---|
|
|
182
|
+
| `MockAuthAdapter` *(default)* | `mock` | Demo, local dev, CI, UI testing | ❌ |
|
|
183
|
+
| `BackendAuthAdapter` | `backend` | FastAPI backend (included) or any REST API | ✅ |
|
|
184
|
+
| `FirebaseAdapter` *(v1 skeleton)* | `firebase` | Google Firebase Auth (extend to fit) | ✅ |
|
|
185
|
+
| **Write your own** | *(any string)* | Supabase · Auth0 · Clerk · NextAuth · AWS Cognito... | — |
|
|
186
|
+
|
|
187
|
+
### Switch to the Backend adapter (FastAPI)
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# .env.local (create at repo root)
|
|
191
|
+
NEXT_PUBLIC_AUTH_ADAPTER=backend
|
|
192
|
+
NEXT_PUBLIC_AUTH_API_URL=http://localhost:8000
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### ✍️ Write a custom adapter
|
|
196
|
+
|
|
197
|
+
Just **implement the `AuthAdapter` interface** — that's the only rule. The contract has 11 methods:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
interface AuthAdapter {
|
|
201
|
+
readonly name: string;
|
|
202
|
+
register(email: string, passkey: string, fullName?: string): Promise<AuthResult>;
|
|
203
|
+
login(email: string, passkey: string): Promise<AuthResult>;
|
|
204
|
+
logout(): Promise<void>;
|
|
205
|
+
resetPassword(email: string): Promise<AuthResult>;
|
|
206
|
+
verifyFace(imageBase64: string): Promise<AuthResult>;
|
|
207
|
+
verifyVoice(audioBlob: Blob): Promise<AuthResult>;
|
|
208
|
+
verifyFingerprint(scanData: string): Promise<AuthResult>;
|
|
209
|
+
verifyPasskey(email?: string): Promise<AuthResult>;
|
|
210
|
+
enrollBiometrics(userId: string): Promise<AuthResult>;
|
|
211
|
+
getCurrentUser(): Promise<UserProfile | null>;
|
|
212
|
+
onAuthStateChanged(callback: (user: UserProfile | null) => void): () => void;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Example (Supabase):
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import type { AuthAdapter, AuthResult, UserProfile } from "@jarvis-security/sdk";
|
|
220
|
+
|
|
221
|
+
export class SupabaseAuthAdapter implements AuthAdapter {
|
|
222
|
+
readonly name = "SupabaseAuthAdapter";
|
|
223
|
+
constructor(private readonly supabase: SupabaseClient) {}
|
|
224
|
+
|
|
225
|
+
async login(email: string, passkey: string): Promise<AuthResult> {
|
|
226
|
+
const { data, error } = await this.supabase.auth.signInWithPassword({ email, password: passkey });
|
|
227
|
+
if (error) return { success: false, error: error.message };
|
|
228
|
+
return { success: true, user: data.user as unknown as UserProfile };
|
|
229
|
+
}
|
|
230
|
+
// ... implement the remaining methods
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Then plug it in:
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
<AuthProvider adapter={new SupabaseAuthAdapter(createClient(...))}>
|
|
238
|
+
<CanvasBackground />
|
|
239
|
+
<AuthPortal />
|
|
240
|
+
</AuthProvider>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
✅ **No UI code changes ever.** Your Supabase/Auth0/Clerk/NextAuth backend drives the same futuristic panels.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 🐍 FastAPI Backend
|
|
248
|
+
|
|
249
|
+
A production-hardened backend lives in [app/python-backend/](./app/python-backend/):
|
|
250
|
+
|
|
251
|
+
| Feature | Implementation |
|
|
252
|
+
|---|---|
|
|
253
|
+
| Password hashing | **bcrypt** (12 rounds, never stores plaintext) |
|
|
254
|
+
| Tokens | **PyJWT** — HS256 access (30 min) + refresh (7 days) tokens |
|
|
255
|
+
| Rate limiting | **slowapi** — 5/min register, 10/min login, 30/min default |
|
|
256
|
+
| Storage | **SQLModel + SQLite** (swap to Postgres via `JARVIS_DB_URL`) |
|
|
257
|
+
| CORS | Configurable via `JARVIS_CORS_ORIGINS` |
|
|
258
|
+
| WebAuthn | Passkey options / verify / register endpoints |
|
|
259
|
+
|
|
260
|
+
### Run it
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
cd app/python-backend
|
|
264
|
+
python -m venv .venv && .venv\Scripts\activate # Windows
|
|
265
|
+
# source .venv/bin/activate # macOS/Linux
|
|
266
|
+
pip install -r requirements.txt
|
|
267
|
+
uvicorn main:app --reload --port 8000
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Environment variables
|
|
271
|
+
|
|
272
|
+
| Variable | Default | Purpose |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| `JARVIS_JWT_SECRET` | *(warns if unset)* | JWT signing key — **must set in production** |
|
|
275
|
+
| `JARVIS_CORS_ORIGINS` | `http://localhost:3000` | Comma-separated allowed origins |
|
|
276
|
+
| `JARVIS_DB_URL` | `sqlite:///jarvis_auth.db` | SQLAlchemy database URL |
|
|
277
|
+
|
|
278
|
+
### API endpoints
|
|
279
|
+
|
|
280
|
+
| Method | Path | Rate limit |
|
|
281
|
+
|---|---|---|
|
|
282
|
+
| `GET` | `/api/v1/health` | 60/min |
|
|
283
|
+
| `POST` | `/api/v1/auth/register` | 5/min |
|
|
284
|
+
| `POST` | `/api/v1/auth/login` | 10/min |
|
|
285
|
+
| `POST` | `/api/v1/auth/refresh` | 30/min |
|
|
286
|
+
| `POST` | `/api/v1/auth/verify-face` | 20/min |
|
|
287
|
+
| `POST` | `/api/v1/auth/verify-voice` | 20/min |
|
|
288
|
+
| `POST` | `/api/v1/auth/verify-fingerprint` | 20/min |
|
|
289
|
+
| `GET` | `/api/v1/auth/webauthn/options` | 10/min |
|
|
290
|
+
| `POST` | `/api/v1/auth/webauthn/verify` | 10/min |
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 🧪 Testing
|
|
295
|
+
|
|
296
|
+
### Frontend — 64 tests
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
npm test # run all tests once
|
|
300
|
+
npm run test:watch # watch mode
|
|
301
|
+
npm run test:coverage # with v8 coverage (60% thresholds)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
| Suite | Tests | Covers |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| `auth-adapter.test.ts` | 31 | MockAuthAdapter register/login/logout/reset, biometric verify, enrollBiometrics persistence, onAuthStateChanged, verifyPasskey, factory, BackendAuthAdapter |
|
|
307
|
+
| `auth-context.test.tsx` | 14 | AuthProvider init, login/register flows, logout, mode/audio toggles, modal, biometric enrollment round-trip |
|
|
308
|
+
| `types.test.ts` | 7 | AuthAdapter interface contract (11 methods), type shapes |
|
|
309
|
+
| `sound-engine.test.ts` | 7 | Web Audio beep/success/error synthesis |
|
|
310
|
+
| `PasskeyForm.test.tsx` | 5 | Form rendering, validation, passkey visibility toggle |
|
|
311
|
+
|
|
312
|
+
### Backend — 10 tests
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
cd app/python-backend
|
|
316
|
+
pip install -r requirements.txt
|
|
317
|
+
pytest -v
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Covers health, register, duplicate-email conflict, login, wrong-passkey rejection, JWT-protected refresh, face/voice/fingerprint verify, and WebAuthn options.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## 📁 Project Structure
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
jarvis-security-suite/ ← npm project root
|
|
328
|
+
├── app/ ← Next.js App Router directory + SDK source
|
|
329
|
+
│ ├── components/
|
|
330
|
+
│ │ ├── AuthPortal.tsx shell: header · grid · modes · modal · footer
|
|
331
|
+
│ │ ├── ArcReactorHud.tsx left HUD — rings · telemetry · speech log
|
|
332
|
+
│ │ ├── CanvasBackground.tsx starfield particles + 40 px grid (pure canvas)
|
|
333
|
+
│ │ └── biometrics/
|
|
334
|
+
│ │ ├── PasskeyForm.tsx email + passkey + fullName + WebAuthn button
|
|
335
|
+
│ │ ├── FacialScanner.tsx getUserMedia + laser + base64 capture
|
|
336
|
+
│ │ ├── VoiceScanner.tsx MediaRecorder + 24-bar waveform
|
|
337
|
+
│ │ └── FingerprintPad.tsx press-hold conic-gradient ring
|
|
338
|
+
│ ├── context/AuthContext.tsx AuthProvider + useAuth() hook
|
|
339
|
+
│ ├── lib/
|
|
340
|
+
│ │ ├── auth-adapter.ts Mock · Backend · Firebase (Strategy classes)
|
|
341
|
+
│ │ └── sound-engine.ts Web Audio synth: beep / success / error
|
|
342
|
+
│ ├── types/index.ts ALL shared types + AuthAdapter interface
|
|
343
|
+
│ ├── python-backend/ Hardened FastAPI backend + pytest suite
|
|
344
|
+
│ ├── index.ts SDK barrel entry point
|
|
345
|
+
│ ├── globals.css fonts + cyber-* classes + scanlines + keyframes
|
|
346
|
+
│ ├── layout.tsx Next.js root HTML shell + metadata
|
|
347
|
+
│ └── page.tsx HOME = AuthProvider + Canvas + AuthPortal
|
|
348
|
+
├── __tests__/ Vitest test suites (64 tests)
|
|
349
|
+
├── .github/workflows/ci.yml CI: typecheck · lint · test · build-sdk
|
|
350
|
+
├── docs/ Task tracking & session history
|
|
351
|
+
├── .env.example adapter env vars
|
|
352
|
+
├── tsup.config.ts SDK bundler config (CJS + ESM + DTS)
|
|
353
|
+
├── vitest.config.ts test runner config
|
|
354
|
+
├── tailwind.config.ts colors · fonts · shadows · keyframes · anims
|
|
355
|
+
└── package.json @jarvis-security/sdk
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 🔐 Security Notes
|
|
361
|
+
|
|
362
|
+
This is a **UI + adapter framework** — real security comes from whichever `AuthAdapter` you plug in.
|
|
363
|
+
|
|
364
|
+
| Concern | MockAdapter | BackendAdapter (FastAPI) | *Your Custom Adapter* |
|
|
365
|
+
|---|---|---|---|
|
|
366
|
+
| Password hashing | ❌ none (demo only) | ✅ bcrypt (12 rounds) | ✅ your responsibility |
|
|
367
|
+
| HTTPS only | N/A (localhost) | ✅ mandatory in prod | ✅ |
|
|
368
|
+
| JWT tokens | ❌ | ✅ access + refresh (PyJWT HS256) | ✅ implement |
|
|
369
|
+
| Rate limiting | ❌ | ✅ slowapi per-endpoint limits | ✅ in your backend |
|
|
370
|
+
| WebAuthn passkeys | ⚠️ feature-detected, needs server | ✅ options/verify/register endpoints | ✅ implement |
|
|
371
|
+
| Input validation | Client-side only | ✅ Pydantic schemas | ✅ |
|
|
372
|
+
|
|
373
|
+
👉 **Use `MockAuthAdapter` only for demos/UI development. Before going live, plug in a security-audited adapter and set `JARVIS_JWT_SECRET`.**
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 🛠 Tech Stack
|
|
378
|
+
|
|
379
|
+
| Layer | Choice |
|
|
380
|
+
|---|---|
|
|
381
|
+
| Framework | **Next.js 14.2** (App Router — all UI components tagged `"use client"`) |
|
|
382
|
+
| Runtime | **React 18.3** with hooks + Context API |
|
|
383
|
+
| Types | **TypeScript 5.6** (`strict: true`) |
|
|
384
|
+
| Styling | **Tailwind 3.4** + custom cyber theme + global CSS augmentations |
|
|
385
|
+
| Icons | `lucide-react` (tree-shakable, ESM) |
|
|
386
|
+
| WebAuthn | `@simplewebauthn/browser` v10 |
|
|
387
|
+
| Utilities | `clsx` + `tailwind-merge` |
|
|
388
|
+
| Audio | Native **Web Audio API** (zero files) |
|
|
389
|
+
| Particles | Native **HTML5 Canvas 2D** (zero deps) |
|
|
390
|
+
| SDK build | **tsup** (dual CJS/ESM + DTS) |
|
|
391
|
+
| Testing | **Vitest 1.6** + Testing Library + jsdom · **pytest** + httpx (backend) |
|
|
392
|
+
| Backend | **FastAPI** + SQLModel + bcrypt + PyJWT + slowapi |
|
|
393
|
+
| CI | **GitHub Actions** — typecheck, lint, test, build-sdk on every PR |
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 🤝 Contributing
|
|
398
|
+
|
|
399
|
+
We welcome contributions! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before opening a PR.
|
|
400
|
+
|
|
401
|
+
**TL;DR:**
|
|
402
|
+
1. Fork & create a feature branch (`feat/...` or `fix/...`)
|
|
403
|
+
2. Keep PRs small — one feature / one bugfix per PR
|
|
404
|
+
3. Write tests for any new feature or bugfix
|
|
405
|
+
4. Ensure `npm run typecheck`, `npm run lint`, `npm test`, and `npm run build:sdk` all pass
|
|
406
|
+
5. Follow the [Code of Conduct](./CODE_OF_CONDUCT.md)
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 📄 License
|
|
411
|
+
|
|
412
|
+
**[MIT](./LICENSE) © 2026** — Just A Rather Very Intelligent System™
|
|
413
|
+
Portions of visual styling inspired by Marvel's Iron Man / Stark Industries HUD aesthetics — this is a fan-built UI kit, *not* affiliated with Marvel/Disney.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
<div align="center">
|
|
418
|
+
<strong>« I have successfully hijacked your authentication flow, sir. »</strong><br/>
|
|
419
|
+
<sub>— J.A.R.V.I.S., probably</sub>
|
|
420
|
+
<br/><br/>
|
|
421
|
+
<strong>⭐ Star this repo on GitHub if this saved you from building a boring login page.</strong>
|
|
422
|
+
</div>
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';var browser=require('@simplewebauthn/browser'),react=require('react'),jsxRuntime=require('react/jsx-runtime'),lucideReact=require('lucide-react');var J="jarvis_auth_user",V={};function Be(){Object.keys(V).forEach(a=>delete V[a]);try{typeof window<"u"&&window.localStorage.removeItem(J);}catch{}}function H(a="OP"){return `${a}-${Math.random().toString(36).slice(2,8).toUpperCase()}-${Date.now().toString(36).slice(-4).toUpperCase()}`}function C(a){typeof window>"u"||(a?window.localStorage.setItem(J,JSON.stringify(a)):window.localStorage.removeItem(J));}function be(){if(typeof window>"u")return null;try{let a=window.localStorage.getItem(J);return a?JSON.parse(a):null}catch{return null}}var Y=class{constructor(){this.name="MockAuthAdapter";this.listeners=new Set;this.currentUser=null;this.currentUser=be();}emit(r){this.listeners.forEach(t=>{try{t(r);}catch{}});}async register(r,t,e){if(!r||!t||!e)return {success:false,error:"All fields are required to register an operative.",errorCode:"missing-fields"};if(t.length<6)return {success:false,error:"Passkey must be at least 6 characters long.",errorCode:"weak-passkey"};if(V[r.toLowerCase()])return {success:false,error:"This email is already registered. Please switch to Sign In.",errorCode:"email-already-in-use"};let n={uid:H("REG"),email:r.toLowerCase(),fullName:e||"Operative",clearanceLevel:"Level 1",hasBiometrics:false,createdAt:new Date().toISOString()};return V[r.toLowerCase()]={passkey:t,user:n},this.currentUser={...n,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser),{success:true,user:this.currentUser}}async login(r,t){let e=r.toLowerCase(),n=V[e];if(n)return n.passkey!==t?{success:false,error:"Invalid email or passkey combination.",errorCode:"invalid-credential"}:(this.currentUser={...n.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser),{success:true,user:this.currentUser});if(r&&t.length>=6){let i={uid:H("MOCK"),email:e,fullName:r.split("@")[0]||"Operative",clearanceLevel:"Level 1",hasBiometrics:false,createdAt:new Date().toISOString(),lastLoginAt:new Date().toISOString()};return this.currentUser=i,C(i),this.emit(i),{success:true,user:i}}return {success:false,error:"Invalid email or passkey combination.",errorCode:"invalid-credential"}}async logout(){return this.currentUser=null,C(null),this.emit(null),{success:true}}async resetPassword(r){return r?{success:true,user:void 0}:{success:false,error:"Please enter your email address first.",errorCode:"missing-email"}}async verifyFace(r){let t=this.currentUser||{uid:H("FACE"),email:"stark@avengers.io",fullName:"Tony Stark (Facial Match)",clearanceLevel:"Level 1",hasBiometrics:true,lastLoginAt:new Date().toISOString()};return this.currentUser=t,C(t),this.emit(t),{success:true,user:t}}async verifyVoice(r){let t=this.currentUser||{uid:H("VOICE"),email:"stark@avengers.io",fullName:"Tony Stark (Voice Match)",clearanceLevel:"Level 1",hasBiometrics:true,lastLoginAt:new Date().toISOString()};return this.currentUser=t,C(t),this.emit(t),{success:true,user:t}}async verifyFingerprint(r){let t=this.currentUser||{uid:H("FP"),email:"stark@avengers.io",fullName:"Tony Stark (Fingerprint Match)",clearanceLevel:"Level 1",hasBiometrics:true,lastLoginAt:new Date().toISOString()};return this.currentUser=t,C(t),this.emit(t),{success:true,user:t}}async enrollBiometrics(r){if(!this.currentUser)return {success:false,error:"Active session required for biometric enrollment.",errorCode:"no-session"};this.currentUser={...this.currentUser,hasBiometrics:true};let t=this.currentUser.email.toLowerCase();return V[t]&&(V[t]={...V[t],user:{...V[t].user,hasBiometrics:true}}),C(this.currentUser),this.emit(this.currentUser),{success:true,user:this.currentUser}}async verifyPasskey(r){if(typeof window>"u"||!window.PublicKeyCredential)return {success:false,error:"WebAuthn / Passkey authentication is not supported in this environment.",errorCode:"passkey-not-supported"};try{let t=r??this.currentUser?.email??"",e=await fetch(`${typeof process<"u"?process.env.NEXT_PUBLIC_AUTH_API_URL??"http://localhost:8000":"http://localhost:8000"}/api/v1/auth/webauthn/options?email=${encodeURIComponent(t)}`).then(c=>c.json()),n=await browser.startAuthentication(e),i=await fetch(`${typeof process<"u"?process.env.NEXT_PUBLIC_AUTH_API_URL??"http://localhost:8000":"http://localhost:8000"}/api/v1/auth/webauthn/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)}).then(c=>{if(!c.ok)throw new Error(c.statusText);return c.json()});return i.success&&i.user?(this.currentUser={...i.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser),{success:!0,user:this.currentUser}):{success:!1,error:i.error||"Passkey verification failed on server.",errorCode:"passkey-verification-failed"}}catch(t){return {success:false,error:t.message||"Passkey authentication failed.",errorCode:"passkey-auth-error"}}}async getCurrentUser(){return this.currentUser}onAuthStateChanged(r){return this.listeners.add(r),queueMicrotask(()=>r(this.currentUser)),()=>this.listeners.delete(r)}},X=class{constructor(r){this.name="BackendAuthAdapter";this.listeners=new Set;this.currentUser=null;this.baseUrl=r||(typeof process<"u"?process.env.NEXT_PUBLIC_AUTH_API_URL??"http://localhost:8000":"http://localhost:8000"),this.currentUser=be();}emit(r){this.listeners.forEach(t=>{try{t(r);}catch{}});}async request(r,t={}){let e=await fetch(`${this.baseUrl}${r}`,{headers:{"Content-Type":"application/json",...t.headers||{}},...t});if(!e.ok){let n=await e.json().catch(()=>({detail:e.statusText}));throw new Error(n.detail||e.statusText)}return await e.json()}async register(r,t,e){try{let n=await this.request("/api/v1/auth/register",{method:"POST",body:JSON.stringify({email:r,passkey:t,full_name:e})});return n.success&&n.user&&(this.currentUser={...n.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser)),{success:n.success,user:n.user}}catch(n){return {success:false,error:n.message||"Registration failed"}}}async login(r,t){try{let e=await this.request("/api/v1/auth/login",{method:"POST",body:JSON.stringify({email:r,passkey:t})});return e.success&&e.user&&(this.currentUser={...e.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser)),{success:e.success,user:e.user}}catch(e){return {success:false,error:e.message||"Login failed"}}}async logout(){return this.currentUser=null,C(null),this.emit(null),{success:true}}async resetPassword(r){return r?{success:true}:{success:false,error:"Please enter your email address first.",errorCode:"missing-email"}}async verifyFace(r){try{let t=await this.request("/api/v1/auth/verify-face",{method:"POST",body:JSON.stringify({image_base64:r})});return t.success&&t.user&&(this.currentUser={...t.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser)),{success:t.success,user:t.user}}catch(t){return {success:false,error:t.message||"Facial verification failed"}}}async verifyVoice(r){try{let t=new FormData;t.append("file",r,"voice.wav");let e=await fetch(`${this.baseUrl}/api/v1/auth/verify-voice`,{method:"POST",body:t});if(!e.ok)throw new Error(e.statusText);let n=await e.json();return n.success&&n.user&&(this.currentUser={...n.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser)),{success:n.success,user:n.user}}catch(t){return {success:false,error:t.message||"Voice verification failed"}}}async verifyFingerprint(r){let t={uid:H("FP-BE"),email:"stark@avengers.io",fullName:"Tony Stark (Backend FP)",clearanceLevel:"Level 1",hasBiometrics:true,lastLoginAt:new Date().toISOString()};return this.currentUser=t,C(t),this.emit(t),{success:true,user:t}}async enrollBiometrics(r){return this.currentUser?(this.currentUser={...this.currentUser,hasBiometrics:true},C(this.currentUser),this.emit(this.currentUser),{success:true,user:this.currentUser}):{success:false,error:"Active session required.",errorCode:"no-session"}}async verifyPasskey(r){if(typeof window>"u"||!window.PublicKeyCredential)return {success:false,error:"WebAuthn / Passkey authentication is not supported in this environment.",errorCode:"passkey-not-supported"};try{let t=r??this.currentUser?.email??"",e=await this.request(`/api/v1/auth/webauthn/options?email=${encodeURIComponent(t)}`),n=await browser.startAuthentication(e),i=await this.request("/api/v1/auth/webauthn/verify",{method:"POST",body:JSON.stringify(n)});return i.success&&i.user?(this.currentUser={...i.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser),{success:!0,user:this.currentUser}):{success:!1,error:"Passkey verification failed on server.",errorCode:"passkey-verification-failed"}}catch(t){return {success:false,error:t.message||"Passkey authentication failed.",errorCode:"passkey-auth-error"}}}async getCurrentUser(){return this.currentUser}onAuthStateChanged(r){return this.listeners.add(r),queueMicrotask(()=>r(this.currentUser)),()=>this.listeners.delete(r)}};function re(a="mock",r){switch(a){case "backend":return new X(r?.baseUrl);case "firebase":return new Y;default:return new Y}}var K=class{constructor(){this.ctx=null;}ensureContext(){if(typeof window>"u")return null;if(!this.ctx){let r=window.AudioContext||window.webkitAudioContext;r&&(this.ctx=new r);}return this.ctx&&this.ctx.state==="suspended"&&this.ctx.resume().catch(()=>{}),this.ctx}playBeep(r=800,t="sine",e=.1,n=.15){let i=this.ensureContext();if(i)try{let c=i.createOscillator(),y=i.createGain();c.type=t,c.frequency.setValueAtTime(r,i.currentTime),y.gain.setValueAtTime(n,i.currentTime),y.gain.exponentialRampToValueAtTime(.001,i.currentTime+e),c.connect(y),y.connect(i.destination),c.start(),c.stop(i.currentTime+e);}catch{}}playSuccess(){this.playBeep(523.25,"triangle",.15),setTimeout(()=>this.playBeep(659.25,"triangle",.15),120),setTimeout(()=>this.playBeep(783.99,"triangle",.25),240);}playError(){this.playBeep(220,"sawtooth",.2,.2),setTimeout(()=>this.playBeep(160,"sawtooth",.3,.2),150);}unlock(){this.ensureContext();}};var fe=react.createContext(null);function _e({children:a,adapter:r="mock",adapterOptions:t}){let e=react.useMemo(()=>typeof r=="string"?re(r,t):r,[r]),n=react.useMemo(()=>{let l=e.name.toLowerCase();return l.includes("backend")?"backend":l.includes("firebase")?"firebase":"mock"},[e]),[i,c]=react.useState(null),[y,u]=react.useState("idle"),[g,d]=react.useState(null),[o,p]=react.useState(true),[P,k]=react.useState("passkey"),[I,w]=react.useState(false),[x,E]=react.useState("Greetings. System active. Enter valid credentials or register new clearance."),[F,O]=react.useState({open:false,isSuccess:true,title:"",message:""}),L=react.useMemo(()=>new K,[]),D=react.useCallback((l=800,f="sine",U=.1)=>{o&&L.playBeep(l,f,U);},[o,L]),q=react.useCallback(()=>{o&&L.playSuccess();},[o,L]),N=react.useCallback(()=>{o&&L.playError();},[o,L]),h=react.useCallback(l=>{E(l);},[]),m=react.useCallback((l,f,U)=>{O({open:true,isSuccess:l,title:f,message:U}),l?q():N();},[q,N]),z=react.useCallback(()=>{D(800,"sine",.05),O(l=>({...l,open:false}));},[D]),Ee=react.useCallback(l=>{k(l),D(700,"sine",.1),h({passkey:"Passkey entry active.",retina:"Facial scan camera matrix standby.",voice:"Voice spectral analyzer standby.",fingerprint:"Fingerprint capacitive scanner standby."}[l]);},[D,h]),Se=react.useCallback(()=>{w(l=>!l),d(null),D(900,"sine",.1),h(I?"Sign in mode active.":"Registration mode active. Submit email and passkey.");},[I,D,h]),Ie=react.useCallback(()=>{p(l=>{let f=!l;return f?(L.playBeep(1e3,"sine",.1),h("Audio sound system online.")):h("Audio sound system muted."),f});},[L,h]);react.useEffect(()=>{u("loading");let l=false,f=e.onAuthStateChanged(T=>{l||(c(T),u(T?"authenticated":"unauthenticated"));});(async()=>{try{let T=await e.getCurrentUser();if(l)return;c(T),u(T?"authenticated":"unauthenticated");}catch{if(l)return;u("unauthenticated");}})();let U=window.setTimeout(()=>{l||u(T=>T==="loading"?"unauthenticated":T);},5e3);return ()=>{l=true,window.clearTimeout(U),f();}},[e]);let Ce=react.useCallback(async(l,f)=>{u("loading"),d(null),h("Validating credentials with authentication core...");let U=await e.login(l,f);if(!U.success){d(U.error||"Authentication failed"),c(null),u("error"),N(),h(`[ERROR]: ${U.error}`),m(false,"AUTHENTICATION FAILED",U.error||"");return}c(U.user??null),u("authenticated"),m(true,"ACCESS GRANTED",`Security clearance confirmed for ${l}. Welcome back.`);},[e,h,N,m]),Re=react.useCallback(async(l,f,U)=>{u("loading"),d(null),h("Creating new operative clearance record...");let T=await e.register(l,f,U);if(!T.success){d(T.error||"Registration failed"),c(null),u("error"),N(),h(`[ERROR]: ${T.error}`),m(false,"REGISTRATION FAILED",T.error||"");return}c(T.user??null),u("authenticated"),m(true,"REGISTRATION COMPLETE",`Operative [${l}] successfully enrolled into Stark Security Database.`);},[e,h,N,m]),ke=react.useCallback(async()=>{u("loading"),await e.logout(),u("unauthenticated"),c(null),D(400,"sine",.2),h("Session terminated.");},[e,D,h]),Te=react.useCallback(async l=>{let f=await e.resetPassword(l);if(!f.success){N(),m(false,"RECOVERY ERROR",f.error||"");return}m(true,"RECOVERY DISPATCHED",`Passkey reset instructions sent to ${l}.`);},[e,N,m]),Pe=react.useCallback(async l=>{u("loading"),h("Analyzing facial geometry mesh...");let f=await e.verifyFace(l);if(!f.success){c(null),u("error"),N(),m(false,"FACIAL SCAN FAILED",f.error||"");return}c(f.user??null),u("authenticated"),m(true,"FACIAL SCAN VERIFIED","Iris vector scan matched in database.");},[e,h,N,m]),Ue=react.useCallback(async l=>{u("loading"),h("Listening for voice waveform match...");let f=await e.verifyVoice(l);if(!f.success){c(null),u("error"),N(),m(false,"VOICE VERIFICATION FAILED",f.error||"");return}c(f.user??null),u("authenticated"),m(true,"VOICE PRINT MATCHED","Voice acoustic spectrum matches Operative profile.");},[e,h,N,m]),Me=react.useCallback(async l=>{u("loading"),h("Fingerprint capacitive scan in progress...");let f=await e.verifyFingerprint(l);if(!f.success){c(null),u("error"),N(),m(false,"FINGERPRINT FAILED",f.error||"");return}c(f.user??null),u("authenticated"),m(true,"FINGERPRINT AUTHORIZED","Dermal ridge pattern verified.");},[e,h,N,m]),Oe=react.useCallback(async()=>{if(!i){N(),m(false,"ENROLLMENT FAILED","No active session. Sign in first.");return}let l=await e.enrollBiometrics(i.uid);if(!l.success){N(),m(false,"ENROLLMENT FAILED",l.error||"");return}c(f=>f&&{...f,hasBiometrics:true}),m(true,"BIOMETRIC LINKED","Device biometrics securely registered to profile.");},[e,i,N,m]),Le=react.useCallback(async()=>{u("loading"),h("Initializing WebAuthn passkey assertion...");let l=await e.verifyPasskey(i?.email);return l.success?(c(l.user??null),u("authenticated"),m(true,"PASSKEY VERIFIED","Device passkey assertion matched in database."),l):(d(l.error||"Passkey authentication failed"),u("error"),N(),m(false,"PASSKEY FAILED",l.error||""),l)},[e,i,h,N,m]),De={user:i,status:y,error:g,adapter:e,adapterName:n,audioEnabled:o,activeMethod:P,isRegisterMode:I,terminalText:x,modal:F,login:Ce,register:Re,logout:ke,resetPassword:Te,verifyFace:Pe,verifyVoice:Ue,verifyFingerprint:Me,enrollBiometrics:Oe,verifyPasskey:Le,setActiveMethod:Ee,toggleMode:Se,toggleAudio:Ie,updateTerminal:h,showModal:m,closeModal:z,playBeep:D,playSuccess:q,playError:N};return jsxRuntime.jsx(fe.Provider,{value:De,children:a})}function R(){let a=react.useContext(fe);if(!a)throw new Error("useAuth must be used within an <AuthProvider>");return a}function ae(){let{user:a,terminalText:r,playBeep:t,updateTerminal:e}=R(),[n,i]=react.useState(false),[c,y]=react.useState(36),[u,g]=react.useState("5.8 GB"),[d,o]=react.useState("00:00:00"),p=react.useRef(null);react.useEffect(()=>{let w=setInterval(()=>{y(Math.floor(Math.random()*25)+20);let x=(5.2+Math.random()*.8).toFixed(1);g(`${x} GB`);let E=new Date,F=String(E.getHours()).padStart(2,"0"),O=String(E.getMinutes()).padStart(2,"0"),L=String(E.getSeconds()).padStart(2,"0");o(`${F}:${O}:${L}`);},2e3);return ()=>clearInterval(w)},[]);let P=()=>{i(true),t(300,"triangle",.4),e("Arc Reactor energy pulse triggered."),setTimeout(()=>i(false),500);},k=a?"CONNECTED":"DISCONNECTED",I=a?"text-cyber-emerald":"text-cyber-red";return jsxRuntime.jsxs("section",{className:"flex flex-col items-center justify-center relative min-h-[300px] w-full",children:[jsxRuntime.jsxs("div",{className:"relative w-64 h-64 md:w-80 md:h-80 flex items-center justify-center cursor-pointer group select-none",onClick:P,role:"button","aria-label":"Arc Reactor pulse trigger",children:[jsxRuntime.jsx("div",{className:"absolute inset-0 border border-cyber-cyan/20 rounded-full"}),jsxRuntime.jsx("div",{className:"absolute inset-[-10px] border border-dashed border-cyber-cyan/10 rounded-full"}),jsxRuntime.jsx("div",{className:"absolute inset-2 border-2 border-dashed border-cyber-cyan/40 rounded-full animate-spin-reverse"}),jsxRuntime.jsxs("div",{className:"absolute inset-8 border border-cyber-cyan/60 rounded-full animate-spin-slow flex items-center justify-center",children:[jsxRuntime.jsx("div",{className:"w-full h-0.5 bg-cyber-cyan/30 absolute"}),jsxRuntime.jsx("div",{className:"h-full w-0.5 bg-cyber-cyan/30 absolute"})]}),jsxRuntime.jsxs("div",{className:"absolute inset-12 border border-cyber-cyan/30 rounded-full flex items-center justify-center",children:[jsxRuntime.jsx("div",{className:"w-2 h-2 bg-cyber-cyan rounded-full absolute -top-1"}),jsxRuntime.jsx("div",{className:"w-2 h-2 bg-cyber-cyan rounded-full absolute -bottom-1"}),jsxRuntime.jsx("div",{className:"w-2 h-2 bg-cyber-cyan rounded-full absolute -left-1"}),jsxRuntime.jsx("div",{className:"w-2 h-2 bg-cyber-cyan rounded-full absolute -right-1"})]}),jsxRuntime.jsx("div",{ref:p,id:"arc-core",className:["relative w-28 h-28 md:w-36 md:h-36 rounded-full bg-cyber-cyan/10 border-2 border-cyber-cyan flex flex-col items-center justify-center shadow-cyber-glow transition-all duration-500 group-hover:scale-105",n?"scale-125 shadow-cyber-glow-strong":""].join(" "),children:jsxRuntime.jsx("div",{className:"w-16 h-16 md:w-20 md:h-20 rounded-full bg-cyber-cyan/20 border border-cyber-cyan/80 flex items-center justify-center animate-pulse-glow",children:jsxRuntime.jsx(lucideReact.Zap,{className:"w-8 h-8 text-cyber-cyan drop-shadow-[0_0_10px_#00f3ff]"})})}),jsxRuntime.jsx("div",{className:"absolute inset-0 rounded-full animate-spin-slow opacity-30 bg-[conic-gradient(from_0deg,transparent_0_300deg,rgba(0,243,255,0.4)_360deg)] pointer-events-none"}),jsxRuntime.jsxs("div",{className:"absolute -top-4 left-0 text-[10px] text-cyber-cyan/70 tracking-widest bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30",children:["SYS.CPU: ",jsxRuntime.jsxs("span",{className:"font-bold",children:[c,"%"]})]}),jsxRuntime.jsxs("div",{className:"absolute -top-4 right-0 text-[10px] text-cyber-cyan/70 tracking-widest bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30",children:["MEM: ",jsxRuntime.jsx("span",{className:"font-bold",children:u})]}),jsxRuntime.jsxs("div",{className:"absolute -bottom-4 left-0 text-[10px] text-cyber-cyan/70 tracking-widest bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30",children:["STATUS: ",jsxRuntime.jsx("span",{className:`${I} font-bold`,children:k})]}),jsxRuntime.jsxs("div",{className:"absolute -bottom-4 right-0 text-[10px] text-cyber-cyan/70 tracking-widest bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30",children:["PWR: ",jsxRuntime.jsx("span",{className:"text-cyber-cyan font-bold",children:"100%"})]})]}),jsxRuntime.jsxs("div",{className:"mt-6 text-center",children:[jsxRuntime.jsx("div",{className:"font-orbitron tracking-widest text-sm text-cyber-cyan text-glow",children:"ARC REACTOR MK VII"}),jsxRuntime.jsx("div",{className:"text-[11px] text-cyber-cyan/50 tracking-wider",children:"STARK SECURITY MATRIX // ONLINE"})]}),jsxRuntime.jsxs("div",{className:"mt-6 w-full max-w-md bg-cyber-bg/80 border border-cyber-cyan/30 p-2.5 rounded text-xs font-mono h-20 overflow-hidden relative",children:[jsxRuntime.jsxs("div",{className:"text-[10px] text-cyber-cyan/40 mb-1 border-b border-cyber-cyan/20 pb-0.5 flex justify-between",children:[jsxRuntime.jsx("span",{children:"JARVIS_SPEECH_LOG"}),jsxRuntime.jsx("span",{children:d})]}),jsxRuntime.jsxs("p",{className:"text-cyber-cyan/90 leading-tight",children:["\u201C",r,"\u201D"]})]})]})}function ne(){let{isRegisterMode:a,login:r,register:t,resetPassword:e,verifyPasskey:n,error:i,status:c,playBeep:y}=R(),[u,g]=react.useState(""),[d,o]=react.useState(""),[p,P]=react.useState(""),[k,I]=react.useState(false),[w,x]=react.useState(true),[E,F]=react.useState(null),O=c==="loading",L=async m=>{m.preventDefault(),F(null);let z=d.trim();if(!z||!p){F("Email and passkey are required.");return}if(a){if(!u.trim()){F("Full name is required for registration.");return}if(p.length<6){F("Passkey must be at least 6 characters long.");return}await t(z,p,u.trim());}else await r(z,p);},D=async m=>{m.preventDefault(),await e(d.trim());},q=()=>{I(m=>!m),y(1100,"sine",.05);},N=a?"ENROLL OPERATIVE":"AUTHENTICATE",h=E||i;return jsxRuntime.jsxs("form",{onSubmit:L,className:"space-y-4",noValidate:true,children:[a&&jsxRuntime.jsxs("div",{children:[jsxRuntime.jsx("label",{htmlFor:"jarvis-fullname",className:"block text-xs text-cyber-cyan/70 tracking-widest mb-1 uppercase",children:"Full Name / Operative ID"}),jsxRuntime.jsxs("div",{className:"relative",children:[jsxRuntime.jsx(lucideReact.UserCheck,{className:"w-4 h-4 absolute left-3 top-3 text-cyber-cyan/50"}),jsxRuntime.jsx("input",{id:"jarvis-fullname",type:"text",autoComplete:"name","aria-label":"Full Name",value:u,onChange:m=>g(m.target.value),placeholder:"Tony Stark",className:"w-full bg-cyber-bg/90 border border-cyber-cyan/40 rounded px-10 py-2.5 text-sm text-cyber-cyan focus:outline-none focus:border-cyber-cyan focus:ring-1 focus:ring-cyber-cyan transition-all placeholder:text-cyber-cyan/30",disabled:O})]})]}),jsxRuntime.jsxs("div",{children:[jsxRuntime.jsx("label",{htmlFor:"jarvis-email",className:"block text-xs text-cyber-cyan/70 tracking-widest mb-1 uppercase",children:"Email Address"}),jsxRuntime.jsxs("div",{className:"relative",children:[jsxRuntime.jsx(lucideReact.Mail,{className:"w-4 h-4 absolute left-3 top-3 text-cyber-cyan/50"}),jsxRuntime.jsx("input",{id:"jarvis-email",type:"email",autoComplete:"email","aria-label":"Email Address",value:d,onChange:m=>o(m.target.value),placeholder:"stark@avengers.io",required:true,className:"w-full bg-cyber-bg/90 border border-cyber-cyan/40 rounded px-10 py-2.5 text-sm text-cyber-cyan focus:outline-none focus:border-cyber-cyan focus:ring-1 focus:ring-cyber-cyan transition-all placeholder:text-cyber-cyan/30",disabled:O})]})]}),jsxRuntime.jsxs("div",{children:[jsxRuntime.jsx("label",{htmlFor:"jarvis-passkey",className:"block text-xs text-cyber-cyan/70 tracking-widest mb-1 uppercase",children:"Passkey"}),jsxRuntime.jsxs("div",{className:"relative",children:[jsxRuntime.jsx(lucideReact.Lock,{className:"w-4 h-4 absolute left-3 top-3 text-cyber-cyan/50"}),jsxRuntime.jsx("input",{id:"jarvis-passkey",type:k?"text":"password",autoComplete:a?"new-password":"current-password","aria-label":"Passkey",value:p,minLength:6,required:true,onChange:m=>P(m.target.value),placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",className:"w-full bg-cyber-bg/90 border border-cyber-cyan/40 rounded px-10 py-2.5 text-sm text-cyber-cyan focus:outline-none focus:border-cyber-cyan focus:ring-1 focus:ring-cyber-cyan transition-all placeholder:text-cyber-cyan/30 pr-12",disabled:O}),jsxRuntime.jsx("button",{type:"button",onClick:q,tabIndex:-1,className:"absolute right-3 top-3 text-cyber-cyan/50 hover:text-cyber-cyan transition-colors","aria-label":k?"Hide passkey":"Show passkey",children:k?jsxRuntime.jsx(lucideReact.EyeOff,{className:"w-4 h-4"}):jsxRuntime.jsx(lucideReact.Eye,{className:"w-4 h-4"})})]})]}),h&&jsxRuntime.jsx("div",{role:"alert",className:"text-xs text-cyber-red bg-cyber-red/10 border border-cyber-red/40 p-2.5 rounded font-mono text-center",children:h}),jsxRuntime.jsxs("div",{className:"relative my-4",children:[jsxRuntime.jsx("div",{className:"absolute inset-0 flex items-center",children:jsxRuntime.jsx("div",{className:"w-full border-t border-cyber-cyan/30"})}),jsxRuntime.jsx("div",{className:"relative flex justify-center",children:jsxRuntime.jsx("span",{className:"px-3 text-xs text-cyber-cyan/50 bg-cyber-bg",children:"OR"})})]}),jsxRuntime.jsx("button",{type:"button",onClick:()=>{y(900,"sine",.1),n();},disabled:O,className:"w-full bg-cyber-cyan/10 border-2 border-cyber-emerald hover:bg-cyber-cyan/30 text-cyber-emerald font-orbitron py-3 rounded tracking-widest font-bold transition-all shadow-cyber-glow flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed","aria-label":"Use device passkey",children:jsxRuntime.jsx("span",{children:"USE DEVICE PASSKEY"})}),jsxRuntime.jsxs("div",{className:"flex justify-between items-center text-xs pt-1",children:[jsxRuntime.jsxs("label",{className:"flex items-center space-x-2 cursor-pointer text-cyber-cyan/70 hover:text-cyber-cyan",children:[jsxRuntime.jsx("input",{type:"checkbox",checked:w,onChange:m=>x(m.target.checked),className:"accent-cyber-cyan bg-cyber-bg border-cyber-cyan/40 rounded",disabled:O}),jsxRuntime.jsx("span",{children:"REMEMBER ID"})]}),jsxRuntime.jsx("a",{href:"#",onClick:D,className:"text-cyber-cyan/70 hover:text-cyber-cyan underline transition-all",children:"RECOVER ACCESS"})]}),jsxRuntime.jsx("button",{type:"submit",disabled:O,className:"w-full mt-4 bg-cyber-cyan/10 border-2 border-cyber-cyan hover:bg-cyber-cyan/30 text-cyber-cyan font-orbitron py-3 rounded tracking-widest font-bold transition-all shadow-cyber-glow hover:scale-[1.01] active:scale-[0.99] flex items-center justify-center space-x-2 disabled:opacity-60 disabled:cursor-not-allowed disabled:hover:scale-100",children:O?jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx(lucideReact.Loader2,{className:"w-4 h-4 animate-spin"}),jsxRuntime.jsx("span",{children:"AUTHENTICATING..."})]}):jsxRuntime.jsx("span",{children:N})})]})}function ce(){let{verifyFace:a,playBeep:r,status:t}=R(),[e,n]=react.useState(false),[i,c]=react.useState("POSITION FACE IN FRAME"),[y,u]=react.useState(null),g=react.useRef(null),d=react.useRef(null),o=react.useRef(null);react.useEffect(()=>()=>{y&&window.clearInterval(y),g.current&&window.clearTimeout(g.current),o.current&&(o.current.getTracks().forEach(w=>w.stop()),o.current=null);},[]);let p=async()=>{if(!(typeof navigator>"u"||!navigator.mediaDevices))try{let w=await navigator.mediaDevices.getUserMedia({video:{facingMode:"user",width:320,height:240},audio:!1});o.current=w,d.current&&(d.current.srcObject=w,await d.current.play().catch(()=>{}));}catch{}},P=()=>{o.current&&(o.current.getTracks().forEach(w=>w.stop()),o.current=null),d.current&&(d.current.srcObject=null);},k=()=>{if(!d.current)return "";try{let w=d.current,x=document.createElement("canvas");x.width=w.videoWidth||320,x.height=w.videoHeight||240;let E=x.getContext("2d");if(E)return E.drawImage(w,0,0,x.width,x.height),x.toDataURL("image/jpeg",.6)}catch{}return ""};return jsxRuntime.jsxs("div",{className:"flex flex-col items-center space-y-4 py-2",children:[jsxRuntime.jsxs("div",{className:"relative w-48 h-48 border-2 border-cyber-cyan/50 rounded-lg overflow-hidden bg-cyber-bg/90 flex items-center justify-center",children:[jsxRuntime.jsx("div",{className:"absolute inset-0 bg-[radial-gradient(#00f3ff_1px,transparent_1px)] [background-size:12px_12px] opacity-20"}),jsxRuntime.jsx("video",{ref:d,muted:true,playsInline:true,className:"absolute inset-0 w-full h-full object-cover opacity-60"}),jsxRuntime.jsxs("div",{className:"w-32 h-32 border border-dashed border-cyber-cyan/60 rounded-full flex items-center justify-center relative z-10",children:[jsxRuntime.jsx(lucideReact.User,{className:"w-16 h-16 text-cyber-cyan/30"}),jsxRuntime.jsx("div",{className:"absolute top-4 left-6 w-1.5 h-1.5 bg-cyber-cyan rounded-full animate-ping"}),jsxRuntime.jsx("div",{className:"absolute top-8 right-8 w-1.5 h-1.5 bg-cyber-cyan rounded-full"})]}),e&&jsxRuntime.jsx("div",{className:"absolute left-0 right-0 h-0.5 bg-cyber-cyan shadow-cyber-glow-strong animate-scan-laser z-20"}),jsxRuntime.jsx("div",{className:"absolute bottom-2 text-[10px] tracking-wider text-cyber-cyan/80 bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30 z-30",children:i})]}),jsxRuntime.jsx("p",{className:"text-xs text-cyber-cyan/60 text-center",children:"Optic vector scan. Interacts with device hardware biometrics when enrolled."}),jsxRuntime.jsxs("button",{type:"button",onClick:async()=>{if(e||t==="loading")return;await p(),n(true),c("SCANNING FACIAL MESH...");let w=window.setInterval(()=>{r(1400,"sine",.05);},300);u(w),g.current=window.setTimeout(async()=>{window.clearInterval(w),u(null);let x=k();P(),c("MATCH CONFIRMED - 99.8%"),await a(x),n(false),g.current=null;},2500);},disabled:e||t==="loading",className:"w-full bg-cyber-cyan/10 border border-cyber-cyan hover:bg-cyber-cyan/30 text-cyber-cyan font-orbitron py-3 rounded tracking-widest font-bold transition-all shadow-cyber-glow flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed",children:[jsxRuntime.jsx(lucideReact.Scan,{className:"w-4 h-4"}),jsxRuntime.jsx("span",{children:e?"ANALYZING...":"INITIALIZE OPTIC SCAN"})]})]})}var at="JARVIS ACCESS AUTHORIZATION CODE SEVEN",ue=[10,14,18,14,10,7,5];function de(){let{verifyVoice:a,playBeep:r,status:t}=R(),[e,n]=react.useState(false),[i,c]=react.useState(ue),[y,u]=react.useState("LISTEN & VERIFY"),g=react.useRef(null),d=react.useRef(null),o=react.useRef(null),p=react.useRef([]);react.useEffect(()=>()=>{g.current&&window.clearInterval(g.current),d.current&&window.clearTimeout(d.current);},[]);let P=async()=>{if(e||t==="loading")return;if(n(true),u("ANALYZING FREQUENCY..."),p.current=[],typeof navigator<"u"&&navigator.mediaDevices)try{let I=await navigator.mediaDevices.getUserMedia({audio:!0}),w=window.MediaRecorder;if(w){let x=new w(I);o.current=x,x.ondataavailable=E=>{E.data&&E.data.size>0&&p.current.push(E.data);},x.start();}else I.getTracks().forEach(x=>x.stop());}catch{}let k=()=>{r(400+Math.random()*600,"sine",.05),c(()=>ue.map(()=>Math.floor(Math.random()*50)+6));};k(),g.current=window.setInterval(k,100),d.current=window.setTimeout(async()=>{g.current&&(window.clearInterval(g.current),g.current=null),c(ue);let I;if(o.current&&p.current.length>0){try{await new Promise(x=>{let E=o.current;E.onstop=()=>x(),E.stop(),E.stream&&E.stream.getTracks().forEach(F=>F.stop());});}catch{}let w=p.current[0]?.type||"audio/webm";I=new Blob(p.current,{type:w});}else I=new Blob(["jarvis-voice-sample"],{type:"audio/wav"});p.current=[],o.current=null,await a(I),n(false),u("LISTEN & VERIFY"),d.current=null;},2800);};return jsxRuntime.jsxs("div",{className:"flex flex-col items-center space-y-4 py-2",children:[jsxRuntime.jsxs("div",{className:"w-full h-32 border border-cyber-cyan/40 bg-cyber-bg/90 rounded p-3 flex flex-col items-center justify-center relative overflow-hidden",children:[jsxRuntime.jsx("div",{className:"flex items-end justify-center gap-1.5 w-full h-16",children:i.map((k,I)=>jsxRuntime.jsx("div",{className:"w-1.5 bg-cyber-cyan rounded-full transition-all duration-150",style:{height:`${k}px`,opacity:Math.max(.3,Math.min(1,k/50))}},I))}),jsxRuntime.jsxs("div",{className:"text-xs text-cyber-cyan font-mono mt-2 tracking-widest text-glow text-center",children:["PHRASE: \u201C",at,"\u201D"]})]}),jsxRuntime.jsx("p",{className:"text-xs text-cyber-cyan/60 text-center",children:"Voice print spectral verification. Speak phrase into microphone."}),jsxRuntime.jsxs("button",{type:"button",onClick:P,disabled:e||t==="loading",className:["w-full bg-cyber-cyan/10 border text-cyber-cyan font-orbitron py-3 rounded tracking-widest font-bold transition-all shadow-cyber-glow flex items-center justify-center gap-2 disabled:cursor-not-allowed",e?"border-cyber-emerald/80 bg-cyber-emerald/15 animate-pulse":"border-cyber-cyan hover:bg-cyber-cyan/30 disabled:opacity-60"].join(" "),children:[jsxRuntime.jsx(lucideReact.Mic,{className:"w-4 h-4"}),jsxRuntime.jsx("span",{children:y})]})]})}function me(){let{verifyFingerprint:a,playBeep:r,status:t}=R(),[e,n]=react.useState(0),[i,c]=react.useState(false),y=react.useRef(null);react.useEffect(()=>()=>{y.current&&window.clearInterval(y.current);},[]);let u=()=>{i||t==="loading"||(c(true),r(600,"sine",.05),y.current=window.setInterval(()=>{n(p=>{let P=p+10;return r(600+P*5,"sine",.05),P>=100?(y.current&&(window.clearInterval(y.current),y.current=null),g(),100):P});},120));},g=async()=>{let p=`fp_${Date.now()}_${Math.random().toString(36).slice(2)}`;await a(p),d(false);},d=(p=true)=>{y.current&&(window.clearInterval(y.current),y.current=null),c(false),p&&n(0);},o=e/100*360;return jsxRuntime.jsxs("div",{className:"flex flex-col items-center space-y-4 py-2",children:[jsxRuntime.jsxs("div",{role:"button",tabIndex:0,onMouseDown:u,onMouseUp:()=>d(e<100),onMouseLeave:()=>d(e<100),onTouchStart:u,onTouchEnd:()=>d(e<100),onKeyDown:p=>{(p.key===" "||p.key==="Enter")&&(p.preventDefault(),u());},onKeyUp:()=>d(e<100),className:["w-36 h-36 border-2 border-dashed rounded-full flex items-center justify-center bg-cyber-bg/90 cursor-pointer relative shadow-cyber-glow transition-all select-none",i?"border-cyber-cyan scale-105":"border-cyber-cyan/50 hover:border-cyber-cyan",t==="loading"?"pointer-events-none opacity-80":""].join(" "),"aria-label":"Fingerprint pad - press and hold to scan",children:[jsxRuntime.jsx(lucideReact.Fingerprint,{className:["w-20 h-20 transition-all duration-200",i?"text-cyber-cyan scale-110":"text-cyber-cyan/60"].join(" ")}),jsxRuntime.jsx("div",{className:"absolute inset-0 rounded-full pointer-events-none",style:{background:e>0?`conic-gradient(from 0deg, rgba(0,243,255,0.9) 0deg, rgba(0,243,255,0.9) ${o}deg, transparent ${o}deg, transparent 360deg)`:"transparent",WebkitMask:"radial-gradient(transparent 58%, black 59%, black 70%, transparent 71%)",mask:"radial-gradient(transparent 58%, black 59%, black 70%, transparent 71%)",opacity:e>0?1:0}}),jsxRuntime.jsx("div",{className:"absolute inset-3 rounded-full border-2 border-t-cyber-cyan/80 border-r-cyber-cyan/40 border-b-cyber-cyan/10 border-l-cyber-cyan/40",style:{animation:i?"spin 0.8s linear infinite":"none"}})]}),jsxRuntime.jsx("div",{className:"w-36 h-1.5 bg-cyber-cyan/20 rounded-full overflow-hidden",children:jsxRuntime.jsx("div",{className:"h-full bg-cyber-cyan transition-all duration-150",style:{width:`${e}%`}})}),jsxRuntime.jsx("p",{className:"text-xs text-cyber-cyan/60 text-center",children:"PRESS AND HOLD THUMBPRINT SCANNER TO VERIFY BIOMETRICS"})]})}function ht(){let{modal:a,closeModal:r}=R();return a.open?jsxRuntime.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-[fadeIn_.2s_ease-out]",role:"dialog","aria-modal":"true","aria-labelledby":"jarvis-modal-title",onClick:t=>{t.target===t.currentTarget&&r();},children:jsxRuntime.jsxs("div",{className:"cyber-panel max-w-sm w-full p-6 text-center rounded-lg space-y-4 shadow-cyber-glow-strong animate-[popIn_.25s_cubic-bezier(0.34,1.56,0.64,1)]",style:{borderColor:a.isSuccess?"rgba(0, 243, 255, 0.5)":"rgba(255, 0, 85, 0.5)"},children:[jsxRuntime.jsx("div",{className:"absolute top-[-2px] right-[-2px] w-3 h-3 border-t-2 border-r-2",style:{borderColor:a.isSuccess?"#00f3ff":"#ff0055"}}),jsxRuntime.jsx("div",{className:"absolute bottom-[-2px] left-[-2px] w-3 h-3 border-b-2 border-l-2",style:{borderColor:a.isSuccess?"#00f3ff":"#ff0055"}}),jsxRuntime.jsx("div",{className:"w-16 h-16 rounded-full mx-auto flex items-center justify-center border-2",style:{borderColor:a.isSuccess?"#00f3ff":"#ff0055",backgroundColor:a.isSuccess?"rgba(0, 243, 255, 0.2)":"rgba(255, 0, 85, 0.2)"},children:a.isSuccess?jsxRuntime.jsx(lucideReact.ShieldCheck,{className:"w-8 h-8",style:{color:"#00f3ff"}}):jsxRuntime.jsx(lucideReact.AlertTriangle,{className:"w-8 h-8",style:{color:"#ff0055"}})}),jsxRuntime.jsx("h3",{id:"jarvis-modal-title",className:`font-orbitron font-bold text-xl tracking-widest ${a.isSuccess?"text-glow":"text-glow-red"}`,style:{color:a.isSuccess?"#00f3ff":"#ff0055"},children:a.title}),jsxRuntime.jsx("p",{className:"text-xs text-cyber-cyan/80 leading-relaxed font-mono",children:a.message}),jsxRuntime.jsx("button",{onClick:r,className:"w-full bg-cyber-cyan/20 border border-cyber-cyan hover:bg-cyber-cyan/40 text-cyber-cyan font-orbitron py-2.5 rounded tracking-widest transition-all",children:"CONTINUE"})]})}):null}function gt(){let{activeMethod:a,setActiveMethod:r}=R(),t=[{id:"retina",icon:jsxRuntime.jsx(lucideReact.Eye,{className:"w-5 h-5 mb-1"}),label:"FACIAL/EYE"},{id:"passkey",icon:jsxRuntime.jsx(lucideReact.Target,{className:"w-5 h-5 mb-1"}),label:"PASSKEY"},{id:"voice",icon:jsxRuntime.jsx(lucideReact.Mic,{className:"w-5 h-5 mb-1"}),label:"VOICE"}],e="flex flex-col items-center justify-center p-3 border rounded transition-all group",n="border-cyber-cyan bg-cyber-cyan/20 shadow-cyber-glow",i="border-cyber-cyan/30 bg-cyber-cyan/5 hover:bg-cyber-cyan/20";return jsxRuntime.jsx("div",{className:"grid grid-cols-3 gap-3 mb-6",children:t.map(c=>{let y=a===c.id;return jsxRuntime.jsxs("button",{type:"button",id:`btn-method-${c.id}`,onClick:()=>r(c.id),className:`${e} ${y?n:i}`,"aria-pressed":y,children:[jsxRuntime.jsx("div",{className:`transition-transform ${y?"":"group-hover:scale-110"}`,children:c.icon}),jsxRuntime.jsx("span",{className:"text-[10px] tracking-wider",children:c.label})]},c.id)})})}function vt(){let{user:a,logout:r,enrollBiometrics:t,status:e}=R(),[n,i]=react.useState(false);return react.useEffect(()=>{i(typeof window<"u"&&typeof window.PublicKeyCredential<"u");},[]),a?jsxRuntime.jsxs("div",{className:"cyber-panel w-full max-w-md p-6 md:p-8 rounded-lg shadow-cyber-glow relative space-y-6 animate-[fadeIn_.3s_ease-out]",children:[jsxRuntime.jsx("div",{className:"cyber-corner-tr"}),jsxRuntime.jsx("div",{className:"cyber-corner-bl"}),jsxRuntime.jsxs("div",{className:"flex justify-between items-center border-b border-cyber-cyan/20 pb-4",children:[jsxRuntime.jsxs("div",{children:[jsxRuntime.jsx("h2",{className:"font-orbitron text-xl font-bold tracking-widest text-glow text-cyber-emerald",children:"CLEARANCE GRANTED"}),jsxRuntime.jsx("p",{className:"text-xs text-cyber-cyan/60 tracking-wider",children:"AUTHENTICATED OPERATIVE COMMAND"})]}),jsxRuntime.jsx("div",{className:"p-2 bg-cyber-emerald/10 border border-cyber-emerald/40 rounded-full text-cyber-emerald",children:jsxRuntime.jsx(lucideReact.ShieldCheck,{className:"w-6 h-6"})})]}),jsxRuntime.jsxs("div",{className:"space-y-3 font-mono text-xs bg-cyber-bg/90 p-4 border border-cyber-cyan/30 rounded",children:[jsxRuntime.jsxs("div",{className:"flex justify-between gap-2",children:[jsxRuntime.jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"OPERATIVE UID:"}),jsxRuntime.jsx("span",{className:"text-cyber-cyan font-bold truncate",children:a.uid})]}),jsxRuntime.jsxs("div",{className:"flex justify-between gap-2",children:[jsxRuntime.jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"EMAIL:"}),jsxRuntime.jsx("span",{className:"text-cyber-cyan font-bold truncate",children:a.email})]}),jsxRuntime.jsxs("div",{className:"flex justify-between gap-2",children:[jsxRuntime.jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"FULL NAME:"}),jsxRuntime.jsx("span",{className:"text-cyber-cyan font-bold truncate",children:a.fullName})]}),jsxRuntime.jsxs("div",{className:"flex justify-between gap-2",children:[jsxRuntime.jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"CLEARANCE:"}),jsxRuntime.jsx("span",{className:"text-cyber-emerald font-bold",children:a.clearanceLevel||"Level 1 - Full Access"})]}),jsxRuntime.jsxs("div",{className:"flex justify-between gap-2",children:[jsxRuntime.jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"BIOMETRICS:"}),jsxRuntime.jsx("span",{className:a.hasBiometrics?"text-cyber-emerald font-bold":"text-cyber-cyan font-bold",children:a.hasBiometrics?"ENROLLED":"NOT ENROLLED"})]})]}),jsxRuntime.jsxs("button",{type:"button",onClick:t,disabled:e==="loading"||a.hasBiometrics,className:"w-full bg-cyber-cyan/10 border border-cyber-cyan hover:bg-cyber-cyan/20 text-cyber-cyan py-2.5 rounded font-orbitron text-xs tracking-wider transition-all flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",children:[jsxRuntime.jsx(lucideReact.Fingerprint,{className:"w-4 h-4"}),jsxRuntime.jsx("span",{children:n?a.hasBiometrics?"BIOMETRICS ALREADY ENROLLED":"ENROLL DEVICE TOUCH ID / FACE ID":"ENROLL BIOMETRICS (SIMULATED)"})]}),jsxRuntime.jsxs("button",{type:"button",onClick:r,disabled:e==="loading",className:"w-full bg-cyber-red/10 border border-cyber-red hover:bg-cyber-red/30 text-cyber-red font-orbitron py-3 rounded tracking-widest font-bold transition-all flex items-center justify-center gap-2 disabled:opacity-60",children:[jsxRuntime.jsx(lucideReact.LogOut,{className:"w-4 h-4"}),jsxRuntime.jsx("span",{children:"TERMINATE SESSION (SIGN OUT)"})]})]}):null}function wt(){let{isRegisterMode:a,toggleMode:r,activeMethod:t,setActiveMethod:e,playBeep:n}=R();return jsxRuntime.jsxs("div",{className:"cyber-panel w-full max-w-md p-6 md:p-8 rounded-lg shadow-cyber-glow relative animate-[fadeIn_.3s_ease-out]",children:[jsxRuntime.jsx("div",{className:"cyber-corner-tr"}),jsxRuntime.jsx("div",{className:"cyber-corner-bl"}),jsxRuntime.jsxs("div",{className:"flex justify-between items-center mb-6 border-b border-cyber-cyan/20 pb-4",children:[jsxRuntime.jsxs("div",{children:[jsxRuntime.jsx("h2",{id:"auth-title",className:"font-orbitron text-xl md:text-2xl font-bold tracking-widest text-glow uppercase",children:a?"REGISTER":"SIGN IN"}),jsxRuntime.jsx("p",{className:"text-xs text-cyber-cyan/60 tracking-wider",children:a?"CREATE NEW OPERATIVE CLEARANCE":"AUTHENTICATION REQUIRED"})]}),jsxRuntime.jsxs("button",{type:"button",onClick:r,className:"text-xs border border-cyber-cyan/40 hover:border-cyber-cyan bg-cyber-cyan/10 hover:bg-cyber-cyan/20 px-3 py-1.5 rounded transition-all tracking-wider flex items-center gap-1",children:[jsxRuntime.jsx(lucideReact.RefreshCw,{className:"w-3 h-3"}),jsxRuntime.jsx("span",{children:a?"SIGN IN":"REGISTER"})]})]}),jsxRuntime.jsx(gt,{}),jsxRuntime.jsxs("div",{className:"min-h-[360px]",children:[t==="passkey"&&jsxRuntime.jsx(ne,{}),t==="retina"&&jsxRuntime.jsx(ce,{}),t==="voice"&&jsxRuntime.jsx(de,{}),t==="fingerprint"&&jsxRuntime.jsx(me,{})]}),jsxRuntime.jsxs("div",{className:"mt-6 pt-4 border-t border-cyber-cyan/20 flex justify-between items-center text-xs",children:[jsxRuntime.jsxs("button",{type:"button",onClick:()=>{e("fingerprint"),n(700,"sine",.1);},className:"text-cyber-cyan/60 hover:text-cyber-cyan flex items-center gap-1",children:[jsxRuntime.jsx(lucideReact.Fingerprint,{className:"w-3.5 h-3.5"}),jsxRuntime.jsx("span",{children:"FINGERPRINT SCAN"})]}),jsxRuntime.jsxs("span",{className:"text-cyber-cyan/40 uppercase tracking-wider",children:["PROTOCOL: ",t]})]})]})}function xt(){let{user:a,audioEnabled:r,toggleAudio:t,adapterName:e}=R(),[n,i]=react.useState({time:"13:14",date:""});react.useEffect(()=>{let y=()=>{let g=new Date,d=String(g.getHours()).padStart(2,"0"),o=String(g.getMinutes()).padStart(2,"0");i({time:`${d}:${o}`,date:g.toLocaleDateString("en-US",{weekday:"long",year:"numeric",month:"long",day:"numeric"})});};y();let u=setInterval(y,1e3);return ()=>clearInterval(u)},[]);let c=()=>{typeof document>"u"||(document.fullscreenElement?document.exitFullscreen&&document.exitFullscreen().catch(()=>{}):document.documentElement.requestFullscreen().catch(()=>{}));};return jsxRuntime.jsxs("div",{className:"min-h-screen w-screen flex flex-col justify-between p-4 md:p-8 relative overflow-x-hidden select-none",children:[jsxRuntime.jsxs("header",{className:"relative z-10 flex justify-between items-center w-full border-b border-cyber-cyan/20 pb-3",children:[jsxRuntime.jsxs("div",{className:"flex items-center space-x-3",children:[jsxRuntime.jsx("div",{className:"w-3 h-3 bg-cyber-cyan rounded-full animate-ping"}),jsxRuntime.jsxs("div",{children:[jsxRuntime.jsxs("h1",{className:"font-orbitron font-bold text-lg md:text-xl tracking-widest text-glow flex items-center gap-2",children:["J.A.R.V.I.S.",jsxRuntime.jsx("span",{className:"text-xs px-2 py-0.5 rounded bg-cyber-cyan/10 border border-cyber-cyan/40 text-cyber-cyan",children:"PRODUCTION AUTH v10.5"})]}),jsxRuntime.jsx("p",{className:"text-xs text-cyber-cyan/60 tracking-wider",children:"REALTIME AUTHENTICATION & ENCRYPTION ENGINE"})]})]}),jsxRuntime.jsxs("div",{className:"hidden md:flex items-center space-x-8 text-xs",children:[jsxRuntime.jsxs("div",{className:"flex items-center space-x-2",children:[jsxRuntime.jsx("span",{className:"text-cyber-cyan/50",children:"ADAPTER:"}),jsxRuntime.jsx("span",{className:"text-cyber-emerald font-bold uppercase",children:e})]}),jsxRuntime.jsxs("div",{className:"flex items-center space-x-2",children:[jsxRuntime.jsx("span",{className:"text-cyber-cyan/50",children:"WEBAUTHN:"}),jsxRuntime.jsx("span",{className:"text-cyber-cyan font-bold",children:"READY"})]}),jsxRuntime.jsxs("div",{className:"flex items-center space-x-2",children:[jsxRuntime.jsx("span",{className:"text-cyber-cyan/50",children:"AI CORE:"}),jsxRuntime.jsx("span",{className:"text-cyber-emerald animate-pulse",children:"ONLINE"})]})]}),jsxRuntime.jsxs("div",{className:"flex items-center space-x-2",children:[jsxRuntime.jsx("button",{onClick:t,"aria-label":r?"Mute audio":"Enable audio",className:["p-2 bg-cyber-cyan/10 border border-cyber-cyan/30 hover:bg-cyber-cyan/20 text-cyber-cyan transition-all rounded",r?"":"opacity-50"].join(" "),children:r?jsxRuntime.jsx(lucideReact.Volume2,{className:"w-4 h-4"}):jsxRuntime.jsx(lucideReact.VolumeX,{className:"w-4 h-4"})}),jsxRuntime.jsx("button",{onClick:c,"aria-label":"Toggle fullscreen",className:"p-2 bg-cyber-cyan/10 border border-cyber-cyan/30 hover:bg-cyber-cyan/20 text-cyber-cyan transition-all rounded",children:jsxRuntime.jsx(lucideReact.Maximize,{className:"w-4 h-4"})})]})]}),jsxRuntime.jsxs("main",{className:"relative z-10 flex-1 grid grid-cols-1 lg:grid-cols-12 gap-8 items-center my-6",children:[jsxRuntime.jsx("section",{className:"lg:col-span-6 flex flex-col items-center justify-center relative min-h-[300px]",children:jsxRuntime.jsx(ae,{})}),jsxRuntime.jsx("section",{className:"lg:col-span-6 flex justify-center",children:a?jsxRuntime.jsx(vt,{}):jsxRuntime.jsx(wt,{})})]}),jsxRuntime.jsxs("footer",{className:"relative z-10 flex flex-col md:flex-row justify-between items-center border-t border-cyber-cyan/20 pt-3 text-xs text-cyber-cyan/60 space-y-2 md:space-y-0",children:[jsxRuntime.jsxs("div",{className:"flex items-center space-x-4",children:[jsxRuntime.jsxs("span",{children:["STARK INDUSTRIES \xA9 ",new Date().getFullYear()]}),jsxRuntime.jsx("span",{children:"\u2022"}),jsxRuntime.jsxs("span",{className:"text-cyber-emerald flex items-center gap-1",children:[jsxRuntime.jsx("span",{className:"w-2 h-2 rounded-full bg-cyber-emerald animate-ping"})," ","SECURE AUTH NODE ACTIVE"]})]}),jsxRuntime.jsxs("div",{className:"flex items-center space-x-6 font-mono",children:[jsxRuntime.jsx("div",{className:"text-cyber-cyan text-sm md:text-base font-bold tracking-widest text-glow",children:n.time}),jsxRuntime.jsx("div",{className:"text-cyber-cyan/50 text-xs uppercase",children:n.date})]})]}),jsxRuntime.jsx(ht,{}),jsxRuntime.jsx("style",{dangerouslySetInnerHTML:{__html:`
|
|
2
|
+
@keyframes fadeIn {
|
|
3
|
+
from { opacity: 0; }
|
|
4
|
+
to { opacity: 1; }
|
|
5
|
+
}
|
|
6
|
+
@keyframes popIn {
|
|
7
|
+
0% { opacity: 0; transform: scale(0.85); }
|
|
8
|
+
100% { opacity: 1; transform: scale(1); }
|
|
9
|
+
}
|
|
10
|
+
`}})]})}function At(){let a=react.useRef(null),r=react.useRef(null);return react.useEffect(()=>{let t=a.current;if(!t)return;let e=t.getContext("2d");if(!e)return;let n=0,i=0,c=[],y=()=>{n=t.width=window.innerWidth,i=t.height=window.innerHeight;};y(),window.addEventListener("resize",y);let u=Math.max(50,Math.min(100,Math.floor(n*i/24e3)));for(let d=0;d<u;d++)c.push({x:Math.random()*n,y:Math.random()*i,size:Math.random()*1.5+.5,speedX:(Math.random()-.5)*.3,speedY:(Math.random()-.5)*.3,opacity:Math.random()*.5+.2});let g=()=>{e.clearRect(0,0,n,i),e.strokeStyle="rgba(0, 243, 255, 0.035)",e.lineWidth=1;let d=40;for(let o=0;o<n;o+=d)e.beginPath(),e.moveTo(o,0),e.lineTo(o,i),e.stroke();for(let o=0;o<i;o+=d)e.beginPath(),e.moveTo(0,o),e.lineTo(n,o),e.stroke();c.forEach(o=>{o.x+=o.speedX,o.y+=o.speedY,o.x<0?o.x=n:o.x>n&&(o.x=0),o.y<0?o.y=i:o.y>i&&(o.y=0),e.fillStyle=`rgba(0, 243, 255, ${o.opacity})`,e.beginPath(),e.arc(o.x,o.y,o.size,0,Math.PI*2),e.fill();}),r.current=requestAnimationFrame(g);};return g(),()=>{r.current!=null&&cancelAnimationFrame(r.current),window.removeEventListener("resize",y);}},[]),jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("canvas",{ref:a,className:"fixed inset-0 z-0 pointer-events-none","aria-hidden":"true"}),jsxRuntime.jsx("div",{className:"scanlines fixed inset-0 z-0 pointer-events-none","aria-hidden":"true"})]})}exports.ArcReactorHud=ae;exports.AuthPortal=xt;exports.AuthProvider=_e;exports.BackendAuthAdapter=X;exports.CanvasBackground=At;exports.FacialScanner=ce;exports.FingerprintPad=me;exports.MockAuthAdapter=Y;exports.PasskeyForm=ne;exports.SoundEngine=K;exports.VoiceScanner=de;exports.__resetMockAdapterStateForTests=Be;exports.createAuthAdapter=re;exports.useAuth=R;
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type AuthStatus = "idle" | "loading" | "authenticated" | "unauthenticated" | "error";
|
|
5
|
+
type BiometricMethod = "passkey" | "retina" | "voice" | "fingerprint";
|
|
6
|
+
type ClearanceLevel = "Level 1" | "Level 2" | "Level 3" | "Admin";
|
|
7
|
+
interface UserProfile {
|
|
8
|
+
uid: string;
|
|
9
|
+
email: string;
|
|
10
|
+
fullName: string;
|
|
11
|
+
clearanceLevel: ClearanceLevel;
|
|
12
|
+
hasBiometrics: boolean;
|
|
13
|
+
createdAt?: string;
|
|
14
|
+
lastLoginAt?: string;
|
|
15
|
+
}
|
|
16
|
+
interface AuthResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
user?: UserProfile;
|
|
19
|
+
error?: string;
|
|
20
|
+
errorCode?: string;
|
|
21
|
+
}
|
|
22
|
+
interface AuthAdapter {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
register(email: string, passkey: string, fullName: string): Promise<AuthResult>;
|
|
25
|
+
login(email: string, passkey: string): Promise<AuthResult>;
|
|
26
|
+
logout(): Promise<AuthResult>;
|
|
27
|
+
resetPassword(email: string): Promise<AuthResult>;
|
|
28
|
+
verifyFace(imageBase64: string): Promise<AuthResult>;
|
|
29
|
+
verifyVoice(audioBlob: Blob): Promise<AuthResult>;
|
|
30
|
+
verifyFingerprint(scanData: string): Promise<AuthResult>;
|
|
31
|
+
enrollBiometrics(userId: string): Promise<AuthResult>;
|
|
32
|
+
verifyPasskey(email?: string): Promise<AuthResult>;
|
|
33
|
+
getCurrentUser(): Promise<UserProfile | null>;
|
|
34
|
+
onAuthStateChanged(callback: (user: UserProfile | null) => void): () => void;
|
|
35
|
+
}
|
|
36
|
+
interface TelemetryData {
|
|
37
|
+
cpu: number;
|
|
38
|
+
memory: string;
|
|
39
|
+
authState: "CONNECTED" | "DISCONNECTED";
|
|
40
|
+
power: number;
|
|
41
|
+
}
|
|
42
|
+
interface TerminalMessage {
|
|
43
|
+
id: string;
|
|
44
|
+
text: string;
|
|
45
|
+
timestamp: Date;
|
|
46
|
+
type: "info" | "success" | "error" | "warning";
|
|
47
|
+
}
|
|
48
|
+
interface AccessModalState {
|
|
49
|
+
open: boolean;
|
|
50
|
+
isSuccess: boolean;
|
|
51
|
+
title: string;
|
|
52
|
+
message: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare function __resetMockAdapterStateForTests(): void;
|
|
56
|
+
declare class MockAuthAdapter implements AuthAdapter {
|
|
57
|
+
readonly name = "MockAuthAdapter";
|
|
58
|
+
private listeners;
|
|
59
|
+
private currentUser;
|
|
60
|
+
constructor();
|
|
61
|
+
private emit;
|
|
62
|
+
register(email: string, passkey: string, fullName: string): Promise<AuthResult>;
|
|
63
|
+
login(email: string, passkey: string): Promise<AuthResult>;
|
|
64
|
+
logout(): Promise<AuthResult>;
|
|
65
|
+
resetPassword(email: string): Promise<AuthResult>;
|
|
66
|
+
verifyFace(_imageBase64: string): Promise<AuthResult>;
|
|
67
|
+
verifyVoice(_audioBlob: Blob): Promise<AuthResult>;
|
|
68
|
+
verifyFingerprint(_scanData: string): Promise<AuthResult>;
|
|
69
|
+
enrollBiometrics(userId: string): Promise<AuthResult>;
|
|
70
|
+
verifyPasskey(_email?: string): Promise<AuthResult>;
|
|
71
|
+
getCurrentUser(): Promise<UserProfile | null>;
|
|
72
|
+
onAuthStateChanged(callback: (user: UserProfile | null) => void): () => void;
|
|
73
|
+
}
|
|
74
|
+
declare class BackendAuthAdapter implements AuthAdapter {
|
|
75
|
+
readonly name = "BackendAuthAdapter";
|
|
76
|
+
private listeners;
|
|
77
|
+
private currentUser;
|
|
78
|
+
private baseUrl;
|
|
79
|
+
constructor(baseUrl?: string);
|
|
80
|
+
private emit;
|
|
81
|
+
private request;
|
|
82
|
+
register(email: string, passkey: string, fullName: string): Promise<AuthResult>;
|
|
83
|
+
login(email: string, passkey: string): Promise<AuthResult>;
|
|
84
|
+
logout(): Promise<AuthResult>;
|
|
85
|
+
resetPassword(email: string): Promise<AuthResult>;
|
|
86
|
+
verifyFace(imageBase64: string): Promise<AuthResult>;
|
|
87
|
+
verifyVoice(audioBlob: Blob): Promise<AuthResult>;
|
|
88
|
+
verifyFingerprint(_scanData: string): Promise<AuthResult>;
|
|
89
|
+
enrollBiometrics(_userId: string): Promise<AuthResult>;
|
|
90
|
+
verifyPasskey(_email?: string): Promise<AuthResult>;
|
|
91
|
+
getCurrentUser(): Promise<UserProfile | null>;
|
|
92
|
+
onAuthStateChanged(callback: (user: UserProfile | null) => void): () => void;
|
|
93
|
+
}
|
|
94
|
+
type AuthAdapterName = "mock" | "backend" | "firebase";
|
|
95
|
+
declare function createAuthAdapter(kind?: AuthAdapterName, options?: {
|
|
96
|
+
baseUrl?: string;
|
|
97
|
+
}): AuthAdapter;
|
|
98
|
+
|
|
99
|
+
interface AuthContextValue {
|
|
100
|
+
user: UserProfile | null;
|
|
101
|
+
status: AuthStatus;
|
|
102
|
+
error: string | null;
|
|
103
|
+
adapter: AuthAdapter;
|
|
104
|
+
adapterName: AuthAdapterName;
|
|
105
|
+
audioEnabled: boolean;
|
|
106
|
+
activeMethod: BiometricMethod;
|
|
107
|
+
isRegisterMode: boolean;
|
|
108
|
+
terminalText: string;
|
|
109
|
+
modal: AccessModalState;
|
|
110
|
+
login: (email: string, passkey: string) => Promise<void>;
|
|
111
|
+
register: (email: string, passkey: string, fullName: string) => Promise<void>;
|
|
112
|
+
logout: () => Promise<void>;
|
|
113
|
+
resetPassword: (email: string) => Promise<void>;
|
|
114
|
+
verifyFace: (imageBase64: string) => Promise<void>;
|
|
115
|
+
verifyVoice: (audioBlob: Blob) => Promise<void>;
|
|
116
|
+
verifyFingerprint: (scanData: string) => Promise<void>;
|
|
117
|
+
enrollBiometrics: () => Promise<void>;
|
|
118
|
+
verifyPasskey: () => Promise<AuthResult>;
|
|
119
|
+
setActiveMethod: (method: BiometricMethod) => void;
|
|
120
|
+
toggleMode: () => void;
|
|
121
|
+
toggleAudio: () => void;
|
|
122
|
+
updateTerminal: (msg: string) => void;
|
|
123
|
+
showModal: (isSuccess: boolean, title: string, message: string) => void;
|
|
124
|
+
closeModal: () => void;
|
|
125
|
+
playBeep: (freq?: number, type?: OscillatorType, duration?: number) => void;
|
|
126
|
+
playSuccess: () => void;
|
|
127
|
+
playError: () => void;
|
|
128
|
+
}
|
|
129
|
+
declare function AuthProvider({ children, adapter: adapterArg, adapterOptions, }: {
|
|
130
|
+
children: ReactNode;
|
|
131
|
+
adapter?: AuthAdapterName | AuthAdapter;
|
|
132
|
+
adapterOptions?: {
|
|
133
|
+
baseUrl?: string;
|
|
134
|
+
};
|
|
135
|
+
}): react.JSX.Element;
|
|
136
|
+
declare function useAuth(): AuthContextValue;
|
|
137
|
+
|
|
138
|
+
declare class SoundEngine {
|
|
139
|
+
private ctx;
|
|
140
|
+
private ensureContext;
|
|
141
|
+
playBeep(freq?: number, type?: OscillatorType, duration?: number, gain?: number): void;
|
|
142
|
+
playSuccess(): void;
|
|
143
|
+
playError(): void;
|
|
144
|
+
unlock(): void;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
declare function AuthPortal(): react.JSX.Element;
|
|
148
|
+
|
|
149
|
+
declare function ArcReactorHud(): react.JSX.Element;
|
|
150
|
+
|
|
151
|
+
declare function CanvasBackground(): react.JSX.Element;
|
|
152
|
+
|
|
153
|
+
declare function PasskeyForm(): react.JSX.Element;
|
|
154
|
+
|
|
155
|
+
declare function FacialScanner(): react.JSX.Element;
|
|
156
|
+
|
|
157
|
+
declare function VoiceScanner(): react.JSX.Element;
|
|
158
|
+
|
|
159
|
+
declare function FingerprintPad(): react.JSX.Element;
|
|
160
|
+
|
|
161
|
+
export { type AccessModalState, ArcReactorHud, type AuthAdapter, type AuthAdapterName, type AuthContextValue, AuthPortal, AuthProvider, type AuthResult, type AuthStatus, BackendAuthAdapter, type BiometricMethod, CanvasBackground, type ClearanceLevel, FacialScanner, FingerprintPad, MockAuthAdapter, PasskeyForm, SoundEngine, type TelemetryData, type TerminalMessage, type UserProfile, VoiceScanner, __resetMockAdapterStateForTests, createAuthAdapter, useAuth };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type AuthStatus = "idle" | "loading" | "authenticated" | "unauthenticated" | "error";
|
|
5
|
+
type BiometricMethod = "passkey" | "retina" | "voice" | "fingerprint";
|
|
6
|
+
type ClearanceLevel = "Level 1" | "Level 2" | "Level 3" | "Admin";
|
|
7
|
+
interface UserProfile {
|
|
8
|
+
uid: string;
|
|
9
|
+
email: string;
|
|
10
|
+
fullName: string;
|
|
11
|
+
clearanceLevel: ClearanceLevel;
|
|
12
|
+
hasBiometrics: boolean;
|
|
13
|
+
createdAt?: string;
|
|
14
|
+
lastLoginAt?: string;
|
|
15
|
+
}
|
|
16
|
+
interface AuthResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
user?: UserProfile;
|
|
19
|
+
error?: string;
|
|
20
|
+
errorCode?: string;
|
|
21
|
+
}
|
|
22
|
+
interface AuthAdapter {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
register(email: string, passkey: string, fullName: string): Promise<AuthResult>;
|
|
25
|
+
login(email: string, passkey: string): Promise<AuthResult>;
|
|
26
|
+
logout(): Promise<AuthResult>;
|
|
27
|
+
resetPassword(email: string): Promise<AuthResult>;
|
|
28
|
+
verifyFace(imageBase64: string): Promise<AuthResult>;
|
|
29
|
+
verifyVoice(audioBlob: Blob): Promise<AuthResult>;
|
|
30
|
+
verifyFingerprint(scanData: string): Promise<AuthResult>;
|
|
31
|
+
enrollBiometrics(userId: string): Promise<AuthResult>;
|
|
32
|
+
verifyPasskey(email?: string): Promise<AuthResult>;
|
|
33
|
+
getCurrentUser(): Promise<UserProfile | null>;
|
|
34
|
+
onAuthStateChanged(callback: (user: UserProfile | null) => void): () => void;
|
|
35
|
+
}
|
|
36
|
+
interface TelemetryData {
|
|
37
|
+
cpu: number;
|
|
38
|
+
memory: string;
|
|
39
|
+
authState: "CONNECTED" | "DISCONNECTED";
|
|
40
|
+
power: number;
|
|
41
|
+
}
|
|
42
|
+
interface TerminalMessage {
|
|
43
|
+
id: string;
|
|
44
|
+
text: string;
|
|
45
|
+
timestamp: Date;
|
|
46
|
+
type: "info" | "success" | "error" | "warning";
|
|
47
|
+
}
|
|
48
|
+
interface AccessModalState {
|
|
49
|
+
open: boolean;
|
|
50
|
+
isSuccess: boolean;
|
|
51
|
+
title: string;
|
|
52
|
+
message: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare function __resetMockAdapterStateForTests(): void;
|
|
56
|
+
declare class MockAuthAdapter implements AuthAdapter {
|
|
57
|
+
readonly name = "MockAuthAdapter";
|
|
58
|
+
private listeners;
|
|
59
|
+
private currentUser;
|
|
60
|
+
constructor();
|
|
61
|
+
private emit;
|
|
62
|
+
register(email: string, passkey: string, fullName: string): Promise<AuthResult>;
|
|
63
|
+
login(email: string, passkey: string): Promise<AuthResult>;
|
|
64
|
+
logout(): Promise<AuthResult>;
|
|
65
|
+
resetPassword(email: string): Promise<AuthResult>;
|
|
66
|
+
verifyFace(_imageBase64: string): Promise<AuthResult>;
|
|
67
|
+
verifyVoice(_audioBlob: Blob): Promise<AuthResult>;
|
|
68
|
+
verifyFingerprint(_scanData: string): Promise<AuthResult>;
|
|
69
|
+
enrollBiometrics(userId: string): Promise<AuthResult>;
|
|
70
|
+
verifyPasskey(_email?: string): Promise<AuthResult>;
|
|
71
|
+
getCurrentUser(): Promise<UserProfile | null>;
|
|
72
|
+
onAuthStateChanged(callback: (user: UserProfile | null) => void): () => void;
|
|
73
|
+
}
|
|
74
|
+
declare class BackendAuthAdapter implements AuthAdapter {
|
|
75
|
+
readonly name = "BackendAuthAdapter";
|
|
76
|
+
private listeners;
|
|
77
|
+
private currentUser;
|
|
78
|
+
private baseUrl;
|
|
79
|
+
constructor(baseUrl?: string);
|
|
80
|
+
private emit;
|
|
81
|
+
private request;
|
|
82
|
+
register(email: string, passkey: string, fullName: string): Promise<AuthResult>;
|
|
83
|
+
login(email: string, passkey: string): Promise<AuthResult>;
|
|
84
|
+
logout(): Promise<AuthResult>;
|
|
85
|
+
resetPassword(email: string): Promise<AuthResult>;
|
|
86
|
+
verifyFace(imageBase64: string): Promise<AuthResult>;
|
|
87
|
+
verifyVoice(audioBlob: Blob): Promise<AuthResult>;
|
|
88
|
+
verifyFingerprint(_scanData: string): Promise<AuthResult>;
|
|
89
|
+
enrollBiometrics(_userId: string): Promise<AuthResult>;
|
|
90
|
+
verifyPasskey(_email?: string): Promise<AuthResult>;
|
|
91
|
+
getCurrentUser(): Promise<UserProfile | null>;
|
|
92
|
+
onAuthStateChanged(callback: (user: UserProfile | null) => void): () => void;
|
|
93
|
+
}
|
|
94
|
+
type AuthAdapterName = "mock" | "backend" | "firebase";
|
|
95
|
+
declare function createAuthAdapter(kind?: AuthAdapterName, options?: {
|
|
96
|
+
baseUrl?: string;
|
|
97
|
+
}): AuthAdapter;
|
|
98
|
+
|
|
99
|
+
interface AuthContextValue {
|
|
100
|
+
user: UserProfile | null;
|
|
101
|
+
status: AuthStatus;
|
|
102
|
+
error: string | null;
|
|
103
|
+
adapter: AuthAdapter;
|
|
104
|
+
adapterName: AuthAdapterName;
|
|
105
|
+
audioEnabled: boolean;
|
|
106
|
+
activeMethod: BiometricMethod;
|
|
107
|
+
isRegisterMode: boolean;
|
|
108
|
+
terminalText: string;
|
|
109
|
+
modal: AccessModalState;
|
|
110
|
+
login: (email: string, passkey: string) => Promise<void>;
|
|
111
|
+
register: (email: string, passkey: string, fullName: string) => Promise<void>;
|
|
112
|
+
logout: () => Promise<void>;
|
|
113
|
+
resetPassword: (email: string) => Promise<void>;
|
|
114
|
+
verifyFace: (imageBase64: string) => Promise<void>;
|
|
115
|
+
verifyVoice: (audioBlob: Blob) => Promise<void>;
|
|
116
|
+
verifyFingerprint: (scanData: string) => Promise<void>;
|
|
117
|
+
enrollBiometrics: () => Promise<void>;
|
|
118
|
+
verifyPasskey: () => Promise<AuthResult>;
|
|
119
|
+
setActiveMethod: (method: BiometricMethod) => void;
|
|
120
|
+
toggleMode: () => void;
|
|
121
|
+
toggleAudio: () => void;
|
|
122
|
+
updateTerminal: (msg: string) => void;
|
|
123
|
+
showModal: (isSuccess: boolean, title: string, message: string) => void;
|
|
124
|
+
closeModal: () => void;
|
|
125
|
+
playBeep: (freq?: number, type?: OscillatorType, duration?: number) => void;
|
|
126
|
+
playSuccess: () => void;
|
|
127
|
+
playError: () => void;
|
|
128
|
+
}
|
|
129
|
+
declare function AuthProvider({ children, adapter: adapterArg, adapterOptions, }: {
|
|
130
|
+
children: ReactNode;
|
|
131
|
+
adapter?: AuthAdapterName | AuthAdapter;
|
|
132
|
+
adapterOptions?: {
|
|
133
|
+
baseUrl?: string;
|
|
134
|
+
};
|
|
135
|
+
}): react.JSX.Element;
|
|
136
|
+
declare function useAuth(): AuthContextValue;
|
|
137
|
+
|
|
138
|
+
declare class SoundEngine {
|
|
139
|
+
private ctx;
|
|
140
|
+
private ensureContext;
|
|
141
|
+
playBeep(freq?: number, type?: OscillatorType, duration?: number, gain?: number): void;
|
|
142
|
+
playSuccess(): void;
|
|
143
|
+
playError(): void;
|
|
144
|
+
unlock(): void;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
declare function AuthPortal(): react.JSX.Element;
|
|
148
|
+
|
|
149
|
+
declare function ArcReactorHud(): react.JSX.Element;
|
|
150
|
+
|
|
151
|
+
declare function CanvasBackground(): react.JSX.Element;
|
|
152
|
+
|
|
153
|
+
declare function PasskeyForm(): react.JSX.Element;
|
|
154
|
+
|
|
155
|
+
declare function FacialScanner(): react.JSX.Element;
|
|
156
|
+
|
|
157
|
+
declare function VoiceScanner(): react.JSX.Element;
|
|
158
|
+
|
|
159
|
+
declare function FingerprintPad(): react.JSX.Element;
|
|
160
|
+
|
|
161
|
+
export { type AccessModalState, ArcReactorHud, type AuthAdapter, type AuthAdapterName, type AuthContextValue, AuthPortal, AuthProvider, type AuthResult, type AuthStatus, BackendAuthAdapter, type BiometricMethod, CanvasBackground, type ClearanceLevel, FacialScanner, FingerprintPad, MockAuthAdapter, PasskeyForm, SoundEngine, type TelemetryData, type TerminalMessage, type UserProfile, VoiceScanner, __resetMockAdapterStateForTests, createAuthAdapter, useAuth };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {startAuthentication}from'@simplewebauthn/browser';import {createContext,useMemo,useState,useCallback,useEffect,useContext,useRef}from'react';import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {Zap,UserCheck,Mail,Lock,EyeOff,Eye,Loader2,User,Scan,Mic,Fingerprint,Volume2,VolumeX,Maximize,ShieldCheck,LogOut,RefreshCw,AlertTriangle,Target}from'lucide-react';var J="jarvis_auth_user",V={};function Be(){Object.keys(V).forEach(a=>delete V[a]);try{typeof window<"u"&&window.localStorage.removeItem(J);}catch{}}function H(a="OP"){return `${a}-${Math.random().toString(36).slice(2,8).toUpperCase()}-${Date.now().toString(36).slice(-4).toUpperCase()}`}function C(a){typeof window>"u"||(a?window.localStorage.setItem(J,JSON.stringify(a)):window.localStorage.removeItem(J));}function be(){if(typeof window>"u")return null;try{let a=window.localStorage.getItem(J);return a?JSON.parse(a):null}catch{return null}}var Y=class{constructor(){this.name="MockAuthAdapter";this.listeners=new Set;this.currentUser=null;this.currentUser=be();}emit(r){this.listeners.forEach(t=>{try{t(r);}catch{}});}async register(r,t,e){if(!r||!t||!e)return {success:false,error:"All fields are required to register an operative.",errorCode:"missing-fields"};if(t.length<6)return {success:false,error:"Passkey must be at least 6 characters long.",errorCode:"weak-passkey"};if(V[r.toLowerCase()])return {success:false,error:"This email is already registered. Please switch to Sign In.",errorCode:"email-already-in-use"};let n={uid:H("REG"),email:r.toLowerCase(),fullName:e||"Operative",clearanceLevel:"Level 1",hasBiometrics:false,createdAt:new Date().toISOString()};return V[r.toLowerCase()]={passkey:t,user:n},this.currentUser={...n,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser),{success:true,user:this.currentUser}}async login(r,t){let e=r.toLowerCase(),n=V[e];if(n)return n.passkey!==t?{success:false,error:"Invalid email or passkey combination.",errorCode:"invalid-credential"}:(this.currentUser={...n.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser),{success:true,user:this.currentUser});if(r&&t.length>=6){let i={uid:H("MOCK"),email:e,fullName:r.split("@")[0]||"Operative",clearanceLevel:"Level 1",hasBiometrics:false,createdAt:new Date().toISOString(),lastLoginAt:new Date().toISOString()};return this.currentUser=i,C(i),this.emit(i),{success:true,user:i}}return {success:false,error:"Invalid email or passkey combination.",errorCode:"invalid-credential"}}async logout(){return this.currentUser=null,C(null),this.emit(null),{success:true}}async resetPassword(r){return r?{success:true,user:void 0}:{success:false,error:"Please enter your email address first.",errorCode:"missing-email"}}async verifyFace(r){let t=this.currentUser||{uid:H("FACE"),email:"stark@avengers.io",fullName:"Tony Stark (Facial Match)",clearanceLevel:"Level 1",hasBiometrics:true,lastLoginAt:new Date().toISOString()};return this.currentUser=t,C(t),this.emit(t),{success:true,user:t}}async verifyVoice(r){let t=this.currentUser||{uid:H("VOICE"),email:"stark@avengers.io",fullName:"Tony Stark (Voice Match)",clearanceLevel:"Level 1",hasBiometrics:true,lastLoginAt:new Date().toISOString()};return this.currentUser=t,C(t),this.emit(t),{success:true,user:t}}async verifyFingerprint(r){let t=this.currentUser||{uid:H("FP"),email:"stark@avengers.io",fullName:"Tony Stark (Fingerprint Match)",clearanceLevel:"Level 1",hasBiometrics:true,lastLoginAt:new Date().toISOString()};return this.currentUser=t,C(t),this.emit(t),{success:true,user:t}}async enrollBiometrics(r){if(!this.currentUser)return {success:false,error:"Active session required for biometric enrollment.",errorCode:"no-session"};this.currentUser={...this.currentUser,hasBiometrics:true};let t=this.currentUser.email.toLowerCase();return V[t]&&(V[t]={...V[t],user:{...V[t].user,hasBiometrics:true}}),C(this.currentUser),this.emit(this.currentUser),{success:true,user:this.currentUser}}async verifyPasskey(r){if(typeof window>"u"||!window.PublicKeyCredential)return {success:false,error:"WebAuthn / Passkey authentication is not supported in this environment.",errorCode:"passkey-not-supported"};try{let t=r??this.currentUser?.email??"",e=await fetch(`${typeof process<"u"?process.env.NEXT_PUBLIC_AUTH_API_URL??"http://localhost:8000":"http://localhost:8000"}/api/v1/auth/webauthn/options?email=${encodeURIComponent(t)}`).then(c=>c.json()),n=await startAuthentication(e),i=await fetch(`${typeof process<"u"?process.env.NEXT_PUBLIC_AUTH_API_URL??"http://localhost:8000":"http://localhost:8000"}/api/v1/auth/webauthn/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)}).then(c=>{if(!c.ok)throw new Error(c.statusText);return c.json()});return i.success&&i.user?(this.currentUser={...i.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser),{success:!0,user:this.currentUser}):{success:!1,error:i.error||"Passkey verification failed on server.",errorCode:"passkey-verification-failed"}}catch(t){return {success:false,error:t.message||"Passkey authentication failed.",errorCode:"passkey-auth-error"}}}async getCurrentUser(){return this.currentUser}onAuthStateChanged(r){return this.listeners.add(r),queueMicrotask(()=>r(this.currentUser)),()=>this.listeners.delete(r)}},X=class{constructor(r){this.name="BackendAuthAdapter";this.listeners=new Set;this.currentUser=null;this.baseUrl=r||(typeof process<"u"?process.env.NEXT_PUBLIC_AUTH_API_URL??"http://localhost:8000":"http://localhost:8000"),this.currentUser=be();}emit(r){this.listeners.forEach(t=>{try{t(r);}catch{}});}async request(r,t={}){let e=await fetch(`${this.baseUrl}${r}`,{headers:{"Content-Type":"application/json",...t.headers||{}},...t});if(!e.ok){let n=await e.json().catch(()=>({detail:e.statusText}));throw new Error(n.detail||e.statusText)}return await e.json()}async register(r,t,e){try{let n=await this.request("/api/v1/auth/register",{method:"POST",body:JSON.stringify({email:r,passkey:t,full_name:e})});return n.success&&n.user&&(this.currentUser={...n.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser)),{success:n.success,user:n.user}}catch(n){return {success:false,error:n.message||"Registration failed"}}}async login(r,t){try{let e=await this.request("/api/v1/auth/login",{method:"POST",body:JSON.stringify({email:r,passkey:t})});return e.success&&e.user&&(this.currentUser={...e.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser)),{success:e.success,user:e.user}}catch(e){return {success:false,error:e.message||"Login failed"}}}async logout(){return this.currentUser=null,C(null),this.emit(null),{success:true}}async resetPassword(r){return r?{success:true}:{success:false,error:"Please enter your email address first.",errorCode:"missing-email"}}async verifyFace(r){try{let t=await this.request("/api/v1/auth/verify-face",{method:"POST",body:JSON.stringify({image_base64:r})});return t.success&&t.user&&(this.currentUser={...t.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser)),{success:t.success,user:t.user}}catch(t){return {success:false,error:t.message||"Facial verification failed"}}}async verifyVoice(r){try{let t=new FormData;t.append("file",r,"voice.wav");let e=await fetch(`${this.baseUrl}/api/v1/auth/verify-voice`,{method:"POST",body:t});if(!e.ok)throw new Error(e.statusText);let n=await e.json();return n.success&&n.user&&(this.currentUser={...n.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser)),{success:n.success,user:n.user}}catch(t){return {success:false,error:t.message||"Voice verification failed"}}}async verifyFingerprint(r){let t={uid:H("FP-BE"),email:"stark@avengers.io",fullName:"Tony Stark (Backend FP)",clearanceLevel:"Level 1",hasBiometrics:true,lastLoginAt:new Date().toISOString()};return this.currentUser=t,C(t),this.emit(t),{success:true,user:t}}async enrollBiometrics(r){return this.currentUser?(this.currentUser={...this.currentUser,hasBiometrics:true},C(this.currentUser),this.emit(this.currentUser),{success:true,user:this.currentUser}):{success:false,error:"Active session required.",errorCode:"no-session"}}async verifyPasskey(r){if(typeof window>"u"||!window.PublicKeyCredential)return {success:false,error:"WebAuthn / Passkey authentication is not supported in this environment.",errorCode:"passkey-not-supported"};try{let t=r??this.currentUser?.email??"",e=await this.request(`/api/v1/auth/webauthn/options?email=${encodeURIComponent(t)}`),n=await startAuthentication(e),i=await this.request("/api/v1/auth/webauthn/verify",{method:"POST",body:JSON.stringify(n)});return i.success&&i.user?(this.currentUser={...i.user,lastLoginAt:new Date().toISOString()},C(this.currentUser),this.emit(this.currentUser),{success:!0,user:this.currentUser}):{success:!1,error:"Passkey verification failed on server.",errorCode:"passkey-verification-failed"}}catch(t){return {success:false,error:t.message||"Passkey authentication failed.",errorCode:"passkey-auth-error"}}}async getCurrentUser(){return this.currentUser}onAuthStateChanged(r){return this.listeners.add(r),queueMicrotask(()=>r(this.currentUser)),()=>this.listeners.delete(r)}};function re(a="mock",r){switch(a){case "backend":return new X(r?.baseUrl);case "firebase":return new Y;default:return new Y}}var K=class{constructor(){this.ctx=null;}ensureContext(){if(typeof window>"u")return null;if(!this.ctx){let r=window.AudioContext||window.webkitAudioContext;r&&(this.ctx=new r);}return this.ctx&&this.ctx.state==="suspended"&&this.ctx.resume().catch(()=>{}),this.ctx}playBeep(r=800,t="sine",e=.1,n=.15){let i=this.ensureContext();if(i)try{let c=i.createOscillator(),y=i.createGain();c.type=t,c.frequency.setValueAtTime(r,i.currentTime),y.gain.setValueAtTime(n,i.currentTime),y.gain.exponentialRampToValueAtTime(.001,i.currentTime+e),c.connect(y),y.connect(i.destination),c.start(),c.stop(i.currentTime+e);}catch{}}playSuccess(){this.playBeep(523.25,"triangle",.15),setTimeout(()=>this.playBeep(659.25,"triangle",.15),120),setTimeout(()=>this.playBeep(783.99,"triangle",.25),240);}playError(){this.playBeep(220,"sawtooth",.2,.2),setTimeout(()=>this.playBeep(160,"sawtooth",.3,.2),150);}unlock(){this.ensureContext();}};var fe=createContext(null);function _e({children:a,adapter:r="mock",adapterOptions:t}){let e=useMemo(()=>typeof r=="string"?re(r,t):r,[r]),n=useMemo(()=>{let l=e.name.toLowerCase();return l.includes("backend")?"backend":l.includes("firebase")?"firebase":"mock"},[e]),[i,c]=useState(null),[y,u]=useState("idle"),[g,d]=useState(null),[o,p]=useState(true),[P,k]=useState("passkey"),[I,w]=useState(false),[x,E]=useState("Greetings. System active. Enter valid credentials or register new clearance."),[F,O]=useState({open:false,isSuccess:true,title:"",message:""}),L=useMemo(()=>new K,[]),D=useCallback((l=800,f="sine",U=.1)=>{o&&L.playBeep(l,f,U);},[o,L]),q=useCallback(()=>{o&&L.playSuccess();},[o,L]),N=useCallback(()=>{o&&L.playError();},[o,L]),h=useCallback(l=>{E(l);},[]),m=useCallback((l,f,U)=>{O({open:true,isSuccess:l,title:f,message:U}),l?q():N();},[q,N]),z=useCallback(()=>{D(800,"sine",.05),O(l=>({...l,open:false}));},[D]),Ee=useCallback(l=>{k(l),D(700,"sine",.1),h({passkey:"Passkey entry active.",retina:"Facial scan camera matrix standby.",voice:"Voice spectral analyzer standby.",fingerprint:"Fingerprint capacitive scanner standby."}[l]);},[D,h]),Se=useCallback(()=>{w(l=>!l),d(null),D(900,"sine",.1),h(I?"Sign in mode active.":"Registration mode active. Submit email and passkey.");},[I,D,h]),Ie=useCallback(()=>{p(l=>{let f=!l;return f?(L.playBeep(1e3,"sine",.1),h("Audio sound system online.")):h("Audio sound system muted."),f});},[L,h]);useEffect(()=>{u("loading");let l=false,f=e.onAuthStateChanged(T=>{l||(c(T),u(T?"authenticated":"unauthenticated"));});(async()=>{try{let T=await e.getCurrentUser();if(l)return;c(T),u(T?"authenticated":"unauthenticated");}catch{if(l)return;u("unauthenticated");}})();let U=window.setTimeout(()=>{l||u(T=>T==="loading"?"unauthenticated":T);},5e3);return ()=>{l=true,window.clearTimeout(U),f();}},[e]);let Ce=useCallback(async(l,f)=>{u("loading"),d(null),h("Validating credentials with authentication core...");let U=await e.login(l,f);if(!U.success){d(U.error||"Authentication failed"),c(null),u("error"),N(),h(`[ERROR]: ${U.error}`),m(false,"AUTHENTICATION FAILED",U.error||"");return}c(U.user??null),u("authenticated"),m(true,"ACCESS GRANTED",`Security clearance confirmed for ${l}. Welcome back.`);},[e,h,N,m]),Re=useCallback(async(l,f,U)=>{u("loading"),d(null),h("Creating new operative clearance record...");let T=await e.register(l,f,U);if(!T.success){d(T.error||"Registration failed"),c(null),u("error"),N(),h(`[ERROR]: ${T.error}`),m(false,"REGISTRATION FAILED",T.error||"");return}c(T.user??null),u("authenticated"),m(true,"REGISTRATION COMPLETE",`Operative [${l}] successfully enrolled into Stark Security Database.`);},[e,h,N,m]),ke=useCallback(async()=>{u("loading"),await e.logout(),u("unauthenticated"),c(null),D(400,"sine",.2),h("Session terminated.");},[e,D,h]),Te=useCallback(async l=>{let f=await e.resetPassword(l);if(!f.success){N(),m(false,"RECOVERY ERROR",f.error||"");return}m(true,"RECOVERY DISPATCHED",`Passkey reset instructions sent to ${l}.`);},[e,N,m]),Pe=useCallback(async l=>{u("loading"),h("Analyzing facial geometry mesh...");let f=await e.verifyFace(l);if(!f.success){c(null),u("error"),N(),m(false,"FACIAL SCAN FAILED",f.error||"");return}c(f.user??null),u("authenticated"),m(true,"FACIAL SCAN VERIFIED","Iris vector scan matched in database.");},[e,h,N,m]),Ue=useCallback(async l=>{u("loading"),h("Listening for voice waveform match...");let f=await e.verifyVoice(l);if(!f.success){c(null),u("error"),N(),m(false,"VOICE VERIFICATION FAILED",f.error||"");return}c(f.user??null),u("authenticated"),m(true,"VOICE PRINT MATCHED","Voice acoustic spectrum matches Operative profile.");},[e,h,N,m]),Me=useCallback(async l=>{u("loading"),h("Fingerprint capacitive scan in progress...");let f=await e.verifyFingerprint(l);if(!f.success){c(null),u("error"),N(),m(false,"FINGERPRINT FAILED",f.error||"");return}c(f.user??null),u("authenticated"),m(true,"FINGERPRINT AUTHORIZED","Dermal ridge pattern verified.");},[e,h,N,m]),Oe=useCallback(async()=>{if(!i){N(),m(false,"ENROLLMENT FAILED","No active session. Sign in first.");return}let l=await e.enrollBiometrics(i.uid);if(!l.success){N(),m(false,"ENROLLMENT FAILED",l.error||"");return}c(f=>f&&{...f,hasBiometrics:true}),m(true,"BIOMETRIC LINKED","Device biometrics securely registered to profile.");},[e,i,N,m]),Le=useCallback(async()=>{u("loading"),h("Initializing WebAuthn passkey assertion...");let l=await e.verifyPasskey(i?.email);return l.success?(c(l.user??null),u("authenticated"),m(true,"PASSKEY VERIFIED","Device passkey assertion matched in database."),l):(d(l.error||"Passkey authentication failed"),u("error"),N(),m(false,"PASSKEY FAILED",l.error||""),l)},[e,i,h,N,m]),De={user:i,status:y,error:g,adapter:e,adapterName:n,audioEnabled:o,activeMethod:P,isRegisterMode:I,terminalText:x,modal:F,login:Ce,register:Re,logout:ke,resetPassword:Te,verifyFace:Pe,verifyVoice:Ue,verifyFingerprint:Me,enrollBiometrics:Oe,verifyPasskey:Le,setActiveMethod:Ee,toggleMode:Se,toggleAudio:Ie,updateTerminal:h,showModal:m,closeModal:z,playBeep:D,playSuccess:q,playError:N};return jsx(fe.Provider,{value:De,children:a})}function R(){let a=useContext(fe);if(!a)throw new Error("useAuth must be used within an <AuthProvider>");return a}function ae(){let{user:a,terminalText:r,playBeep:t,updateTerminal:e}=R(),[n,i]=useState(false),[c,y]=useState(36),[u,g]=useState("5.8 GB"),[d,o]=useState("00:00:00"),p=useRef(null);useEffect(()=>{let w=setInterval(()=>{y(Math.floor(Math.random()*25)+20);let x=(5.2+Math.random()*.8).toFixed(1);g(`${x} GB`);let E=new Date,F=String(E.getHours()).padStart(2,"0"),O=String(E.getMinutes()).padStart(2,"0"),L=String(E.getSeconds()).padStart(2,"0");o(`${F}:${O}:${L}`);},2e3);return ()=>clearInterval(w)},[]);let P=()=>{i(true),t(300,"triangle",.4),e("Arc Reactor energy pulse triggered."),setTimeout(()=>i(false),500);},k=a?"CONNECTED":"DISCONNECTED",I=a?"text-cyber-emerald":"text-cyber-red";return jsxs("section",{className:"flex flex-col items-center justify-center relative min-h-[300px] w-full",children:[jsxs("div",{className:"relative w-64 h-64 md:w-80 md:h-80 flex items-center justify-center cursor-pointer group select-none",onClick:P,role:"button","aria-label":"Arc Reactor pulse trigger",children:[jsx("div",{className:"absolute inset-0 border border-cyber-cyan/20 rounded-full"}),jsx("div",{className:"absolute inset-[-10px] border border-dashed border-cyber-cyan/10 rounded-full"}),jsx("div",{className:"absolute inset-2 border-2 border-dashed border-cyber-cyan/40 rounded-full animate-spin-reverse"}),jsxs("div",{className:"absolute inset-8 border border-cyber-cyan/60 rounded-full animate-spin-slow flex items-center justify-center",children:[jsx("div",{className:"w-full h-0.5 bg-cyber-cyan/30 absolute"}),jsx("div",{className:"h-full w-0.5 bg-cyber-cyan/30 absolute"})]}),jsxs("div",{className:"absolute inset-12 border border-cyber-cyan/30 rounded-full flex items-center justify-center",children:[jsx("div",{className:"w-2 h-2 bg-cyber-cyan rounded-full absolute -top-1"}),jsx("div",{className:"w-2 h-2 bg-cyber-cyan rounded-full absolute -bottom-1"}),jsx("div",{className:"w-2 h-2 bg-cyber-cyan rounded-full absolute -left-1"}),jsx("div",{className:"w-2 h-2 bg-cyber-cyan rounded-full absolute -right-1"})]}),jsx("div",{ref:p,id:"arc-core",className:["relative w-28 h-28 md:w-36 md:h-36 rounded-full bg-cyber-cyan/10 border-2 border-cyber-cyan flex flex-col items-center justify-center shadow-cyber-glow transition-all duration-500 group-hover:scale-105",n?"scale-125 shadow-cyber-glow-strong":""].join(" "),children:jsx("div",{className:"w-16 h-16 md:w-20 md:h-20 rounded-full bg-cyber-cyan/20 border border-cyber-cyan/80 flex items-center justify-center animate-pulse-glow",children:jsx(Zap,{className:"w-8 h-8 text-cyber-cyan drop-shadow-[0_0_10px_#00f3ff]"})})}),jsx("div",{className:"absolute inset-0 rounded-full animate-spin-slow opacity-30 bg-[conic-gradient(from_0deg,transparent_0_300deg,rgba(0,243,255,0.4)_360deg)] pointer-events-none"}),jsxs("div",{className:"absolute -top-4 left-0 text-[10px] text-cyber-cyan/70 tracking-widest bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30",children:["SYS.CPU: ",jsxs("span",{className:"font-bold",children:[c,"%"]})]}),jsxs("div",{className:"absolute -top-4 right-0 text-[10px] text-cyber-cyan/70 tracking-widest bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30",children:["MEM: ",jsx("span",{className:"font-bold",children:u})]}),jsxs("div",{className:"absolute -bottom-4 left-0 text-[10px] text-cyber-cyan/70 tracking-widest bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30",children:["STATUS: ",jsx("span",{className:`${I} font-bold`,children:k})]}),jsxs("div",{className:"absolute -bottom-4 right-0 text-[10px] text-cyber-cyan/70 tracking-widest bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30",children:["PWR: ",jsx("span",{className:"text-cyber-cyan font-bold",children:"100%"})]})]}),jsxs("div",{className:"mt-6 text-center",children:[jsx("div",{className:"font-orbitron tracking-widest text-sm text-cyber-cyan text-glow",children:"ARC REACTOR MK VII"}),jsx("div",{className:"text-[11px] text-cyber-cyan/50 tracking-wider",children:"STARK SECURITY MATRIX // ONLINE"})]}),jsxs("div",{className:"mt-6 w-full max-w-md bg-cyber-bg/80 border border-cyber-cyan/30 p-2.5 rounded text-xs font-mono h-20 overflow-hidden relative",children:[jsxs("div",{className:"text-[10px] text-cyber-cyan/40 mb-1 border-b border-cyber-cyan/20 pb-0.5 flex justify-between",children:[jsx("span",{children:"JARVIS_SPEECH_LOG"}),jsx("span",{children:d})]}),jsxs("p",{className:"text-cyber-cyan/90 leading-tight",children:["\u201C",r,"\u201D"]})]})]})}function ne(){let{isRegisterMode:a,login:r,register:t,resetPassword:e,verifyPasskey:n,error:i,status:c,playBeep:y}=R(),[u,g]=useState(""),[d,o]=useState(""),[p,P]=useState(""),[k,I]=useState(false),[w,x]=useState(true),[E,F]=useState(null),O=c==="loading",L=async m=>{m.preventDefault(),F(null);let z=d.trim();if(!z||!p){F("Email and passkey are required.");return}if(a){if(!u.trim()){F("Full name is required for registration.");return}if(p.length<6){F("Passkey must be at least 6 characters long.");return}await t(z,p,u.trim());}else await r(z,p);},D=async m=>{m.preventDefault(),await e(d.trim());},q=()=>{I(m=>!m),y(1100,"sine",.05);},N=a?"ENROLL OPERATIVE":"AUTHENTICATE",h=E||i;return jsxs("form",{onSubmit:L,className:"space-y-4",noValidate:true,children:[a&&jsxs("div",{children:[jsx("label",{htmlFor:"jarvis-fullname",className:"block text-xs text-cyber-cyan/70 tracking-widest mb-1 uppercase",children:"Full Name / Operative ID"}),jsxs("div",{className:"relative",children:[jsx(UserCheck,{className:"w-4 h-4 absolute left-3 top-3 text-cyber-cyan/50"}),jsx("input",{id:"jarvis-fullname",type:"text",autoComplete:"name","aria-label":"Full Name",value:u,onChange:m=>g(m.target.value),placeholder:"Tony Stark",className:"w-full bg-cyber-bg/90 border border-cyber-cyan/40 rounded px-10 py-2.5 text-sm text-cyber-cyan focus:outline-none focus:border-cyber-cyan focus:ring-1 focus:ring-cyber-cyan transition-all placeholder:text-cyber-cyan/30",disabled:O})]})]}),jsxs("div",{children:[jsx("label",{htmlFor:"jarvis-email",className:"block text-xs text-cyber-cyan/70 tracking-widest mb-1 uppercase",children:"Email Address"}),jsxs("div",{className:"relative",children:[jsx(Mail,{className:"w-4 h-4 absolute left-3 top-3 text-cyber-cyan/50"}),jsx("input",{id:"jarvis-email",type:"email",autoComplete:"email","aria-label":"Email Address",value:d,onChange:m=>o(m.target.value),placeholder:"stark@avengers.io",required:true,className:"w-full bg-cyber-bg/90 border border-cyber-cyan/40 rounded px-10 py-2.5 text-sm text-cyber-cyan focus:outline-none focus:border-cyber-cyan focus:ring-1 focus:ring-cyber-cyan transition-all placeholder:text-cyber-cyan/30",disabled:O})]})]}),jsxs("div",{children:[jsx("label",{htmlFor:"jarvis-passkey",className:"block text-xs text-cyber-cyan/70 tracking-widest mb-1 uppercase",children:"Passkey"}),jsxs("div",{className:"relative",children:[jsx(Lock,{className:"w-4 h-4 absolute left-3 top-3 text-cyber-cyan/50"}),jsx("input",{id:"jarvis-passkey",type:k?"text":"password",autoComplete:a?"new-password":"current-password","aria-label":"Passkey",value:p,minLength:6,required:true,onChange:m=>P(m.target.value),placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",className:"w-full bg-cyber-bg/90 border border-cyber-cyan/40 rounded px-10 py-2.5 text-sm text-cyber-cyan focus:outline-none focus:border-cyber-cyan focus:ring-1 focus:ring-cyber-cyan transition-all placeholder:text-cyber-cyan/30 pr-12",disabled:O}),jsx("button",{type:"button",onClick:q,tabIndex:-1,className:"absolute right-3 top-3 text-cyber-cyan/50 hover:text-cyber-cyan transition-colors","aria-label":k?"Hide passkey":"Show passkey",children:k?jsx(EyeOff,{className:"w-4 h-4"}):jsx(Eye,{className:"w-4 h-4"})})]})]}),h&&jsx("div",{role:"alert",className:"text-xs text-cyber-red bg-cyber-red/10 border border-cyber-red/40 p-2.5 rounded font-mono text-center",children:h}),jsxs("div",{className:"relative my-4",children:[jsx("div",{className:"absolute inset-0 flex items-center",children:jsx("div",{className:"w-full border-t border-cyber-cyan/30"})}),jsx("div",{className:"relative flex justify-center",children:jsx("span",{className:"px-3 text-xs text-cyber-cyan/50 bg-cyber-bg",children:"OR"})})]}),jsx("button",{type:"button",onClick:()=>{y(900,"sine",.1),n();},disabled:O,className:"w-full bg-cyber-cyan/10 border-2 border-cyber-emerald hover:bg-cyber-cyan/30 text-cyber-emerald font-orbitron py-3 rounded tracking-widest font-bold transition-all shadow-cyber-glow flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed","aria-label":"Use device passkey",children:jsx("span",{children:"USE DEVICE PASSKEY"})}),jsxs("div",{className:"flex justify-between items-center text-xs pt-1",children:[jsxs("label",{className:"flex items-center space-x-2 cursor-pointer text-cyber-cyan/70 hover:text-cyber-cyan",children:[jsx("input",{type:"checkbox",checked:w,onChange:m=>x(m.target.checked),className:"accent-cyber-cyan bg-cyber-bg border-cyber-cyan/40 rounded",disabled:O}),jsx("span",{children:"REMEMBER ID"})]}),jsx("a",{href:"#",onClick:D,className:"text-cyber-cyan/70 hover:text-cyber-cyan underline transition-all",children:"RECOVER ACCESS"})]}),jsx("button",{type:"submit",disabled:O,className:"w-full mt-4 bg-cyber-cyan/10 border-2 border-cyber-cyan hover:bg-cyber-cyan/30 text-cyber-cyan font-orbitron py-3 rounded tracking-widest font-bold transition-all shadow-cyber-glow hover:scale-[1.01] active:scale-[0.99] flex items-center justify-center space-x-2 disabled:opacity-60 disabled:cursor-not-allowed disabled:hover:scale-100",children:O?jsxs(Fragment,{children:[jsx(Loader2,{className:"w-4 h-4 animate-spin"}),jsx("span",{children:"AUTHENTICATING..."})]}):jsx("span",{children:N})})]})}function ce(){let{verifyFace:a,playBeep:r,status:t}=R(),[e,n]=useState(false),[i,c]=useState("POSITION FACE IN FRAME"),[y,u]=useState(null),g=useRef(null),d=useRef(null),o=useRef(null);useEffect(()=>()=>{y&&window.clearInterval(y),g.current&&window.clearTimeout(g.current),o.current&&(o.current.getTracks().forEach(w=>w.stop()),o.current=null);},[]);let p=async()=>{if(!(typeof navigator>"u"||!navigator.mediaDevices))try{let w=await navigator.mediaDevices.getUserMedia({video:{facingMode:"user",width:320,height:240},audio:!1});o.current=w,d.current&&(d.current.srcObject=w,await d.current.play().catch(()=>{}));}catch{}},P=()=>{o.current&&(o.current.getTracks().forEach(w=>w.stop()),o.current=null),d.current&&(d.current.srcObject=null);},k=()=>{if(!d.current)return "";try{let w=d.current,x=document.createElement("canvas");x.width=w.videoWidth||320,x.height=w.videoHeight||240;let E=x.getContext("2d");if(E)return E.drawImage(w,0,0,x.width,x.height),x.toDataURL("image/jpeg",.6)}catch{}return ""};return jsxs("div",{className:"flex flex-col items-center space-y-4 py-2",children:[jsxs("div",{className:"relative w-48 h-48 border-2 border-cyber-cyan/50 rounded-lg overflow-hidden bg-cyber-bg/90 flex items-center justify-center",children:[jsx("div",{className:"absolute inset-0 bg-[radial-gradient(#00f3ff_1px,transparent_1px)] [background-size:12px_12px] opacity-20"}),jsx("video",{ref:d,muted:true,playsInline:true,className:"absolute inset-0 w-full h-full object-cover opacity-60"}),jsxs("div",{className:"w-32 h-32 border border-dashed border-cyber-cyan/60 rounded-full flex items-center justify-center relative z-10",children:[jsx(User,{className:"w-16 h-16 text-cyber-cyan/30"}),jsx("div",{className:"absolute top-4 left-6 w-1.5 h-1.5 bg-cyber-cyan rounded-full animate-ping"}),jsx("div",{className:"absolute top-8 right-8 w-1.5 h-1.5 bg-cyber-cyan rounded-full"})]}),e&&jsx("div",{className:"absolute left-0 right-0 h-0.5 bg-cyber-cyan shadow-cyber-glow-strong animate-scan-laser z-20"}),jsx("div",{className:"absolute bottom-2 text-[10px] tracking-wider text-cyber-cyan/80 bg-cyber-bg/80 px-2 py-0.5 border border-cyber-cyan/30 z-30",children:i})]}),jsx("p",{className:"text-xs text-cyber-cyan/60 text-center",children:"Optic vector scan. Interacts with device hardware biometrics when enrolled."}),jsxs("button",{type:"button",onClick:async()=>{if(e||t==="loading")return;await p(),n(true),c("SCANNING FACIAL MESH...");let w=window.setInterval(()=>{r(1400,"sine",.05);},300);u(w),g.current=window.setTimeout(async()=>{window.clearInterval(w),u(null);let x=k();P(),c("MATCH CONFIRMED - 99.8%"),await a(x),n(false),g.current=null;},2500);},disabled:e||t==="loading",className:"w-full bg-cyber-cyan/10 border border-cyber-cyan hover:bg-cyber-cyan/30 text-cyber-cyan font-orbitron py-3 rounded tracking-widest font-bold transition-all shadow-cyber-glow flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed",children:[jsx(Scan,{className:"w-4 h-4"}),jsx("span",{children:e?"ANALYZING...":"INITIALIZE OPTIC SCAN"})]})]})}var at="JARVIS ACCESS AUTHORIZATION CODE SEVEN",ue=[10,14,18,14,10,7,5];function de(){let{verifyVoice:a,playBeep:r,status:t}=R(),[e,n]=useState(false),[i,c]=useState(ue),[y,u]=useState("LISTEN & VERIFY"),g=useRef(null),d=useRef(null),o=useRef(null),p=useRef([]);useEffect(()=>()=>{g.current&&window.clearInterval(g.current),d.current&&window.clearTimeout(d.current);},[]);let P=async()=>{if(e||t==="loading")return;if(n(true),u("ANALYZING FREQUENCY..."),p.current=[],typeof navigator<"u"&&navigator.mediaDevices)try{let I=await navigator.mediaDevices.getUserMedia({audio:!0}),w=window.MediaRecorder;if(w){let x=new w(I);o.current=x,x.ondataavailable=E=>{E.data&&E.data.size>0&&p.current.push(E.data);},x.start();}else I.getTracks().forEach(x=>x.stop());}catch{}let k=()=>{r(400+Math.random()*600,"sine",.05),c(()=>ue.map(()=>Math.floor(Math.random()*50)+6));};k(),g.current=window.setInterval(k,100),d.current=window.setTimeout(async()=>{g.current&&(window.clearInterval(g.current),g.current=null),c(ue);let I;if(o.current&&p.current.length>0){try{await new Promise(x=>{let E=o.current;E.onstop=()=>x(),E.stop(),E.stream&&E.stream.getTracks().forEach(F=>F.stop());});}catch{}let w=p.current[0]?.type||"audio/webm";I=new Blob(p.current,{type:w});}else I=new Blob(["jarvis-voice-sample"],{type:"audio/wav"});p.current=[],o.current=null,await a(I),n(false),u("LISTEN & VERIFY"),d.current=null;},2800);};return jsxs("div",{className:"flex flex-col items-center space-y-4 py-2",children:[jsxs("div",{className:"w-full h-32 border border-cyber-cyan/40 bg-cyber-bg/90 rounded p-3 flex flex-col items-center justify-center relative overflow-hidden",children:[jsx("div",{className:"flex items-end justify-center gap-1.5 w-full h-16",children:i.map((k,I)=>jsx("div",{className:"w-1.5 bg-cyber-cyan rounded-full transition-all duration-150",style:{height:`${k}px`,opacity:Math.max(.3,Math.min(1,k/50))}},I))}),jsxs("div",{className:"text-xs text-cyber-cyan font-mono mt-2 tracking-widest text-glow text-center",children:["PHRASE: \u201C",at,"\u201D"]})]}),jsx("p",{className:"text-xs text-cyber-cyan/60 text-center",children:"Voice print spectral verification. Speak phrase into microphone."}),jsxs("button",{type:"button",onClick:P,disabled:e||t==="loading",className:["w-full bg-cyber-cyan/10 border text-cyber-cyan font-orbitron py-3 rounded tracking-widest font-bold transition-all shadow-cyber-glow flex items-center justify-center gap-2 disabled:cursor-not-allowed",e?"border-cyber-emerald/80 bg-cyber-emerald/15 animate-pulse":"border-cyber-cyan hover:bg-cyber-cyan/30 disabled:opacity-60"].join(" "),children:[jsx(Mic,{className:"w-4 h-4"}),jsx("span",{children:y})]})]})}function me(){let{verifyFingerprint:a,playBeep:r,status:t}=R(),[e,n]=useState(0),[i,c]=useState(false),y=useRef(null);useEffect(()=>()=>{y.current&&window.clearInterval(y.current);},[]);let u=()=>{i||t==="loading"||(c(true),r(600,"sine",.05),y.current=window.setInterval(()=>{n(p=>{let P=p+10;return r(600+P*5,"sine",.05),P>=100?(y.current&&(window.clearInterval(y.current),y.current=null),g(),100):P});},120));},g=async()=>{let p=`fp_${Date.now()}_${Math.random().toString(36).slice(2)}`;await a(p),d(false);},d=(p=true)=>{y.current&&(window.clearInterval(y.current),y.current=null),c(false),p&&n(0);},o=e/100*360;return jsxs("div",{className:"flex flex-col items-center space-y-4 py-2",children:[jsxs("div",{role:"button",tabIndex:0,onMouseDown:u,onMouseUp:()=>d(e<100),onMouseLeave:()=>d(e<100),onTouchStart:u,onTouchEnd:()=>d(e<100),onKeyDown:p=>{(p.key===" "||p.key==="Enter")&&(p.preventDefault(),u());},onKeyUp:()=>d(e<100),className:["w-36 h-36 border-2 border-dashed rounded-full flex items-center justify-center bg-cyber-bg/90 cursor-pointer relative shadow-cyber-glow transition-all select-none",i?"border-cyber-cyan scale-105":"border-cyber-cyan/50 hover:border-cyber-cyan",t==="loading"?"pointer-events-none opacity-80":""].join(" "),"aria-label":"Fingerprint pad - press and hold to scan",children:[jsx(Fingerprint,{className:["w-20 h-20 transition-all duration-200",i?"text-cyber-cyan scale-110":"text-cyber-cyan/60"].join(" ")}),jsx("div",{className:"absolute inset-0 rounded-full pointer-events-none",style:{background:e>0?`conic-gradient(from 0deg, rgba(0,243,255,0.9) 0deg, rgba(0,243,255,0.9) ${o}deg, transparent ${o}deg, transparent 360deg)`:"transparent",WebkitMask:"radial-gradient(transparent 58%, black 59%, black 70%, transparent 71%)",mask:"radial-gradient(transparent 58%, black 59%, black 70%, transparent 71%)",opacity:e>0?1:0}}),jsx("div",{className:"absolute inset-3 rounded-full border-2 border-t-cyber-cyan/80 border-r-cyber-cyan/40 border-b-cyber-cyan/10 border-l-cyber-cyan/40",style:{animation:i?"spin 0.8s linear infinite":"none"}})]}),jsx("div",{className:"w-36 h-1.5 bg-cyber-cyan/20 rounded-full overflow-hidden",children:jsx("div",{className:"h-full bg-cyber-cyan transition-all duration-150",style:{width:`${e}%`}})}),jsx("p",{className:"text-xs text-cyber-cyan/60 text-center",children:"PRESS AND HOLD THUMBPRINT SCANNER TO VERIFY BIOMETRICS"})]})}function ht(){let{modal:a,closeModal:r}=R();return a.open?jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-[fadeIn_.2s_ease-out]",role:"dialog","aria-modal":"true","aria-labelledby":"jarvis-modal-title",onClick:t=>{t.target===t.currentTarget&&r();},children:jsxs("div",{className:"cyber-panel max-w-sm w-full p-6 text-center rounded-lg space-y-4 shadow-cyber-glow-strong animate-[popIn_.25s_cubic-bezier(0.34,1.56,0.64,1)]",style:{borderColor:a.isSuccess?"rgba(0, 243, 255, 0.5)":"rgba(255, 0, 85, 0.5)"},children:[jsx("div",{className:"absolute top-[-2px] right-[-2px] w-3 h-3 border-t-2 border-r-2",style:{borderColor:a.isSuccess?"#00f3ff":"#ff0055"}}),jsx("div",{className:"absolute bottom-[-2px] left-[-2px] w-3 h-3 border-b-2 border-l-2",style:{borderColor:a.isSuccess?"#00f3ff":"#ff0055"}}),jsx("div",{className:"w-16 h-16 rounded-full mx-auto flex items-center justify-center border-2",style:{borderColor:a.isSuccess?"#00f3ff":"#ff0055",backgroundColor:a.isSuccess?"rgba(0, 243, 255, 0.2)":"rgba(255, 0, 85, 0.2)"},children:a.isSuccess?jsx(ShieldCheck,{className:"w-8 h-8",style:{color:"#00f3ff"}}):jsx(AlertTriangle,{className:"w-8 h-8",style:{color:"#ff0055"}})}),jsx("h3",{id:"jarvis-modal-title",className:`font-orbitron font-bold text-xl tracking-widest ${a.isSuccess?"text-glow":"text-glow-red"}`,style:{color:a.isSuccess?"#00f3ff":"#ff0055"},children:a.title}),jsx("p",{className:"text-xs text-cyber-cyan/80 leading-relaxed font-mono",children:a.message}),jsx("button",{onClick:r,className:"w-full bg-cyber-cyan/20 border border-cyber-cyan hover:bg-cyber-cyan/40 text-cyber-cyan font-orbitron py-2.5 rounded tracking-widest transition-all",children:"CONTINUE"})]})}):null}function gt(){let{activeMethod:a,setActiveMethod:r}=R(),t=[{id:"retina",icon:jsx(Eye,{className:"w-5 h-5 mb-1"}),label:"FACIAL/EYE"},{id:"passkey",icon:jsx(Target,{className:"w-5 h-5 mb-1"}),label:"PASSKEY"},{id:"voice",icon:jsx(Mic,{className:"w-5 h-5 mb-1"}),label:"VOICE"}],e="flex flex-col items-center justify-center p-3 border rounded transition-all group",n="border-cyber-cyan bg-cyber-cyan/20 shadow-cyber-glow",i="border-cyber-cyan/30 bg-cyber-cyan/5 hover:bg-cyber-cyan/20";return jsx("div",{className:"grid grid-cols-3 gap-3 mb-6",children:t.map(c=>{let y=a===c.id;return jsxs("button",{type:"button",id:`btn-method-${c.id}`,onClick:()=>r(c.id),className:`${e} ${y?n:i}`,"aria-pressed":y,children:[jsx("div",{className:`transition-transform ${y?"":"group-hover:scale-110"}`,children:c.icon}),jsx("span",{className:"text-[10px] tracking-wider",children:c.label})]},c.id)})})}function vt(){let{user:a,logout:r,enrollBiometrics:t,status:e}=R(),[n,i]=useState(false);return useEffect(()=>{i(typeof window<"u"&&typeof window.PublicKeyCredential<"u");},[]),a?jsxs("div",{className:"cyber-panel w-full max-w-md p-6 md:p-8 rounded-lg shadow-cyber-glow relative space-y-6 animate-[fadeIn_.3s_ease-out]",children:[jsx("div",{className:"cyber-corner-tr"}),jsx("div",{className:"cyber-corner-bl"}),jsxs("div",{className:"flex justify-between items-center border-b border-cyber-cyan/20 pb-4",children:[jsxs("div",{children:[jsx("h2",{className:"font-orbitron text-xl font-bold tracking-widest text-glow text-cyber-emerald",children:"CLEARANCE GRANTED"}),jsx("p",{className:"text-xs text-cyber-cyan/60 tracking-wider",children:"AUTHENTICATED OPERATIVE COMMAND"})]}),jsx("div",{className:"p-2 bg-cyber-emerald/10 border border-cyber-emerald/40 rounded-full text-cyber-emerald",children:jsx(ShieldCheck,{className:"w-6 h-6"})})]}),jsxs("div",{className:"space-y-3 font-mono text-xs bg-cyber-bg/90 p-4 border border-cyber-cyan/30 rounded",children:[jsxs("div",{className:"flex justify-between gap-2",children:[jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"OPERATIVE UID:"}),jsx("span",{className:"text-cyber-cyan font-bold truncate",children:a.uid})]}),jsxs("div",{className:"flex justify-between gap-2",children:[jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"EMAIL:"}),jsx("span",{className:"text-cyber-cyan font-bold truncate",children:a.email})]}),jsxs("div",{className:"flex justify-between gap-2",children:[jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"FULL NAME:"}),jsx("span",{className:"text-cyber-cyan font-bold truncate",children:a.fullName})]}),jsxs("div",{className:"flex justify-between gap-2",children:[jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"CLEARANCE:"}),jsx("span",{className:"text-cyber-emerald font-bold",children:a.clearanceLevel||"Level 1 - Full Access"})]}),jsxs("div",{className:"flex justify-between gap-2",children:[jsx("span",{className:"text-cyber-cyan/50 shrink-0",children:"BIOMETRICS:"}),jsx("span",{className:a.hasBiometrics?"text-cyber-emerald font-bold":"text-cyber-cyan font-bold",children:a.hasBiometrics?"ENROLLED":"NOT ENROLLED"})]})]}),jsxs("button",{type:"button",onClick:t,disabled:e==="loading"||a.hasBiometrics,className:"w-full bg-cyber-cyan/10 border border-cyber-cyan hover:bg-cyber-cyan/20 text-cyber-cyan py-2.5 rounded font-orbitron text-xs tracking-wider transition-all flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",children:[jsx(Fingerprint,{className:"w-4 h-4"}),jsx("span",{children:n?a.hasBiometrics?"BIOMETRICS ALREADY ENROLLED":"ENROLL DEVICE TOUCH ID / FACE ID":"ENROLL BIOMETRICS (SIMULATED)"})]}),jsxs("button",{type:"button",onClick:r,disabled:e==="loading",className:"w-full bg-cyber-red/10 border border-cyber-red hover:bg-cyber-red/30 text-cyber-red font-orbitron py-3 rounded tracking-widest font-bold transition-all flex items-center justify-center gap-2 disabled:opacity-60",children:[jsx(LogOut,{className:"w-4 h-4"}),jsx("span",{children:"TERMINATE SESSION (SIGN OUT)"})]})]}):null}function wt(){let{isRegisterMode:a,toggleMode:r,activeMethod:t,setActiveMethod:e,playBeep:n}=R();return jsxs("div",{className:"cyber-panel w-full max-w-md p-6 md:p-8 rounded-lg shadow-cyber-glow relative animate-[fadeIn_.3s_ease-out]",children:[jsx("div",{className:"cyber-corner-tr"}),jsx("div",{className:"cyber-corner-bl"}),jsxs("div",{className:"flex justify-between items-center mb-6 border-b border-cyber-cyan/20 pb-4",children:[jsxs("div",{children:[jsx("h2",{id:"auth-title",className:"font-orbitron text-xl md:text-2xl font-bold tracking-widest text-glow uppercase",children:a?"REGISTER":"SIGN IN"}),jsx("p",{className:"text-xs text-cyber-cyan/60 tracking-wider",children:a?"CREATE NEW OPERATIVE CLEARANCE":"AUTHENTICATION REQUIRED"})]}),jsxs("button",{type:"button",onClick:r,className:"text-xs border border-cyber-cyan/40 hover:border-cyber-cyan bg-cyber-cyan/10 hover:bg-cyber-cyan/20 px-3 py-1.5 rounded transition-all tracking-wider flex items-center gap-1",children:[jsx(RefreshCw,{className:"w-3 h-3"}),jsx("span",{children:a?"SIGN IN":"REGISTER"})]})]}),jsx(gt,{}),jsxs("div",{className:"min-h-[360px]",children:[t==="passkey"&&jsx(ne,{}),t==="retina"&&jsx(ce,{}),t==="voice"&&jsx(de,{}),t==="fingerprint"&&jsx(me,{})]}),jsxs("div",{className:"mt-6 pt-4 border-t border-cyber-cyan/20 flex justify-between items-center text-xs",children:[jsxs("button",{type:"button",onClick:()=>{e("fingerprint"),n(700,"sine",.1);},className:"text-cyber-cyan/60 hover:text-cyber-cyan flex items-center gap-1",children:[jsx(Fingerprint,{className:"w-3.5 h-3.5"}),jsx("span",{children:"FINGERPRINT SCAN"})]}),jsxs("span",{className:"text-cyber-cyan/40 uppercase tracking-wider",children:["PROTOCOL: ",t]})]})]})}function xt(){let{user:a,audioEnabled:r,toggleAudio:t,adapterName:e}=R(),[n,i]=useState({time:"13:14",date:""});useEffect(()=>{let y=()=>{let g=new Date,d=String(g.getHours()).padStart(2,"0"),o=String(g.getMinutes()).padStart(2,"0");i({time:`${d}:${o}`,date:g.toLocaleDateString("en-US",{weekday:"long",year:"numeric",month:"long",day:"numeric"})});};y();let u=setInterval(y,1e3);return ()=>clearInterval(u)},[]);let c=()=>{typeof document>"u"||(document.fullscreenElement?document.exitFullscreen&&document.exitFullscreen().catch(()=>{}):document.documentElement.requestFullscreen().catch(()=>{}));};return jsxs("div",{className:"min-h-screen w-screen flex flex-col justify-between p-4 md:p-8 relative overflow-x-hidden select-none",children:[jsxs("header",{className:"relative z-10 flex justify-between items-center w-full border-b border-cyber-cyan/20 pb-3",children:[jsxs("div",{className:"flex items-center space-x-3",children:[jsx("div",{className:"w-3 h-3 bg-cyber-cyan rounded-full animate-ping"}),jsxs("div",{children:[jsxs("h1",{className:"font-orbitron font-bold text-lg md:text-xl tracking-widest text-glow flex items-center gap-2",children:["J.A.R.V.I.S.",jsx("span",{className:"text-xs px-2 py-0.5 rounded bg-cyber-cyan/10 border border-cyber-cyan/40 text-cyber-cyan",children:"PRODUCTION AUTH v10.5"})]}),jsx("p",{className:"text-xs text-cyber-cyan/60 tracking-wider",children:"REALTIME AUTHENTICATION & ENCRYPTION ENGINE"})]})]}),jsxs("div",{className:"hidden md:flex items-center space-x-8 text-xs",children:[jsxs("div",{className:"flex items-center space-x-2",children:[jsx("span",{className:"text-cyber-cyan/50",children:"ADAPTER:"}),jsx("span",{className:"text-cyber-emerald font-bold uppercase",children:e})]}),jsxs("div",{className:"flex items-center space-x-2",children:[jsx("span",{className:"text-cyber-cyan/50",children:"WEBAUTHN:"}),jsx("span",{className:"text-cyber-cyan font-bold",children:"READY"})]}),jsxs("div",{className:"flex items-center space-x-2",children:[jsx("span",{className:"text-cyber-cyan/50",children:"AI CORE:"}),jsx("span",{className:"text-cyber-emerald animate-pulse",children:"ONLINE"})]})]}),jsxs("div",{className:"flex items-center space-x-2",children:[jsx("button",{onClick:t,"aria-label":r?"Mute audio":"Enable audio",className:["p-2 bg-cyber-cyan/10 border border-cyber-cyan/30 hover:bg-cyber-cyan/20 text-cyber-cyan transition-all rounded",r?"":"opacity-50"].join(" "),children:r?jsx(Volume2,{className:"w-4 h-4"}):jsx(VolumeX,{className:"w-4 h-4"})}),jsx("button",{onClick:c,"aria-label":"Toggle fullscreen",className:"p-2 bg-cyber-cyan/10 border border-cyber-cyan/30 hover:bg-cyber-cyan/20 text-cyber-cyan transition-all rounded",children:jsx(Maximize,{className:"w-4 h-4"})})]})]}),jsxs("main",{className:"relative z-10 flex-1 grid grid-cols-1 lg:grid-cols-12 gap-8 items-center my-6",children:[jsx("section",{className:"lg:col-span-6 flex flex-col items-center justify-center relative min-h-[300px]",children:jsx(ae,{})}),jsx("section",{className:"lg:col-span-6 flex justify-center",children:a?jsx(vt,{}):jsx(wt,{})})]}),jsxs("footer",{className:"relative z-10 flex flex-col md:flex-row justify-between items-center border-t border-cyber-cyan/20 pt-3 text-xs text-cyber-cyan/60 space-y-2 md:space-y-0",children:[jsxs("div",{className:"flex items-center space-x-4",children:[jsxs("span",{children:["STARK INDUSTRIES \xA9 ",new Date().getFullYear()]}),jsx("span",{children:"\u2022"}),jsxs("span",{className:"text-cyber-emerald flex items-center gap-1",children:[jsx("span",{className:"w-2 h-2 rounded-full bg-cyber-emerald animate-ping"})," ","SECURE AUTH NODE ACTIVE"]})]}),jsxs("div",{className:"flex items-center space-x-6 font-mono",children:[jsx("div",{className:"text-cyber-cyan text-sm md:text-base font-bold tracking-widest text-glow",children:n.time}),jsx("div",{className:"text-cyber-cyan/50 text-xs uppercase",children:n.date})]})]}),jsx(ht,{}),jsx("style",{dangerouslySetInnerHTML:{__html:`
|
|
2
|
+
@keyframes fadeIn {
|
|
3
|
+
from { opacity: 0; }
|
|
4
|
+
to { opacity: 1; }
|
|
5
|
+
}
|
|
6
|
+
@keyframes popIn {
|
|
7
|
+
0% { opacity: 0; transform: scale(0.85); }
|
|
8
|
+
100% { opacity: 1; transform: scale(1); }
|
|
9
|
+
}
|
|
10
|
+
`}})]})}function At(){let a=useRef(null),r=useRef(null);return useEffect(()=>{let t=a.current;if(!t)return;let e=t.getContext("2d");if(!e)return;let n=0,i=0,c=[],y=()=>{n=t.width=window.innerWidth,i=t.height=window.innerHeight;};y(),window.addEventListener("resize",y);let u=Math.max(50,Math.min(100,Math.floor(n*i/24e3)));for(let d=0;d<u;d++)c.push({x:Math.random()*n,y:Math.random()*i,size:Math.random()*1.5+.5,speedX:(Math.random()-.5)*.3,speedY:(Math.random()-.5)*.3,opacity:Math.random()*.5+.2});let g=()=>{e.clearRect(0,0,n,i),e.strokeStyle="rgba(0, 243, 255, 0.035)",e.lineWidth=1;let d=40;for(let o=0;o<n;o+=d)e.beginPath(),e.moveTo(o,0),e.lineTo(o,i),e.stroke();for(let o=0;o<i;o+=d)e.beginPath(),e.moveTo(0,o),e.lineTo(n,o),e.stroke();c.forEach(o=>{o.x+=o.speedX,o.y+=o.speedY,o.x<0?o.x=n:o.x>n&&(o.x=0),o.y<0?o.y=i:o.y>i&&(o.y=0),e.fillStyle=`rgba(0, 243, 255, ${o.opacity})`,e.beginPath(),e.arc(o.x,o.y,o.size,0,Math.PI*2),e.fill();}),r.current=requestAnimationFrame(g);};return g(),()=>{r.current!=null&&cancelAnimationFrame(r.current),window.removeEventListener("resize",y);}},[]),jsxs(Fragment,{children:[jsx("canvas",{ref:a,className:"fixed inset-0 z-0 pointer-events-none","aria-hidden":"true"}),jsx("div",{className:"scanlines fixed inset-0 z-0 pointer-events-none","aria-hidden":"true"})]})}export{ae as ArcReactorHud,xt as AuthPortal,_e as AuthProvider,X as BackendAuthAdapter,At as CanvasBackground,ce as FacialScanner,me as FingerprintPad,Y as MockAuthAdapter,ne as PasskeyForm,K as SoundEngine,de as VoiceScanner,Be as __resetMockAdapterStateForTests,re as createAuthAdapter,R as useAuth};
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jarvis-security/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "J.A.R.V.I.S. Futuristic Pluggable Authentication Suite",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"jarvis",
|
|
11
|
+
"security",
|
|
12
|
+
"authentication",
|
|
13
|
+
"biometric",
|
|
14
|
+
"pluggable"
|
|
15
|
+
],
|
|
16
|
+
"author": "Aaqib Javaid",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.mjs",
|
|
24
|
+
"require": "./dist/index.cjs"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "next dev",
|
|
34
|
+
"build": "next build",
|
|
35
|
+
"start": "next start",
|
|
36
|
+
"lint": "next lint",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"test:coverage": "vitest run --coverage",
|
|
41
|
+
"build:sdk": "tsup"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@simplewebauthn/browser": "^10.0.0",
|
|
45
|
+
"clsx": "^2.1.1",
|
|
46
|
+
"lucide-react": "^0.460.0",
|
|
47
|
+
"tailwind-merge": "^2.5.4"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": "^18.0.0",
|
|
51
|
+
"react-dom": "^18.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
55
|
+
"@testing-library/react": "^15.0.7",
|
|
56
|
+
"@testing-library/user-event": "^14.5.2",
|
|
57
|
+
"@types/node": "^20.17.6",
|
|
58
|
+
"@types/react": "^18.3.12",
|
|
59
|
+
"@types/react-dom": "^18.3.1",
|
|
60
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
61
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
62
|
+
"autoprefixer": "^10.4.20",
|
|
63
|
+
"eslint": "^8.57.0",
|
|
64
|
+
"eslint-config-next": "^14.2.18",
|
|
65
|
+
"jsdom": "^24.1.3",
|
|
66
|
+
"next": "^14.2.18",
|
|
67
|
+
"postcss": "^8.4.49",
|
|
68
|
+
"react": "^18.3.1",
|
|
69
|
+
"react-dom": "^18.3.1",
|
|
70
|
+
"tailwindcss": "^3.4.15",
|
|
71
|
+
"tsup": "^8.5.1",
|
|
72
|
+
"typescript": "^5.6.3",
|
|
73
|
+
"vitest": "^1.6.1"
|
|
74
|
+
},
|
|
75
|
+
"allowScripts": {
|
|
76
|
+
"esbuild@0.27.7": true,
|
|
77
|
+
"esbuild@0.25.12": true,
|
|
78
|
+
"esbuild@0.21.5": true,
|
|
79
|
+
"unrs-resolver@1.12.2": true
|
|
80
|
+
}
|
|
81
|
+
}
|