@ccatto/react-auth 1.0.0 → 1.2.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/dist/{config-CvzbPvtw.d.cts → config-MuDc5UVc.d.cts} +30 -1
- package/dist/{config-CvzbPvtw.d.ts → config-MuDc5UVc.d.ts} +30 -1
- package/dist/index.d.cts +11 -5
- package/dist/index.d.ts +11 -5
- package/dist/server.cjs +27 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +27 -2
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,6 +9,28 @@ interface CattoAuthSocialProvider {
|
|
|
9
9
|
clientSecret: string;
|
|
10
10
|
scope?: string[];
|
|
11
11
|
}
|
|
12
|
+
/** Phone-number + OTP auth config (Better Auth `phoneNumber` plugin). */
|
|
13
|
+
interface CattoAuthPhoneConfig {
|
|
14
|
+
/** Master switch — when false/undefined the plugin is not registered. */
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Deliver the OTP code via SMS. The app owns the transport (Telnyx, Twilio, etc.).
|
|
18
|
+
* Throw/reject to surface a send failure to the caller.
|
|
19
|
+
*/
|
|
20
|
+
sendOtp: (args: {
|
|
21
|
+
phoneNumber: string;
|
|
22
|
+
code: string;
|
|
23
|
+
}) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Domain used to mint a synthetic email for phone-only users on first verify
|
|
26
|
+
* (Better Auth requires an email). Default: 'phone.catto.local'.
|
|
27
|
+
*/
|
|
28
|
+
tempEmailDomain?: string;
|
|
29
|
+
/** Require phone verification before the number is trusted. Default: true. */
|
|
30
|
+
requireVerification?: boolean;
|
|
31
|
+
/** OTP lifetime in seconds. Default: Better Auth's default (300). */
|
|
32
|
+
otpExpiresInSeconds?: number;
|
|
33
|
+
}
|
|
12
34
|
/** Minimal database client interface — compatible with PrismaClient without version coupling */
|
|
13
35
|
interface CattoAuthDatabaseClient {
|
|
14
36
|
user: {
|
|
@@ -36,7 +58,14 @@ interface CattoAuthServerConfig {
|
|
|
36
58
|
google?: CattoAuthSocialProvider;
|
|
37
59
|
facebook?: CattoAuthSocialProvider;
|
|
38
60
|
github?: CattoAuthSocialProvider;
|
|
61
|
+
apple?: CattoAuthSocialProvider;
|
|
39
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Phone-number + OTP authentication (Better Auth `phoneNumber` plugin).
|
|
65
|
+
* Optional — only enabled when `enabled` is true and a `sendOtp` transport
|
|
66
|
+
* is provided. The app owns the SMS transport (Telnyx, Twilio, etc.).
|
|
67
|
+
*/
|
|
68
|
+
phoneAuth?: CattoAuthPhoneConfig;
|
|
40
69
|
/** Session configuration */
|
|
41
70
|
session?: {
|
|
42
71
|
/** Session expiry in seconds (default: 69 days = 5961600) */
|
|
@@ -85,4 +114,4 @@ interface CattoSessionProviderConfig {
|
|
|
85
114
|
enrichedSessionEndpoint?: string;
|
|
86
115
|
}
|
|
87
116
|
|
|
88
|
-
export type { CattoAuthClientConfig as C, CattoAuthDatabaseClient as a,
|
|
117
|
+
export type { CattoAuthClientConfig as C, CattoAuthDatabaseClient as a, CattoAuthPhoneConfig as b, CattoAuthServerConfig as c, CattoAuthSocialProvider as d, CattoSessionProviderConfig as e };
|
|
@@ -9,6 +9,28 @@ interface CattoAuthSocialProvider {
|
|
|
9
9
|
clientSecret: string;
|
|
10
10
|
scope?: string[];
|
|
11
11
|
}
|
|
12
|
+
/** Phone-number + OTP auth config (Better Auth `phoneNumber` plugin). */
|
|
13
|
+
interface CattoAuthPhoneConfig {
|
|
14
|
+
/** Master switch — when false/undefined the plugin is not registered. */
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Deliver the OTP code via SMS. The app owns the transport (Telnyx, Twilio, etc.).
|
|
18
|
+
* Throw/reject to surface a send failure to the caller.
|
|
19
|
+
*/
|
|
20
|
+
sendOtp: (args: {
|
|
21
|
+
phoneNumber: string;
|
|
22
|
+
code: string;
|
|
23
|
+
}) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Domain used to mint a synthetic email for phone-only users on first verify
|
|
26
|
+
* (Better Auth requires an email). Default: 'phone.catto.local'.
|
|
27
|
+
*/
|
|
28
|
+
tempEmailDomain?: string;
|
|
29
|
+
/** Require phone verification before the number is trusted. Default: true. */
|
|
30
|
+
requireVerification?: boolean;
|
|
31
|
+
/** OTP lifetime in seconds. Default: Better Auth's default (300). */
|
|
32
|
+
otpExpiresInSeconds?: number;
|
|
33
|
+
}
|
|
12
34
|
/** Minimal database client interface — compatible with PrismaClient without version coupling */
|
|
13
35
|
interface CattoAuthDatabaseClient {
|
|
14
36
|
user: {
|
|
@@ -36,7 +58,14 @@ interface CattoAuthServerConfig {
|
|
|
36
58
|
google?: CattoAuthSocialProvider;
|
|
37
59
|
facebook?: CattoAuthSocialProvider;
|
|
38
60
|
github?: CattoAuthSocialProvider;
|
|
61
|
+
apple?: CattoAuthSocialProvider;
|
|
39
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Phone-number + OTP authentication (Better Auth `phoneNumber` plugin).
|
|
65
|
+
* Optional — only enabled when `enabled` is true and a `sendOtp` transport
|
|
66
|
+
* is provided. The app owns the SMS transport (Telnyx, Twilio, etc.).
|
|
67
|
+
*/
|
|
68
|
+
phoneAuth?: CattoAuthPhoneConfig;
|
|
40
69
|
/** Session configuration */
|
|
41
70
|
session?: {
|
|
42
71
|
/** Session expiry in seconds (default: 69 days = 5961600) */
|
|
@@ -85,4 +114,4 @@ interface CattoSessionProviderConfig {
|
|
|
85
114
|
enrichedSessionEndpoint?: string;
|
|
86
115
|
}
|
|
87
116
|
|
|
88
|
-
export type { CattoAuthClientConfig as C, CattoAuthDatabaseClient as a,
|
|
117
|
+
export type { CattoAuthClientConfig as C, CattoAuthDatabaseClient as a, CattoAuthPhoneConfig as b, CattoAuthServerConfig as c, CattoAuthSocialProvider as d, CattoSessionProviderConfig as e };
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as CattoAuthClientConfig, a as CattoAuthDatabaseClient, b as
|
|
1
|
+
export { C as CattoAuthClientConfig, a as CattoAuthDatabaseClient, b as CattoAuthPhoneConfig, c as CattoAuthServerConfig, d as CattoAuthSocialProvider, e as CattoSessionProviderConfig } from './config-MuDc5UVc.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @ccatto/react-auth - Session Types
|
|
@@ -13,8 +13,13 @@ interface EnrichedUser {
|
|
|
13
13
|
/** Custom fields added by enrichSession hook */
|
|
14
14
|
playerID?: number;
|
|
15
15
|
role: string;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Optional multi-tenant fields. Apps that don't model organizations can omit
|
|
18
|
+
* them entirely; legacy callers that always set them (incl. to null / [])
|
|
19
|
+
* still typecheck.
|
|
20
|
+
*/
|
|
21
|
+
organizationId?: string | null;
|
|
22
|
+
organizations?: Array<{
|
|
18
23
|
id: string;
|
|
19
24
|
name: string;
|
|
20
25
|
slug: string;
|
|
@@ -42,8 +47,9 @@ interface CompatSessionUser {
|
|
|
42
47
|
image: string | null;
|
|
43
48
|
playerID?: number;
|
|
44
49
|
role: string;
|
|
45
|
-
organizationId
|
|
46
|
-
|
|
50
|
+
/** See `EnrichedUser.organizationId` — optional multi-tenant field. */
|
|
51
|
+
organizationId?: string | null;
|
|
52
|
+
organizations?: Array<{
|
|
47
53
|
id: string;
|
|
48
54
|
name: string;
|
|
49
55
|
slug: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as CattoAuthClientConfig, a as CattoAuthDatabaseClient, b as
|
|
1
|
+
export { C as CattoAuthClientConfig, a as CattoAuthDatabaseClient, b as CattoAuthPhoneConfig, c as CattoAuthServerConfig, d as CattoAuthSocialProvider, e as CattoSessionProviderConfig } from './config-MuDc5UVc.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @ccatto/react-auth - Session Types
|
|
@@ -13,8 +13,13 @@ interface EnrichedUser {
|
|
|
13
13
|
/** Custom fields added by enrichSession hook */
|
|
14
14
|
playerID?: number;
|
|
15
15
|
role: string;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Optional multi-tenant fields. Apps that don't model organizations can omit
|
|
18
|
+
* them entirely; legacy callers that always set them (incl. to null / [])
|
|
19
|
+
* still typecheck.
|
|
20
|
+
*/
|
|
21
|
+
organizationId?: string | null;
|
|
22
|
+
organizations?: Array<{
|
|
18
23
|
id: string;
|
|
19
24
|
name: string;
|
|
20
25
|
slug: string;
|
|
@@ -42,8 +47,9 @@ interface CompatSessionUser {
|
|
|
42
47
|
image: string | null;
|
|
43
48
|
playerID?: number;
|
|
44
49
|
role: string;
|
|
45
|
-
organizationId
|
|
46
|
-
|
|
50
|
+
/** See `EnrichedUser.organizationId` — optional multi-tenant field. */
|
|
51
|
+
organizationId?: string | null;
|
|
52
|
+
organizations?: Array<{
|
|
47
53
|
id: string;
|
|
48
54
|
name: string;
|
|
49
55
|
slug: string;
|
package/dist/server.cjs
CHANGED
|
@@ -4,6 +4,7 @@ var betterAuth = require('better-auth');
|
|
|
4
4
|
var prisma = require('better-auth/adapters/prisma');
|
|
5
5
|
var api = require('better-auth/api');
|
|
6
6
|
var nextJs = require('better-auth/next-js');
|
|
7
|
+
var plugins = require('better-auth/plugins');
|
|
7
8
|
|
|
8
9
|
// src/server/create-auth.ts
|
|
9
10
|
var DEFAULT_TOKEN_EXPIRY_SECONDS = 69 * 24 * 60 * 60;
|
|
@@ -16,6 +17,9 @@ function createCattoAuth(config) {
|
|
|
16
17
|
if (config.socialProviders?.github) {
|
|
17
18
|
socialProviders.github = config.socialProviders.github;
|
|
18
19
|
}
|
|
20
|
+
if (config.socialProviders?.apple) {
|
|
21
|
+
socialProviders.apple = config.socialProviders.apple;
|
|
22
|
+
}
|
|
19
23
|
if (config.socialProviders?.facebook) {
|
|
20
24
|
socialProviders.facebook = {
|
|
21
25
|
...config.socialProviders.facebook,
|
|
@@ -25,6 +29,27 @@ function createCattoAuth(config) {
|
|
|
25
29
|
]
|
|
26
30
|
};
|
|
27
31
|
}
|
|
32
|
+
const plugins$1 = [];
|
|
33
|
+
const phoneCfg = config.phoneAuth;
|
|
34
|
+
if (phoneCfg?.enabled && phoneCfg.sendOtp) {
|
|
35
|
+
const tempEmailDomain = phoneCfg.tempEmailDomain ?? "phone.catto.local";
|
|
36
|
+
plugins$1.push(
|
|
37
|
+
plugins.phoneNumber({
|
|
38
|
+
sendOTP: async ({ phoneNumber: toNumber, code }) => {
|
|
39
|
+
await phoneCfg.sendOtp({ phoneNumber: toNumber, code });
|
|
40
|
+
},
|
|
41
|
+
requireVerification: phoneCfg.requireVerification ?? true,
|
|
42
|
+
...phoneCfg.otpExpiresInSeconds ? { expiresIn: phoneCfg.otpExpiresInSeconds } : {},
|
|
43
|
+
signUpOnVerification: {
|
|
44
|
+
getTempEmail: (toNumber) => `${toNumber}@${tempEmailDomain}`,
|
|
45
|
+
// Use the phone number as the initial display name; the app can
|
|
46
|
+
// prompt for a real name post-verify.
|
|
47
|
+
getTempName: (toNumber) => toNumber
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
plugins$1.push(nextJs.nextCookies());
|
|
28
53
|
const auth = betterAuth.betterAuth({
|
|
29
54
|
// Database
|
|
30
55
|
// Cast needed: prismaAdapter expects PrismaClient but we use a minimal interface for package portability
|
|
@@ -76,8 +101,8 @@ function createCattoAuth(config) {
|
|
|
76
101
|
enabled: false
|
|
77
102
|
}
|
|
78
103
|
},
|
|
79
|
-
// Plugins
|
|
80
|
-
plugins:
|
|
104
|
+
// Plugins (phoneNumber when configured, then nextCookies last)
|
|
105
|
+
plugins: plugins$1,
|
|
81
106
|
// Hooks
|
|
82
107
|
hooks: {
|
|
83
108
|
// Before hooks: email normalization
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/create-auth.ts"],"names":["betterAuth","prismaAdapter","nextCookies","createAuthMiddleware"],"mappings":"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","file":"server.cjs","sourcesContent":["/**\n * @ccatto/react-auth/server - createCattoAuth Factory\n *\n * Creates a configured Better Auth instance with sensible defaults,\n * automatic email normalization, and pluggable hooks for\n * session enrichment and user lifecycle events.\n *\n * @example\n * ```typescript\n * import { createCattoAuth } from '@ccatto/react-auth/server';\n * import { prisma } from '@myapp/database';\n *\n * export const { auth, getEnrichedSession } = createCattoAuth({\n * database: prisma,\n * databaseProvider: 'postgresql',\n * secret: process.env.BETTER_AUTH_SECRET!,\n * baseURL: process.env.BETTER_AUTH_URL!,\n * hooks: {\n * enrichSession: async (userId, db) => {\n * // Return custom fields to merge into session.user\n * return { role: 'admin', customField: 'value' };\n * },\n * },\n * });\n * ```\n */\nimport { betterAuth } from 'better-auth';\nimport { prismaAdapter } from 'better-auth/adapters/prisma';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport { nextCookies } from 'better-auth/next-js';\nimport type {\n CattoAuthServerConfig,\n CattoAuthSocialProvider,\n} from '../types/config';\n\n// Default: 69 days (matches common long-lived session configs)\nconst DEFAULT_TOKEN_EXPIRY_SECONDS = 69 * 24 * 60 * 60;\n\nexport interface CreateCattoAuthResult {\n /** The Better Auth instance (use for API routes) */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n auth: any;\n /**\n * Get enriched session with custom fields from hooks.enrichSession.\n * Call this instead of auth.api.getSession for full session data.\n */\n getEnrichedSession: (headers: Headers) => Promise<any | null>;\n}\n\nexport function createCattoAuth(\n config: CattoAuthServerConfig,\n): CreateCattoAuthResult {\n const expiresIn =\n config.session?.expiresInSeconds ?? DEFAULT_TOKEN_EXPIRY_SECONDS;\n\n // Build social providers config\n const socialProviders: Record<\n string,\n CattoAuthSocialProvider & { scope?: string[] }\n > = {};\n if (config.socialProviders?.google) {\n socialProviders.google = config.socialProviders.google;\n }\n if (config.socialProviders?.github) {\n socialProviders.github = config.socialProviders.github;\n }\n if (config.socialProviders?.facebook) {\n socialProviders.facebook = {\n ...config.socialProviders.facebook,\n scope: config.socialProviders.facebook.scope ?? [\n 'email',\n 'public_profile',\n ],\n };\n }\n\n const auth = betterAuth({\n // Database\n // Cast needed: prismaAdapter expects PrismaClient but we use a minimal interface for package portability\n database: prismaAdapter(\n config.database as Parameters<typeof prismaAdapter>[0],\n {\n provider: config.databaseProvider,\n },\n ),\n\n // Secret & URL\n secret: config.secret,\n baseURL: config.baseURL,\n\n // Email/password\n emailAndPassword: {\n enabled: config.emailAndPassword?.enabled ?? true,\n requireEmailVerification:\n config.emailAndPassword?.requireEmailVerification ?? false,\n },\n\n // Social providers\n socialProviders,\n\n // Session\n session: {\n expiresIn,\n cookieCache: {\n enabled: true,\n maxAge: expiresIn,\n },\n },\n\n // Account linking\n account: {\n accountLinking: {\n enabled: true,\n trustedProviders: Object.keys(socialProviders),\n },\n },\n\n // User fields\n user: {\n additionalFields: {\n role: {\n type: 'string' as const,\n required: false,\n defaultValue: 'user',\n },\n },\n },\n\n // Advanced\n advanced: {\n useSecureCookies:\n config.advanced?.useSecureCookies ??\n process.env.NODE_ENV === 'production',\n crossSubDomainCookies: {\n enabled: false,\n },\n },\n\n // Plugins\n plugins: [nextCookies()],\n\n // Hooks\n hooks: {\n // Before hooks: email normalization\n before: createAuthMiddleware(async (ctx) => {\n if (ctx.path === '/sign-up/email') {\n const body = ctx.body as { email?: string } | undefined;\n if (body?.email && typeof body.email === 'string') {\n (ctx.body as { email: string }).email = body.email.toLowerCase();\n }\n }\n }),\n\n // After hooks: user lifecycle events\n after: createAuthMiddleware(async (ctx) => {\n if (\n ctx.path.startsWith('/sign-in') ||\n ctx.path.startsWith('/sign-up')\n ) {\n const newSession = ctx.context.newSession;\n if (newSession?.user) {\n const user = newSession.user;\n\n // Normalize email in database if needed\n const normalizedEmail = user.email?.toLowerCase();\n if (normalizedEmail && user.email !== normalizedEmail) {\n try {\n await config.database.user.update({\n where: { id: user.id },\n data: { email: normalizedEmail },\n });\n } catch (hookError) {\n config.logger?.warn?.(\n '[CattoAuth] Email normalization failed',\n {\n userId: user.id,\n error:\n hookError instanceof Error\n ? hookError.message\n : String(hookError),\n },\n );\n }\n }\n\n // Call onUserCreated hook\n if (config.hooks?.onUserCreated) {\n try {\n await config.hooks.onUserCreated(\n {\n id: user.id,\n email: normalizedEmail || user.email,\n name: user.name,\n },\n config.database,\n );\n } catch (hookError) {\n config.logger?.warn?.('[CattoAuth] onUserCreated hook failed', {\n userId: user.id,\n error:\n hookError instanceof Error\n ? hookError.message\n : String(hookError),\n });\n }\n }\n }\n }\n }),\n },\n });\n\n // Enriched session factory\n async function getEnrichedSession(headers: Headers): Promise<any | null> {\n try {\n const baseSession = await auth.api.getSession({ headers });\n if (!baseSession?.user) return null;\n\n const userId = baseSession.user.id;\n\n // Call enrichSession hook if provided\n const enrichment = config.hooks?.enrichSession\n ? await config.hooks.enrichSession(userId, config.database)\n : {};\n\n return {\n user: {\n id: baseSession.user.id,\n email: baseSession.user.email,\n name: baseSession.user.name,\n image: baseSession.user.image,\n ...enrichment,\n },\n session: baseSession.session,\n };\n } catch {\n return null;\n }\n }\n\n return { auth, getEnrichedSession };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/create-auth.ts"],"names":["plugins","phoneNumber","nextCookies","betterAuth","prismaAdapter","createAuthMiddleware"],"mappings":"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","file":"server.cjs","sourcesContent":["/**\n * @ccatto/react-auth/server - createCattoAuth Factory\n *\n * Creates a configured Better Auth instance with sensible defaults,\n * automatic email normalization, and pluggable hooks for\n * session enrichment and user lifecycle events.\n *\n * @example\n * ```typescript\n * import { createCattoAuth } from '@ccatto/react-auth/server';\n * import { prisma } from '@myapp/database';\n *\n * export const { auth, getEnrichedSession } = createCattoAuth({\n * database: prisma,\n * databaseProvider: 'postgresql',\n * secret: process.env.BETTER_AUTH_SECRET!,\n * baseURL: process.env.BETTER_AUTH_URL!,\n * hooks: {\n * enrichSession: async (userId, db) => {\n * // Return custom fields to merge into session.user\n * return { role: 'admin', customField: 'value' };\n * },\n * },\n * });\n * ```\n */\nimport { betterAuth } from 'better-auth';\nimport { prismaAdapter } from 'better-auth/adapters/prisma';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport { nextCookies } from 'better-auth/next-js';\nimport { phoneNumber } from 'better-auth/plugins';\nimport type {\n CattoAuthServerConfig,\n CattoAuthSocialProvider,\n} from '../types/config';\n\n// Default: 69 days (matches common long-lived session configs)\nconst DEFAULT_TOKEN_EXPIRY_SECONDS = 69 * 24 * 60 * 60;\n\nexport interface CreateCattoAuthResult {\n /** The Better Auth instance (use for API routes) */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n auth: any;\n /**\n * Get enriched session with custom fields from hooks.enrichSession.\n * Call this instead of auth.api.getSession for full session data.\n */\n getEnrichedSession: (headers: Headers) => Promise<any | null>;\n}\n\nexport function createCattoAuth(\n config: CattoAuthServerConfig,\n): CreateCattoAuthResult {\n const expiresIn =\n config.session?.expiresInSeconds ?? DEFAULT_TOKEN_EXPIRY_SECONDS;\n\n // Build social providers config\n const socialProviders: Record<\n string,\n CattoAuthSocialProvider & { scope?: string[] }\n > = {};\n if (config.socialProviders?.google) {\n socialProviders.google = config.socialProviders.google;\n }\n if (config.socialProviders?.github) {\n socialProviders.github = config.socialProviders.github;\n }\n if (config.socialProviders?.apple) {\n socialProviders.apple = config.socialProviders.apple;\n }\n if (config.socialProviders?.facebook) {\n socialProviders.facebook = {\n ...config.socialProviders.facebook,\n scope: config.socialProviders.facebook.scope ?? [\n 'email',\n 'public_profile',\n ],\n };\n }\n\n // Build plugins list. nextCookies() must stay last so Set-Cookie headers\n // from any preceding plugin/route are forwarded in Next.js server actions.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const plugins: any[] = [];\n const phoneCfg = config.phoneAuth;\n if (phoneCfg?.enabled && phoneCfg.sendOtp) {\n const tempEmailDomain = phoneCfg.tempEmailDomain ?? 'phone.catto.local';\n plugins.push(\n phoneNumber({\n sendOTP: async ({ phoneNumber: toNumber, code }) => {\n await phoneCfg.sendOtp({ phoneNumber: toNumber, code });\n },\n requireVerification: phoneCfg.requireVerification ?? true,\n ...(phoneCfg.otpExpiresInSeconds\n ? { expiresIn: phoneCfg.otpExpiresInSeconds }\n : {}),\n signUpOnVerification: {\n getTempEmail: (toNumber: string) => `${toNumber}@${tempEmailDomain}`,\n // Use the phone number as the initial display name; the app can\n // prompt for a real name post-verify.\n getTempName: (toNumber: string) => toNumber,\n },\n }),\n );\n }\n plugins.push(nextCookies());\n\n const auth = betterAuth({\n // Database\n // Cast needed: prismaAdapter expects PrismaClient but we use a minimal interface for package portability\n database: prismaAdapter(\n config.database as Parameters<typeof prismaAdapter>[0],\n {\n provider: config.databaseProvider,\n },\n ),\n\n // Secret & URL\n secret: config.secret,\n baseURL: config.baseURL,\n\n // Email/password\n emailAndPassword: {\n enabled: config.emailAndPassword?.enabled ?? true,\n requireEmailVerification:\n config.emailAndPassword?.requireEmailVerification ?? false,\n },\n\n // Social providers\n socialProviders,\n\n // Session\n session: {\n expiresIn,\n cookieCache: {\n enabled: true,\n maxAge: expiresIn,\n },\n },\n\n // Account linking\n account: {\n accountLinking: {\n enabled: true,\n trustedProviders: Object.keys(socialProviders),\n },\n },\n\n // User fields\n user: {\n additionalFields: {\n role: {\n type: 'string' as const,\n required: false,\n defaultValue: 'user',\n },\n },\n },\n\n // Advanced\n advanced: {\n useSecureCookies:\n config.advanced?.useSecureCookies ??\n process.env.NODE_ENV === 'production',\n crossSubDomainCookies: {\n enabled: false,\n },\n },\n\n // Plugins (phoneNumber when configured, then nextCookies last)\n plugins,\n\n // Hooks\n hooks: {\n // Before hooks: email normalization\n before: createAuthMiddleware(async (ctx) => {\n if (ctx.path === '/sign-up/email') {\n const body = ctx.body as { email?: string } | undefined;\n if (body?.email && typeof body.email === 'string') {\n (ctx.body as { email: string }).email = body.email.toLowerCase();\n }\n }\n }),\n\n // After hooks: user lifecycle events\n after: createAuthMiddleware(async (ctx) => {\n if (\n ctx.path.startsWith('/sign-in') ||\n ctx.path.startsWith('/sign-up')\n ) {\n const newSession = ctx.context.newSession;\n if (newSession?.user) {\n const user = newSession.user;\n\n // Normalize email in database if needed\n const normalizedEmail = user.email?.toLowerCase();\n if (normalizedEmail && user.email !== normalizedEmail) {\n try {\n await config.database.user.update({\n where: { id: user.id },\n data: { email: normalizedEmail },\n });\n } catch (hookError) {\n config.logger?.warn?.(\n '[CattoAuth] Email normalization failed',\n {\n userId: user.id,\n error:\n hookError instanceof Error\n ? hookError.message\n : String(hookError),\n },\n );\n }\n }\n\n // Call onUserCreated hook\n if (config.hooks?.onUserCreated) {\n try {\n await config.hooks.onUserCreated(\n {\n id: user.id,\n email: normalizedEmail || user.email,\n name: user.name,\n },\n config.database,\n );\n } catch (hookError) {\n config.logger?.warn?.('[CattoAuth] onUserCreated hook failed', {\n userId: user.id,\n error:\n hookError instanceof Error\n ? hookError.message\n : String(hookError),\n });\n }\n }\n }\n }\n }),\n },\n });\n\n // Enriched session factory\n async function getEnrichedSession(headers: Headers): Promise<any | null> {\n try {\n const baseSession = await auth.api.getSession({ headers });\n if (!baseSession?.user) return null;\n\n const userId = baseSession.user.id;\n\n // Call enrichSession hook if provided\n const enrichment = config.hooks?.enrichSession\n ? await config.hooks.enrichSession(userId, config.database)\n : {};\n\n return {\n user: {\n id: baseSession.user.id,\n email: baseSession.user.email,\n name: baseSession.user.name,\n image: baseSession.user.image,\n ...enrichment,\n },\n session: baseSession.session,\n };\n } catch {\n return null;\n }\n }\n\n return { auth, getEnrichedSession };\n}\n"]}
|
package/dist/server.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { c as CattoAuthServerConfig } from './config-MuDc5UVc.cjs';
|
|
2
|
+
export { b as CattoAuthPhoneConfig, d as CattoAuthSocialProvider } from './config-MuDc5UVc.cjs';
|
|
3
3
|
|
|
4
4
|
interface CreateCattoAuthResult {
|
|
5
5
|
/** The Better Auth instance (use for API routes) */
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { c as CattoAuthServerConfig } from './config-MuDc5UVc.js';
|
|
2
|
+
export { b as CattoAuthPhoneConfig, d as CattoAuthSocialProvider } from './config-MuDc5UVc.js';
|
|
3
3
|
|
|
4
4
|
interface CreateCattoAuthResult {
|
|
5
5
|
/** The Better Auth instance (use for API routes) */
|
package/dist/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import { betterAuth } from 'better-auth';
|
|
|
2
2
|
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
|
3
3
|
import { createAuthMiddleware } from 'better-auth/api';
|
|
4
4
|
import { nextCookies } from 'better-auth/next-js';
|
|
5
|
+
import { phoneNumber } from 'better-auth/plugins';
|
|
5
6
|
|
|
6
7
|
// src/server/create-auth.ts
|
|
7
8
|
var DEFAULT_TOKEN_EXPIRY_SECONDS = 69 * 24 * 60 * 60;
|
|
@@ -14,6 +15,9 @@ function createCattoAuth(config) {
|
|
|
14
15
|
if (config.socialProviders?.github) {
|
|
15
16
|
socialProviders.github = config.socialProviders.github;
|
|
16
17
|
}
|
|
18
|
+
if (config.socialProviders?.apple) {
|
|
19
|
+
socialProviders.apple = config.socialProviders.apple;
|
|
20
|
+
}
|
|
17
21
|
if (config.socialProviders?.facebook) {
|
|
18
22
|
socialProviders.facebook = {
|
|
19
23
|
...config.socialProviders.facebook,
|
|
@@ -23,6 +27,27 @@ function createCattoAuth(config) {
|
|
|
23
27
|
]
|
|
24
28
|
};
|
|
25
29
|
}
|
|
30
|
+
const plugins = [];
|
|
31
|
+
const phoneCfg = config.phoneAuth;
|
|
32
|
+
if (phoneCfg?.enabled && phoneCfg.sendOtp) {
|
|
33
|
+
const tempEmailDomain = phoneCfg.tempEmailDomain ?? "phone.catto.local";
|
|
34
|
+
plugins.push(
|
|
35
|
+
phoneNumber({
|
|
36
|
+
sendOTP: async ({ phoneNumber: toNumber, code }) => {
|
|
37
|
+
await phoneCfg.sendOtp({ phoneNumber: toNumber, code });
|
|
38
|
+
},
|
|
39
|
+
requireVerification: phoneCfg.requireVerification ?? true,
|
|
40
|
+
...phoneCfg.otpExpiresInSeconds ? { expiresIn: phoneCfg.otpExpiresInSeconds } : {},
|
|
41
|
+
signUpOnVerification: {
|
|
42
|
+
getTempEmail: (toNumber) => `${toNumber}@${tempEmailDomain}`,
|
|
43
|
+
// Use the phone number as the initial display name; the app can
|
|
44
|
+
// prompt for a real name post-verify.
|
|
45
|
+
getTempName: (toNumber) => toNumber
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
plugins.push(nextCookies());
|
|
26
51
|
const auth = betterAuth({
|
|
27
52
|
// Database
|
|
28
53
|
// Cast needed: prismaAdapter expects PrismaClient but we use a minimal interface for package portability
|
|
@@ -74,8 +99,8 @@ function createCattoAuth(config) {
|
|
|
74
99
|
enabled: false
|
|
75
100
|
}
|
|
76
101
|
},
|
|
77
|
-
// Plugins
|
|
78
|
-
plugins
|
|
102
|
+
// Plugins (phoneNumber when configured, then nextCookies last)
|
|
103
|
+
plugins,
|
|
79
104
|
// Hooks
|
|
80
105
|
hooks: {
|
|
81
106
|
// Before hooks: email normalization
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/create-auth.ts"],"names":[],"mappings":";;;;;;AAoCA,IAAM,4BAAA,GAA+B,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,EAAA;AAa7C,SAAS,gBACd,MAAA,EACuB;AACvB,EAAA,MAAM,SAAA,GACJ,MAAA,CAAO,OAAA,EAAS,gBAAA,IAAoB,4BAAA;AAGtC,EAAA,MAAM,kBAGF,EAAC;AACL,EAAA,IAAI,MAAA,CAAO,iBAAiB,MAAA,EAAQ;AAClC,IAAA,eAAA,CAAgB,MAAA,GAAS,OAAO,eAAA,CAAgB,MAAA;AAAA,EAClD;AACA,EAAA,IAAI,MAAA,CAAO,iBAAiB,MAAA,EAAQ;AAClC,IAAA,eAAA,CAAgB,MAAA,GAAS,OAAO,eAAA,CAAgB,MAAA;AAAA,EAClD;AACA,EAAA,IAAI,MAAA,CAAO,iBAAiB,QAAA,EAAU;AACpC,IAAA,eAAA,CAAgB,QAAA,GAAW;AAAA,MACzB,GAAG,OAAO,eAAA,CAAgB,QAAA;AAAA,MAC1B,KAAA,EAAO,MAAA,CAAO,eAAA,CAAgB,QAAA,CAAS,KAAA,IAAS;AAAA,QAC9C,OAAA;AAAA,QACA;AAAA;AACF,KACF;AAAA,EACF;AAEA,EAAA,MAAM,OAAO,UAAA,CAAW;AAAA;AAAA;AAAA,IAGtB,QAAA,EAAU,aAAA;AAAA,MACR,MAAA,CAAO,QAAA;AAAA,MACP;AAAA,QACE,UAAU,MAAA,CAAO;AAAA;AACnB,KACF;AAAA;AAAA,IAGA,QAAQ,MAAA,CAAO,MAAA;AAAA,IACf,SAAS,MAAA,CAAO,OAAA;AAAA;AAAA,IAGhB,gBAAA,EAAkB;AAAA,MAChB,OAAA,EAAS,MAAA,CAAO,gBAAA,EAAkB,OAAA,IAAW,IAAA;AAAA,MAC7C,wBAAA,EACE,MAAA,CAAO,gBAAA,EAAkB,wBAAA,IAA4B;AAAA,KACzD;AAAA;AAAA,IAGA,eAAA;AAAA;AAAA,IAGA,OAAA,EAAS;AAAA,MACP,SAAA;AAAA,MACA,WAAA,EAAa;AAAA,QACX,OAAA,EAAS,IAAA;AAAA,QACT,MAAA,EAAQ;AAAA;AACV,KACF;AAAA;AAAA,IAGA,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB;AAAA,QACd,OAAA,EAAS,IAAA;AAAA,QACT,gBAAA,EAAkB,MAAA,CAAO,IAAA,CAAK,eAAe;AAAA;AAC/C,KACF;AAAA;AAAA,IAGA,IAAA,EAAM;AAAA,MACJ,gBAAA,EAAkB;AAAA,QAChB,IAAA,EAAM;AAAA,UACJ,IAAA,EAAM,QAAA;AAAA,UACN,QAAA,EAAU,KAAA;AAAA,UACV,YAAA,EAAc;AAAA;AAChB;AACF,KACF;AAAA;AAAA,IAGA,QAAA,EAAU;AAAA,MACR,kBACE,MAAA,CAAO,QAAA,EAAU,gBAAA,IACjB,OAAA,CAAQ,IAAI,QAAA,KAAa,YAAA;AAAA,MAC3B,qBAAA,EAAuB;AAAA,QACrB,OAAA,EAAS;AAAA;AACX,KACF;AAAA;AAAA,IAGA,OAAA,EAAS,CAAC,WAAA,EAAa,CAAA;AAAA;AAAA,IAGvB,KAAA,EAAO;AAAA;AAAA,MAEL,MAAA,EAAQ,oBAAA,CAAqB,OAAO,GAAA,KAAQ;AAC1C,QAAA,IAAI,GAAA,CAAI,SAAS,gBAAA,EAAkB;AACjC,UAAA,MAAM,OAAO,GAAA,CAAI,IAAA;AACjB,UAAA,IAAI,IAAA,EAAM,KAAA,IAAS,OAAO,IAAA,CAAK,UAAU,QAAA,EAAU;AACjD,YAAC,GAAA,CAAI,IAAA,CAA2B,KAAA,GAAQ,IAAA,CAAK,MAAM,WAAA,EAAY;AAAA,UACjE;AAAA,QACF;AAAA,MACF,CAAC,CAAA;AAAA;AAAA,MAGD,KAAA,EAAO,oBAAA,CAAqB,OAAO,GAAA,KAAQ;AACzC,QAAA,IACE,GAAA,CAAI,KAAK,UAAA,CAAW,UAAU,KAC9B,GAAA,CAAI,IAAA,CAAK,UAAA,CAAW,UAAU,CAAA,EAC9B;AACA,UAAA,MAAM,UAAA,GAAa,IAAI,OAAA,CAAQ,UAAA;AAC/B,UAAA,IAAI,YAAY,IAAA,EAAM;AACpB,YAAA,MAAM,OAAO,UAAA,CAAW,IAAA;AAGxB,YAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,KAAA,EAAO,WAAA,EAAY;AAChD,YAAA,IAAI,eAAA,IAAmB,IAAA,CAAK,KAAA,KAAU,eAAA,EAAiB;AACrD,cAAA,IAAI;AACF,gBAAA,MAAM,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,MAAA,CAAO;AAAA,kBAChC,KAAA,EAAO,EAAE,EAAA,EAAI,IAAA,CAAK,EAAA,EAAG;AAAA,kBACrB,IAAA,EAAM,EAAE,KAAA,EAAO,eAAA;AAAgB,iBAChC,CAAA;AAAA,cACH,SAAS,SAAA,EAAW;AAClB,gBAAA,MAAA,CAAO,MAAA,EAAQ,IAAA;AAAA,kBACb,wCAAA;AAAA,kBACA;AAAA,oBACE,QAAQ,IAAA,CAAK,EAAA;AAAA,oBACb,OACE,SAAA,YAAqB,KAAA,GACjB,SAAA,CAAU,OAAA,GACV,OAAO,SAAS;AAAA;AACxB,iBACF;AAAA,cACF;AAAA,YACF;AAGA,YAAA,IAAI,MAAA,CAAO,OAAO,aAAA,EAAe;AAC/B,cAAA,IAAI;AACF,gBAAA,MAAM,OAAO,KAAA,CAAM,aAAA;AAAA,kBACjB;AAAA,oBACE,IAAI,IAAA,CAAK,EAAA;AAAA,oBACT,KAAA,EAAO,mBAAmB,IAAA,CAAK,KAAA;AAAA,oBAC/B,MAAM,IAAA,CAAK;AAAA,mBACb;AAAA,kBACA,MAAA,CAAO;AAAA,iBACT;AAAA,cACF,SAAS,SAAA,EAAW;AAClB,gBAAA,MAAA,CAAO,MAAA,EAAQ,OAAO,uCAAA,EAAyC;AAAA,kBAC7D,QAAQ,IAAA,CAAK,EAAA;AAAA,kBACb,OACE,SAAA,YAAqB,KAAA,GACjB,SAAA,CAAU,OAAA,GACV,OAAO,SAAS;AAAA,iBACvB,CAAA;AAAA,cACH;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA;AACH,GACD,CAAA;AAGD,EAAA,eAAe,mBAAmB,OAAA,EAAuC;AACvE,IAAA,IAAI;AACF,MAAA,MAAM,cAAc,MAAM,IAAA,CAAK,IAAI,UAAA,CAAW,EAAE,SAAS,CAAA;AACzD,MAAA,IAAI,CAAC,WAAA,EAAa,IAAA,EAAM,OAAO,IAAA;AAE/B,MAAA,MAAM,MAAA,GAAS,YAAY,IAAA,CAAK,EAAA;AAGhC,MAAA,MAAM,UAAA,GAAa,MAAA,CAAO,KAAA,EAAO,aAAA,GAC7B,MAAM,MAAA,CAAO,KAAA,CAAM,aAAA,CAAc,MAAA,EAAQ,MAAA,CAAO,QAAQ,CAAA,GACxD,EAAC;AAEL,MAAA,OAAO;AAAA,QACL,IAAA,EAAM;AAAA,UACJ,EAAA,EAAI,YAAY,IAAA,CAAK,EAAA;AAAA,UACrB,KAAA,EAAO,YAAY,IAAA,CAAK,KAAA;AAAA,UACxB,IAAA,EAAM,YAAY,IAAA,CAAK,IAAA;AAAA,UACvB,KAAA,EAAO,YAAY,IAAA,CAAK,KAAA;AAAA,UACxB,GAAG;AAAA,SACL;AAAA,QACA,SAAS,WAAA,CAAY;AAAA,OACvB;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,MAAM,kBAAA,EAAmB;AACpC","file":"server.js","sourcesContent":["/**\n * @ccatto/react-auth/server - createCattoAuth Factory\n *\n * Creates a configured Better Auth instance with sensible defaults,\n * automatic email normalization, and pluggable hooks for\n * session enrichment and user lifecycle events.\n *\n * @example\n * ```typescript\n * import { createCattoAuth } from '@ccatto/react-auth/server';\n * import { prisma } from '@myapp/database';\n *\n * export const { auth, getEnrichedSession } = createCattoAuth({\n * database: prisma,\n * databaseProvider: 'postgresql',\n * secret: process.env.BETTER_AUTH_SECRET!,\n * baseURL: process.env.BETTER_AUTH_URL!,\n * hooks: {\n * enrichSession: async (userId, db) => {\n * // Return custom fields to merge into session.user\n * return { role: 'admin', customField: 'value' };\n * },\n * },\n * });\n * ```\n */\nimport { betterAuth } from 'better-auth';\nimport { prismaAdapter } from 'better-auth/adapters/prisma';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport { nextCookies } from 'better-auth/next-js';\nimport type {\n CattoAuthServerConfig,\n CattoAuthSocialProvider,\n} from '../types/config';\n\n// Default: 69 days (matches common long-lived session configs)\nconst DEFAULT_TOKEN_EXPIRY_SECONDS = 69 * 24 * 60 * 60;\n\nexport interface CreateCattoAuthResult {\n /** The Better Auth instance (use for API routes) */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n auth: any;\n /**\n * Get enriched session with custom fields from hooks.enrichSession.\n * Call this instead of auth.api.getSession for full session data.\n */\n getEnrichedSession: (headers: Headers) => Promise<any | null>;\n}\n\nexport function createCattoAuth(\n config: CattoAuthServerConfig,\n): CreateCattoAuthResult {\n const expiresIn =\n config.session?.expiresInSeconds ?? DEFAULT_TOKEN_EXPIRY_SECONDS;\n\n // Build social providers config\n const socialProviders: Record<\n string,\n CattoAuthSocialProvider & { scope?: string[] }\n > = {};\n if (config.socialProviders?.google) {\n socialProviders.google = config.socialProviders.google;\n }\n if (config.socialProviders?.github) {\n socialProviders.github = config.socialProviders.github;\n }\n if (config.socialProviders?.facebook) {\n socialProviders.facebook = {\n ...config.socialProviders.facebook,\n scope: config.socialProviders.facebook.scope ?? [\n 'email',\n 'public_profile',\n ],\n };\n }\n\n const auth = betterAuth({\n // Database\n // Cast needed: prismaAdapter expects PrismaClient but we use a minimal interface for package portability\n database: prismaAdapter(\n config.database as Parameters<typeof prismaAdapter>[0],\n {\n provider: config.databaseProvider,\n },\n ),\n\n // Secret & URL\n secret: config.secret,\n baseURL: config.baseURL,\n\n // Email/password\n emailAndPassword: {\n enabled: config.emailAndPassword?.enabled ?? true,\n requireEmailVerification:\n config.emailAndPassword?.requireEmailVerification ?? false,\n },\n\n // Social providers\n socialProviders,\n\n // Session\n session: {\n expiresIn,\n cookieCache: {\n enabled: true,\n maxAge: expiresIn,\n },\n },\n\n // Account linking\n account: {\n accountLinking: {\n enabled: true,\n trustedProviders: Object.keys(socialProviders),\n },\n },\n\n // User fields\n user: {\n additionalFields: {\n role: {\n type: 'string' as const,\n required: false,\n defaultValue: 'user',\n },\n },\n },\n\n // Advanced\n advanced: {\n useSecureCookies:\n config.advanced?.useSecureCookies ??\n process.env.NODE_ENV === 'production',\n crossSubDomainCookies: {\n enabled: false,\n },\n },\n\n // Plugins\n plugins: [nextCookies()],\n\n // Hooks\n hooks: {\n // Before hooks: email normalization\n before: createAuthMiddleware(async (ctx) => {\n if (ctx.path === '/sign-up/email') {\n const body = ctx.body as { email?: string } | undefined;\n if (body?.email && typeof body.email === 'string') {\n (ctx.body as { email: string }).email = body.email.toLowerCase();\n }\n }\n }),\n\n // After hooks: user lifecycle events\n after: createAuthMiddleware(async (ctx) => {\n if (\n ctx.path.startsWith('/sign-in') ||\n ctx.path.startsWith('/sign-up')\n ) {\n const newSession = ctx.context.newSession;\n if (newSession?.user) {\n const user = newSession.user;\n\n // Normalize email in database if needed\n const normalizedEmail = user.email?.toLowerCase();\n if (normalizedEmail && user.email !== normalizedEmail) {\n try {\n await config.database.user.update({\n where: { id: user.id },\n data: { email: normalizedEmail },\n });\n } catch (hookError) {\n config.logger?.warn?.(\n '[CattoAuth] Email normalization failed',\n {\n userId: user.id,\n error:\n hookError instanceof Error\n ? hookError.message\n : String(hookError),\n },\n );\n }\n }\n\n // Call onUserCreated hook\n if (config.hooks?.onUserCreated) {\n try {\n await config.hooks.onUserCreated(\n {\n id: user.id,\n email: normalizedEmail || user.email,\n name: user.name,\n },\n config.database,\n );\n } catch (hookError) {\n config.logger?.warn?.('[CattoAuth] onUserCreated hook failed', {\n userId: user.id,\n error:\n hookError instanceof Error\n ? hookError.message\n : String(hookError),\n });\n }\n }\n }\n }\n }),\n },\n });\n\n // Enriched session factory\n async function getEnrichedSession(headers: Headers): Promise<any | null> {\n try {\n const baseSession = await auth.api.getSession({ headers });\n if (!baseSession?.user) return null;\n\n const userId = baseSession.user.id;\n\n // Call enrichSession hook if provided\n const enrichment = config.hooks?.enrichSession\n ? await config.hooks.enrichSession(userId, config.database)\n : {};\n\n return {\n user: {\n id: baseSession.user.id,\n email: baseSession.user.email,\n name: baseSession.user.name,\n image: baseSession.user.image,\n ...enrichment,\n },\n session: baseSession.session,\n };\n } catch {\n return null;\n }\n }\n\n return { auth, getEnrichedSession };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/create-auth.ts"],"names":[],"mappings":";;;;;;;AAqCA,IAAM,4BAAA,GAA+B,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,EAAA;AAa7C,SAAS,gBACd,MAAA,EACuB;AACvB,EAAA,MAAM,SAAA,GACJ,MAAA,CAAO,OAAA,EAAS,gBAAA,IAAoB,4BAAA;AAGtC,EAAA,MAAM,kBAGF,EAAC;AACL,EAAA,IAAI,MAAA,CAAO,iBAAiB,MAAA,EAAQ;AAClC,IAAA,eAAA,CAAgB,MAAA,GAAS,OAAO,eAAA,CAAgB,MAAA;AAAA,EAClD;AACA,EAAA,IAAI,MAAA,CAAO,iBAAiB,MAAA,EAAQ;AAClC,IAAA,eAAA,CAAgB,MAAA,GAAS,OAAO,eAAA,CAAgB,MAAA;AAAA,EAClD;AACA,EAAA,IAAI,MAAA,CAAO,iBAAiB,KAAA,EAAO;AACjC,IAAA,eAAA,CAAgB,KAAA,GAAQ,OAAO,eAAA,CAAgB,KAAA;AAAA,EACjD;AACA,EAAA,IAAI,MAAA,CAAO,iBAAiB,QAAA,EAAU;AACpC,IAAA,eAAA,CAAgB,QAAA,GAAW;AAAA,MACzB,GAAG,OAAO,eAAA,CAAgB,QAAA;AAAA,MAC1B,KAAA,EAAO,MAAA,CAAO,eAAA,CAAgB,QAAA,CAAS,KAAA,IAAS;AAAA,QAC9C,OAAA;AAAA,QACA;AAAA;AACF,KACF;AAAA,EACF;AAKA,EAAA,MAAM,UAAiB,EAAC;AACxB,EAAA,MAAM,WAAW,MAAA,CAAO,SAAA;AACxB,EAAA,IAAI,QAAA,EAAU,OAAA,IAAW,QAAA,CAAS,OAAA,EAAS;AACzC,IAAA,MAAM,eAAA,GAAkB,SAAS,eAAA,IAAmB,mBAAA;AACpD,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN,WAAA,CAAY;AAAA,QACV,SAAS,OAAO,EAAE,WAAA,EAAa,QAAA,EAAU,MAAK,KAAM;AAClD,UAAA,MAAM,SAAS,OAAA,CAAQ,EAAE,WAAA,EAAa,QAAA,EAAU,MAAM,CAAA;AAAA,QACxD,CAAA;AAAA,QACA,mBAAA,EAAqB,SAAS,mBAAA,IAAuB,IAAA;AAAA,QACrD,GAAI,SAAS,mBAAA,GACT,EAAE,WAAW,QAAA,CAAS,mBAAA,KACtB,EAAC;AAAA,QACL,oBAAA,EAAsB;AAAA,UACpB,cAAc,CAAC,QAAA,KAAqB,CAAA,EAAG,QAAQ,IAAI,eAAe,CAAA,CAAA;AAAA;AAAA;AAAA,UAGlE,WAAA,EAAa,CAAC,QAAA,KAAqB;AAAA;AACrC,OACD;AAAA,KACH;AAAA,EACF;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,aAAa,CAAA;AAE1B,EAAA,MAAM,OAAO,UAAA,CAAW;AAAA;AAAA;AAAA,IAGtB,QAAA,EAAU,aAAA;AAAA,MACR,MAAA,CAAO,QAAA;AAAA,MACP;AAAA,QACE,UAAU,MAAA,CAAO;AAAA;AACnB,KACF;AAAA;AAAA,IAGA,QAAQ,MAAA,CAAO,MAAA;AAAA,IACf,SAAS,MAAA,CAAO,OAAA;AAAA;AAAA,IAGhB,gBAAA,EAAkB;AAAA,MAChB,OAAA,EAAS,MAAA,CAAO,gBAAA,EAAkB,OAAA,IAAW,IAAA;AAAA,MAC7C,wBAAA,EACE,MAAA,CAAO,gBAAA,EAAkB,wBAAA,IAA4B;AAAA,KACzD;AAAA;AAAA,IAGA,eAAA;AAAA;AAAA,IAGA,OAAA,EAAS;AAAA,MACP,SAAA;AAAA,MACA,WAAA,EAAa;AAAA,QACX,OAAA,EAAS,IAAA;AAAA,QACT,MAAA,EAAQ;AAAA;AACV,KACF;AAAA;AAAA,IAGA,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB;AAAA,QACd,OAAA,EAAS,IAAA;AAAA,QACT,gBAAA,EAAkB,MAAA,CAAO,IAAA,CAAK,eAAe;AAAA;AAC/C,KACF;AAAA;AAAA,IAGA,IAAA,EAAM;AAAA,MACJ,gBAAA,EAAkB;AAAA,QAChB,IAAA,EAAM;AAAA,UACJ,IAAA,EAAM,QAAA;AAAA,UACN,QAAA,EAAU,KAAA;AAAA,UACV,YAAA,EAAc;AAAA;AAChB;AACF,KACF;AAAA;AAAA,IAGA,QAAA,EAAU;AAAA,MACR,kBACE,MAAA,CAAO,QAAA,EAAU,gBAAA,IACjB,OAAA,CAAQ,IAAI,QAAA,KAAa,YAAA;AAAA,MAC3B,qBAAA,EAAuB;AAAA,QACrB,OAAA,EAAS;AAAA;AACX,KACF;AAAA;AAAA,IAGA,OAAA;AAAA;AAAA,IAGA,KAAA,EAAO;AAAA;AAAA,MAEL,MAAA,EAAQ,oBAAA,CAAqB,OAAO,GAAA,KAAQ;AAC1C,QAAA,IAAI,GAAA,CAAI,SAAS,gBAAA,EAAkB;AACjC,UAAA,MAAM,OAAO,GAAA,CAAI,IAAA;AACjB,UAAA,IAAI,IAAA,EAAM,KAAA,IAAS,OAAO,IAAA,CAAK,UAAU,QAAA,EAAU;AACjD,YAAC,GAAA,CAAI,IAAA,CAA2B,KAAA,GAAQ,IAAA,CAAK,MAAM,WAAA,EAAY;AAAA,UACjE;AAAA,QACF;AAAA,MACF,CAAC,CAAA;AAAA;AAAA,MAGD,KAAA,EAAO,oBAAA,CAAqB,OAAO,GAAA,KAAQ;AACzC,QAAA,IACE,GAAA,CAAI,KAAK,UAAA,CAAW,UAAU,KAC9B,GAAA,CAAI,IAAA,CAAK,UAAA,CAAW,UAAU,CAAA,EAC9B;AACA,UAAA,MAAM,UAAA,GAAa,IAAI,OAAA,CAAQ,UAAA;AAC/B,UAAA,IAAI,YAAY,IAAA,EAAM;AACpB,YAAA,MAAM,OAAO,UAAA,CAAW,IAAA;AAGxB,YAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,KAAA,EAAO,WAAA,EAAY;AAChD,YAAA,IAAI,eAAA,IAAmB,IAAA,CAAK,KAAA,KAAU,eAAA,EAAiB;AACrD,cAAA,IAAI;AACF,gBAAA,MAAM,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,MAAA,CAAO;AAAA,kBAChC,KAAA,EAAO,EAAE,EAAA,EAAI,IAAA,CAAK,EAAA,EAAG;AAAA,kBACrB,IAAA,EAAM,EAAE,KAAA,EAAO,eAAA;AAAgB,iBAChC,CAAA;AAAA,cACH,SAAS,SAAA,EAAW;AAClB,gBAAA,MAAA,CAAO,MAAA,EAAQ,IAAA;AAAA,kBACb,wCAAA;AAAA,kBACA;AAAA,oBACE,QAAQ,IAAA,CAAK,EAAA;AAAA,oBACb,OACE,SAAA,YAAqB,KAAA,GACjB,SAAA,CAAU,OAAA,GACV,OAAO,SAAS;AAAA;AACxB,iBACF;AAAA,cACF;AAAA,YACF;AAGA,YAAA,IAAI,MAAA,CAAO,OAAO,aAAA,EAAe;AAC/B,cAAA,IAAI;AACF,gBAAA,MAAM,OAAO,KAAA,CAAM,aAAA;AAAA,kBACjB;AAAA,oBACE,IAAI,IAAA,CAAK,EAAA;AAAA,oBACT,KAAA,EAAO,mBAAmB,IAAA,CAAK,KAAA;AAAA,oBAC/B,MAAM,IAAA,CAAK;AAAA,mBACb;AAAA,kBACA,MAAA,CAAO;AAAA,iBACT;AAAA,cACF,SAAS,SAAA,EAAW;AAClB,gBAAA,MAAA,CAAO,MAAA,EAAQ,OAAO,uCAAA,EAAyC;AAAA,kBAC7D,QAAQ,IAAA,CAAK,EAAA;AAAA,kBACb,OACE,SAAA,YAAqB,KAAA,GACjB,SAAA,CAAU,OAAA,GACV,OAAO,SAAS;AAAA,iBACvB,CAAA;AAAA,cACH;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA;AACH,GACD,CAAA;AAGD,EAAA,eAAe,mBAAmB,OAAA,EAAuC;AACvE,IAAA,IAAI;AACF,MAAA,MAAM,cAAc,MAAM,IAAA,CAAK,IAAI,UAAA,CAAW,EAAE,SAAS,CAAA;AACzD,MAAA,IAAI,CAAC,WAAA,EAAa,IAAA,EAAM,OAAO,IAAA;AAE/B,MAAA,MAAM,MAAA,GAAS,YAAY,IAAA,CAAK,EAAA;AAGhC,MAAA,MAAM,UAAA,GAAa,MAAA,CAAO,KAAA,EAAO,aAAA,GAC7B,MAAM,MAAA,CAAO,KAAA,CAAM,aAAA,CAAc,MAAA,EAAQ,MAAA,CAAO,QAAQ,CAAA,GACxD,EAAC;AAEL,MAAA,OAAO;AAAA,QACL,IAAA,EAAM;AAAA,UACJ,EAAA,EAAI,YAAY,IAAA,CAAK,EAAA;AAAA,UACrB,KAAA,EAAO,YAAY,IAAA,CAAK,KAAA;AAAA,UACxB,IAAA,EAAM,YAAY,IAAA,CAAK,IAAA;AAAA,UACvB,KAAA,EAAO,YAAY,IAAA,CAAK,KAAA;AAAA,UACxB,GAAG;AAAA,SACL;AAAA,QACA,SAAS,WAAA,CAAY;AAAA,OACvB;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,MAAM,kBAAA,EAAmB;AACpC","file":"server.js","sourcesContent":["/**\n * @ccatto/react-auth/server - createCattoAuth Factory\n *\n * Creates a configured Better Auth instance with sensible defaults,\n * automatic email normalization, and pluggable hooks for\n * session enrichment and user lifecycle events.\n *\n * @example\n * ```typescript\n * import { createCattoAuth } from '@ccatto/react-auth/server';\n * import { prisma } from '@myapp/database';\n *\n * export const { auth, getEnrichedSession } = createCattoAuth({\n * database: prisma,\n * databaseProvider: 'postgresql',\n * secret: process.env.BETTER_AUTH_SECRET!,\n * baseURL: process.env.BETTER_AUTH_URL!,\n * hooks: {\n * enrichSession: async (userId, db) => {\n * // Return custom fields to merge into session.user\n * return { role: 'admin', customField: 'value' };\n * },\n * },\n * });\n * ```\n */\nimport { betterAuth } from 'better-auth';\nimport { prismaAdapter } from 'better-auth/adapters/prisma';\nimport { createAuthMiddleware } from 'better-auth/api';\nimport { nextCookies } from 'better-auth/next-js';\nimport { phoneNumber } from 'better-auth/plugins';\nimport type {\n CattoAuthServerConfig,\n CattoAuthSocialProvider,\n} from '../types/config';\n\n// Default: 69 days (matches common long-lived session configs)\nconst DEFAULT_TOKEN_EXPIRY_SECONDS = 69 * 24 * 60 * 60;\n\nexport interface CreateCattoAuthResult {\n /** The Better Auth instance (use for API routes) */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n auth: any;\n /**\n * Get enriched session with custom fields from hooks.enrichSession.\n * Call this instead of auth.api.getSession for full session data.\n */\n getEnrichedSession: (headers: Headers) => Promise<any | null>;\n}\n\nexport function createCattoAuth(\n config: CattoAuthServerConfig,\n): CreateCattoAuthResult {\n const expiresIn =\n config.session?.expiresInSeconds ?? DEFAULT_TOKEN_EXPIRY_SECONDS;\n\n // Build social providers config\n const socialProviders: Record<\n string,\n CattoAuthSocialProvider & { scope?: string[] }\n > = {};\n if (config.socialProviders?.google) {\n socialProviders.google = config.socialProviders.google;\n }\n if (config.socialProviders?.github) {\n socialProviders.github = config.socialProviders.github;\n }\n if (config.socialProviders?.apple) {\n socialProviders.apple = config.socialProviders.apple;\n }\n if (config.socialProviders?.facebook) {\n socialProviders.facebook = {\n ...config.socialProviders.facebook,\n scope: config.socialProviders.facebook.scope ?? [\n 'email',\n 'public_profile',\n ],\n };\n }\n\n // Build plugins list. nextCookies() must stay last so Set-Cookie headers\n // from any preceding plugin/route are forwarded in Next.js server actions.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const plugins: any[] = [];\n const phoneCfg = config.phoneAuth;\n if (phoneCfg?.enabled && phoneCfg.sendOtp) {\n const tempEmailDomain = phoneCfg.tempEmailDomain ?? 'phone.catto.local';\n plugins.push(\n phoneNumber({\n sendOTP: async ({ phoneNumber: toNumber, code }) => {\n await phoneCfg.sendOtp({ phoneNumber: toNumber, code });\n },\n requireVerification: phoneCfg.requireVerification ?? true,\n ...(phoneCfg.otpExpiresInSeconds\n ? { expiresIn: phoneCfg.otpExpiresInSeconds }\n : {}),\n signUpOnVerification: {\n getTempEmail: (toNumber: string) => `${toNumber}@${tempEmailDomain}`,\n // Use the phone number as the initial display name; the app can\n // prompt for a real name post-verify.\n getTempName: (toNumber: string) => toNumber,\n },\n }),\n );\n }\n plugins.push(nextCookies());\n\n const auth = betterAuth({\n // Database\n // Cast needed: prismaAdapter expects PrismaClient but we use a minimal interface for package portability\n database: prismaAdapter(\n config.database as Parameters<typeof prismaAdapter>[0],\n {\n provider: config.databaseProvider,\n },\n ),\n\n // Secret & URL\n secret: config.secret,\n baseURL: config.baseURL,\n\n // Email/password\n emailAndPassword: {\n enabled: config.emailAndPassword?.enabled ?? true,\n requireEmailVerification:\n config.emailAndPassword?.requireEmailVerification ?? false,\n },\n\n // Social providers\n socialProviders,\n\n // Session\n session: {\n expiresIn,\n cookieCache: {\n enabled: true,\n maxAge: expiresIn,\n },\n },\n\n // Account linking\n account: {\n accountLinking: {\n enabled: true,\n trustedProviders: Object.keys(socialProviders),\n },\n },\n\n // User fields\n user: {\n additionalFields: {\n role: {\n type: 'string' as const,\n required: false,\n defaultValue: 'user',\n },\n },\n },\n\n // Advanced\n advanced: {\n useSecureCookies:\n config.advanced?.useSecureCookies ??\n process.env.NODE_ENV === 'production',\n crossSubDomainCookies: {\n enabled: false,\n },\n },\n\n // Plugins (phoneNumber when configured, then nextCookies last)\n plugins,\n\n // Hooks\n hooks: {\n // Before hooks: email normalization\n before: createAuthMiddleware(async (ctx) => {\n if (ctx.path === '/sign-up/email') {\n const body = ctx.body as { email?: string } | undefined;\n if (body?.email && typeof body.email === 'string') {\n (ctx.body as { email: string }).email = body.email.toLowerCase();\n }\n }\n }),\n\n // After hooks: user lifecycle events\n after: createAuthMiddleware(async (ctx) => {\n if (\n ctx.path.startsWith('/sign-in') ||\n ctx.path.startsWith('/sign-up')\n ) {\n const newSession = ctx.context.newSession;\n if (newSession?.user) {\n const user = newSession.user;\n\n // Normalize email in database if needed\n const normalizedEmail = user.email?.toLowerCase();\n if (normalizedEmail && user.email !== normalizedEmail) {\n try {\n await config.database.user.update({\n where: { id: user.id },\n data: { email: normalizedEmail },\n });\n } catch (hookError) {\n config.logger?.warn?.(\n '[CattoAuth] Email normalization failed',\n {\n userId: user.id,\n error:\n hookError instanceof Error\n ? hookError.message\n : String(hookError),\n },\n );\n }\n }\n\n // Call onUserCreated hook\n if (config.hooks?.onUserCreated) {\n try {\n await config.hooks.onUserCreated(\n {\n id: user.id,\n email: normalizedEmail || user.email,\n name: user.name,\n },\n config.database,\n );\n } catch (hookError) {\n config.logger?.warn?.('[CattoAuth] onUserCreated hook failed', {\n userId: user.id,\n error:\n hookError instanceof Error\n ? hookError.message\n : String(hookError),\n });\n }\n }\n }\n }\n }),\n },\n });\n\n // Enriched session factory\n async function getEnrichedSession(headers: Headers): Promise<any | null> {\n try {\n const baseSession = await auth.api.getSession({ headers });\n if (!baseSession?.user) return null;\n\n const userId = baseSession.user.id;\n\n // Call enrichSession hook if provided\n const enrichment = config.hooks?.enrichSession\n ? await config.hooks.enrichSession(userId, config.database)\n : {};\n\n return {\n user: {\n id: baseSession.user.id,\n email: baseSession.user.email,\n name: baseSession.user.name,\n image: baseSession.user.image,\n ...enrichment,\n },\n session: baseSession.session,\n };\n } catch {\n return null;\n }\n }\n\n return { auth, getEnrichedSession };\n}\n"]}
|