isitme 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # isitme
2
2
 
3
- Passkey authentication for Express. One line of code, no database, no passwords.
3
+ Free passkey authentication for solo builders. One owner per domain. No accounts. No passwords. No kidding.
4
4
 
5
5
  ```
6
- npm install isitme
6
+ npm i isitme
7
7
  ```
8
8
 
9
9
  ## Quick start
@@ -24,81 +24,53 @@ app.get("/", (req, res) => {
24
24
  app.listen(3000);
25
25
  ```
26
26
 
27
- The first visitor registers a passkey with their fingerprint or face. After that, every request requires biometric authentication.
27
+ Visit `localhost:3000` register a passkey with your fingerprint, and the app is locked to you.
28
28
 
29
- ## Options
29
+ ## React
30
30
 
31
- ```js
32
- app.use(isitme({
33
- secret: "your-jwt-secret", // default: random (sessions lost on restart)
34
- storage: "./auth.json", // default: "./auth.json", false for in-memory
35
- publicPaths: ["/", "/about"], // routes that skip auth
36
- prefix: "/auth", // auth endpoint prefix
37
- sessionExpiryDays: 7, // session duration
38
- page: { title: "My App" }, // customize login page, or false to disable
39
- }));
40
- ```
31
+ ```jsx
32
+ import { IsItMe } from "isitme/react";
41
33
 
42
- ## Public paths and conditional auth
34
+ <IsItMe>
35
+ <h1>Admin dashboard</h1>
36
+ <p>Only you can see this.</p>
37
+ </IsItMe>
38
+ ```
43
39
 
44
- The middleware sets `req.isAuthenticated` on every request, even on public paths:
40
+ ## Browser API
45
41
 
46
42
  ```js
47
- app.use(isitme({ publicPaths: ["/"] }));
43
+ import { signin, isItMe, logout } from "isitme/browser";
48
44
 
49
- app.get("/", (req, res) => {
50
- if (req.isAuthenticated) {
51
- res.send("Welcome back!");
52
- } else {
53
- res.send("Sign in to continue.");
54
- }
55
- });
45
+ await signin(); // register or login — one call
46
+ const session = await isItMe(); // silent check — no UI
47
+ await logout(); // end session
56
48
  ```
57
49
 
58
- ## Browser client
50
+ `signin()` checks if a passkey exists for this domain. First visit triggers registration, every visit after triggers login. No branching logic needed.
59
51
 
60
- For custom login pages, import the browser helpers:
52
+ For manual control, use `login()` and `register()` directly:
61
53
 
62
54
  ```js
63
- import { register, login, getAuthStatus } from "isitme/browser";
55
+ import { login, register, isItMe, IsitmeError } from "isitme/browser";
64
56
 
65
- const status = await getAuthStatus();
66
-
67
- if (!status.isSetup) {
68
- await register();
69
- } else {
57
+ try {
70
58
  await login();
59
+ } catch (err) {
60
+ if (err instanceof IsitmeError && err.code === "NOT_SETUP") {
61
+ await register(); // no passkey yet — register first
62
+ }
71
63
  }
72
64
  ```
73
65
 
74
- ## Custom storage
75
-
76
- Implement the `StorageAdapter` interface to use any backend:
77
-
78
- ```js
79
- import { isitme } from "isitme";
80
-
81
- app.use(isitme({
82
- storage: {
83
- isSetupComplete() { /* ... */ },
84
- getUser() { /* ... */ },
85
- getCredentials() { /* ... */ },
86
- getCredentialById(id) { /* ... */ },
87
- saveUserAndCredential(user, credential) { /* ... */ },
88
- updateCredentialCounter(id, counter) { /* ... */ },
89
- getAuthDataJson() { /* ... */ },
90
- }
91
- }));
92
- ```
66
+ ## Docs
93
67
 
94
- ## How it works
68
+ Full API reference, configuration options, and framework guides:
95
69
 
96
- 1. `isitme()` adds auth routes (`/auth/*`) and a guard middleware to your app
97
- 2. Unauthenticated visitors see a built-in login page
98
- 3. Registration creates a WebAuthn passkey tied to the device's biometrics
99
- 4. Login verifies the passkey and sets a JWT session cookie
100
- 5. `req.isAuthenticated` is available on every request
70
+ **https://isitme.dev/docs**
101
71
 
102
- ## License
72
+ ## Links
103
73
 
104
- MIT
74
+ - **Docs:** https://isitme.dev/docs
75
+ - **GitHub:** https://github.com/isitme-dev/isitme
76
+ - **npm:** https://www.npmjs.com/package/isitme
package/dist/browser.d.ts CHANGED
@@ -2,6 +2,10 @@ interface AuthStatus {
2
2
  setupComplete: boolean;
3
3
  isAuthenticated: boolean;
4
4
  }
5
+ interface Session {
6
+ authenticated: true;
7
+ registered: true;
8
+ }
5
9
  interface RegisterResult {
6
10
  verified: boolean;
7
11
  authData?: string;
@@ -9,15 +13,43 @@ interface RegisterResult {
9
13
  interface LoginResult {
10
14
  verified: boolean;
11
15
  }
16
+ interface SigninResult {
17
+ verified: boolean;
18
+ action: "register" | "login";
19
+ authData?: string;
20
+ }
21
+ type IsitmeErrorCode = "NOT_SETUP" | "ALREADY_SETUP" | "CREDENTIAL_NOT_FOUND" | "CHALLENGE_EXPIRED" | "VERIFICATION_FAILED" | "SITE_NOT_FOUND" | "SITE_BLOCKED" | "NETWORK_ERROR" | "PASSKEY_CANCELLED" | "UNKNOWN";
22
+ declare class IsitmeError extends Error {
23
+ code: IsitmeErrorCode;
24
+ cause?: Error;
25
+ constructor(code: IsitmeErrorCode, message: string, cause?: Error);
26
+ }
12
27
  interface IsitmeBrowserOptions {
13
- /** Auth API prefix. Default: `/auth` */
28
+ /** Auth API prefix. Default: `/_isitme` */
14
29
  prefix?: string;
30
+ /** Cloud API URL (e.g. `https://api.isitme.dev`). When set, auth uses the cloud API + localStorage instead of same-origin cookies. */
31
+ api?: string;
15
32
  }
16
33
 
17
- declare function register(opts?: IsitmeBrowserOptions): Promise<RegisterResult>;
34
+ declare function signin(opts?: IsitmeBrowserOptions): Promise<SigninResult>;
35
+
36
+ /**
37
+ * Silent session check. Returns session info if authenticated, null otherwise.
38
+ * Never shows UI — use `login()` or `register()` for that.
39
+ */
40
+ declare function isItMe(opts?: IsitmeBrowserOptions): Promise<Session | null>;
41
+
42
+ /**
43
+ * End the current session. Works in both server and cloud mode.
44
+ * Server mode: POSTs to `/_isitme/logout` to clear the session cookie.
45
+ * Cloud mode: clears the session from localStorage.
46
+ */
47
+ declare function logout(opts?: IsitmeBrowserOptions): Promise<void>;
18
48
 
19
49
  declare function login(opts?: IsitmeBrowserOptions): Promise<LoginResult>;
20
50
 
51
+ declare function register(opts?: IsitmeBrowserOptions): Promise<RegisterResult>;
52
+
21
53
  declare function getAuthStatus(opts?: IsitmeBrowserOptions): Promise<AuthStatus>;
22
54
 
23
- export { type AuthStatus, type IsitmeBrowserOptions, type LoginResult, type RegisterResult, getAuthStatus, login, register };
55
+ export { type AuthStatus, type IsitmeBrowserOptions, IsitmeError, type IsitmeErrorCode, type LoginResult, type RegisterResult, type Session, type SigninResult, getAuthStatus, isItMe, login, logout, register, signin };
package/dist/browser.js CHANGED
@@ -355,46 +355,308 @@ async function startAuthentication(options) {
355
355
  }
356
356
 
357
357
  // ../browser/dist/index.js
358
- async function register(opts = {}) {
359
- const prefix = opts.prefix || "/auth";
360
- const optRes = await fetch(`${prefix}/register/options`, { method: "POST" });
361
- if (!optRes.ok) throw new Error("Failed to get registration options");
362
- const { options, nonce, userId } = await optRes.json();
363
- const registration = await startRegistration({ optionsJSON: options });
364
- const verifyRes = await fetch(`${prefix}/register/verify`, {
358
+ var IsitmeError = class extends Error {
359
+ code;
360
+ cause;
361
+ constructor(code, message, cause) {
362
+ super(message);
363
+ this.name = "IsitmeError";
364
+ this.code = code;
365
+ this.cause = cause;
366
+ }
367
+ };
368
+ var DEFAULT_PREFIX = "/_isitme";
369
+ var STORAGE_KEY = "isitme_session";
370
+ var Errors = {
371
+ SETUP_COMPLETE: "Setup already complete",
372
+ NO_CREDENTIALS: "No credentials registered",
373
+ CHALLENGE_EXPIRED: "Challenge expired",
374
+ VERIFICATION_FAILED: "Verification failed",
375
+ CREDENTIAL_NOT_FOUND: "Credential not found"
376
+ };
377
+ function getDomain() {
378
+ return location.hostname;
379
+ }
380
+ function getCloudSession() {
381
+ try {
382
+ const raw = localStorage.getItem(STORAGE_KEY);
383
+ if (!raw) return null;
384
+ return JSON.parse(raw);
385
+ } catch {
386
+ return null;
387
+ }
388
+ }
389
+ function saveSession(data) {
390
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
391
+ }
392
+ function clearCloudSession() {
393
+ localStorage.removeItem(STORAGE_KEY);
394
+ }
395
+ async function apiPost(api, path, body) {
396
+ const res = await fetch(api + path, {
365
397
  method: "POST",
366
398
  headers: { "Content-Type": "application/json" },
367
- body: JSON.stringify({ nonce, userId, response: registration })
399
+ body: JSON.stringify(body)
368
400
  });
369
- const result = await verifyRes.json();
370
- if (!verifyRes.ok) throw new Error(result.error || "Registration failed");
371
- return result;
401
+ const data = await res.json();
402
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
403
+ return data;
372
404
  }
373
- async function login(opts = {}) {
374
- const prefix = opts.prefix || "/auth";
375
- const optRes = await fetch(`${prefix}/login/options`, { method: "POST" });
376
- if (!optRes.ok) throw new Error("Failed to get login options");
377
- const { options, nonce } = await optRes.json();
378
- const authentication = await startAuthentication({ optionsJSON: options });
379
- const verifyRes = await fetch(`${prefix}/login/verify`, {
380
- method: "POST",
381
- headers: { "Content-Type": "application/json" },
382
- body: JSON.stringify({ nonce, response: authentication })
405
+ async function cloudRegister(api) {
406
+ const domain = getDomain();
407
+ const ch = await apiPost(api, "/v1/challenge", {
408
+ domain,
409
+ type: "registration"
410
+ });
411
+ const regResp = await startRegistration({
412
+ optionsJSON: {
413
+ challenge: ch.challenge,
414
+ rp: { name: ch.rpName, id: ch.rpId },
415
+ user: {
416
+ id: crypto.randomUUID(),
417
+ name: domain + "-owner",
418
+ displayName: domain + " owner"
419
+ },
420
+ pubKeyCredParams: [
421
+ { alg: -7, type: "public-key" },
422
+ { alg: -257, type: "public-key" }
423
+ ],
424
+ authenticatorSelection: {
425
+ residentKey: "preferred",
426
+ userVerification: "preferred"
427
+ },
428
+ timeout: 6e4,
429
+ attestation: "none"
430
+ }
383
431
  });
384
- if (!verifyRes.ok) {
385
- const data = await verifyRes.json();
386
- throw new Error(data.error || "Authentication failed");
432
+ const result = await apiPost(api, "/v1/register", {
433
+ challengeId: ch.challengeId,
434
+ credentialId: regResp.id,
435
+ publicKey: regResp.response.publicKey,
436
+ signCount: 0,
437
+ transports: regResp.response.transports || []
438
+ });
439
+ saveSession({
440
+ userId: result.userId,
441
+ siteId: result.siteId,
442
+ credentialId: regResp.id,
443
+ transports: regResp.response.transports || [],
444
+ authenticatedAt: Date.now(),
445
+ domain
446
+ });
447
+ return { verified: true };
448
+ }
449
+ async function cloudLogin(api) {
450
+ const domain = getDomain();
451
+ const existing = getCloudSession();
452
+ const ch = await apiPost(api, "/v1/challenge", {
453
+ domain,
454
+ type: "authentication"
455
+ });
456
+ const allowCredentials = ch.credentialIds.map((id) => ({
457
+ id,
458
+ type: "public-key",
459
+ transports: existing?.transports || []
460
+ }));
461
+ const authResp = await startAuthentication({
462
+ optionsJSON: {
463
+ challenge: ch.challenge,
464
+ rpId: ch.rpId,
465
+ allowCredentials,
466
+ timeout: 6e4,
467
+ userVerification: "preferred"
468
+ }
469
+ });
470
+ const result = await apiPost(api, "/v1/authenticate", {
471
+ challengeId: ch.challengeId,
472
+ credentialId: authResp.id,
473
+ signCount: 0
474
+ });
475
+ saveSession({
476
+ userId: result.userId,
477
+ siteId: result.siteId,
478
+ credentialId: authResp.id,
479
+ transports: existing?.transports || [],
480
+ authenticatedAt: Date.now(),
481
+ domain
482
+ });
483
+ return { verified: true };
484
+ }
485
+ async function cloudStatus(api) {
486
+ const domain = getDomain();
487
+ const session = getCloudSession();
488
+ let setupComplete = false;
489
+ try {
490
+ const res = await fetch(api + "/v1/site/" + encodeURIComponent(domain));
491
+ if (res.ok) {
492
+ const data = await res.json();
493
+ setupComplete = data.setupComplete === true;
494
+ }
495
+ } catch {
496
+ setupComplete = !!session;
497
+ }
498
+ return {
499
+ setupComplete,
500
+ isAuthenticated: !!session
501
+ };
502
+ }
503
+ function cloudIsItMe() {
504
+ const session = getCloudSession();
505
+ if (session) {
506
+ return { authenticated: true, registered: true };
387
507
  }
388
- return verifyRes.json();
508
+ return null;
389
509
  }
390
510
  async function getAuthStatus(opts = {}) {
391
- const prefix = opts.prefix || "/auth";
392
- const res = await fetch(`${prefix}/status`);
393
- if (!res.ok) throw new Error("Failed to fetch auth status");
511
+ if (opts.api) return cloudStatus(opts.api);
512
+ const prefix = opts.prefix || DEFAULT_PREFIX;
513
+ const url = `${prefix}/status`;
514
+ const res = await fetch(url, { credentials: "include" });
515
+ if (!res.ok) throw new Error(`Auth status check failed: ${res.status} from ${url}`);
394
516
  return res.json();
395
517
  }
518
+ function classifyRegisterError(err) {
519
+ if (err instanceof IsitmeError) return err;
520
+ const msg = err instanceof Error ? err.message : String(err);
521
+ if (msg.includes(Errors.SETUP_COMPLETE) || msg.includes("already has an owner"))
522
+ return new IsitmeError("ALREADY_SETUP", "This domain already has an owner. Use login() or signin() instead.", err instanceof Error ? err : void 0);
523
+ if (msg.includes(Errors.CHALLENGE_EXPIRED))
524
+ return new IsitmeError("CHALLENGE_EXPIRED", "Registration challenge expired. Please try again.", err instanceof Error ? err : void 0);
525
+ if (msg.includes(Errors.VERIFICATION_FAILED))
526
+ return new IsitmeError("VERIFICATION_FAILED", "Passkey verification failed during registration.", err instanceof Error ? err : void 0);
527
+ if (msg.includes("Site not found"))
528
+ return new IsitmeError("SITE_NOT_FOUND", "Domain not found. The cloud API may be unreachable.", err instanceof Error ? err : void 0);
529
+ if (msg.includes("blocked"))
530
+ return new IsitmeError("SITE_BLOCKED", "This domain has been blocked.", err instanceof Error ? err : void 0);
531
+ if (msg.includes("The operation either timed out") || msg.includes("NotAllowedError") || msg.includes("cancelled") || msg.includes("canceled") || msg.includes("AbortError"))
532
+ return new IsitmeError("PASSKEY_CANCELLED", "Passkey prompt was cancelled or timed out.", err instanceof Error ? err : void 0);
533
+ return new IsitmeError("UNKNOWN", `Registration failed: ${msg}`, err instanceof Error ? err : void 0);
534
+ }
535
+ async function register(opts = {}) {
536
+ try {
537
+ if (opts.api) return await cloudRegister(opts.api);
538
+ const prefix = opts.prefix || DEFAULT_PREFIX;
539
+ const optUrl = `${prefix}/register/start`;
540
+ const optRes = await fetch(optUrl, { method: "POST", credentials: "include" });
541
+ if (!optRes.ok) {
542
+ const body = await optRes.json().catch(() => null);
543
+ throw new Error(body?.error || `Registration options failed: ${optRes.status} from ${optUrl}`);
544
+ }
545
+ const { options, nonce, userId } = await optRes.json();
546
+ const registration = await startRegistration({ optionsJSON: options });
547
+ const verifyUrl = `${prefix}/register/finish`;
548
+ const verifyRes = await fetch(verifyUrl, {
549
+ method: "POST",
550
+ credentials: "include",
551
+ headers: { "Content-Type": "application/json" },
552
+ body: JSON.stringify({ nonce, userId, response: registration })
553
+ });
554
+ const result = await verifyRes.json();
555
+ if (!verifyRes.ok) throw new Error(result.error || `Registration verify failed: ${verifyRes.status} from ${verifyUrl}`);
556
+ return result;
557
+ } catch (err) {
558
+ throw classifyRegisterError(err);
559
+ }
560
+ }
561
+ function classifyLoginError(err) {
562
+ if (err instanceof IsitmeError) return err;
563
+ const msg = err instanceof Error ? err.message : String(err);
564
+ if (msg.includes(Errors.NO_CREDENTIALS) || msg.includes("not complete"))
565
+ return new IsitmeError("NOT_SETUP", "No passkey registered for this domain. Use register() or signin() first.", err instanceof Error ? err : void 0);
566
+ if (msg.includes(Errors.CREDENTIAL_NOT_FOUND))
567
+ return new IsitmeError("CREDENTIAL_NOT_FOUND", "Passkey not recognized. It may have been removed or registered on a different domain.", err instanceof Error ? err : void 0);
568
+ if (msg.includes(Errors.CHALLENGE_EXPIRED))
569
+ return new IsitmeError("CHALLENGE_EXPIRED", "Authentication challenge expired. Please try again.", err instanceof Error ? err : void 0);
570
+ if (msg.includes(Errors.VERIFICATION_FAILED))
571
+ return new IsitmeError("VERIFICATION_FAILED", "Passkey verification failed. Try again or re-register.", err instanceof Error ? err : void 0);
572
+ if (msg.includes("The operation either timed out") || msg.includes("NotAllowedError") || msg.includes("cancelled") || msg.includes("canceled") || msg.includes("AbortError"))
573
+ return new IsitmeError("PASSKEY_CANCELLED", "Passkey prompt was cancelled or timed out.", err instanceof Error ? err : void 0);
574
+ return new IsitmeError("UNKNOWN", `Login failed: ${msg}`, err instanceof Error ? err : void 0);
575
+ }
576
+ async function login(opts = {}) {
577
+ try {
578
+ if (opts.api) return await cloudLogin(opts.api);
579
+ const prefix = opts.prefix || DEFAULT_PREFIX;
580
+ const optUrl = `${prefix}/login/start`;
581
+ const optRes = await fetch(optUrl, { method: "POST", credentials: "include" });
582
+ if (!optRes.ok) {
583
+ const body = await optRes.json().catch(() => null);
584
+ throw new Error(body?.error || `Login options failed: ${optRes.status} from ${optUrl}`);
585
+ }
586
+ const { options, nonce } = await optRes.json();
587
+ const authentication = await startAuthentication({ optionsJSON: options });
588
+ const verifyUrl = `${prefix}/login/finish`;
589
+ const verifyRes = await fetch(verifyUrl, {
590
+ method: "POST",
591
+ credentials: "include",
592
+ headers: { "Content-Type": "application/json" },
593
+ body: JSON.stringify({ nonce, response: authentication })
594
+ });
595
+ if (!verifyRes.ok) {
596
+ const data = await verifyRes.json().catch(() => null);
597
+ throw new Error(data?.error || `Login verify failed: ${verifyRes.status} from ${verifyUrl}`);
598
+ }
599
+ return verifyRes.json();
600
+ } catch (err) {
601
+ throw classifyLoginError(err);
602
+ }
603
+ }
604
+ async function signin(opts = {}) {
605
+ let status;
606
+ try {
607
+ status = await getAuthStatus(opts);
608
+ } catch (err) {
609
+ throw new IsitmeError(
610
+ "NETWORK_ERROR",
611
+ "Could not check auth status. Make sure isitme middleware is running or the cloud API is reachable.",
612
+ err instanceof Error ? err : void 0
613
+ );
614
+ }
615
+ if (status.setupComplete) {
616
+ const result = await login(opts);
617
+ return { verified: result.verified, action: "login" };
618
+ } else {
619
+ const result = await register(opts);
620
+ return { verified: result.verified, action: "register", authData: result.authData };
621
+ }
622
+ }
623
+ async function isItMe(opts = {}) {
624
+ if (opts.api) return cloudIsItMe();
625
+ const prefix = opts.prefix || DEFAULT_PREFIX;
626
+ const res = await fetch(`${prefix}/status`, { credentials: "include" });
627
+ if (!res.ok) return null;
628
+ const status = await res.json();
629
+ if (status.isAuthenticated) {
630
+ return { authenticated: true, registered: true };
631
+ }
632
+ return null;
633
+ }
634
+ async function logout(opts = {}) {
635
+ if (opts.api) {
636
+ clearCloudSession();
637
+ return;
638
+ }
639
+ const prefix = opts.prefix || DEFAULT_PREFIX;
640
+ const url = `${prefix}/logout`;
641
+ try {
642
+ const res = await fetch(url, { method: "POST", credentials: "include" });
643
+ if (!res.ok) {
644
+ throw new Error(`Logout failed: ${res.status} from ${url}`);
645
+ }
646
+ } catch (err) {
647
+ throw new IsitmeError(
648
+ "NETWORK_ERROR",
649
+ "Could not reach the auth server to log out.",
650
+ err instanceof Error ? err : void 0
651
+ );
652
+ }
653
+ }
396
654
  export {
655
+ IsitmeError,
397
656
  getAuthStatus,
657
+ isItMe,
398
658
  login,
399
- register
659
+ logout,
660
+ register,
661
+ signin
400
662
  };
@@ -0,0 +1,100 @@
1
+ import { Response, Request, Router } from 'express';
2
+
3
+ interface StoredCredential {
4
+ id: string;
5
+ publicKey: string;
6
+ counter: number;
7
+ transports?: AuthenticatorTransport[];
8
+ }
9
+ interface StoredUser {
10
+ id: string;
11
+ username: string;
12
+ }
13
+ interface StorageAdapter {
14
+ isSetupComplete(): boolean | Promise<boolean>;
15
+ getUser(): StoredUser | null | Promise<StoredUser | null>;
16
+ getCredentials(): StoredCredential[] | Promise<StoredCredential[]>;
17
+ getCredentialById(id: string): StoredCredential | undefined | Promise<StoredCredential | undefined>;
18
+ saveUserAndCredential(user: StoredUser, credential: StoredCredential): void | Promise<void>;
19
+ updateCredentialCounter(id: string, counter: number): void | Promise<void>;
20
+ getAuthDataJson(): string | Promise<string>;
21
+ }
22
+ interface PageOptions {
23
+ title?: string;
24
+ heading?: string;
25
+ brandColor?: string;
26
+ logoUrl?: string;
27
+ description?: string;
28
+ brandName?: string;
29
+ }
30
+ interface IsitmeOptions {
31
+ /** JWT signing secret. If omitted, a random secret is generated (sessions won't survive restarts). */
32
+ sessionSecret?: string;
33
+ /** File path for JSON storage, `false` for in-memory, or a custom StorageAdapter. Default: `"./auth.json"` */
34
+ storage?: string | false | StorageAdapter;
35
+ /** Route prefix for auth endpoints. Default: `"/_isitme"` */
36
+ prefix?: string;
37
+ /** Session cookie name. Default: `"isitme_session"` */
38
+ cookieName?: string;
39
+ /** Session max age in seconds. Default: `86400` (1 day) */
40
+ sessionMaxAge?: number;
41
+ /** WebAuthn Relying Party ID. Default: auto from hostname */
42
+ rpId?: string;
43
+ /** WebAuthn RP display name. Default: `"isitme"` */
44
+ rpName?: string;
45
+ /** WebAuthn origin. Default: auto from request */
46
+ origin?: string;
47
+ /** Extra paths to skip auth for (in addition to auth routes and static files). */
48
+ publicPaths?: string[];
49
+ /** Login page config. Set to `false` to disable the built-in page. */
50
+ loginPage?: false | PageOptions;
51
+ /** Callback after first passkey registration. Receives the auth data JSON string. */
52
+ onRegister?: (authData: string) => void;
53
+ /** Set cookie Secure flag. Default: `true`. Express adapter passes `process.env.NODE_ENV === "production"`. */
54
+ secure?: boolean;
55
+ /** Skip auth entirely on localhost (127.0.0.1 / localhost). Only for development. Default: `false` */
56
+ bypassAuthOnLocalhost?: boolean;
57
+ }
58
+ interface RequestInfo {
59
+ hostname: string;
60
+ protocol: string;
61
+ host: string;
62
+ }
63
+ interface CookieConfig {
64
+ name: string;
65
+ maxAge: number;
66
+ httpOnly: boolean;
67
+ secure: boolean;
68
+ sameSite: "strict" | "lax" | "none";
69
+ path: string;
70
+ }
71
+ interface SetCookieAction {
72
+ name: string;
73
+ value: string;
74
+ options: CookieConfig;
75
+ }
76
+ interface ClearCookieAction {
77
+ name: string;
78
+ options: CookieConfig;
79
+ }
80
+ interface HandlerResult {
81
+ status: number;
82
+ body?: unknown;
83
+ html?: string;
84
+ redirect?: string;
85
+ setCookie?: SetCookieAction;
86
+ clearCookie?: ClearCookieAction;
87
+ }
88
+
89
+ declare global {
90
+ namespace Express {
91
+ interface Request {
92
+ isAuthenticated?: boolean;
93
+ }
94
+ }
95
+ }
96
+ declare function getRequestInfo(req: Request): RequestInfo;
97
+ declare function applyResult(res: Response, result: HandlerResult): void;
98
+ declare function isitme(options?: IsitmeOptions): Router;
99
+
100
+ export { applyResult, getRequestInfo, isitme };