@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.
@@ -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.0",
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.0"
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
- "@opensaas/stack-core": "0.1.0"
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
  }
@@ -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 with user extensions
118
- const authLists = getAuthLists(normalized.extendUserList)
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'
@@ -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
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Re-export Better Auth plugins for convenience
3
+ * This allows users to import plugins from @opensaas/stack-auth/plugins
4
+ * instead of better-auth/plugins
5
+ */
6
+
7
+ export { mcp } from 'better-auth/plugins'
@@ -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)