passkey-magic 0.1.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 +302 -0
- package/dist/adapters/memory.d.mts +10 -0
- package/dist/adapters/memory.mjs +142 -0
- package/dist/adapters/unstorage.d.mts +30 -0
- package/dist/adapters/unstorage.mjs +238 -0
- package/dist/better-auth/client.d.mts +9 -0
- package/dist/better-auth/client.mjs +7 -0
- package/dist/better-auth/index.d.mts +2 -0
- package/dist/better-auth/index.mjs +4044 -0
- package/dist/client/index.d.mts +167 -0
- package/dist/client/index.mjs +166 -0
- package/dist/crypto-KHRNe6EL.mjs +45 -0
- package/dist/index-BoHvgaqz.d.mts +2816 -0
- package/dist/index-Cqqpr_uS.d.mts +176 -0
- package/dist/nitro/index.d.mts +89 -0
- package/dist/nitro/index.mjs +117 -0
- package/dist/server/index.d.mts +3 -0
- package/dist/server/index.mjs +3 -0
- package/dist/server-BXkm8lU0.mjs +823 -0
- package/dist/types-BjM1f6uu.d.mts +249 -0
- package/package.json +74 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { a as AuthResult, c as Credential, d as QRSessionStatus, f as RegistrationResponseJSON, h as User, l as EmailAdapter, n as AuthEventHandler, o as AuthenticationResponseJSON, p as Session, r as AuthEventMap, t as AuthConfig, u as MagicLinkMethods } from "./types-BjM1f6uu.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/server/index.d.ts
|
|
4
|
+
/** Methods available on every `createAuth()` instance regardless of config. */
|
|
5
|
+
interface BaseAuthMethods {
|
|
6
|
+
/**
|
|
7
|
+
* Generate WebAuthn registration options. Send the `options` to the browser.
|
|
8
|
+
* If `userId` is omitted, a new ID is generated.
|
|
9
|
+
*/
|
|
10
|
+
generateRegistrationOptions(params: {
|
|
11
|
+
userId?: string;
|
|
12
|
+
email?: string;
|
|
13
|
+
userName?: string;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
options: PublicKeyCredentialCreationOptionsJSON;
|
|
16
|
+
userId: string;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Verify a WebAuthn registration response and create a session.
|
|
20
|
+
* Creates a new user if the `userId` doesn't exist yet.
|
|
21
|
+
*/
|
|
22
|
+
verifyRegistration(params: {
|
|
23
|
+
userId: string;
|
|
24
|
+
response: RegistrationResponseJSON;
|
|
25
|
+
}): Promise<AuthResult & {
|
|
26
|
+
method: 'passkey';
|
|
27
|
+
credential: Credential;
|
|
28
|
+
}>;
|
|
29
|
+
/** Generate WebAuthn authentication options. Send the `options` to the browser. */
|
|
30
|
+
generateAuthenticationOptions(params?: {
|
|
31
|
+
userId?: string;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
options: PublicKeyCredentialRequestOptionsJSON;
|
|
34
|
+
}>;
|
|
35
|
+
/** Verify a WebAuthn authentication response and create a session. */
|
|
36
|
+
verifyAuthentication(params: {
|
|
37
|
+
response: AuthenticationResponseJSON;
|
|
38
|
+
}): Promise<AuthResult & {
|
|
39
|
+
method: 'passkey';
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Generate registration options for adding a passkey to an existing account.
|
|
43
|
+
* Excludes the user's existing credentials.
|
|
44
|
+
*/
|
|
45
|
+
addPasskey(params: {
|
|
46
|
+
userId: string;
|
|
47
|
+
userName?: string;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
options: PublicKeyCredentialCreationOptionsJSON;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Verify and store a new passkey for an existing user.
|
|
53
|
+
* Does not create a session — the user is already authenticated.
|
|
54
|
+
*/
|
|
55
|
+
verifyAddPasskey(params: {
|
|
56
|
+
userId: string;
|
|
57
|
+
response: RegistrationResponseJSON;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
credential: Credential;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Update a credential's metadata (e.g. label).
|
|
63
|
+
* @throws If the credential doesn't exist.
|
|
64
|
+
*/
|
|
65
|
+
updateCredential(params: {
|
|
66
|
+
credentialId: string;
|
|
67
|
+
label: string;
|
|
68
|
+
}): Promise<void>;
|
|
69
|
+
/** Remove a passkey. Verifies the credential exists and cleans up. */
|
|
70
|
+
removeCredential(credentialId: string): Promise<void>;
|
|
71
|
+
/** List all passkeys for a user. */
|
|
72
|
+
getUserCredentials(userId: string): Promise<Credential[]>;
|
|
73
|
+
/** Create a new QR login session. Returns a `sessionId` to encode in a QR code. */
|
|
74
|
+
createQRSession(): Promise<{
|
|
75
|
+
sessionId: string;
|
|
76
|
+
}>;
|
|
77
|
+
/** Poll the status of a QR session. */
|
|
78
|
+
getQRSessionStatus(sessionId: string): Promise<QRSessionStatus>;
|
|
79
|
+
/** Mark a QR session as scanned (called from the phone after scanning). */
|
|
80
|
+
markQRSessionScanned(sessionId: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Complete a QR session — authenticates on the phone, creates a session
|
|
83
|
+
* for the desktop. Called from the phone after passkey auth.
|
|
84
|
+
*/
|
|
85
|
+
completeQRSession(params: {
|
|
86
|
+
sessionId: string;
|
|
87
|
+
response: AuthenticationResponseJSON;
|
|
88
|
+
}): Promise<AuthResult & {
|
|
89
|
+
method: 'qr';
|
|
90
|
+
}>;
|
|
91
|
+
/** Validate a session token. Returns user + session if valid, null if expired/invalid. */
|
|
92
|
+
validateSession(token: string): Promise<{
|
|
93
|
+
user: User;
|
|
94
|
+
session: Session;
|
|
95
|
+
} | null>;
|
|
96
|
+
/** List all active sessions for a user. */
|
|
97
|
+
getUserSessions(userId: string): Promise<Session[]>;
|
|
98
|
+
/** Revoke a single session by token. */
|
|
99
|
+
revokeSession(token: string): Promise<void>;
|
|
100
|
+
/** Revoke a single session by ID. */
|
|
101
|
+
revokeSessionById(sessionId: string): Promise<void>;
|
|
102
|
+
/** Revoke all sessions for a user. */
|
|
103
|
+
revokeAllSessions(userId: string): Promise<void>;
|
|
104
|
+
/** Get a user by ID. Returns `null` if not found. */
|
|
105
|
+
getUser(userId: string): Promise<User | null>;
|
|
106
|
+
/**
|
|
107
|
+
* Check if an email is available (not already linked to a user).
|
|
108
|
+
* Useful before showing a registration form.
|
|
109
|
+
*/
|
|
110
|
+
isEmailAvailable(email: string): Promise<boolean>;
|
|
111
|
+
/**
|
|
112
|
+
* Link an email to a user account.
|
|
113
|
+
* @throws If the email is already linked to another user.
|
|
114
|
+
* @throws If the email format is invalid.
|
|
115
|
+
*/
|
|
116
|
+
linkEmail(params: {
|
|
117
|
+
userId: string;
|
|
118
|
+
email: string;
|
|
119
|
+
}): Promise<{
|
|
120
|
+
user: User;
|
|
121
|
+
}>;
|
|
122
|
+
/**
|
|
123
|
+
* Unlink the email from a user account.
|
|
124
|
+
* @throws If the user has no email linked.
|
|
125
|
+
*/
|
|
126
|
+
unlinkEmail(params: {
|
|
127
|
+
userId: string;
|
|
128
|
+
}): Promise<{
|
|
129
|
+
user: User;
|
|
130
|
+
}>;
|
|
131
|
+
/**
|
|
132
|
+
* Delete a user account and all associated data (credentials, sessions).
|
|
133
|
+
* This action is irreversible.
|
|
134
|
+
*/
|
|
135
|
+
deleteAccount(userId: string): Promise<void>;
|
|
136
|
+
/** Subscribe to typed auth events. Returns an unsubscribe function. */
|
|
137
|
+
on<K extends keyof AuthEventMap>(event: K, handler: AuthEventHandler<K>): () => void;
|
|
138
|
+
/**
|
|
139
|
+
* Create a Web Standard `Request → Response` handler for all auth routes.
|
|
140
|
+
* Works with any framework that speaks `Request`/`Response` (Bun, Deno, CF Workers, Hono, etc.).
|
|
141
|
+
*/
|
|
142
|
+
createHandler(opts?: {
|
|
143
|
+
pathPrefix?: string;
|
|
144
|
+
}): (request: Request) => Promise<Response>;
|
|
145
|
+
}
|
|
146
|
+
/** Full auth instance type. Magic link methods are present only when `EmailAdapter` is configured. */
|
|
147
|
+
type AuthInstance<TEmail> = BaseAuthMethods & (TEmail extends EmailAdapter ? MagicLinkMethods : unknown);
|
|
148
|
+
/**
|
|
149
|
+
* Create a passkey-magic auth instance.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const auth = createAuth({
|
|
154
|
+
* rpName: 'My App',
|
|
155
|
+
* rpID: 'example.com',
|
|
156
|
+
* origin: 'https://example.com',
|
|
157
|
+
* storage: memoryAdapter(),
|
|
158
|
+
* })
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* @example With magic link fallback:
|
|
162
|
+
* ```ts
|
|
163
|
+
* const auth = createAuth({
|
|
164
|
+
* rpName: 'My App',
|
|
165
|
+
* rpID: 'example.com',
|
|
166
|
+
* origin: 'https://example.com',
|
|
167
|
+
* storage: memoryAdapter(),
|
|
168
|
+
* email: myEmailAdapter,
|
|
169
|
+
* magicLinkURL: 'https://example.com/auth/verify',
|
|
170
|
+
* })
|
|
171
|
+
* auth.sendMagicLink({ email: 'user@example.com' }) // ✓ type-safe
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
declare function createAuth<TEmail extends EmailAdapter | undefined = undefined>(config: AuthConfig<TEmail>): AuthInstance<TEmail>;
|
|
175
|
+
//#endregion
|
|
176
|
+
export { BaseAuthMethods as n, createAuth as r, AuthInstance as t };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { i as AuthHooks, l as EmailAdapter, m as StorageAdapter } from "../types-BjM1f6uu.mjs";
|
|
2
|
+
import { n as BaseAuthMethods, t as AuthInstance } from "../index-Cqqpr_uS.mjs";
|
|
3
|
+
import { Storage } from "unstorage";
|
|
4
|
+
|
|
5
|
+
//#region src/nitro/plugin.d.ts
|
|
6
|
+
interface PasskeyMagicNitroOptions<TEmail extends EmailAdapter | undefined = undefined> {
|
|
7
|
+
/** Relying party name shown to users during passkey prompts */
|
|
8
|
+
rpName: string;
|
|
9
|
+
/** Relying party ID — typically the domain (e.g. "example.com") */
|
|
10
|
+
rpID: string;
|
|
11
|
+
/** Expected origin(s) for WebAuthn verification */
|
|
12
|
+
origin: string | string[];
|
|
13
|
+
/**
|
|
14
|
+
* Storage for auth data. Accepts either:
|
|
15
|
+
* - An unstorage `Storage` instance (e.g. from `useStorage()`) — automatically wrapped
|
|
16
|
+
* - A passkey-magic `StorageAdapter` for full control
|
|
17
|
+
*/
|
|
18
|
+
storage?: Storage | StorageAdapter;
|
|
19
|
+
/** Optional email adapter to enable magic link fallback */
|
|
20
|
+
email?: TEmail;
|
|
21
|
+
/** Base URL for magic link verification (required if email is provided) */
|
|
22
|
+
magicLinkURL?: string;
|
|
23
|
+
/** Route prefix for the auth handler. Defaults to "/auth" */
|
|
24
|
+
pathPrefix?: string;
|
|
25
|
+
/** unstorage key prefix for all auth data. Defaults to "auth" */
|
|
26
|
+
storageBase?: string;
|
|
27
|
+
/** Session TTL in ms. Defaults to 7 days */
|
|
28
|
+
sessionTTL?: number;
|
|
29
|
+
/** Challenge TTL in ms. Defaults to 60 seconds */
|
|
30
|
+
challengeTTL?: number;
|
|
31
|
+
/** Magic link TTL in ms. Defaults to 15 minutes */
|
|
32
|
+
magicLinkTTL?: number;
|
|
33
|
+
/** QR session TTL in ms. Defaults to 5 minutes */
|
|
34
|
+
qrSessionTTL?: number;
|
|
35
|
+
/** Custom ID generator */
|
|
36
|
+
generateId?: () => string;
|
|
37
|
+
/** Lifecycle hooks */
|
|
38
|
+
hooks?: AuthHooks;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get the auth instance created by the Nitro plugin.
|
|
42
|
+
* Use this in route handlers to access auth methods.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { useAuth } from 'passkey-magic/nitro'
|
|
47
|
+
*
|
|
48
|
+
* export default defineHandler(async (event) => {
|
|
49
|
+
* const auth = useAuth()
|
|
50
|
+
* const session = await auth.validateSession(token)
|
|
51
|
+
* })
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare function useAuth<TEmail extends EmailAdapter | undefined = undefined>(): AuthInstance<TEmail>;
|
|
55
|
+
/**
|
|
56
|
+
* Create a passkey-magic Nitro plugin.
|
|
57
|
+
*
|
|
58
|
+
* Registers auth routes under the configured path prefix and makes
|
|
59
|
+
* the auth instance available via `useAuth()` in route handlers.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* // plugins/auth.ts
|
|
64
|
+
* import { definePlugin } from 'nitro'
|
|
65
|
+
* import { useStorage } from 'nitro/storage'
|
|
66
|
+
* import { passkeyMagic } from 'passkey-magic/nitro'
|
|
67
|
+
*
|
|
68
|
+
* export default definePlugin((nitroApp) => {
|
|
69
|
+
* passkeyMagic({
|
|
70
|
+
* rpName: 'My App',
|
|
71
|
+
* rpID: 'example.com',
|
|
72
|
+
* origin: 'https://example.com',
|
|
73
|
+
* storage: useStorage(),
|
|
74
|
+
* }).setup(nitroApp)
|
|
75
|
+
* })
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function passkeyMagic<TEmail extends EmailAdapter | undefined = undefined>(options: PasskeyMagicNitroOptions<TEmail>): {
|
|
79
|
+
setup: (nitroApp: NitroApp) => BaseAuthMethods;
|
|
80
|
+
};
|
|
81
|
+
interface NitroApp {
|
|
82
|
+
hooks: {
|
|
83
|
+
hook: (event: string, handler: (...args: any[]) => any) => () => void;
|
|
84
|
+
};
|
|
85
|
+
h3?: any;
|
|
86
|
+
fetch?: any;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { type PasskeyMagicNitroOptions, passkeyMagic, useAuth };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import "../crypto-KHRNe6EL.mjs";
|
|
2
|
+
import { t as createAuth } from "../server-BXkm8lU0.mjs";
|
|
3
|
+
import { unstorageAdapter } from "../adapters/unstorage.mjs";
|
|
4
|
+
//#region src/nitro/plugin.ts
|
|
5
|
+
let _authInstance = null;
|
|
6
|
+
/**
|
|
7
|
+
* Get the auth instance created by the Nitro plugin.
|
|
8
|
+
* Use this in route handlers to access auth methods.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { useAuth } from 'passkey-magic/nitro'
|
|
13
|
+
*
|
|
14
|
+
* export default defineHandler(async (event) => {
|
|
15
|
+
* const auth = useAuth()
|
|
16
|
+
* const session = await auth.validateSession(token)
|
|
17
|
+
* })
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function useAuth() {
|
|
21
|
+
if (!_authInstance) throw new Error("passkey-magic: auth not initialized. Make sure the Nitro plugin is registered.");
|
|
22
|
+
return _authInstance;
|
|
23
|
+
}
|
|
24
|
+
function isUnstorageInstance(obj) {
|
|
25
|
+
return obj && typeof obj.getItem === "function" && typeof obj.setItem === "function" && typeof obj.removeItem === "function" && !("createUser" in obj);
|
|
26
|
+
}
|
|
27
|
+
function resolveStorageAdapter(storage, storageBase) {
|
|
28
|
+
if (!storage) throw new Error("passkey-magic: storage is required. Pass a unstorage instance (e.g. useStorage()) or a passkey-magic StorageAdapter.");
|
|
29
|
+
if (isUnstorageInstance(storage)) return unstorageAdapter(storage, { base: storageBase });
|
|
30
|
+
return storage;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a passkey-magic Nitro plugin.
|
|
34
|
+
*
|
|
35
|
+
* Registers auth routes under the configured path prefix and makes
|
|
36
|
+
* the auth instance available via `useAuth()` in route handlers.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // plugins/auth.ts
|
|
41
|
+
* import { definePlugin } from 'nitro'
|
|
42
|
+
* import { useStorage } from 'nitro/storage'
|
|
43
|
+
* import { passkeyMagic } from 'passkey-magic/nitro'
|
|
44
|
+
*
|
|
45
|
+
* export default definePlugin((nitroApp) => {
|
|
46
|
+
* passkeyMagic({
|
|
47
|
+
* rpName: 'My App',
|
|
48
|
+
* rpID: 'example.com',
|
|
49
|
+
* origin: 'https://example.com',
|
|
50
|
+
* storage: useStorage(),
|
|
51
|
+
* }).setup(nitroApp)
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
function passkeyMagic(options) {
|
|
56
|
+
const pathPrefix = options.pathPrefix ?? "/auth";
|
|
57
|
+
function setup(nitroApp) {
|
|
58
|
+
const adapter = resolveStorageAdapter(options.storage, options.storageBase);
|
|
59
|
+
const auth = createAuth({
|
|
60
|
+
rpName: options.rpName,
|
|
61
|
+
rpID: options.rpID,
|
|
62
|
+
origin: options.origin,
|
|
63
|
+
storage: adapter,
|
|
64
|
+
email: options.email,
|
|
65
|
+
magicLinkURL: options.magicLinkURL,
|
|
66
|
+
sessionTTL: options.sessionTTL,
|
|
67
|
+
challengeTTL: options.challengeTTL,
|
|
68
|
+
magicLinkTTL: options.magicLinkTTL,
|
|
69
|
+
qrSessionTTL: options.qrSessionTTL,
|
|
70
|
+
generateId: options.generateId,
|
|
71
|
+
hooks: options.hooks
|
|
72
|
+
});
|
|
73
|
+
_authInstance = auth;
|
|
74
|
+
const handler = auth.createHandler({ pathPrefix });
|
|
75
|
+
nitroApp.hooks.hook("request", async (event) => {
|
|
76
|
+
if (!(event.path ?? event.req?.url ?? "/").startsWith(pathPrefix)) return;
|
|
77
|
+
await writeWebResponse(event, await handler(toWebRequest(event)));
|
|
78
|
+
});
|
|
79
|
+
nitroApp.hooks.hook("close", async () => {
|
|
80
|
+
_authInstance = null;
|
|
81
|
+
});
|
|
82
|
+
return auth;
|
|
83
|
+
}
|
|
84
|
+
return { setup };
|
|
85
|
+
}
|
|
86
|
+
function toWebRequest(event) {
|
|
87
|
+
const req = event.req ?? event.node?.req;
|
|
88
|
+
const method = req?.method ?? event.method ?? "GET";
|
|
89
|
+
const url = `http://localhost${event.path ?? req?.url ?? "/"}`;
|
|
90
|
+
const headers = new Headers();
|
|
91
|
+
if (req?.headers) {
|
|
92
|
+
for (const [key, value] of Object.entries(req.headers)) if (value) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
93
|
+
}
|
|
94
|
+
const init = {
|
|
95
|
+
method,
|
|
96
|
+
headers
|
|
97
|
+
};
|
|
98
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
99
|
+
if (event._body !== void 0) init.body = JSON.stringify(event._body);
|
|
100
|
+
else if (req?.body) init.body = req.body;
|
|
101
|
+
}
|
|
102
|
+
return new Request(url, init);
|
|
103
|
+
}
|
|
104
|
+
async function writeWebResponse(event, response) {
|
|
105
|
+
const res = event.res ?? event.node?.res;
|
|
106
|
+
if (res && typeof res.writeHead === "function") {
|
|
107
|
+
const headers = {};
|
|
108
|
+
response.headers.forEach((value, key) => {
|
|
109
|
+
headers[key] = value;
|
|
110
|
+
});
|
|
111
|
+
res.writeHead(response.status, headers);
|
|
112
|
+
const body = await response.text();
|
|
113
|
+
res.end(body);
|
|
114
|
+
} else if (event.respondWith) await event.respondWith(response);
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { passkeyMagic, useAuth };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as AuthResult, c as Credential, d as QRSessionStatus, f as RegistrationResponseJSON, h as User, i as AuthHooks, l as EmailAdapter, m as StorageAdapter, n as AuthEventHandler, o as AuthenticationResponseJSON, p as Session, r as AuthEventMap, t as AuthConfig, u as MagicLinkMethods } from "../types-BjM1f6uu.mjs";
|
|
2
|
+
import { n as BaseAuthMethods, r as createAuth, t as AuthInstance } from "../index-Cqqpr_uS.mjs";
|
|
3
|
+
export { AuthConfig, AuthEventHandler, AuthEventMap, AuthHooks, AuthInstance, AuthResult, AuthenticationResponseJSON, BaseAuthMethods, Credential, EmailAdapter, MagicLinkMethods, QRSessionStatus, RegistrationResponseJSON, Session, StorageAdapter, User, createAuth };
|