@cfast/auth 0.0.1
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/LICENSE +21 -0
- package/README.md +394 -0
- package/dist/client.d.ts +123 -0
- package/dist/client.js +226 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.js +260 -0
- package/dist/plugin.d.ts +41 -0
- package/dist/plugin.js +14 -0
- package/dist/schema.d.ts +1052 -0
- package/dist/schema.js +85 -0
- package/dist/types-19GeiMs4.d.ts +64 -0
- package/dist/types-8eZilolN.d.ts +63 -0
- package/dist/types-DZ7AeznW.d.ts +74 -0
- package/dist/types-DdbPIOVK.d.ts +85 -0
- package/dist/types-DrnTPiku.d.ts +62 -0
- package/dist/types-ghXti5CW.d.ts +191 -0
- package/package.json +66 -0
package/dist/schema.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/schema.ts
|
|
2
|
+
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
3
|
+
var users = sqliteTable("users", {
|
|
4
|
+
id: text("id").primaryKey(),
|
|
5
|
+
email: text("email").notNull().unique(),
|
|
6
|
+
name: text("name").notNull(),
|
|
7
|
+
image: text("image"),
|
|
8
|
+
emailVerified: integer("email_verified", { mode: "boolean" }).notNull().default(false),
|
|
9
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
10
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
11
|
+
});
|
|
12
|
+
var sessions = sqliteTable("sessions", {
|
|
13
|
+
id: text("id").primaryKey(),
|
|
14
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
15
|
+
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
16
|
+
token: text("token").notNull().unique(),
|
|
17
|
+
ipAddress: text("ip_address"),
|
|
18
|
+
userAgent: text("user_agent"),
|
|
19
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
20
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
21
|
+
});
|
|
22
|
+
var accounts = sqliteTable("accounts", {
|
|
23
|
+
id: text("id").primaryKey(),
|
|
24
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
25
|
+
accountId: text("account_id").notNull(),
|
|
26
|
+
providerId: text("provider_id").notNull(),
|
|
27
|
+
accessToken: text("access_token"),
|
|
28
|
+
refreshToken: text("refresh_token"),
|
|
29
|
+
accessTokenExpiresAt: integer("access_token_expires_at", {
|
|
30
|
+
mode: "timestamp"
|
|
31
|
+
}),
|
|
32
|
+
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
|
|
33
|
+
mode: "timestamp"
|
|
34
|
+
}),
|
|
35
|
+
scope: text("scope"),
|
|
36
|
+
idToken: text("id_token"),
|
|
37
|
+
password: text("password"),
|
|
38
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
39
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
40
|
+
});
|
|
41
|
+
var verifications = sqliteTable("verifications", {
|
|
42
|
+
id: text("id").primaryKey(),
|
|
43
|
+
identifier: text("identifier").notNull(),
|
|
44
|
+
value: text("value").notNull(),
|
|
45
|
+
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
46
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
47
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
48
|
+
});
|
|
49
|
+
var passkeys = sqliteTable("passkeys", {
|
|
50
|
+
id: text("id").primaryKey(),
|
|
51
|
+
name: text("name"),
|
|
52
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
53
|
+
publicKey: text("public_key").notNull(),
|
|
54
|
+
credentialId: text("credential_id").notNull().unique(),
|
|
55
|
+
counter: integer("counter").notNull().default(0),
|
|
56
|
+
deviceType: text("device_type"),
|
|
57
|
+
backedUp: integer("backed_up", { mode: "boolean" }).default(false),
|
|
58
|
+
transports: text("transports"),
|
|
59
|
+
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
|
|
60
|
+
() => /* @__PURE__ */ new Date()
|
|
61
|
+
)
|
|
62
|
+
});
|
|
63
|
+
var roles = sqliteTable("roles", {
|
|
64
|
+
id: text("id").primaryKey(),
|
|
65
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
66
|
+
role: text("role").notNull(),
|
|
67
|
+
grantedBy: text("granted_by").references(() => users.id),
|
|
68
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
69
|
+
});
|
|
70
|
+
var impersonationLogs = sqliteTable("impersonation_logs", {
|
|
71
|
+
id: text("id").primaryKey(),
|
|
72
|
+
adminId: text("admin_id").notNull().references(() => users.id),
|
|
73
|
+
targetUserId: text("target_user_id").notNull().references(() => users.id),
|
|
74
|
+
startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
75
|
+
endedAt: integer("ended_at", { mode: "timestamp" })
|
|
76
|
+
});
|
|
77
|
+
export {
|
|
78
|
+
accounts,
|
|
79
|
+
impersonationLogs,
|
|
80
|
+
passkeys,
|
|
81
|
+
roles,
|
|
82
|
+
sessions,
|
|
83
|
+
users,
|
|
84
|
+
verifications
|
|
85
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
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 };
|
|
@@ -0,0 +1,63 @@
|
|
|
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 };
|
|
@@ -0,0 +1,74 @@
|
|
|
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 };
|
|
@@ -0,0 +1,85 @@
|
|
|
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 };
|
|
@@ -0,0 +1,62 @@
|
|
|
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 };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Permissions, Grant } from '@cfast/permissions';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The authenticated user object available throughout the application.
|
|
5
|
+
*
|
|
6
|
+
* Contains identity fields, assigned roles, and optional impersonation state.
|
|
7
|
+
* This is the user shape returned by {@link AuthContext} and {@link AuthenticatedContext},
|
|
8
|
+
* and is the same object passed to `createDb({ user })` for permission resolution.
|
|
9
|
+
*/
|
|
10
|
+
type AuthUser = {
|
|
11
|
+
/** Unique user identifier (UUID from Better Auth). */
|
|
12
|
+
id: string;
|
|
13
|
+
/** The user's email address, used for magic link authentication. */
|
|
14
|
+
email: string;
|
|
15
|
+
/** Display name for the user. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** URL to the user's avatar image, or `null` if not set. */
|
|
18
|
+
avatarUrl: string | null;
|
|
19
|
+
/** Roles assigned to this user, matching role names from `@cfast/permissions`. */
|
|
20
|
+
roles: string[];
|
|
21
|
+
/** Whether an admin is currently impersonating this user. */
|
|
22
|
+
isImpersonating?: boolean;
|
|
23
|
+
/** The real admin user performing the impersonation, if active. */
|
|
24
|
+
realUser?: {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The auth context for a request, which may or may not be authenticated.
|
|
31
|
+
*
|
|
32
|
+
* When the user is not logged in, `user` is `null` and `grants` contains
|
|
33
|
+
* only the anonymous role grants. Use {@link AuthenticatedContext} when
|
|
34
|
+
* you need a guaranteed non-null user.
|
|
35
|
+
*/
|
|
36
|
+
type AuthContext = {
|
|
37
|
+
/** The current user, or `null` if the request is unauthenticated. */
|
|
38
|
+
user: AuthUser | null;
|
|
39
|
+
/** Permission grants resolved from the user's roles (or anonymous roles). */
|
|
40
|
+
grants: Grant[];
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* A narrowed {@link AuthContext} where the user is guaranteed to be present.
|
|
44
|
+
*
|
|
45
|
+
* Returned by `auth.requireUser()`, which redirects to the login page if
|
|
46
|
+
* the request is unauthenticated. Use this in loaders and actions that
|
|
47
|
+
* require a logged-in user.
|
|
48
|
+
*/
|
|
49
|
+
type AuthenticatedContext = {
|
|
50
|
+
/** The authenticated user (always present). */
|
|
51
|
+
user: AuthUser;
|
|
52
|
+
/** Permission grants resolved from the user's assigned roles. */
|
|
53
|
+
grants: Grant[];
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Configuration for {@link createAuth}.
|
|
57
|
+
*
|
|
58
|
+
* Defines authentication methods, session behavior, role management rules,
|
|
59
|
+
* and integration with `@cfast/permissions`.
|
|
60
|
+
*/
|
|
61
|
+
type AuthConfig = {
|
|
62
|
+
/** The permissions config from `definePermissions()`. Roles are inferred from this. */
|
|
63
|
+
permissions: Permissions;
|
|
64
|
+
/** Optional Drizzle schema override for the Better Auth database adapter. */
|
|
65
|
+
schema?: Record<string, unknown>;
|
|
66
|
+
/** WebAuthn passkey configuration. Required to enable passkey authentication. */
|
|
67
|
+
passkeys?: {
|
|
68
|
+
/** Relying party display name shown during WebAuthn registration. */
|
|
69
|
+
rpName: string;
|
|
70
|
+
/** Relying party identifier, typically the app's domain (e.g., `"myapp.com"`). */
|
|
71
|
+
rpId: string;
|
|
72
|
+
};
|
|
73
|
+
/** Magic link email configuration. Required to enable magic link authentication. */
|
|
74
|
+
magicLink?: {
|
|
75
|
+
/** Callback to send the magic link email. Receives the user's email and the login URL. */
|
|
76
|
+
sendMagicLink: (params: {
|
|
77
|
+
email: string;
|
|
78
|
+
url: string;
|
|
79
|
+
}) => Promise<void>;
|
|
80
|
+
};
|
|
81
|
+
/** Session lifetime configuration. */
|
|
82
|
+
session?: {
|
|
83
|
+
/** How long sessions last before expiring (e.g., `"30d"`, `"12h"`, `"60m"`). Defaults to `"30d"`. */
|
|
84
|
+
expiresIn?: string;
|
|
85
|
+
};
|
|
86
|
+
/** Redirect paths for the authentication flow. */
|
|
87
|
+
redirects?: {
|
|
88
|
+
/** Where to redirect after successful login. Defaults to `"/"`. */
|
|
89
|
+
afterLogin?: string;
|
|
90
|
+
/** Where to send unauthenticated users. Defaults to `"/login"`. */
|
|
91
|
+
loginPath?: string;
|
|
92
|
+
};
|
|
93
|
+
/** Roles assigned to unauthenticated (anonymous) requests for permission resolution. */
|
|
94
|
+
anonymousRoles?: string[];
|
|
95
|
+
/** Default roles assigned to authenticated users who have no explicit role assignments. Defaults to `["reader"]`. */
|
|
96
|
+
defaultRoles?: string[];
|
|
97
|
+
/** Custom table name for storing role assignments. Defaults to `"roles"`. */
|
|
98
|
+
roleTableName?: string;
|
|
99
|
+
/** Maps each role to the set of roles it is allowed to assign. Controls who can promote whom. */
|
|
100
|
+
roleGrants?: Record<string, string[]>;
|
|
101
|
+
/** Impersonation feature configuration. */
|
|
102
|
+
impersonation?: {
|
|
103
|
+
/** Roles permitted to impersonate other users. Defaults to `["admin"]`. */
|
|
104
|
+
allowedRoles?: string[];
|
|
105
|
+
};
|
|
106
|
+
/** Custom email template functions. */
|
|
107
|
+
templates?: {
|
|
108
|
+
/** Returns an HTML string for the magic link email. Receives the login URL and recipient email. */
|
|
109
|
+
magicLink?: (props: {
|
|
110
|
+
url: string;
|
|
111
|
+
email: string;
|
|
112
|
+
}) => string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Per-request environment bindings required to initialize an {@link AuthInstance}.
|
|
117
|
+
*
|
|
118
|
+
* Passed to the `initAuth()` function returned by {@link createAuth}.
|
|
119
|
+
*/
|
|
120
|
+
type AuthEnvConfig = {
|
|
121
|
+
/** The Cloudflare D1 database binding for session and user storage. */
|
|
122
|
+
d1: D1Database;
|
|
123
|
+
/** The application's public URL, used as the base URL for Better Auth endpoints. */
|
|
124
|
+
appUrl: string;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* The initialized auth instance with methods for session management, role assignment,
|
|
128
|
+
* impersonation, and request handling.
|
|
129
|
+
*
|
|
130
|
+
* Created by calling the `initAuth()` function returned from {@link createAuth}
|
|
131
|
+
* with an {@link AuthEnvConfig}.
|
|
132
|
+
*/
|
|
133
|
+
type AuthInstance = {
|
|
134
|
+
/**
|
|
135
|
+
* Builds an {@link AuthContext} from the request's session cookie.
|
|
136
|
+
* Returns a context with `user: null` if the session is invalid or missing.
|
|
137
|
+
*/
|
|
138
|
+
createContext: (request: Request) => Promise<AuthContext>;
|
|
139
|
+
/**
|
|
140
|
+
* Like {@link AuthInstance.createContext | createContext}, but redirects to the login page
|
|
141
|
+
* if the user is not authenticated. Sets a `cfast_redirect_to` cookie so the user
|
|
142
|
+
* returns to the original URL after login.
|
|
143
|
+
*
|
|
144
|
+
* @throws {Response} A 302 redirect response when the user is not authenticated.
|
|
145
|
+
*/
|
|
146
|
+
requireUser: (request: Request) => Promise<AuthenticatedContext>;
|
|
147
|
+
/** Retrieves all roles assigned to a user. */
|
|
148
|
+
getRoles: (userId: string) => Promise<string[]>;
|
|
149
|
+
/**
|
|
150
|
+
* Assigns a single role to a user (additive, does not remove existing roles).
|
|
151
|
+
* When `caller.callerRoles` is provided, validates against `roleGrants` rules.
|
|
152
|
+
*/
|
|
153
|
+
setRole: (userId: string, role: string, caller?: {
|
|
154
|
+
callerRoles?: string[];
|
|
155
|
+
}) => Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* Replaces all of a user's roles with the given set.
|
|
158
|
+
* When `caller.callerRoles` is provided, validates each role against `roleGrants` rules.
|
|
159
|
+
*/
|
|
160
|
+
setRoles: (userId: string, roles: string[], caller?: {
|
|
161
|
+
callerRoles?: string[];
|
|
162
|
+
}) => Promise<void>;
|
|
163
|
+
/** Removes a single role from a user. */
|
|
164
|
+
removeRole: (userId: string, role: string) => Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Starts an impersonation session where the admin acts as the target user.
|
|
167
|
+
* Only users with roles listed in `impersonation.allowedRoles` can impersonate.
|
|
168
|
+
*
|
|
169
|
+
* @throws {Error} If the admin user's roles do not permit impersonation.
|
|
170
|
+
*/
|
|
171
|
+
impersonate: (adminUserId: string, targetUserId: string) => Promise<void>;
|
|
172
|
+
/** Ends all active impersonation sessions for the given admin user. */
|
|
173
|
+
stopImpersonating: (adminUserId: string) => Promise<void>;
|
|
174
|
+
/**
|
|
175
|
+
* Sends a magic link email to the given address for passwordless authentication.
|
|
176
|
+
*
|
|
177
|
+
* @throws {Error} If the magic link plugin is not configured.
|
|
178
|
+
*/
|
|
179
|
+
sendMagicLink: (params: {
|
|
180
|
+
/** The recipient's email address. */
|
|
181
|
+
email: string;
|
|
182
|
+
/** Override the post-login redirect URL. Defaults to `redirects.afterLogin`. */
|
|
183
|
+
callbackURL?: string;
|
|
184
|
+
}) => Promise<void>;
|
|
185
|
+
/** Forwards an HTTP request to the Better Auth handler for processing auth API routes. */
|
|
186
|
+
handler: (request: Request) => Promise<Response>;
|
|
187
|
+
/** The underlying Better Auth instance, for escape-hatch usage. */
|
|
188
|
+
api: unknown;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export type { AuthUser as A, AuthConfig as a, AuthEnvConfig as b, AuthInstance as c, AuthContext as d, AuthenticatedContext as e };
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cfast/auth",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Authentication for Cloudflare Workers: magic email, passkeys, roles, and impersonation",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/DanielMSchmidt/cfast.git",
|
|
9
|
+
"directory": "packages/auth"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./client": {
|
|
20
|
+
"import": "./dist/client.js",
|
|
21
|
+
"types": "./dist/client.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"./plugin": {
|
|
24
|
+
"import": "./dist/plugin.js",
|
|
25
|
+
"types": "./dist/plugin.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"./schema": {
|
|
28
|
+
"import": "./dist/schema.js",
|
|
29
|
+
"types": "./dist/schema.d.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"sideEffects": false,
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"better-auth": ">=1",
|
|
41
|
+
"drizzle-orm": ">=0.35",
|
|
42
|
+
"react": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@cfast/permissions": "0.0.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@cloudflare/workers-types": "^4.20260305.1",
|
|
49
|
+
"@testing-library/react": "^16.3.2",
|
|
50
|
+
"@types/react": "^19.0.0",
|
|
51
|
+
"better-auth": "^1.2.0",
|
|
52
|
+
"drizzle-orm": "^0.45.1",
|
|
53
|
+
"jsdom": "^28.1.0",
|
|
54
|
+
"react": "^19.0.0",
|
|
55
|
+
"tsup": "^8",
|
|
56
|
+
"typescript": "^5.7",
|
|
57
|
+
"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
|
+
}
|
|
66
|
+
}
|