hazo_auth 7.0.2 → 8.0.1
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 +34 -0
- package/SETUP_CHECKLIST.md +31 -0
- package/cli-src/lib/auth/auth_types.ts +3 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +19 -0
- package/cli-src/lib/legal/legal_docs_config.server.ts +61 -0
- package/cli-src/lib/legal/legal_docs_reader.server.ts +36 -0
- package/cli-src/lib/legal/legal_docs_service.ts +196 -0
- package/cli-src/lib/legal/legal_docs_types.ts +31 -0
- package/cli-src/lib/services/registration_service.ts +16 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -0
- package/dist/components/layouts/index.d.ts +1 -0
- package/dist/components/layouts/index.d.ts.map +1 -1
- package/dist/components/layouts/index.js +2 -0
- package/dist/components/layouts/legal/index.d.ts +5 -0
- package/dist/components/layouts/legal/index.d.ts.map +1 -0
- package/dist/components/layouts/legal/index.js +4 -0
- package/dist/components/layouts/legal/legal_acceptance_gate.d.ts +7 -0
- package/dist/components/layouts/legal/legal_acceptance_gate.d.ts.map +1 -0
- package/dist/components/layouts/legal/legal_acceptance_gate.js +84 -0
- package/dist/components/layouts/legal/legal_doc_checkbox_list.d.ts +9 -0
- package/dist/components/layouts/legal/legal_doc_checkbox_list.d.ts.map +1 -0
- package/dist/components/layouts/legal/legal_doc_checkbox_list.js +11 -0
- package/dist/components/layouts/legal/legal_doc_combined_view.d.ts +9 -0
- package/dist/components/layouts/legal/legal_doc_combined_view.d.ts.map +1 -0
- package/dist/components/layouts/legal/legal_doc_combined_view.js +11 -0
- package/dist/components/layouts/legal/legal_doc_drawer.d.ts +8 -0
- package/dist/components/layouts/legal/legal_doc_drawer.d.ts.map +1 -0
- package/dist/components/layouts/legal/legal_doc_drawer.js +55 -0
- package/dist/components/layouts/register/hooks/use_register_form.d.ts +5 -1
- package/dist/components/layouts/register/hooks/use_register_form.d.ts.map +1 -1
- package/dist/components/layouts/register/hooks/use_register_form.js +25 -10
- package/dist/components/layouts/register/index.d.ts.map +1 -1
- package/dist/components/layouts/register/index.js +21 -1
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +45 -7
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/input-otp.d.ts +2 -2
- package/dist/components/ui/sheet.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.d.ts +2 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +0 -2
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +19 -0
- package/dist/lib/legal/legal_docs_config.server.d.ts +22 -0
- package/dist/lib/legal/legal_docs_config.server.d.ts.map +1 -0
- package/dist/lib/legal/legal_docs_config.server.js +52 -0
- package/dist/lib/legal/legal_docs_reader.server.d.ts +15 -0
- package/dist/lib/legal/legal_docs_reader.server.d.ts.map +1 -0
- package/dist/lib/legal/legal_docs_reader.server.js +24 -0
- package/dist/lib/legal/legal_docs_service.d.ts +49 -0
- package/dist/lib/legal/legal_docs_service.d.ts.map +1 -0
- package/dist/lib/legal/legal_docs_service.js +140 -0
- package/dist/lib/legal/legal_docs_types.d.ts +25 -0
- package/dist/lib/legal/legal_docs_types.d.ts.map +1 -0
- package/dist/lib/legal/legal_docs_types.js +2 -0
- package/dist/lib/services/registration_service.d.ts +5 -0
- package/dist/lib/services/registration_service.d.ts.map +1 -1
- package/dist/lib/services/registration_service.js +6 -0
- package/dist/page_components/index.d.ts +0 -5
- package/dist/page_components/index.d.ts.map +1 -1
- package/dist/page_components/index.js +0 -5
- package/dist/server/routes/index.d.ts +3 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +4 -0
- package/dist/server/routes/legal_docs_accept.d.ts +3 -0
- package/dist/server/routes/legal_docs_accept.d.ts.map +1 -0
- package/dist/server/routes/legal_docs_accept.js +43 -0
- package/dist/server/routes/legal_docs_get.d.ts +3 -0
- package/dist/server/routes/legal_docs_get.d.ts.map +1 -0
- package/dist/server/routes/legal_docs_get.js +49 -0
- package/dist/server/routes/legal_docs_publish.d.ts +3 -0
- package/dist/server/routes/legal_docs_publish.d.ts.map +1 -0
- package/dist/server/routes/legal_docs_publish.js +35 -0
- package/dist/server/routes/register.d.ts.map +1 -1
- package/dist/server/routes/register.js +26 -0
- package/dist/server/routes/user_management_users.d.ts +2 -2
- package/dist/server/routes/user_management_users.d.ts.map +1 -1
- package/dist/server/routes/user_management_users.js +46 -2
- package/dist/strings.d.ts +2 -0
- package/dist/strings.d.ts.map +1 -0
- package/dist/strings.js +3 -0
- package/package.json +5 -22
|
@@ -39,6 +39,24 @@ function parse_app_user_data(json_string) {
|
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse raw legal_acceptance field from DB to LegalAcceptanceMap
|
|
44
|
+
* @param raw - Raw value from database (string or object)
|
|
45
|
+
* @returns Parsed LegalAcceptanceMap or null
|
|
46
|
+
*/
|
|
47
|
+
function parse_legal_acceptance(raw) {
|
|
48
|
+
if (!raw)
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
52
|
+
if (typeof parsed !== 'object' || Array.isArray(parsed))
|
|
53
|
+
return null;
|
|
54
|
+
return parsed;
|
|
55
|
+
}
|
|
56
|
+
catch (_a) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
42
60
|
/**
|
|
43
61
|
* Gets client IP address from request
|
|
44
62
|
* @param request - NextRequest object
|
|
@@ -132,6 +150,7 @@ async function fetch_user_data_from_db(user_id) {
|
|
|
132
150
|
profile_picture_url: user_db.profile_picture_url || null,
|
|
133
151
|
managed_by_user_id: user_db.managed_by_user_id || null,
|
|
134
152
|
app_user_data: parse_app_user_data(user_db.app_user_data),
|
|
153
|
+
legal_acceptance: parse_legal_acceptance(user_db.legal_acceptance),
|
|
135
154
|
};
|
|
136
155
|
// v5.x: Fetch user's roles from hazo_user_scopes (scope-based role assignments)
|
|
137
156
|
// Each scope assignment has a role_id (string UUID)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import type { LegalDocsConfig } from './legal_docs_types';
|
|
3
|
+
/**
|
|
4
|
+
* Reads legal docs configuration from hazo_auth_config.ini.
|
|
5
|
+
* Returns an empty docs array if the section is absent (legal docs disabled).
|
|
6
|
+
*
|
|
7
|
+
* Expected INI shape:
|
|
8
|
+
* [hazo_auth__legal_docs]
|
|
9
|
+
* display_mode = separate ; or: combined
|
|
10
|
+
* doc_1_key = terms
|
|
11
|
+
* doc_1_title = Terms of Service
|
|
12
|
+
* doc_1_path = legal/terms.md
|
|
13
|
+
* doc_2_key = privacy
|
|
14
|
+
* doc_2_title = Privacy Policy
|
|
15
|
+
* doc_2_path = legal/privacy.md
|
|
16
|
+
*/
|
|
17
|
+
export declare function get_legal_docs_config(): LegalDocsConfig;
|
|
18
|
+
/**
|
|
19
|
+
* Call this in tests to clear the cache between runs.
|
|
20
|
+
*/
|
|
21
|
+
export declare function _reset_legal_docs_config_cache(): void;
|
|
22
|
+
//# sourceMappingURL=legal_docs_config.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legal_docs_config.server.d.ts","sourceRoot":"","sources":["../../../src/lib/legal/legal_docs_config.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAIrB,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAW1E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAsBvD;AAED;;GAEG;AACH,wBAAgB,8BAA8B,IAAI,IAAI,CAErD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// file_description: server-only helper to read legal docs configuration from hazo_auth_config.ini
|
|
2
|
+
// section: server-only-guard
|
|
3
|
+
import 'server-only';
|
|
4
|
+
// section: imports
|
|
5
|
+
import { read_config_section } from '../config/config_loader.server.js';
|
|
6
|
+
// section: constants
|
|
7
|
+
const SECTION_NAME = 'hazo_auth__legal_docs';
|
|
8
|
+
// section: cache
|
|
9
|
+
// Cached after first load — INI changes require server restart anyway
|
|
10
|
+
let _cached = null;
|
|
11
|
+
// section: exports
|
|
12
|
+
/**
|
|
13
|
+
* Reads legal docs configuration from hazo_auth_config.ini.
|
|
14
|
+
* Returns an empty docs array if the section is absent (legal docs disabled).
|
|
15
|
+
*
|
|
16
|
+
* Expected INI shape:
|
|
17
|
+
* [hazo_auth__legal_docs]
|
|
18
|
+
* display_mode = separate ; or: combined
|
|
19
|
+
* doc_1_key = terms
|
|
20
|
+
* doc_1_title = Terms of Service
|
|
21
|
+
* doc_1_path = legal/terms.md
|
|
22
|
+
* doc_2_key = privacy
|
|
23
|
+
* doc_2_title = Privacy Policy
|
|
24
|
+
* doc_2_path = legal/privacy.md
|
|
25
|
+
*/
|
|
26
|
+
export function get_legal_docs_config() {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
if (_cached)
|
|
29
|
+
return _cached;
|
|
30
|
+
const section = (_a = read_config_section(SECTION_NAME)) !== null && _a !== void 0 ? _a : {};
|
|
31
|
+
const docs = [];
|
|
32
|
+
let i = 1;
|
|
33
|
+
while (section[`doc_${i}_key`]) {
|
|
34
|
+
docs.push({
|
|
35
|
+
key: section[`doc_${i}_key`],
|
|
36
|
+
title: (_b = section[`doc_${i}_title`]) !== null && _b !== void 0 ? _b : section[`doc_${i}_key`],
|
|
37
|
+
path: section[`doc_${i}_path`],
|
|
38
|
+
});
|
|
39
|
+
i++;
|
|
40
|
+
}
|
|
41
|
+
_cached = {
|
|
42
|
+
docs,
|
|
43
|
+
display_mode: section['display_mode'] === 'combined' ? 'combined' : 'separate',
|
|
44
|
+
};
|
|
45
|
+
return _cached;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Call this in tests to clear the cache between runs.
|
|
49
|
+
*/
|
|
50
|
+
export function _reset_legal_docs_config_cache() {
|
|
51
|
+
_cached = null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
export interface ReadDocResult {
|
|
3
|
+
content: string;
|
|
4
|
+
hash: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Reads a legal document from the filesystem and returns its text content
|
|
8
|
+
* together with a deterministic SHA-256 hash of that content.
|
|
9
|
+
*
|
|
10
|
+
* @param doc_path - Absolute path, or a path relative to process.cwd().
|
|
11
|
+
* @returns { content, hash } where hash is formatted as "sha256:<hex>".
|
|
12
|
+
* @throws If the file cannot be read.
|
|
13
|
+
*/
|
|
14
|
+
export declare function read_legal_doc(doc_path: string): ReadDocResult;
|
|
15
|
+
//# sourceMappingURL=legal_docs_reader.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legal_docs_reader.server.d.ts","sourceRoot":"","sources":["../../../src/lib/legal/legal_docs_reader.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AASrB,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAS9D"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// file_description: server-only utility that reads a legal document from disk and returns its content + SHA-256 hash
|
|
2
|
+
// section: server-only-guard
|
|
3
|
+
import 'server-only';
|
|
4
|
+
// section: imports
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
// section: exports
|
|
9
|
+
/**
|
|
10
|
+
* Reads a legal document from the filesystem and returns its text content
|
|
11
|
+
* together with a deterministic SHA-256 hash of that content.
|
|
12
|
+
*
|
|
13
|
+
* @param doc_path - Absolute path, or a path relative to process.cwd().
|
|
14
|
+
* @returns { content, hash } where hash is formatted as "sha256:<hex>".
|
|
15
|
+
* @throws If the file cannot be read.
|
|
16
|
+
*/
|
|
17
|
+
export function read_legal_doc(doc_path) {
|
|
18
|
+
const abs_path = path.isAbsolute(doc_path)
|
|
19
|
+
? doc_path
|
|
20
|
+
: path.join(process.cwd(), doc_path);
|
|
21
|
+
const content = fs.readFileSync(abs_path, 'utf-8');
|
|
22
|
+
const hex = createHash('sha256').update(content).digest('hex');
|
|
23
|
+
return { content, hash: `sha256:${hex}` };
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write legal acceptance records. Call from:
|
|
3
|
+
* - registration_service (bundled with register POST, pre-session)
|
|
4
|
+
* - legal_docs_accept route (authenticated, post-login)
|
|
5
|
+
*
|
|
6
|
+
* Inserts one row per doc into hazo_legal_acceptances (audit history) and
|
|
7
|
+
* merges the result into the denormalised hazo_users.legal_acceptance JSONB
|
|
8
|
+
* column for fast "has this user accepted the current version?" queries.
|
|
9
|
+
*/
|
|
10
|
+
export declare function write_legal_acceptance(adapter: any, user_id: string, accepted: Record<string, {
|
|
11
|
+
hash: string;
|
|
12
|
+
}>, ip: string | null, user_agent: string | null): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Publish a doc version as the new required version (admin action).
|
|
15
|
+
* Upserts hazo_legal_doc_versions keyed on doc_key.
|
|
16
|
+
*/
|
|
17
|
+
export declare function publish_doc_version(adapter: any, doc_key: string, required_hash: string, published_by_user_id: string): Promise<{
|
|
18
|
+
published_at: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Return required version info keyed by doc_key.
|
|
22
|
+
*/
|
|
23
|
+
export declare function get_required_versions(adapter: any, doc_keys: string[]): Promise<Record<string, {
|
|
24
|
+
required_hash: string;
|
|
25
|
+
published_at: string;
|
|
26
|
+
}>>;
|
|
27
|
+
/**
|
|
28
|
+
* Return acceptance history for a user across all doc keys, newest first.
|
|
29
|
+
*/
|
|
30
|
+
export declare function get_user_acceptance_history(adapter: any, user_id: string): Promise<Array<{
|
|
31
|
+
doc_key: string;
|
|
32
|
+
doc_hash: string;
|
|
33
|
+
accepted_at: string;
|
|
34
|
+
ip: string | null;
|
|
35
|
+
user_agent: string | null;
|
|
36
|
+
}>>;
|
|
37
|
+
/**
|
|
38
|
+
* Count how many users have accepted the current required hash for a doc key.
|
|
39
|
+
* Returns { current, total } where current is users on the required_hash and
|
|
40
|
+
* total is all users in the system (including those with no legal_acceptance data).
|
|
41
|
+
*
|
|
42
|
+
* Note: This performs an in-process scan over all users. For large user bases
|
|
43
|
+
* consider a dedicated SQL query in a future optimisation.
|
|
44
|
+
*/
|
|
45
|
+
export declare function get_compliance_count(adapter: any, doc_key: string, required_hash: string): Promise<{
|
|
46
|
+
current: number;
|
|
47
|
+
total: number;
|
|
48
|
+
}>;
|
|
49
|
+
//# sourceMappingURL=legal_docs_service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legal_docs_service.d.ts","sourceRoot":"","sources":["../../../src/lib/legal/legal_docs_service.ts"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1C,EAAE,EAAE,MAAM,GAAG,IAAI,EACjB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,IAAI,CAAC,CAwCf;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,oBAAoB,EAAE,MAAM,GAC3B,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CA2BnC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,GAAG,EACZ,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAoB1E;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,KAAK,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC,CAAC,CAeF;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAkB7C"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// file_description: service functions for the legal document acceptance system
|
|
2
|
+
// section: imports
|
|
3
|
+
import { createCrudService } from 'hazo_connect/server';
|
|
4
|
+
// section: helpers
|
|
5
|
+
function generate_id() {
|
|
6
|
+
return crypto.randomUUID();
|
|
7
|
+
}
|
|
8
|
+
// section: exports
|
|
9
|
+
/**
|
|
10
|
+
* Write legal acceptance records. Call from:
|
|
11
|
+
* - registration_service (bundled with register POST, pre-session)
|
|
12
|
+
* - legal_docs_accept route (authenticated, post-login)
|
|
13
|
+
*
|
|
14
|
+
* Inserts one row per doc into hazo_legal_acceptances (audit history) and
|
|
15
|
+
* merges the result into the denormalised hazo_users.legal_acceptance JSONB
|
|
16
|
+
* column for fast "has this user accepted the current version?" queries.
|
|
17
|
+
*/
|
|
18
|
+
export async function write_legal_acceptance(adapter, user_id, accepted, ip, user_agent) {
|
|
19
|
+
var _a;
|
|
20
|
+
const now = new Date().toISOString();
|
|
21
|
+
// 1. Insert one history row per doc
|
|
22
|
+
const history_service = createCrudService(adapter, 'hazo_legal_acceptances');
|
|
23
|
+
for (const [doc_key, { hash }] of Object.entries(accepted)) {
|
|
24
|
+
await history_service.insert({
|
|
25
|
+
id: generate_id(),
|
|
26
|
+
user_id,
|
|
27
|
+
doc_key,
|
|
28
|
+
doc_hash: hash,
|
|
29
|
+
accepted_at: now,
|
|
30
|
+
ip,
|
|
31
|
+
user_agent,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// 2. Merge into denormalized JSONB on hazo_users
|
|
35
|
+
const users_service = createCrudService(adapter, 'hazo_users');
|
|
36
|
+
const rows = await users_service.findBy({ id: user_id });
|
|
37
|
+
const existing_raw = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.legal_acceptance;
|
|
38
|
+
let existing = {};
|
|
39
|
+
if (existing_raw) {
|
|
40
|
+
try {
|
|
41
|
+
existing = typeof existing_raw === 'string'
|
|
42
|
+
? JSON.parse(existing_raw)
|
|
43
|
+
: existing_raw;
|
|
44
|
+
}
|
|
45
|
+
catch ( /* corrupt — start fresh */_b) { /* corrupt — start fresh */ }
|
|
46
|
+
}
|
|
47
|
+
const updated = Object.assign({}, existing);
|
|
48
|
+
for (const [doc_key, { hash }] of Object.entries(accepted)) {
|
|
49
|
+
updated[doc_key] = { hash, accepted_at: now, ip, user_agent };
|
|
50
|
+
}
|
|
51
|
+
await users_service.updateById(user_id, {
|
|
52
|
+
legal_acceptance: JSON.stringify(updated),
|
|
53
|
+
changed_at: now,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Publish a doc version as the new required version (admin action).
|
|
58
|
+
* Upserts hazo_legal_doc_versions keyed on doc_key.
|
|
59
|
+
*/
|
|
60
|
+
export async function publish_doc_version(adapter, doc_key, required_hash, published_by_user_id) {
|
|
61
|
+
const now = new Date().toISOString();
|
|
62
|
+
// createCrudService with doc_key as primary key so updateById works correctly
|
|
63
|
+
const versions_service = createCrudService(adapter, 'hazo_legal_doc_versions', {
|
|
64
|
+
primaryKeys: ['doc_key'],
|
|
65
|
+
autoId: false,
|
|
66
|
+
});
|
|
67
|
+
const existing = await versions_service.findBy({ doc_key });
|
|
68
|
+
if (existing.length > 0) {
|
|
69
|
+
await versions_service.updateById(doc_key, {
|
|
70
|
+
required_hash,
|
|
71
|
+
published_at: now,
|
|
72
|
+
published_by_user_id,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
await versions_service.insert({
|
|
77
|
+
doc_key,
|
|
78
|
+
required_hash,
|
|
79
|
+
published_at: now,
|
|
80
|
+
published_by_user_id,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return { published_at: now };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Return required version info keyed by doc_key.
|
|
87
|
+
*/
|
|
88
|
+
export async function get_required_versions(adapter, doc_keys) {
|
|
89
|
+
if (doc_keys.length === 0)
|
|
90
|
+
return {};
|
|
91
|
+
const versions_service = createCrudService(adapter, 'hazo_legal_doc_versions', {
|
|
92
|
+
primaryKeys: ['doc_key'],
|
|
93
|
+
autoId: false,
|
|
94
|
+
});
|
|
95
|
+
const rows = await versions_service.list((qb) => qb.whereIn('doc_key', doc_keys).select(['doc_key', 'required_hash', 'published_at']));
|
|
96
|
+
const result = {};
|
|
97
|
+
for (const row of rows) {
|
|
98
|
+
result[String(row.doc_key)] = {
|
|
99
|
+
required_hash: String(row.required_hash),
|
|
100
|
+
published_at: String(row.published_at),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Return acceptance history for a user across all doc keys, newest first.
|
|
107
|
+
*/
|
|
108
|
+
export async function get_user_acceptance_history(adapter, user_id) {
|
|
109
|
+
const history_service = createCrudService(adapter, 'hazo_legal_acceptances');
|
|
110
|
+
return history_service.list((qb) => qb
|
|
111
|
+
.where('user_id', 'eq', user_id)
|
|
112
|
+
.select(['doc_key', 'doc_hash', 'accepted_at', 'ip', 'user_agent'])
|
|
113
|
+
.order('accepted_at', 'desc'));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Count how many users have accepted the current required hash for a doc key.
|
|
117
|
+
* Returns { current, total } where current is users on the required_hash and
|
|
118
|
+
* total is all users in the system (including those with no legal_acceptance data).
|
|
119
|
+
*
|
|
120
|
+
* Note: This performs an in-process scan over all users. For large user bases
|
|
121
|
+
* consider a dedicated SQL query in a future optimisation.
|
|
122
|
+
*/
|
|
123
|
+
export async function get_compliance_count(adapter, doc_key, required_hash) {
|
|
124
|
+
var _a, _b;
|
|
125
|
+
const users_service = createCrudService(adapter, 'hazo_users');
|
|
126
|
+
const all_users = await users_service.list((qb) => qb.select(['id', 'legal_acceptance']));
|
|
127
|
+
let current = 0;
|
|
128
|
+
for (const user of all_users) {
|
|
129
|
+
let map = {};
|
|
130
|
+
try {
|
|
131
|
+
map = typeof user.legal_acceptance === 'string'
|
|
132
|
+
? JSON.parse(user.legal_acceptance)
|
|
133
|
+
: ((_a = user.legal_acceptance) !== null && _a !== void 0 ? _a : {});
|
|
134
|
+
}
|
|
135
|
+
catch ( /* ignore corrupt rows */_c) { /* ignore corrupt rows */ }
|
|
136
|
+
if (((_b = map[doc_key]) === null || _b === void 0 ? void 0 : _b.hash) === required_hash)
|
|
137
|
+
current++;
|
|
138
|
+
}
|
|
139
|
+
return { current, total: all_users.length };
|
|
140
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface LegalDocConfig {
|
|
2
|
+
key: string;
|
|
3
|
+
title: string;
|
|
4
|
+
path: string;
|
|
5
|
+
}
|
|
6
|
+
export interface LegalDocsConfig {
|
|
7
|
+
docs: LegalDocConfig[];
|
|
8
|
+
display_mode: 'separate' | 'combined';
|
|
9
|
+
}
|
|
10
|
+
export interface LegalDoc {
|
|
11
|
+
key: string;
|
|
12
|
+
title: string;
|
|
13
|
+
content: string;
|
|
14
|
+
hash: string;
|
|
15
|
+
required_hash: string | null;
|
|
16
|
+
required_published_at: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface LegalAcceptanceRecord {
|
|
19
|
+
hash: string;
|
|
20
|
+
accepted_at: string;
|
|
21
|
+
ip: string | null;
|
|
22
|
+
user_agent: string | null;
|
|
23
|
+
}
|
|
24
|
+
export type LegalAcceptanceMap = Record<string, LegalAcceptanceRecord>;
|
|
25
|
+
//# sourceMappingURL=legal_docs_types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legal_docs_types.d.ts","sourceRoot":"","sources":["../../../src/lib/legal/legal_docs_types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,YAAY,EAAE,UAAU,GAAG,UAAU,CAAC;CACvC;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAGD,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC"}
|
|
@@ -4,6 +4,11 @@ export type RegistrationData = {
|
|
|
4
4
|
password: string;
|
|
5
5
|
name?: string;
|
|
6
6
|
url_on_logon?: string;
|
|
7
|
+
legal_accepted?: Record<string, {
|
|
8
|
+
hash: string;
|
|
9
|
+
}>;
|
|
10
|
+
ip?: string | null;
|
|
11
|
+
user_agent?: string | null;
|
|
7
12
|
};
|
|
8
13
|
export type RegistrationResult = {
|
|
9
14
|
success: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registration_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/registration_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"registration_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/registration_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAmBvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,kBAAkB,CAAC,CAmK7B"}
|
|
@@ -10,6 +10,7 @@ import { send_template_email } from "./email_service.js";
|
|
|
10
10
|
import { sanitize_error_for_user } from "../utils/error_sanitizer.js";
|
|
11
11
|
import { get_line_number } from "../utils/api_route_helpers.js";
|
|
12
12
|
import { is_user_types_enabled, get_default_user_type, } from "../user_types_config.server.js";
|
|
13
|
+
import { write_legal_acceptance } from "../legal/legal_docs_service.js";
|
|
13
14
|
// section: helpers
|
|
14
15
|
/**
|
|
15
16
|
* Registers a new user in the database using hazo_connect
|
|
@@ -18,6 +19,7 @@ import { is_user_types_enabled, get_default_user_type, } from "../user_types_con
|
|
|
18
19
|
* @returns Registration result with success status and user_id or error
|
|
19
20
|
*/
|
|
20
21
|
export async function register_user(adapter, data) {
|
|
22
|
+
var _a, _b;
|
|
21
23
|
try {
|
|
22
24
|
const { email, password, name, url_on_logon } = data;
|
|
23
25
|
// Create CRUD service for hazo_users table
|
|
@@ -130,6 +132,10 @@ export async function register_user(adapter, data) {
|
|
|
130
132
|
});
|
|
131
133
|
}
|
|
132
134
|
}
|
|
135
|
+
// Write legal acceptance records if provided
|
|
136
|
+
if (data.legal_accepted && Object.keys(data.legal_accepted).length > 0) {
|
|
137
|
+
await write_legal_acceptance(adapter, user_id, data.legal_accepted, (_a = data.ip) !== null && _a !== void 0 ? _a : null, (_b = data.user_agent) !== null && _b !== void 0 ? _b : null);
|
|
138
|
+
}
|
|
133
139
|
return {
|
|
134
140
|
success: true,
|
|
135
141
|
user_id,
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export { LoginPage, type LoginPageProps } from "./login.js";
|
|
2
|
-
export { RegisterPage, type RegisterPageProps } from "./register.js";
|
|
3
|
-
export { ForgotPasswordPage, type ForgotPasswordPageProps } from "./forgot_password.js";
|
|
4
|
-
export { ResetPasswordPage, type ResetPasswordPageProps } from "./reset_password.js";
|
|
5
|
-
export { VerifyEmailPage, type VerifyEmailPageProps } from "./verify_email.js";
|
|
6
1
|
export { MySettingsPage, type MySettingsPageProps } from "./my_settings.js";
|
|
7
2
|
export { CreateFirmPage, type CreateFirmPageProps } from "./create_firm.js";
|
|
8
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/page_components/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/page_components/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
// file_description: barrel export for zero-config page components
|
|
2
2
|
// These components can be used directly by consumers with sensible defaults
|
|
3
3
|
// section: page_exports
|
|
4
|
-
export { LoginPage } from "./login.js";
|
|
5
|
-
export { RegisterPage } from "./register.js";
|
|
6
|
-
export { ForgotPasswordPage } from "./forgot_password.js";
|
|
7
|
-
export { ResetPasswordPage } from "./reset_password.js";
|
|
8
|
-
export { VerifyEmailPage } from "./verify_email.js";
|
|
9
4
|
export { MySettingsPage } from "./my_settings.js";
|
|
10
5
|
export { CreateFirmPage } from "./create_firm.js";
|
|
@@ -34,6 +34,9 @@ export { POST as relationshipUpgradePOST } from "./relationship_upgrade.js";
|
|
|
34
34
|
export { POST as pinLoginPOST } from "./pin_login.js";
|
|
35
35
|
export { otpRequestPOST } from "./otp/request.js";
|
|
36
36
|
export { otpVerifyPOST } from "./otp/verify.js";
|
|
37
|
+
export { legalDocsGET } from './legal_docs_get.js';
|
|
38
|
+
export { legalDocsAcceptPOST } from './legal_docs_accept.js';
|
|
39
|
+
export { legalDocsPublishPOST } from './legal_docs_publish.js';
|
|
37
40
|
export { consentMeGET } from "./consent_me.js";
|
|
38
41
|
export { stringsDefaultsGET } from "./strings_defaults.js";
|
|
39
42
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/routes/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/routes/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,10 @@ export { POST as pinLoginPOST } from "./pin_login.js";
|
|
|
48
48
|
// OTP sign-in routes
|
|
49
49
|
export { otpRequestPOST } from "./otp/request.js";
|
|
50
50
|
export { otpVerifyPOST } from "./otp/verify.js";
|
|
51
|
+
// Legal docs routes
|
|
52
|
+
export { legalDocsGET } from './legal_docs_get.js';
|
|
53
|
+
export { legalDocsAcceptPOST } from './legal_docs_accept.js';
|
|
54
|
+
export { legalDocsPublishPOST } from './legal_docs_publish.js';
|
|
51
55
|
// Consent routes
|
|
52
56
|
export { consentMeGET } from "./consent_me.js";
|
|
53
57
|
// Strings routes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legal_docs_accept.d.ts","sourceRoot":"","sources":["../../../src/server/routes/legal_docs_accept.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA0CrF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// file_description: POST /api/hazo_auth/legal_docs/accept — records a user's acceptance of one or more legal docs
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import { hazo_get_auth } from '../../lib/auth/hazo_get_auth.server.js';
|
|
4
|
+
import { get_client_ip } from '../../lib/auth/hazo_get_auth.server.js';
|
|
5
|
+
import { get_legal_docs_config } from '../../lib/legal/legal_docs_config.server.js';
|
|
6
|
+
import { read_legal_doc } from '../../lib/legal/legal_docs_reader.server.js';
|
|
7
|
+
import { write_legal_acceptance } from '../../lib/legal/legal_docs_service.js';
|
|
8
|
+
import { get_hazo_connect_instance } from '../../lib/hazo_connect_instance.server.js';
|
|
9
|
+
import { create_app_logger } from '../../lib/app_logger.js';
|
|
10
|
+
export async function legalDocsAcceptPOST(request) {
|
|
11
|
+
const logger = create_app_logger();
|
|
12
|
+
try {
|
|
13
|
+
const auth = await hazo_get_auth(request);
|
|
14
|
+
if (!auth.user) {
|
|
15
|
+
return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 });
|
|
16
|
+
}
|
|
17
|
+
const body = await request.json().catch(() => null);
|
|
18
|
+
const accepted = body === null || body === void 0 ? void 0 : body.accepted;
|
|
19
|
+
if (!accepted || typeof accepted !== 'object' || Object.keys(accepted).length === 0) {
|
|
20
|
+
return NextResponse.json({ ok: false, error: 'accepted is required' }, { status: 400 });
|
|
21
|
+
}
|
|
22
|
+
const config = get_legal_docs_config();
|
|
23
|
+
for (const [doc_key, { hash }] of Object.entries(accepted)) {
|
|
24
|
+
const doc_config = config.docs.find(d => d.key === doc_key);
|
|
25
|
+
if (!doc_config) {
|
|
26
|
+
return NextResponse.json({ ok: false, error: `Unknown doc key: ${doc_key}` }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
const { hash: current_hash } = read_legal_doc(doc_config.path);
|
|
29
|
+
if (hash !== current_hash) {
|
|
30
|
+
return NextResponse.json({ ok: false, error: `Hash mismatch for "${doc_key}" — user may have seen a stale version` }, { status: 400 });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const ip = get_client_ip(request);
|
|
34
|
+
const user_agent = request.headers.get('user-agent');
|
|
35
|
+
const adapter = get_hazo_connect_instance();
|
|
36
|
+
await write_legal_acceptance(adapter, auth.user.id, accepted, ip, user_agent);
|
|
37
|
+
return NextResponse.json({ ok: true });
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
logger.error('legal_docs_accept: internal server error', { err });
|
|
41
|
+
return NextResponse.json({ ok: false, error: 'Internal server error' }, { status: 500 });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legal_docs_get.d.ts","sourceRoot":"","sources":["../../../src/server/routes/legal_docs_get.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOxD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA8C/E"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// file_description: GET /api/hazo_auth/legal_docs — returns configured legal docs with content and required version info
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import { get_legal_docs_config } from '../../lib/legal/legal_docs_config.server.js';
|
|
4
|
+
import { read_legal_doc } from '../../lib/legal/legal_docs_reader.server.js';
|
|
5
|
+
import { get_required_versions } from '../../lib/legal/legal_docs_service.js';
|
|
6
|
+
import { get_hazo_connect_instance } from '../../lib/hazo_connect_instance.server.js';
|
|
7
|
+
import { create_app_logger } from '../../lib/app_logger.js';
|
|
8
|
+
export async function legalDocsGET(_request) {
|
|
9
|
+
const logger = create_app_logger();
|
|
10
|
+
try {
|
|
11
|
+
const config = get_legal_docs_config();
|
|
12
|
+
if (config.docs.length === 0) {
|
|
13
|
+
return NextResponse.json({ ok: true, data: { display_mode: config.display_mode, docs: [] } });
|
|
14
|
+
}
|
|
15
|
+
const doc_results = config.docs.map((doc_config) => {
|
|
16
|
+
try {
|
|
17
|
+
const { content, hash } = read_legal_doc(doc_config.path);
|
|
18
|
+
return { ok: true, key: doc_config.key, title: doc_config.title, content, hash };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
logger.error('legal_docs_get: failed to read legal doc file', { err, key: doc_config.key, path: doc_config.path });
|
|
22
|
+
return { ok: false, key: doc_config.key, error: `File not found: ${doc_config.path}` };
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const failed = doc_results.find(d => !d.ok);
|
|
26
|
+
if (failed) {
|
|
27
|
+
return NextResponse.json({ ok: false, error: `Legal doc "${failed.key}" could not be read. Check hazo_auth_config.ini path.` }, { status: 500 });
|
|
28
|
+
}
|
|
29
|
+
const adapter = get_hazo_connect_instance();
|
|
30
|
+
const keys = config.docs.map(d => d.key);
|
|
31
|
+
const required_versions = await get_required_versions(adapter, keys);
|
|
32
|
+
const docs = doc_results.map((d) => {
|
|
33
|
+
var _a, _b, _c, _d;
|
|
34
|
+
return ({
|
|
35
|
+
key: d.key,
|
|
36
|
+
title: d.title,
|
|
37
|
+
content: d.content,
|
|
38
|
+
hash: d.hash,
|
|
39
|
+
required_hash: (_b = (_a = required_versions[d.key]) === null || _a === void 0 ? void 0 : _a.required_hash) !== null && _b !== void 0 ? _b : null,
|
|
40
|
+
required_published_at: (_d = (_c = required_versions[d.key]) === null || _c === void 0 ? void 0 : _c.published_at) !== null && _d !== void 0 ? _d : null,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
return NextResponse.json({ ok: true, data: { display_mode: config.display_mode, docs } });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
logger.error('legal_docs_get: internal server error', { err });
|
|
47
|
+
return NextResponse.json({ ok: false, error: 'Internal server error' }, { status: 500 });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legal_docs_publish.d.ts","sourceRoot":"","sources":["../../../src/server/routes/legal_docs_publish.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQxD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA+BtF"}
|