@opensaas/stack-auth 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.
Files changed (56) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/INTEGRATION_SUMMARY.md +425 -0
  3. package/README.md +445 -0
  4. package/dist/client/index.d.ts +38 -0
  5. package/dist/client/index.d.ts.map +1 -0
  6. package/dist/client/index.js +23 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/config/index.d.ts +50 -0
  9. package/dist/config/index.d.ts.map +1 -0
  10. package/dist/config/index.js +115 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/config/types.d.ts +160 -0
  13. package/dist/config/types.d.ts.map +1 -0
  14. package/dist/config/types.js +2 -0
  15. package/dist/config/types.js.map +1 -0
  16. package/dist/index.d.ts +35 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +34 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/lists/index.d.ts +46 -0
  21. package/dist/lists/index.d.ts.map +1 -0
  22. package/dist/lists/index.js +227 -0
  23. package/dist/lists/index.js.map +1 -0
  24. package/dist/server/index.d.ts +27 -0
  25. package/dist/server/index.d.ts.map +1 -0
  26. package/dist/server/index.js +90 -0
  27. package/dist/server/index.js.map +1 -0
  28. package/dist/ui/components/ForgotPasswordForm.d.ts +36 -0
  29. package/dist/ui/components/ForgotPasswordForm.d.ts.map +1 -0
  30. package/dist/ui/components/ForgotPasswordForm.js +50 -0
  31. package/dist/ui/components/ForgotPasswordForm.js.map +1 -0
  32. package/dist/ui/components/SignInForm.d.ts +52 -0
  33. package/dist/ui/components/SignInForm.d.ts.map +1 -0
  34. package/dist/ui/components/SignInForm.js +66 -0
  35. package/dist/ui/components/SignInForm.js.map +1 -0
  36. package/dist/ui/components/SignUpForm.d.ts +56 -0
  37. package/dist/ui/components/SignUpForm.d.ts.map +1 -0
  38. package/dist/ui/components/SignUpForm.js +74 -0
  39. package/dist/ui/components/SignUpForm.js.map +1 -0
  40. package/dist/ui/index.d.ts +7 -0
  41. package/dist/ui/index.d.ts.map +1 -0
  42. package/dist/ui/index.js +4 -0
  43. package/dist/ui/index.js.map +1 -0
  44. package/package.json +55 -0
  45. package/src/client/index.ts +44 -0
  46. package/src/config/index.ts +140 -0
  47. package/src/config/types.ts +166 -0
  48. package/src/index.ts +44 -0
  49. package/src/lists/index.ts +245 -0
  50. package/src/server/index.ts +120 -0
  51. package/src/ui/components/ForgotPasswordForm.tsx +120 -0
  52. package/src/ui/components/SignInForm.tsx +191 -0
  53. package/src/ui/components/SignUpForm.tsx +238 -0
  54. package/src/ui/index.ts +7 -0
  55. package/tsconfig.json +14 -0
  56. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,160 @@
1
+ import type { ExtendUserListConfig } from '../lists/index.js';
2
+ /**
3
+ * OAuth provider configuration
4
+ */
5
+ export type OAuthProvider = {
6
+ clientId: string;
7
+ clientSecret: string;
8
+ enabled?: boolean;
9
+ };
10
+ /**
11
+ * Social provider configurations
12
+ */
13
+ export type SocialProvidersConfig = {
14
+ github?: OAuthProvider;
15
+ google?: OAuthProvider;
16
+ discord?: OAuthProvider;
17
+ twitter?: OAuthProvider;
18
+ [key: string]: OAuthProvider | undefined;
19
+ };
20
+ /**
21
+ * Email and password configuration
22
+ */
23
+ export type EmailPasswordConfig = {
24
+ enabled: boolean;
25
+ /**
26
+ * Minimum password length
27
+ * @default 8
28
+ */
29
+ minPasswordLength?: number;
30
+ /**
31
+ * Require password confirmation
32
+ * @default true
33
+ */
34
+ requireConfirmation?: boolean;
35
+ };
36
+ /**
37
+ * Email verification configuration
38
+ */
39
+ export type EmailVerificationConfig = {
40
+ enabled: boolean;
41
+ /**
42
+ * Send verification email on sign up
43
+ * @default true
44
+ */
45
+ sendOnSignUp?: boolean;
46
+ /**
47
+ * Token expiration in seconds
48
+ * @default 86400 (24 hours)
49
+ */
50
+ tokenExpiration?: number;
51
+ };
52
+ /**
53
+ * Password reset configuration
54
+ */
55
+ export type PasswordResetConfig = {
56
+ enabled: boolean;
57
+ /**
58
+ * Token expiration in seconds
59
+ * @default 3600 (1 hour)
60
+ */
61
+ tokenExpiration?: number;
62
+ };
63
+ /**
64
+ * Session configuration
65
+ */
66
+ export type SessionConfig = {
67
+ /**
68
+ * Session expiration in seconds
69
+ * @default 604800 (7 days)
70
+ */
71
+ expiresIn?: number;
72
+ /**
73
+ * Update session expiration on each request
74
+ * @default true
75
+ */
76
+ updateAge?: boolean;
77
+ };
78
+ /**
79
+ * Auth configuration options
80
+ */
81
+ export type AuthConfig = {
82
+ /**
83
+ * Email and password authentication
84
+ */
85
+ emailAndPassword?: EmailPasswordConfig | {
86
+ enabled: true;
87
+ };
88
+ /**
89
+ * Email verification
90
+ */
91
+ emailVerification?: EmailVerificationConfig | {
92
+ enabled: true;
93
+ };
94
+ /**
95
+ * Password reset
96
+ */
97
+ passwordReset?: PasswordResetConfig | {
98
+ enabled: true;
99
+ };
100
+ /**
101
+ * OAuth/social providers
102
+ */
103
+ socialProviders?: SocialProvidersConfig;
104
+ /**
105
+ * Session configuration
106
+ */
107
+ session?: SessionConfig;
108
+ /**
109
+ * Which fields to include in the session object
110
+ * This determines what data is available in access control functions
111
+ * @default ['userId', 'email', 'name']
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * sessionFields: ['userId', 'email', 'name', 'role']
116
+ * // session will be: { userId: string, email: string, name: string, role: string }
117
+ * ```
118
+ */
119
+ sessionFields?: string[];
120
+ /**
121
+ * Extend the auto-generated User list with custom fields
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * extendUserList: {
126
+ * fields: {
127
+ * role: text({ defaultValue: 'user' }),
128
+ * company: text(),
129
+ * }
130
+ * }
131
+ * ```
132
+ */
133
+ extendUserList?: ExtendUserListConfig;
134
+ /**
135
+ * Custom email sending function for verification and password reset
136
+ * If not provided, emails will be logged to console
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * sendEmail: async ({ to, subject, html }) => {
141
+ * await resend.emails.send({ to, subject, html })
142
+ * }
143
+ * ```
144
+ */
145
+ sendEmail?: (params: {
146
+ to: string;
147
+ subject: string;
148
+ html: string;
149
+ }) => Promise<void>;
150
+ };
151
+ /**
152
+ * Internal normalized auth configuration
153
+ * Used after parsing user config
154
+ */
155
+ export type NormalizedAuthConfig = Required<Omit<AuthConfig, 'emailAndPassword' | 'emailVerification' | 'passwordReset'>> & {
156
+ emailAndPassword: Required<EmailPasswordConfig>;
157
+ emailVerification: Required<EmailVerificationConfig>;
158
+ passwordReset: Required<PasswordResetConfig>;
159
+ };
160
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE7D;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAA;CACzC,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB;;OAEG;IACH,gBAAgB,CAAC,EAAE,mBAAmB,GAAG;QAAE,OAAO,EAAE,IAAI,CAAA;KAAE,CAAA;IAE1D;;OAEG;IACH,iBAAiB,CAAC,EAAE,uBAAuB,GAAG;QAAE,OAAO,EAAE,IAAI,CAAA;KAAE,CAAA;IAE/D;;OAEG;IACH,aAAa,CAAC,EAAE,mBAAmB,GAAG;QAAE,OAAO,EAAE,IAAI,CAAA;KAAE,CAAA;IAEvD;;OAEG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAA;IAEvC;;OAEG;IACH,OAAO,CAAC,EAAE,aAAa,CAAA;IAEvB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IAExB;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAA;IAErC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACrF,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CACzC,IAAI,CAAC,UAAU,EAAE,kBAAkB,GAAG,mBAAmB,GAAG,eAAe,CAAC,CAC7E,GAAG;IACF,gBAAgB,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAA;IAC/C,iBAAiB,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAA;IACpD,aAAa,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAA;CAC7C,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @opensaas/stack-auth
3
+ *
4
+ * Better-auth integration for OpenSaas Stack
5
+ *
6
+ * This package provides:
7
+ * - Auto-generated User, Session, Account, Verification lists
8
+ * - Session integration with OpenSaas access control
9
+ * - Pre-built auth UI components (SignIn, SignUp, ForgotPassword)
10
+ * - Easy configuration with withAuth() wrapper
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // opensaas.config.ts
15
+ * import { config } from '@opensaas/stack-core'
16
+ * import { withAuth, authConfig } from '@opensaas/stack-auth'
17
+ *
18
+ * export default withAuth(
19
+ * config({
20
+ * db: { provider: 'sqlite', url: 'file:./dev.db' },
21
+ * lists: { ... }
22
+ * }),
23
+ * authConfig({
24
+ * emailAndPassword: { enabled: true },
25
+ * emailVerification: { enabled: true },
26
+ * })
27
+ * )
28
+ * ```
29
+ */
30
+ export { withAuth, authConfig, normalizeAuthConfig } from './config/index.js';
31
+ export type { AuthConfig, NormalizedAuthConfig } from './config/index.js';
32
+ export type * from './config/types.js';
33
+ export { getAuthLists, createUserList, createSessionList, createAccountList, createVerificationList, } from './lists/index.js';
34
+ export type { ExtendUserListConfig } from './lists/index.js';
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC7E,YAAY,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AACzE,mBAAmB,mBAAmB,CAAA;AAGtC,OAAO,EACL,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @opensaas/stack-auth
3
+ *
4
+ * Better-auth integration for OpenSaas Stack
5
+ *
6
+ * This package provides:
7
+ * - Auto-generated User, Session, Account, Verification lists
8
+ * - Session integration with OpenSaas access control
9
+ * - Pre-built auth UI components (SignIn, SignUp, ForgotPassword)
10
+ * - Easy configuration with withAuth() wrapper
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // opensaas.config.ts
15
+ * import { config } from '@opensaas/stack-core'
16
+ * import { withAuth, authConfig } from '@opensaas/stack-auth'
17
+ *
18
+ * export default withAuth(
19
+ * config({
20
+ * db: { provider: 'sqlite', url: 'file:./dev.db' },
21
+ * lists: { ... }
22
+ * }),
23
+ * authConfig({
24
+ * emailAndPassword: { enabled: true },
25
+ * emailVerification: { enabled: true },
26
+ * })
27
+ * )
28
+ * ```
29
+ */
30
+ // Config exports
31
+ export { withAuth, authConfig, normalizeAuthConfig } from './config/index.js';
32
+ // List generators (for advanced use cases)
33
+ export { getAuthLists, createUserList, createSessionList, createAccountList, createVerificationList, } from './lists/index.js';
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,iBAAiB;AACjB,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAI7E,2CAA2C;AAC3C,OAAO,EACL,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,46 @@
1
+ import type { ListConfig, FieldConfig } from '@opensaas/stack-core';
2
+ /**
3
+ * Configuration for extending the auto-generated User list
4
+ */
5
+ export type ExtendUserListConfig = {
6
+ /**
7
+ * Additional fields to add to the User list
8
+ * You can add custom fields beyond the basic better-auth fields
9
+ */
10
+ fields?: Record<string, FieldConfig>;
11
+ /**
12
+ * Access control for the User list
13
+ * If not provided, defaults to basic access control (users can update their own records)
14
+ */
15
+ access?: ListConfig['access'];
16
+ /**
17
+ * Hooks for the User list
18
+ */
19
+ hooks?: ListConfig['hooks'];
20
+ };
21
+ /**
22
+ * Create the base User list with better-auth required fields
23
+ * This matches the better-auth user schema
24
+ */
25
+ export declare function createUserList(config?: ExtendUserListConfig): ListConfig;
26
+ /**
27
+ * Create the Session list for better-auth
28
+ * Stores active user sessions
29
+ */
30
+ export declare function createSessionList(): ListConfig;
31
+ /**
32
+ * Create the Account list for better-auth
33
+ * Stores OAuth provider accounts and credentials
34
+ */
35
+ export declare function createAccountList(): ListConfig;
36
+ /**
37
+ * Create the Verification list for better-auth
38
+ * Stores email verification tokens, password reset tokens, etc.
39
+ */
40
+ export declare function createVerificationList(): ListConfig;
41
+ /**
42
+ * Get all auth lists required by better-auth
43
+ * This is the main export used by withAuth()
44
+ */
45
+ export declare function getAuthLists(userConfig?: ExtendUserListConfig): Record<string, ListConfig>;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lists/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAEnE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACpC;;;OAGG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC7B;;OAEG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;CAC5B,CAAA;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,oBAAoB,GAAG,UAAU,CAqDxE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,UAAU,CA+C9C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,UAAU,CA0D9C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,UAAU,CA2BnD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAO1F"}
@@ -0,0 +1,227 @@
1
+ import { list } from '@opensaas/stack-core';
2
+ import { text, timestamp, checkbox, relationship } from '@opensaas/stack-core/fields';
3
+ /**
4
+ * Create the base User list with better-auth required fields
5
+ * This matches the better-auth user schema
6
+ */
7
+ export function createUserList(config) {
8
+ return list({
9
+ fields: {
10
+ // Better-auth required fields
11
+ name: text({
12
+ validation: { isRequired: true },
13
+ }),
14
+ email: text({
15
+ validation: { isRequired: true },
16
+ isIndexed: 'unique',
17
+ }),
18
+ emailVerified: checkbox({
19
+ defaultValue: false,
20
+ }),
21
+ image: text(),
22
+ // Relationships to other auth tables
23
+ sessions: relationship({
24
+ ref: 'Session.user',
25
+ many: true,
26
+ }),
27
+ accounts: relationship({
28
+ ref: 'Account.user',
29
+ many: true,
30
+ }),
31
+ // Custom fields from user config
32
+ ...(config?.fields || {}),
33
+ },
34
+ access: config?.access || {
35
+ operation: {
36
+ // Anyone can query users (for displaying names, etc.)
37
+ query: () => true,
38
+ // Anyone can create a user (sign up)
39
+ create: () => true,
40
+ // Only update your own user record
41
+ update: ({ session, item }) => {
42
+ if (!session)
43
+ return false;
44
+ const userId = session.userId;
45
+ const itemId = item?.id;
46
+ return userId === itemId;
47
+ },
48
+ // Only delete your own user record
49
+ delete: ({ session, item }) => {
50
+ if (!session)
51
+ return false;
52
+ const userId = session.userId;
53
+ const itemId = item?.id;
54
+ return userId === itemId;
55
+ },
56
+ },
57
+ },
58
+ hooks: config?.hooks,
59
+ });
60
+ }
61
+ /**
62
+ * Create the Session list for better-auth
63
+ * Stores active user sessions
64
+ */
65
+ export function createSessionList() {
66
+ return list({
67
+ fields: {
68
+ // Session token (stored in cookie, used as primary key)
69
+ token: text({
70
+ validation: { isRequired: true },
71
+ isIndexed: 'unique',
72
+ }),
73
+ // Expiration timestamp
74
+ expiresAt: timestamp(),
75
+ // Optional: IP address for security
76
+ ipAddress: text(),
77
+ // Optional: User agent for security
78
+ userAgent: text(),
79
+ // Relationship to user (userId will be auto-generated)
80
+ user: relationship({
81
+ ref: 'User.sessions',
82
+ }),
83
+ },
84
+ access: {
85
+ operation: {
86
+ // Only the session owner can query their sessions
87
+ query: ({ session }) => {
88
+ if (!session)
89
+ return false;
90
+ const userId = session.userId;
91
+ if (!userId)
92
+ return false;
93
+ // Return Prisma filter for nested relationship
94
+ return {
95
+ user: {
96
+ id: { equals: userId },
97
+ },
98
+ };
99
+ },
100
+ // Better-auth handles session creation
101
+ create: () => true,
102
+ // No manual updates
103
+ update: () => false,
104
+ // Better-auth handles session deletion (logout)
105
+ delete: ({ session, item }) => {
106
+ if (!session)
107
+ return false;
108
+ const userId = session.userId;
109
+ const itemUserId = item?.user?.id;
110
+ return userId === itemUserId;
111
+ },
112
+ },
113
+ },
114
+ });
115
+ }
116
+ /**
117
+ * Create the Account list for better-auth
118
+ * Stores OAuth provider accounts and credentials
119
+ */
120
+ export function createAccountList() {
121
+ return list({
122
+ fields: {
123
+ // Account identifier from provider
124
+ accountId: text({
125
+ validation: { isRequired: true },
126
+ }),
127
+ // Provider identifier (e.g., 'github', 'google', 'credentials')
128
+ providerId: text({
129
+ validation: { isRequired: true },
130
+ }),
131
+ // Relationship to user (userId will be auto-generated)
132
+ user: relationship({
133
+ ref: 'User.accounts',
134
+ }),
135
+ // OAuth tokens
136
+ accessToken: text(),
137
+ refreshToken: text(),
138
+ accessTokenExpiresAt: timestamp(),
139
+ refreshTokenExpiresAt: timestamp(),
140
+ scope: text(),
141
+ idToken: text(),
142
+ // Password hash for credential provider (better-auth stores in account table)
143
+ password: text(),
144
+ },
145
+ access: {
146
+ operation: {
147
+ // Only the account owner can query their accounts
148
+ query: ({ session }) => {
149
+ if (!session)
150
+ return false;
151
+ const userId = session.userId;
152
+ if (!userId)
153
+ return false;
154
+ // Return Prisma filter for nested relationship
155
+ return {
156
+ user: {
157
+ id: { equals: userId },
158
+ },
159
+ };
160
+ },
161
+ // Better-auth handles account creation
162
+ create: () => true,
163
+ // Better-auth handles account updates (token refresh)
164
+ update: ({ session, item }) => {
165
+ if (!session)
166
+ return false;
167
+ const userId = session.userId;
168
+ const itemUserId = item?.user?.id;
169
+ return userId === itemUserId;
170
+ },
171
+ // Account owner can delete their accounts
172
+ delete: ({ session, item }) => {
173
+ if (!session)
174
+ return false;
175
+ const userId = session.userId;
176
+ const itemUserId = item?.user?.id;
177
+ return userId === itemUserId;
178
+ },
179
+ },
180
+ },
181
+ });
182
+ }
183
+ /**
184
+ * Create the Verification list for better-auth
185
+ * Stores email verification tokens, password reset tokens, etc.
186
+ */
187
+ export function createVerificationList() {
188
+ return list({
189
+ fields: {
190
+ // Identifier (e.g., email address)
191
+ identifier: text({
192
+ validation: { isRequired: true },
193
+ }),
194
+ // Token value
195
+ value: text({
196
+ validation: { isRequired: true },
197
+ }),
198
+ // Expiration timestamp
199
+ expiresAt: timestamp(),
200
+ },
201
+ access: {
202
+ operation: {
203
+ // No public querying (better-auth handles verification internally)
204
+ query: () => false,
205
+ // Better-auth creates verification tokens
206
+ create: () => true,
207
+ // No updates
208
+ update: () => false,
209
+ // Better-auth deletes used/expired tokens
210
+ delete: () => true,
211
+ },
212
+ },
213
+ });
214
+ }
215
+ /**
216
+ * Get all auth lists required by better-auth
217
+ * This is the main export used by withAuth()
218
+ */
219
+ export function getAuthLists(userConfig) {
220
+ return {
221
+ User: createUserList(userConfig),
222
+ Session: createSessionList(),
223
+ Account: createAccountList(),
224
+ Verification: createVerificationList(),
225
+ };
226
+ }
227
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lists/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { betterAuth } from 'better-auth';
2
+ import type { BetterAuthOptions } from 'better-auth';
3
+ import type { OpenSaasConfig, AccessContext } from '@opensaas/stack-core';
4
+ import type { NormalizedAuthConfig } from '../config/types.js';
5
+ /**
6
+ * Create a better-auth instance from OpenSaas config
7
+ * This should be called once at app startup
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // lib/auth.ts
12
+ * import { createAuth } from '@opensaas/stack-auth/server'
13
+ * import config from '../opensaas.config'
14
+ *
15
+ * export const auth = createAuth(config)
16
+ * ```
17
+ */
18
+ export declare function createAuth(opensaasConfig: OpenSaasConfig & {
19
+ __authConfig?: NormalizedAuthConfig;
20
+ }, context: AccessContext): import("better-auth").Auth<BetterAuthOptions>;
21
+ /**
22
+ * Get session from better-auth and transform it to OpenSaas session format
23
+ * This is used internally by the generated context
24
+ */
25
+ export declare function getSessionFromAuth(auth: ReturnType<typeof betterAuth>, sessionFields: string[]): Promise<Record<string, unknown> | null>;
26
+ export type { BetterAuthOptions };
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAkB,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACzF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAc9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,cAAc,EAAE,cAAc,GAAG;IAAE,YAAY,CAAC,EAAE,oBAAoB,CAAA;CAAE,EACxE,OAAO,EAAE,aAAa,iDAkDvB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EACnC,aAAa,EAAE,MAAM,EAAE,2CA0BxB;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
@@ -0,0 +1,90 @@
1
+ import { betterAuth } from 'better-auth';
2
+ import { prismaAdapter } from 'better-auth/adapters/prisma';
3
+ /**
4
+ * Get better-auth database configuration from OpenSaas config
5
+ */
6
+ function getDatabaseConfig(dbConfig, context) {
7
+ return prismaAdapter(context.prisma, {
8
+ provider: dbConfig.provider,
9
+ });
10
+ }
11
+ /**
12
+ * Create a better-auth instance from OpenSaas config
13
+ * This should be called once at app startup
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // lib/auth.ts
18
+ * import { createAuth } from '@opensaas/stack-auth/server'
19
+ * import config from '../opensaas.config'
20
+ *
21
+ * export const auth = createAuth(config)
22
+ * ```
23
+ */
24
+ export function createAuth(opensaasConfig, context) {
25
+ // Extract auth config (added by withAuth)
26
+ const authConfig = opensaasConfig.__authConfig;
27
+ if (!authConfig) {
28
+ throw new Error('Auth config not found. Make sure to wrap your config with withAuth() in opensaas.config.ts');
29
+ }
30
+ // Build better-auth configuration
31
+ const betterAuthConfig = {
32
+ database: getDatabaseConfig(opensaasConfig.db, context),
33
+ // Enable email and password if configured
34
+ emailAndPassword: authConfig.emailAndPassword.enabled
35
+ ? {
36
+ enabled: true,
37
+ requireEmailVerification: authConfig.emailVerification.enabled,
38
+ }
39
+ : undefined,
40
+ // Configure session
41
+ session: {
42
+ expiresIn: authConfig.session.expiresIn || 604800,
43
+ updateAge: authConfig.session.updateAge ? (authConfig.session.expiresIn || 604800) / 10 : 0,
44
+ },
45
+ // Trust host (required for production)
46
+ trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(',') || [],
47
+ // Social providers
48
+ socialProviders: Object.entries(authConfig.socialProviders)
49
+ .filter(([_, config]) => config?.enabled !== false)
50
+ .reduce((acc, [provider, config]) => {
51
+ if (config) {
52
+ acc[provider] = {
53
+ clientId: config.clientId,
54
+ clientSecret: config.clientSecret,
55
+ };
56
+ }
57
+ return acc;
58
+ }, {}),
59
+ };
60
+ return betterAuth(betterAuthConfig);
61
+ }
62
+ /**
63
+ * Get session from better-auth and transform it to OpenSaas session format
64
+ * This is used internally by the generated context
65
+ */
66
+ export async function getSessionFromAuth(auth, sessionFields) {
67
+ try {
68
+ const session = await auth.api.getSession({
69
+ headers: new Headers(),
70
+ });
71
+ if (!session?.user) {
72
+ return null;
73
+ }
74
+ // Build session object with requested fields
75
+ const result = {};
76
+ for (const field of sessionFields) {
77
+ if (field === 'userId') {
78
+ result.userId = session.user.id;
79
+ }
80
+ else if (field in session.user) {
81
+ result[field] = session.user[field];
82
+ }
83
+ }
84
+ return result;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}