@mrgnw/anahtar 0.0.27 → 0.0.29-diagnostic.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/README.md +7 -1
- package/dist/components/AuthPill.svelte +8 -5
- package/dist/components/AuthPill.svelte.d.ts +2 -0
- package/dist/passkey.js +20 -5
- package/package.json +15 -9
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# anahtar: incredibly simple & secure auth with passkeys and email+otp fallback
|
|
2
2
|
|
|
3
3
|
> **Pre-alpha.** API may change between releases.
|
|
4
4
|
|
|
@@ -13,6 +13,12 @@ Auth for SvelteKit. Email+OTP + optional passkeys.
|
|
|
13
13
|
3. First login -> passkey registration prompt
|
|
14
14
|
4. Future logins -> passkey autofill, OTP fallback
|
|
15
15
|
|
|
16
|
+
## Roadmap: Proof-of-concept: everything can change
|
|
17
|
+
The goal is simplicity, we will likely stay with passkeys and email+OTP only.
|
|
18
|
+
|
|
19
|
+
- we may change the implemenation.
|
|
20
|
+
- we will likely split the UI elements from the API, making it easier to run headless, just use the UI, or use both.
|
|
21
|
+
|
|
16
22
|
## Quick start
|
|
17
23
|
|
|
18
24
|
```sh
|
|
@@ -25,6 +25,8 @@ interface Props {
|
|
|
25
25
|
onStepChange?: (step: 'email' | 'otp' | 'authenticated') => void;
|
|
26
26
|
/** Extra inline icons rendered before the sign-out button when authenticated. */
|
|
27
27
|
actions?: Snippet;
|
|
28
|
+
/** Show `·` dot separators between pill segments. Defaults to false (flex gap only). */
|
|
29
|
+
separators?: boolean;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
let {
|
|
@@ -39,6 +41,7 @@ let {
|
|
|
39
41
|
getPasskeys,
|
|
40
42
|
onStepChange,
|
|
41
43
|
actions,
|
|
44
|
+
separators = false,
|
|
42
45
|
}: Props = $props();
|
|
43
46
|
|
|
44
47
|
let expanded = $state(false);
|
|
@@ -348,7 +351,7 @@ async function removePasskey(id: string) {
|
|
|
348
351
|
</button>
|
|
349
352
|
{:else if isAuthenticated}
|
|
350
353
|
<span class="anahtar-pill-email">{user?.email}</span>
|
|
351
|
-
<span class="anahtar-pill-sep">·</span>
|
|
354
|
+
{#if separators}<span class="anahtar-pill-sep">·</span>{/if}
|
|
352
355
|
{#if getPasskeys}
|
|
353
356
|
<button
|
|
354
357
|
class="anahtar-pill-icon"
|
|
@@ -361,11 +364,11 @@ async function removePasskey(id: string) {
|
|
|
361
364
|
<circle cx="7.5" cy="15.5" r="5.5"/><path d="m11.5 12 4-4"/><path d="m15 7 2 2"/><path d="m17.5 4.5 2 2"/>
|
|
362
365
|
</svg>
|
|
363
366
|
</button>
|
|
364
|
-
<span class="anahtar-pill-sep">·</span>
|
|
367
|
+
{#if separators}<span class="anahtar-pill-sep">·</span>{/if}
|
|
365
368
|
{/if}
|
|
366
369
|
{#if actions}
|
|
367
370
|
{@render actions()}
|
|
368
|
-
<span class="anahtar-pill-sep">·</span>
|
|
371
|
+
{#if separators}<span class="anahtar-pill-sep">·</span>{/if}
|
|
369
372
|
{/if}
|
|
370
373
|
<button class="anahtar-pill-icon anahtar-pill-signout" onclick={handleSignOut} title="Sign out" disabled={loading}>
|
|
371
374
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -373,7 +376,7 @@ async function removePasskey(id: string) {
|
|
|
373
376
|
</svg>
|
|
374
377
|
</button>
|
|
375
378
|
{#if compact}
|
|
376
|
-
<span class="anahtar-pill-sep">·</span>
|
|
379
|
+
{#if separators}<span class="anahtar-pill-sep">·</span>{/if}
|
|
377
380
|
<button class="anahtar-pill-icon" onclick={() => { expanded = false; showPasskeys = false; }} title="Collapse">
|
|
378
381
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
379
382
|
<line x1="18" x2="6" y1="6" y2="18"/><line x1="6" x2="18" y1="6" y2="18"/>
|
|
@@ -383,7 +386,7 @@ async function removePasskey(id: string) {
|
|
|
383
386
|
|
|
384
387
|
{:else if otpStep}
|
|
385
388
|
<span class="anahtar-pill-otp-label">{email}</span>
|
|
386
|
-
<span class="anahtar-pill-sep">·</span>
|
|
389
|
+
{#if separators}<span class="anahtar-pill-sep">·</span>{/if}
|
|
387
390
|
<div class="anahtar-pill-otp-boxes">
|
|
388
391
|
{#each otpDigits as _, i}
|
|
389
392
|
<input
|
|
@@ -21,6 +21,8 @@ interface Props {
|
|
|
21
21
|
onStepChange?: (step: 'email' | 'otp' | 'authenticated') => void;
|
|
22
22
|
/** Extra inline icons rendered before the sign-out button when authenticated. */
|
|
23
23
|
actions?: Snippet;
|
|
24
|
+
/** Show `·` dot separators between pill segments. Defaults to false (flex gap only). */
|
|
25
|
+
separators?: boolean;
|
|
24
26
|
}
|
|
25
27
|
declare const AuthPill: import("svelte").Component<Props, {}, "">;
|
|
26
28
|
type AuthPill = ReturnType<typeof AuthPill>;
|
package/dist/passkey.js
CHANGED
|
@@ -121,13 +121,25 @@ export async function generateAuthenticationChallenge(db, requestUrl, config) {
|
|
|
121
121
|
}
|
|
122
122
|
export async function verifyAuthenticationResponse(db, response, requestUrl, config) {
|
|
123
123
|
const { rpID, origin } = getWebAuthnConfig(requestUrl, config);
|
|
124
|
+
const idHint = `${response.id.slice(0, 8)}…`;
|
|
124
125
|
const passkey = await db.getPasskeyByCredentialId(response.id);
|
|
125
|
-
if (!passkey)
|
|
126
|
+
if (!passkey) {
|
|
127
|
+
console.warn(`[anahtar login-finish] no stored passkey for credentialId=${idHint} (rpID=${rpID})`);
|
|
126
128
|
return null;
|
|
127
|
-
|
|
129
|
+
}
|
|
130
|
+
let challenge;
|
|
131
|
+
try {
|
|
132
|
+
challenge = JSON.parse(Buffer.from(response.response.clientDataJSON, "base64url").toString()).challenge;
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
console.warn(`[anahtar login-finish] clientDataJSON parse failed: ${e}`);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
128
138
|
const stored = await db.consumeChallenge(challenge);
|
|
129
|
-
if (!stored)
|
|
139
|
+
if (!stored) {
|
|
140
|
+
console.warn(`[anahtar login-finish] challenge not found or expired (credentialId=${idHint})`);
|
|
130
141
|
return null;
|
|
142
|
+
}
|
|
131
143
|
try {
|
|
132
144
|
const verification = await verifyAuthResponse({
|
|
133
145
|
response,
|
|
@@ -144,14 +156,17 @@ export async function verifyAuthenticationResponse(db, response, requestUrl, con
|
|
|
144
156
|
: undefined,
|
|
145
157
|
},
|
|
146
158
|
});
|
|
147
|
-
if (!verification.verified)
|
|
159
|
+
if (!verification.verified) {
|
|
160
|
+
console.warn(`[anahtar login-finish] not verified (credentialId=${idHint}, expectedRPID=${rpID}, expectedOrigin=${origin})`);
|
|
148
161
|
return null;
|
|
162
|
+
}
|
|
149
163
|
await db.updatePasskeyCounter(passkey.id, verification.authenticationInfo.newCounter);
|
|
150
164
|
return {
|
|
151
165
|
user: { id: passkey.userId, email: passkey.email },
|
|
152
166
|
};
|
|
153
167
|
}
|
|
154
|
-
catch {
|
|
168
|
+
catch (e) {
|
|
169
|
+
console.warn(`[anahtar login-finish] verification threw (credentialId=${idHint}, expectedRPID=${rpID}, expectedOrigin=${origin}): ${e}`);
|
|
155
170
|
return null;
|
|
156
171
|
}
|
|
157
172
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrgnw/anahtar",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29-diagnostic.0",
|
|
4
4
|
"description": "Opinionated, reusable auth for SvelteKit. Email+OTP + passkeys.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
"registry": "https://registry.npmjs.org/",
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "svelte-package",
|
|
17
|
+
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
18
|
+
"test": "vitest run --config vitest.unit.ts && vitest run --config vitest.browser.ts",
|
|
19
|
+
"test:unit": "vitest run --config vitest.unit.ts",
|
|
20
|
+
"test:browser": "vitest run --config vitest.browser.ts",
|
|
21
|
+
"prepublishOnly": "pnpm build"
|
|
22
|
+
},
|
|
15
23
|
"svelte": "./dist/index.js",
|
|
16
24
|
"types": "./dist/index.d.ts",
|
|
17
25
|
"exports": {
|
|
@@ -60,6 +68,11 @@
|
|
|
60
68
|
"@oslojs/encoding": "^1.1.0",
|
|
61
69
|
"@simplewebauthn/server": "^13.2.2"
|
|
62
70
|
},
|
|
71
|
+
"pnpm": {
|
|
72
|
+
"onlyBuiltDependencies": [
|
|
73
|
+
"better-sqlite3"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
63
76
|
"devDependencies": {
|
|
64
77
|
"@simplewebauthn/browser": "^13.2.2",
|
|
65
78
|
"@sveltejs/kit": "^2.31.1",
|
|
@@ -75,12 +88,5 @@
|
|
|
75
88
|
"svelte-check": "^4.3.1",
|
|
76
89
|
"typescript": "^5.9.2",
|
|
77
90
|
"vitest": "^3.2.1"
|
|
78
|
-
},
|
|
79
|
-
"scripts": {
|
|
80
|
-
"build": "svelte-package",
|
|
81
|
-
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
82
|
-
"test": "vitest run --config vitest.unit.ts && vitest run --config vitest.browser.ts",
|
|
83
|
-
"test:unit": "vitest run --config vitest.unit.ts",
|
|
84
|
-
"test:browser": "vitest run --config vitest.browser.ts"
|
|
85
91
|
}
|
|
86
|
-
}
|
|
92
|
+
}
|