hazo_auth 5.1.16 → 5.1.17
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 +20 -0
- package/SETUP_CHECKLIST.md +39 -0
- package/cli-src/lib/config/default_config.ts +8 -0
- package/cli-src/lib/oauth_config.server.ts +36 -0
- package/cli-src/lib/services/invitation_service.ts +29 -0
- package/cli-src/lib/services/post_verification_service.ts +62 -5
- package/config/hazo_auth_config.ini +14 -0
- 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/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 +8 -0
- package/dist/lib/services/invitation_service.d.ts +2 -0
- package/dist/lib/services/invitation_service.d.ts.map +1 -1
- package/dist/lib/services/invitation_service.js +23 -0
- package/dist/lib/services/post_verification_service.d.ts +27 -5
- package/dist/lib/services/post_verification_service.d.ts.map +1 -1
- package/dist/lib/services/post_verification_service.js +35 -4
- package/dist/server/routes/oauth_google_callback.d.ts.map +1 -1
- package/dist/server/routes/oauth_google_callback.js +20 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -921,6 +921,19 @@ auto_link_unverified_accounts = true
|
|
|
921
921
|
# Customize button text (optional)
|
|
922
922
|
google_button_text = Continue with Google
|
|
923
923
|
oauth_divider_text = or
|
|
924
|
+
|
|
925
|
+
# Post-Login Redirect Configuration (v5.1.16+)
|
|
926
|
+
# URL for users who need to create a firm (default: /hazo_auth/create_firm)
|
|
927
|
+
# create_firm_url = /hazo_auth/create_firm
|
|
928
|
+
|
|
929
|
+
# Default redirect after OAuth login for users with scopes (default: /)
|
|
930
|
+
# default_redirect = /
|
|
931
|
+
|
|
932
|
+
# Skip invitation table check (set true if not using invitations)
|
|
933
|
+
# skip_invitation_check = false
|
|
934
|
+
|
|
935
|
+
# Redirect when skip_invitation_check=true and user has no scope (default: /)
|
|
936
|
+
# no_scope_redirect = /
|
|
924
937
|
```
|
|
925
938
|
|
|
926
939
|
### Step 5: Create NextAuth API Routes
|
|
@@ -1034,6 +1047,13 @@ Google OAuth adds one new dependency:
|
|
|
1034
1047
|
- Verify `/api/hazo_auth/oauth/google/callback` route exists
|
|
1035
1048
|
- Check server logs for errors during session creation
|
|
1036
1049
|
|
|
1050
|
+
**404 after Google OAuth login (v5.1.16+ fix):**
|
|
1051
|
+
- If users get 404 after Google OAuth, the `hazo_invitations` table may be missing
|
|
1052
|
+
- **Option 1:** Run migration `009_scope_consolidation.sql` to create the table
|
|
1053
|
+
- **Option 2:** Set `skip_invitation_check = true` in `[hazo_auth__oauth]` if not using invitations
|
|
1054
|
+
- Check logs for `invitation_table_missing` warnings
|
|
1055
|
+
- If using custom paths, set `create_firm_url` to your app's create firm page URL
|
|
1056
|
+
|
|
1037
1057
|
---
|
|
1038
1058
|
|
|
1039
1059
|
## Using Components
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -723,6 +723,19 @@ auto_link_unverified_accounts = true
|
|
|
723
723
|
# Customize button text (optional)
|
|
724
724
|
google_button_text = Continue with Google
|
|
725
725
|
oauth_divider_text = or
|
|
726
|
+
|
|
727
|
+
# Post-Login Redirect Configuration (v5.1.16+)
|
|
728
|
+
# URL for users who need to create a firm (default: /hazo_auth/create_firm)
|
|
729
|
+
# create_firm_url = /hazo_auth/create_firm
|
|
730
|
+
|
|
731
|
+
# Default redirect after OAuth login for users with scopes (default: /)
|
|
732
|
+
# default_redirect = /
|
|
733
|
+
|
|
734
|
+
# Skip invitation table check (set true if not using invitations)
|
|
735
|
+
# skip_invitation_check = false
|
|
736
|
+
|
|
737
|
+
# Redirect when skip_invitation_check=true and user has no scope (default: /)
|
|
738
|
+
# no_scope_redirect = /
|
|
726
739
|
```
|
|
727
740
|
|
|
728
741
|
**Configuration Options:**
|
|
@@ -785,6 +798,7 @@ ls app/api/hazo_auth/set_password/route.ts
|
|
|
785
798
|
- [ ] OAuth migration applied (google_id and auth_providers columns added)
|
|
786
799
|
- [ ] `[hazo_auth__oauth]` section configured in `hazo_auth_config.ini`
|
|
787
800
|
- [ ] OAuth API routes created (`[...nextauth]`, `oauth/google/callback`, `set_password`)
|
|
801
|
+
- [ ] Post-login redirect configured (if not using invitations, set `skip_invitation_check = true`)
|
|
788
802
|
|
|
789
803
|
---
|
|
790
804
|
|
|
@@ -1957,6 +1971,31 @@ curl -H "Cookie: hazo_auth_session=YOUR_TOKEN; hazo_auth_scope_id=SCOPE_UUID" \
|
|
|
1957
1971
|
2. Check cookies are being set (inspect browser devtools > Application > Cookies)
|
|
1958
1972
|
3. Ensure API routes are on same domain (no CORS issues)
|
|
1959
1973
|
|
|
1974
|
+
### Issue: 404 after Google OAuth login
|
|
1975
|
+
|
|
1976
|
+
**Symptoms:** User completes Google sign-in but gets redirected to 404 page (typically `/hazo_auth/create_firm`).
|
|
1977
|
+
|
|
1978
|
+
**Solutions:**
|
|
1979
|
+
1. **If not using invitations system:** Set `skip_invitation_check = true` in `[hazo_auth__oauth]`:
|
|
1980
|
+
```ini
|
|
1981
|
+
[hazo_auth__oauth]
|
|
1982
|
+
skip_invitation_check = true
|
|
1983
|
+
no_scope_redirect = /
|
|
1984
|
+
```
|
|
1985
|
+
|
|
1986
|
+
2. **If using invitations system:** Run the migration to create `hazo_invitations` table:
|
|
1987
|
+
```bash
|
|
1988
|
+
npm run migrate migrations/009_scope_consolidation.sql
|
|
1989
|
+
```
|
|
1990
|
+
|
|
1991
|
+
3. **If using custom paths:** Set `create_firm_url` to your app's create firm page:
|
|
1992
|
+
```ini
|
|
1993
|
+
[hazo_auth__oauth]
|
|
1994
|
+
create_firm_url = /my-app/create-organization
|
|
1995
|
+
```
|
|
1996
|
+
|
|
1997
|
+
4. **Check logs** for `invitation_table_missing` warnings - this indicates the table doesn't exist
|
|
1998
|
+
|
|
1960
1999
|
### Issue: Navbar logo or company name not showing
|
|
1961
2000
|
|
|
1962
2001
|
**Symptoms:** Navbar appears but logo and/or company name are missing.
|
|
@@ -165,6 +165,14 @@ export const DEFAULT_OAUTH = {
|
|
|
165
165
|
google_button_text: "Continue with Google",
|
|
166
166
|
/** Text displayed on the divider between OAuth and email/password form */
|
|
167
167
|
oauth_divider_text: "or continue with email",
|
|
168
|
+
/** URL for users who need to create a firm (default: /hazo_auth/create_firm) */
|
|
169
|
+
create_firm_url: "/hazo_auth/create_firm",
|
|
170
|
+
/** Default redirect after OAuth login for users with scopes */
|
|
171
|
+
default_redirect: "/",
|
|
172
|
+
/** Skip invitation table check (set true if not using invitations) */
|
|
173
|
+
skip_invitation_check: false,
|
|
174
|
+
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
175
|
+
no_scope_redirect: "/",
|
|
168
176
|
} as const;
|
|
169
177
|
|
|
170
178
|
// section: navbar
|
|
@@ -15,6 +15,14 @@ export type OAuthConfig = {
|
|
|
15
15
|
google_button_text: string;
|
|
16
16
|
/** Text displayed on the divider between OAuth and email/password form */
|
|
17
17
|
oauth_divider_text: string;
|
|
18
|
+
/** URL for users who need to create a firm (default: /hazo_auth/create_firm) */
|
|
19
|
+
create_firm_url: string;
|
|
20
|
+
/** Default redirect after OAuth login for users with scopes */
|
|
21
|
+
default_redirect: string;
|
|
22
|
+
/** Skip invitation table check (set true if not using invitations) */
|
|
23
|
+
skip_invitation_check: boolean;
|
|
24
|
+
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
25
|
+
no_scope_redirect: string;
|
|
18
26
|
};
|
|
19
27
|
|
|
20
28
|
// section: constants
|
|
@@ -57,12 +65,40 @@ export function get_oauth_config(): OAuthConfig {
|
|
|
57
65
|
DEFAULT_OAUTH.oauth_divider_text
|
|
58
66
|
);
|
|
59
67
|
|
|
68
|
+
const create_firm_url = get_config_value(
|
|
69
|
+
SECTION_NAME,
|
|
70
|
+
"create_firm_url",
|
|
71
|
+
DEFAULT_OAUTH.create_firm_url
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const default_redirect = get_config_value(
|
|
75
|
+
SECTION_NAME,
|
|
76
|
+
"default_redirect",
|
|
77
|
+
DEFAULT_OAUTH.default_redirect
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const skip_invitation_check = get_config_boolean(
|
|
81
|
+
SECTION_NAME,
|
|
82
|
+
"skip_invitation_check",
|
|
83
|
+
DEFAULT_OAUTH.skip_invitation_check
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const no_scope_redirect = get_config_value(
|
|
87
|
+
SECTION_NAME,
|
|
88
|
+
"no_scope_redirect",
|
|
89
|
+
DEFAULT_OAUTH.no_scope_redirect
|
|
90
|
+
);
|
|
91
|
+
|
|
60
92
|
return {
|
|
61
93
|
enable_google,
|
|
62
94
|
enable_email_password,
|
|
63
95
|
auto_link_unverified_accounts,
|
|
64
96
|
google_button_text,
|
|
65
97
|
oauth_divider_text,
|
|
98
|
+
create_firm_url,
|
|
99
|
+
default_redirect,
|
|
100
|
+
skip_invitation_check,
|
|
101
|
+
no_scope_redirect,
|
|
66
102
|
};
|
|
67
103
|
}
|
|
68
104
|
|
|
@@ -29,6 +29,8 @@ export type InvitationServiceResult = {
|
|
|
29
29
|
invitation?: InvitationRecord;
|
|
30
30
|
invitations?: InvitationRecord[];
|
|
31
31
|
error?: string;
|
|
32
|
+
/** Set to true when the hazo_invitations table doesn't exist */
|
|
33
|
+
table_missing?: boolean;
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
export type CreateInvitationData = {
|
|
@@ -172,6 +174,33 @@ export async function get_pending_invitation_by_email(
|
|
|
172
174
|
};
|
|
173
175
|
} catch (error) {
|
|
174
176
|
const logger = create_app_logger();
|
|
177
|
+
|
|
178
|
+
// Check if the error indicates the table doesn't exist
|
|
179
|
+
const error_string = error instanceof Error ? error.message : String(error);
|
|
180
|
+
const is_table_missing =
|
|
181
|
+
// SQLite: "no such table: hazo_invitations"
|
|
182
|
+
error_string.toLowerCase().includes("no such table") ||
|
|
183
|
+
// PostgreSQL: 'relation "hazo_invitations" does not exist'
|
|
184
|
+
(error_string.toLowerCase().includes("relation") && error_string.toLowerCase().includes("does not exist")) ||
|
|
185
|
+
// PostgREST: 404 response for missing table
|
|
186
|
+
(error_string.includes("404") && error_string.includes("hazo_invitations"));
|
|
187
|
+
|
|
188
|
+
if (is_table_missing) {
|
|
189
|
+
logger.warn("invitation_table_missing", {
|
|
190
|
+
filename: "invitation_service.ts",
|
|
191
|
+
line_number: 0,
|
|
192
|
+
operation: "get_pending_invitation_by_email",
|
|
193
|
+
email_address,
|
|
194
|
+
note: "hazo_invitations table does not exist - run migration or set skip_invitation_check=true",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: "Invitation table not found",
|
|
200
|
+
table_missing: true,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
175
204
|
const error_message = sanitize_error_for_user(error, {
|
|
176
205
|
logToConsole: true,
|
|
177
206
|
logToLogger: true,
|
|
@@ -27,6 +27,28 @@ export type PostVerificationOptions = {
|
|
|
27
27
|
create_firm_url?: string; // Default: "/hazo_auth/create_firm"
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
export type PostLoginRedirectOptions = {
|
|
31
|
+
/** Default redirect for users with scopes (default: "/") */
|
|
32
|
+
default_redirect?: string;
|
|
33
|
+
/** URL for users who need to create a firm (default: "/hazo_auth/create_firm") */
|
|
34
|
+
create_firm_url?: string;
|
|
35
|
+
/** Skip invitation table check (set true if not using invitations) */
|
|
36
|
+
skip_invitation_check?: boolean;
|
|
37
|
+
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
38
|
+
no_scope_redirect?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type PostLoginRedirectResult = {
|
|
42
|
+
/** The URL to redirect the user to */
|
|
43
|
+
redirect_url: string;
|
|
44
|
+
/** True if user needs onboarding (no scope, create firm, etc.) */
|
|
45
|
+
needs_onboarding: boolean;
|
|
46
|
+
/** True if invitation check was skipped due to config */
|
|
47
|
+
invitation_check_skipped?: boolean;
|
|
48
|
+
/** True if invitation table query failed (table missing) */
|
|
49
|
+
invitation_table_error?: boolean;
|
|
50
|
+
};
|
|
51
|
+
|
|
30
52
|
// section: constants
|
|
31
53
|
|
|
32
54
|
const DEFAULT_REDIRECT_URL = "/";
|
|
@@ -181,36 +203,71 @@ export async function needs_onboarding(
|
|
|
181
203
|
|
|
182
204
|
/**
|
|
183
205
|
* Gets the appropriate redirect URL for a user after login
|
|
184
|
-
* Takes into account scope status and url_on_logon
|
|
206
|
+
* Takes into account scope status, invitations, and url_on_logon
|
|
207
|
+
*
|
|
208
|
+
* @param adapter - HazoConnect adapter
|
|
209
|
+
* @param user_id - User ID
|
|
210
|
+
* @param user_email - User email (for invitation lookup)
|
|
211
|
+
* @param options - Configuration options (or string for backwards compatibility)
|
|
185
212
|
*/
|
|
186
213
|
export async function get_post_login_redirect(
|
|
187
214
|
adapter: HazoConnectAdapter,
|
|
188
215
|
user_id: string,
|
|
189
216
|
user_email: string,
|
|
190
|
-
|
|
191
|
-
): Promise<
|
|
217
|
+
options?: PostLoginRedirectOptions | string,
|
|
218
|
+
): Promise<PostLoginRedirectResult> {
|
|
219
|
+
// Handle backwards compatibility: if options is a string, treat it as default_redirect
|
|
220
|
+
const opts: PostLoginRedirectOptions = typeof options === "string"
|
|
221
|
+
? { default_redirect: options }
|
|
222
|
+
: options || {};
|
|
223
|
+
|
|
224
|
+
const default_redirect = opts.default_redirect || DEFAULT_REDIRECT_URL;
|
|
225
|
+
const create_firm_url = opts.create_firm_url || CREATE_FIRM_URL;
|
|
226
|
+
const skip_invitation_check = opts.skip_invitation_check || false;
|
|
227
|
+
const no_scope_redirect = opts.no_scope_redirect || DEFAULT_REDIRECT_URL;
|
|
228
|
+
|
|
192
229
|
try {
|
|
193
230
|
// Check if user has scope
|
|
194
231
|
const has_scope = await user_has_any_scope(adapter, user_id);
|
|
195
232
|
|
|
196
233
|
if (!has_scope) {
|
|
234
|
+
// User has no scope - check invitations (unless skipped)
|
|
235
|
+
if (skip_invitation_check) {
|
|
236
|
+
// Invitation check skipped via config - redirect to no_scope_redirect
|
|
237
|
+
return {
|
|
238
|
+
redirect_url: no_scope_redirect,
|
|
239
|
+
needs_onboarding: true,
|
|
240
|
+
invitation_check_skipped: true,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
197
244
|
// Check for invitation
|
|
198
245
|
const invitation_result = await get_pending_invitation_by_email(
|
|
199
246
|
adapter,
|
|
200
247
|
user_email,
|
|
201
248
|
);
|
|
202
249
|
|
|
250
|
+
// Check if invitation table is missing
|
|
251
|
+
if (invitation_result.table_missing) {
|
|
252
|
+
// Table doesn't exist - redirect to create_firm_url but flag the error
|
|
253
|
+
return {
|
|
254
|
+
redirect_url: create_firm_url,
|
|
255
|
+
needs_onboarding: true,
|
|
256
|
+
invitation_table_error: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
203
260
|
if (invitation_result.success && invitation_result.invitation) {
|
|
204
261
|
// Has invitation - they need to complete the flow
|
|
205
262
|
return {
|
|
206
|
-
redirect_url:
|
|
263
|
+
redirect_url: create_firm_url,
|
|
207
264
|
needs_onboarding: true,
|
|
208
265
|
};
|
|
209
266
|
}
|
|
210
267
|
|
|
211
268
|
// No scope, no invitation - needs to create firm
|
|
212
269
|
return {
|
|
213
|
-
redirect_url:
|
|
270
|
+
redirect_url: create_firm_url,
|
|
214
271
|
needs_onboarding: true,
|
|
215
272
|
};
|
|
216
273
|
}
|
|
@@ -158,6 +158,20 @@ auto_link_unverified_accounts = true
|
|
|
158
158
|
# Text displayed on the divider between OAuth and email/password form
|
|
159
159
|
# oauth_divider_text = or continue with email
|
|
160
160
|
|
|
161
|
+
# Post-Login Redirect Configuration
|
|
162
|
+
# URL for users who need to create a firm/organization (default: /hazo_auth/create_firm)
|
|
163
|
+
# create_firm_url = /hazo_auth/create_firm
|
|
164
|
+
|
|
165
|
+
# Default redirect after OAuth login for users with scopes (default: /)
|
|
166
|
+
# default_redirect = /
|
|
167
|
+
|
|
168
|
+
# Skip invitation table check (set true if not using invitations)
|
|
169
|
+
# When enabled, users without scopes will redirect to no_scope_redirect instead of checking invitations
|
|
170
|
+
# skip_invitation_check = false
|
|
171
|
+
|
|
172
|
+
# Redirect when skip_invitation_check=true and user has no scope (default: /)
|
|
173
|
+
# no_scope_redirect = /
|
|
174
|
+
|
|
161
175
|
[hazo_auth__login_layout]
|
|
162
176
|
# Image configuration
|
|
163
177
|
# If image_src is commented out or empty, the default image from package assets will be used
|
|
@@ -124,6 +124,14 @@ export declare const DEFAULT_OAUTH: {
|
|
|
124
124
|
readonly google_button_text: "Continue with Google";
|
|
125
125
|
/** Text displayed on the divider between OAuth and email/password form */
|
|
126
126
|
readonly oauth_divider_text: "or continue with email";
|
|
127
|
+
/** URL for users who need to create a firm (default: /hazo_auth/create_firm) */
|
|
128
|
+
readonly create_firm_url: "/hazo_auth/create_firm";
|
|
129
|
+
/** Default redirect after OAuth login for users with scopes */
|
|
130
|
+
readonly default_redirect: "/";
|
|
131
|
+
/** Skip invitation table check (set true if not using invitations) */
|
|
132
|
+
readonly skip_invitation_check: false;
|
|
133
|
+
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
134
|
+
readonly no_scope_redirect: "/";
|
|
127
135
|
};
|
|
128
136
|
export declare const DEFAULT_NAVBAR: {
|
|
129
137
|
/** Enable navbar on auth pages */
|
|
@@ -314,6 +322,14 @@ export declare const HAZO_AUTH_DEFAULTS: {
|
|
|
314
322
|
readonly google_button_text: "Continue with Google";
|
|
315
323
|
/** Text displayed on the divider between OAuth and email/password form */
|
|
316
324
|
readonly oauth_divider_text: "or continue with email";
|
|
325
|
+
/** URL for users who need to create a firm (default: /hazo_auth/create_firm) */
|
|
326
|
+
readonly create_firm_url: "/hazo_auth/create_firm";
|
|
327
|
+
/** Default redirect after OAuth login for users with scopes */
|
|
328
|
+
readonly default_redirect: "/";
|
|
329
|
+
/** Skip invitation table check (set true if not using invitations) */
|
|
330
|
+
readonly skip_invitation_check: false;
|
|
331
|
+
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
332
|
+
readonly no_scope_redirect: "/";
|
|
317
333
|
};
|
|
318
334
|
readonly devLock: {
|
|
319
335
|
/** Enable the development lock screen (also requires HAZO_AUTH_DEV_LOCK_ENABLED env var) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default_config.d.ts","sourceRoot":"","sources":["../../../src/lib/config/default_config.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,6BAA6B;;;;;;CAMhC,CAAC;AAGX,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC;AAGX,eAAO,MAAM,uBAAuB;;;;;;;CAO1B,CAAC;AAGX,eAAO,MAAM,gBAAgB;;;;;;;;;CASnB,CAAC;AAGX,eAAO,MAAM,kBAAkB;;;CAGrB,CAAC;AAGX,eAAO,MAAM,gBAAgB;;;;;CAKnB,CAAC;AAGX,eAAO,MAAM,yBAAyB;;;;;;CAM5B,CAAC;AAGX,eAAO,MAAM,aAAa;4BACI,MAAM,GAAG,SAAS;;;;;;CAMtC,CAAC;AAGX,eAAO,MAAM,gBAAgB;4BACC,MAAM,GAAG,SAAS;;;;;CAKtC,CAAC;AAGX,eAAO,MAAM,uBAAuB;;;;CAI1B,CAAC;AAGX,eAAO,MAAM,sBAAsB;;;;CAIzB,CAAC;AAGX,eAAO,MAAM,0BAA0B;;;;;CAK7B,CAAC;AAGX,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC;AAGX,eAAO,MAAM,uBAAuB;;;;CAI1B,CAAC;AAGX,eAAO,MAAM,oBAAoB;;;;CAIvB,CAAC;AAGX,eAAO,MAAM,gBAAgB;0BACE,YAAY,GAAG,cAAc;;;;;IAK1D,2DAA2D;;CAEnD,CAAC;AAGX,eAAO,MAAM,wBAAwB;;;;;;;;CAQ3B,CAAC;AAGX,eAAO,MAAM,iBAAiB;;CAEpB,CAAC;AAGX,eAAO,MAAM,aAAa;IACxB,kHAAkH;;IAElH,8CAA8C;;IAE9C,iGAAiG;;IAEjG,kDAAkD;;IAElD,0EAA0E;;
|
|
1
|
+
{"version":3,"file":"default_config.d.ts","sourceRoot":"","sources":["../../../src/lib/config/default_config.ts"],"names":[],"mappings":"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"}
|
|
@@ -146,6 +146,14 @@ export const DEFAULT_OAUTH = {
|
|
|
146
146
|
google_button_text: "Continue with Google",
|
|
147
147
|
/** Text displayed on the divider between OAuth and email/password form */
|
|
148
148
|
oauth_divider_text: "or continue with email",
|
|
149
|
+
/** URL for users who need to create a firm (default: /hazo_auth/create_firm) */
|
|
150
|
+
create_firm_url: "/hazo_auth/create_firm",
|
|
151
|
+
/** Default redirect after OAuth login for users with scopes */
|
|
152
|
+
default_redirect: "/",
|
|
153
|
+
/** Skip invitation table check (set true if not using invitations) */
|
|
154
|
+
skip_invitation_check: false,
|
|
155
|
+
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
156
|
+
no_scope_redirect: "/",
|
|
149
157
|
};
|
|
150
158
|
// section: navbar
|
|
151
159
|
export const DEFAULT_NAVBAR = {
|
|
@@ -9,6 +9,14 @@ export type OAuthConfig = {
|
|
|
9
9
|
google_button_text: string;
|
|
10
10
|
/** Text displayed on the divider between OAuth and email/password form */
|
|
11
11
|
oauth_divider_text: string;
|
|
12
|
+
/** URL for users who need to create a firm (default: /hazo_auth/create_firm) */
|
|
13
|
+
create_firm_url: string;
|
|
14
|
+
/** Default redirect after OAuth login for users with scopes */
|
|
15
|
+
default_redirect: string;
|
|
16
|
+
/** Skip invitation table check (set true if not using invitations) */
|
|
17
|
+
skip_invitation_check: boolean;
|
|
18
|
+
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
19
|
+
no_scope_redirect: string;
|
|
12
20
|
};
|
|
13
21
|
/**
|
|
14
22
|
* Reads OAuth configuration from hazo_auth_config.ini file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/oauth_config.server.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG;IACxB,gCAAgC;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,8CAA8C;IAC9C,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4EAA4E;IAC5E,6BAA6B,EAAE,OAAO,CAAC;IACvC,kDAAkD;IAClD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,kBAAkB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"oauth_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/oauth_config.server.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG;IACxB,gCAAgC;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,8CAA8C;IAC9C,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4EAA4E;IAC5E,6BAA6B,EAAE,OAAO,CAAC;IACvC,kDAAkD;IAClD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,eAAe,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qEAAqE;IACrE,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAMF;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,CAkE9C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAMnD"}
|
|
@@ -16,12 +16,20 @@ export function get_oauth_config() {
|
|
|
16
16
|
const auto_link_unverified_accounts = get_config_boolean(SECTION_NAME, "auto_link_unverified_accounts", DEFAULT_OAUTH.auto_link_unverified_accounts);
|
|
17
17
|
const google_button_text = get_config_value(SECTION_NAME, "google_button_text", DEFAULT_OAUTH.google_button_text);
|
|
18
18
|
const oauth_divider_text = get_config_value(SECTION_NAME, "oauth_divider_text", DEFAULT_OAUTH.oauth_divider_text);
|
|
19
|
+
const create_firm_url = get_config_value(SECTION_NAME, "create_firm_url", DEFAULT_OAUTH.create_firm_url);
|
|
20
|
+
const default_redirect = get_config_value(SECTION_NAME, "default_redirect", DEFAULT_OAUTH.default_redirect);
|
|
21
|
+
const skip_invitation_check = get_config_boolean(SECTION_NAME, "skip_invitation_check", DEFAULT_OAUTH.skip_invitation_check);
|
|
22
|
+
const no_scope_redirect = get_config_value(SECTION_NAME, "no_scope_redirect", DEFAULT_OAUTH.no_scope_redirect);
|
|
19
23
|
return {
|
|
20
24
|
enable_google,
|
|
21
25
|
enable_email_password,
|
|
22
26
|
auto_link_unverified_accounts,
|
|
23
27
|
google_button_text,
|
|
24
28
|
oauth_divider_text,
|
|
29
|
+
create_firm_url,
|
|
30
|
+
default_redirect,
|
|
31
|
+
skip_invitation_check,
|
|
32
|
+
no_scope_redirect,
|
|
25
33
|
};
|
|
26
34
|
}
|
|
27
35
|
/**
|
|
@@ -17,6 +17,8 @@ export type InvitationServiceResult = {
|
|
|
17
17
|
invitation?: InvitationRecord;
|
|
18
18
|
invitations?: InvitationRecord[];
|
|
19
19
|
error?: string;
|
|
20
|
+
/** Set to true when the hazo_invitations table doesn't exist */
|
|
21
|
+
table_missing?: boolean;
|
|
20
22
|
};
|
|
21
23
|
export type CreateInvitationData = {
|
|
22
24
|
email_address: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invitation_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/invitation_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AASvD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"invitation_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/invitation_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AASvD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AASF;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CAyElC;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,uBAAuB,CAAC,CAkFlC;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,uBAAuB,CAAC,CAmClC;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,uBAAuB,CAAC,CA8FlC;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,uBAAuB,CAAC,CA0DlC;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAoClC;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,kBAAkB,EAC3B,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAmClC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0CvE"}
|
|
@@ -112,6 +112,29 @@ export async function get_pending_invitation_by_email(adapter, email_address) {
|
|
|
112
112
|
}
|
|
113
113
|
catch (error) {
|
|
114
114
|
const logger = create_app_logger();
|
|
115
|
+
// Check if the error indicates the table doesn't exist
|
|
116
|
+
const error_string = error instanceof Error ? error.message : String(error);
|
|
117
|
+
const is_table_missing =
|
|
118
|
+
// SQLite: "no such table: hazo_invitations"
|
|
119
|
+
error_string.toLowerCase().includes("no such table") ||
|
|
120
|
+
// PostgreSQL: 'relation "hazo_invitations" does not exist'
|
|
121
|
+
(error_string.toLowerCase().includes("relation") && error_string.toLowerCase().includes("does not exist")) ||
|
|
122
|
+
// PostgREST: 404 response for missing table
|
|
123
|
+
(error_string.includes("404") && error_string.includes("hazo_invitations"));
|
|
124
|
+
if (is_table_missing) {
|
|
125
|
+
logger.warn("invitation_table_missing", {
|
|
126
|
+
filename: "invitation_service.ts",
|
|
127
|
+
line_number: 0,
|
|
128
|
+
operation: "get_pending_invitation_by_email",
|
|
129
|
+
email_address,
|
|
130
|
+
note: "hazo_invitations table does not exist - run migration or set skip_invitation_check=true",
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: "Invitation table not found",
|
|
135
|
+
table_missing: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
115
138
|
const error_message = sanitize_error_for_user(error, {
|
|
116
139
|
logToConsole: true,
|
|
117
140
|
logToLogger: true,
|
|
@@ -11,6 +11,26 @@ export type PostVerificationOptions = {
|
|
|
11
11
|
default_redirect_url?: string;
|
|
12
12
|
create_firm_url?: string;
|
|
13
13
|
};
|
|
14
|
+
export type PostLoginRedirectOptions = {
|
|
15
|
+
/** Default redirect for users with scopes (default: "/") */
|
|
16
|
+
default_redirect?: string;
|
|
17
|
+
/** URL for users who need to create a firm (default: "/hazo_auth/create_firm") */
|
|
18
|
+
create_firm_url?: string;
|
|
19
|
+
/** Skip invitation table check (set true if not using invitations) */
|
|
20
|
+
skip_invitation_check?: boolean;
|
|
21
|
+
/** Redirect when skip_invitation_check=true and user has no scope */
|
|
22
|
+
no_scope_redirect?: string;
|
|
23
|
+
};
|
|
24
|
+
export type PostLoginRedirectResult = {
|
|
25
|
+
/** The URL to redirect the user to */
|
|
26
|
+
redirect_url: string;
|
|
27
|
+
/** True if user needs onboarding (no scope, create firm, etc.) */
|
|
28
|
+
needs_onboarding: boolean;
|
|
29
|
+
/** True if invitation check was skipped due to config */
|
|
30
|
+
invitation_check_skipped?: boolean;
|
|
31
|
+
/** True if invitation table query failed (table missing) */
|
|
32
|
+
invitation_table_error?: boolean;
|
|
33
|
+
};
|
|
14
34
|
/**
|
|
15
35
|
* Handles the post-email-verification flow
|
|
16
36
|
*
|
|
@@ -36,10 +56,12 @@ export declare function handle_post_verification(adapter: HazoConnectAdapter, us
|
|
|
36
56
|
export declare function needs_onboarding(adapter: HazoConnectAdapter, user_id: string): Promise<boolean>;
|
|
37
57
|
/**
|
|
38
58
|
* Gets the appropriate redirect URL for a user after login
|
|
39
|
-
* Takes into account scope status and url_on_logon
|
|
59
|
+
* Takes into account scope status, invitations, and url_on_logon
|
|
60
|
+
*
|
|
61
|
+
* @param adapter - HazoConnect adapter
|
|
62
|
+
* @param user_id - User ID
|
|
63
|
+
* @param user_email - User email (for invitation lookup)
|
|
64
|
+
* @param options - Configuration options (or string for backwards compatibility)
|
|
40
65
|
*/
|
|
41
|
-
export declare function get_post_login_redirect(adapter: HazoConnectAdapter, user_id: string, user_email: string,
|
|
42
|
-
redirect_url: string;
|
|
43
|
-
needs_onboarding: boolean;
|
|
44
|
-
}>;
|
|
66
|
+
export declare function get_post_login_redirect(adapter: HazoConnectAdapter, user_id: string, user_email: string, options?: PostLoginRedirectOptions | string): Promise<PostLoginRedirectResult>;
|
|
45
67
|
//# sourceMappingURL=post_verification_service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post_verification_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/post_verification_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAYvD,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,aAAa,CAAC;AAEhE,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,sBAAsB,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AA8BF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,sBAAsB,CAAC,CAqFjC;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED
|
|
1
|
+
{"version":3,"file":"post_verification_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/post_verification_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAYvD,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,aAAa,CAAC;AAEhE,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,sBAAsB,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,yDAAyD;IACzD,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,4DAA4D;IAC5D,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AA8BF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,sBAAsB,CAAC,CAqFjC;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,wBAAwB,GAAG,MAAM,GAC1C,OAAO,CAAC,uBAAuB,CAAC,CAsElC"}
|
|
@@ -126,25 +126,56 @@ export async function needs_onboarding(adapter, user_id) {
|
|
|
126
126
|
}
|
|
127
127
|
/**
|
|
128
128
|
* Gets the appropriate redirect URL for a user after login
|
|
129
|
-
* Takes into account scope status and url_on_logon
|
|
129
|
+
* Takes into account scope status, invitations, and url_on_logon
|
|
130
|
+
*
|
|
131
|
+
* @param adapter - HazoConnect adapter
|
|
132
|
+
* @param user_id - User ID
|
|
133
|
+
* @param user_email - User email (for invitation lookup)
|
|
134
|
+
* @param options - Configuration options (or string for backwards compatibility)
|
|
130
135
|
*/
|
|
131
|
-
export async function get_post_login_redirect(adapter, user_id, user_email,
|
|
136
|
+
export async function get_post_login_redirect(adapter, user_id, user_email, options) {
|
|
137
|
+
// Handle backwards compatibility: if options is a string, treat it as default_redirect
|
|
138
|
+
const opts = typeof options === "string"
|
|
139
|
+
? { default_redirect: options }
|
|
140
|
+
: options || {};
|
|
141
|
+
const default_redirect = opts.default_redirect || DEFAULT_REDIRECT_URL;
|
|
142
|
+
const create_firm_url = opts.create_firm_url || CREATE_FIRM_URL;
|
|
143
|
+
const skip_invitation_check = opts.skip_invitation_check || false;
|
|
144
|
+
const no_scope_redirect = opts.no_scope_redirect || DEFAULT_REDIRECT_URL;
|
|
132
145
|
try {
|
|
133
146
|
// Check if user has scope
|
|
134
147
|
const has_scope = await user_has_any_scope(adapter, user_id);
|
|
135
148
|
if (!has_scope) {
|
|
149
|
+
// User has no scope - check invitations (unless skipped)
|
|
150
|
+
if (skip_invitation_check) {
|
|
151
|
+
// Invitation check skipped via config - redirect to no_scope_redirect
|
|
152
|
+
return {
|
|
153
|
+
redirect_url: no_scope_redirect,
|
|
154
|
+
needs_onboarding: true,
|
|
155
|
+
invitation_check_skipped: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
136
158
|
// Check for invitation
|
|
137
159
|
const invitation_result = await get_pending_invitation_by_email(adapter, user_email);
|
|
160
|
+
// Check if invitation table is missing
|
|
161
|
+
if (invitation_result.table_missing) {
|
|
162
|
+
// Table doesn't exist - redirect to create_firm_url but flag the error
|
|
163
|
+
return {
|
|
164
|
+
redirect_url: create_firm_url,
|
|
165
|
+
needs_onboarding: true,
|
|
166
|
+
invitation_table_error: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
138
169
|
if (invitation_result.success && invitation_result.invitation) {
|
|
139
170
|
// Has invitation - they need to complete the flow
|
|
140
171
|
return {
|
|
141
|
-
redirect_url:
|
|
172
|
+
redirect_url: create_firm_url,
|
|
142
173
|
needs_onboarding: true,
|
|
143
174
|
};
|
|
144
175
|
}
|
|
145
176
|
// No scope, no invitation - needs to create firm
|
|
146
177
|
return {
|
|
147
|
-
redirect_url:
|
|
178
|
+
redirect_url: create_firm_url,
|
|
148
179
|
needs_onboarding: true,
|
|
149
180
|
};
|
|
150
181
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth_google_callback.d.ts","sourceRoot":"","sources":["../../../src/server/routes/oauth_google_callback.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"oauth_google_callback.d.ts","sourceRoot":"","sources":["../../../src/server/routes/oauth_google_callback.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAuBxD;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW,kCAgJ7C"}
|
|
@@ -9,6 +9,7 @@ import { get_login_config } from "../../lib/login_config.server.js";
|
|
|
9
9
|
import { get_cookie_name, get_cookie_options, BASE_COOKIE_NAMES } from "../../lib/cookies_config.server.js";
|
|
10
10
|
import { get_hazo_connect_instance } from "../../lib/hazo_connect_instance.server.js";
|
|
11
11
|
import { get_post_login_redirect } from "../../lib/services/post_verification_service.js";
|
|
12
|
+
import { get_oauth_config } from "../../lib/oauth_config.server.js";
|
|
12
13
|
// section: api_handler
|
|
13
14
|
/**
|
|
14
15
|
* Handles the OAuth callback after Google sign-in
|
|
@@ -62,10 +63,25 @@ export async function GET(request) {
|
|
|
62
63
|
});
|
|
63
64
|
// Get redirect URL based on user's scope/invitation status
|
|
64
65
|
const loginConfig = get_login_config();
|
|
65
|
-
const
|
|
66
|
+
const oauthConfig = get_oauth_config();
|
|
66
67
|
// Check if user needs onboarding (no scope, no invitation = create firm)
|
|
67
68
|
const hazoConnect = get_hazo_connect_instance();
|
|
68
|
-
const { redirect_url: determined_redirect, needs_onboarding } = await get_post_login_redirect(hazoConnect, user_id, email,
|
|
69
|
+
const { redirect_url: determined_redirect, needs_onboarding, invitation_check_skipped, invitation_table_error, } = await get_post_login_redirect(hazoConnect, user_id, email, {
|
|
70
|
+
default_redirect: oauthConfig.default_redirect || loginConfig.redirectRoute || "/",
|
|
71
|
+
create_firm_url: oauthConfig.create_firm_url,
|
|
72
|
+
skip_invitation_check: oauthConfig.skip_invitation_check,
|
|
73
|
+
no_scope_redirect: oauthConfig.no_scope_redirect,
|
|
74
|
+
});
|
|
75
|
+
// Log warning if invitation table is missing
|
|
76
|
+
if (invitation_table_error) {
|
|
77
|
+
logger.warn("invitation_table_missing", {
|
|
78
|
+
filename: get_filename(),
|
|
79
|
+
line_number: get_line_number(),
|
|
80
|
+
user_id,
|
|
81
|
+
email,
|
|
82
|
+
note: "hazo_invitations table does not exist - run migration or set skip_invitation_check=true in [hazo_auth__oauth]",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
69
85
|
logger.info("google_callback_post_login_redirect", {
|
|
70
86
|
filename: get_filename(),
|
|
71
87
|
line_number: get_line_number(),
|
|
@@ -73,6 +89,8 @@ export async function GET(request) {
|
|
|
73
89
|
email,
|
|
74
90
|
redirect_url: determined_redirect,
|
|
75
91
|
needs_onboarding,
|
|
92
|
+
invitation_check_skipped,
|
|
93
|
+
invitation_table_error,
|
|
76
94
|
});
|
|
77
95
|
// Create redirect response
|
|
78
96
|
const redirect_url = new URL(determined_redirect, request.url);
|
package/package.json
CHANGED