hazo_auth 6.1.1 → 7.0.2
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 +65 -167
- package/SETUP_CHECKLIST.md +28 -100
- package/cli-src/cli/generate.ts +1 -10
- package/cli-src/cli/validate.ts +0 -4
- package/cli-src/lib/auth/auth_types.ts +12 -21
- package/cli-src/lib/auth/hazo_get_tenant_auth.server.ts +24 -25
- package/cli-src/lib/auth/index.ts +2 -2
- package/cli-src/lib/auth/nextauth_config.ts +61 -1
- package/cli-src/lib/auth/with_auth.server.ts +15 -15
- package/cli-src/lib/config/default_config.ts +8 -0
- package/cli-src/lib/cookies_config.server.ts +1 -1
- package/cli-src/lib/login_config.server.ts +2 -18
- package/cli-src/lib/oauth_config.server.ts +32 -0
- package/cli-src/lib/register_config.server.ts +4 -0
- package/cli-src/lib/services/email_template_manifest.ts +0 -17
- package/cli-src/lib/services/index.ts +2 -8
- package/cli-src/lib/services/oauth_service.ts +143 -0
- package/cli-src/lib/services/otp_service.ts +7 -2
- package/cli-src/lib/services/session_token_service.ts +0 -2
- package/config/hazo_auth_config.example.ini +0 -38
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +1 -10
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +0 -4
- package/dist/client.d.ts +0 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +0 -1
- package/dist/components/layouts/login/index.d.ts +5 -7
- package/dist/components/layouts/login/index.d.ts.map +1 -1
- package/dist/components/layouts/login/index.js +5 -2
- package/dist/components/layouts/otp/index.d.ts +12 -1
- package/dist/components/layouts/otp/index.d.ts.map +1 -1
- package/dist/components/layouts/otp/index.js +4 -2
- package/dist/components/layouts/register/index.d.ts +4 -0
- package/dist/components/layouts/register/index.d.ts.map +1 -1
- package/dist/components/layouts/register/index.js +4 -1
- package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +21 -0
- package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -0
- package/dist/components/layouts/shared/components/facebook_sign_in_button.js +47 -0
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +3 -8
- package/dist/components/layouts/shared/index.d.ts +2 -0
- package/dist/components/layouts/shared/index.d.ts.map +1 -1
- package/dist/components/layouts/shared/index.js +1 -0
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +39 -2
- package/dist/consent/consent_state.d.ts +18 -0
- package/dist/consent/consent_state.d.ts.map +1 -0
- package/dist/consent/consent_state.js +29 -0
- package/dist/consent/cookie_consent_banner.d.ts +11 -0
- package/dist/consent/cookie_consent_banner.d.ts.map +1 -0
- package/dist/consent/cookie_consent_banner.js +40 -0
- package/dist/consent/gtm_mapping.d.ts +13 -0
- package/dist/consent/gtm_mapping.d.ts.map +1 -0
- package/dist/consent/gtm_mapping.js +30 -0
- package/dist/consent/index.d.ts +7 -0
- package/dist/consent/index.d.ts.map +1 -0
- package/dist/consent/index.js +7 -0
- package/dist/consent/manage_modal.d.ts +2 -0
- package/dist/consent/manage_modal.d.ts.map +1 -0
- package/dist/consent/manage_modal.js +33 -0
- package/dist/consent/read_consent.d.ts +15 -0
- package/dist/consent/read_consent.d.ts.map +1 -0
- package/dist/consent/read_consent.js +23 -0
- package/dist/consent/use_consent.d.ts +7 -0
- package/dist/consent/use_consent.d.ts.map +1 -0
- package/dist/consent/use_consent.js +55 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.d.ts +12 -13
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +0 -8
- package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts +7 -8
- package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_tenant_auth.server.js +22 -23
- package/dist/lib/auth/index.d.ts +2 -2
- package/dist/lib/auth/index.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.js +50 -1
- package/dist/lib/auth/with_auth.server.d.ts +13 -13
- package/dist/lib/auth/with_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/with_auth.server.js +2 -2
- package/dist/lib/config/default_config.d.ts +16 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +8 -0
- package/dist/lib/cookies_config.server.d.ts +1 -1
- package/dist/lib/cookies_config.server.js +1 -1
- package/dist/lib/login_config.server.d.ts +0 -6
- package/dist/lib/login_config.server.d.ts.map +1 -1
- package/dist/lib/login_config.server.js +2 -11
- package/dist/lib/oauth_config.server.d.ts +8 -0
- package/dist/lib/oauth_config.server.d.ts.map +1 -1
- package/dist/lib/oauth_config.server.js +10 -0
- package/dist/lib/register_config.server.d.ts +2 -0
- package/dist/lib/register_config.server.d.ts.map +1 -1
- package/dist/lib/register_config.server.js +2 -0
- package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
- package/dist/lib/services/email_template_manifest.js +0 -17
- package/dist/lib/services/index.d.ts +0 -2
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +0 -1
- package/dist/lib/services/oauth_service.d.ts +13 -0
- package/dist/lib/services/oauth_service.d.ts.map +1 -1
- package/dist/lib/services/oauth_service.js +122 -0
- package/dist/lib/services/otp_service.d.ts +1 -1
- package/dist/lib/services/otp_service.d.ts.map +1 -1
- package/dist/lib/services/otp_service.js +6 -1
- package/dist/lib/services/session_token_service.d.ts +0 -2
- package/dist/lib/services/session_token_service.d.ts.map +1 -1
- package/dist/lib/services/session_token_service.js +0 -2
- package/dist/server/routes/assets.d.ts +8 -0
- package/dist/server/routes/assets.d.ts.map +1 -0
- package/dist/server/routes/assets.js +38 -0
- package/dist/server/routes/consent_me.d.ts +4 -0
- package/dist/server/routes/consent_me.d.ts.map +1 -0
- package/dist/server/routes/consent_me.js +15 -0
- package/dist/server/routes/index.d.ts +6 -3
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +9 -4
- package/dist/server/routes/me.d.ts.map +1 -1
- package/dist/server/routes/me.js +1 -43
- package/dist/server/routes/oauth_facebook_callback.d.ts +8 -0
- package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -0
- package/dist/server/routes/oauth_facebook_callback.js +164 -0
- package/dist/server/routes/otp/verify.js +2 -2
- package/dist/server/routes/strings_defaults.d.ts +4 -0
- package/dist/server/routes/strings_defaults.d.ts.map +1 -0
- package/dist/server/routes/strings_defaults.js +7 -0
- package/dist/server/routes/user_management_users.d.ts +11 -0
- package/dist/server/routes/user_management_users.d.ts.map +1 -1
- package/dist/server/routes/user_management_users.js +50 -0
- package/dist/server-lib.d.ts +0 -3
- package/dist/server-lib.d.ts.map +1 -1
- package/dist/server-lib.js +0 -2
- package/dist/server_pages/forgot_password.d.ts +1 -1
- package/dist/server_pages/forgot_password.d.ts.map +1 -1
- package/dist/server_pages/forgot_password.js +9 -3
- package/dist/server_pages/forgot_password_client_wrapper.d.ts +3 -1
- package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
- package/dist/server_pages/index.d.ts +2 -0
- package/dist/server_pages/index.d.ts.map +1 -1
- package/dist/server_pages/index.js +1 -0
- package/dist/server_pages/login.d.ts +1 -1
- package/dist/server_pages/login.d.ts.map +1 -1
- package/dist/server_pages/login.js +12 -3
- package/dist/server_pages/login_client_wrapper.d.ts +4 -1
- package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/login_client_wrapper.js +2 -2
- package/dist/server_pages/my_settings.d.ts +1 -1
- package/dist/server_pages/my_settings.d.ts.map +1 -1
- package/dist/server_pages/my_settings.js +1 -2
- package/dist/server_pages/otp.d.ts +16 -2
- package/dist/server_pages/otp.d.ts.map +1 -1
- package/dist/server_pages/otp.js +10 -3
- package/dist/server_pages/register.d.ts +1 -1
- package/dist/server_pages/register.d.ts.map +1 -1
- package/dist/server_pages/register.js +11 -3
- package/dist/server_pages/register_client_wrapper.d.ts +3 -1
- package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/register_client_wrapper.js +2 -2
- package/dist/server_pages/reset_password.d.ts +1 -1
- package/dist/server_pages/reset_password.d.ts.map +1 -1
- package/dist/server_pages/reset_password.js +9 -3
- package/dist/server_pages/reset_password_client_wrapper.d.ts +3 -1
- package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/reset_password_client_wrapper.js +2 -2
- package/dist/server_pages/verify_email.d.ts +1 -1
- package/dist/server_pages/verify_email.d.ts.map +1 -1
- package/dist/server_pages/verify_email.js +8 -3
- package/dist/server_pages/verify_email_client_wrapper.d.ts +3 -1
- package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/verify_email_client_wrapper.js +2 -2
- package/dist/strings/default_strings.d.ts +47 -0
- package/dist/strings/default_strings.d.ts.map +1 -0
- package/dist/strings/default_strings.js +18 -0
- package/dist/strings/index.d.ts +4 -0
- package/dist/strings/index.d.ts.map +1 -0
- package/dist/strings/index.js +3 -0
- package/dist/strings/strings_context.d.ts +12 -0
- package/dist/strings/strings_context.d.ts.map +1 -0
- package/dist/strings/strings_context.js +23 -0
- package/dist/strings/strings_provider.d.ts +26 -0
- package/dist/strings/strings_provider.d.ts.map +1 -0
- package/dist/strings/strings_provider.js +45 -0
- package/dist/theme/create_theme.d.ts +7 -0
- package/dist/theme/create_theme.d.ts.map +1 -0
- package/dist/theme/create_theme.js +97 -0
- package/dist/theme/hex_to_hsl.d.ts +16 -0
- package/dist/theme/hex_to_hsl.d.ts.map +1 -0
- package/dist/theme/hex_to_hsl.js +110 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +3 -0
- package/dist/theme/luminance.d.ts +11 -0
- package/dist/theme/luminance.d.ts.map +1 -0
- package/dist/theme/luminance.js +45 -0
- package/dist/theme/theme_provider.d.ts +14 -0
- package/dist/theme/theme_provider.d.ts.map +1 -0
- package/dist/theme/theme_provider.js +23 -0
- package/dist/theme/theme_types.d.ts +36 -0
- package/dist/theme/theme_types.d.ts.map +1 -0
- package/dist/theme/theme_types.js +1 -0
- package/dist/themes/index.d.ts +2 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +2 -0
- package/dist/themes/preset_neutral.d.ts +3 -0
- package/dist/themes/preset_neutral.d.ts.map +1 -0
- package/dist/themes/preset_neutral.js +14 -0
- package/package.json +25 -22
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
TenantAuthOptions,
|
|
15
15
|
TenantAuthResult,
|
|
16
16
|
RequiredTenantAuthResult,
|
|
17
|
-
|
|
17
|
+
TenantOrganization,
|
|
18
18
|
ScopeDetails,
|
|
19
19
|
} from "./auth_types";
|
|
20
20
|
import {
|
|
@@ -62,15 +62,15 @@ export function extract_scope_id_from_request(
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
* Builds
|
|
65
|
+
* Builds TenantOrganization from scope details and access info
|
|
66
66
|
* @param scope_details - Full scope details from cache
|
|
67
67
|
* @param is_super_admin - Whether user is accessing as super admin
|
|
68
|
-
* @returns
|
|
68
|
+
* @returns TenantOrganization object
|
|
69
69
|
*/
|
|
70
|
-
function
|
|
70
|
+
function build_tenant_organization(
|
|
71
71
|
scope_details: ScopeDetails,
|
|
72
72
|
is_super_admin: boolean,
|
|
73
|
-
):
|
|
73
|
+
): TenantOrganization {
|
|
74
74
|
return {
|
|
75
75
|
id: scope_details.id,
|
|
76
76
|
name: scope_details.name,
|
|
@@ -96,21 +96,20 @@ function build_selected_scope(
|
|
|
96
96
|
* Tenant-aware authentication function
|
|
97
97
|
*
|
|
98
98
|
* Extracts tenant/scope context from request headers or cookies,
|
|
99
|
-
* validates access, and returns enriched result
|
|
100
|
-
* selected scope.
|
|
99
|
+
* validates access, and returns enriched result with organization info.
|
|
101
100
|
*
|
|
102
101
|
* Header priority: X-Hazo-Scope-Id > Cookie
|
|
103
102
|
*
|
|
104
103
|
* @param request - NextRequest object
|
|
105
104
|
* @param options - TenantAuthOptions for customization
|
|
106
|
-
* @returns TenantAuthResult with user, permissions,
|
|
105
|
+
* @returns TenantAuthResult with user, permissions, organization, and user_scopes
|
|
107
106
|
*
|
|
108
107
|
* @example
|
|
109
108
|
* ```typescript
|
|
110
109
|
* const auth = await hazo_get_tenant_auth(request);
|
|
111
|
-
* if (auth.authenticated && auth.
|
|
110
|
+
* if (auth.authenticated && auth.organization) {
|
|
112
111
|
* // Access tenant-specific data
|
|
113
|
-
* const data = await getData(auth.
|
|
112
|
+
* const data = await getData(auth.organization.id);
|
|
114
113
|
* }
|
|
115
114
|
* ```
|
|
116
115
|
*/
|
|
@@ -137,8 +136,8 @@ export async function hazo_get_tenant_auth(
|
|
|
137
136
|
user: null,
|
|
138
137
|
permissions: [],
|
|
139
138
|
permission_ok: false,
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
organization: null,
|
|
140
|
+
organization_id: null,
|
|
142
141
|
user_scopes: [],
|
|
143
142
|
scope_ok: false,
|
|
144
143
|
};
|
|
@@ -156,8 +155,8 @@ export async function hazo_get_tenant_auth(
|
|
|
156
155
|
// User scopes from cache or empty array
|
|
157
156
|
const user_scopes: ScopeDetails[] = cached?.scopes || [];
|
|
158
157
|
|
|
159
|
-
// Build
|
|
160
|
-
let
|
|
158
|
+
// Build organization info if scope access was successful
|
|
159
|
+
let organization: TenantOrganization | null = null;
|
|
161
160
|
|
|
162
161
|
if (scope_id && auth_result.scope_ok && auth_result.scope_access_via) {
|
|
163
162
|
// Find the scope in user's scopes that matches the access_via scope
|
|
@@ -166,7 +165,7 @@ export async function hazo_get_tenant_auth(
|
|
|
166
165
|
);
|
|
167
166
|
|
|
168
167
|
if (access_scope) {
|
|
169
|
-
|
|
168
|
+
organization = build_tenant_organization(
|
|
170
169
|
access_scope,
|
|
171
170
|
auth_result.scope_access_via.is_super_admin || false,
|
|
172
171
|
);
|
|
@@ -175,7 +174,7 @@ export async function hazo_get_tenant_auth(
|
|
|
175
174
|
const hazoConnect = get_hazo_connect_instance();
|
|
176
175
|
const scope_result = await get_scope_by_id(hazoConnect, scope_id);
|
|
177
176
|
if (scope_result.success && scope_result.scope) {
|
|
178
|
-
|
|
177
|
+
organization = {
|
|
179
178
|
id: scope_result.scope.id,
|
|
180
179
|
name: scope_result.scope.name,
|
|
181
180
|
slug: null, // Could fetch from scope if slug column exists
|
|
@@ -201,8 +200,8 @@ export async function hazo_get_tenant_auth(
|
|
|
201
200
|
permissions: auth_result.permissions,
|
|
202
201
|
permission_ok: auth_result.permission_ok,
|
|
203
202
|
missing_permissions: auth_result.missing_permissions,
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
organization,
|
|
204
|
+
organization_id: organization?.id || null,
|
|
206
205
|
user_scopes,
|
|
207
206
|
scope_ok: auth_result.scope_ok,
|
|
208
207
|
scope_access_via: auth_result.scope_access_via,
|
|
@@ -219,15 +218,15 @@ export async function hazo_get_tenant_auth(
|
|
|
219
218
|
*
|
|
220
219
|
* @param request - NextRequest object
|
|
221
220
|
* @param options - TenantAuthOptions for customization
|
|
222
|
-
* @returns RequiredTenantAuthResult with guaranteed non-null
|
|
221
|
+
* @returns RequiredTenantAuthResult with guaranteed non-null organization
|
|
223
222
|
* @throws AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedError
|
|
224
223
|
*
|
|
225
224
|
* @example
|
|
226
225
|
* ```typescript
|
|
227
226
|
* try {
|
|
228
227
|
* const auth = await require_tenant_auth(request);
|
|
229
|
-
* // auth.
|
|
230
|
-
* const data = await getData(auth.
|
|
228
|
+
* // auth.organization is guaranteed non-null here
|
|
229
|
+
* const data = await getData(auth.organization.id);
|
|
231
230
|
* } catch (error) {
|
|
232
231
|
* if (error instanceof HazoAuthError) {
|
|
233
232
|
* return NextResponse.json(
|
|
@@ -258,14 +257,14 @@ export async function require_tenant_auth(
|
|
|
258
257
|
throw new TenantAccessDeniedError(scope_id, result.user_scopes);
|
|
259
258
|
}
|
|
260
259
|
|
|
261
|
-
// Check if
|
|
262
|
-
if (!result.
|
|
260
|
+
// Check if organization context is required but missing
|
|
261
|
+
if (!result.organization) {
|
|
263
262
|
throw new TenantRequiredError(
|
|
264
|
-
"No
|
|
263
|
+
"No organization context provided. Include X-Hazo-Scope-Id header or scope cookie.",
|
|
265
264
|
result.user_scopes,
|
|
266
265
|
);
|
|
267
266
|
}
|
|
268
267
|
|
|
269
|
-
// Type assertion: at this point we know
|
|
268
|
+
// Type assertion: at this point we know organization is non-null
|
|
270
269
|
return result as RequiredTenantAuthResult;
|
|
271
270
|
}
|
|
@@ -22,7 +22,7 @@ export {
|
|
|
22
22
|
} from "./hazo_get_tenant_auth.server.js";
|
|
23
23
|
export type {
|
|
24
24
|
ScopeDetails,
|
|
25
|
-
|
|
25
|
+
TenantOrganization,
|
|
26
26
|
TenantAuthOptions,
|
|
27
27
|
TenantAuthResult,
|
|
28
28
|
RequiredTenantAuthResult,
|
|
@@ -50,7 +50,7 @@ export {
|
|
|
50
50
|
} from "./with_auth.server.js";
|
|
51
51
|
export type {
|
|
52
52
|
AuthenticatedTenantAuth,
|
|
53
|
-
|
|
53
|
+
AuthenticatedTenantAuthWithOrg,
|
|
54
54
|
WithAuthOptions,
|
|
55
55
|
} from "./with_auth.server";
|
|
56
56
|
|
|
@@ -5,6 +5,8 @@ import type { JWT } from "next-auth/jwt";
|
|
|
5
5
|
// ESM/CJS interop: next-auth providers are CommonJS, handle both export scenarios
|
|
6
6
|
import GoogleProviderImport from "next-auth/providers/google";
|
|
7
7
|
const GoogleProvider = (GoogleProviderImport as any).default || GoogleProviderImport;
|
|
8
|
+
import FacebookProviderImport from "next-auth/providers/facebook";
|
|
9
|
+
const FacebookProvider = (FacebookProviderImport as any).default || FacebookProviderImport;
|
|
8
10
|
import { get_oauth_config } from "../oauth_config.server.js";
|
|
9
11
|
import { handle_google_oauth_login } from "../services/oauth_service.js";
|
|
10
12
|
import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
@@ -67,6 +69,16 @@ export function get_nextauth_config(): AuthOptions {
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
// Add Facebook provider if enabled
|
|
73
|
+
if (oauth_config.enable_facebook_oauth && oauth_config.facebook_app_id) {
|
|
74
|
+
providers.push(
|
|
75
|
+
FacebookProvider({
|
|
76
|
+
clientId: oauth_config.facebook_app_id,
|
|
77
|
+
clientSecret: oauth_config.facebook_app_secret,
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
return {
|
|
71
83
|
providers,
|
|
72
84
|
pages: {
|
|
@@ -85,7 +97,10 @@ export function get_nextauth_config(): AuthOptions {
|
|
|
85
97
|
|
|
86
98
|
// Always redirect to our custom callback after sign-in to set hazo_auth cookies
|
|
87
99
|
// The callbackUrl from signIn() comes through as `url`
|
|
88
|
-
if (
|
|
100
|
+
if (
|
|
101
|
+
url.includes("/api/hazo_auth/oauth/google/callback") ||
|
|
102
|
+
url.includes("/api/hazo_auth/oauth/facebook/callback")
|
|
103
|
+
) {
|
|
89
104
|
return url;
|
|
90
105
|
}
|
|
91
106
|
|
|
@@ -98,6 +113,9 @@ export function get_nextauth_config(): AuthOptions {
|
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
// Default: redirect to our custom OAuth callback to set cookies
|
|
116
|
+
if (url.includes("facebook")) {
|
|
117
|
+
return `${baseUrl}/api/hazo_auth/oauth/facebook/callback`;
|
|
118
|
+
}
|
|
101
119
|
return `${baseUrl}/api/hazo_auth/oauth/google/callback`;
|
|
102
120
|
},
|
|
103
121
|
/**
|
|
@@ -162,6 +180,48 @@ export function get_nextauth_config(): AuthOptions {
|
|
|
162
180
|
return false;
|
|
163
181
|
}
|
|
164
182
|
}
|
|
183
|
+
|
|
184
|
+
if (account?.provider === "facebook" && profile) {
|
|
185
|
+
try {
|
|
186
|
+
const fbProfile = profile as NextAuthCallbackProfile;
|
|
187
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
188
|
+
const { handle_facebook_oauth_login } = await import("../services/oauth_service");
|
|
189
|
+
|
|
190
|
+
logger.info("nextauth_facebook_signin_attempt", {
|
|
191
|
+
email: user.email,
|
|
192
|
+
facebook_id: account.providerAccountId,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const result = await handle_facebook_oauth_login(hazoConnect, {
|
|
196
|
+
facebook_id: account.providerAccountId,
|
|
197
|
+
email: user.email || fbProfile.email || "",
|
|
198
|
+
name: user.name || fbProfile.name || undefined,
|
|
199
|
+
profile_picture_url: user.image || undefined,
|
|
200
|
+
// Facebook's email_verified is not exposed in the profile; default to false
|
|
201
|
+
// for safety — the user will be auto-verified if email matches a verified hazo user.
|
|
202
|
+
email_verified: false,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (!result.success) {
|
|
206
|
+
logger.error("nextauth_facebook_signin_failed", { email: user.email, error: result.error });
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
logger.info("nextauth_facebook_signin_success", {
|
|
211
|
+
user_id: result.user_id,
|
|
212
|
+
email: result.email,
|
|
213
|
+
is_new_user: result.is_new_user,
|
|
214
|
+
was_linked: result.was_linked,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
(account as Record<string, unknown>).hazo_user_id = result.user_id;
|
|
218
|
+
return true;
|
|
219
|
+
} catch (err) {
|
|
220
|
+
logger.error("nextauth_facebook_signin_exception", { error: String(err) });
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
165
225
|
return true;
|
|
166
226
|
},
|
|
167
227
|
/**
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
PermissionError,
|
|
11
11
|
type TenantAuthOptions,
|
|
12
12
|
type TenantAuthResult,
|
|
13
|
-
type
|
|
13
|
+
type TenantOrganization,
|
|
14
14
|
type HazoAuthUser,
|
|
15
15
|
type ScopeDetails,
|
|
16
16
|
type ScopeAccessInfo,
|
|
@@ -27,19 +27,19 @@ export type AuthenticatedTenantAuth = {
|
|
|
27
27
|
permissions: string[];
|
|
28
28
|
permission_ok: boolean;
|
|
29
29
|
missing_permissions?: string[];
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
organization: TenantOrganization | null;
|
|
31
|
+
organization_id: string | null;
|
|
32
32
|
user_scopes: ScopeDetails[];
|
|
33
33
|
scope_ok?: boolean;
|
|
34
34
|
scope_access_via?: ScopeAccessInfo;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Authenticated branch with guaranteed non-null
|
|
38
|
+
* Authenticated branch with guaranteed non-null organization
|
|
39
39
|
*/
|
|
40
|
-
export type
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
export type AuthenticatedTenantAuthWithOrg = AuthenticatedTenantAuth & {
|
|
41
|
+
organization: TenantOrganization;
|
|
42
|
+
organization_id: string;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -48,8 +48,8 @@ export type AuthenticatedTenantAuthWithSelectedScope = AuthenticatedTenantAuth &
|
|
|
48
48
|
*/
|
|
49
49
|
export type WithAuthOptions = TenantAuthOptions & {
|
|
50
50
|
/**
|
|
51
|
-
* If true, requires
|
|
52
|
-
* Narrows auth type to
|
|
51
|
+
* If true, requires organization context (403 if missing)
|
|
52
|
+
* Narrows auth type to AuthenticatedTenantAuthWithOrg
|
|
53
53
|
*/
|
|
54
54
|
require_tenant?: boolean;
|
|
55
55
|
};
|
|
@@ -75,7 +75,7 @@ type AuthenticatedHandler<TParams> = (
|
|
|
75
75
|
*/
|
|
76
76
|
type AuthenticatedTenantHandler<TParams> = (
|
|
77
77
|
request: NextRequest,
|
|
78
|
-
auth:
|
|
78
|
+
auth: AuthenticatedTenantAuthWithOrg,
|
|
79
79
|
params: TParams,
|
|
80
80
|
) => Promise<NextResponse> | NextResponse;
|
|
81
81
|
|
|
@@ -138,7 +138,7 @@ async function resolve_params<TParams>(
|
|
|
138
138
|
*
|
|
139
139
|
* - Calls `hazo_get_tenant_auth` and returns 401 if not authenticated
|
|
140
140
|
* - Returns 403 if `required_permissions` are specified and not satisfied
|
|
141
|
-
* - Returns 403 if `require_tenant: true` and no
|
|
141
|
+
* - Returns 403 if `require_tenant: true` and no organization context
|
|
142
142
|
* - Resolves `await context.params` (Next.js 15 pattern)
|
|
143
143
|
* - Catches HazoAuthError, PermissionError, and unexpected errors
|
|
144
144
|
*
|
|
@@ -161,8 +161,8 @@ async function resolve_params<TParams>(
|
|
|
161
161
|
* // With tenant requirement
|
|
162
162
|
* export const GET = withAuth<{ id: string }>(
|
|
163
163
|
* async (request, auth, { id }) => {
|
|
164
|
-
* // auth.
|
|
165
|
-
* const data = await getData(auth.
|
|
164
|
+
* // auth.organization is guaranteed non-null
|
|
165
|
+
* const data = await getData(auth.organization.id, id);
|
|
166
166
|
* return NextResponse.json(data);
|
|
167
167
|
* },
|
|
168
168
|
* { require_tenant: true }
|
|
@@ -227,10 +227,10 @@ export function withAuth<TParams = Record<string, never>>(
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// Check tenant requirement
|
|
230
|
-
if (options.require_tenant && !auth.
|
|
230
|
+
if (options.require_tenant && !auth.organization) {
|
|
231
231
|
return NextResponse.json(
|
|
232
232
|
{
|
|
233
|
-
error: "
|
|
233
|
+
error: "Organization context required",
|
|
234
234
|
code: "TENANT_REQUIRED",
|
|
235
235
|
},
|
|
236
236
|
{ status: 403 },
|
|
@@ -177,6 +177,14 @@ export const DEFAULT_OAUTH = {
|
|
|
177
177
|
skip_invitation_check: false,
|
|
178
178
|
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
179
179
|
no_scope_redirect: "/",
|
|
180
|
+
/** Enable Facebook OAuth login (requires HAZO_AUTH_FACEBOOK_APP_ID and HAZO_AUTH_FACEBOOK_APP_SECRET env vars) */
|
|
181
|
+
enable_facebook_oauth: false,
|
|
182
|
+
/** Facebook App ID — set via env var HAZO_AUTH_FACEBOOK_APP_ID or config */
|
|
183
|
+
facebook_app_id: "",
|
|
184
|
+
/** Facebook App Secret — set via env var HAZO_AUTH_FACEBOOK_APP_SECRET or config */
|
|
185
|
+
facebook_app_secret: "",
|
|
186
|
+
/** Text displayed on the Facebook sign-in button */
|
|
187
|
+
facebook_button_text: "Continue with Facebook",
|
|
180
188
|
} as const;
|
|
181
189
|
|
|
182
190
|
// section: navbar
|
|
@@ -27,10 +27,10 @@ export const BASE_COOKIE_NAMES = {
|
|
|
27
27
|
USER_ID: "hazo_auth_user_id",
|
|
28
28
|
USER_EMAIL: "hazo_auth_user_email",
|
|
29
29
|
SESSION: "hazo_auth_session",
|
|
30
|
-
SESSION_KIND: "hazo_auth_session_kind", // v6.1: marks OTP-issued sessions so /me can apply sliding expiry
|
|
31
30
|
DEV_LOCK: "hazo_auth_dev_lock",
|
|
32
31
|
SCOPE_ID: "hazo_auth_scope_id", // v5.2: Tenant context cookie for multi-tenancy
|
|
33
32
|
ANON_ID: "hazo_auth_anon_id", // v5.2: Stable opaque per-visitor ID for anonymous flows (e.g. hazo_feedback)
|
|
33
|
+
SESSION_KIND: "hazo_auth_session_kind", // v5.4: Sign-in method identifier (e.g. "otp", "google", "password")
|
|
34
34
|
} as const;
|
|
35
35
|
|
|
36
36
|
// section: main_function
|
|
@@ -7,10 +7,8 @@ import { get_config_value, get_config_value_allow_empty } from "./config/config_
|
|
|
7
7
|
import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
|
|
8
8
|
import { get_oauth_config, type OAuthConfig } from "./oauth_config.server.js";
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
// 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
|
|
13
|
-
const DEFAULT_LOGIN_IMAGE_PATH = "/hazo_auth/images/login_default.jpg";
|
|
10
|
+
// Assets served via the package's own API route — no manual copy needed.
|
|
11
|
+
const DEFAULT_LOGIN_IMAGE_PATH = "/api/hazo_auth/assets/login_default.jpg";
|
|
14
12
|
|
|
15
13
|
// section: types
|
|
16
14
|
export type LoginConfig = {
|
|
@@ -31,12 +29,6 @@ export type LoginConfig = {
|
|
|
31
29
|
imageBackgroundColor: string;
|
|
32
30
|
/** OAuth configuration */
|
|
33
31
|
oauth: OAuthConfig;
|
|
34
|
-
/** Whether the OTP sign-in link is shown below the login form */
|
|
35
|
-
otpSigninEnabled: boolean;
|
|
36
|
-
/** Label for the OTP sign-in link */
|
|
37
|
-
otpSigninLabel: string;
|
|
38
|
-
/** href for the OTP sign-in link */
|
|
39
|
-
otpSigninHref: string;
|
|
40
32
|
};
|
|
41
33
|
|
|
42
34
|
// section: helpers
|
|
@@ -100,11 +92,6 @@ export function get_login_config(): LoginConfig {
|
|
|
100
92
|
// Get OAuth configuration
|
|
101
93
|
const oauth = get_oauth_config();
|
|
102
94
|
|
|
103
|
-
// OTP sign-in link
|
|
104
|
-
const otpSigninEnabled = get_config_value(section, "otp_signin_enabled", "false") === "true";
|
|
105
|
-
const otpSigninLabel = get_config_value(section, "otp_signin_label", "Sign in with email code");
|
|
106
|
-
const otpSigninHref = get_config_value(section, "otp_signin_href", "/hazo_auth/otp");
|
|
107
|
-
|
|
108
95
|
return {
|
|
109
96
|
redirectRoute,
|
|
110
97
|
successMessage,
|
|
@@ -122,9 +109,6 @@ export function get_login_config(): LoginConfig {
|
|
|
122
109
|
imageAlt,
|
|
123
110
|
imageBackgroundColor,
|
|
124
111
|
oauth,
|
|
125
|
-
otpSigninEnabled,
|
|
126
|
-
otpSigninLabel,
|
|
127
|
-
otpSigninHref,
|
|
128
112
|
};
|
|
129
113
|
}
|
|
130
114
|
|
|
@@ -30,6 +30,14 @@ export type OAuthConfig = {
|
|
|
30
30
|
skip_invitation_check: boolean;
|
|
31
31
|
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
32
32
|
no_scope_redirect: string;
|
|
33
|
+
/** Enable Facebook OAuth login */
|
|
34
|
+
enable_facebook_oauth: boolean;
|
|
35
|
+
/** Facebook App ID (env: HAZO_AUTH_FACEBOOK_APP_ID overrides config) */
|
|
36
|
+
facebook_app_id: string;
|
|
37
|
+
/** Facebook App Secret (env: HAZO_AUTH_FACEBOOK_APP_SECRET overrides config) */
|
|
38
|
+
facebook_app_secret: string;
|
|
39
|
+
/** Text displayed on the Facebook sign-in button */
|
|
40
|
+
facebook_button_text: string;
|
|
33
41
|
};
|
|
34
42
|
|
|
35
43
|
// section: constants
|
|
@@ -108,6 +116,26 @@ export function get_oauth_config(): OAuthConfig {
|
|
|
108
116
|
DEFAULT_OAUTH.no_scope_redirect
|
|
109
117
|
);
|
|
110
118
|
|
|
119
|
+
const enable_facebook_oauth = get_config_boolean(
|
|
120
|
+
SECTION_NAME,
|
|
121
|
+
"enable_facebook_oauth",
|
|
122
|
+
false
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const facebook_app_id =
|
|
126
|
+
process.env.HAZO_AUTH_FACEBOOK_APP_ID ||
|
|
127
|
+
get_config_value(SECTION_NAME, "facebook_app_id", "");
|
|
128
|
+
|
|
129
|
+
const facebook_app_secret =
|
|
130
|
+
process.env.HAZO_AUTH_FACEBOOK_APP_SECRET ||
|
|
131
|
+
get_config_value(SECTION_NAME, "facebook_app_secret", "");
|
|
132
|
+
|
|
133
|
+
const facebook_button_text = get_config_value(
|
|
134
|
+
SECTION_NAME,
|
|
135
|
+
"facebook_button_text",
|
|
136
|
+
"Continue with Facebook"
|
|
137
|
+
);
|
|
138
|
+
|
|
111
139
|
return {
|
|
112
140
|
enable_google,
|
|
113
141
|
enable_email_password,
|
|
@@ -120,6 +148,10 @@ export function get_oauth_config(): OAuthConfig {
|
|
|
120
148
|
default_redirect,
|
|
121
149
|
skip_invitation_check,
|
|
122
150
|
no_scope_redirect,
|
|
151
|
+
enable_facebook_oauth,
|
|
152
|
+
facebook_app_id,
|
|
153
|
+
facebook_app_secret,
|
|
154
|
+
facebook_button_text,
|
|
123
155
|
};
|
|
124
156
|
}
|
|
125
157
|
|
|
@@ -40,6 +40,8 @@ export type RegisterConfig = {
|
|
|
40
40
|
enable_email_password: boolean;
|
|
41
41
|
google_button_text: string;
|
|
42
42
|
oauth_divider_text: string;
|
|
43
|
+
enable_facebook_oauth: boolean;
|
|
44
|
+
facebook_button_text: string;
|
|
43
45
|
};
|
|
44
46
|
};
|
|
45
47
|
|
|
@@ -125,6 +127,8 @@ export function get_register_config(): RegisterConfig {
|
|
|
125
127
|
enable_email_password: oauthConfig.enable_email_password,
|
|
126
128
|
google_button_text: registerGoogleButtonText,
|
|
127
129
|
oauth_divider_text: oauthConfig.oauth_divider_text,
|
|
130
|
+
enable_facebook_oauth: oauthConfig.enable_facebook_oauth,
|
|
131
|
+
facebook_button_text: oauthConfig.facebook_button_text,
|
|
128
132
|
},
|
|
129
133
|
};
|
|
130
134
|
}
|
|
@@ -101,21 +101,4 @@ export const hazo_auth_template_manifest: SystemTemplateManifest[] = [
|
|
|
101
101
|
},
|
|
102
102
|
],
|
|
103
103
|
},
|
|
104
|
-
{
|
|
105
|
-
template_name: "otp_signin_code",
|
|
106
|
-
template_label: "OTP sign-in code",
|
|
107
|
-
category: SYSTEM_CATEGORY,
|
|
108
|
-
html: read_template("otp_signin_code", "html"),
|
|
109
|
-
text: read_template("otp_signin_code", "txt"),
|
|
110
|
-
variables: [
|
|
111
|
-
{
|
|
112
|
-
variable_name: "otp_code",
|
|
113
|
-
variable_description: "6-digit OTP code for email sign-in (v6.1.0+)",
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
variable_name: "expires_in_minutes",
|
|
117
|
-
variable_description: "Number of minutes until the OTP code expires",
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
},
|
|
121
104
|
];
|
|
@@ -20,11 +20,5 @@ export * from "./scope_service.js";
|
|
|
20
20
|
export * from "./user_scope_service.js";
|
|
21
21
|
export * from "./oauth_service.js";
|
|
22
22
|
export * from "./branding_service.js";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
verify_email_otp,
|
|
26
|
-
generate_otp_code,
|
|
27
|
-
hash_otp_code,
|
|
28
|
-
verify_otp_code,
|
|
29
|
-
} from "./otp_service.js";
|
|
30
|
-
export type { RequestEmailOTPResult, VerifyEmailOTPResult } from "./otp_service";
|
|
23
|
+
|
|
24
|
+
|
|
@@ -22,6 +22,15 @@ export type GoogleOAuthData = {
|
|
|
22
22
|
email_verified: boolean;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
export type FacebookOAuthData = {
|
|
26
|
+
facebook_id: string;
|
|
27
|
+
email: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
profile_picture_url?: string;
|
|
30
|
+
/** Facebook does not always verify emails — only link when hazo user is verified */
|
|
31
|
+
email_verified: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
25
34
|
export type OAuthLoginResult = {
|
|
26
35
|
success: boolean;
|
|
27
36
|
user_id?: string;
|
|
@@ -241,6 +250,140 @@ export async function handle_google_oauth_login(
|
|
|
241
250
|
}
|
|
242
251
|
}
|
|
243
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Handles Facebook OAuth login: find-by-facebook_id → find-by-email+link → create new.
|
|
255
|
+
* Mirrors handle_google_oauth_login exactly. Uses auto_link_unverified_accounts gate.
|
|
256
|
+
*/
|
|
257
|
+
export async function handle_facebook_oauth_login(
|
|
258
|
+
adapter: HazoConnectAdapter,
|
|
259
|
+
data: FacebookOAuthData
|
|
260
|
+
): Promise<OAuthLoginResult> {
|
|
261
|
+
const logger = create_app_logger();
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const { facebook_id, email, name, profile_picture_url, email_verified } = data;
|
|
265
|
+
const oauth_config = get_oauth_config();
|
|
266
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
267
|
+
const now = new Date().toISOString();
|
|
268
|
+
|
|
269
|
+
// Step 1: existing user with this facebook_id
|
|
270
|
+
const users_by_fb_id = await users_service.findBy({ facebook_id });
|
|
271
|
+
if (Array.isArray(users_by_fb_id) && users_by_fb_id.length > 0) {
|
|
272
|
+
const user = users_by_fb_id[0];
|
|
273
|
+
await users_service.updateById(user.id, { last_logon: now, changed_at: now });
|
|
274
|
+
logger.info("oauth_service_facebook_login_existing_fb_user", {
|
|
275
|
+
filename: "oauth_service.ts",
|
|
276
|
+
user_id: user.id,
|
|
277
|
+
email: user.email_address,
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
user_id: user.id as string,
|
|
282
|
+
is_new_user: false,
|
|
283
|
+
was_linked: false,
|
|
284
|
+
email: user.email_address as string,
|
|
285
|
+
name: user.name as string | undefined,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Step 2: existing user with matching email
|
|
290
|
+
const users_by_email = await users_service.findBy({ email_address: email });
|
|
291
|
+
if (Array.isArray(users_by_email) && users_by_email.length > 0) {
|
|
292
|
+
const user = users_by_email[0];
|
|
293
|
+
const user_email_verified = user.email_verified as boolean;
|
|
294
|
+
|
|
295
|
+
if (!user_email_verified && !oauth_config.auto_link_unverified_accounts) {
|
|
296
|
+
return {
|
|
297
|
+
success: false,
|
|
298
|
+
error: "An account with this email exists but is not verified. Please verify your email first.",
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const current_auth_providers = (user.auth_providers as string) || "email";
|
|
303
|
+
const new_auth_providers = current_auth_providers.includes("facebook")
|
|
304
|
+
? current_auth_providers
|
|
305
|
+
: `${current_auth_providers},facebook`;
|
|
306
|
+
|
|
307
|
+
const update_data: Record<string, unknown> = {
|
|
308
|
+
facebook_id,
|
|
309
|
+
auth_providers: new_auth_providers,
|
|
310
|
+
last_logon: now,
|
|
311
|
+
changed_at: now,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
if (!user_email_verified && email_verified) {
|
|
315
|
+
update_data.email_verified = true;
|
|
316
|
+
}
|
|
317
|
+
if (!user.name && name) update_data.name = name;
|
|
318
|
+
if (!user.profile_picture_url && profile_picture_url) {
|
|
319
|
+
update_data.profile_picture_url = profile_picture_url;
|
|
320
|
+
update_data.profile_source = "custom";
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
await users_service.updateById(user.id, update_data);
|
|
324
|
+
logger.info("oauth_service_facebook_linked_to_existing", {
|
|
325
|
+
filename: "oauth_service.ts",
|
|
326
|
+
user_id: user.id,
|
|
327
|
+
email,
|
|
328
|
+
was_unverified: !user_email_verified,
|
|
329
|
+
});
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
user_id: user.id as string,
|
|
333
|
+
is_new_user: false,
|
|
334
|
+
was_linked: true,
|
|
335
|
+
email: user.email_address as string,
|
|
336
|
+
name: user.name as string | undefined,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Step 3: create new user
|
|
341
|
+
const user_id = randomUUID();
|
|
342
|
+
const insert_data: Record<string, unknown> = {
|
|
343
|
+
id: user_id,
|
|
344
|
+
email_address: email,
|
|
345
|
+
facebook_id,
|
|
346
|
+
auth_providers: "facebook",
|
|
347
|
+
email_verified: email_verified,
|
|
348
|
+
last_logon: now,
|
|
349
|
+
created_at: now,
|
|
350
|
+
changed_at: now,
|
|
351
|
+
};
|
|
352
|
+
if (name) insert_data.name = name;
|
|
353
|
+
if (profile_picture_url) {
|
|
354
|
+
insert_data.profile_picture_url = profile_picture_url;
|
|
355
|
+
insert_data.profile_source = "custom";
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const inserted = await users_service.insert(insert_data);
|
|
359
|
+
if (!Array.isArray(inserted) || inserted.length === 0) {
|
|
360
|
+
return { success: false, error: "Failed to create user account" };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
logger.info("oauth_service_facebook_new_user_created", {
|
|
364
|
+
filename: "oauth_service.ts",
|
|
365
|
+
user_id,
|
|
366
|
+
email,
|
|
367
|
+
});
|
|
368
|
+
return {
|
|
369
|
+
success: true,
|
|
370
|
+
user_id,
|
|
371
|
+
is_new_user: true,
|
|
372
|
+
was_linked: false,
|
|
373
|
+
email,
|
|
374
|
+
name,
|
|
375
|
+
};
|
|
376
|
+
} catch (error) {
|
|
377
|
+
const user_friendly_error = sanitize_error_for_user(error, {
|
|
378
|
+
logToConsole: true,
|
|
379
|
+
logToLogger: true,
|
|
380
|
+
logger,
|
|
381
|
+
context: { filename: "oauth_service.ts", email: data.email, operation: "handle_facebook_oauth_login" },
|
|
382
|
+
});
|
|
383
|
+
return { success: false, error: user_friendly_error };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
244
387
|
/**
|
|
245
388
|
* Links a Google account to an existing user
|
|
246
389
|
* @param adapter - The hazo_connect adapter instance
|