@shtora/auth 0.0.0-stage → 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.
@@ -0,0 +1,148 @@
1
+ export interface AuthUser {
2
+ readonly id: string;
3
+ readonly email: string;
4
+ }
5
+ export interface UserStore<User extends AuthUser = AuthUser> {
6
+ findByEmail(email: string): Promise<User | null>;
7
+ findById(id: string): Promise<User | null>;
8
+ /** Enforce normalized-email uniqueness atomically; hash the password before persistence. */
9
+ create(input: {
10
+ email: string;
11
+ password: string;
12
+ }): Promise<{
13
+ created: true;
14
+ user: User;
15
+ } | {
16
+ created: false;
17
+ }>;
18
+ }
19
+ export interface CredentialVerifier<User extends AuthUser = AuthUser> {
20
+ verify(input: {
21
+ email: string;
22
+ password: string;
23
+ user: User | null;
24
+ }): Promise<boolean>;
25
+ }
26
+ export type AuthActionTokenPurpose = "email-verification" | "password-reset";
27
+ export interface AuthActionTokenStore extends ExpiredRecordStore {
28
+ /** Atomically replace any existing token for this user and purpose. */
29
+ create(input: {
30
+ purpose: AuthActionTokenPurpose;
31
+ userId: string;
32
+ tokenHash: string;
33
+ expiresAtMs: number;
34
+ }): Promise<void>;
35
+ /** Consume only an unexpired matching token, atomically; never return its stored hash. */
36
+ consume(input: {
37
+ purpose: AuthActionTokenPurpose;
38
+ tokenHash: string;
39
+ nowMs: number;
40
+ }): Promise<{
41
+ userId: string;
42
+ } | null>;
43
+ }
44
+ export interface AuthActionTokenDelivery {
45
+ /** Enqueue delivery promptly; callers must not log the bearer token. */
46
+ send(input: {
47
+ email: string;
48
+ token: string;
49
+ expiresAtMs: number;
50
+ }): Promise<void>;
51
+ tokenTtlSeconds?: number;
52
+ }
53
+ export interface EmailVerificationOptions<User extends AuthUser = AuthUser> extends AuthActionTokenDelivery {
54
+ tokens: AuthActionTokenStore;
55
+ isVerified(user: User): boolean;
56
+ markVerified(userId: string): Promise<void>;
57
+ }
58
+ export interface PasswordResetOptions extends AuthActionTokenDelivery {
59
+ tokens: AuthActionTokenStore;
60
+ /** Update the application's password hash; this package never hashes or stores passwords. */
61
+ setPassword(input: {
62
+ userId: string;
63
+ password: string;
64
+ }): Promise<void>;
65
+ }
66
+ /** id is random and non-secret; tokenHash is the cookie token digest. Timestamps are Unix epoch milliseconds. */
67
+ export interface StoredSession {
68
+ readonly id: string;
69
+ readonly tokenHash: string;
70
+ readonly userId: string;
71
+ readonly createdAtMs: number;
72
+ readonly expiresAtMs: number;
73
+ }
74
+ export interface AuthOptions<User extends AuthUser = AuthUser> {
75
+ users: UserStore<User>;
76
+ credentials: CredentialVerifier<User>;
77
+ sessions: SessionStore;
78
+ emailPolicy?: AccessPolicy;
79
+ resolveClientIp?: (request: Request) => string | null | Promise<string | null>;
80
+ ipPolicy?: AccessPolicy;
81
+ rateLimiter?: AuthRateLimiter;
82
+ emailVerification?: EmailVerificationOptions<User>;
83
+ passwordReset?: PasswordResetOptions;
84
+ allowedHosts?: readonly string[];
85
+ onError?: (event: {
86
+ operation: string;
87
+ error: unknown;
88
+ }) => void | Promise<void>;
89
+ requireOrigin?: boolean;
90
+ basePath?: string;
91
+ cookieName?: string;
92
+ sessionTtlSeconds?: number;
93
+ maxRequestBytes?: number;
94
+ }
95
+ export interface ExpiredRecordStore {
96
+ /** Delete at most `limit` expired records; call periodically from a scheduled task. */
97
+ deleteExpired(input: {
98
+ nowMs: number;
99
+ limit: number;
100
+ }): Promise<void>;
101
+ }
102
+ export interface SessionStore extends ExpiredRecordStore {
103
+ create(session: StoredSession): Promise<void>;
104
+ findByTokenHash(tokenHash: string): Promise<StoredSession | null>;
105
+ deleteByTokenHash(tokenHash: string): Promise<void>;
106
+ listByUserId(userId: string): Promise<readonly StoredSession[]>;
107
+ deleteByIdForUser(id: string, userId: string): Promise<void>;
108
+ deleteByUserId(userId: string): Promise<void>;
109
+ }
110
+ export interface AuthRateLimiter {
111
+ check(input: {
112
+ action: "register" | "login" | "email-verification" | "password-reset";
113
+ email: string;
114
+ ip: string;
115
+ }): Promise<{
116
+ allowed: boolean;
117
+ retryAfterSeconds?: number;
118
+ }>;
119
+ }
120
+ export type AuthRequestRateLimitAction = "oidc-start" | "oidc-callback" | "webauthn-registration-options" | "webauthn-registration-verify" | "webauthn-authentication-options" | "webauthn-authentication-verify";
121
+ export interface AuthRequestRateLimiter {
122
+ check(input: {
123
+ action: AuthRequestRateLimitAction;
124
+ ip: string;
125
+ }): Promise<{
126
+ allowed: boolean;
127
+ retryAfterSeconds?: number;
128
+ }>;
129
+ }
130
+ export interface AccessPolicy {
131
+ allow(input: {
132
+ value: string;
133
+ request: Request;
134
+ }): Promise<boolean>;
135
+ }
136
+ export interface AuthenticatedUser {
137
+ readonly id: string;
138
+ readonly email: string;
139
+ }
140
+ export interface AuthApi {
141
+ handle(request: Request): Promise<Response>;
142
+ /** Delete a bounded batch of expired sessions and action tokens from each configured store. */
143
+ cleanupExpired(): Promise<void>;
144
+ /** Rejects if a backing adapter fails; unexpected errors are observed before being rethrown. */
145
+ getSession(request: Request): Promise<AuthenticatedUser | null>;
146
+ /** Use only after an external identity protocol has been verified; creates a server-side session. */
147
+ createSessionResponse(request: Request, userId: string): Promise<Response>;
148
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,68 @@
1
+ import type { WebAuthnCredential } from "@simplewebauthn/server";
2
+ import type { AuthApi, AuthRequestRateLimiter, ExpiredRecordStore } from "./types.js";
3
+ export type WebAuthnChallengePurpose = "registration" | "authentication";
4
+ /** Stores must consume atomically and delete at most the requested number of expired challenges per cleanup call. */
5
+ export interface WebAuthnChallengeStore extends ExpiredRecordStore {
6
+ create(challenge: {
7
+ id: string;
8
+ purpose: WebAuthnChallengePurpose;
9
+ userId: string | null;
10
+ value: string;
11
+ expiresAtMs: number;
12
+ }): Promise<void>;
13
+ consume(input: {
14
+ id: string;
15
+ purpose: WebAuthnChallengePurpose;
16
+ userId: string | null;
17
+ nowMs: number;
18
+ }): Promise<{
19
+ value: string;
20
+ } | null>;
21
+ }
22
+ /** The counter update must be atomic compare-and-set; create must enforce credential-ID uniqueness. */
23
+ export interface WebAuthnCredentialStore {
24
+ listByUserId(userId: string): Promise<readonly WebAuthnCredential[]>;
25
+ findById(id: string): Promise<{
26
+ userId: string;
27
+ credential: WebAuthnCredential;
28
+ } | null>;
29
+ create(input: {
30
+ userId: string;
31
+ credential: WebAuthnCredential;
32
+ }): Promise<boolean>;
33
+ updateCounter(input: {
34
+ id: string;
35
+ expectedCounter: number;
36
+ newCounter: number;
37
+ }): Promise<boolean>;
38
+ }
39
+ export interface WebAuthnUser {
40
+ /** Stable opaque application ID, encoded as UTF-8 and limited to 64 bytes. */
41
+ id: string;
42
+ name: string;
43
+ displayName: string;
44
+ }
45
+ export interface WebAuthnOptions {
46
+ /** Exact origin, including scheme and port; never inferred from an incoming request. */
47
+ expectedOrigin: string;
48
+ /** Exact RP ID configured by the relying party. */
49
+ rpID: string;
50
+ rpName: string;
51
+ challenges: WebAuthnChallengeStore;
52
+ credentials: WebAuthnCredentialStore;
53
+ /** Return the authenticated application user for registration, or null. */
54
+ resolveRegistrationUser(request: Request): Promise<WebAuthnUser | null>;
55
+ auth: Pick<AuthApi, "createSessionResponse">;
56
+ resolveClientIp: (request: Request) => string | null | Promise<string | null>;
57
+ rateLimiter: AuthRequestRateLimiter;
58
+ challengeTtlMs?: number;
59
+ onError?(input: {
60
+ operation: string;
61
+ error: unknown;
62
+ }): void | Promise<void>;
63
+ }
64
+ /** Creates framework-neutral WebAuthn registration and authentication HTTP handlers. */
65
+ export declare function createWebAuthn(options: WebAuthnOptions): {
66
+ handle(request: Request): Promise<Response>;
67
+ cleanupExpired(): Promise<void>;
68
+ };
@@ -0,0 +1,302 @@
1
+ import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse, } from "@simplewebauthn/server";
2
+ const defaultChallengeTtlMs = 5 * 60 * 1000;
3
+ const maxChallengeTtlMs = 15 * 60 * 1000;
4
+ const maxBodyBytes = 64 * 1024;
5
+ function json(body, status = 200, headers) {
6
+ return new Response(JSON.stringify(body), {
7
+ status,
8
+ headers: {
9
+ "Cache-Control": "no-store",
10
+ "Content-Type": "application/json; charset=utf-8",
11
+ "X-Content-Type-Options": "nosniff",
12
+ ...Object.fromEntries(new Headers(headers)),
13
+ },
14
+ });
15
+ }
16
+ function reject() {
17
+ return json({ error: "invalid_request" }, 400);
18
+ }
19
+ function randomId() {
20
+ const bytes = new Uint8Array(32);
21
+ crypto.getRandomValues(bytes);
22
+ let binary = "";
23
+ for (const byte of bytes)
24
+ binary += String.fromCharCode(byte);
25
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
26
+ }
27
+ function hasSameOrigin(request, expectedOrigin) {
28
+ const origin = request.headers.get("origin");
29
+ if (!origin)
30
+ return false;
31
+ try {
32
+ const parsed = new URL(origin);
33
+ return parsed.origin === origin &&
34
+ !parsed.username &&
35
+ !parsed.password &&
36
+ parsed.pathname === "/" &&
37
+ !parsed.search &&
38
+ !parsed.hash &&
39
+ origin === expectedOrigin;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ }
45
+ async function readJson(request) {
46
+ const contentLength = Number(request.headers.get("content-length"));
47
+ if (Number.isFinite(contentLength) && contentLength > maxBodyBytes)
48
+ return null;
49
+ const reader = request.body?.getReader();
50
+ if (!reader)
51
+ return null;
52
+ const chunks = [];
53
+ let length = 0;
54
+ try {
55
+ while (true) {
56
+ const { done, value } = await reader.read();
57
+ if (done)
58
+ break;
59
+ length += value.byteLength;
60
+ if (length > maxBodyBytes) {
61
+ await reader.cancel();
62
+ return null;
63
+ }
64
+ chunks.push(value);
65
+ }
66
+ const bytes = new Uint8Array(length);
67
+ let offset = 0;
68
+ for (const chunk of chunks) {
69
+ bytes.set(chunk, offset);
70
+ offset += chunk.byteLength;
71
+ }
72
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
73
+ return value !== null && typeof value === "object" && !Array.isArray(value)
74
+ ? value
75
+ : null;
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ finally {
81
+ reader.releaseLock();
82
+ }
83
+ }
84
+ function isLoopback(hostname) {
85
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
86
+ return host === "localhost" || host.endsWith(".localhost") || host === "::1" || host.startsWith("127.");
87
+ }
88
+ async function report(options, operation, error) {
89
+ try {
90
+ await options.onError?.({ operation, error });
91
+ }
92
+ catch {
93
+ // Error observers must not affect the generic public result.
94
+ }
95
+ }
96
+ /** Creates framework-neutral WebAuthn registration and authentication HTTP handlers. */
97
+ export function createWebAuthn(options) {
98
+ let expectedOrigin;
99
+ try {
100
+ const parsed = new URL(options.expectedOrigin);
101
+ const isSecureScheme = parsed.protocol === "https:";
102
+ const isLocalHttp = parsed.protocol === "http:" && isLoopback(parsed.hostname);
103
+ if (parsed.origin !== options.expectedOrigin || (!isSecureScheme && !isLocalHttp))
104
+ throw new TypeError();
105
+ const hostname = parsed.hostname.toLowerCase();
106
+ const rpID = options.rpID.toLowerCase();
107
+ if (parsed.origin !== options.expectedOrigin ||
108
+ (!isSecureScheme && !isLocalHttp) ||
109
+ !rpID ||
110
+ (hostname !== rpID && !hostname.endsWith(`.${rpID}`)) ||
111
+ !options.rpName.trim())
112
+ throw new TypeError();
113
+ expectedOrigin = parsed.origin;
114
+ }
115
+ catch {
116
+ throw new TypeError("expectedOrigin must be HTTPS (or localhost HTTP), rpID must match its host, and rpName must be non-empty");
117
+ }
118
+ const challengeTtlMs = options.challengeTtlMs ?? defaultChallengeTtlMs;
119
+ if (!Number.isSafeInteger(challengeTtlMs) || challengeTtlMs < 1_000 || challengeTtlMs > maxChallengeTtlMs) {
120
+ throw new RangeError("challengeTtlMs must be between 1000 and 900000");
121
+ }
122
+ async function createChallenge(purpose, userId, value) {
123
+ const id = randomId();
124
+ await options.challenges.create({
125
+ id,
126
+ purpose,
127
+ userId,
128
+ value,
129
+ expiresAtMs: Date.now() + challengeTtlMs,
130
+ });
131
+ return id;
132
+ }
133
+ async function registrationOptions(request) {
134
+ const user = await options.resolveRegistrationUser(request);
135
+ if (!user || !user.id || !user.name || !user.displayName)
136
+ return json({ error: "unauthenticated" }, 401);
137
+ const userID = new TextEncoder().encode(user.id);
138
+ if (userID.byteLength > 64)
139
+ throw new RangeError("WebAuthn user IDs must not exceed 64 UTF-8 bytes");
140
+ const existing = await options.credentials.listByUserId(user.id);
141
+ const publicKey = await generateRegistrationOptions({
142
+ rpName: options.rpName,
143
+ rpID: options.rpID,
144
+ userID,
145
+ userName: user.name,
146
+ userDisplayName: user.displayName,
147
+ authenticatorSelection: { residentKey: "required", userVerification: "required" },
148
+ excludeCredentials: existing.map(({ id, transports }) => ({ id, transports })),
149
+ });
150
+ const transactionId = await createChallenge("registration", user.id, publicKey.challenge);
151
+ return json({ transactionId, options: publicKey });
152
+ }
153
+ async function registrationVerify(request) {
154
+ const body = await readJson(request);
155
+ if (!body || typeof body.transactionId !== "string" || !body.response || typeof body.response !== "object")
156
+ return reject();
157
+ const user = await options.resolveRegistrationUser(request);
158
+ if (!user)
159
+ return json({ error: "unauthenticated" }, 401);
160
+ const transaction = await options.challenges.consume({
161
+ id: body.transactionId,
162
+ purpose: "registration",
163
+ userId: user.id,
164
+ nowMs: Date.now(),
165
+ });
166
+ if (!transaction)
167
+ return reject();
168
+ let verification;
169
+ try {
170
+ verification = await verifyRegistrationResponse({
171
+ response: body.response,
172
+ expectedChallenge: transaction.value,
173
+ expectedOrigin,
174
+ expectedRPID: options.rpID,
175
+ requireUserVerification: true,
176
+ });
177
+ }
178
+ catch {
179
+ return reject();
180
+ }
181
+ if (!verification.verified || !verification.registrationInfo)
182
+ return reject();
183
+ const { credential } = verification.registrationInfo;
184
+ if (!(await options.credentials.create({ userId: user.id, credential })))
185
+ return reject();
186
+ return json({ verified: true });
187
+ }
188
+ async function authenticationOptions() {
189
+ const publicKey = await generateAuthenticationOptions({
190
+ rpID: options.rpID,
191
+ userVerification: "required",
192
+ });
193
+ const transactionId = await createChallenge("authentication", null, publicKey.challenge);
194
+ return json({ transactionId, options: publicKey });
195
+ }
196
+ async function authenticationVerify(request) {
197
+ const body = await readJson(request);
198
+ if (!body || typeof body.transactionId !== "string" || !body.response || typeof body.response !== "object" ||
199
+ typeof body.response.id !== "string")
200
+ return reject();
201
+ const response = body.response;
202
+ const transaction = await options.challenges.consume({
203
+ id: body.transactionId,
204
+ purpose: "authentication",
205
+ userId: null,
206
+ nowMs: Date.now(),
207
+ });
208
+ if (!transaction)
209
+ return reject();
210
+ const stored = await options.credentials.findById(response.id);
211
+ if (!stored || stored.credential.id !== response.id)
212
+ return reject();
213
+ let verification;
214
+ try {
215
+ verification = await verifyAuthenticationResponse({
216
+ response,
217
+ expectedChallenge: transaction.value,
218
+ expectedOrigin,
219
+ expectedRPID: options.rpID,
220
+ credential: stored.credential,
221
+ requireUserVerification: true,
222
+ });
223
+ }
224
+ catch {
225
+ return reject();
226
+ }
227
+ if (!verification.verified)
228
+ return reject();
229
+ const updated = await options.credentials.updateCounter({
230
+ id: stored.credential.id,
231
+ expectedCounter: stored.credential.counter,
232
+ newCounter: verification.authenticationInfo.newCounter,
233
+ });
234
+ if (!updated)
235
+ return reject();
236
+ const session = await options.auth.createSessionResponse(request, stored.userId);
237
+ if (session.status === 200)
238
+ return session;
239
+ return json({ error: "unauthenticated" }, session.status === 403 ? 403 : 401);
240
+ }
241
+ async function handle(request) {
242
+ if (request.method !== "POST")
243
+ return json({ error: "method_not_allowed" }, 405);
244
+ if (!hasSameOrigin(request, expectedOrigin))
245
+ return json({ error: "invalid_origin" }, 403);
246
+ const path = new URL(request.url).pathname;
247
+ const routes = {
248
+ "/webauthn/registration/options": {
249
+ operation: "registration.options",
250
+ rateLimitAction: "webauthn-registration-options",
251
+ run: () => registrationOptions(request),
252
+ },
253
+ "/webauthn/registration/verify": {
254
+ operation: "registration.verify",
255
+ rateLimitAction: "webauthn-registration-verify",
256
+ run: () => registrationVerify(request),
257
+ },
258
+ "/webauthn/authentication/options": {
259
+ operation: "authentication.options",
260
+ rateLimitAction: "webauthn-authentication-options",
261
+ run: authenticationOptions,
262
+ },
263
+ "/webauthn/authentication/verify": {
264
+ operation: "authentication.verify",
265
+ rateLimitAction: "webauthn-authentication-verify",
266
+ run: () => authenticationVerify(request),
267
+ },
268
+ };
269
+ const route = routes[path];
270
+ if (!route)
271
+ return json({ error: "not_found" }, 404);
272
+ try {
273
+ const ip = await options.resolveClientIp(request);
274
+ const decision = ip
275
+ ? await options.rateLimiter.check({ action: route.rateLimitAction, ip })
276
+ : { allowed: false, retryAfterSeconds: 60 };
277
+ if (!decision.allowed) {
278
+ const seconds = Number.isSafeInteger(decision.retryAfterSeconds) && decision.retryAfterSeconds > 0
279
+ ? decision.retryAfterSeconds
280
+ : 60;
281
+ return json({ error: "rate_limited" }, 429, { "Retry-After": String(seconds) });
282
+ }
283
+ return await route.run();
284
+ }
285
+ catch (error) {
286
+ await report(options, route.operation, error);
287
+ return json({ error: "internal_error" }, 500);
288
+ }
289
+ }
290
+ return {
291
+ handle,
292
+ async cleanupExpired() {
293
+ try {
294
+ await options.challenges.deleteExpired({ nowMs: Date.now(), limit: 500 });
295
+ }
296
+ catch (error) {
297
+ await report(options, "challenge.cleanup", error);
298
+ throw error;
299
+ }
300
+ },
301
+ };
302
+ }
@@ -0,0 +1,39 @@
1
+ CREATE TABLE IF NOT EXISTS shtora_sessions (
2
+ id TEXT PRIMARY KEY NOT NULL,
3
+ token_hash TEXT NOT NULL UNIQUE,
4
+ user_id TEXT NOT NULL,
5
+ created_at_ms INTEGER NOT NULL,
6
+ expires_at_ms INTEGER NOT NULL
7
+ );
8
+
9
+ CREATE INDEX IF NOT EXISTS shtora_sessions_user_created
10
+ ON shtora_sessions (user_id, created_at_ms, id);
11
+
12
+ CREATE TABLE IF NOT EXISTS shtora_rate_limits (
13
+ key_hash TEXT PRIMARY KEY NOT NULL,
14
+ window_start_ms INTEGER NOT NULL,
15
+ count INTEGER NOT NULL CHECK (count > 0)
16
+ );
17
+
18
+ CREATE TABLE IF NOT EXISTS shtora_action_tokens (
19
+ purpose TEXT NOT NULL,
20
+ user_id TEXT NOT NULL,
21
+ token_hash TEXT NOT NULL,
22
+ expires_at_ms INTEGER NOT NULL,
23
+ PRIMARY KEY (purpose, token_hash)
24
+ );
25
+
26
+ CREATE INDEX IF NOT EXISTS shtora_action_tokens_expiry
27
+ ON shtora_action_tokens (expires_at_ms);
28
+ CREATE INDEX IF NOT EXISTS shtora_action_tokens_user_purpose
29
+ ON shtora_action_tokens (purpose, user_id);
30
+
31
+ -- Preferred cleanup path: schedule cleanupD1AuthState(db, { rateLimitMaxWindowSeconds }).
32
+ -- It uses bounded batches for expired sessions/action tokens and completed rate-limit windows.
33
+ -- Manual equivalent for rate counters:
34
+ -- DELETE FROM shtora_rate_limits WHERE window_start_ms < :now_ms - :max_window_ms;
35
+ -- Set :max_window_ms to the longest configured rate-limit window in milliseconds.
36
+ -- Manual equivalent for action tokens and sessions:
37
+ -- DELETE FROM shtora_action_tokens WHERE expires_at_ms <= :now_ms;
38
+ -- DELETE FROM shtora_sessions WHERE expires_at_ms <= :now_ms;
39
+ -- Apply 0002_session_expiry_index.sql and 0003_rate_limit_expiry_index.sql for cleanup indexes.
@@ -0,0 +1,2 @@
1
+ CREATE INDEX IF NOT EXISTS shtora_sessions_expiry
2
+ ON shtora_sessions (expires_at_ms);
@@ -0,0 +1,2 @@
1
+ CREATE INDEX IF NOT EXISTS shtora_rate_limits_window_start
2
+ ON shtora_rate_limits (window_start_ms);
package/package.json CHANGED
@@ -1,6 +1,80 @@
1
1
  {
2
2
  "name": "@shtora/auth",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "description": "Framework-neutral auth handlers and session management for Next.js and Cloudflare Workers, with optional D1, OIDC, and WebAuthn adapters.",
4
+ "keywords": [
5
+ "authentication",
6
+ "auth",
7
+ "nextjs",
8
+ "next.js",
9
+ "cloudflare-workers",
10
+ "cloudflare-d1",
11
+ "workers",
12
+ "session-management",
13
+ "sessions",
14
+ "typescript",
15
+ "edge-runtime",
16
+ "oidc",
17
+ "openid-connect",
18
+ "webauthn",
19
+ "passkeys",
20
+ "email-verification",
21
+ "password-reset"
22
+ ],
23
+ "version": "0.1.0",
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ },
30
+ "./cloudflare": {
31
+ "types": "./dist/cloudflare.d.ts",
32
+ "import": "./dist/cloudflare.js"
33
+ },
34
+ "./oidc": {
35
+ "types": "./dist/oidc.d.ts",
36
+ "import": "./dist/oidc.js"
37
+ },
38
+ "./webauthn": {
39
+ "types": "./dist/webauthn.d.ts",
40
+ "import": "./dist/webauthn.js"
41
+ }
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/orgShtora-1/auth.git"
46
+ },
47
+ "homepage": "https://github.com/orgShtora-1/auth#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/orgShtora-1/auth/issues"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "peerDependencies": {
55
+ "@simplewebauthn/server": ">=14.0.3 <15",
56
+ "openid-client": ">=6 <7"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@simplewebauthn/server": {
60
+ "optional": true
61
+ },
62
+ "openid-client": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "files": ["dist", "migrations"],
67
+ "sideEffects": false,
68
+ "scripts": {
69
+ "build": "tsc -p tsconfig.build.json",
70
+ "prepack": "tsc -p tsconfig.build.json",
71
+ "typecheck": "tsc --noEmit",
72
+ "test": "bun test"
73
+ },
74
+ "devDependencies": {
75
+ "@simplewebauthn/server": "^14.0.3",
76
+ "@types/bun": "latest",
77
+ "openid-client": "^6.8.1",
78
+ "typescript": "^7.0.2"
79
+ }
80
+ }