@cfast/auth 0.0.1 → 0.2.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 CHANGED
@@ -138,12 +138,12 @@ function Header() {
138
138
 
139
139
  ### Login Page
140
140
 
141
- The consumer creates their own login route and renders `<LoginPage>`. The component accepts an `authClient` prop and a `components` prop for UI slot overrides. Default slots render plain HTML — use `@cfast/ui/joy` for Joy UI styling.
141
+ The consumer creates their own login route and renders `<LoginPage>`. The component accepts an `authClient` prop and a `components` prop for UI slot overrides. Default slots render plain HTML — use `@cfast/joy` for Joy UI styling.
142
142
 
143
143
  ```typescript
144
144
  // routes/login.tsx
145
145
  import { LoginPage } from "@cfast/auth/client";
146
- import { joyLoginComponents } from "@cfast/ui/joy";
146
+ import { joyLoginComponents } from "@cfast/joy";
147
147
  import { authClient } from "~/auth.client";
148
148
 
149
149
  export default function Login() {
@@ -196,7 +196,7 @@ export default function Login() {
196
196
  }
197
197
  ```
198
198
 
199
- For Joy UI, use the pre-built `joyLoginComponents` from `@cfast/ui/joy` instead of writing custom slots.
199
+ For Joy UI, use the pre-built `joyLoginComponents` from `@cfast/joy` instead of writing custom slots.
200
200
 
201
201
  ### Redirect Flow
202
202
 
package/dist/client.d.ts CHANGED
@@ -72,9 +72,15 @@ type LoginComponents = {
72
72
  ErrorMessage?: ComponentType<{
73
73
  error: string;
74
74
  }>;
75
+ PasskeySignUpButton?: ComponentType<{
76
+ onClick: () => void;
77
+ loading: boolean;
78
+ }>;
75
79
  };
76
80
  type LoginPageProps = {
77
- authClient: {
81
+ /** Pass `undefined` during SSR (e.g. from a `.client.ts` module). The
82
+ * component renders a static shell and hydrates with full interactivity. */
83
+ authClient?: {
78
84
  signIn: {
79
85
  magicLink: (opts: {
80
86
  email: string;
@@ -89,6 +95,24 @@ type LoginPageProps = {
89
95
  } | null;
90
96
  } | undefined>;
91
97
  };
98
+ signUp?: {
99
+ email: (opts: {
100
+ email: string;
101
+ password: string;
102
+ name: string;
103
+ }) => Promise<{
104
+ error?: {
105
+ message?: string;
106
+ } | null;
107
+ }>;
108
+ };
109
+ passkey?: {
110
+ addPasskey: () => Promise<{
111
+ error?: {
112
+ message?: string;
113
+ } | null;
114
+ } | undefined>;
115
+ };
92
116
  };
93
117
  components?: LoginComponents;
94
118
  title?: string;
package/dist/client.js CHANGED
@@ -133,6 +133,21 @@ function DefaultPasskeyButton({
133
133
  }
134
134
  );
135
135
  }
136
+ function DefaultPasskeySignUpButton({
137
+ onClick,
138
+ loading
139
+ }) {
140
+ return /* @__PURE__ */ jsx4(
141
+ "button",
142
+ {
143
+ type: "button",
144
+ onClick,
145
+ disabled: loading,
146
+ style: { width: "100%", padding: 8 },
147
+ children: loading ? "Creating account..." : "Sign up with Passkey"
148
+ }
149
+ );
150
+ }
136
151
  function DefaultSuccessMessage({ email }) {
137
152
  return /* @__PURE__ */ jsxs("div", { role: "status", children: [
138
153
  "Check your email (",
@@ -154,6 +169,7 @@ function LoginPage({
154
169
  const EmailInput = components.EmailInput ?? DefaultEmailInput;
155
170
  const MagicLinkBtn = components.MagicLinkButton ?? DefaultMagicLinkButton;
156
171
  const PasskeyBtn = components.PasskeyButton ?? DefaultPasskeyButton;
172
+ const PasskeySignUpBtn = components.PasskeySignUpButton ?? DefaultPasskeySignUpButton;
157
173
  const SuccessMsg = components.SuccessMessage ?? DefaultSuccessMessage;
158
174
  const ErrorMsg = components.ErrorMessage ?? DefaultErrorMessage;
159
175
  const [email, setEmail] = useState("");
@@ -161,11 +177,14 @@ function LoginPage({
161
177
  const [loading, setLoading] = useState(false);
162
178
  const [error, setError] = useState(null);
163
179
  const [passkeyLoading, setPasskeyLoading] = useState(false);
180
+ const [passkeySignUpLoading, setPasskeySignUpLoading] = useState(false);
181
+ const canPasskeySignUp = !!(authClient?.signUp?.email && authClient?.passkey?.addPasskey);
164
182
  async function handleMagicLink() {
165
183
  if (!email.trim()) {
166
184
  setError("Please enter your email address.");
167
185
  return;
168
186
  }
187
+ if (!authClient) return;
169
188
  setLoading(true);
170
189
  setError(null);
171
190
  try {
@@ -183,6 +202,7 @@ function LoginPage({
183
202
  }
184
203
  }
185
204
  async function handlePasskey() {
205
+ if (!authClient) return;
186
206
  setPasskeyLoading(true);
187
207
  setError(null);
188
208
  try {
@@ -198,6 +218,36 @@ function LoginPage({
198
218
  setPasskeyLoading(false);
199
219
  }
200
220
  }
221
+ async function handlePasskeySignUp() {
222
+ if (!email.trim()) {
223
+ setError("Please enter your email address.");
224
+ return;
225
+ }
226
+ if (!authClient) return;
227
+ setPasskeySignUpLoading(true);
228
+ setError(null);
229
+ try {
230
+ const signUpResult = await authClient.signUp.email({
231
+ email,
232
+ password: crypto.randomUUID(),
233
+ name: ""
234
+ });
235
+ if (signUpResult.error) {
236
+ setError(signUpResult.error.message ?? "Sign-up failed.");
237
+ return;
238
+ }
239
+ const passkeyResult = await authClient.passkey.addPasskey();
240
+ if (passkeyResult?.error) {
241
+ setError(passkeyResult.error.message ?? "Passkey registration failed.");
242
+ return;
243
+ }
244
+ onSuccess?.();
245
+ } catch {
246
+ setError("Passkey sign-up failed. Please try again.");
247
+ } finally {
248
+ setPasskeySignUpLoading(false);
249
+ }
250
+ }
201
251
  return /* @__PURE__ */ jsxs(Layout, { children: [
202
252
  /* @__PURE__ */ jsx4("h2", { children: title }),
203
253
  subtitle && /* @__PURE__ */ jsx4("p", { children: subtitle }),
@@ -205,7 +255,14 @@ function LoginPage({
205
255
  sent ? /* @__PURE__ */ jsx4(SuccessMsg, { email }) : /* @__PURE__ */ jsxs("div", { children: [
206
256
  /* @__PURE__ */ jsx4(EmailInput, { value: email, onChange: setEmail }),
207
257
  /* @__PURE__ */ jsx4("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsx4(MagicLinkBtn, { onClick: handleMagicLink, loading }) }),
208
- authClient?.signIn?.passkey && /* @__PURE__ */ jsx4("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsx4(PasskeyBtn, { onClick: handlePasskey, loading: passkeyLoading }) })
258
+ authClient?.signIn?.passkey && /* @__PURE__ */ jsx4("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsx4(PasskeyBtn, { onClick: handlePasskey, loading: passkeyLoading }) }),
259
+ canPasskeySignUp && /* @__PURE__ */ jsx4("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsx4(
260
+ PasskeySignUpBtn,
261
+ {
262
+ onClick: handlePasskeySignUp,
263
+ loading: passkeySignUpLoading
264
+ }
265
+ ) })
209
266
  ] })
210
267
  ] });
211
268
  }
@@ -213,6 +270,7 @@ function LoginPage({
213
270
  // src/client/create-auth-client.ts
214
271
  import { createAuthClient } from "better-auth/react";
215
272
  import { magicLinkClient } from "better-auth/client/plugins";
273
+ import { passkeyClient } from "@better-auth/passkey/client";
216
274
  export {
217
275
  AuthClientProvider,
218
276
  AuthGuard,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as AuthConfig, b as AuthEnvConfig, c as AuthInstance } from './types-ghXti5CW.js';
2
- export { d as AuthContext, A as AuthUser, e as AuthenticatedContext } from './types-ghXti5CW.js';
3
- import '@cfast/permissions';
1
+ import { a as AuthConfig, b as AuthEnvConfig, c as AuthInstance, A as AuthUser } from './types-ghXti5CW.js';
2
+ export { d as AuthContext, e as AuthenticatedContext } from './types-ghXti5CW.js';
3
+ import { Grant } from '@cfast/permissions';
4
4
 
5
5
  /**
6
6
  * Creates a pre-configured auth factory for Cloudflare Workers.
@@ -164,4 +164,43 @@ declare function createAuthRouteHandlers(getAuth: () => AuthInstance): {
164
164
  }) => Promise<Response>;
165
165
  };
166
166
 
167
- export { AuthConfig, AuthEnvConfig, AuthInstance, createAuth, createAuthRouteHandlers, createImpersonationManager, createRoleManager };
167
+ /**
168
+ * Adapter type matching @cfast/admin's AdminAuthConfig interface.
169
+ * Duplicated here to avoid a circular dependency on @cfast/admin.
170
+ */
171
+ type AdminAuthBridge = {
172
+ requireUser(request: Request): Promise<{
173
+ user: AuthUser;
174
+ grants: Grant[];
175
+ }>;
176
+ hasRole(user: {
177
+ roles: string[];
178
+ }, role: string): boolean;
179
+ getRoles(userId: string): Promise<string[]>;
180
+ setRole(userId: string, role: string): Promise<void>;
181
+ removeRole(userId: string, role: string): Promise<void>;
182
+ setRoles(userId: string, roles: string[]): Promise<void>;
183
+ };
184
+ /**
185
+ * Creates an admin auth adapter from a cfast auth instance factory.
186
+ *
187
+ * Replaces ~150 lines of manual adapter boilerplate with a single call.
188
+ * The factory is called per-operation to ensure fresh env bindings on Workers.
189
+ *
190
+ * @param getAuth - Factory that returns an initialized AuthInstance.
191
+ * @returns An object satisfying `@cfast/admin`'s `AdminAuthConfig` interface.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * import { createAdminAuth } from "@cfast/auth";
196
+ *
197
+ * const auth = createAdminAuth(() =>
198
+ * initAuth({ d1: env.get().DB, appUrl: env.get().APP_URL })
199
+ * );
200
+ *
201
+ * const admin = createAdmin({ auth, db: createDbForAdmin, schema });
202
+ * ```
203
+ */
204
+ declare function createAdminAuth(getAuth: () => AuthInstance): AdminAuthBridge;
205
+
206
+ export { AuthConfig, AuthEnvConfig, AuthInstance, AuthUser, createAdminAuth, createAuth, createAuthRouteHandlers, createImpersonationManager, createRoleManager };
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { betterAuth } from "better-auth";
3
3
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
4
4
  import { magicLink } from "better-auth/plugins/magic-link";
5
+ import { passkey } from "@better-auth/passkey";
5
6
  import { drizzle } from "drizzle-orm/d1";
6
7
  import { resolveGrants } from "@cfast/permissions";
7
8
 
@@ -127,6 +128,15 @@ function createAuth(config) {
127
128
  magicLink({ sendMagicLink: config.magicLink.sendMagicLink })
128
129
  );
129
130
  }
131
+ if (config.passkeys) {
132
+ plugins.push(
133
+ passkey({
134
+ rpName: config.passkeys.rpName,
135
+ rpID: config.passkeys.rpId,
136
+ origin: env.appUrl
137
+ })
138
+ );
139
+ }
130
140
  const expiresIn = config.session?.expiresIn ? parseExpiresIn(config.session.expiresIn) : void 0;
131
141
  const auth = betterAuth({
132
142
  baseURL: env.appUrl,
@@ -252,7 +262,33 @@ function createAuthRouteHandlers(getAuth) {
252
262
  }
253
263
  return { loader: handleRequest, action: handleRequest };
254
264
  }
265
+
266
+ // src/admin-auth.ts
267
+ function createAdminAuth(getAuth) {
268
+ return {
269
+ async requireUser(request) {
270
+ const ctx = await getAuth().requireUser(request);
271
+ return { user: ctx.user, grants: ctx.grants };
272
+ },
273
+ hasRole(user, role) {
274
+ return user.roles.includes(role);
275
+ },
276
+ async getRoles(userId) {
277
+ return getAuth().getRoles(userId);
278
+ },
279
+ async setRole(userId, role) {
280
+ await getAuth().setRole(userId, role);
281
+ },
282
+ async removeRole(userId, role) {
283
+ await getAuth().removeRole(userId, role);
284
+ },
285
+ async setRoles(userId, roles) {
286
+ await getAuth().setRoles(userId, roles);
287
+ }
288
+ };
289
+ }
255
290
  export {
291
+ createAdminAuth,
256
292
  createAuth,
257
293
  createAuthRouteHandlers,
258
294
  createImpersonationManager,
package/llms.txt ADDED
@@ -0,0 +1,216 @@
1
+ # @cfast/auth
2
+
3
+ > Authentication for Cloudflare Workers: magic email links, passkeys, roles, impersonation. Built on Better Auth.
4
+
5
+ ## When to use
6
+
7
+ Use `@cfast/auth` to add authentication to a cfast app. It handles login (magic link + passkeys), session management, role assignment, and user impersonation. It produces the `user` and `grants` objects that `@cfast/db` needs for permission-aware queries.
8
+
9
+ ## Key concepts
10
+
11
+ - **Two-phase initialization.** `createAuth(config)` returns an `initAuth` factory. Call `initAuth({ d1, appUrl })` per-request to get an `AuthInstance`.
12
+ - **Roles bridge auth and permissions.** Auth assigns roles to users. `@cfast/permissions` defines what roles can do. The `grants` from auth feed directly into `createDb()`.
13
+ - **Cookie-based redirect-back.** Unauthenticated users get a `cfast_redirect_to` cookie before redirecting to `/login`. After login, the cookie restores their intended destination.
14
+
15
+ ## API Reference
16
+
17
+ ### Server: createAuth(config) => initAuth
18
+
19
+ ```typescript
20
+ import { createAuth } from "@cfast/auth";
21
+
22
+ export const initAuth = createAuth({
23
+ permissions: Permissions, // from definePermissions()
24
+ magicLink?: {
25
+ sendMagicLink: (params: { email: string; url: string }) => Promise<void>,
26
+ },
27
+ passkeys?: { rpName: string; rpId: string },
28
+ session?: { expiresIn?: string }, // e.g. "30d"
29
+ redirects?: { afterLogin?: string; loginPath?: string },
30
+ anonymousRoles?: string[],
31
+ defaultRoles?: string[], // default: ["reader"]
32
+ roleGrants?: Record<string, string[]>, // who can assign which roles
33
+ impersonation?: { allowedRoles?: string[] },
34
+ templates?: { magicLink?: (props: { url: string; email: string }) => string },
35
+ });
36
+ ```
37
+
38
+ ### initAuth(env) => AuthInstance
39
+
40
+ ```typescript
41
+ const auth: AuthInstance = initAuth({ d1: env.DB, appUrl: "https://myapp.com" });
42
+ ```
43
+
44
+ ### AuthInstance methods
45
+
46
+ ```typescript
47
+ auth.createContext(request): Promise<AuthContext>
48
+ // { user: AuthUser | null, grants: Grant[] }
49
+
50
+ auth.requireUser(request): Promise<AuthenticatedContext>
51
+ // { user: AuthUser, grants: Grant[] } -- throws 302 redirect if not authenticated
52
+
53
+ auth.getRoles(userId): Promise<string[]>
54
+ auth.setRole(userId, role, caller?): Promise<void>
55
+ auth.setRoles(userId, roles, caller?): Promise<void>
56
+ auth.removeRole(userId, role): Promise<void>
57
+
58
+ auth.impersonate(adminUserId, targetUserId): Promise<void>
59
+ auth.stopImpersonating(adminUserId): Promise<void>
60
+
61
+ auth.sendMagicLink({ email, callbackURL? }): Promise<void>
62
+ auth.handler(request): Promise<Response> // forwards to Better Auth
63
+ ```
64
+
65
+ ### AuthUser type
66
+
67
+ ```typescript
68
+ type AuthUser = {
69
+ id: string;
70
+ email: string;
71
+ name: string;
72
+ avatarUrl: string | null;
73
+ roles: string[];
74
+ isImpersonating?: boolean;
75
+ realUser?: { id: string; name: string };
76
+ };
77
+ ```
78
+
79
+ ### createAdminAuth(getAuth): AdminAuthBridge
80
+
81
+ ```typescript
82
+ import { createAdminAuth } from "@cfast/auth";
83
+
84
+ function createAdminAuth(getAuth: () => AuthInstance): AdminAuthBridge
85
+ ```
86
+
87
+ Creates an admin auth adapter from a cfast auth instance factory. Replaces ~150 lines of manual adapter boilerplate with a single call. The factory is called per-operation to ensure fresh env bindings on Workers.
88
+
89
+ Returns an object satisfying `@cfast/admin`'s `AdminAuthConfig` interface with: `requireUser`, `hasRole`, `getRoles`, `setRole`, `removeRole`, `setRoles`.
90
+
91
+ ```typescript
92
+ const auth = createAdminAuth(() =>
93
+ initAuth({ d1: env.get().DB, appUrl: env.get().APP_URL })
94
+ );
95
+ const admin = createAdmin({ auth, db: createDbForAdmin, schema });
96
+ ```
97
+
98
+ ### Client exports (`@cfast/auth/client`)
99
+
100
+ ```typescript
101
+ // Providers
102
+ <AuthClientProvider authClient={authClient}> // wraps app root
103
+ <AuthProvider user={user}> // wraps layout routes
104
+ <AuthGuard user={user}> // layout component, provides user to children
105
+
106
+ // Hooks
107
+ useCurrentUser(): AuthUser | null // user from nearest AuthGuard/AuthProvider
108
+ useAuth(): UseAuthReturn // { signOut, registerPasskey, deletePasskey, stopImpersonating, authClient }
109
+
110
+ // Login
111
+ <LoginPage authClient={authClient} components? title? subtitle? />
112
+ createAuthClient(): AuthClientInstance
113
+ magicLinkClient(): plugin // re-exported from better-auth
114
+ passkeyClient(): plugin // re-exported from @better-auth/passkey
115
+ ```
116
+
117
+ ### LoginComponents slots
118
+
119
+ ```typescript
120
+ type LoginComponents = {
121
+ Layout?, EmailInput?, PasskeyButton?, PasskeySignUpButton?,
122
+ MagicLinkButton?, SuccessMessage?, ErrorMessage?,
123
+ };
124
+ ```
125
+
126
+ ### Passkey sign-up
127
+
128
+ When the `authClient` passed to `<LoginPage>` has both `signUp.email` and `passkey.addPasskey` (i.e., the client was created with `passkeyClient()`), a "Sign up with Passkey" button appears automatically. The flow:
129
+
130
+ 1. User enters email, clicks "Sign up with Passkey"
131
+ 2. Account is created via `signUp.email` with a random password
132
+ 3. Browser's WebAuthn registration prompt fires immediately via `addPasskey()`
133
+ 4. User is signed up with a passkey — no magic link email needed
134
+
135
+ To enable passkey sign-up, configure both server and client:
136
+
137
+ ```typescript
138
+ // Server: createAuth config
139
+ const initAuth = createAuth({
140
+ permissions,
141
+ passkeys: { rpName: "My App", rpId: "myapp.com" },
142
+ magicLink: { sendMagicLink: ... },
143
+ });
144
+
145
+ // Client: auth client
146
+ import { createAuthClient, magicLinkClient, passkeyClient } from "@cfast/auth/client";
147
+ const authClient = createAuthClient({
148
+ plugins: [magicLinkClient(), passkeyClient()],
149
+ });
150
+ ```
151
+
152
+ ### Route plugin (`@cfast/auth/plugin`)
153
+
154
+ ```typescript
155
+ import { authRoutes } from "@cfast/auth/plugin";
156
+ // In routes.ts:
157
+ export default [...authRoutes({ handlerFile: "routes/auth.$.tsx" }), ...otherRoutes];
158
+ ```
159
+
160
+ ### Schema (`@cfast/auth/schema`)
161
+
162
+ Drizzle tables: `user`, `session`, `passkey`, `role`, `impersonation_log`.
163
+
164
+ ## Usage Examples
165
+
166
+ ### Protect a layout route
167
+
168
+ ```typescript
169
+ // routes/_protected.tsx
170
+ import { AuthGuard } from "@cfast/auth/client";
171
+
172
+ export async function loader({ request, context }) {
173
+ const auth = initAuth({ d1: context.env.DB, appUrl: context.env.APP_URL });
174
+ const { user, grants } = await auth.requireUser(request);
175
+ return { user };
176
+ }
177
+
178
+ export default function ProtectedLayout() {
179
+ const { user } = useLoaderData<typeof loader>();
180
+ return <AuthGuard user={user}><Outlet /></AuthGuard>;
181
+ }
182
+ ```
183
+
184
+ ### Auth -> Db flow in a loader
185
+
186
+ ```typescript
187
+ export async function loader({ request, context }) {
188
+ const auth = initAuth({ d1: context.env.DB, appUrl: context.env.APP_URL });
189
+ const { user, grants } = await auth.requireUser(request);
190
+
191
+ const db = createDb({ d1: context.env.DB, schema, grants, user });
192
+ const posts = await db.query(postsTable).findMany().run({});
193
+ return { user, posts };
194
+ }
195
+ ```
196
+
197
+ ### Role management
198
+
199
+ ```typescript
200
+ await auth.setRole(userId, "editor");
201
+ await auth.setRoles(userId, ["editor", "moderator"]);
202
+ await auth.removeRole(userId, "editor");
203
+ ```
204
+
205
+ ## Integration
206
+
207
+ - **@cfast/permissions** -- `createAuth({ permissions })` takes the permissions config. Roles defined in permissions are the same roles assigned to users. `auth.requireUser()` returns `grants` resolved from the user's roles.
208
+ - **@cfast/db** -- Pass `{ user, grants }` from `auth.requireUser()` directly to `createDb()`. Role changes via `auth.setRole()` take effect on the next request.
209
+ - **@cfast/joy** -- Provides `joyLoginComponents` for Joy UI styled login page slots.
210
+
211
+ ## Common Mistakes
212
+
213
+ - **Calling `createAuth()` per-request** -- `createAuth()` is called once at module level. Only `initAuth()` is called per-request with the D1 binding.
214
+ - **Forgetting to add auth routes** -- The magic link callback and passkey endpoints need `authRoutes()` in your `routes.ts`.
215
+ - **Using `unsafe()` instead of roles for admin** -- Admins should have a proper role with grants, not bypass permissions entirely.
216
+ - **Not wrapping the app with `AuthClientProvider`** -- `useAuth()` and `LoginPage` require the provider at the app root.
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@cfast/auth",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "Authentication for Cloudflare Workers: magic email, passkeys, roles, and impersonation",
5
+ "keywords": [
6
+ "cfast",
7
+ "cloudflare-workers",
8
+ "authentication",
9
+ "passkeys",
10
+ "magic-link"
11
+ ],
5
12
  "license": "MIT",
6
13
  "repository": {
7
14
  "type": "git",
@@ -30,19 +37,28 @@
30
37
  }
31
38
  },
32
39
  "files": [
33
- "dist"
40
+ "dist",
41
+ "llms.txt"
34
42
  ],
35
43
  "sideEffects": false,
36
44
  "publishConfig": {
37
45
  "access": "public"
38
46
  },
47
+ "scripts": {
48
+ "build": "tsup src/index.ts src/client.ts src/plugin.ts src/schema.ts --format esm --dts",
49
+ "dev": "tsup src/index.ts src/client.ts src/plugin.ts src/schema.ts --format esm --dts --watch",
50
+ "test": "vitest run",
51
+ "typecheck": "tsc --noEmit",
52
+ "lint": "eslint src/"
53
+ },
39
54
  "peerDependencies": {
55
+ "@better-auth/passkey": ">=1",
40
56
  "better-auth": ">=1",
41
57
  "drizzle-orm": ">=0.35",
42
58
  "react": ">=18"
43
59
  },
44
60
  "dependencies": {
45
- "@cfast/permissions": "0.0.1"
61
+ "@cfast/permissions": "workspace:*"
46
62
  },
47
63
  "devDependencies": {
48
64
  "@cloudflare/workers-types": "^4.20260305.1",
@@ -55,12 +71,5 @@
55
71
  "tsup": "^8",
56
72
  "typescript": "^5.7",
57
73
  "vitest": "^4.1.0"
58
- },
59
- "scripts": {
60
- "build": "tsup src/index.ts src/client.ts src/plugin.ts src/schema.ts --format esm --dts",
61
- "dev": "tsup src/index.ts src/client.ts src/plugin.ts src/schema.ts --format esm --dts --watch",
62
- "test": "vitest run",
63
- "typecheck": "tsc --noEmit",
64
- "lint": "eslint src/"
65
74
  }
66
- }
75
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Daniel Schmidt
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,64 +0,0 @@
1
- import { Permissions, Grant } from '@cfast/permissions';
2
-
3
- type AuthUser = {
4
- id: string;
5
- email: string;
6
- name: string;
7
- avatarUrl: string | null;
8
- roles: string[];
9
- isImpersonating?: boolean;
10
- realUser?: {
11
- id: string;
12
- name: string;
13
- };
14
- };
15
- type AuthContext = {
16
- user: AuthUser | null;
17
- grants: Grant[];
18
- };
19
- type AuthenticatedContext = {
20
- user: AuthUser;
21
- grants: Grant[];
22
- };
23
- type AuthConfig = {
24
- permissions: Permissions;
25
- schema?: Record<string, unknown>;
26
- passkeys?: {
27
- rpName: string;
28
- rpId: string;
29
- };
30
- magicLink?: {
31
- sendMagicLink: (params: {
32
- email: string;
33
- url: string;
34
- }) => Promise<void>;
35
- };
36
- session?: {
37
- expiresIn?: string;
38
- };
39
- redirects?: {
40
- afterLogin?: string;
41
- loginPath?: string;
42
- };
43
- anonymousRoles?: string[];
44
- defaultRoles?: string[];
45
- roleTableName?: string;
46
- };
47
- type AuthEnvConfig = {
48
- d1: D1Database;
49
- appUrl: string;
50
- };
51
- type AuthInstance = {
52
- createContext: (request: Request) => Promise<AuthContext>;
53
- requireUser: (request: Request) => Promise<AuthenticatedContext>;
54
- getRoles: (userId: string) => Promise<string[]>;
55
- setRole: (userId: string, role: string) => Promise<void>;
56
- setRoles: (userId: string, roles: string[]) => Promise<void>;
57
- removeRole: (userId: string, role: string) => Promise<void>;
58
- /** Handle auth API requests (forwards to Better Auth) */
59
- handler: (request: Request) => Promise<Response>;
60
- /** The underlying Better Auth instance */
61
- api: unknown;
62
- };
63
-
64
- export type { AuthUser as A, AuthConfig as a, AuthEnvConfig as b, AuthInstance as c, AuthContext as d, AuthenticatedContext as e };
@@ -1,63 +0,0 @@
1
- import { Permissions, Grant } from '@cfast/permissions';
2
-
3
- type AuthUser = {
4
- id: string;
5
- email: string;
6
- name: string;
7
- avatarUrl: string | null;
8
- roles: string[];
9
- isImpersonating?: boolean;
10
- realUser?: {
11
- id: string;
12
- name: string;
13
- };
14
- };
15
- type AuthContext = {
16
- user: AuthUser | null;
17
- grants: Grant[];
18
- };
19
- type AuthenticatedContext = {
20
- user: AuthUser;
21
- grants: Grant[];
22
- };
23
- type AuthConfig = {
24
- permissions: Permissions;
25
- schema?: Record<string, unknown>;
26
- passkeys?: {
27
- rpName: string;
28
- rpId: string;
29
- };
30
- magicLink?: {
31
- sendMagicLink: (params: {
32
- email: string;
33
- url: string;
34
- }) => Promise<void>;
35
- };
36
- session?: {
37
- expiresIn?: string;
38
- };
39
- redirects?: {
40
- afterLogin?: string;
41
- loginPath?: string;
42
- };
43
- anonymousRoles?: string[];
44
- defaultRoles?: string[];
45
- };
46
- type AuthEnvConfig = {
47
- d1: D1Database;
48
- appUrl: string;
49
- };
50
- type AuthInstance = {
51
- createContext: (request: Request) => Promise<AuthContext>;
52
- requireUser: (request: Request) => Promise<AuthenticatedContext>;
53
- getRoles: (userId: string) => Promise<string[]>;
54
- setRole: (userId: string, role: string) => Promise<void>;
55
- setRoles: (userId: string, roles: string[]) => Promise<void>;
56
- removeRole: (userId: string, role: string) => Promise<void>;
57
- /** Handle auth API requests (forwards to Better Auth) */
58
- handler: (request: Request) => Promise<Response>;
59
- /** The underlying Better Auth instance */
60
- api: unknown;
61
- };
62
-
63
- export type { AuthUser as A, AuthConfig as a, AuthEnvConfig as b, AuthInstance as c, AuthContext as d, AuthenticatedContext as e };
@@ -1,74 +0,0 @@
1
- import { Permissions, Grant } from '@cfast/permissions';
2
-
3
- type AuthUser = {
4
- id: string;
5
- email: string;
6
- name: string;
7
- avatarUrl: string | null;
8
- roles: string[];
9
- isImpersonating?: boolean;
10
- realUser?: {
11
- id: string;
12
- name: string;
13
- };
14
- };
15
- type AuthContext = {
16
- user: AuthUser | null;
17
- grants: Grant[];
18
- };
19
- type AuthenticatedContext = {
20
- user: AuthUser;
21
- grants: Grant[];
22
- };
23
- type AuthConfig = {
24
- permissions: Permissions;
25
- schema?: Record<string, unknown>;
26
- passkeys?: {
27
- rpName: string;
28
- rpId: string;
29
- };
30
- magicLink?: {
31
- sendMagicLink: (params: {
32
- email: string;
33
- url: string;
34
- }) => Promise<void>;
35
- };
36
- session?: {
37
- expiresIn?: string;
38
- };
39
- redirects?: {
40
- afterLogin?: string;
41
- loginPath?: string;
42
- };
43
- anonymousRoles?: string[];
44
- defaultRoles?: string[];
45
- roleTableName?: string;
46
- roleGrants?: Record<string, string[]>;
47
- impersonation?: {
48
- allowedRoles?: string[];
49
- };
50
- };
51
- type AuthEnvConfig = {
52
- d1: D1Database;
53
- appUrl: string;
54
- };
55
- type AuthInstance = {
56
- createContext: (request: Request) => Promise<AuthContext>;
57
- requireUser: (request: Request) => Promise<AuthenticatedContext>;
58
- getRoles: (userId: string) => Promise<string[]>;
59
- setRole: (userId: string, role: string, caller?: {
60
- callerRoles?: string[];
61
- }) => Promise<void>;
62
- setRoles: (userId: string, roles: string[], caller?: {
63
- callerRoles?: string[];
64
- }) => Promise<void>;
65
- removeRole: (userId: string, role: string) => Promise<void>;
66
- impersonate: (adminUserId: string, targetUserId: string) => Promise<void>;
67
- stopImpersonating: (adminUserId: string) => Promise<void>;
68
- /** Handle auth API requests (forwards to Better Auth) */
69
- handler: (request: Request) => Promise<Response>;
70
- /** The underlying Better Auth instance */
71
- api: unknown;
72
- };
73
-
74
- export type { AuthUser as A, AuthConfig as a, AuthEnvConfig as b, AuthInstance as c, AuthContext as d, AuthenticatedContext as e };
@@ -1,85 +0,0 @@
1
- import { Permissions, Grant } from '@cfast/permissions';
2
-
3
- type AuthUser = {
4
- id: string;
5
- email: string;
6
- name: string;
7
- avatarUrl: string | null;
8
- roles: string[];
9
- isImpersonating?: boolean;
10
- realUser?: {
11
- id: string;
12
- name: string;
13
- };
14
- };
15
- type AuthContext = {
16
- user: AuthUser | null;
17
- grants: Grant[];
18
- };
19
- type AuthenticatedContext = {
20
- user: AuthUser;
21
- grants: Grant[];
22
- };
23
- type AuthConfig = {
24
- permissions: Permissions;
25
- schema?: Record<string, unknown>;
26
- passkeys?: {
27
- rpName: string;
28
- rpId: string;
29
- };
30
- magicLink?: {
31
- sendMagicLink: (params: {
32
- email: string;
33
- url: string;
34
- }) => Promise<void>;
35
- };
36
- session?: {
37
- expiresIn?: string;
38
- };
39
- redirects?: {
40
- afterLogin?: string;
41
- loginPath?: string;
42
- };
43
- anonymousRoles?: string[];
44
- defaultRoles?: string[];
45
- roleTableName?: string;
46
- roleGrants?: Record<string, string[]>;
47
- impersonation?: {
48
- allowedRoles?: string[];
49
- };
50
- templates?: {
51
- magicLink?: (props: {
52
- url: string;
53
- email: string;
54
- }) => string;
55
- };
56
- };
57
- type AuthEnvConfig = {
58
- d1: D1Database;
59
- appUrl: string;
60
- };
61
- type AuthInstance = {
62
- createContext: (request: Request) => Promise<AuthContext>;
63
- requireUser: (request: Request) => Promise<AuthenticatedContext>;
64
- getRoles: (userId: string) => Promise<string[]>;
65
- setRole: (userId: string, role: string, caller?: {
66
- callerRoles?: string[];
67
- }) => Promise<void>;
68
- setRoles: (userId: string, roles: string[], caller?: {
69
- callerRoles?: string[];
70
- }) => Promise<void>;
71
- removeRole: (userId: string, role: string) => Promise<void>;
72
- impersonate: (adminUserId: string, targetUserId: string) => Promise<void>;
73
- stopImpersonating: (adminUserId: string) => Promise<void>;
74
- /** Send a magic link email to the given address */
75
- sendMagicLink: (params: {
76
- email: string;
77
- callbackURL?: string;
78
- }) => Promise<void>;
79
- /** Handle auth API requests (forwards to Better Auth) */
80
- handler: (request: Request) => Promise<Response>;
81
- /** The underlying Better Auth instance */
82
- api: unknown;
83
- };
84
-
85
- export type { AuthUser as A, AuthConfig as a, AuthEnvConfig as b, AuthInstance as c, AuthContext as d, AuthenticatedContext as e };
@@ -1,62 +0,0 @@
1
- import { Permissions, Grant } from '@cfast/permissions';
2
-
3
- type AuthUser = {
4
- id: string;
5
- email: string;
6
- name: string;
7
- avatarUrl: string | null;
8
- roles: string[];
9
- isImpersonating?: boolean;
10
- realUser?: {
11
- id: string;
12
- name: string;
13
- };
14
- };
15
- type AuthContext = {
16
- user: AuthUser | null;
17
- grants: Grant[];
18
- };
19
- type AuthenticatedContext = {
20
- user: AuthUser;
21
- grants: Grant[];
22
- };
23
- type AuthConfig = {
24
- permissions: Permissions;
25
- passkeys?: {
26
- rpName: string;
27
- rpId: string;
28
- };
29
- magicLink?: {
30
- sendMagicLink: (params: {
31
- email: string;
32
- url: string;
33
- }) => Promise<void>;
34
- };
35
- session?: {
36
- expiresIn?: string;
37
- };
38
- redirects?: {
39
- afterLogin?: string;
40
- loginPath?: string;
41
- };
42
- anonymousRoles?: string[];
43
- defaultRoles?: string[];
44
- };
45
- type AuthEnvConfig = {
46
- d1: D1Database;
47
- appUrl: string;
48
- };
49
- type AuthInstance = {
50
- createContext: (request: Request) => Promise<AuthContext>;
51
- requireUser: (request: Request) => Promise<AuthenticatedContext>;
52
- getRoles: (userId: string) => Promise<string[]>;
53
- setRole: (userId: string, role: string) => Promise<void>;
54
- setRoles: (userId: string, roles: string[]) => Promise<void>;
55
- removeRole: (userId: string, role: string) => Promise<void>;
56
- /** Handle auth API requests (forwards to Better Auth) */
57
- handler: (request: Request) => Promise<Response>;
58
- /** The underlying Better Auth instance */
59
- api: unknown;
60
- };
61
-
62
- export type { AuthUser as A, AuthConfig as a, AuthEnvConfig as b, AuthInstance as c, AuthContext as d, AuthenticatedContext as e };