@opensaas/stack-auth 0.1.0 → 0.1.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +11 -0
- package/CLAUDE.md +196 -0
- package/LICENSE +21 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +24 -2
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +16 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +7 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema-converter.d.ts +40 -0
- package/dist/server/schema-converter.d.ts.map +1 -0
- package/dist/server/schema-converter.js +250 -0
- package/dist/server/schema-converter.js.map +1 -0
- package/package.json +14 -4
- package/src/config/index.ts +28 -2
- package/src/config/types.ts +19 -1
- package/src/plugins/index.ts +7 -0
- package/src/server/index.ts +3 -0
- package/src/server/schema-converter.ts +299 -0
- package/tests/config.test.ts +270 -0
- package/tests/lists.test.ts +356 -0
- package/tests/schema-converter.test.ts +304 -0
- package/tsconfig.test.json +7 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert Better Auth database schema to OpenSaaS list configs
|
|
3
|
+
* Allows dynamic list generation from Better Auth plugins
|
|
4
|
+
*/
|
|
5
|
+
import { list } from '@opensaas/stack-core';
|
|
6
|
+
import { text, timestamp, checkbox, integer } from '@opensaas/stack-core/fields';
|
|
7
|
+
/**
|
|
8
|
+
* Convert Better Auth field type to OpenSaaS field config
|
|
9
|
+
*/
|
|
10
|
+
function convertField(fieldName, betterAuthField) {
|
|
11
|
+
const { type, required, unique, defaultValue, references } = betterAuthField;
|
|
12
|
+
// System fields are auto-generated by OpenSaaS
|
|
13
|
+
if (fieldName === 'id' || fieldName === 'createdAt' || fieldName === 'updatedAt') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
// Handle references (relationships)
|
|
17
|
+
if (references) {
|
|
18
|
+
// Relationships are handled separately
|
|
19
|
+
// Return null here and handle in relationship pass
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
// Map Better Auth types to OpenSaaS field types
|
|
23
|
+
switch (type) {
|
|
24
|
+
case 'string':
|
|
25
|
+
return text({
|
|
26
|
+
validation: { isRequired: required },
|
|
27
|
+
isIndexed: unique ? 'unique' : undefined,
|
|
28
|
+
defaultValue: typeof defaultValue === 'string' ? defaultValue : undefined,
|
|
29
|
+
});
|
|
30
|
+
case 'number':
|
|
31
|
+
return integer({
|
|
32
|
+
validation: { isRequired: required },
|
|
33
|
+
defaultValue: typeof defaultValue === 'number' ? defaultValue : undefined,
|
|
34
|
+
});
|
|
35
|
+
case 'boolean':
|
|
36
|
+
return checkbox({
|
|
37
|
+
defaultValue: typeof defaultValue === 'boolean' ? defaultValue : false,
|
|
38
|
+
});
|
|
39
|
+
case 'date':
|
|
40
|
+
return timestamp({
|
|
41
|
+
defaultValue: defaultValue === 'now' ? { kind: 'now' } : undefined,
|
|
42
|
+
});
|
|
43
|
+
default:
|
|
44
|
+
// Unknown type - default to text
|
|
45
|
+
console.warn(`[stack-auth] Unknown Better Auth field type "${type}" for field "${fieldName}", defaulting to text field`);
|
|
46
|
+
return text({
|
|
47
|
+
validation: { isRequired: required },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get default access control for auth tables
|
|
53
|
+
* Most auth tables should only be accessible to their owners
|
|
54
|
+
*/
|
|
55
|
+
function getDefaultAccess(tableName) {
|
|
56
|
+
const lowerTableName = tableName.toLowerCase();
|
|
57
|
+
// User table - special access control
|
|
58
|
+
if (lowerTableName === 'user') {
|
|
59
|
+
return {
|
|
60
|
+
operation: {
|
|
61
|
+
query: () => true, // Anyone can query users
|
|
62
|
+
create: () => true, // Anyone can create (sign up)
|
|
63
|
+
update: ({ session, item }) => {
|
|
64
|
+
if (!session)
|
|
65
|
+
return false;
|
|
66
|
+
const userId = session.userId;
|
|
67
|
+
const itemId = item?.id;
|
|
68
|
+
return userId === itemId;
|
|
69
|
+
},
|
|
70
|
+
delete: ({ session, item }) => {
|
|
71
|
+
if (!session)
|
|
72
|
+
return false;
|
|
73
|
+
const userId = session.userId;
|
|
74
|
+
const itemId = item?.id;
|
|
75
|
+
return userId === itemId;
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Session table
|
|
81
|
+
if (lowerTableName === 'session') {
|
|
82
|
+
return {
|
|
83
|
+
operation: {
|
|
84
|
+
query: ({ session }) => {
|
|
85
|
+
if (!session)
|
|
86
|
+
return false;
|
|
87
|
+
const userId = session.userId;
|
|
88
|
+
if (!userId)
|
|
89
|
+
return false;
|
|
90
|
+
return {
|
|
91
|
+
user: { id: { equals: userId } },
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
create: () => true, // Better-auth handles session creation
|
|
95
|
+
update: () => false, // No manual updates
|
|
96
|
+
delete: ({ session, item }) => {
|
|
97
|
+
if (!session)
|
|
98
|
+
return false;
|
|
99
|
+
const userId = session.userId;
|
|
100
|
+
const itemUserId = item?.user?.id;
|
|
101
|
+
return userId === itemUserId;
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// Account table
|
|
107
|
+
if (lowerTableName === 'account') {
|
|
108
|
+
return {
|
|
109
|
+
operation: {
|
|
110
|
+
query: ({ session }) => {
|
|
111
|
+
if (!session)
|
|
112
|
+
return false;
|
|
113
|
+
const userId = session.userId;
|
|
114
|
+
if (!userId)
|
|
115
|
+
return false;
|
|
116
|
+
return {
|
|
117
|
+
user: { id: { equals: userId } },
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
create: () => true, // Better-auth handles account creation
|
|
121
|
+
update: ({ session, item }) => {
|
|
122
|
+
if (!session)
|
|
123
|
+
return false;
|
|
124
|
+
const userId = session.userId;
|
|
125
|
+
const itemUserId = item?.user?.id;
|
|
126
|
+
return userId === itemUserId;
|
|
127
|
+
},
|
|
128
|
+
delete: ({ session, item }) => {
|
|
129
|
+
if (!session)
|
|
130
|
+
return false;
|
|
131
|
+
const userId = session.userId;
|
|
132
|
+
const itemUserId = item?.user?.id;
|
|
133
|
+
return userId === itemUserId;
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Verification table
|
|
139
|
+
if (lowerTableName === 'verification') {
|
|
140
|
+
return {
|
|
141
|
+
operation: {
|
|
142
|
+
query: () => false, // No public querying
|
|
143
|
+
create: () => true, // Better-auth creates verification tokens
|
|
144
|
+
update: () => false, // No updates
|
|
145
|
+
delete: () => true, // Better-auth deletes used/expired tokens
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// OAuth tables (from MCP/OIDC plugins)
|
|
150
|
+
if (lowerTableName === 'oauthapplication' ||
|
|
151
|
+
lowerTableName === 'oauthaccesstoken' ||
|
|
152
|
+
lowerTableName === 'oauthconsent') {
|
|
153
|
+
return {
|
|
154
|
+
operation: {
|
|
155
|
+
query: ({ session }) => {
|
|
156
|
+
if (!session)
|
|
157
|
+
return false;
|
|
158
|
+
const userId = session.userId;
|
|
159
|
+
if (!userId)
|
|
160
|
+
return false;
|
|
161
|
+
// Filter by userId if field exists
|
|
162
|
+
return {
|
|
163
|
+
userId: { equals: userId },
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
create: () => true, // Better-auth/plugins handle creation
|
|
167
|
+
update: ({ session, item }) => {
|
|
168
|
+
if (!session)
|
|
169
|
+
return false;
|
|
170
|
+
const userId = session.userId;
|
|
171
|
+
const itemUserId = item?.userId;
|
|
172
|
+
return userId === itemUserId;
|
|
173
|
+
},
|
|
174
|
+
delete: ({ session, item }) => {
|
|
175
|
+
if (!session)
|
|
176
|
+
return false;
|
|
177
|
+
const userId = session.userId;
|
|
178
|
+
const itemUserId = item?.userId;
|
|
179
|
+
return userId === itemUserId;
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// Default: restrict all access (safest default)
|
|
185
|
+
return {
|
|
186
|
+
operation: {
|
|
187
|
+
query: () => false,
|
|
188
|
+
create: () => false,
|
|
189
|
+
update: () => false,
|
|
190
|
+
delete: () => false,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Convert Better Auth table schema to OpenSaaS ListConfig
|
|
196
|
+
*/
|
|
197
|
+
export function convertTableToList(tableName, tableSchema) {
|
|
198
|
+
const fields = {};
|
|
199
|
+
// First pass: convert regular fields
|
|
200
|
+
for (const [fieldName, fieldAttr] of Object.entries(tableSchema.fields)) {
|
|
201
|
+
const fieldConfig = convertField(fieldName, fieldAttr);
|
|
202
|
+
if (fieldConfig) {
|
|
203
|
+
fields[fieldName] = fieldConfig;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Second pass: add relationships
|
|
207
|
+
// NOTE: Better Auth uses one-way references, but OpenSaaS requires bidirectional relationships
|
|
208
|
+
// We skip relationship conversion for now and rely on the foreign key fields
|
|
209
|
+
// Users can manually add bidirectional relationships if needed by extending the User list
|
|
210
|
+
for (const [fieldName, fieldAttr] of Object.entries(tableSchema.fields)) {
|
|
211
|
+
if (fieldAttr.references) {
|
|
212
|
+
// For now, treat reference fields as regular text fields (foreign keys)
|
|
213
|
+
// This preserves the userId, clientId, etc. fields that Better Auth expects
|
|
214
|
+
if (!fields[fieldName]) {
|
|
215
|
+
fields[fieldName] = text({
|
|
216
|
+
validation: { isRequired: fieldAttr.required },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Create list config with default access control
|
|
222
|
+
return list({
|
|
223
|
+
fields,
|
|
224
|
+
access: getDefaultAccess(tableName),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Convert all Better Auth tables to OpenSaaS list configs
|
|
229
|
+
* This is called by withAuth() to generate lists from Better Auth + plugins
|
|
230
|
+
*/
|
|
231
|
+
export function convertBetterAuthSchema(tables) {
|
|
232
|
+
const lists = {};
|
|
233
|
+
for (const [tableName, tableSchema] of Object.entries(tables)) {
|
|
234
|
+
// Convert table name to PascalCase for OpenSaaS list key
|
|
235
|
+
const listKey = tableSchema.modelName || toPascalCase(tableName);
|
|
236
|
+
lists[listKey] = convertTableToList(tableName, tableSchema);
|
|
237
|
+
}
|
|
238
|
+
return lists;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Convert table name to PascalCase
|
|
242
|
+
* e.g., "oauth_application" -> "OauthApplication"
|
|
243
|
+
*/
|
|
244
|
+
function toPascalCase(str) {
|
|
245
|
+
return str
|
|
246
|
+
.split(/[_-]/)
|
|
247
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
248
|
+
.join('');
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=schema-converter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-converter.js","sourceRoot":"","sources":["../../src/server/schema-converter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensaas/stack-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Better-auth integration for OpenSaas Stack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"./ui": {
|
|
22
22
|
"types": "./dist/ui/index.d.ts",
|
|
23
23
|
"default": "./dist/ui/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./plugins": {
|
|
26
|
+
"types": "./dist/plugins/index.d.ts",
|
|
27
|
+
"default": "./dist/plugins/index.js"
|
|
24
28
|
}
|
|
25
29
|
},
|
|
26
30
|
"keywords": [
|
|
@@ -35,21 +39,27 @@
|
|
|
35
39
|
"better-auth": "^1.3.29",
|
|
36
40
|
"react": "^18.0.0 || ^19.0.0",
|
|
37
41
|
"next": "^15.0.0 || ^16.0.0",
|
|
38
|
-
"@opensaas/stack-core": "0.1.
|
|
42
|
+
"@opensaas/stack-core": "0.1.1"
|
|
39
43
|
},
|
|
40
44
|
"dependencies": {},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@types/node": "^24.7.2",
|
|
43
47
|
"@types/react": "^19.2.2",
|
|
48
|
+
"@vitest/coverage-v8": "^4.0.4",
|
|
49
|
+
"@vitest/ui": "^4.0.0",
|
|
44
50
|
"better-auth": "^1.3.29",
|
|
45
51
|
"next": "^16.0.0",
|
|
46
52
|
"react": "^19.2.0",
|
|
47
53
|
"typescript": "^5.9.3",
|
|
48
|
-
"
|
|
54
|
+
"vitest": "^4.0.0",
|
|
55
|
+
"@opensaas/stack-core": "0.1.1"
|
|
49
56
|
},
|
|
50
57
|
"scripts": {
|
|
51
58
|
"build": "tsc",
|
|
52
59
|
"dev": "tsc --watch",
|
|
53
|
-
"clean": "rm -rf .turbo dist tsconfig.tsbuildinfo"
|
|
60
|
+
"clean": "rm -rf .turbo dist tsconfig.tsbuildinfo",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:ui": "vitest --ui",
|
|
63
|
+
"test:coverage": "vitest run --coverage"
|
|
54
64
|
}
|
|
55
65
|
}
|
package/src/config/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
PasswordResetConfig,
|
|
8
8
|
} from './types.js'
|
|
9
9
|
import { getAuthLists } from '../lists/index.js'
|
|
10
|
+
import { convertBetterAuthSchema } from '../server/schema-converter.js'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Normalize auth configuration with defaults
|
|
@@ -65,6 +66,7 @@ export function normalizeAuthConfig(config: AuthConfig): NormalizedAuthConfig {
|
|
|
65
66
|
console.log(`Subject: ${subject}`)
|
|
66
67
|
console.log(`Body: ${html}`)
|
|
67
68
|
}),
|
|
69
|
+
betterAuthPlugins: config.betterAuthPlugins || [],
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -114,8 +116,8 @@ export function authConfig(config: AuthConfig): AuthConfig {
|
|
|
114
116
|
export function withAuth(opensaasConfig: OpenSaasConfig, authConfig: AuthConfig): OpenSaasConfig {
|
|
115
117
|
const normalized = normalizeAuthConfig(authConfig)
|
|
116
118
|
|
|
117
|
-
// Get auth lists
|
|
118
|
-
const authLists =
|
|
119
|
+
// Get auth lists from plugins
|
|
120
|
+
const authLists = getAuthListsFromPlugins(normalized)
|
|
119
121
|
|
|
120
122
|
// Merge auth lists with user lists (auth lists take priority)
|
|
121
123
|
const mergedLists = {
|
|
@@ -136,5 +138,29 @@ export function withAuth(opensaasConfig: OpenSaasConfig, authConfig: AuthConfig)
|
|
|
136
138
|
return result
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Get auth lists by extracting schemas from Better Auth plugins
|
|
143
|
+
* This inspects the plugin objects directly without requiring a database connection
|
|
144
|
+
*/
|
|
145
|
+
function getAuthListsFromPlugins(authConfig: NormalizedAuthConfig) {
|
|
146
|
+
// Start with base Better Auth tables (always required)
|
|
147
|
+
const authLists = getAuthLists(authConfig.extendUserList)
|
|
148
|
+
|
|
149
|
+
// Extract additional tables from plugins
|
|
150
|
+
for (const plugin of authConfig.betterAuthPlugins) {
|
|
151
|
+
if (plugin && typeof plugin === 'object' && 'schema' in plugin) {
|
|
152
|
+
// Plugin has schema property - convert to OpenSaaS lists
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Plugin schema types are dynamic
|
|
154
|
+
const pluginSchema = plugin.schema as any
|
|
155
|
+
|
|
156
|
+
// Convert plugin schema to OpenSaaS lists and merge
|
|
157
|
+
const pluginLists = convertBetterAuthSchema(pluginSchema)
|
|
158
|
+
Object.assign(authLists, pluginLists)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return authLists
|
|
163
|
+
}
|
|
164
|
+
|
|
139
165
|
export type { AuthConfig, NormalizedAuthConfig }
|
|
140
166
|
export * from './types.js'
|
package/src/config/types.ts
CHANGED
|
@@ -151,6 +151,22 @@ export type AuthConfig = {
|
|
|
151
151
|
* ```
|
|
152
152
|
*/
|
|
153
153
|
sendEmail?: (params: { to: string; subject: string; html: string }) => Promise<void>
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Additional Better Auth plugins to enable
|
|
157
|
+
* Allows integrating any Better Auth plugin (MCP, 2FA, etc.)
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* import { mcp } from 'better-auth/plugins'
|
|
162
|
+
*
|
|
163
|
+
* betterAuthPlugins: [
|
|
164
|
+
* mcp({ loginPage: '/sign-in' })
|
|
165
|
+
* ]
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Better Auth plugin types are not exposed, must use any
|
|
169
|
+
betterAuthPlugins?: any[]
|
|
154
170
|
}
|
|
155
171
|
|
|
156
172
|
/**
|
|
@@ -158,9 +174,11 @@ export type AuthConfig = {
|
|
|
158
174
|
* Used after parsing user config
|
|
159
175
|
*/
|
|
160
176
|
export type NormalizedAuthConfig = Required<
|
|
161
|
-
Omit<AuthConfig, 'emailAndPassword' | 'emailVerification' | 'passwordReset'>
|
|
177
|
+
Omit<AuthConfig, 'emailAndPassword' | 'emailVerification' | 'passwordReset' | 'betterAuthPlugins'>
|
|
162
178
|
> & {
|
|
163
179
|
emailAndPassword: Required<EmailPasswordConfig>
|
|
164
180
|
emailVerification: Required<EmailVerificationConfig>
|
|
165
181
|
passwordReset: Required<PasswordResetConfig>
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Better Auth plugin types are not exposed, must use any
|
|
183
|
+
betterAuthPlugins: any[]
|
|
166
184
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -78,6 +78,9 @@ export function createAuth(
|
|
|
78
78
|
},
|
|
79
79
|
{} as Record<string, { clientId: string; clientSecret: string }>,
|
|
80
80
|
),
|
|
81
|
+
|
|
82
|
+
// Pass through any additional Better Auth plugins
|
|
83
|
+
plugins: authConfig.betterAuthPlugins || [],
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
return betterAuth(betterAuthConfig)
|