hazo_auth 9.1.1 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +124 -6
  2. package/SETUP_CHECKLIST.md +24 -16
  3. package/cli-src/cli/init_users.ts +40 -48
  4. package/cli-src/lib/auth/auth_types.ts +0 -2
  5. package/cli-src/lib/auth/hazo_get_auth.server.ts +31 -25
  6. package/cli-src/lib/auth/hazo_get_tenant_auth.server.ts +9 -13
  7. package/cli-src/lib/auth/nextauth_config.ts +41 -0
  8. package/cli-src/lib/auth/request_google_scopes.ts +23 -0
  9. package/cli-src/lib/constants.ts +2 -0
  10. package/cli-src/lib/profile_pic_menu_config.server.ts +4 -3
  11. package/cli-src/lib/schema/sqlite_schema.ts +16 -4
  12. package/cli-src/lib/scope_hierarchy_config.server.ts +1 -9
  13. package/cli-src/lib/services/google_token_service.ts +408 -0
  14. package/cli-src/lib/services/index.ts +1 -1
  15. package/cli-src/lib/services/invitation_service.ts +1 -1
  16. package/cli-src/lib/services/scope_service.ts +2 -76
  17. package/cli-src/lib/services/user_scope_service.ts +7 -61
  18. package/dist/cli/init_users.d.ts.map +1 -1
  19. package/dist/cli/init_users.js +42 -42
  20. package/dist/client.d.ts +2 -1
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +3 -1
  23. package/dist/components/layouts/google_token_test/index.d.ts +6 -0
  24. package/dist/components/layouts/google_token_test/index.d.ts.map +1 -0
  25. package/dist/components/layouts/google_token_test/index.js +74 -0
  26. package/dist/components/layouts/shared/components/profile_pic_menu.d.ts.map +1 -1
  27. package/dist/components/layouts/shared/components/profile_pic_menu.js +7 -1
  28. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  29. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -1
  33. package/dist/lib/auth/auth_types.d.ts +0 -2
  34. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  35. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  36. package/dist/lib/auth/hazo_get_auth.server.js +27 -19
  37. package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts.map +1 -1
  38. package/dist/lib/auth/hazo_get_tenant_auth.server.js +10 -10
  39. package/dist/lib/auth/nextauth_config.d.ts +2 -0
  40. package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
  41. package/dist/lib/auth/nextauth_config.js +39 -1
  42. package/dist/lib/auth/request_google_scopes.d.ts +10 -0
  43. package/dist/lib/auth/request_google_scopes.d.ts.map +1 -0
  44. package/dist/lib/auth/request_google_scopes.js +13 -0
  45. package/dist/lib/constants.d.ts +1 -0
  46. package/dist/lib/constants.d.ts.map +1 -1
  47. package/dist/lib/constants.js +1 -0
  48. package/dist/lib/profile_pic_menu_config.server.d.ts +2 -1
  49. package/dist/lib/profile_pic_menu_config.server.d.ts.map +1 -1
  50. package/dist/lib/profile_pic_menu_config.server.js +1 -1
  51. package/dist/lib/schema/sqlite_schema.d.ts +1 -1
  52. package/dist/lib/schema/sqlite_schema.d.ts.map +1 -1
  53. package/dist/lib/schema/sqlite_schema.js +16 -4
  54. package/dist/lib/scope_hierarchy_config.server.d.ts +0 -2
  55. package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -1
  56. package/dist/lib/scope_hierarchy_config.server.js +1 -3
  57. package/dist/lib/services/google_token_service.d.ts +48 -0
  58. package/dist/lib/services/google_token_service.d.ts.map +1 -0
  59. package/dist/lib/services/google_token_service.js +319 -0
  60. package/dist/lib/services/index.d.ts +1 -0
  61. package/dist/lib/services/index.d.ts.map +1 -1
  62. package/dist/lib/services/index.js +1 -0
  63. package/dist/lib/services/invitation_service.d.ts +1 -1
  64. package/dist/lib/services/invitation_service.js +1 -1
  65. package/dist/lib/services/scope_service.d.ts +1 -14
  66. package/dist/lib/services/scope_service.d.ts.map +1 -1
  67. package/dist/lib/services/scope_service.js +2 -67
  68. package/dist/lib/services/user_scope_service.d.ts +5 -12
  69. package/dist/lib/services/user_scope_service.d.ts.map +1 -1
  70. package/dist/lib/services/user_scope_service.js +8 -45
  71. package/dist/server/routes/google_token.d.ts +13 -0
  72. package/dist/server/routes/google_token.d.ts.map +1 -0
  73. package/dist/server/routes/google_token.js +66 -0
  74. package/dist/server/routes/index.d.ts +1 -0
  75. package/dist/server/routes/index.d.ts.map +1 -1
  76. package/dist/server/routes/index.js +2 -0
  77. package/dist/server/routes/invitations.d.ts +1 -1
  78. package/dist/server/routes/invitations.d.ts.map +1 -1
  79. package/dist/server/routes/invitations.js +12 -11
  80. package/dist/server/routes/user_management_users.d.ts +1 -1
  81. package/package.json +17 -13
@@ -16,6 +16,7 @@ import { is_hrbac_enabled, get_scope_hierarchy_config } from "../scope_hierarchy
16
16
  import { check_user_scope_access, get_user_scopes, } from "../services/user_scope_service.js";
17
17
  import { get_cookie_name, BASE_COOKIE_NAMES } from "../cookies_config.server.js";
18
18
  import { get_app_permission_descriptions } from "../app_permissions_config.server.js";
19
+ import { GLOBAL_ADMIN_PERMISSION } from "../constants.js";
19
20
  // section: helpers
20
21
  /**
21
22
  * Parse JSON string to object, returning null on failure
@@ -287,7 +288,6 @@ async function check_scope_access_internal(user_id, scope_id) {
287
288
  scope_access_via: {
288
289
  scope_id: result.access_via.scope_id,
289
290
  scope_name: result.access_via.scope_name,
290
- is_super_admin: result.is_super_admin,
291
291
  },
292
292
  user_scopes,
293
293
  };
@@ -445,25 +445,33 @@ export async function hazo_get_auth(request, options) {
445
445
  let scope_access_via;
446
446
  const hrbac_enabled = is_hrbac_enabled();
447
447
  if (hrbac_enabled && (options === null || options === void 0 ? void 0 : options.scope_id)) {
448
- const scope_result = await check_scope_access_internal(user.id, options.scope_id);
449
- scope_ok = scope_result.scope_ok;
450
- scope_access_via = scope_result.scope_access_via;
451
- // Log scope denial if permission logging is enabled
452
- if (!scope_ok && config.log_permission_denials) {
453
- const client_ip = get_client_ip(request);
454
- logger.warn("auth_utility_scope_access_denied", {
455
- filename: get_filename(),
456
- line_number: get_line_number(),
457
- user_id: user.id,
458
- scope_id: options.scope_id,
459
- user_scopes: scope_result.user_scopes,
460
- ip: client_ip,
461
- correlation_id: getCorrelationId(),
462
- });
448
+ // Global admin permission grants access to all scopes
449
+ const has_global_admin = permissions.includes(GLOBAL_ADMIN_PERMISSION);
450
+ if (has_global_admin) {
451
+ scope_ok = true;
452
+ scope_access_via = { scope_id: options.scope_id };
463
453
  }
464
- // Throw error if strict mode and scope access denied
465
- if (!scope_ok && options.strict) {
466
- throw new ScopeAccessError(options.scope_id, scope_result.user_scopes);
454
+ else {
455
+ const scope_result = await check_scope_access_internal(user.id, options.scope_id);
456
+ scope_ok = scope_result.scope_ok;
457
+ scope_access_via = scope_result.scope_access_via;
458
+ // Log scope denial if permission logging is enabled
459
+ if (!scope_ok && config.log_permission_denials) {
460
+ const client_ip = get_client_ip(request);
461
+ logger.warn("auth_utility_scope_access_denied", {
462
+ filename: get_filename(),
463
+ line_number: get_line_number(),
464
+ user_id: user.id,
465
+ scope_id: options.scope_id,
466
+ user_scopes: scope_result.user_scopes,
467
+ ip: client_ip,
468
+ correlation_id: getCorrelationId(),
469
+ });
470
+ }
471
+ // Throw error if strict mode and scope access denied
472
+ if (!scope_ok && options.strict) {
473
+ throw new ScopeAccessError(options.scope_id, scope_result.user_scopes);
474
+ }
467
475
  }
468
476
  }
469
477
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"hazo_get_tenant_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_tenant_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAGrB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO1C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,EAGzB,MAAM,cAAc,CAAC;AAqBtB;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,iBAAiB,GACzB,MAAM,GAAG,SAAS,CAYpB;AAiCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CA0F3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,wBAAwB,CAAC,CA0BnC"}
1
+ {"version":3,"file":"hazo_get_tenant_auth.server.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/hazo_get_tenant_auth.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAGrB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ1C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,EAGzB,MAAM,cAAc,CAAC;AAqBtB;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,iBAAiB,GACzB,MAAM,GAAG,SAAS,CAYpB;AA8BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAwF3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,wBAAwB,CAAC,CA0BnC"}
@@ -4,6 +4,7 @@ import "server-only";
4
4
  import { hazo_get_auth } from "./hazo_get_auth.server.js";
5
5
  import { get_auth_cache } from "./auth_cache.js";
6
6
  import { get_scope_by_id } from "../services/scope_service.js";
7
+ import { GLOBAL_ADMIN_PERMISSION } from "../constants.js";
7
8
  import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
8
9
  import { get_cookie_name } from "../cookies_config.server.js";
9
10
  import { get_auth_utility_config } from "../auth_utility_config.server.js";
@@ -39,19 +40,17 @@ export function extract_scope_id_from_request(request, options) {
39
40
  return cookie_value;
40
41
  }
41
42
  /**
42
- * Builds TenantOrganization from scope details and access info
43
+ * Builds TenantOrganization from scope details
43
44
  * @param scope_details - Full scope details from cache
44
- * @param is_super_admin - Whether user is accessing as super admin
45
45
  * @returns TenantOrganization object
46
46
  */
47
- function build_tenant_organization(scope_details, is_super_admin) {
47
+ function build_tenant_organization(scope_details) {
48
48
  return {
49
49
  id: scope_details.id,
50
50
  name: scope_details.name,
51
51
  slug: scope_details.slug,
52
52
  level: scope_details.level,
53
53
  role_id: scope_details.role_id,
54
- is_super_admin,
55
54
  branding: scope_details.logo_url || scope_details.primary_color
56
55
  ? {
57
56
  logo_url: scope_details.logo_url,
@@ -113,13 +112,15 @@ export async function hazo_get_tenant_auth(request, options = {}) {
113
112
  // Build organization info if scope access was successful
114
113
  let organization = null;
115
114
  if (scope_id && auth_result.scope_ok && auth_result.scope_access_via) {
116
- // Find the scope in user's scopes that matches the access_via scope
115
+ // Try to find the scope in user's cached scope assignments first.
116
+ // For global admins the scope may not be in their cache (they can access any scope),
117
+ // in which case we fall through to the permission-based fetch below.
117
118
  const access_scope = user_scopes.find((s) => { var _a; return s.id === ((_a = auth_result.scope_access_via) === null || _a === void 0 ? void 0 : _a.scope_id); });
118
119
  if (access_scope) {
119
- organization = build_tenant_organization(access_scope, auth_result.scope_access_via.is_super_admin || false);
120
+ organization = build_tenant_organization(access_scope);
120
121
  }
121
- else if (auth_result.scope_access_via.is_super_admin) {
122
- // Super admin accessing scope they're not assigned to - fetch scope details
122
+ else if (auth_result.permissions.includes(GLOBAL_ADMIN_PERMISSION)) {
123
+ // Global admin accessing a scope they aren't directly assigned to fetch scope details
123
124
  const hazoConnect = get_hazo_connect_instance();
124
125
  const scope_result = await get_scope_by_id(hazoConnect, scope_id);
125
126
  if (scope_result.success && scope_result.scope) {
@@ -128,8 +129,7 @@ export async function hazo_get_tenant_auth(request, options = {}) {
128
129
  name: scope_result.scope.name,
129
130
  slug: null, // Could fetch from scope if slug column exists
130
131
  level: scope_result.scope.level,
131
- role_id: "", // Super admin doesn't have a role in the scope
132
- is_super_admin: true,
132
+ role_id: "", // Global admin doesn't have a role assignment in the scope
133
133
  branding: scope_result.scope.logo_url
134
134
  ? {
135
135
  logo_url: scope_result.scope.logo_url,
@@ -12,6 +12,8 @@ export type NextAuthCallbackAccount = {
12
12
  access_token?: string;
13
13
  id_token?: string;
14
14
  expires_at?: number;
15
+ refresh_token?: string;
16
+ scope?: string;
15
17
  };
16
18
  export type NextAuthCallbackProfile = {
17
19
  sub?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"nextauth_config.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/nextauth_config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,WAAW,CAAC;AAatD,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,CAkOjD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAW7C"}
1
+ {"version":3,"file":"nextauth_config.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/nextauth_config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,WAAW,CAAC;AActD,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,CAwQjD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAW7C"}
@@ -7,6 +7,7 @@ import { get_oauth_config } from "../oauth_config.server.js";
7
7
  import { handle_google_oauth_login } from "../services/oauth_service.js";
8
8
  import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
9
9
  import { create_app_logger } from "../app_logger.js";
10
+ import { store_google_oauth_token, GoogleTokenStorageUnconfigured } from "../services/google_token_service.js";
10
11
  // section: config
11
12
  /**
12
13
  * Gets NextAuth.js configuration with enabled OAuth providers
@@ -29,6 +30,7 @@ export function get_nextauth_config() {
29
30
  prompt: "consent",
30
31
  access_type: "offline",
31
32
  response_type: "code",
33
+ include_granted_scopes: "true",
32
34
  },
33
35
  },
34
36
  }));
@@ -79,7 +81,7 @@ export function get_nextauth_config() {
79
81
  * Sign-in callback - handle user creation/linking for Google OAuth
80
82
  */
81
83
  async signIn({ account, profile, user, }) {
82
- var _a;
84
+ var _a, _b, _c;
83
85
  const logger = create_app_logger();
84
86
  if ((account === null || account === void 0 ? void 0 : account.provider) === "google" && profile) {
85
87
  try {
@@ -113,6 +115,42 @@ export function get_nextauth_config() {
113
115
  });
114
116
  // Store user_id in account for the JWT callback to pick up
115
117
  account.hazo_user_id = result.user_id;
118
+ // Capture refresh token when extra scopes were granted
119
+ const BASE_SCOPES = ["openid", "email", "profile"];
120
+ const granted_scopes = ((_b = account.scope) !== null && _b !== void 0 ? _b : "").split(" ").filter(Boolean);
121
+ const has_extra_scopes = granted_scopes.some(s => !BASE_SCOPES.includes(s));
122
+ if (account.refresh_token && has_extra_scopes) {
123
+ try {
124
+ await store_google_oauth_token({
125
+ user_id: result.user_id,
126
+ refresh_token: account.refresh_token,
127
+ access_token: account.access_token,
128
+ scopes: (_c = account.scope) !== null && _c !== void 0 ? _c : "",
129
+ expires_at: account.expires_at
130
+ ? new Date(account.expires_at * 1000).toISOString()
131
+ : undefined,
132
+ });
133
+ logger.info("nextauth_google_token_stored", {
134
+ user_id: result.user_id,
135
+ scopes: account.scope,
136
+ });
137
+ }
138
+ catch (tokenError) {
139
+ if (tokenError instanceof GoogleTokenStorageUnconfigured) {
140
+ logger.error("nextauth_google_token_storage_unconfigured", {
141
+ user_id: result.user_id,
142
+ error: tokenError.message,
143
+ });
144
+ return "/api/auth/error?error=GoogleTokenStorageUnconfigured";
145
+ }
146
+ const errorMsg = tokenError instanceof Error ? tokenError.message : String(tokenError);
147
+ logger.error("nextauth_google_token_store_failed", {
148
+ user_id: result.user_id,
149
+ error: errorMsg,
150
+ });
151
+ // Non-fatal: continue sign-in even if token storage fails
152
+ }
153
+ }
116
154
  return true;
117
155
  }
118
156
  catch (error) {
@@ -0,0 +1,10 @@
1
+ import { signIn } from "next-auth/react";
2
+ /**
3
+ * Triggers an incremental Google OAuth consent flow to grant additional scopes.
4
+ * Call from a client component when the user needs to grant extra Google API access
5
+ * (e.g. analytics, sheets). On completion, hazo_auth stores the refresh token.
6
+ */
7
+ export declare function requestGoogleScopes(scopes: string[], opts?: {
8
+ callbackUrl?: string;
9
+ }): ReturnType<typeof signIn>;
10
+ //# sourceMappingURL=request_google_scopes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request_google_scopes.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/request_google_scopes.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B,UAAU,CAAC,OAAO,MAAM,CAAC,CAS3B"}
@@ -0,0 +1,13 @@
1
+ // file_description: client helper to trigger incremental Google OAuth scope consent
2
+ "use client";
3
+ import { signIn } from "next-auth/react";
4
+ /**
5
+ * Triggers an incremental Google OAuth consent flow to grant additional scopes.
6
+ * Call from a client component when the user needs to grant extra Google API access
7
+ * (e.g. analytics, sheets). On completion, hazo_auth stores the refresh token.
8
+ */
9
+ export function requestGoogleScopes(scopes, opts) {
10
+ var _a;
11
+ const scope = Array.from(new Set(["openid", "email", "profile", ...scopes])).join(" ");
12
+ return signIn("google", { callbackUrl: (_a = opts === null || opts === void 0 ? void 0 : opts.callbackUrl) !== null && _a !== void 0 ? _a : "/" }, { scope, access_type: "offline", include_granted_scopes: "true", prompt: "consent" });
13
+ }
@@ -8,4 +8,5 @@ export declare const HAZO_AUTH_PERMISSIONS: {
8
8
  readonly ADMIN_TEST_ACCESS: "admin_test_access";
9
9
  };
10
10
  export declare const ALL_ADMIN_PERMISSIONS: string[];
11
+ export declare const GLOBAL_ADMIN_PERMISSION = "hazo_org_global_admin";
11
12
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB;;;;;;;;CAQxB,CAAC;AAEX,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAAyC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB;;;;;;;;CAQxB,CAAC;AAEX,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAAyC,CAAC;AAEpF,eAAO,MAAM,uBAAuB,0BAA0B,CAAC"}
@@ -11,3 +11,4 @@ export const HAZO_AUTH_PERMISSIONS = {
11
11
  ADMIN_TEST_ACCESS: "admin_test_access",
12
12
  };
13
13
  export const ALL_ADMIN_PERMISSIONS = Object.values(HAZO_AUTH_PERMISSIONS);
14
+ export const GLOBAL_ADMIN_PERMISSION = "hazo_org_global_admin";
@@ -1,10 +1,11 @@
1
1
  import "server-only";
2
- export type MenuItemType = "info" | "link" | "separator";
2
+ export type MenuItemType = "info" | "link" | "separator" | "action";
3
3
  export type ProfilePicMenuMenuItem = {
4
4
  type: MenuItemType;
5
5
  label?: string;
6
6
  value?: string;
7
7
  href?: string;
8
+ onSelect?: () => void;
8
9
  order: number;
9
10
  id: string;
10
11
  };
@@ -1 +1 @@
1
- {"version":3,"file":"profile_pic_menu_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/profile_pic_menu_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAQrB,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;AAEzD,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;CAC7C,CAAC;AA4EF;;;;GAIG;AACH,wBAAgB,2BAA2B,IAAI,oBAAoB,CA4BlE"}
1
+ {"version":3,"file":"profile_pic_menu_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/profile_pic_menu_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAQrB,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEpE,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;CAC7C,CAAC;AA4EF;;;;GAIG;AACH,wBAAgB,2BAA2B,IAAI,oBAAoB,CA4BlE"}
@@ -20,7 +20,7 @@ function parse_custom_menu_items(items_string) {
20
20
  }
21
21
  const type = parts[0];
22
22
  if (type !== "info" && type !== "link" && type !== "separator") {
23
- return; // Invalid type, skip
23
+ return; // Invalid type or action (action items carry callbacks, not expressible in INI)
24
24
  }
25
25
  if (type === "separator") {
26
26
  const order = parseInt(parts[1] || "1", 10);
@@ -1,2 +1,2 @@
1
- export declare const SQLITE_SCHEMA = "\n-- hazo_auth canonical SQLite schema\n-- This schema creates all tables required by hazo_auth\n\n-- Users table (from migration 011)\nCREATE TABLE IF NOT EXISTS hazo_users (\n id TEXT PRIMARY KEY,\n email_address TEXT NOT NULL UNIQUE,\n password_hash TEXT,\n name TEXT,\n email_verified BOOLEAN DEFAULT false,\n login_attempts INTEGER DEFAULT 0,\n last_logon TEXT,\n profile_picture_url TEXT,\n profile_source TEXT CHECK(profile_source IN ('gravatar', 'custom', 'predefined')),\n mfa_secret TEXT,\n url_on_logon TEXT,\n google_id TEXT UNIQUE,\n auth_providers TEXT DEFAULT 'email',\n user_type TEXT,\n app_user_data TEXT,\n status TEXT DEFAULT 'ACTIVE' CHECK(status IN ('PENDING', 'ACTIVE', 'BLOCKED')),\n managed_by_user_id TEXT REFERENCES hazo_users(id) ON DELETE SET NULL,\n pin_hash TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_users_email ON hazo_users(email_address);\nCREATE INDEX IF NOT EXISTS idx_hazo_users_google_id ON hazo_users(google_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_users_status ON hazo_users(status);\n\n-- Refresh tokens table\n-- Note: runtime (token_service.ts) writes token_hash (argon2-hashed value) on\n-- insert and verifies via argon2.verify(token_hash, plaintext_token) on read.\n-- The plaintext \"token\" column is retained nullable for legacy compatibility\n-- but new writes only set token_hash.\nCREATE TABLE IF NOT EXISTS hazo_refresh_tokens (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n token TEXT UNIQUE,\n token_hash TEXT,\n token_type TEXT DEFAULT 'refresh',\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_refresh_tokens_user ON hazo_refresh_tokens(user_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_refresh_tokens_token ON hazo_refresh_tokens(token);\nCREATE INDEX IF NOT EXISTS idx_hazo_refresh_tokens_token_hash ON hazo_refresh_tokens(token_hash);\n\n-- Roles table\nCREATE TABLE IF NOT EXISTS hazo_roles (\n id TEXT PRIMARY KEY,\n role_name TEXT NOT NULL UNIQUE,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Permissions table\nCREATE TABLE IF NOT EXISTS hazo_permissions (\n id TEXT PRIMARY KEY,\n permission_name TEXT NOT NULL UNIQUE,\n description TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Role-permission assignments (composite PK, no id column)\nCREATE TABLE IF NOT EXISTS hazo_role_permissions (\n role_id TEXT NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,\n permission_id TEXT NOT NULL REFERENCES hazo_permissions(id) ON DELETE CASCADE,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (role_id, permission_id)\n);\n\n-- Unified scopes table (hierarchical multi-tenancy)\nCREATE TABLE IF NOT EXISTS hazo_scopes (\n id TEXT PRIMARY KEY,\n parent_id TEXT REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n level TEXT NOT NULL,\n logo_url TEXT,\n primary_color TEXT,\n secondary_color TEXT,\n tagline TEXT,\n slug TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_scopes_parent ON hazo_scopes(parent_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);\nCREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);\n\n-- Super admin scope\nINSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)\nVALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', datetime('now'), datetime('now'));\n\n-- Default system scope (for non-multi-tenancy mode)\nINSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)\nVALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', datetime('now'), datetime('now'));\n\n-- User-scope assignments (membership model with scope-specific roles)\nCREATE TABLE IF NOT EXISTS hazo_user_scopes (\n user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n root_scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n role_id TEXT NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,\n status TEXT DEFAULT 'ACTIVE' CHECK (status IN ('INVITED', 'ACTIVE', 'SUSPENDED', 'DEPARTED')),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (user_id, scope_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_scope ON hazo_user_scopes(scope_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_root ON hazo_user_scopes(root_scope_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_role ON hazo_user_scopes(role_id);\n\n-- Invitations table\nCREATE TABLE IF NOT EXISTS hazo_invitations (\n id TEXT PRIMARY KEY,\n email_address TEXT NOT NULL,\n token TEXT NOT NULL UNIQUE,\n scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n root_scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n role_id TEXT NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,\n invited_by TEXT REFERENCES hazo_users(id) ON DELETE SET NULL,\n status TEXT NOT NULL DEFAULT 'PENDING' CHECK(status IN ('PENDING', 'ACCEPTED', 'EXPIRED', 'REVOKED')),\n expires_at TEXT NOT NULL,\n accepted_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_email ON hazo_invitations(email_address);\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_token ON hazo_invitations(token);\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_scope ON hazo_invitations(scope_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_status ON hazo_invitations(status);\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_expires ON hazo_invitations(expires_at);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_users_managed_by ON hazo_users(managed_by_user_id);\n\n-- Relationships table\nCREATE TABLE IF NOT EXISTS hazo_user_relationships (\n id TEXT PRIMARY KEY,\n parent_user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n child_user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n relationship_type TEXT NOT NULL DEFAULT 'parent',\n can_view_progress BOOLEAN DEFAULT true,\n can_edit_profile BOOLEAN DEFAULT true,\n can_delete BOOLEAN DEFAULT false,\n is_self BOOLEAN DEFAULT false,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE(parent_user_id, child_user_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_user_relationships_parent ON hazo_user_relationships(parent_user_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_user_relationships_child ON hazo_user_relationships(child_user_id);\n\n-- Firm admin role (for firm creators)\nINSERT OR IGNORE INTO hazo_roles (id, role_name, created_at, changed_at)\nVALUES (lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)),2) || '-' || substr('89ab',abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)),2) || '-' || hex(randomblob(6))), 'firm_admin', datetime('now'), datetime('now'));\n";
1
+ export declare const SQLITE_SCHEMA = "\n-- hazo_auth canonical SQLite schema\n-- This schema creates all tables required by hazo_auth\n\n-- Users table (from migration 011)\nCREATE TABLE IF NOT EXISTS hazo_users (\n id TEXT PRIMARY KEY,\n email_address TEXT NOT NULL UNIQUE,\n password_hash TEXT,\n name TEXT,\n email_verified BOOLEAN DEFAULT false,\n login_attempts INTEGER DEFAULT 0,\n last_logon TEXT,\n profile_picture_url TEXT,\n profile_source TEXT CHECK(profile_source IN ('gravatar', 'custom', 'predefined')),\n mfa_secret TEXT,\n url_on_logon TEXT,\n google_id TEXT UNIQUE,\n auth_providers TEXT DEFAULT 'email',\n user_type TEXT,\n app_user_data TEXT,\n status TEXT DEFAULT 'ACTIVE' CHECK(status IN ('PENDING', 'ACTIVE', 'BLOCKED')),\n managed_by_user_id TEXT REFERENCES hazo_users(id) ON DELETE SET NULL,\n pin_hash TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_users_email ON hazo_users(email_address);\nCREATE INDEX IF NOT EXISTS idx_hazo_users_google_id ON hazo_users(google_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_users_status ON hazo_users(status);\n\n-- Refresh tokens table\n-- Note: runtime (token_service.ts) writes token_hash (argon2-hashed value) on\n-- insert and verifies via argon2.verify(token_hash, plaintext_token) on read.\n-- The plaintext \"token\" column is retained nullable for legacy compatibility\n-- but new writes only set token_hash.\nCREATE TABLE IF NOT EXISTS hazo_refresh_tokens (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n token TEXT UNIQUE,\n token_hash TEXT,\n token_type TEXT DEFAULT 'refresh',\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_refresh_tokens_user ON hazo_refresh_tokens(user_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_refresh_tokens_token ON hazo_refresh_tokens(token);\nCREATE INDEX IF NOT EXISTS idx_hazo_refresh_tokens_token_hash ON hazo_refresh_tokens(token_hash);\n\n-- Roles table\nCREATE TABLE IF NOT EXISTS hazo_roles (\n id TEXT PRIMARY KEY,\n role_name TEXT NOT NULL UNIQUE,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Permissions table\nCREATE TABLE IF NOT EXISTS hazo_permissions (\n id TEXT PRIMARY KEY,\n permission_name TEXT NOT NULL UNIQUE,\n description TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Role-permission assignments (composite PK, no id column)\nCREATE TABLE IF NOT EXISTS hazo_role_permissions (\n role_id TEXT NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,\n permission_id TEXT NOT NULL REFERENCES hazo_permissions(id) ON DELETE CASCADE,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (role_id, permission_id)\n);\n\n-- Unified scopes table (hierarchical multi-tenancy)\nCREATE TABLE IF NOT EXISTS hazo_scopes (\n id TEXT PRIMARY KEY,\n parent_id TEXT REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n level TEXT NOT NULL,\n logo_url TEXT,\n primary_color TEXT,\n secondary_color TEXT,\n tagline TEXT,\n slug TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_scopes_parent ON hazo_scopes(parent_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);\nCREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);\n\n-- Default system scope (for non-multi-tenancy mode)\nINSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)\nVALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', datetime('now'), datetime('now'));\n\n-- User-scope assignments (membership model with scope-specific roles)\nCREATE TABLE IF NOT EXISTS hazo_user_scopes (\n user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n root_scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n role_id TEXT NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,\n status TEXT DEFAULT 'ACTIVE' CHECK (status IN ('INVITED', 'ACTIVE', 'SUSPENDED', 'DEPARTED')),\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (user_id, scope_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_scope ON hazo_user_scopes(scope_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_root ON hazo_user_scopes(root_scope_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_user_scopes_role ON hazo_user_scopes(role_id);\n\n-- Invitations table\nCREATE TABLE IF NOT EXISTS hazo_invitations (\n id TEXT PRIMARY KEY,\n email_address TEXT NOT NULL,\n token TEXT NOT NULL UNIQUE,\n scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n root_scope_id TEXT NOT NULL REFERENCES hazo_scopes(id) ON DELETE CASCADE,\n role_id TEXT NOT NULL REFERENCES hazo_roles(id) ON DELETE CASCADE,\n invited_by TEXT REFERENCES hazo_users(id) ON DELETE SET NULL,\n status TEXT NOT NULL DEFAULT 'PENDING' CHECK(status IN ('PENDING', 'ACCEPTED', 'EXPIRED', 'REVOKED')),\n expires_at TEXT NOT NULL,\n accepted_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_email ON hazo_invitations(email_address);\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_token ON hazo_invitations(token);\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_scope ON hazo_invitations(scope_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_status ON hazo_invitations(status);\nCREATE INDEX IF NOT EXISTS idx_hazo_invitations_expires ON hazo_invitations(expires_at);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_users_managed_by ON hazo_users(managed_by_user_id);\n\n-- Relationships table\nCREATE TABLE IF NOT EXISTS hazo_user_relationships (\n id TEXT PRIMARY KEY,\n parent_user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n child_user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n relationship_type TEXT NOT NULL DEFAULT 'parent',\n can_view_progress BOOLEAN DEFAULT true,\n can_edit_profile BOOLEAN DEFAULT true,\n can_delete BOOLEAN DEFAULT false,\n is_self BOOLEAN DEFAULT false,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE(parent_user_id, child_user_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_user_relationships_parent ON hazo_user_relationships(parent_user_id);\nCREATE INDEX IF NOT EXISTS idx_hazo_user_relationships_child ON hazo_user_relationships(child_user_id);\n\n-- Firm admin role (for firm creators)\nINSERT OR IGNORE INTO hazo_roles (id, role_name, created_at, changed_at)\nVALUES (lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)),2) || '-' || substr('89ab',abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)),2) || '-' || hex(randomblob(6))), 'firm_admin', datetime('now'), datetime('now'));\n\n-- Google OAuth tokens (encrypted refresh/access tokens for extra-scope API access)\nCREATE TABLE IF NOT EXISTS hazo_google_oauth_tokens (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,\n provider TEXT NOT NULL DEFAULT 'google',\n refresh_token_enc TEXT NOT NULL,\n access_token_enc TEXT,\n scopes TEXT NOT NULL DEFAULT '',\n expires_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n changed_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE(user_id, provider)\n);\n\nCREATE INDEX IF NOT EXISTS idx_hazo_google_oauth_tokens_user ON hazo_google_oauth_tokens(user_id);\n";
2
2
  //# sourceMappingURL=sqlite_schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite_schema.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/sqlite_schema.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,aAAa,uuOAmKzB,CAAC"}
1
+ {"version":3,"file":"sqlite_schema.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/sqlite_schema.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,aAAa,uoPA+KzB,CAAC"}
@@ -96,10 +96,6 @@ CREATE INDEX IF NOT EXISTS idx_hazo_scopes_parent ON hazo_scopes(parent_id);
96
96
  CREATE INDEX IF NOT EXISTS idx_hazo_scopes_level ON hazo_scopes(level);
97
97
  CREATE INDEX IF NOT EXISTS idx_hazo_scopes_slug ON hazo_scopes(slug);
98
98
 
99
- -- Super admin scope
100
- INSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
101
- VALUES ('00000000-0000-0000-0000-000000000000', NULL, 'Super Admin', 'system', datetime('now'), datetime('now'));
102
-
103
99
  -- Default system scope (for non-multi-tenancy mode)
104
100
  INSERT OR IGNORE INTO hazo_scopes (id, parent_id, name, level, created_at, changed_at)
105
101
  VALUES ('00000000-0000-0000-0000-000000000001', NULL, 'System', 'default', datetime('now'), datetime('now'));
@@ -164,4 +160,20 @@ CREATE INDEX IF NOT EXISTS idx_hazo_user_relationships_child ON hazo_user_relati
164
160
  -- Firm admin role (for firm creators)
165
161
  INSERT OR IGNORE INTO hazo_roles (id, role_name, created_at, changed_at)
166
162
  VALUES (lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)),2) || '-' || substr('89ab',abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)),2) || '-' || hex(randomblob(6))), 'firm_admin', datetime('now'), datetime('now'));
163
+
164
+ -- Google OAuth tokens (encrypted refresh/access tokens for extra-scope API access)
165
+ CREATE TABLE IF NOT EXISTS hazo_google_oauth_tokens (
166
+ id TEXT PRIMARY KEY,
167
+ user_id TEXT NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
168
+ provider TEXT NOT NULL DEFAULT 'google',
169
+ refresh_token_enc TEXT NOT NULL,
170
+ access_token_enc TEXT,
171
+ scopes TEXT NOT NULL DEFAULT '',
172
+ expires_at TEXT,
173
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
174
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
175
+ UNIQUE(user_id, provider)
176
+ );
177
+
178
+ CREATE INDEX IF NOT EXISTS idx_hazo_google_oauth_tokens_user ON hazo_google_oauth_tokens(user_id);
167
179
  `;
@@ -10,8 +10,6 @@ export type ScopeHierarchyConfig = {
10
10
  scope_cache_ttl_minutes: number;
11
11
  /** Maximum entries in scope cache (default: 5000) */
12
12
  scope_cache_max_entries: number;
13
- /** Super admin scope ID */
14
- super_admin_scope_id: string;
15
13
  /** Default system scope ID (for non-multi-tenancy mode) */
16
14
  default_system_scope_id: string;
17
15
  };
@@ -1 +1 @@
1
- {"version":3,"file":"scope_hierarchy_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/scope_hierarchy_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAYrB;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAC;IACtB,2DAA2D;IAC3D,uBAAuB,EAAE,MAAM,CAAC;IAChC,qDAAqD;IACrD,uBAAuB,EAAE,MAAM,CAAC;IAChC,2BAA2B;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAQF;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,oBAAoB,CAmCjE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
1
+ {"version":3,"file":"scope_hierarchy_config.server.d.ts","sourceRoot":"","sources":["../../src/lib/scope_hierarchy_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAYrB;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAC;IACtB,2DAA2D;IAC3D,uBAAuB,EAAE,MAAM,CAAC;IAChC,qDAAqD;IACrD,uBAAuB,EAAE,MAAM,CAAC;IAChC,2DAA2D;IAC3D,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAQF;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,oBAAoB,CA6BjE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
@@ -3,7 +3,7 @@
3
3
  import "server-only";
4
4
  // section: imports
5
5
  import { get_config_value, get_config_number, get_config_boolean, } from "./config/config_loader.server.js";
6
- import { SUPER_ADMIN_SCOPE_ID, DEFAULT_SYSTEM_SCOPE_ID } from "./services/scope_service.js";
6
+ import { DEFAULT_SYSTEM_SCOPE_ID } from "./services/scope_service.js";
7
7
  // section: constants
8
8
  const SECTION_NAME = "hazo_auth__scope_hierarchy";
9
9
  // section: helpers
@@ -19,13 +19,11 @@ export function get_scope_hierarchy_config() {
19
19
  const scope_cache_ttl_minutes = get_config_number(SECTION_NAME, "scope_cache_ttl_minutes", 15);
20
20
  const scope_cache_max_entries = get_config_number(SECTION_NAME, "scope_cache_max_entries", 5000);
21
21
  // Scope IDs (with defaults)
22
- const super_admin_scope_id = get_config_value(SECTION_NAME, "super_admin_scope_id", SUPER_ADMIN_SCOPE_ID);
23
22
  const default_system_scope_id = get_config_value(SECTION_NAME, "default_system_scope_id", DEFAULT_SYSTEM_SCOPE_ID);
24
23
  return {
25
24
  enable_hrbac,
26
25
  scope_cache_ttl_minutes,
27
26
  scope_cache_max_entries,
28
- super_admin_scope_id,
29
27
  default_system_scope_id,
30
28
  };
31
29
  }
@@ -0,0 +1,48 @@
1
+ export declare class GoogleTokenStorageUnconfigured extends Error {
2
+ constructor();
3
+ }
4
+ export type StoreGoogleOAuthTokenParams = {
5
+ user_id: string;
6
+ refresh_token: string;
7
+ access_token?: string;
8
+ scopes: string;
9
+ expires_at?: string;
10
+ };
11
+ export type GoogleTokenResult = {
12
+ ok: true;
13
+ access_token: string;
14
+ scopes: string;
15
+ } | {
16
+ ok: false;
17
+ error: "not_connected" | "reconsent_required" | "refresh_failed" | "unconfigured";
18
+ };
19
+ export type GoogleTokenStatus = {
20
+ connected: boolean;
21
+ scopes: string;
22
+ expires_at: string | null;
23
+ };
24
+ /**
25
+ * Stores (inserts or updates) Google OAuth tokens for a user, encrypting sensitive fields.
26
+ * Throws GoogleTokenStorageUnconfigured if hazo_secure/crypto is unavailable or keys are missing.
27
+ */
28
+ export declare function store_google_oauth_token(params: StoreGoogleOAuthTokenParams): Promise<void>;
29
+ /**
30
+ * Returns a valid access token for the user, refreshing via Google if needed.
31
+ * Returns a typed error result instead of throwing for expected failure modes.
32
+ */
33
+ export declare function getGoogleToken(userId: string, opts?: {
34
+ scopes?: string[];
35
+ }): Promise<GoogleTokenResult>;
36
+ /**
37
+ * Revokes the user's Google OAuth tokens — best-effort remote revocation, then deletes DB row.
38
+ */
39
+ export declare function revoke_google_oauth_token(userId: string): Promise<{
40
+ ok: boolean;
41
+ error?: string;
42
+ }>;
43
+ /**
44
+ * Returns connection status and scope information for a user's Google OAuth connection.
45
+ * Does not decrypt or refresh tokens.
46
+ */
47
+ export declare function get_google_token_status(userId: string): Promise<GoogleTokenStatus>;
48
+ //# sourceMappingURL=google_token_service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google_token_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/google_token_service.ts"],"names":[],"mappings":"AAUA,qBAAa,8BAA+B,SAAQ,KAAK;;CAOxD;AAID,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,eAAe,GAAG,oBAAoB,GAAG,gBAAgB,GAAG,cAAc,CAAA;CAAE,CAAC;AAErG,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AA6CF;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,IAAI,CAAC,CA0Ef;AAID;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CA6I5B;AAID;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6D1C;AAID;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAexF"}