@opensaas/stack-auth 0.1.4 → 0.1.6
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 +17 -0
- package/README.md +1 -1
- package/dist/config/index.d.ts +0 -42
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +0 -83
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin.d.ts +25 -0
- package/dist/config/plugin.d.ts.map +1 -0
- package/dist/config/plugin.js +80 -0
- package/dist/config/plugin.js.map +1 -0
- package/dist/index.d.ts +14 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -13
- package/dist/index.js.map +1 -1
- package/dist/mcp/better-auth.d.ts +1 -1
- package/dist/mcp/better-auth.js +1 -1
- package/dist/server/index.d.ts +3 -5
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +73 -37
- package/dist/server/index.js.map +1 -1
- package/package.json +12 -3
- package/src/config/index.ts +0 -95
- package/src/config/plugin.ts +86 -0
- package/src/index.ts +14 -13
- package/src/mcp/better-auth.ts +1 -1
- package/src/server/index.ts +86 -45
- package/tests/config.test.ts +223 -64
- package/tsconfig.tsbuildinfo +1 -1
package/src/config/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { OpenSaasConfig } from '@opensaas/stack-core'
|
|
2
1
|
import type {
|
|
3
2
|
AuthConfig,
|
|
4
3
|
NormalizedAuthConfig,
|
|
@@ -6,8 +5,6 @@ import type {
|
|
|
6
5
|
EmailVerificationConfig,
|
|
7
6
|
PasswordResetConfig,
|
|
8
7
|
} from './types.js'
|
|
9
|
-
import { getAuthLists } from '../lists/index.js'
|
|
10
|
-
import { convertBetterAuthSchema } from '../server/schema-converter.js'
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* Normalize auth configuration with defaults
|
|
@@ -70,97 +67,5 @@ export function normalizeAuthConfig(config: AuthConfig): NormalizedAuthConfig {
|
|
|
70
67
|
}
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
/**
|
|
74
|
-
* Auth configuration builder
|
|
75
|
-
* Use this to create an auth configuration object
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* ```typescript
|
|
79
|
-
* import { authConfig } from '@opensaas/stack-auth'
|
|
80
|
-
*
|
|
81
|
-
* const auth = authConfig({
|
|
82
|
-
* emailAndPassword: { enabled: true },
|
|
83
|
-
* emailVerification: { enabled: true },
|
|
84
|
-
* socialProviders: {
|
|
85
|
-
* github: { clientId: '...', clientSecret: '...' }
|
|
86
|
-
* }
|
|
87
|
-
* })
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
export function authConfig(config: AuthConfig): AuthConfig {
|
|
91
|
-
return config
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Wrap an OpenSaas config with better-auth integration
|
|
96
|
-
* This merges the auth lists into the user's config and sets up session handling
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```typescript
|
|
100
|
-
* import { config } from '@opensaas/stack-core'
|
|
101
|
-
* import { withAuth, authConfig } from '@opensaas/stack-auth'
|
|
102
|
-
*
|
|
103
|
-
* export default withAuth(
|
|
104
|
-
* config({
|
|
105
|
-
* db: { provider: 'sqlite', url: 'file:./dev.db' },
|
|
106
|
-
* lists: {
|
|
107
|
-
* Post: list({ ... })
|
|
108
|
-
* }
|
|
109
|
-
* }),
|
|
110
|
-
* authConfig({
|
|
111
|
-
* emailAndPassword: { enabled: true }
|
|
112
|
-
* })
|
|
113
|
-
* )
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
export function withAuth(opensaasConfig: OpenSaasConfig, authConfig: AuthConfig): OpenSaasConfig {
|
|
117
|
-
const normalized = normalizeAuthConfig(authConfig)
|
|
118
|
-
|
|
119
|
-
// Get auth lists from plugins
|
|
120
|
-
const authLists = getAuthListsFromPlugins(normalized)
|
|
121
|
-
|
|
122
|
-
// Merge auth lists with user lists (auth lists take priority)
|
|
123
|
-
const mergedLists = {
|
|
124
|
-
...opensaasConfig.lists,
|
|
125
|
-
...authLists,
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Return merged config with auth config attached
|
|
129
|
-
// Note: Session integration happens in the generator/context
|
|
130
|
-
const result: OpenSaasConfig & { __authConfig?: NormalizedAuthConfig } = {
|
|
131
|
-
...opensaasConfig,
|
|
132
|
-
lists: mergedLists,
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Store auth config for internal use
|
|
136
|
-
result.__authConfig = normalized
|
|
137
|
-
|
|
138
|
-
return result
|
|
139
|
-
}
|
|
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
|
-
|
|
165
70
|
export type { AuthConfig, NormalizedAuthConfig }
|
|
166
71
|
export * from './types.js'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Plugin } from '@opensaas/stack-core'
|
|
2
|
+
import type { AuthConfig, NormalizedAuthConfig } from './types.js'
|
|
3
|
+
import { normalizeAuthConfig } from './index.js'
|
|
4
|
+
import { getAuthLists } from '../lists/index.js'
|
|
5
|
+
import { convertBetterAuthSchema } from '../server/schema-converter.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Auth plugin for OpenSaas Stack
|
|
9
|
+
* Provides Better-auth integration with automatic list generation and session management
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { config } from '@opensaas/stack-core'
|
|
14
|
+
* import { authPlugin } from '@opensaas/stack-auth'
|
|
15
|
+
*
|
|
16
|
+
* export default config({
|
|
17
|
+
* plugins: [
|
|
18
|
+
* authPlugin({
|
|
19
|
+
* emailAndPassword: { enabled: true },
|
|
20
|
+
* sessionFields: ['userId', 'email', 'name', 'role']
|
|
21
|
+
* })
|
|
22
|
+
* ],
|
|
23
|
+
* db: { provider: 'sqlite', url: 'file:./dev.db' },
|
|
24
|
+
* lists: { Post: list({...}) }
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function authPlugin(config: AuthConfig): Plugin {
|
|
29
|
+
const normalized = normalizeAuthConfig(config)
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
name: 'auth',
|
|
33
|
+
version: '0.1.0',
|
|
34
|
+
|
|
35
|
+
init: async (context) => {
|
|
36
|
+
// Get auth lists from base Better Auth schema
|
|
37
|
+
const authLists = getAuthLists(normalized.extendUserList)
|
|
38
|
+
|
|
39
|
+
// Extract additional lists from Better Auth plugins
|
|
40
|
+
for (const plugin of normalized.betterAuthPlugins) {
|
|
41
|
+
if (plugin && typeof plugin === 'object' && 'schema' in plugin) {
|
|
42
|
+
// Plugin has schema property - convert to OpenSaaS lists
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Plugin schema types are dynamic
|
|
44
|
+
const pluginSchema = plugin.schema as any
|
|
45
|
+
const pluginLists = convertBetterAuthSchema(pluginSchema)
|
|
46
|
+
|
|
47
|
+
// Add or extend lists from plugin
|
|
48
|
+
for (const [listName, listConfig] of Object.entries(pluginLists)) {
|
|
49
|
+
if (context.config.lists[listName]) {
|
|
50
|
+
// List exists, extend it
|
|
51
|
+
context.extendList(listName, {
|
|
52
|
+
fields: listConfig.fields,
|
|
53
|
+
hooks: listConfig.hooks,
|
|
54
|
+
access: listConfig.access,
|
|
55
|
+
mcp: listConfig.mcp,
|
|
56
|
+
})
|
|
57
|
+
} else {
|
|
58
|
+
// List doesn't exist, add it
|
|
59
|
+
context.addList(listName, listConfig)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add all auth lists
|
|
66
|
+
for (const [listName, listConfig] of Object.entries(authLists)) {
|
|
67
|
+
if (context.config.lists[listName]) {
|
|
68
|
+
// If user defined a User list, extend it with auth fields
|
|
69
|
+
context.extendList(listName, {
|
|
70
|
+
fields: listConfig.fields,
|
|
71
|
+
hooks: listConfig.hooks,
|
|
72
|
+
access: listConfig.access,
|
|
73
|
+
mcp: listConfig.mcp,
|
|
74
|
+
})
|
|
75
|
+
} else {
|
|
76
|
+
// Otherwise, add the auth list
|
|
77
|
+
context.addList(listName, listConfig)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Store auth config for runtime access
|
|
82
|
+
// Access at runtime via: config._pluginData.auth
|
|
83
|
+
context.setPluginData<NormalizedAuthConfig>('auth', normalized)
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,29 +7,30 @@
|
|
|
7
7
|
* - Auto-generated User, Session, Account, Verification lists
|
|
8
8
|
* - Session integration with OpenSaas access control
|
|
9
9
|
* - Pre-built auth UI components (SignIn, SignUp, ForgotPassword)
|
|
10
|
-
* - Easy configuration with
|
|
10
|
+
* - Easy configuration with authPlugin()
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```typescript
|
|
14
14
|
* // opensaas.config.ts
|
|
15
15
|
* import { config } from '@opensaas/stack-core'
|
|
16
|
-
* import {
|
|
16
|
+
* import { authPlugin } from '@opensaas/stack-auth'
|
|
17
17
|
*
|
|
18
|
-
* export default
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* }
|
|
27
|
-
* )
|
|
18
|
+
* export default config({
|
|
19
|
+
* plugins: [
|
|
20
|
+
* authPlugin({
|
|
21
|
+
* emailAndPassword: { enabled: true },
|
|
22
|
+
* emailVerification: { enabled: true },
|
|
23
|
+
* })
|
|
24
|
+
* ],
|
|
25
|
+
* db: { provider: 'sqlite', url: 'file:./dev.db' },
|
|
26
|
+
* lists: { ... }
|
|
27
|
+
* })
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
// Config exports
|
|
32
|
-
export {
|
|
32
|
+
export { normalizeAuthConfig } from './config/index.js'
|
|
33
|
+
export { authPlugin } from './config/plugin.js'
|
|
33
34
|
export type { AuthConfig, NormalizedAuthConfig } from './config/index.js'
|
|
34
35
|
export type * from './config/types.js'
|
|
35
36
|
|
package/src/mcp/better-auth.ts
CHANGED
|
@@ -93,7 +93,7 @@ export function withMcpAuth(
|
|
|
93
93
|
* @example
|
|
94
94
|
* ```typescript
|
|
95
95
|
* const mcpSession = await auth.api.getMcpSession({ headers: req.headers })
|
|
96
|
-
* const context = getContext(mcpSessionToContextSession(mcpSession))
|
|
96
|
+
* const context = await getContext(mcpSessionToContextSession(mcpSession))
|
|
97
97
|
* const posts = await context.db.post.findMany()
|
|
98
98
|
* ```
|
|
99
99
|
*/
|
package/src/server/index.ts
CHANGED
|
@@ -25,65 +25,106 @@ function getDatabaseConfig(
|
|
|
25
25
|
* // lib/auth.ts
|
|
26
26
|
* import { createAuth } from '@opensaas/stack-auth/server'
|
|
27
27
|
* import config from '../opensaas.config'
|
|
28
|
+
* import { rawOpensaasContext } from '@/.opensaas/context'
|
|
28
29
|
*
|
|
29
|
-
* export const auth = createAuth(config)
|
|
30
|
+
* export const auth = createAuth(config, rawOpensaasContext)
|
|
30
31
|
* ```
|
|
31
32
|
*/
|
|
32
33
|
export function createAuth(
|
|
33
|
-
opensaasConfig: OpenSaasConfig
|
|
34
|
-
context: AccessContext
|
|
34
|
+
opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>,
|
|
35
|
+
context: AccessContext | Promise<AccessContext>,
|
|
35
36
|
) {
|
|
36
|
-
//
|
|
37
|
-
const
|
|
37
|
+
// Resolve config and context asynchronously
|
|
38
|
+
const configPromise = Promise.resolve(opensaasConfig)
|
|
39
|
+
const contextPromise = Promise.resolve(context)
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// Create auth instance lazily when needed
|
|
42
|
+
let authInstance: ReturnType<typeof betterAuth> | null = null
|
|
43
|
+
let authPromise: Promise<ReturnType<typeof betterAuth>> | null = null
|
|
44
|
+
|
|
45
|
+
async function getAuthInstance() {
|
|
46
|
+
if (authInstance) return authInstance
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
if (!authPromise) {
|
|
49
|
+
authPromise = (async () => {
|
|
50
|
+
const resolvedConfig = await configPromise
|
|
51
|
+
const resolvedContext = await contextPromise
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
// Extract auth config from plugin data
|
|
54
|
+
const authConfig = resolvedConfig._pluginData?.auth as NormalizedAuthConfig | undefined
|
|
55
|
+
|
|
56
|
+
if (!authConfig) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'Auth config not found. Make sure to use authPlugin() in your opensaas.config.ts',
|
|
59
|
+
)
|
|
54
60
|
}
|
|
55
|
-
: undefined,
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
// Build better-auth configuration
|
|
63
|
+
const betterAuthConfig: BetterAuthOptions = {
|
|
64
|
+
database: getDatabaseConfig(resolvedConfig.db, resolvedContext),
|
|
65
|
+
|
|
66
|
+
// Enable email and password if configured
|
|
67
|
+
emailAndPassword: authConfig.emailAndPassword.enabled
|
|
68
|
+
? {
|
|
69
|
+
enabled: true,
|
|
70
|
+
requireEmailVerification: authConfig.emailVerification.enabled,
|
|
71
|
+
}
|
|
72
|
+
: undefined,
|
|
73
|
+
|
|
74
|
+
// Configure session
|
|
75
|
+
session: {
|
|
76
|
+
expiresIn: authConfig.session.expiresIn || 604800,
|
|
77
|
+
updateAge: authConfig.session.updateAge
|
|
78
|
+
? (authConfig.session.expiresIn || 604800) / 10
|
|
79
|
+
: 0,
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Trust host (required for production)
|
|
83
|
+
trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(',') || [],
|
|
84
|
+
|
|
85
|
+
// Social providers
|
|
86
|
+
socialProviders: Object.entries(authConfig.socialProviders)
|
|
87
|
+
.filter(([_, config]) => config?.enabled !== false)
|
|
88
|
+
.reduce(
|
|
89
|
+
(acc, [provider, config]) => {
|
|
90
|
+
if (config) {
|
|
91
|
+
acc[provider] = {
|
|
92
|
+
clientId: config.clientId,
|
|
93
|
+
clientSecret: config.clientSecret,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return acc
|
|
97
|
+
},
|
|
98
|
+
{} as Record<string, { clientId: string; clientSecret: string }>,
|
|
99
|
+
),
|
|
100
|
+
|
|
101
|
+
// Pass through any additional Better Auth plugins
|
|
102
|
+
plugins: authConfig.betterAuthPlugins || [],
|
|
103
|
+
}
|
|
62
104
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
socialProviders: Object.entries(authConfig.socialProviders)
|
|
68
|
-
.filter(([_, config]) => config?.enabled !== false)
|
|
69
|
-
.reduce(
|
|
70
|
-
(acc, [provider, config]) => {
|
|
71
|
-
if (config) {
|
|
72
|
-
acc[provider] = {
|
|
73
|
-
clientId: config.clientId,
|
|
74
|
-
clientSecret: config.clientSecret,
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return acc
|
|
78
|
-
},
|
|
79
|
-
{} as Record<string, { clientId: string; clientSecret: string }>,
|
|
80
|
-
),
|
|
105
|
+
authInstance = betterAuth(betterAuthConfig)
|
|
106
|
+
return authInstance
|
|
107
|
+
})()
|
|
108
|
+
}
|
|
81
109
|
|
|
82
|
-
|
|
83
|
-
plugins: authConfig.betterAuthPlugins || [],
|
|
110
|
+
return authPromise
|
|
84
111
|
}
|
|
85
112
|
|
|
86
|
-
|
|
113
|
+
// Return a proxy that lazily initializes the auth instance
|
|
114
|
+
return new Proxy({} as ReturnType<typeof betterAuth>, {
|
|
115
|
+
get(_, prop) {
|
|
116
|
+
return (...args: unknown[]) => {
|
|
117
|
+
return (async () => {
|
|
118
|
+
const instance = await getAuthInstance()
|
|
119
|
+
const value = instance[prop as keyof typeof instance]
|
|
120
|
+
if (typeof value === 'function') {
|
|
121
|
+
return (value as (...args: unknown[]) => unknown).apply(instance, args)
|
|
122
|
+
}
|
|
123
|
+
return value
|
|
124
|
+
})()
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
})
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
/**
|