@checkstack/auth-backend 0.0.2 → 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/CHANGELOG.md +92 -0
- package/drizzle/0002_lowly_squirrel_girl.sql +43 -0
- package/drizzle/0003_tranquil_sally_floyd.sql +8 -0
- package/drizzle/meta/0002_snapshot.json +1017 -0
- package/drizzle/meta/0003_snapshot.json +1050 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +1 -1
- package/src/index.ts +13 -5
- package/src/router.ts +435 -0
- package/src/schema.ts +107 -0
- package/src/teams.test.ts +1230 -0
- package/src/utils/auth-error-redirect.ts +2 -3
- package/src/utils/user.test.ts +60 -41
- package/src/utils/user.ts +9 -1
|
@@ -15,15 +15,14 @@ export function encodeAuthError(message: string): string {
|
|
|
15
15
|
/**
|
|
16
16
|
* Build auth error redirect URL
|
|
17
17
|
* @param errorMessage - User-friendly error message
|
|
18
|
-
* @param frontendUrl - Frontend base URL (defaults to
|
|
18
|
+
* @param frontendUrl - Frontend base URL (defaults to BASE_URL env var)
|
|
19
19
|
* @returns The full redirect URL to the error page
|
|
20
20
|
*/
|
|
21
21
|
export function buildAuthErrorUrl(
|
|
22
22
|
errorMessage: string,
|
|
23
23
|
frontendUrl?: string
|
|
24
24
|
): string {
|
|
25
|
-
const base =
|
|
26
|
-
frontendUrl || process.env.VITE_FRONTEND_URL || "http://localhost:5173";
|
|
25
|
+
const base = frontendUrl || process.env.BASE_URL;
|
|
27
26
|
const encoded = encodeAuthError(errorMessage);
|
|
28
27
|
return `${base}/auth/error?error=${encodeURIComponent(encoded)}`;
|
|
29
28
|
}
|
package/src/utils/user.test.ts
CHANGED
|
@@ -3,37 +3,38 @@ import { enrichUser } from "./user";
|
|
|
3
3
|
import { User } from "better-auth/types";
|
|
4
4
|
|
|
5
5
|
// Mock Drizzle DB
|
|
6
|
-
const createMockDb = (data: {
|
|
7
|
-
|
|
6
|
+
const createMockDb = (data: {
|
|
7
|
+
roles?: unknown[];
|
|
8
|
+
permissions?: unknown[];
|
|
9
|
+
teams?: unknown[];
|
|
10
|
+
}) => {
|
|
11
|
+
const mockDb: unknown = {
|
|
8
12
|
select: mock(() => mockDb),
|
|
9
13
|
from: mock(() => mockDb),
|
|
10
14
|
innerJoin: mock(() => mockDb),
|
|
11
15
|
where: mock(() => mockDb),
|
|
12
16
|
};
|
|
13
17
|
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// We need to look at the schema name or some identifier.
|
|
22
|
-
// Since we are mocking the schema as well, we can check equality.
|
|
23
|
-
// However, for a simple mock, we can just alternate or use a counter.
|
|
24
|
-
|
|
25
|
-
// In enrichUser:
|
|
26
|
-
// 1. select from userRole (inner join role)
|
|
27
|
-
// 2. select from rolePermission (inner join permission) -> for each role
|
|
28
|
-
|
|
29
|
-
// Let's use a simpler approach: track call count for this specific mock instance
|
|
30
|
-
if (!mockDb._callCount) mockDb._callCount = 0;
|
|
31
|
-
mockDb._callCount++;
|
|
18
|
+
// Track call count for sequential responses
|
|
19
|
+
// Call order in enrichUser: 1=roles, 2+=permissions per role, final=teams
|
|
20
|
+
let callCount = 0;
|
|
21
|
+
const nonAdminRoles = (data.roles || []).filter(
|
|
22
|
+
(r) => (r as { roleId: string }).roleId !== "admin"
|
|
23
|
+
);
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
// eslint-disable-next-line unicorn/no-thenable
|
|
26
|
+
(mockDb as { then: unknown }).then = (resolve: (arg0: unknown) => void) => {
|
|
27
|
+
callCount++;
|
|
28
|
+
if (callCount === 1) {
|
|
29
|
+
// First call: get roles
|
|
34
30
|
return resolve(data.roles || []);
|
|
35
31
|
}
|
|
36
|
-
|
|
32
|
+
if (callCount <= 1 + nonAdminRoles.length && nonAdminRoles.length > 0) {
|
|
33
|
+
// Permission calls for each non-admin role
|
|
34
|
+
return resolve(data.permissions || []);
|
|
35
|
+
}
|
|
36
|
+
// Team memberships (final call)
|
|
37
|
+
return resolve(data.teams || []);
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
return mockDb;
|
|
@@ -52,48 +53,66 @@ describe("enrichUser", () => {
|
|
|
52
53
|
it("should enrich user with admin role and wildcard permission", async () => {
|
|
53
54
|
const mockDb = createMockDb({
|
|
54
55
|
roles: [{ roleId: "admin" }],
|
|
56
|
+
teams: [{ teamId: "team-1" }],
|
|
55
57
|
});
|
|
56
58
|
|
|
57
|
-
const result = await enrichUser(
|
|
59
|
+
const result = await enrichUser(
|
|
60
|
+
baseUser,
|
|
61
|
+
mockDb as Parameters<typeof enrichUser>[1]
|
|
62
|
+
);
|
|
58
63
|
|
|
59
64
|
expect(result.roles).toContain("admin");
|
|
60
65
|
expect(result.permissions).toContain("*");
|
|
66
|
+
expect(result.teamIds).toEqual(["team-1"]);
|
|
61
67
|
});
|
|
62
68
|
|
|
63
69
|
it("should enrich user with custom roles and permissions", async () => {
|
|
64
70
|
const mockDb = createMockDb({
|
|
65
|
-
roles: [{ roleId: "editor" }
|
|
66
|
-
permissions: [{ permissionId: "blog.
|
|
71
|
+
roles: [{ roleId: "editor" }],
|
|
72
|
+
permissions: [{ permissionId: "blog.edit" }],
|
|
73
|
+
teams: [],
|
|
67
74
|
});
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
let callCount = 0;
|
|
75
|
-
// eslint-disable-next-line unicorn/no-thenable
|
|
76
|
-
mockDb.then = (resolve: (arg0: unknown) => void) => {
|
|
77
|
-
callCount++;
|
|
78
|
-
if (callCount === 1) return resolve([{ roleId: "editor" }]);
|
|
79
|
-
if (callCount === 2) return resolve([{ permissionId: "blog.edit" }]);
|
|
80
|
-
return resolve([]);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const result = await enrichUser(baseUser, mockDb);
|
|
76
|
+
const result = await enrichUser(
|
|
77
|
+
baseUser,
|
|
78
|
+
mockDb as Parameters<typeof enrichUser>[1]
|
|
79
|
+
);
|
|
84
80
|
|
|
85
81
|
expect(result.roles).toContain("editor");
|
|
86
82
|
expect(result.permissions).toContain("blog.edit");
|
|
83
|
+
expect(result.teamIds).toEqual([]);
|
|
87
84
|
});
|
|
88
85
|
|
|
89
86
|
it("should handle user with no roles", async () => {
|
|
90
87
|
const mockDb = createMockDb({
|
|
91
88
|
roles: [],
|
|
89
|
+
teams: [],
|
|
92
90
|
});
|
|
93
91
|
|
|
94
|
-
const result = await enrichUser(
|
|
92
|
+
const result = await enrichUser(
|
|
93
|
+
baseUser,
|
|
94
|
+
mockDb as Parameters<typeof enrichUser>[1]
|
|
95
|
+
);
|
|
95
96
|
|
|
96
97
|
expect(result.roles).toEqual([]);
|
|
97
98
|
expect(result.permissions).toEqual([]);
|
|
99
|
+
expect(result.teamIds).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should include multiple team memberships", async () => {
|
|
103
|
+
const mockDb = createMockDb({
|
|
104
|
+
roles: [{ roleId: "admin" }],
|
|
105
|
+
teams: [{ teamId: "team-1" }, { teamId: "team-2" }, { teamId: "team-3" }],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const result = await enrichUser(
|
|
109
|
+
baseUser,
|
|
110
|
+
mockDb as Parameters<typeof enrichUser>[1]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(result.teamIds).toHaveLength(3);
|
|
114
|
+
expect(result.teamIds).toContain("team-1");
|
|
115
|
+
expect(result.teamIds).toContain("team-2");
|
|
116
|
+
expect(result.teamIds).toContain("team-3");
|
|
98
117
|
});
|
|
99
118
|
});
|
package/src/utils/user.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { RealUser } from "@checkstack/backend-api";
|
|
|
5
5
|
import * as schema from "../schema";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Enriches a better-auth User with roles and
|
|
8
|
+
* Enriches a better-auth User with roles, permissions, and team memberships from the database.
|
|
9
9
|
* Returns a RealUser type for use in the RPC context.
|
|
10
10
|
*/
|
|
11
11
|
export const enrichUser = async (
|
|
@@ -48,6 +48,13 @@ export const enrichUser = async (
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// 3. Get Team memberships
|
|
52
|
+
const userTeams = await db
|
|
53
|
+
.select({ teamId: schema.userTeam.teamId })
|
|
54
|
+
.from(schema.userTeam)
|
|
55
|
+
.where(eq(schema.userTeam.userId, user.id));
|
|
56
|
+
const teamIds = userTeams.map((t) => t.teamId);
|
|
57
|
+
|
|
51
58
|
return {
|
|
52
59
|
// Spread user first to preserve additional properties
|
|
53
60
|
...user,
|
|
@@ -58,5 +65,6 @@ export const enrichUser = async (
|
|
|
58
65
|
name: user.name,
|
|
59
66
|
roles,
|
|
60
67
|
permissions: [...permissions],
|
|
68
|
+
teamIds,
|
|
61
69
|
};
|
|
62
70
|
};
|