@mrgnw/anahtar 0.0.25 → 0.0.27

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.
@@ -2,7 +2,7 @@
2
2
  import { guessDeviceName } from '../device.js';
3
3
  import { resolveMessages, detectLocaleClient, type AuthMessages } from '../i18n/index.js';
4
4
  import PasskeyPrompt from './PasskeyPrompt.svelte';
5
- import { onMount } from 'svelte';
5
+ import { onMount, type Snippet } from 'svelte';
6
6
  import { slide } from 'svelte/transition';
7
7
 
8
8
  interface PasskeyInfo {
@@ -23,6 +23,8 @@ interface Props {
23
23
  onPasskeysChange?: () => void | Promise<void>;
24
24
  getPasskeys?: () => Promise<PasskeyInfo[]>;
25
25
  onStepChange?: (step: 'email' | 'otp' | 'authenticated') => void;
26
+ /** Extra inline icons rendered before the sign-out button when authenticated. */
27
+ actions?: Snippet;
26
28
  }
27
29
 
28
30
  let {
@@ -36,6 +38,7 @@ let {
36
38
  onPasskeysChange,
37
39
  getPasskeys,
38
40
  onStepChange,
41
+ actions,
39
42
  }: Props = $props();
40
43
 
41
44
  let expanded = $state(false);
@@ -360,6 +363,10 @@ async function removePasskey(id: string) {
360
363
  </button>
361
364
  <span class="anahtar-pill-sep">&middot;</span>
362
365
  {/if}
366
+ {#if actions}
367
+ {@render actions()}
368
+ <span class="anahtar-pill-sep">&middot;</span>
369
+ {/if}
363
370
  <button class="anahtar-pill-icon anahtar-pill-signout" onclick={handleSignOut} title="Sign out" disabled={loading}>
364
371
  <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">
365
372
  <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>
@@ -1,4 +1,5 @@
1
1
  import { type AuthMessages } from '../i18n/index.js';
2
+ import { type Snippet } from 'svelte';
2
3
  interface PasskeyInfo {
3
4
  id: string;
4
5
  credentialId?: string;
@@ -18,6 +19,8 @@ interface Props {
18
19
  onPasskeysChange?: () => void | Promise<void>;
19
20
  getPasskeys?: () => Promise<PasskeyInfo[]>;
20
21
  onStepChange?: (step: 'email' | 'otp' | 'authenticated') => void;
22
+ /** Extra inline icons rendered before the sign-out button when authenticated. */
23
+ actions?: Snippet;
21
24
  }
22
25
  declare const AuthPill: import("svelte").Component<Props, {}, "">;
23
26
  type AuthPill = ReturnType<typeof AuthPill>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrgnw/anahtar",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "Opinionated, reusable auth for SvelteKit. Email+OTP + passkeys.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,10 +32,6 @@
32
32
  "types": "./dist/db/d1.d.ts",
33
33
  "default": "./dist/db/d1.js"
34
34
  },
35
- "./tinybase": {
36
- "types": "./dist/db/tinybase.d.ts",
37
- "default": "./dist/db/tinybase.js"
38
- },
39
35
  "./components": {
40
36
  "types": "./dist/components/index.d.ts",
41
37
  "svelte": "./dist/components/index.js",
@@ -49,8 +45,7 @@
49
45
  "peerDependencies": {
50
46
  "@simplewebauthn/browser": "^13.0.0",
51
47
  "@sveltejs/kit": "^2.0.0",
52
- "svelte": "^5.0.0",
53
- "tinybase": "^8.0.0"
48
+ "svelte": "^5.0.0"
54
49
  },
55
50
  "peerDependenciesMeta": {
56
51
  "svelte": {
@@ -58,9 +53,6 @@
58
53
  },
59
54
  "@simplewebauthn/browser": {
60
55
  "optional": true
61
- },
62
- "tinybase": {
63
- "optional": true
64
56
  }
65
57
  },
66
58
  "dependencies": {
@@ -78,7 +70,6 @@
78
70
  "@testing-library/user-event": "^14.6.1",
79
71
  "@types/better-sqlite3": "^7.6.13",
80
72
  "better-sqlite3": "^12.6.2",
81
- "tinybase": "^8.0.1",
82
73
  "happy-dom": "^20.6.1",
83
74
  "svelte": "^5.38.1",
84
75
  "svelte-check": "^4.3.1",
@@ -1,7 +0,0 @@
1
- import type { Store } from 'tinybase';
2
- import type { AuthDB } from '../types.js';
3
- interface TinybaseAdapterOptions {
4
- tablePrefix?: string;
5
- }
6
- export declare function tinybaseAdapter(store: Store, options?: TinybaseAdapterOptions): AuthDB;
7
- export {};
@@ -1,200 +0,0 @@
1
- import { createIndexes } from 'tinybase/indexes';
2
- function encodeBytes(bytes) {
3
- return btoa(String.fromCharCode(...bytes));
4
- }
5
- function decodeBytes(str) {
6
- return Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
7
- }
8
- export function tinybaseAdapter(store, options = {}) {
9
- const p = options.tablePrefix ?? 'auth_';
10
- const t = {
11
- users: `${p}users`,
12
- sessions: `${p}sessions`,
13
- otpCodes: `${p}otp_codes`,
14
- passkeys: `${p}passkeys`,
15
- challenges: `${p}challenges`
16
- };
17
- const indexes = createIndexes(store);
18
- return {
19
- init() {
20
- indexes.setIndexDefinition('email_to_user', t.users, 'email');
21
- indexes.setIndexDefinition('email_to_otp', t.otpCodes, 'email');
22
- indexes.setIndexDefinition('credential_to_passkey', t.passkeys, 'credential_id');
23
- indexes.setIndexDefinition('user_to_passkey', t.passkeys, 'user_id');
24
- },
25
- getUserByEmail(email) {
26
- const ids = indexes.getSliceRowIds('email_to_user', email);
27
- if (!ids.length)
28
- return null;
29
- const row = store.getRow(t.users, ids[0]);
30
- return {
31
- id: ids[0],
32
- email: row.email,
33
- skipPasskeyPrompt: row.skip_passkey_prompt === 1,
34
- createdAt: row.created_at
35
- };
36
- },
37
- createUser(email) {
38
- const id = crypto.randomUUID();
39
- const createdAt = Math.floor(Date.now() / 1000);
40
- store.setRow(t.users, id, {
41
- email,
42
- skip_passkey_prompt: 0,
43
- created_at: createdAt
44
- });
45
- return { id, email, skipPasskeyPrompt: false, createdAt };
46
- },
47
- setSkipPasskeyPrompt(userId, skip) {
48
- store.setCell(t.users, userId, 'skip_passkey_prompt', skip ? 1 : 0);
49
- },
50
- createSession(tokenHash, userId, expiresAt) {
51
- store.setRow(t.sessions, tokenHash, {
52
- user_id: userId,
53
- expires_at: expiresAt,
54
- created_at: Math.floor(Date.now() / 1000)
55
- });
56
- },
57
- getSession(tokenHash) {
58
- const session = store.getRow(t.sessions, tokenHash);
59
- if (!session.user_id)
60
- return null;
61
- const userId = session.user_id;
62
- const user = store.getRow(t.users, userId);
63
- if (!user.email)
64
- return null;
65
- return {
66
- id: tokenHash,
67
- userId,
68
- expiresAt: session.expires_at,
69
- email: user.email
70
- };
71
- },
72
- deleteSession(tokenHash) {
73
- store.delRow(t.sessions, tokenHash);
74
- },
75
- storeOTP(email, id, code, expiresAt) {
76
- store.setRow(t.otpCodes, id, {
77
- email,
78
- code,
79
- attempts: 0,
80
- expires_at: expiresAt,
81
- created_at: Date.now()
82
- });
83
- },
84
- getLatestOTP(email) {
85
- const ids = indexes.getSliceRowIds('email_to_otp', email);
86
- if (!ids.length)
87
- return null;
88
- let latestId = ids[0];
89
- let latestCreatedAt = store.getCell(t.otpCodes, latestId, 'created_at');
90
- for (let i = 1; i < ids.length; i++) {
91
- const createdAt = store.getCell(t.otpCodes, ids[i], 'created_at');
92
- if (createdAt > latestCreatedAt) {
93
- latestCreatedAt = createdAt;
94
- latestId = ids[i];
95
- }
96
- }
97
- const row = store.getRow(t.otpCodes, latestId);
98
- return {
99
- id: latestId,
100
- email: row.email,
101
- code: row.code,
102
- attempts: row.attempts,
103
- expiresAt: row.expires_at
104
- };
105
- },
106
- updateOTPAttempts(id, attempts) {
107
- store.setCell(t.otpCodes, id, 'attempts', attempts);
108
- },
109
- deleteOTP(id) {
110
- store.delRow(t.otpCodes, id);
111
- },
112
- deleteOTPsForEmail(email) {
113
- const ids = indexes.getSliceRowIds('email_to_otp', email);
114
- for (const id of ids) {
115
- store.delRow(t.otpCodes, id);
116
- }
117
- },
118
- storeChallenge(challenge, userId, expiresAt) {
119
- // Clean up expired challenges
120
- const now = Date.now();
121
- store.forEachRow(t.challenges, (rowId) => {
122
- const exp = store.getCell(t.challenges, rowId, 'expires_at');
123
- if (exp < now)
124
- store.delRow(t.challenges, rowId);
125
- });
126
- store.setRow(t.challenges, challenge, {
127
- user_id: userId,
128
- expires_at: expiresAt,
129
- created_at: Math.floor(Date.now() / 1000)
130
- });
131
- },
132
- consumeChallenge(challenge) {
133
- const row = store.getRow(t.challenges, challenge);
134
- if (!row.user_id)
135
- return null;
136
- store.delRow(t.challenges, challenge);
137
- if (row.expires_at < Date.now())
138
- return null;
139
- return { userId: row.user_id };
140
- },
141
- getPasskeyByCredentialId(credentialId) {
142
- const ids = indexes.getSliceRowIds('credential_to_passkey', credentialId);
143
- if (!ids.length)
144
- return null;
145
- const id = ids[0];
146
- const row = store.getRow(t.passkeys, id);
147
- const userId = row.user_id;
148
- const user = store.getRow(t.users, userId);
149
- if (!user.email)
150
- return null;
151
- return {
152
- id,
153
- userId,
154
- credentialId: row.credential_id,
155
- publicKey: decodeBytes(row.public_key),
156
- counter: row.counter,
157
- transports: row.transports ?? null,
158
- name: row.name ?? null,
159
- createdAt: row.created_at,
160
- email: user.email
161
- };
162
- },
163
- getUserPasskeys(userId) {
164
- const ids = indexes.getSliceRowIds('user_to_passkey', userId);
165
- return ids.map((id) => {
166
- const row = store.getRow(t.passkeys, id);
167
- return {
168
- id,
169
- credentialId: row.credential_id,
170
- publicKey: decodeBytes(row.public_key),
171
- counter: row.counter,
172
- transports: row.transports ?? null,
173
- name: row.name ?? null,
174
- createdAt: row.created_at
175
- };
176
- });
177
- },
178
- storePasskey(passkey) {
179
- store.setRow(t.passkeys, passkey.id, {
180
- user_id: passkey.userId,
181
- credential_id: passkey.credentialId,
182
- public_key: encodeBytes(passkey.publicKey),
183
- counter: passkey.counter,
184
- ...(passkey.transports !== null && { transports: passkey.transports }),
185
- ...(passkey.name !== null && { name: passkey.name }),
186
- created_at: Math.floor(Date.now() / 1000)
187
- });
188
- },
189
- updatePasskeyCounter(id, counter) {
190
- store.setCell(t.passkeys, id, 'counter', counter);
191
- },
192
- deletePasskey(id, userId) {
193
- const row = store.getRow(t.passkeys, id);
194
- if (!row.user_id || row.user_id !== userId)
195
- return false;
196
- store.delRow(t.passkeys, id);
197
- return true;
198
- }
199
- };
200
- }