@xk2800/nextjs-template 0.7.1-beta.6 → 0.7.1-beta.8
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/README.md +31 -29
- package/components/dashboard/admin/broadcastActions.tsx +148 -0
- package/components/dashboard/admin/confirmSendDialog.tsx +44 -0
- package/components/dashboard/admin/marketingEmailForm.tsx +115 -0
- package/components/dashboard/admin/marketingSection.tsx +67 -0
- package/components/dashboard/admin/subscribersCard.tsx +62 -0
- package/components/dashboard/marketingUpdatesCard.tsx +62 -0
- package/components/dashboard/marketingUpdatesSection.tsx +10 -0
- package/components/ui/select.tsx +189 -0
- package/dist/activity/logger.js +6 -0
- package/dist/activity/logger.js.map +1 -1
- package/dist/activity/logger.mjs +3 -3
- package/dist/activity/queries.js +6 -0
- package/dist/activity/queries.js.map +1 -1
- package/dist/activity/queries.mjs +2 -2
- package/dist/admin/queries.js +6 -0
- package/dist/admin/queries.js.map +1 -1
- package/dist/admin/queries.mjs +2 -2
- package/dist/api/feedback.js +7 -0
- package/dist/api/feedback.js.map +1 -1
- package/dist/api/feedback.mjs +7 -6
- package/dist/api/feedback.mjs.map +1 -1
- package/dist/api/marketing.d.mts +36 -0
- package/dist/api/marketing.d.ts +36 -0
- package/dist/api/marketing.js +1357 -0
- package/dist/api/marketing.js.map +1 -0
- package/dist/api/marketing.mjs +170 -0
- package/dist/api/marketing.mjs.map +1 -0
- package/dist/auth/helpers.d.mts +3 -3
- package/dist/auth/helpers.d.ts +3 -3
- package/dist/auth/helpers.js +7 -0
- package/dist/auth/helpers.js.map +1 -1
- package/dist/auth/helpers.mjs +8 -63
- package/dist/auth/helpers.mjs.map +1 -1
- package/dist/auth/index.js +7 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +6 -5
- package/dist/auth-client/index.d.mts +16 -16
- package/dist/auth-client/index.d.ts +16 -16
- package/dist/{chunk-6KTSULA5.mjs → chunk-6MDQRT6U.mjs} +3 -3
- package/dist/{chunk-6KTSULA5.mjs.map → chunk-6MDQRT6U.mjs.map} +1 -1
- package/dist/chunk-6Z3WUSDJ.mjs +62 -0
- package/dist/chunk-6Z3WUSDJ.mjs.map +1 -0
- package/dist/{chunk-FVLMSLTX.mjs → chunk-7F2POZM6.mjs} +3 -3
- package/dist/{chunk-FVLMSLTX.mjs.map → chunk-7F2POZM6.mjs.map} +1 -1
- package/dist/{chunk-4GGLYS5R.mjs → chunk-7QVIOQ47.mjs} +8 -16
- package/dist/chunk-7QVIOQ47.mjs.map +1 -0
- package/dist/chunk-C6RGN6QA.mjs +60 -0
- package/dist/chunk-C6RGN6QA.mjs.map +1 -0
- package/dist/chunk-OVKVD2HH.mjs +25 -0
- package/dist/chunk-OVKVD2HH.mjs.map +1 -0
- package/dist/{chunk-F776DQAM.mjs → chunk-P2G23JMY.mjs} +4 -4
- package/dist/{chunk-F776DQAM.mjs.map → chunk-P2G23JMY.mjs.map} +1 -1
- package/dist/{chunk-TMJYAOC2.mjs → chunk-TKWNEKK6.mjs} +8 -2
- package/dist/chunk-TKWNEKK6.mjs.map +1 -0
- package/dist/{chunk-F7CG5CA3.mjs → chunk-ZJMFE5B2.mjs} +3 -3
- package/dist/{chunk-F7CG5CA3.mjs.map → chunk-ZJMFE5B2.mjs.map} +1 -1
- package/dist/config/env.d.mts +2 -0
- package/dist/config/env.d.ts +2 -0
- package/dist/config/env.js +6 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/env.mjs +1 -1
- package/dist/db/index.js +6 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/index.mjs +2 -2
- package/dist/errors.js +6 -0
- package/dist/errors.js.map +1 -1
- package/dist/errors.mjs +3 -3
- package/dist/marketing/index.d.mts +35 -0
- package/dist/marketing/index.d.ts +35 -0
- package/dist/marketing/index.js +125 -0
- package/dist/marketing/index.js.map +1 -0
- package/dist/marketing/index.mjs +6 -0
- package/dist/marketing/index.mjs.map +1 -0
- package/dist/sessions/queries.js +6 -0
- package/dist/sessions/queries.js.map +1 -1
- package/dist/sessions/queries.mjs +2 -2
- package/dist/settings/queries.js +6 -0
- package/dist/settings/queries.js.map +1 -1
- package/dist/settings/queries.mjs +3 -3
- package/dist/users/queries.js +6 -0
- package/dist/users/queries.js.map +1 -1
- package/dist/users/queries.mjs +2 -2
- package/package.json +23 -3
- package/dist/chunk-4GGLYS5R.mjs.map +0 -1
- package/dist/chunk-TMJYAOC2.mjs.map +0 -1
|
@@ -826,8 +826,8 @@ declare const authClient: better_auth_react.ReactAuthClient<{
|
|
|
826
826
|
$Infer: {
|
|
827
827
|
body: {
|
|
828
828
|
permissions: {
|
|
829
|
-
readonly user?: ("
|
|
830
|
-
readonly session?: ("
|
|
829
|
+
readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "set-email" | "get" | "update")[] | undefined;
|
|
830
|
+
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
831
831
|
};
|
|
832
832
|
} & {
|
|
833
833
|
userId?: string | undefined;
|
|
@@ -916,8 +916,8 @@ declare const authClient: better_auth_react.ReactAuthClient<{
|
|
|
916
916
|
admin: {
|
|
917
917
|
checkRolePermission: <R extends "user" | "admin">(data: {
|
|
918
918
|
permissions: {
|
|
919
|
-
readonly user?: ("
|
|
920
|
-
readonly session?: ("
|
|
919
|
+
readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "set-email" | "get" | "update")[] | undefined;
|
|
920
|
+
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
921
921
|
};
|
|
922
922
|
} & {
|
|
923
923
|
role: R;
|
|
@@ -2423,8 +2423,8 @@ declare const useSession: () => {
|
|
|
2423
2423
|
banned: boolean | null | undefined;
|
|
2424
2424
|
} & {
|
|
2425
2425
|
role?: string | null | undefined;
|
|
2426
|
-
banExpires?: Date | null | undefined;
|
|
2427
2426
|
banReason?: string | null | undefined;
|
|
2427
|
+
banExpires?: Date | null | undefined;
|
|
2428
2428
|
} & {
|
|
2429
2429
|
twoFactorEnabled: boolean | null | undefined;
|
|
2430
2430
|
} & {}>;
|
|
@@ -2450,7 +2450,7 @@ declare const useSession: () => {
|
|
|
2450
2450
|
};
|
|
2451
2451
|
declare const signIn: {
|
|
2452
2452
|
social: <FetchOptions extends better_auth.ClientFetchOption<Partial<{
|
|
2453
|
-
provider: "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat"
|
|
2453
|
+
provider: (string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
|
|
2454
2454
|
callbackURL?: string | undefined;
|
|
2455
2455
|
newUserCallbackURL?: string | undefined;
|
|
2456
2456
|
errorCallbackURL?: string | undefined;
|
|
@@ -2475,7 +2475,7 @@ declare const signIn: {
|
|
|
2475
2475
|
additionalParams?: Record<string, string> | undefined;
|
|
2476
2476
|
additionalData?: Record<string, any> | undefined;
|
|
2477
2477
|
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: better_auth.Prettify<Omit<{
|
|
2478
|
-
provider: "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat"
|
|
2478
|
+
provider: (string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
|
|
2479
2479
|
callbackURL?: string | undefined;
|
|
2480
2480
|
newUserCallbackURL?: string | undefined;
|
|
2481
2481
|
errorCallbackURL?: string | undefined;
|
|
@@ -2502,7 +2502,7 @@ declare const signIn: {
|
|
|
2502
2502
|
} & {
|
|
2503
2503
|
fetchOptions?: FetchOptions | undefined;
|
|
2504
2504
|
}, "provider"> & {
|
|
2505
|
-
provider: "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat"
|
|
2505
|
+
provider: (string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
|
|
2506
2506
|
fetchOptions?: FetchOptions | undefined;
|
|
2507
2507
|
}>, data_1?: FetchOptions | undefined) => Promise<_better_fetch_fetch.BetterFetchResponse<{
|
|
2508
2508
|
redirect: boolean;
|
|
@@ -2533,8 +2533,8 @@ declare const signIn: {
|
|
|
2533
2533
|
banned: boolean | null | undefined;
|
|
2534
2534
|
} & {
|
|
2535
2535
|
role?: string | null | undefined;
|
|
2536
|
-
banExpires?: Date | null | undefined;
|
|
2537
2536
|
banReason?: string | null | undefined;
|
|
2537
|
+
banExpires?: Date | null | undefined;
|
|
2538
2538
|
} & {
|
|
2539
2539
|
twoFactorEnabled: boolean | null | undefined;
|
|
2540
2540
|
} & {}>;
|
|
@@ -2581,8 +2581,8 @@ declare const signIn: {
|
|
|
2581
2581
|
banned: boolean | null | undefined;
|
|
2582
2582
|
} & {
|
|
2583
2583
|
role?: string | null | undefined;
|
|
2584
|
-
banExpires?: Date | null | undefined;
|
|
2585
2584
|
banReason?: string | null | undefined;
|
|
2585
|
+
banExpires?: Date | null | undefined;
|
|
2586
2586
|
} & {
|
|
2587
2587
|
twoFactorEnabled: boolean | null | undefined;
|
|
2588
2588
|
} & {}>;
|
|
@@ -3481,8 +3481,8 @@ declare const signUp: {
|
|
|
3481
3481
|
$Infer: {
|
|
3482
3482
|
body: {
|
|
3483
3483
|
permissions: {
|
|
3484
|
-
readonly user?: ("
|
|
3485
|
-
readonly session?: ("
|
|
3484
|
+
readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "set-email" | "get" | "update")[] | undefined;
|
|
3485
|
+
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
3486
3486
|
};
|
|
3487
3487
|
} & {
|
|
3488
3488
|
userId?: string | undefined;
|
|
@@ -3571,8 +3571,8 @@ declare const signUp: {
|
|
|
3571
3571
|
admin: {
|
|
3572
3572
|
checkRolePermission: <R extends "user" | "admin">(data: {
|
|
3573
3573
|
permissions: {
|
|
3574
|
-
readonly user?: ("
|
|
3575
|
-
readonly session?: ("
|
|
3574
|
+
readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "set-email" | "get" | "update")[] | undefined;
|
|
3575
|
+
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
3576
3576
|
};
|
|
3577
3577
|
} & {
|
|
3578
3578
|
role: R;
|
|
@@ -5155,8 +5155,8 @@ declare const signUp: {
|
|
|
5155
5155
|
banned: boolean | null | undefined;
|
|
5156
5156
|
} & {
|
|
5157
5157
|
role?: string | null | undefined;
|
|
5158
|
-
banExpires?: Date | null | undefined;
|
|
5159
5158
|
banReason?: string | null | undefined;
|
|
5159
|
+
banExpires?: Date | null | undefined;
|
|
5160
5160
|
} & {
|
|
5161
5161
|
twoFactorEnabled: boolean | null | undefined;
|
|
5162
5162
|
} & {}>;
|
|
@@ -5184,8 +5184,8 @@ declare const signUp: {
|
|
|
5184
5184
|
banned: boolean | null | undefined;
|
|
5185
5185
|
} & {
|
|
5186
5186
|
role?: string | null | undefined;
|
|
5187
|
-
banExpires?: Date | null | undefined;
|
|
5188
5187
|
banReason?: string | null | undefined;
|
|
5188
|
+
banExpires?: Date | null | undefined;
|
|
5189
5189
|
} & {
|
|
5190
5190
|
twoFactorEnabled: boolean | null | undefined;
|
|
5191
5191
|
} & {}>;
|
|
@@ -826,8 +826,8 @@ declare const authClient: better_auth_react.ReactAuthClient<{
|
|
|
826
826
|
$Infer: {
|
|
827
827
|
body: {
|
|
828
828
|
permissions: {
|
|
829
|
-
readonly user?: ("
|
|
830
|
-
readonly session?: ("
|
|
829
|
+
readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "set-email" | "get" | "update")[] | undefined;
|
|
830
|
+
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
831
831
|
};
|
|
832
832
|
} & {
|
|
833
833
|
userId?: string | undefined;
|
|
@@ -916,8 +916,8 @@ declare const authClient: better_auth_react.ReactAuthClient<{
|
|
|
916
916
|
admin: {
|
|
917
917
|
checkRolePermission: <R extends "user" | "admin">(data: {
|
|
918
918
|
permissions: {
|
|
919
|
-
readonly user?: ("
|
|
920
|
-
readonly session?: ("
|
|
919
|
+
readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "set-email" | "get" | "update")[] | undefined;
|
|
920
|
+
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
921
921
|
};
|
|
922
922
|
} & {
|
|
923
923
|
role: R;
|
|
@@ -2423,8 +2423,8 @@ declare const useSession: () => {
|
|
|
2423
2423
|
banned: boolean | null | undefined;
|
|
2424
2424
|
} & {
|
|
2425
2425
|
role?: string | null | undefined;
|
|
2426
|
-
banExpires?: Date | null | undefined;
|
|
2427
2426
|
banReason?: string | null | undefined;
|
|
2427
|
+
banExpires?: Date | null | undefined;
|
|
2428
2428
|
} & {
|
|
2429
2429
|
twoFactorEnabled: boolean | null | undefined;
|
|
2430
2430
|
} & {}>;
|
|
@@ -2450,7 +2450,7 @@ declare const useSession: () => {
|
|
|
2450
2450
|
};
|
|
2451
2451
|
declare const signIn: {
|
|
2452
2452
|
social: <FetchOptions extends better_auth.ClientFetchOption<Partial<{
|
|
2453
|
-
provider: "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat"
|
|
2453
|
+
provider: (string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
|
|
2454
2454
|
callbackURL?: string | undefined;
|
|
2455
2455
|
newUserCallbackURL?: string | undefined;
|
|
2456
2456
|
errorCallbackURL?: string | undefined;
|
|
@@ -2475,7 +2475,7 @@ declare const signIn: {
|
|
|
2475
2475
|
additionalParams?: Record<string, string> | undefined;
|
|
2476
2476
|
additionalData?: Record<string, any> | undefined;
|
|
2477
2477
|
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: better_auth.Prettify<Omit<{
|
|
2478
|
-
provider: "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat"
|
|
2478
|
+
provider: (string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
|
|
2479
2479
|
callbackURL?: string | undefined;
|
|
2480
2480
|
newUserCallbackURL?: string | undefined;
|
|
2481
2481
|
errorCallbackURL?: string | undefined;
|
|
@@ -2502,7 +2502,7 @@ declare const signIn: {
|
|
|
2502
2502
|
} & {
|
|
2503
2503
|
fetchOptions?: FetchOptions | undefined;
|
|
2504
2504
|
}, "provider"> & {
|
|
2505
|
-
provider: "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat"
|
|
2505
|
+
provider: (string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
|
|
2506
2506
|
fetchOptions?: FetchOptions | undefined;
|
|
2507
2507
|
}>, data_1?: FetchOptions | undefined) => Promise<_better_fetch_fetch.BetterFetchResponse<{
|
|
2508
2508
|
redirect: boolean;
|
|
@@ -2533,8 +2533,8 @@ declare const signIn: {
|
|
|
2533
2533
|
banned: boolean | null | undefined;
|
|
2534
2534
|
} & {
|
|
2535
2535
|
role?: string | null | undefined;
|
|
2536
|
-
banExpires?: Date | null | undefined;
|
|
2537
2536
|
banReason?: string | null | undefined;
|
|
2537
|
+
banExpires?: Date | null | undefined;
|
|
2538
2538
|
} & {
|
|
2539
2539
|
twoFactorEnabled: boolean | null | undefined;
|
|
2540
2540
|
} & {}>;
|
|
@@ -2581,8 +2581,8 @@ declare const signIn: {
|
|
|
2581
2581
|
banned: boolean | null | undefined;
|
|
2582
2582
|
} & {
|
|
2583
2583
|
role?: string | null | undefined;
|
|
2584
|
-
banExpires?: Date | null | undefined;
|
|
2585
2584
|
banReason?: string | null | undefined;
|
|
2585
|
+
banExpires?: Date | null | undefined;
|
|
2586
2586
|
} & {
|
|
2587
2587
|
twoFactorEnabled: boolean | null | undefined;
|
|
2588
2588
|
} & {}>;
|
|
@@ -3481,8 +3481,8 @@ declare const signUp: {
|
|
|
3481
3481
|
$Infer: {
|
|
3482
3482
|
body: {
|
|
3483
3483
|
permissions: {
|
|
3484
|
-
readonly user?: ("
|
|
3485
|
-
readonly session?: ("
|
|
3484
|
+
readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "set-email" | "get" | "update")[] | undefined;
|
|
3485
|
+
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
3486
3486
|
};
|
|
3487
3487
|
} & {
|
|
3488
3488
|
userId?: string | undefined;
|
|
@@ -3571,8 +3571,8 @@ declare const signUp: {
|
|
|
3571
3571
|
admin: {
|
|
3572
3572
|
checkRolePermission: <R extends "user" | "admin">(data: {
|
|
3573
3573
|
permissions: {
|
|
3574
|
-
readonly user?: ("
|
|
3575
|
-
readonly session?: ("
|
|
3574
|
+
readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "set-email" | "get" | "update")[] | undefined;
|
|
3575
|
+
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
3576
3576
|
};
|
|
3577
3577
|
} & {
|
|
3578
3578
|
role: R;
|
|
@@ -5155,8 +5155,8 @@ declare const signUp: {
|
|
|
5155
5155
|
banned: boolean | null | undefined;
|
|
5156
5156
|
} & {
|
|
5157
5157
|
role?: string | null | undefined;
|
|
5158
|
-
banExpires?: Date | null | undefined;
|
|
5159
5158
|
banReason?: string | null | undefined;
|
|
5159
|
+
banExpires?: Date | null | undefined;
|
|
5160
5160
|
} & {
|
|
5161
5161
|
twoFactorEnabled: boolean | null | undefined;
|
|
5162
5162
|
} & {}>;
|
|
@@ -5184,8 +5184,8 @@ declare const signUp: {
|
|
|
5184
5184
|
banned: boolean | null | undefined;
|
|
5185
5185
|
} & {
|
|
5186
5186
|
role?: string | null | undefined;
|
|
5187
|
-
banExpires?: Date | null | undefined;
|
|
5188
5187
|
banReason?: string | null | undefined;
|
|
5188
|
+
banExpires?: Date | null | undefined;
|
|
5189
5189
|
} & {
|
|
5190
5190
|
twoFactorEnabled: boolean | null | undefined;
|
|
5191
5191
|
} & {}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { db } from './chunk-
|
|
1
|
+
import { db } from './chunk-ZJMFE5B2.mjs';
|
|
2
2
|
import { errorRefs } from './chunk-TR5XFBTD.mjs';
|
|
3
3
|
import 'server-only';
|
|
4
4
|
import { randomBytes } from 'crypto';
|
|
@@ -72,5 +72,5 @@ function withActionErrors(action) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export { ActionError, lookupErrorDetail, unexpectedErrorMessage, withActionErrors };
|
|
75
|
-
//# sourceMappingURL=chunk-
|
|
76
|
-
//# sourceMappingURL=chunk-
|
|
75
|
+
//# sourceMappingURL=chunk-6MDQRT6U.mjs.map
|
|
76
|
+
//# sourceMappingURL=chunk-6MDQRT6U.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/errors.ts"],"names":[],"mappings":"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","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../lib/errors.ts"],"names":[],"mappings":"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","file":"chunk-6MDQRT6U.mjs","sourcesContent":["import \"server-only\"\nimport { randomBytes } from \"node:crypto\"\nimport { after } from \"next/server\"\nimport { eq, lt } from \"drizzle-orm\"\nimport { db } from \"../server/db\"\nimport { errorRefs } from \"../server/db/schema\"\n\n// No 0/O/1/I so a user can read a ref back without mix-ups.\nconst REF_ALPHABET = \"23456789ABCDEFGHJKLMNPQRSTUVWXYZ\"\nconst REF_PATTERN = /\\(ref: ([2-9A-HJ-NP-Z]{6})\\)/\nconst DETAIL_MAX_LENGTH = 8000\nconst RETENTION_MS = 30 * 24 * 60 * 60 * 1000\n\nfunction describeError(error: unknown): string {\n if (error instanceof Error) return error.stack || `${error.name}: ${error.message}`\n try {\n return JSON.stringify(error, null, 2)\n } catch {\n return String(error)\n }\n}\n\n// Saves the detail so a later feedback report can carry it. Runs after the\n// response when inside a request (after()), and never fails the caller.\nfunction storeDetail(ref: string, message: string, error: unknown) {\n const save = async () => {\n try {\n await db.insert(errorRefs).values({\n ref,\n message,\n detail: describeError(error).slice(0, DETAIL_MAX_LENGTH),\n })\n // ponytail: prune on insert; a scheduled cleanup if error volume ever gets high.\n await db.delete(errorRefs).where(lt(errorRefs.createdAt, new Date(Date.now() - RETENTION_MS)))\n } catch (storeError) {\n console.error(`[error ref ${ref}] could not store error detail`, storeError)\n }\n }\n try {\n after(save)\n } catch {\n void save() // outside a request scope (e.g. a script)\n }\n}\n\n/** The stored technical detail for the ref inside a user-facing error message, if any. */\nexport async function lookupErrorDetail(text: string): Promise<string | null> {\n const ref = text.match(REF_PATTERN)?.[1]\n if (!ref) return null\n const [row] = await db\n .select({ detail: errorRefs.detail })\n .from(errorRefs)\n .where(eq(errorRefs.ref, ref))\n .limit(1)\n return row?.detail ?? null\n}\n\n/**\n * Logs an unexpected error under a short reference and returns the message to\n * show instead, e.g. \"Failed to update role (ref: K7Q2PX)\". The user, and any\n * feedback report they send, only ever get the ref. The error itself goes to\n * the server log (\"error ref K7Q2PX\") and the error_refs table, from which the\n * feedback route attaches it to reports sent to Discord/email.\n */\nexport function unexpectedErrorMessage(\n error: unknown,\n message = \"Something went wrong. Please try again.\"\n): string {\n const ref = Array.from(randomBytes(6), (b) => REF_ALPHABET[b % REF_ALPHABET.length]).join(\"\")\n console.error(`[error ref ${ref}] ${message}`, error)\n storeDetail(ref, message, error)\n return `${message} (ref: ${ref})`\n}\n\n/**\n * Throw this for messages that are safe to show to the end user (validation,\n * permission, and state-conflict errors). Anything else thrown inside a\n * `withActionErrors`-wrapped action is logged server-side and replaced with a\n * generic message carrying an error ref.\n */\nexport class ActionError extends Error {}\n\nexport type ActionResult<T> =\n | { ok: true; data: T }\n | { ok: false; message: string }\n\nfunction isFrameworkControlFlowError(error: unknown): boolean {\n const digest = (error as { digest?: unknown } | null)?.digest\n return (\n typeof digest === \"string\" &&\n (digest.startsWith(\"NEXT_REDIRECT\") || digest.startsWith(\"NEXT_HTTP_ERROR_FALLBACK\"))\n )\n}\n\n/** Wraps a server action so it returns an ActionResult instead of throwing. */\nexport function withActionErrors<Args extends unknown[], T>(\n action: (...args: Args) => Promise<T>\n): (...args: Args) => Promise<ActionResult<T>> {\n return async (...args: Args): Promise<ActionResult<T>> => {\n try {\n return { ok: true, data: await action(...args) }\n } catch (error) {\n if (error instanceof ActionError) {\n return { ok: false, message: error.message }\n }\n if (isFrameworkControlFlowError(error)) {\n throw error\n }\n // Returned, not thrown: Next.js replaces thrown server-action messages\n // in production, which would drop the ref.\n return { ok: false, message: unexpectedErrorMessage(error) }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { auth } from './chunk-7QVIOQ47.mjs';
|
|
2
|
+
import { db } from './chunk-ZJMFE5B2.mjs';
|
|
3
|
+
import { users } from './chunk-TR5XFBTD.mjs';
|
|
4
|
+
import 'server-only';
|
|
5
|
+
import { cache } from 'react';
|
|
6
|
+
import { headers } from 'next/headers';
|
|
7
|
+
import { redirect } from 'next/navigation';
|
|
8
|
+
import { eq } from 'drizzle-orm';
|
|
9
|
+
|
|
10
|
+
var getCurrentSession = cache(async () => {
|
|
11
|
+
const session = await auth.api.getSession({
|
|
12
|
+
headers: await headers()
|
|
13
|
+
});
|
|
14
|
+
return session;
|
|
15
|
+
});
|
|
16
|
+
var requireAuth = cache(async () => {
|
|
17
|
+
var _a;
|
|
18
|
+
const session = await getCurrentSession();
|
|
19
|
+
if (!(session == null ? void 0 : session.user)) {
|
|
20
|
+
redirect("/login");
|
|
21
|
+
}
|
|
22
|
+
const user = await db.select().from(users).where(eq(users.id, session.user.id)).limit(1);
|
|
23
|
+
if ((_a = user[0]) == null ? void 0 : _a.banned) {
|
|
24
|
+
await auth.api.signOut({
|
|
25
|
+
headers: await headers()
|
|
26
|
+
});
|
|
27
|
+
redirect("/login?error=account_banned");
|
|
28
|
+
}
|
|
29
|
+
return session;
|
|
30
|
+
});
|
|
31
|
+
function hasRole(userRole, requiredRole) {
|
|
32
|
+
if (!userRole) return false;
|
|
33
|
+
if (requiredRole === "admin") {
|
|
34
|
+
return userRole === "admin";
|
|
35
|
+
}
|
|
36
|
+
return userRole === "user" || userRole === "admin" || userRole === "tester";
|
|
37
|
+
}
|
|
38
|
+
async function requireRole(requiredRole) {
|
|
39
|
+
const session = await requireAuth();
|
|
40
|
+
if (!hasRole(session.user.role, requiredRole)) {
|
|
41
|
+
redirect("/dashboard");
|
|
42
|
+
}
|
|
43
|
+
return session;
|
|
44
|
+
}
|
|
45
|
+
function normalizeCallbackUrl(value) {
|
|
46
|
+
if (typeof value !== "string" || !value.trim()) return "/dashboard";
|
|
47
|
+
const candidate = value.trim();
|
|
48
|
+
try {
|
|
49
|
+
const appOrigin = new URL(process.env.NEXT_PUBLIC_APP_URL || process.env.BETTER_AUTH_URL || "http://localhost:3000").origin;
|
|
50
|
+
const callbackUrl = new URL(candidate, appOrigin);
|
|
51
|
+
if (callbackUrl.origin !== appOrigin) {
|
|
52
|
+
return "/dashboard";
|
|
53
|
+
}
|
|
54
|
+
return `${callbackUrl.pathname}${callbackUrl.search}${callbackUrl.hash}` || "/dashboard";
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return "/dashboard";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { getCurrentSession, hasRole, normalizeCallbackUrl, requireAuth, requireRole };
|
|
61
|
+
//# sourceMappingURL=chunk-6Z3WUSDJ.mjs.map
|
|
62
|
+
//# sourceMappingURL=chunk-6Z3WUSDJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../lib/auth-helpers.ts"],"names":[],"mappings":";;;;;;;;;AAgBO,IAAM,iBAAA,GAAoB,MAAM,YAAY;AACjD,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,GAAA,CAAI,UAAA,CAAW;AAAA,IACxC,OAAA,EAAS,MAAM,OAAA;AAAQ,GACxB,CAAA;AAED,EAAA,OAAO,OAAA;AACT,CAAC;AASM,IAAM,WAAA,GAAc,MAAM,YAAY;AA/B7C,EAAA,IAAA,EAAA;AAgCE,EAAA,MAAM,OAAA,GAAU,MAAM,iBAAA,EAAkB;AAExC,EAAA,IAAI,EAAC,mCAAS,IAAA,CAAA,EAAM;AAClB,IAAA,QAAA,CAAS,QAAQ,CAAA;AAAA,EACnB;AAGA,EAAA,MAAM,OAAO,MAAM,EAAA,CAChB,QAAO,CACP,IAAA,CAAK,KAAK,CAAA,CACV,KAAA,CAAM,EAAA,CAAG,KAAA,CAAM,IAAI,OAAA,CAAQ,IAAA,CAAK,EAAE,CAAC,CAAA,CACnC,MAAM,CAAC,CAAA;AAEV,EAAA,IAAA,CAAI,EAAA,GAAA,IAAA,CAAK,CAAC,CAAA,KAAN,IAAA,GAAA,MAAA,GAAA,EAAA,CAAS,MAAA,EAAQ;AAEnB,IAAA,MAAM,IAAA,CAAK,IAAI,OAAA,CAAQ;AAAA,MACrB,OAAA,EAAS,MAAM,OAAA;AAAQ,KACxB,CAAA;AACD,IAAA,QAAA,CAAS,6BAA6B,CAAA;AAAA,EACxC;AAEA,EAAA,OAAO,OAAA;AACT,CAAC;AAKM,SAAS,OAAA,CAAQ,UAAqC,YAAA,EAAyC;AACpG,EAAA,IAAI,CAAC,UAAU,OAAO,KAAA;AAEtB,EAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,IAAA,OAAO,QAAA,KAAa,OAAA;AAAA,EACtB;AAGA,EAAA,OAAO,QAAA,KAAa,MAAA,IAAU,QAAA,KAAa,OAAA,IAAW,QAAA,KAAa,QAAA;AACrE;AAKA,eAAsB,YAAY,YAAA,EAAgC;AAChE,EAAA,MAAM,OAAA,GAAU,MAAM,WAAA,EAAY;AAElC,EAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,IAAA,EAAM,YAAY,CAAA,EAAG;AAC7C,IAAA,QAAA,CAAS,YAAY,CAAA;AAAA,EACvB;AAEA,EAAA,OAAO,OAAA;AACT;AAQO,SAAS,qBAAqB,KAAA,EAAmC;AACtE,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,KAAA,CAAM,IAAA,IAAQ,OAAO,YAAA;AAEvD,EAAA,MAAM,SAAA,GAAY,MAAM,IAAA,EAAK;AAE7B,EAAA,IAAI;AACF,IAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,uBAAuB,OAAA,CAAQ,GAAA,CAAI,eAAA,IAAmB,uBAAuB,CAAA,CAAE,MAAA;AACrH,IAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,SAAA,EAAW,SAAS,CAAA;AAEhD,IAAA,IAAI,WAAA,CAAY,WAAW,SAAA,EAAW;AACpC,MAAA,OAAO,YAAA;AAAA,IACT;AAEA,IAAA,OAAO,CAAA,EAAG,YAAY,QAAQ,CAAA,EAAG,YAAY,MAAM,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA,CAAA,IAAM,YAAA;AAAA,EAC9E,CAAA,CAAA,OAAQ,CAAA,EAAA;AACN,IAAA,OAAO,YAAA;AAAA,EACT;AACF","file":"chunk-6Z3WUSDJ.mjs","sourcesContent":["import \"server-only\"\nimport { cache } from \"react\"\nimport { auth } from \"../server/auth\"\nimport { headers } from \"next/headers\"\nimport { redirect } from \"next/navigation\"\nimport { db } from \"../server/db\"\nimport { users } from \"../server/db/schema\"\nimport { eq } from \"drizzle-orm\"\n\n/**\n * Get current session (server-side only)\n * Returns session data or null if not authenticated\n *\n * Wrapped in React's cache() so the layout and page (both of which call\n * requireAuth per request) share one auth.api.getSession call instead of two.\n */\nexport const getCurrentSession = cache(async () => {\n const session = await auth.api.getSession({\n headers: await headers()\n })\n\n return session\n})\n\n/**\n * Require authentication - redirect to login if not authenticated\n * Use in server components/layouts that need protection\n *\n * Wrapped in cache() so the layout, page, and any admin layout that all call\n * requireAuth per request share one banned-status DB lookup instead of several.\n */\nexport const requireAuth = cache(async () => {\n const session = await getCurrentSession()\n\n if (!session?.user) {\n redirect('/login')\n }\n\n // Check if user is banned\n const user = await db\n .select()\n .from(users)\n .where(eq(users.id, session.user.id))\n .limit(1)\n\n if (user[0]?.banned) {\n // Sign out the user\n await auth.api.signOut({\n headers: await headers()\n })\n redirect('/login?error=account_banned')\n }\n\n return session\n})\n\n/**\n * Check if user has specific role\n */\nexport function hasRole(userRole: string | null | undefined, requiredRole: 'user' | 'admin'): boolean {\n if (!userRole) return false\n\n if (requiredRole === 'admin') {\n return userRole === 'admin'\n }\n\n // 'user' role check - every role ('user', 'admin', 'tester') has access\n return userRole === 'user' || userRole === 'admin' || userRole === 'tester'\n}\n\n/**\n * Require specific role - throw error if user doesn't have it\n */\nexport async function requireRole(requiredRole: 'user' | 'admin') {\n const session = await requireAuth()\n\n if (!hasRole(session.user.role, requiredRole)) {\n redirect('/dashboard')\n }\n\n return session\n}\n\n/**\n * Normalize a callbackUrl query param to a same-origin path before it's ever\n * passed to redirect() or an auth form's callbackURL. Guards against open\n * redirects (e.g. /login?callbackUrl=https://evil.com) — anything that\n * doesn't resolve to this app's own origin falls back to /dashboard.\n */\nexport function normalizeCallbackUrl(value?: string | string[]): string {\n if (typeof value !== 'string' || !value.trim()) return '/dashboard'\n\n const candidate = value.trim()\n\n try {\n const appOrigin = new URL(process.env.NEXT_PUBLIC_APP_URL || process.env.BETTER_AUTH_URL || 'http://localhost:3000').origin\n const callbackUrl = new URL(candidate, appOrigin)\n\n if (callbackUrl.origin !== appOrigin) {\n return '/dashboard'\n }\n\n return `${callbackUrl.pathname}${callbackUrl.search}${callbackUrl.hash}` || '/dashboard'\n } catch {\n return '/dashboard'\n }\n}\n\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { db } from './chunk-
|
|
1
|
+
import { db } from './chunk-ZJMFE5B2.mjs';
|
|
2
2
|
import { activityLogs } from './chunk-TR5XFBTD.mjs';
|
|
3
3
|
import 'server-only';
|
|
4
4
|
|
|
@@ -37,5 +37,5 @@ async function logActivity({
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export { logActivity };
|
|
40
|
-
//# sourceMappingURL=chunk-
|
|
41
|
-
//# sourceMappingURL=chunk-
|
|
40
|
+
//# sourceMappingURL=chunk-7F2POZM6.mjs.map
|
|
41
|
+
//# sourceMappingURL=chunk-7F2POZM6.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/activity-logger.ts"],"names":[],"mappings":";;;;AAsCA,eAAsB,WAAA,CAAY;AAAA,EAChC,MAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,EAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAsB;AACpB,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,CAAG,MAAA,CAAO,YAAY,CAAA,CAAE,MAAA,CAAO;AAAA,MACnC,MAAA;AAAA,MACA,MAAA;AAAA,MACA,WAAA;AAAA,MACA,WAAW,SAAA,IAAa,IAAA;AAAA,MACxB,WAAW,SAAA,IAAa,IAAA;AAAA,MACxB,QAAA,EAAU,QAAA,GAAW,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAA,GAAI,IAAA;AAAA,MAChD,aAAa,WAAA,IAAe,IAAA;AAAA,MAC5B,IAAI,EAAA,IAAM,IAAA;AAAA,MACV,SAAS,OAAA,IAAW,IAAA;AAAA,MACpB,YAAY,UAAA,IAAc,IAAA;AAAA,MAC1B,SAAS,OAAA,IAAW,IAAA;AAAA,MACpB,MAAM,IAAA,IAAQ;AAAA,KACf,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,2BAA2B,KAAK,CAAA;AAAA,EAEhD;AACF","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../lib/activity-logger.ts"],"names":[],"mappings":";;;;AAsCA,eAAsB,WAAA,CAAY;AAAA,EAChC,MAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,EAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAsB;AACpB,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,CAAG,MAAA,CAAO,YAAY,CAAA,CAAE,MAAA,CAAO;AAAA,MACnC,MAAA;AAAA,MACA,MAAA;AAAA,MACA,WAAA;AAAA,MACA,WAAW,SAAA,IAAa,IAAA;AAAA,MACxB,WAAW,SAAA,IAAa,IAAA;AAAA,MACxB,QAAA,EAAU,QAAA,GAAW,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAA,GAAI,IAAA;AAAA,MAChD,aAAa,WAAA,IAAe,IAAA;AAAA,MAC5B,IAAI,EAAA,IAAM,IAAA;AAAA,MACV,SAAS,OAAA,IAAW,IAAA;AAAA,MACpB,YAAY,UAAA,IAAc,IAAA;AAAA,MAC1B,SAAS,OAAA,IAAW,IAAA;AAAA,MACpB,MAAM,IAAA,IAAQ;AAAA,KACf,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,2BAA2B,KAAK,CAAA;AAAA,EAEhD;AACF","file":"chunk-7F2POZM6.mjs","sourcesContent":["import \"server-only\"\nimport { db } from \"../server/db\"\nimport { activityLogs } from \"../server/db/schema\"\n\nexport type ActivityAction =\n | 'login'\n | 'logout'\n | 'login_failed'\n | 'password_changed'\n | 'email_changed'\n | 'profile_updated'\n | 'session_revoked'\n | 'user_deleted'\n | 'user_banned'\n | 'user_unbanned'\n | 'role_changed'\n | 'impersonation_started'\n | 'impersonation_stopped'\n | 'settings_changed'\n | 'passkey_added'\n | 'passkey_removed'\n\ninterface LogActivityParams {\n userId: string\n action: ActivityAction\n description: string\n ipAddress?: string | null\n userAgent?: string | null\n metadata?: Record<string, any>\n // Populated on 'login' events only — see server/auth.ts's session.create hook.\n referrerUrl?: string | null\n os?: string | null\n browser?: string | null\n deviceType?: string | null\n country?: string | null\n city?: string | null\n}\n\nexport async function logActivity({\n userId,\n action,\n description,\n ipAddress,\n userAgent,\n metadata,\n referrerUrl,\n os,\n browser,\n deviceType,\n country,\n city,\n}: LogActivityParams) {\n try {\n await db.insert(activityLogs).values({\n userId,\n action,\n description,\n ipAddress: ipAddress || null,\n userAgent: userAgent || null,\n metadata: metadata ? JSON.stringify(metadata) : null,\n referrerUrl: referrerUrl || null,\n os: os || null,\n browser: browser || null,\n deviceType: deviceType || null,\n country: country || null,\n city: city || null,\n })\n } catch (error) {\n console.error('Failed to log activity:', error)\n // Don't throw - activity logging should not break the app\n }\n}\n"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { logActivity } from './chunk-
|
|
1
|
+
import { logActivity } from './chunk-7F2POZM6.mjs';
|
|
2
2
|
import { getClientIp, parseUserAgent, lookupGeoLocation, formatLocation, formatDeviceInfo, WINDOW_MS, isOverLimit, MAX_IP, RETENTION_MS } from './chunk-6OAHCEDJ.mjs';
|
|
3
|
-
import { getEffectiveAuthFlags } from './chunk-
|
|
4
|
-
import {
|
|
3
|
+
import { getEffectiveAuthFlags } from './chunk-P2G23JMY.mjs';
|
|
4
|
+
import { getResend, EMAIL_FROM } from './chunk-OVKVD2HH.mjs';
|
|
5
|
+
import { db } from './chunk-ZJMFE5B2.mjs';
|
|
5
6
|
import { users, sessions, passkeys, twoFactors, verifications, accounts, authThrottle } from './chunk-TR5XFBTD.mjs';
|
|
6
|
-
import { config } from './chunk-
|
|
7
|
+
import { config } from './chunk-TKWNEKK6.mjs';
|
|
7
8
|
import { __spreadValues, __spreadProps } from './chunk-3465SI5N.mjs';
|
|
8
9
|
import 'server-only';
|
|
9
10
|
import { betterAuth } from 'better-auth';
|
|
@@ -67,15 +68,6 @@ async function clearDeviceThrottle(fingerprint) {
|
|
|
67
68
|
console.error("auth-throttle clear failed", error);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
|
-
var EMAIL_FROM = config.EMAIL_FROM;
|
|
71
|
-
var resend;
|
|
72
|
-
async function getResend() {
|
|
73
|
-
if (!resend) {
|
|
74
|
-
const { Resend } = await import('resend');
|
|
75
|
-
resend = new Resend(config.RESEND_API_KEY);
|
|
76
|
-
}
|
|
77
|
-
return resend;
|
|
78
|
-
}
|
|
79
71
|
|
|
80
72
|
// server/auth.ts
|
|
81
73
|
async function logFailedLogin(ctx) {
|
|
@@ -465,6 +457,6 @@ var auth = new Proxy({}, {
|
|
|
465
457
|
}
|
|
466
458
|
});
|
|
467
459
|
|
|
468
|
-
export {
|
|
469
|
-
//# sourceMappingURL=chunk-
|
|
470
|
-
//# sourceMappingURL=chunk-
|
|
460
|
+
export { auth, createAuth };
|
|
461
|
+
//# sourceMappingURL=chunk-7QVIOQ47.mjs.map
|
|
462
|
+
//# sourceMappingURL=chunk-7QVIOQ47.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../lib/cookie-names.ts","../lib/auth-throttle.ts","../server/auth.ts"],"names":["eq","sql","current","adminPlugin"],"mappings":";;;;;;;;;;;;;;;;;;AAMO,IAAM,qBAAA,GAAwB,gBAAA;AAM9B,IAAM,yBAAA,GAA4B,sBAAA;ACHzC,eAAsB,iBAAA,CAAkB,aAAqB,GAAA,EAAgC;AAC3F,EAAA,IAAI;AACF,IAAA,MAAM,CAAC,GAAG,CAAA,GAAI,MAAM,EAAA,CACjB,MAAA,CAAO,EAAE,KAAA,EAAO,YAAA,CAAa,KAAA,EAAO,WAAA,EAAa,YAAA,CAAa,WAAA,EAAa,CAAA,CAC3E,IAAA,CAAK,YAAY,CAAA,CACjB,KAAA,CAAM,EAAA,CAAG,YAAA,CAAa,WAAA,EAAa,WAAW,CAAC,CAAA,CAC/C,KAAA,CAAM,CAAC,CAAA;AACV,IAAA,OAAO,WAAA,CAAY,GAAA,EAAK,IAAA,CAAK,GAAA,IAAO,GAAG,CAAA;AAAA,EACzC,SAAS,KAAA,EAAO;AAEd,IAAA,OAAA,CAAQ,KAAA,CAAM,+CAA+C,KAAK,CAAA;AAClE,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAcA,eAAsB,mBAAA,CAAoB,aAAqB,IAAA,EAAkC;AAC/F,EAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AACrB,EAAA,MAAM,SAAS,IAAI,IAAA,CAAK,GAAA,CAAI,OAAA,KAAY,SAAS,CAAA;AACjD,EAAA,MAAM,IAAA,GAAO;AAAA,IACX,aAAA,EAAe,GAAA;AAAA,IACf,WAAW,IAAA,CAAK,SAAA;AAAA,IAChB,WAAW,IAAA,CAAK,SAAA;AAAA,IAChB,WAAW,IAAA,CAAK,KAAA;AAAA,IAChB,UAAU,IAAA,CAAK;AAAA,GACjB;AACA,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,CACH,MAAA,CAAO,YAAY,CAAA,CACnB,MAAA,CAAO,cAAA,CAAA,EAAE,WAAA,EAAa,KAAA,EAAO,CAAA,EAAG,WAAA,EAAa,GAAA,EAAA,EAAQ,IAAA,CAAM,EAC3D,kBAAA,CAAmB;AAAA,MAClB,QAAQ,YAAA,CAAa,WAAA;AAAA,MACrB,GAAA,EAAK,cAAA,CAAA;AAAA,QACH,KAAA,EAAO,gBAAgB,YAAA,CAAa,WAAW,MAAM,MAAM,CAAA,aAAA,EAAgB,aAAa,KAAK,CAAA,QAAA,CAAA;AAAA,QAC7F,WAAA,EAAa,GAAA,CAAA,UAAA,EAAgB,YAAA,CAAa,WAAW,CAAA,GAAA,EAAM,MAAM,CAAA,MAAA,EAAS,GAAG,CAAA,MAAA,EAAS,YAAA,CAAa,WAAW,CAAA,IAAA;AAAA,OAAA,EAC3G,IAAA;AAAA,KAEN,CAAA;AAAA,EACL,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AAAA,EACnD;AACA,EAAA,MAAM,eAAe,GAAG,CAAA;AAC1B;AAMA,IAAM,cAAA,GAAiB,KAAK,EAAA,GAAK,GAAA;AACjC,IAAI,SAAA,GAAY,CAAA;AAEhB,eAAsB,cAAA,CAAe,GAAA,mBAAM,IAAI,IAAA,EAAK,EAAkB;AACpE,EAAA,IAAI,GAAA,CAAI,OAAA,EAAQ,GAAI,SAAA,GAAY,cAAA,EAAgB;AAChD,EAAA,SAAA,GAAY,IAAI,OAAA,EAAQ;AACxB,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,CACH,MAAA,CAAO,YAAY,CAAA,CACnB,MAAM,EAAA,CAAG,YAAA,CAAa,aAAA,EAAe,IAAI,KAAK,GAAA,CAAI,OAAA,EAAQ,GAAI,YAAY,CAAC,CAAC,CAAA;AAAA,EACjF,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AAAA,EACnD;AACF;AAEA,eAAsB,oBAAoB,WAAA,EAAoC;AAC5E,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,CAAG,OAAO,YAAY,CAAA,CAAE,MAAM,EAAA,CAAG,YAAA,CAAa,WAAA,EAAa,WAAW,CAAC,CAAA;AAAA,EAC/E,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AAAA,EACnD;AACF;;;AC1DA,eAAe,eAAe,GAAA,EAAmD;AA/BjF,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAgCE,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAA,CAAO,EAAA,GAAA,GAAA,CAAI,IAAA,KAAJ,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAA8C,KAAA;AAC3D,IAAA,MAAM,QAAQ,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,CAAI,aAAY,GAAI,IAAA;AAC5D,IAAA,IAAI,CAAC,KAAA,EAAO;AAEZ,IAAA,MAAM,CAAC,IAAI,CAAA,GAAI,MAAM,GAClB,MAAA,CAAO,EAAE,EAAA,EAAI,KAAA,CAAM,EAAA,EAAI,EACvB,IAAA,CAAK,KAAK,CAAA,CACV,KAAA,CAAMA,EAAAA,CAAG,KAAA,CAAM,OAAO,KAAK,CAAC,CAAA,CAC5B,KAAA,CAAM,CAAC,CAAA;AACV,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,MAAM,WAAA,CAAY;AAAA,MAChB,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,MAAA,EAAQ,cAAA;AAAA,MACR,WAAA,EAAa,sBAAA;AAAA,MACb,SAAA,EAAW,WAAA,CAAY,GAAA,CAAI,OAAO,CAAA;AAAA,MAClC,YAAW,EAAA,GAAA,CAAA,EAAA,GAAA,GAAA,CAAI,OAAA,KAAJ,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAa,GAAA,CAAI,kBAAjB,IAAA,GAAA,EAAA,GAAkC;AAAA,KAC9C,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,uCAAuC,KAAK,CAAA;AAAA,EAC5D;AACF;AAOA,eAAe,mBAAA,CACb,KACA,MAAA,EACA;AAhEF,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAiEE,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAU,MAAM,iBAAA,CAAkB,GAAG,CAAA;AAC3C,IAAA,MAAM,OAAO,OAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,OAAA,CAAS,IAAA;AACtB,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,MAAM,EAAA,GAAK,WAAA,CAAY,GAAA,CAAI,OAAO,CAAA;AAElC,IAAA,MAAM,WAAA,CAAY;AAAA,MAChB,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,MAAA,EAAQ,MAAA,KAAW,OAAA,GAAU,eAAA,GAAkB,iBAAA;AAAA,MAC/C,WAAA,EAAa,WAAW,MAAM,CAAA,CAAA;AAAA,MAC9B,SAAA,EAAW,EAAA;AAAA,MACX,YAAW,EAAA,GAAA,CAAA,EAAA,GAAA,GAAA,CAAI,OAAA,KAAJ,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAa,GAAA,CAAI,kBAAjB,IAAA,GAAA,EAAA,GAAkC;AAAA,KAC9C,CAAA;AAED,IAAA,IAAI,CAAC,OAAO,cAAA,EAAgB;AAE5B,IAAA,MAAM,EAAE,0BAAA,EAA2B,GAAI,MAAM,OAAO,8CAAkD,CAAA;AACtG,IAAA,MAAA,CAAO,MAAM,SAAA,EAAU,EAAG,MAAA,CAAO,IAAA,CAAK;AAAA,MACpC,IAAA,EAAM,UAAA;AAAA,MACN,EAAA,EAAI,CAAC,IAAA,CAAK,KAAK,CAAA;AAAA,MACf,OAAA,EAAS,MAAA,KAAW,OAAA,GAAU,qCAAA,GAAwC,yCAAA;AAAA,MACtE,OAAO,0BAAA,CAA2B;AAAA,QAChC,SAAA,EAAW,KAAK,IAAA,IAAQ,OAAA;AAAA,QACxB,MAAA;AAAA,QACA,MAAA,EAAQ,gBAAA,CAAiB,cAAA,CAAA,CAAe,EAAA,GAAA,CAAA,EAAA,GAAA,GAAA,CAAI,OAAA,KAAJ,mBAAa,GAAA,CAAI,YAAA,CAAA,KAAjB,IAAA,GAAA,EAAA,GAAkC,IAAI,CAAC,CAAA;AAAA,QAC/E,QAAA,EAAU,cAAA,CAAe,iBAAA,CAAkB,EAAE,CAAC,CAAA;AAAA,QAC9C,WAAW,EAAA,IAAM,SAAA;AAAA,QACjB,IAAA,EAAA,iBAAM,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,QAC7B,SAAA,EAAW,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,eAAA,IAAmB,OAAA,CAAQ,GAAA,CAAI,YAAA,IAAgB,OAAA,CAAQ,GAAA,CAAI,mBAAA,IAAuB,EAAE,CAAA,mBAAA;AAAA,OAC/G;AAAA,KACF,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,8CAA8C,KAAK,CAAA;AAAA,EACnE;AACF;AAEO,SAAS,UAAA,CAAW,SAAA,GAA2B,EAAC,EAAG;AACxD,EAAA,MAAM,eAAA,GAAqE,cAAA,CAAA,cAAA,CAAA,EAAA,EACrE,MAAA,CAAO,kBAAA,GAAqB;AAAA,IAC9B,MAAA,EAAQ;AAAA,MACN,UAAU,OAAA,CAAQ,GAAA,CAAI,cAAA,IAAkB,OAAA,CAAQ,IAAI,gBAAA,IAAoB,EAAA;AAAA,MACxE,cAAc,OAAA,CAAQ,GAAA,CAAI,kBAAA,IAAsB,OAAA,CAAQ,IAAI,oBAAA,IAAwB;AAAA;AACtF,GACF,GAAI,EAAC,CAAA,EACF,SAAA,CAAU,eAAA,CAAA;AAEf,EAAA,MAAM,0BAA0B,MAAA,CAAO,0BAAA;AAMvC,EAAA,MAAM,2BAA2B,MAAA,CAAO,MAAA,CAAO,eAAe,CAAA,CAAE,KAAK,OAAO,CAAA;AAE5E,EAAA,IAAI,CAAC,wBAAA,IAA4B,CAAC,uBAAA,EAAyB;AACzD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AAKA,EAAA,MAAM,UAAU,OAAA,CAAQ,GAAA,CAAI,YAAA,IAAgB,OAAA,CAAQ,IAAI,eAAA,IAAmB,uBAAA;AAE3E,EAAA,OAAO,UAAA,CAAW;AAAA,IAChB,QAAA,EAAU,eAAe,EAAA,EAAI;AAAA,MAC3B,QAAA,EAAU,IAAA;AAAA,MACV,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,KAAA;AAAA,QACN,OAAA,EAAS,QAAA;AAAA,QACT,OAAA,EAAS,QAAA;AAAA,QACT,YAAA,EAAc,aAAA;AAAA,QACd,SAAA,EAAW,UAAA;AAAA,QACX,OAAA,EAAS;AAAA;AACX,KACD,CAAA;AAAA;AAAA,IAGD,OAAA,EAAS;AAAA,MACP,SAAA,EAAW,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,EAAA;AAAA;AAAA,MAC1B,SAAA,EAAW,KAAK,EAAA,GAAK,EAAA;AAAA;AAAA,MACrB,WAAA,EAAa;AAAA,QACX,OAAA,EAAS,IAAA;AAAA;AAAA,QACT,QAAQ,CAAA,GAAI;AAAA;AAAA;AACd,KACF;AAAA;AAAA;AAAA,IAIA,gBAAA,EAAkB;AAAA,MAChB,OAAA,EAAS,uBAAA;AAAA,MACT,wBAAA,EAA0B,KAAA;AAAA;AAAA;AAAA;AAAA,MAI1B,MAAM,iBAAA,CAAkB,EAAE,IAAA,EAAM,KAAI,EAAG;AACrC,QAAA,MAAM,EAAE,0BAAA,EAA2B,GAAI,MAAM,OAAO,8CAAkD,CAAA;AACtG,QAAA,MAAA,CAAO,MAAM,SAAA,EAAU,EAAG,MAAA,CAAO,IAAA,CAAK;AAAA,UACpC,IAAA,EAAM,UAAA;AAAA,UACN,EAAA,EAAI,CAAC,IAAA,CAAK,KAAK,CAAA;AAAA,UACf,OAAA,EAAS,qBAAA;AAAA,UACT,KAAA,EAAO,2BAA2B,EAAE,SAAA,EAAW,KAAK,IAAA,IAAQ,OAAA,EAAS,KAAK;AAAA,SAC3E,CAAA;AAAA,MACH;AAAA,KACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,iBAAA,EAAmB;AAAA,MACjB,2BAAA,EAA6B,IAAA;AAAA,MAC7B,MAAM,qBAAA,CAAsB,EAAE,IAAA,EAAM,KAAI,EAAG;AACzC,QAAA,MAAM,EAAE,wBAAA,EAAyB,GAAI,MAAM,OAAO,4CAAgD,CAAA;AAClG,QAAA,MAAA,CAAO,MAAM,SAAA,EAAU,EAAG,MAAA,CAAO,IAAA,CAAK;AAAA,UACpC,IAAA,EAAM,UAAA;AAAA,UACN,EAAA,EAAI,CAAC,IAAA,CAAK,KAAK,CAAA;AAAA,UACf,OAAA,EAAS,oBAAA;AAAA,UACT,KAAA,EAAO,yBAAyB,EAAE,SAAA,EAAW,KAAK,IAAA,IAAQ,OAAA,EAAS,KAAK;AAAA,SACzE,CAAA;AAAA,MACH;AAAA,KACF;AAAA;AAAA;AAAA,IAIA,eAAA;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,MAAA;AAAA,UACd,KAAA,EAAO;AAAA;AAAA;AACT,OACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,UAAA,EAAY,EAAE,OAAA,EAAS,IAAA;AAAK,KAC9B;AAAA;AAAA,IAGA,QAAA,EAAU;AAAA,MACR,QAAA,EAAU;AAAA,QACR,UAAA,EAAY,MAAM,QAAA;AAAS;AAC7B,KACF;AAAA;AAAA;AAAA;AAAA,IAKA,aAAA,EAAe;AAAA,MACb,OAAA,EAAS;AAAA,QACP,MAAA,EAAQ;AAAA,UACN,MAAM,KAAA,CAAM,OAAA,EAAS,OAAA,EAAS;AAnOxC,YAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAwOY,YAAA,MAAM,iBAAkB,OAAA,CAA+C,cAAA;AACvE,YAAA,IAAI,cAAA,EAAgB;AAClB,cAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAClB,MAAA,CAAO,EAAE,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,CAAA,CAC7B,IAAA,CAAK,KAAK,CAAA,CACV,KAAA,CAAMA,GAAG,KAAA,CAAM,EAAA,EAAI,QAAQ,MAAM,CAAC,CAAA,CAClC,KAAA,CAAM,CAAC,CAAA;AAEV,cAAA,MAAM,WAAA,CAAY;AAAA,gBAChB,MAAA,EAAQ,cAAA;AAAA,gBACR,MAAA,EAAQ,uBAAA;AAAA,gBACR,WAAA,EAAa,gCAA+B,EAAA,GAAA,CAAA,EAAA,GAAA,MAAA,CAAO,CAAC,MAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAW,KAAA,KAAX,IAAA,GAAA,EAAA,GAAoB,OAAA,CAAQ,MAAM,CAAA,CAAA;AAAA,gBAC9E,QAAA,EAAU,EAAE,kBAAA,EAAoB,OAAA,CAAQ,MAAA;AAAO,eAChD,CAAA;AACD,cAAA;AAAA,YACF;AAEA,YAAA,IAAI;AACF,cAAA,MAAM,GACH,MAAA,CAAO,KAAK,EACZ,GAAA,CAAI,EAAE,6BAAa,IAAI,IAAA,EAAK,EAAG,EAC/B,KAAA,CAAMA,EAAAA,CAAG,MAAM,EAAA,EAAI,OAAA,CAAQ,MAAM,CAAC,CAAA;AAAA,YACvC,SAAS,KAAA,EAAO;AACd,cAAA,OAAA,CAAQ,KAAA,CAAM,gCAAgC,KAAK,CAAA;AAAA,YACrD;AAEA,YAAA,IAAI;AACF,cAAA,MAAM,UAAU,OAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,OAAA,CAAS,OAAA;AACzB,cAAA,MAAM,SAAA,GAAY,YAAY,OAAO,CAAA;AACrC,cAAA,MAAM,SAAA,GAAA,CAAY,EAAA,GAAA,OAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,OAAA,CAAS,GAAA,CAAI,YAAA,CAAA,KAAb,IAAA,GAAA,EAAA,GAA8B,IAAA;AAQhD,cAAA,MAAM,gBAAc,EAAA,GAAA,OAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,OAAA,CAAS,SAAA,KAAT,iCAAqB,qBAAA,CAAA,MACpC,OAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,OAAA,CAAS,IAAI,SAAA,CAAA,CAAA,IACb,IAAA;AACL,cAAA,MAAM,MAAA,GAAS,eAAe,SAAS,CAAA;AACvC,cAAA,MAAM,GAAA,GAAM,kBAAkB,SAAS,CAAA;AAKvC,cAAA,MAAM,CAAC,EAAE,KAAA,EAAO,IAAI,MAAM,EAAA,CACvB,MAAA,CAAO,EAAE,KAAA,EAAOC,GAAAA,CAAAA,QAAAA,CAAAA,EAAuB,CAAA,CACvC,IAAA,CAAK,QAAQ,CAAA,CACb,KAAA,CAAMD,GAAG,QAAA,CAAS,MAAA,EAAQ,OAAA,CAAQ,MAAM,CAAC,CAAA;AAC5C,cAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,KAAK,CAAA,IAAK,CAAA;AAExC,cAAA,MAAM,WAAA,CAAY;AAAA,gBAChB,QAAQ,OAAA,CAAQ,MAAA;AAAA,gBAChB,MAAA,EAAQ,OAAA;AAAA,gBACR,WAAA,EAAa,iBACT,sCAAA,GACA,gBAAA;AAAA,gBACJ,SAAA;AAAA,gBACA,SAAA;AAAA,gBACA,WAAA;AAAA,gBACA,IAAI,MAAA,CAAO,EAAA;AAAA,gBACX,SAAS,MAAA,CAAO,OAAA;AAAA,gBAChB,YAAY,MAAA,CAAO,UAAA;AAAA,gBACnB,SAAS,GAAA,CAAI,OAAA;AAAA,gBACb,MAAM,GAAA,CAAI;AAAA,eACX,CAAA;AAAA,YACH,SAAS,KAAA,EAAO;AACd,cAAA,OAAA,CAAQ,KAAA,CAAM,gCAAgC,KAAK,CAAA;AAAA,YACrD;AAAA,UACF;AAAA;AACF;AACF,KACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,KAAA,EAAO;AAAA,MACL,MAAA,EAAQ,oBAAA,CAAqB,OAAO,GAAA,KAAQ;AA1TlD,QAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAmUQ,QAAA,IAAI,GAAA,CAAI,IAAA,KAAS,gBAAA,IAAoB,GAAA,CAAI,SAAS,gBAAA,EAAkB;AAClE,UAAA,MAAM,WAAA,GAAA,CAAc,EAAA,GAAA,GAAA,CAAI,OAAA,KAAJ,IAAA,GAAA,MAAA,GAAA,EAAA,CAAa,GAAA,CAAI,yBAAA,CAAA;AACrC,UAAA,MAAM,EAAA,GAAK,WAAA,CAAY,GAAA,CAAI,OAAO,CAAA;AAClC,UAAA,MAAM,SAAA,GACH,WAAA,IAAgB,MAAM,iBAAA,CAAkB,WAAW,CAAA,IACnD,EAAA,IAAO,MAAM,iBAAA,CAAkB,CAAA,GAAA,EAAM,EAAE,CAAA,CAAA,EAAI,MAAM,CAAA;AACpD,UAAA,IAAI,SAAA,EAAW;AACb,YAAA,MAAM,IAAI,SAAS,mBAAA,EAAqB;AAAA,cACtC,OAAA,EAAS;AAAA,aACV,CAAA;AAAA,UACH;AAAA,QACF;AAOA,QAAA,IAAI,GAAA,CAAI,SAAS,iBAAA,EAAmB;AAGlC,UAAA,IAAA,CAAA,CAAI,EAAA,GAAA,GAAA,CAAI,IAAA,KAAJ,IAAA,GAAA,MAAA,GAAA,EAAA,CAAU,QAAA,MAAa,QAAA,EAAU;AACnC,YAAA,MAAM,KAAA,GAAQ,MAAM,qBAAA,EAAsB;AAC1C,YAAA,IAAI,CAAC,MAAM,MAAA,EAAQ;AACjB,cAAA,MAAM,IAAI,QAAA,CAAS,WAAA,EAAa,EAAE,OAAA,EAAS,yCAAyC,CAAA;AAAA,YACtF;AAAA,UACF;AACA,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,GAAA,CAAI,SAAS,gBAAA,EAAkB;AACjC,UAAA,MAAM,KAAA,GAAQ,MAAM,qBAAA,EAAsB;AAC1C,UAAA,IAAI,CAAC,MAAM,aAAA,EAAe;AACxB,YAAA,MAAM,IAAI,QAAA,CAAS,WAAA,EAAa,EAAE,OAAA,EAAS,iDAAiD,CAAA;AAAA,UAC9F;AACA,UAAA;AAAA,QACF;AAOA,QAAA,IAAI,GAAA,CAAI,SAAS,mBAAA,EAAqB;AACpC,UAAA,MAAM,KAAA,GAAQ,MAAM,qBAAA,EAAsB;AAC1C,UAAA,IAAI,CAAC,KAAA,CAAM,MAAA,IAAU,CAAC,MAAM,MAAA,EAAQ;AAClC,YAAA,MAAM,IAAI,QAAA,CAAS,WAAA,EAAa,EAAE,OAAA,EAAS,yCAAyC,CAAA;AAAA,UACtF;AACA,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,GAAA,CAAI,SAAS,cAAA,EAAgB;AAC/B,UAAA,MAAME,WAAU,MAAM,iBAAA,CAAkB,KAAK,EAAE,kBAAA,EAAoB,MAAM,CAAA;AACzE,UAAA,IAAI,CAACA,QAAAA,EAAS;AAEd,UAAA,IAAIA,QAAAA,CAAQ,QAAQ,cAAA,EAAgB;AAClC,YAAA,MAAM,IAAI,QAAA,CAAS,WAAA,EAAa,EAAE,OAAA,EAAS,kDAAkD,CAAA;AAAA,UAC/F;AAEA,UAAA,IAAKA,QAAAA,CAAQ,IAAA,CAA2B,IAAA,KAAS,OAAA,EAAS;AACxD,YAAA,MAAM,CAAC,EAAE,KAAA,EAAO,IAAI,MAAM,EAAA,CACvB,OAAO,EAAE,KAAA,EAAOD,eAAuB,CAAA,CACvC,KAAK,KAAK,CAAA,CACV,MAAMD,EAAAA,CAAG,KAAA,CAAM,IAAA,EAAM,OAAO,CAAC,CAAA;AAChC,YAAA,IAAI,MAAA,CAAO,KAAK,CAAA,IAAK,CAAA,EAAG;AACtB,cAAA,MAAM,IAAI,QAAA,CAAS,WAAA,EAAa,EAAE,OAAA,EAAS,+DAA+D,CAAA;AAAA,YAC5G;AAAA,UACF;AACA,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,GAAA,CAAI,SAAS,2BAAA,EAA6B;AAE9C,QAAA,MAAM,UAAU,MAAM,iBAAA,CAAkB,KAAK,EAAE,kBAAA,EAAoB,MAAM,CAAA;AACzE,QAAA,IAAI,OAAA,IAAA,IAAA,GAAA,MAAA,GAAA,OAAA,CAAS,QAAQ,cAAA,EAAgB;AACnC,UAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAClB,MAAA,CAAO,EAAE,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,CAAA,CAC7B,IAAA,CAAK,KAAK,CAAA,CACV,KAAA,CAAMA,EAAAA,CAAG,KAAA,CAAM,EAAA,EAAI,OAAA,CAAQ,QAAQ,MAAM,CAAC,CAAA,CAC1C,KAAA,CAAM,CAAC,CAAA;AAEV,UAAA,MAAM,WAAA,CAAY;AAAA,YAChB,MAAA,EAAQ,QAAQ,OAAA,CAAQ,cAAA;AAAA,YACxB,MAAA,EAAQ,uBAAA;AAAA,YACR,WAAA,EAAa,CAAA,4BAAA,EAAA,CAA+B,EAAA,GAAA,CAAA,EAAA,GAAA,MAAA,CAAO,CAAC,CAAA,KAAR,mBAAW,KAAA,KAAX,IAAA,GAAA,EAAA,GAAoB,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA,CAAA;AAAA,YACtF,QAAA,EAAU,EAAE,kBAAA,EAAoB,OAAA,CAAQ,QAAQ,MAAA;AAAO,WACxD,CAAA;AAAA,QACH;AAAA,MACF,CAAC,CAAA;AAAA,MACD,KAAA,EAAO,oBAAA,CAAqB,OAAO,GAAA,KAAQ;AA5ZjD,QAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAiaQ,QAAA,IAAI,GAAA,CAAI,IAAA,KAAS,8BAAA,IAAkC,GAAA,CAAI,SAAS,yBAAA,EAA2B;AACzF,UAAA,IAAI,GAAA,CAAI,OAAA,CAAQ,QAAA,YAAoB,QAAA,EAAU;AAC9C,UAAA,MAAM,mBAAA;AAAA,YACJ,GAAA;AAAA,YACA,GAAA,CAAI,IAAA,KAAS,8BAAA,GAAiC,OAAA,GAAU;AAAA,WAC1D;AACA,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,GAAA,CAAI,IAAA,KAAS,gBAAA,IAAoB,GAAA,CAAI,SAAS,gBAAA,EAAkB;AAEpE,QAAA,MAAM,WAAA,GAAA,CAAc,EAAA,GAAA,GAAA,CAAI,OAAA,KAAJ,IAAA,GAAA,MAAA,GAAA,EAAA,CAAa,GAAA,CAAI,yBAAA,CAAA;AACrC,QAAA,MAAM,EAAA,GAAK,WAAA,CAAY,GAAA,CAAI,OAAO,CAAA;AAClC,QAAA,MAAM,KAAA,GAAQ,EAAA,GAAK,CAAA,GAAA,EAAM,EAAE,CAAA,CAAA,GAAK,IAAA;AAChC,QAAA,IAAI,CAAC,WAAA,IAAe,CAAC,KAAA,EAAO;AAE5B,QAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAA,CAAQ,QAAA,YAAoB,QAAA;AAC/C,QAAA,MAAM,QAAA,GAAA,CAAY,EAAA,GAAA,GAAA,CAAI,IAAA,KAAJ,IAAA,GAAA,MAAA,GAAA,EAAA,CAA8C,KAAA;AAChE,QAAA,MAAM,IAAA,GAAO;AAAA,UACX,SAAA,EAAW,EAAA;AAAA,UACX,YAAW,EAAA,GAAA,CAAA,EAAA,GAAA,GAAA,CAAI,OAAA,KAAJ,IAAA,GAAA,MAAA,GAAA,EAAA,CAAa,GAAA,CAAI,kBAAjB,IAAA,GAAA,EAAA,GAAkC,IAAA;AAAA,UAC7C,OAAO,OAAO,QAAA,KAAa,QAAA,GAAW,QAAA,CAAS,aAAY,GAAI;AAAA,SACjE;AACA,QAAA,MAAM,IAAA,GAAO,CAAC,WAAA,EAAa,KAAK,CAAA,CAAE,OAAO,CAAC,CAAA,KAAmB,CAAC,CAAC,CAAC,CAAA;AAIhE,QAAA,IAAI,GAAA,CAAI,SAAS,gBAAA,EAAkB;AACjC,UAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ,mBAAA,CAAoB,GAAA,EAAK,aAAA,CAAA,cAAA,CAAA,EAAA,EAAK,OAAL,EAAW,IAAA,EAAM,QAAA,EAAS,CAAC,CAAC,CAAC,CAAA;AAC1F,UAAA;AAAA,QACF;AAMA,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ,mBAAA,CAAoB,GAAA,EAAK,aAAA,CAAA,cAAA,CAAA,EAAA,EAAK,OAAL,EAAW,IAAA,EAAM,QAAA,EAAS,CAAC,CAAC,CAAC,CAAA;AAC1F,UAAA,MAAM,eAAe,GAAG,CAAA;AAAA,QAC1B,CAAA,MAAO;AACL,UAAA,MAAM,OAAA,CAAQ,IAAI,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ,mBAAA,CAAoB,GAAG,CAAC,CAAC,CAAA;AAAA,QAC/D;AAAA,MACF,CAAC;AAAA,KACH;AAAA;AAAA,IAGA,OAAA;AAAA;AAAA,IAGA,MAAA,EAAQ,QAAQ,GAAA,CAAI,WAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWpB,UAAA,EAAY;AAAA,MACV,QAAA,EAAU;AAAA,KACZ;AAAA,IAEA,OAAA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMPG,KAAA,CAAY;AAAA,QACV,UAAA,EAAY,CAAC,OAAO,CAAA;AAAA,QACpB,MAAA,EAAQ;AAAA,UACN,IAAA,EAAM;AAAA,YACJ,MAAA,EAAQ;AAAA,cACN,SAAA,EAAW;AAAA;AACb;AACF;AACF,OACD,CAAA;AAAA;AAAA;AAAA,MAGD,SAAA,CAAU,EAAE,MAAA,EAAQ,MAAA,CAAO,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA,MAIrC,OAAA,CAAQ;AAAA,QACN,IAAA,EAAM,IAAI,GAAA,CAAI,OAAO,CAAA,CAAE,QAAA;AAAA,QACvB,QAAQ,MAAA,CAAO,QAAA;AAAA,QACf,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA;AAAA;AAAA;AAAA,MAID,GAAI,MAAA,CAAO,mBAAA,IAAuB,OAAA,CAAQ,eAAA,CAAgB,MAAM,CAAA,GAAI,CAAC,MAAA,EAAQ,CAAA,GAAI;AAAC;AACpF,GACD,CAAA;AACH;AAUA,IAAI,KAAA;AACJ,SAAS,cAAA,GAAiB;AACxB,EAAA,IAAI,CAAC,KAAA,EAAO,KAAA,GAAQ,UAAA,EAAW;AAC/B,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,IAAA,GAAO,IAAI,KAAA,CAAM,EAAC,EAAoC;AAAA,EACjE,GAAA,CAAI,SAAS,IAAA,EAAM;AACjB,IAAA,MAAM,WAAW,cAAA,EAAe;AAChC,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,QAAA,EAAU,IAAI,CAAA;AACxC,IAAA,OAAO,OAAO,KAAA,KAAU,UAAA,GAAa,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAA,GAAI,KAAA;AAAA,EAC9D,CAAA;AAAA;AAAA;AAAA;AAAA,EAIA,GAAA,CAAI,SAAS,IAAA,EAAM;AACjB,IAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,cAAA,EAAe,EAAG,IAAI,CAAA;AAAA,EAC3C,CAAA;AAAA,EACA,QAAQ,OAAA,EAAS;AACf,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,cAAA,EAAgB,CAAA;AAAA,EACzC,CAAA;AAAA,EACA,wBAAA,CAAyB,SAAS,IAAA,EAAM;AACtC,IAAA,OAAO,OAAA,CAAQ,wBAAA,CAAyB,cAAA,EAAe,EAAG,IAAI,CAAA;AAAA,EAChE;AACF,CAAC","file":"chunk-7QVIOQ47.mjs","sourcesContent":["// Shared cookie/header names used by both client components and server/auth.ts\n// — no \"server-only\" guard, since both sides need them.\n\n// Stashes the page a social sign-in was initiated from, so the login-activity\n// log shows the real referrer instead of the OAuth provider's own domain\n// (e.g. accounts.google.com) once the browser redirects back.\nexport const LOGIN_REFERRER_COOKIE = 'login_referrer'\n\n// Carries the client's FingerprintJS visitorId on /sign-in/email and\n// /sign-up/email requests — the key for the per-device abuse throttle\n// (see lib/auth-throttle.ts). A header, not a cookie, so it's only sent on\n// the auth calls that need it, not every request.\nexport const DEVICE_FINGERPRINT_HEADER = 'x-device-fingerprint'\n\n// The visitor's cookie choice (lib/cookie-consent.ts): \"all\", \"necessary\", or\n// a comma list of the purposes they ticked under \"Customize\", e.g.\n// \"fingerprint\" — one choice per purpose, as regulators like the CNIL expect.\nexport const COOKIE_CONSENT_COOKIE = 'cookie_consent'\n\n// \"fingerprint\": FingerprintJS device id (new-device alerts, sign-in throttle,\n// shared-device detection, admin Devices card). \"onetap\": Google One Tap,\n// whose script may set Google's own cookies.\nexport type ConsentPurpose = 'fingerprint' | 'onetap'\nexport const CONSENT_PURPOSES: ConsentPurpose[] = ['fingerprint', 'onetap']\n\n// Version of the consent wording (banner text + privacy page purposes),\n// stored with each consent record (consent_log.version) so you can show which\n// text a user agreed to. Bump it when that wording changes materially.\nexport const CONSENT_VERSION = 1\n\n// A well-formed cookie_consent value: \"all\", \"necessary\", or a comma list of\n// known purposes. Used to validate what the client sends to app/api/consent.\nexport function isValidConsent(value: unknown): value is string {\n if (value === 'all' || value === 'necessary') return true\n if (typeof value !== 'string' || !value) return false\n const parts = value.split(',')\n return new Set(parts).size === parts.length && parts.every((p) => (CONSENT_PURPOSES as string[]).includes(p))\n}\n\n// Does a cookie_consent value allow this purpose? Plain function, no React,\n// so route handlers can use it too (app/api/device-check).\nexport function consentAllows(value: string | null | undefined, purpose: ConsentPurpose): boolean {\n if (value === 'all') return true\n if (!value || value === 'necessary') return false\n return value.split(',').includes(purpose)\n}\n\n// Consent mode, set by proxy.ts from the admin's System Settings toggle\n// (\"1\" = consent required, \"0\" = not). Read by lib/cookie-consent.ts.\nexport const CONSENT_REQUIRED_COOKIE = 'consent_required'\n\n// FingerprintJS signals that are never sent or stored. Accessibility\n// preferences can hint at a disability (GDPR Art. 9 health data); colorGamut\n// mostly tracks display hardware but is read alongside them. Filtered on the\n// client (lib/device-fingerprint.ts) and again on the server\n// (app/api/device-check), since the client can't be trusted.\nexport const EXCLUDED_SIGNALS = new Set([\n 'reducedMotion', 'reducedTransparency', 'contrast', 'forcedColors',\n 'invertedColors', 'monochrome', 'colorGamut',\n])\n","import \"server-only\"\nimport { eq, lt, sql } from \"drizzle-orm\"\nimport { db } from \"../server/db\"\nimport { authThrottle } from \"../server/db/schema\"\nimport { RETENTION_MS, WINDOW_MS, isOverLimit } from \"./auth-throttle-limits\"\n\n// Is this key (device fingerprint, or \"ip:<address>\" backstop) currently over\n// the cap? Read-only — the caller (server/auth.ts before-hook) decides\n// whether to 429.\nexport async function isDeviceThrottled(fingerprint: string, max?: number): Promise<boolean> {\n try {\n const [row] = await db\n .select({ count: authThrottle.count, windowStart: authThrottle.windowStart })\n .from(authThrottle)\n .where(eq(authThrottle.fingerprint, fingerprint))\n .limit(1)\n return isOverLimit(row, Date.now(), max)\n } catch (error) {\n // Fail open — a throttle-store hiccup must never lock everyone out of sign-in.\n console.error(\"auth-throttle read failed, allowing request\", error)\n return false\n }\n}\n\nexport type AttemptMeta = {\n ipAddress: string | null\n userAgent: string | null\n email: string | null\n kind: \"signin\" | \"signup\"\n}\n\n// Atomic upsert: new row at 1, +1 within the live window, or reset to 1 when\n// the previous window has elapsed. One statement, so a device hammering the\n// endpoint (the whole point) can't race its own counter. The meta fields are\n// overwritten every time — they're \"last seen from this device\", for the\n// admin panel's block-list view (lib/admin-queries.ts).\nexport async function recordDeviceAttempt(fingerprint: string, meta: AttemptMeta): Promise<void> {\n const now = new Date()\n const cutoff = new Date(now.getTime() - WINDOW_MS)\n const seen = {\n lastAttemptAt: now,\n ipAddress: meta.ipAddress,\n userAgent: meta.userAgent,\n lastEmail: meta.email,\n lastKind: meta.kind,\n }\n try {\n await db\n .insert(authThrottle)\n .values({ fingerprint, count: 1, windowStart: now, ...seen })\n .onConflictDoUpdate({\n target: authThrottle.fingerprint,\n set: {\n count: sql`case when ${authThrottle.windowStart} < ${cutoff} then 1 else ${authThrottle.count} + 1 end`,\n windowStart: sql`case when ${authThrottle.windowStart} < ${cutoff} then ${now} else ${authThrottle.windowStart} end`,\n ...seen,\n },\n })\n } catch (error) {\n console.error(\"auth-throttle write failed\", error)\n }\n await pruneStaleRows(now)\n}\n\n// Retention sweep piggybacked on the write path, so no cron is needed.\n// ponytail: runs at most hourly per server instance, and only when someone\n// attempts a sign-in — a site with zero attempts keeps stale rows until the\n// next one. Call pruneStaleRows() from a cron if you need a hard guarantee.\nconst PRUNE_EVERY_MS = 60 * 60 * 1000\nlet lastPrune = 0\n\nexport async function pruneStaleRows(now = new Date()): Promise<void> {\n if (now.getTime() - lastPrune < PRUNE_EVERY_MS) return\n lastPrune = now.getTime()\n try {\n await db\n .delete(authThrottle)\n .where(lt(authThrottle.lastAttemptAt, new Date(now.getTime() - RETENTION_MS)))\n } catch (error) {\n console.error(\"auth-throttle prune failed\", error)\n }\n}\n\nexport async function clearDeviceThrottle(fingerprint: string): Promise<void> {\n try {\n await db.delete(authThrottle).where(eq(authThrottle.fingerprint, fingerprint))\n } catch (error) {\n console.error(\"auth-throttle clear failed\", error)\n }\n}\n","import \"server-only\"\nimport { betterAuth, type BetterAuthOptions } from \"better-auth\"\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\"\nimport { createId } from \"@paralleldrive/cuid2\"\nimport { eq, sql } from \"drizzle-orm\"\nimport { db } from \"./db\"\nimport { users, accounts, sessions, verifications, twoFactors, passkeys } from \"./db/schema\"\nimport { config } from \"@/config/env\"\nimport { oneTap, admin as adminPlugin, twoFactor } from \"better-auth/plugins\";\nimport { passkey } from \"@better-auth/passkey\";\nimport { createAuthMiddleware, getSessionFromCtx, APIError } from \"better-auth/api\"\nimport { logActivity } from \"@/lib/activity-logger\"\nimport { getClientIp, parseUserAgent, lookupGeoLocation, formatDeviceInfo, formatLocation } from \"@/lib/request-info\"\nimport { LOGIN_REFERRER_COOKIE, DEVICE_FINGERPRINT_HEADER } from \"@/lib/cookie-names\"\nimport { isDeviceThrottled, recordDeviceAttempt, clearDeviceThrottle } from \"@/lib/auth-throttle\"\nimport { MAX_IP } from \"@/lib/auth-throttle-limits\"\nimport { getEffectiveAuthFlags } from \"@/lib/settings-queries\"\nimport { getResend, EMAIL_FROM } from \"@/lib/resend\"\n// Email templates are imported lazily where they're sent, so\n// @react-email/components only loads when an email goes out.\n\ntype AuthOverrides = {\n // Merged with (not replacing) the default `google` provider below, so\n // consuming projects can add e.g. `apple` without forking this file.\n socialProviders?: BetterAuthOptions[\"socialProviders\"]\n}\n\n// A failed credential sign-in, written to the activity feed — but only when\n// the attempted email maps to a real account (activityLogs.userId is NOT\n// NULL, and a miss has no user to attribute the row to). Best-effort: runs\n// in an after-hook and must never disturb the sign-in response.\nasync function logFailedLogin(ctx: { body?: unknown; headers?: Headers | null }) {\n try {\n const raw = (ctx.body as { email?: unknown } | undefined)?.email\n const email = typeof raw === \"string\" ? raw.toLowerCase() : null\n if (!email) return\n\n const [user] = await db\n .select({ id: users.id })\n .from(users)\n .where(eq(users.email, email))\n .limit(1)\n if (!user) return\n\n await logActivity({\n userId: user.id,\n action: \"login_failed\",\n description: \"Failed login attempt\",\n ipAddress: getClientIp(ctx.headers),\n userAgent: ctx.headers?.get(\"user-agent\") ?? null,\n })\n } catch (error) {\n console.error(\"Failed to log login_failed activity\", error)\n }\n}\n\n// Email the account owner that a passkey was added or removed. Runs from the\n// auth `after` hook on a successful /passkey/verify-registration or\n// /passkey/delete-passkey; best-effort — a mail failure must not fail the\n// endpoint. Also written to the activity feed. Self-disables when Resend\n// isn't configured.\nasync function notifyPasskeyChange(\n ctx: Parameters<Parameters<typeof createAuthMiddleware>[0]>[0],\n action: \"added\" | \"removed\",\n) {\n try {\n const session = await getSessionFromCtx(ctx)\n const user = session?.user\n if (!user) return\n\n const ip = getClientIp(ctx.headers)\n\n await logActivity({\n userId: user.id,\n action: action === \"added\" ? \"passkey_added\" : \"passkey_removed\",\n description: `Passkey ${action}`,\n ipAddress: ip,\n userAgent: ctx.headers?.get(\"user-agent\") ?? null,\n })\n\n if (!config.RESEND_API_KEY) return\n\n const { EmailTemplatePasskeyChange } = await import(\"@/components/email/email-template-passkey-change\")\n await (await getResend()).emails.send({\n from: EMAIL_FROM,\n to: [user.email],\n subject: action === \"added\" ? \"A passkey was added to your account\" : \"A passkey was removed from your account\",\n react: EmailTemplatePasskeyChange({\n firstName: user.name || \"there\",\n action,\n device: formatDeviceInfo(parseUserAgent(ctx.headers?.get(\"user-agent\") ?? null)),\n location: formatLocation(lookupGeoLocation(ip)),\n ipAddress: ip || \"Unknown\",\n when: new Date().toUTCString(),\n manageUrl: `${process.env.BETTER_AUTH_URL || process.env.NEXTAUTH_URL || process.env.NEXT_PUBLIC_APP_URL || \"\"}/dashboard/settings`,\n }),\n })\n } catch (error) {\n console.error(\"Failed to send passkey-change notification\", error)\n }\n}\n\nexport function createAuth(overrides: AuthOverrides = {}) {\n const socialProviders: NonNullable<BetterAuthOptions[\"socialProviders\"]> = {\n ...(config.AUTH_ENABLE_GOOGLE ? {\n google: {\n clientId: process.env.AUTH_GOOGLE_ID || process.env.GOOGLE_CLIENT_ID || \"\",\n clientSecret: process.env.AUTH_GOOGLE_SECRET || process.env.GOOGLE_CLIENT_SECRET || \"\",\n },\n } : {}),\n ...overrides.socialProviders,\n }\n const emailAndPasswordEnabled = config.AUTH_ENABLE_EMAIL_PASSWORD\n\n // Guard on the *final* merged config, after overrides.socialProviders is applied —\n // this only fails when there's truly no way to sign in. A project that disables both\n // built-ins via AUTH_ENABLE_GOOGLE=false / AUTH_ENABLE_EMAIL_PASSWORD=false but adds\n // its own provider (e.g. apple) here is a valid config and won't trip this.\n const hasEnabledSocialProvider = Object.values(socialProviders).some(Boolean)\n\n if (!hasEnabledSocialProvider && !emailAndPasswordEnabled) {\n throw new Error(\n \"No auth provider is enabled — every social provider is off and emailAndPassword.enabled is false. \" +\n \"Enable AUTH_ENABLE_GOOGLE / AUTH_ENABLE_EMAIL_PASSWORD, or pass your own provider via createAuth({ socialProviders: {...} }).\"\n )\n }\n\n // Shared by the `baseURL` field below and the passkey plugin's rpID/origin\n // — WebAuthn credentials are bound to this exact origin, so it must never\n // fall back to better-auth's own \"localhost\" passkey default in production.\n const baseURL = process.env.NEXTAUTH_URL || process.env.BETTER_AUTH_URL || \"http://localhost:3000\"\n\n return betterAuth({\n database: drizzleAdapter(db, {\n provider: \"pg\",\n schema: {\n user: users,\n account: accounts,\n session: sessions,\n verification: verifications,\n twoFactor: twoFactors,\n passkey: passkeys,\n },\n }),\n\n // Session configuration (30 days expiration matching previous NextAuth setup)\n session: {\n expiresIn: 60 * 60 * 24 * 30, // 30 days\n updateAge: 60 * 60 * 24, // Update session every 24 hours\n cookieCache: {\n enabled: true, // Enable hybrid session caching for performance\n maxAge: 5 * 60, // 5 minutes\n }\n },\n\n // Email & Password Authentication (replaces Credentials provider)\n // Toggle per-project via AUTH_ENABLE_EMAIL_PASSWORD in that project's own .env.*\n emailAndPassword: {\n enabled: emailAndPasswordEnabled,\n requireEmailVerification: false,\n // `url` is fully composed by better-auth (baseURL + verification token +\n // callbackURL) — see app/(auth)/forgot-password and reset-password for\n // the pages this points at.\n async sendResetPassword({ user, url }) {\n const { EmailTemplateResetPassword } = await import(\"@/components/email/email-template-reset-password\")\n await (await getResend()).emails.send({\n from: EMAIL_FROM,\n to: [user.email],\n subject: \"Reset your password\",\n react: EmailTemplateResetPassword({ firstName: user.name || \"there\", url }),\n })\n },\n },\n\n // Email verification. `requireEmailVerification` above stays false, so this\n // is opt-in: users trigger it from the dashboard via\n // authClient.sendVerificationEmail(). better-auth composes `url`\n // (baseURL + token + callbackURL) and serves the /api/auth/verify-email\n // callback that flips users.emailVerified on click.\n emailVerification: {\n autoSignInAfterVerification: true,\n async sendVerificationEmail({ user, url }) {\n const { EmailTemplateVerifyEmail } = await import(\"@/components/email/email-template-verify-email\")\n await (await getResend()).emails.send({\n from: EMAIL_FROM,\n to: [user.email],\n subject: \"Confirm your email\",\n react: EmailTemplateVerifyEmail({ firstName: user.name || \"there\", url }),\n })\n },\n },\n\n // Social Providers — google is included by default; toggle per-project via\n // AUTH_ENABLE_GOOGLE. overrides.socialProviders can still add more (or replace it).\n socialProviders,\n\n // User schema configuration\n user: {\n additionalFields: {\n role: {\n type: \"string\",\n required: false,\n defaultValue: \"user\",\n input: false, // Prevent users from setting their own role\n },\n },\n // Self-service \"Delete account\" (dashboard Settings → Danger zone).\n // Credential users confirm with their password; OAuth-only users have\n // no password, so better-auth instead requires a session younger than\n // `freshAge` (default 1 day). Every user-owned table cascades on delete.\n // Impersonation and last-admin guards live in hooks.before below.\n deleteUser: { enabled: true },\n },\n\n // Advanced options\n advanced: {\n database: {\n generateId: () => createId(),\n },\n },\n\n // Every new session row corresponds to a sign-in (credential or OAuth) —\n // use that as the \"last logged in\" signal rather than session updates,\n // which also fire on cookie-cache refresh / expiry extension.\n databaseHooks: {\n session: {\n create: {\n async after(session, context) {\n // Impersonation sessions (created by the admin plugin's\n // impersonateUser endpoint) also go through session.create —\n // don't let \"an admin looked at this account\" show up as the\n // user's own last login, and log it as an audit event instead.\n const impersonatedBy = (session as { impersonatedBy?: string | null }).impersonatedBy\n if (impersonatedBy) {\n const target = await db\n .select({ email: users.email })\n .from(users)\n .where(eq(users.id, session.userId))\n .limit(1)\n\n await logActivity({\n userId: impersonatedBy,\n action: 'impersonation_started',\n description: `Admin started impersonating ${target[0]?.email ?? session.userId}`,\n metadata: { impersonatedUserId: session.userId },\n })\n return\n }\n\n try {\n await db\n .update(users)\n .set({ lastLoginAt: new Date() })\n .where(eq(users.id, session.userId))\n } catch (error) {\n console.error(\"Failed to update lastLoginAt\", error)\n }\n\n try {\n const headers = context?.headers\n const ipAddress = getClientIp(headers)\n const userAgent = headers?.get('user-agent') ?? null\n // For OAuth, the live referer header on this request is the\n // provider's own callback page (e.g. accounts.google.com), not\n // the page the user actually came from — socialLogin.tsx stashes\n // the real page in a cookie right before the redirect, which\n // survives the round trip since it's set on our own origin.\n // Falls back to the live header for email/password sign-in,\n // where this request *is* the originating request.\n const referrerUrl = context?.getCookie?.(LOGIN_REFERRER_COOKIE)\n || headers?.get('referer')\n || null\n const device = parseUserAgent(userAgent)\n const geo = lookupGeoLocation(ipAddress)\n\n // Every session row for this user, including the one just\n // created — count === 1 means signup just auto-signed them in\n // (better-auth's default), anything higher is a returning login.\n const [{ count }] = await db\n .select({ count: sql<number>`count(*)` })\n .from(sessions)\n .where(eq(sessions.userId, session.userId))\n const isFirstSession = Number(count) <= 1\n\n await logActivity({\n userId: session.userId,\n action: 'login',\n description: isFirstSession\n ? 'New account registered and signed in'\n : 'User logged in',\n ipAddress,\n userAgent,\n referrerUrl,\n os: device.os,\n browser: device.browser,\n deviceType: device.deviceType,\n country: geo.country,\n city: geo.city,\n })\n } catch (error) {\n console.error(\"Failed to log login activity\", error)\n }\n },\n },\n },\n },\n // better-auth's admin plugin ends impersonation by deleting the\n // impersonation session and restoring the admin's original session\n // straight from a signed cookie — it never runs session.create, so\n // there's no databaseHooks entry point to log the stop event from.\n // Reading the session here (before the endpoint deletes it) is the only\n // point where both the admin id and the impersonated user id are still\n // available together.\n hooks: {\n before: createAuthMiddleware(async (ctx) => {\n // Per-device abuse throttle for the two credential entry points.\n // Primarily keyed by the client's FingerprintJS visitorId\n // (x-device-fingerprint header, set in emailPasswordLogin/Signup), but\n // that header is client-supplied and unsigned — omitting it or\n // randomizing it per request bypasses a fingerprint-only check\n // entirely. IP is a lower-ceiling-tolerant backstop (shared behind\n // NAT/CGNAT/VPN, so it uses MAX_IP, not MAX) that still applies when\n // the header is missing or spoofed.\n if (ctx.path === \"/sign-in/email\" || ctx.path === \"/sign-up/email\") {\n const fingerprint = ctx.headers?.get(DEVICE_FINGERPRINT_HEADER)\n const ip = getClientIp(ctx.headers)\n const throttled =\n (fingerprint && (await isDeviceThrottled(fingerprint))) ||\n (ip && (await isDeviceThrottled(`ip:${ip}`, MAX_IP)))\n if (throttled) {\n throw new APIError(\"TOO_MANY_REQUESTS\", {\n message: \"Too many attempts from this device. Try again in a few minutes.\",\n })\n }\n }\n\n // Live kill-switch for the two built-in sign-in methods, on top of\n // the (untouched) provider registration above — lets an admin\n // disable a method from the System Settings page and have it take\n // effect immediately, even for a client that already has the button\n // rendered, instead of only on next deploy.\n if (ctx.path === \"/sign-in/social\") {\n // Scoped to \"google\" specifically since overrides.socialProviders\n // can register other providers (e.g. apple) this flag shouldn't gate.\n if (ctx.body?.provider === \"google\") {\n const flags = await getEffectiveAuthFlags()\n if (!flags.google) {\n throw new APIError(\"FORBIDDEN\", { message: \"Google sign-in is currently disabled.\" })\n }\n }\n return\n }\n\n if (ctx.path === \"/sign-in/email\") {\n const flags = await getEffectiveAuthFlags()\n if (!flags.emailPassword) {\n throw new APIError(\"FORBIDDEN\", { message: \"Email/password sign-in is currently disabled.\" })\n }\n return\n }\n\n // One Tap isn't a separate identity, it's a UI shortcut into the\n // same Google account — its plugin hits its own endpoint entirely\n // (not /sign-in/social), so it needs its own guard, and it needs\n // both flags: Google itself must still be allowed, and the One Tap\n // prompt specifically must be enabled.\n if (ctx.path === \"/one-tap/callback\") {\n const flags = await getEffectiveAuthFlags()\n if (!flags.google || !flags.oneTap) {\n throw new APIError(\"FORBIDDEN\", { message: \"Google One Tap is currently disabled.\" })\n }\n return\n }\n\n if (ctx.path === \"/delete-user\") {\n const current = await getSessionFromCtx(ctx, { disableCookieCache: true })\n if (!current) return // better-auth's own session check rejects it\n // An admin \"viewing as\" a user must not be able to destroy their account.\n if (current.session.impersonatedBy) {\n throw new APIError(\"FORBIDDEN\", { message: \"Stop impersonating before deleting an account.\" })\n }\n // Deleting the last admin would leave nobody able to reach the admin dashboard.\n if ((current.user as { role?: string }).role === \"admin\") {\n const [{ count }] = await db\n .select({ count: sql<number>`count(*)` })\n .from(users)\n .where(eq(users.role, \"admin\"))\n if (Number(count) <= 1) {\n throw new APIError(\"FORBIDDEN\", { message: \"You're the only admin. Promote another user to admin first.\" })\n }\n }\n return\n }\n\n if (ctx.path !== \"/admin/stop-impersonating\") return\n\n const current = await getSessionFromCtx(ctx, { disableCookieCache: true })\n if (current?.session.impersonatedBy) {\n const target = await db\n .select({ email: users.email })\n .from(users)\n .where(eq(users.id, current.session.userId))\n .limit(1)\n\n await logActivity({\n userId: current.session.impersonatedBy,\n action: 'impersonation_stopped',\n description: `Admin stopped impersonating ${target[0]?.email ?? current.session.userId}`,\n metadata: { impersonatedUserId: current.session.userId },\n })\n }\n }),\n after: createAuthMiddleware(async (ctx) => {\n // Passkey add/remove is a security-sensitive change to how the account\n // can be signed into — email the user whenever one lands, the same way\n // new-device sign-ins are surfaced. Best-effort: never disturb the\n // endpoint's own response.\n if (ctx.path === \"/passkey/verify-registration\" || ctx.path === \"/passkey/delete-passkey\") {\n if (ctx.context.returned instanceof APIError) return\n await notifyPasskeyChange(\n ctx,\n ctx.path === \"/passkey/verify-registration\" ? \"added\" : \"removed\",\n )\n return\n }\n\n if (ctx.path !== \"/sign-in/email\" && ctx.path !== \"/sign-up/email\") return\n\n const fingerprint = ctx.headers?.get(DEVICE_FINGERPRINT_HEADER)\n const ip = getClientIp(ctx.headers)\n const ipKey = ip ? `ip:${ip}` : null\n if (!fingerprint && !ipKey) return\n\n const failed = ctx.context.returned instanceof APIError\n const rawEmail = (ctx.body as { email?: unknown } | undefined)?.email\n const meta = {\n ipAddress: ip,\n userAgent: ctx.headers?.get(\"user-agent\") ?? null,\n email: typeof rawEmail === \"string\" ? rawEmail.toLowerCase() : null,\n }\n const keys = [fingerprint, ipKey].filter((k): k is string => !!k)\n\n // Sign-up abuse is mass account creation — the *successful* ones are\n // the problem — so every sign-up from this device counts.\n if (ctx.path === \"/sign-up/email\") {\n await Promise.all(keys.map((key) => recordDeviceAttempt(key, { ...meta, kind: \"signup\" })))\n return\n }\n\n // /sign-in/email: count failures; a success clears the counter.\n // ponytail: a successful sign-in wipes the device's failure budget, so\n // a credential-stuffing hit resets itself — per-outcome counters if\n // that ceiling ever bites.\n if (failed) {\n await Promise.all(keys.map((key) => recordDeviceAttempt(key, { ...meta, kind: \"signin\" })))\n await logFailedLogin(ctx)\n } else {\n await Promise.all(keys.map((key) => clearDeviceThrottle(key)))\n }\n }),\n },\n\n // Base URL for callbacks\n baseURL,\n\n // Secret for signing cookies and tokens\n secret: process.env.AUTH_SECRET!,\n\n // The OAuth `state` is a one-time verification row: the callback consumes\n // and deletes it. When the same callback URL gets hit twice (browser\n // prefetch / bfcache / double nav — common on a repeat Google sign-in\n // where Google auto-redirects with no consent screen), the first hit\n // logs the user in and the second finds no `state` row and throws\n // `please_restart_the_process`. Default sends that to better-auth's bare\n // error page; point it at /login instead, which already bounces an\n // authenticated user straight to their callbackUrl — so the losing race\n // just lands them logged in. See better-auth#5658 / #6544.\n onAPIError: {\n errorURL: \"/login\",\n },\n\n plugins: [\n // Registered for impersonation (auth.api.impersonateUser / stopImpersonating)\n // only — role/ban/delete stay on our own custom routes above. adminRoles\n // matches our existing role column so the plugin's own permission checks\n // line up with hasRole(role, 'admin'). banReason is remapped onto our\n // existing bannedReason column instead of adding a duplicate field.\n adminPlugin({\n adminRoles: [\"admin\"],\n schema: {\n user: {\n fields: {\n banReason: \"bannedReason\",\n },\n },\n },\n }),\n // TOTP second factor + encrypted backup codes. `issuer` is the label\n // shown in the user's authenticator app.\n twoFactor({ issuer: config.APP_NAME }),\n // WebAuthn passkeys (Touch ID / Windows Hello / security keys) as a\n // passwordless sign-in method. rpID must be the bare host and origin\n // the full URL — both derived from baseURL above.\n passkey({\n rpID: new URL(baseURL).hostname,\n rpName: config.APP_NAME,\n origin: baseURL,\n }),\n // checks to see if oneTap is enabled and if google provider is available, then add the oneTap plugin.\n // Keep this spread last: a leading spread turns `plugins` from a tuple into a plain\n // array and better-auth 1.7 then drops plugin fields (e.g. session.impersonatedBy).\n ...(config.AUTH_ENABLE_ONE_TAP && Boolean(socialProviders.google) ? [oneTap()] : []),\n ]\n })\n}\n\n// Lazy: constructing the default instance validates config and throws if no\n// provider is enabled (see the guard above). Building it eagerly at module\n// load would mean a downstream project that only imports `createAuth` to\n// build its *own* customized instance (README §5) still pays that throw —\n// merely importing this module would run it, regardless of whether the\n// consuming project ever touches this default export. Deferring construction\n// to first property access means the throw only fires for code that actually\n// uses the package's own unconfigured `auth` singleton.\nlet _auth: ReturnType<typeof createAuth> | undefined\nfunction getDefaultAuth() {\n if (!_auth) _auth = createAuth()\n return _auth\n}\n\nexport const auth = new Proxy({} as ReturnType<typeof createAuth>, {\n get(_target, prop) {\n const instance = getDefaultAuth()\n const value = Reflect.get(instance, prop)\n return typeof value === \"function\" ? value.bind(instance) : value\n },\n // \"prop\" in auth (e.g. better-auth's toNextJsHandler does `\"handler\" in auth`)\n // must also see the real instance — without this it checks the empty\n // placeholder target and always reports false.\n has(_target, prop) {\n return Reflect.has(getDefaultAuth(), prop)\n },\n ownKeys(_target) {\n return Reflect.ownKeys(getDefaultAuth())\n },\n getOwnPropertyDescriptor(_target, prop) {\n return Reflect.getOwnPropertyDescriptor(getDefaultAuth(), prop)\n },\n})\n"]}
|