hazo_auth 10.2.1 → 10.2.3
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 +23 -0
- package/cli-src/cli/demo.ts +72 -0
- package/cli-src/cli/index.ts +57 -0
- package/cli-src/lib/services/demo_account_service.ts +227 -0
- package/cli-src/lib/services/index.ts +1 -0
- package/dist/cli/demo.d.ts +12 -0
- package/dist/cli/demo.d.ts.map +1 -0
- package/dist/cli/demo.js +59 -0
- package/dist/cli/index.js +61 -0
- package/dist/components/layouts/my_settings/components/profile_picture_upload_tab.d.ts +7 -1
- package/dist/components/layouts/my_settings/components/profile_picture_upload_tab.d.ts.map +1 -1
- package/dist/components/layouts/my_settings/components/profile_picture_upload_tab.js +46 -15
- package/dist/lib/services/demo_account_service.d.ts +43 -0
- package/dist/lib/services/demo_account_service.d.ts.map +1 -0
- package/dist/lib/services/demo_account_service.js +171 -0
- package/dist/lib/services/index.d.ts +1 -0
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +1 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -381,6 +381,29 @@ npx hazo_auth validate # Check your setup and configuration
|
|
|
381
381
|
npx hazo_auth --help # Show all commands
|
|
382
382
|
```
|
|
383
383
|
|
|
384
|
+
#### Dev-only demo account seeder
|
|
385
|
+
|
|
386
|
+
Seed a **login-ready** test user (email pre-verified, assigned to the default
|
|
387
|
+
system scope with a role, no verification email sent) for local development.
|
|
388
|
+
This is **physically incapable of running in production**: it refuses unless the
|
|
389
|
+
resolved env (`HAZO_ENV ?? NODE_ENV`) is non-production **and**
|
|
390
|
+
`HAZO_AUTH_ALLOW_DEMO_SEED=true` is set.
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# Create (member by default; --admin grants the global-admin role)
|
|
394
|
+
HAZO_AUTH_ALLOW_DEMO_SEED=true npx hazo_auth demo-create --admin --email=demo@example.com
|
|
395
|
+
# or via npm script
|
|
396
|
+
HAZO_AUTH_ALLOW_DEMO_SEED=true npm run demo:create -- --admin
|
|
397
|
+
|
|
398
|
+
# Delete (by email, prefix, or all __demo_* users)
|
|
399
|
+
HAZO_AUTH_ALLOW_DEMO_SEED=true npx hazo_auth demo-delete --email=demo@example.com
|
|
400
|
+
HAZO_AUTH_ALLOW_DEMO_SEED=true npm run demo:delete -- --all
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Programmatic equivalents are exported from `hazo_auth/server-lib`:
|
|
404
|
+
`create_demo_user`, `delete_demo_users`, `assert_demo_seed_allowed`,
|
|
405
|
+
`DemoSeedNotAllowed`.
|
|
406
|
+
|
|
384
407
|
### Using Zero-Config Page Components (v2.0+)
|
|
385
408
|
|
|
386
409
|
**NEW in v2.0:** All pages are now React Server Components that initialize everything on the server. No configuration, no loading state, no hassle!
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// file_description: CLI command handlers for demo account seeding (dev-only)
|
|
2
|
+
|
|
3
|
+
// section: imports
|
|
4
|
+
import { get_hazo_connect_instance } from "../lib/hazo_connect_instance.server.js";
|
|
5
|
+
import {
|
|
6
|
+
create_demo_user,
|
|
7
|
+
delete_demo_users,
|
|
8
|
+
DemoSeedNotAllowed,
|
|
9
|
+
type CreateDemoUserOptions,
|
|
10
|
+
type DeleteDemoUsersOptions,
|
|
11
|
+
} from "../lib/services/demo_account_service.js";
|
|
12
|
+
|
|
13
|
+
// section: create_handler
|
|
14
|
+
/**
|
|
15
|
+
* Creates a login-ready demo user and prints a summary.
|
|
16
|
+
* Exits with code 1 if demo seeding is not allowed or an error occurs.
|
|
17
|
+
*/
|
|
18
|
+
export async function handle_demo_create(opts: CreateDemoUserOptions): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
const adapter = get_hazo_connect_instance();
|
|
21
|
+
const result = await create_demo_user(adapter, opts);
|
|
22
|
+
|
|
23
|
+
console.log("");
|
|
24
|
+
console.log("=".repeat(60));
|
|
25
|
+
console.log("DEMO USER CREATED");
|
|
26
|
+
console.log("=".repeat(60));
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(` Email: ${result.email}`);
|
|
29
|
+
console.log(` Password: ${result.password}`);
|
|
30
|
+
console.log(` User ID: ${result.user_id}`);
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log(" ✓ Email pre-verified — account is login-ready.");
|
|
33
|
+
console.log(" ✓ Assigned to the default system scope.");
|
|
34
|
+
if (opts.admin) {
|
|
35
|
+
console.log(" ✓ Admin account — global-admin role assigned.");
|
|
36
|
+
}
|
|
37
|
+
console.log("=".repeat(60));
|
|
38
|
+
console.log("");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof DemoSeedNotAllowed) {
|
|
41
|
+
console.error(
|
|
42
|
+
"Demo seeding is disabled. Set HAZO_AUTH_ALLOW_DEMO_SEED=true in a non-production env.",
|
|
43
|
+
);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// section: delete_handler
|
|
52
|
+
/**
|
|
53
|
+
* Deletes demo users matching the given email or prefix(es) and prints a summary.
|
|
54
|
+
* Exits with code 1 if demo seeding is not allowed or an error occurs.
|
|
55
|
+
*/
|
|
56
|
+
export async function handle_demo_delete(opts: DeleteDemoUsersOptions): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
const adapter = get_hazo_connect_instance();
|
|
59
|
+
const result = await delete_demo_users(adapter, opts);
|
|
60
|
+
|
|
61
|
+
console.log(`✓ Deleted ${result.deleted} demo user(s).`);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error instanceof DemoSeedNotAllowed) {
|
|
64
|
+
console.error(
|
|
65
|
+
"Demo seeding is disabled. Set HAZO_AUTH_ALLOW_DEMO_SEED=true in a non-production env.",
|
|
66
|
+
);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
package/cli-src/cli/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { handle_init } from "./init.js";
|
|
|
9
9
|
import { handle_init_users, show_init_users_help } from "./init_users.js";
|
|
10
10
|
import { handle_init_db } from "./init_db.js";
|
|
11
11
|
import { handle_init_permissions, show_init_permissions_help } from "./init_permissions.js";
|
|
12
|
+
import { handle_demo_create, handle_demo_delete } from "./demo.js";
|
|
12
13
|
|
|
13
14
|
// section: constants
|
|
14
15
|
const VERSION = "1.6.0";
|
|
@@ -23,6 +24,8 @@ Commands:
|
|
|
23
24
|
init-db Create SQLite database with hazo_auth schema (standalone)
|
|
24
25
|
init-permissions Create default permissions from config (no user required)
|
|
25
26
|
init-users Initialize permissions, roles, and super user from config
|
|
27
|
+
demo-create [DEV ONLY] Create a login-ready demo user (needs HAZO_AUTH_ALLOW_DEMO_SEED=true)
|
|
28
|
+
demo-delete [DEV ONLY] Delete demo users by email/prefix (needs HAZO_AUTH_ALLOW_DEMO_SEED=true)
|
|
26
29
|
validate Check your hazo_auth setup and configuration
|
|
27
30
|
generate-routes Generate API route files and pages in your project
|
|
28
31
|
schema Print the canonical SQLite schema SQL
|
|
@@ -41,6 +44,8 @@ Examples:
|
|
|
41
44
|
npx hazo_auth generate-routes --pages
|
|
42
45
|
npx hazo_auth generate-routes --all --dir=src/app
|
|
43
46
|
npx hazo_auth schema
|
|
47
|
+
HAZO_AUTH_ALLOW_DEMO_SEED=true npx hazo_auth demo-create --admin
|
|
48
|
+
HAZO_AUTH_ALLOW_DEMO_SEED=true npx hazo_auth demo-delete --all
|
|
44
49
|
|
|
45
50
|
Documentation:
|
|
46
51
|
https://github.com/your-repo/hazo_auth/blob/main/SETUP_CHECKLIST.md
|
|
@@ -216,6 +221,58 @@ Requires: better-sqlite3 (npm install better-sqlite3)
|
|
|
216
221
|
break;
|
|
217
222
|
}
|
|
218
223
|
|
|
224
|
+
case "demo-create": {
|
|
225
|
+
if (help) {
|
|
226
|
+
console.log(`
|
|
227
|
+
hazo_auth demo-create [DEV ONLY]
|
|
228
|
+
|
|
229
|
+
Create a login-ready demo user (email pre-verified, assigned to the default system scope).
|
|
230
|
+
Refuses to run unless the env is non-production AND HAZO_AUTH_ALLOW_DEMO_SEED=true.
|
|
231
|
+
|
|
232
|
+
Options:
|
|
233
|
+
--email=<addr> Email for the demo user (default: __demo_<timestamp>@hazo.test)
|
|
234
|
+
--password=<pw> Password (default: Demo_Pass123!)
|
|
235
|
+
--name=<name> Display name (default: Demo User)
|
|
236
|
+
--admin Grant the global-admin role instead of a plain member role
|
|
237
|
+
`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const demoCreateOpts: { email?: string; password?: string; name?: string; admin?: boolean } = {};
|
|
241
|
+
for (const arg of args) {
|
|
242
|
+
if (arg.startsWith("--email=")) demoCreateOpts.email = arg.replace("--email=", "");
|
|
243
|
+
else if (arg.startsWith("--password=")) demoCreateOpts.password = arg.replace("--password=", "");
|
|
244
|
+
else if (arg.startsWith("--name=")) demoCreateOpts.name = arg.replace("--name=", "");
|
|
245
|
+
else if (arg === "--admin") demoCreateOpts.admin = true;
|
|
246
|
+
}
|
|
247
|
+
await handle_demo_create(demoCreateOpts);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
case "demo-delete": {
|
|
252
|
+
if (help) {
|
|
253
|
+
console.log(`
|
|
254
|
+
hazo_auth demo-delete [DEV ONLY]
|
|
255
|
+
|
|
256
|
+
Delete demo users (and their scope rows) by email or prefix. Defaults to the __demo_ prefix.
|
|
257
|
+
Refuses to run unless the env is non-production AND HAZO_AUTH_ALLOW_DEMO_SEED=true.
|
|
258
|
+
|
|
259
|
+
Options:
|
|
260
|
+
--email=<addr> Delete only the user with this exact email
|
|
261
|
+
--prefix=<p> Delete users whose email starts with this prefix (default: __demo_)
|
|
262
|
+
--all Delete all __demo_-prefixed users (the default behaviour)
|
|
263
|
+
`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const demoDeleteOpts: { email?: string; prefixes?: string[] } = {};
|
|
267
|
+
for (const arg of args) {
|
|
268
|
+
if (arg.startsWith("--email=")) demoDeleteOpts.email = arg.replace("--email=", "");
|
|
269
|
+
else if (arg.startsWith("--prefix=")) demoDeleteOpts.prefixes = [arg.replace("--prefix=", "")];
|
|
270
|
+
}
|
|
271
|
+
// --all is a no-op / explicit default (prefixes stays undefined → service defaults to ['__demo_'])
|
|
272
|
+
await handle_demo_delete(demoDeleteOpts);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
|
|
219
276
|
case "validate":
|
|
220
277
|
if (help) {
|
|
221
278
|
console.log(`
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// file_description: dev-only service for seeding demo accounts in hazo_auth; physically blocked in production
|
|
2
|
+
|
|
3
|
+
// section: imports
|
|
4
|
+
import type { HazoConnectAdapter } from "hazo_connect";
|
|
5
|
+
import { createCrudService } from "hazo_connect/server";
|
|
6
|
+
import argon2 from "argon2";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
import { DEFAULT_SYSTEM_SCOPE_ID } from "./scope_service.js";
|
|
9
|
+
import { GLOBAL_ADMIN_PERMISSION } from "../constants.js";
|
|
10
|
+
|
|
11
|
+
// section: errors
|
|
12
|
+
|
|
13
|
+
export class DemoSeedNotAllowed extends Error {
|
|
14
|
+
constructor(message: string) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "DemoSeedNotAllowed";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// section: guards
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Throws DemoSeedNotAllowed if the current environment is production
|
|
24
|
+
* or if HAZO_AUTH_ALLOW_DEMO_SEED is not set to "true".
|
|
25
|
+
* This must be called at the top of every exported function in this module.
|
|
26
|
+
*/
|
|
27
|
+
export function assert_demo_seed_allowed(): void {
|
|
28
|
+
const env = process.env.HAZO_ENV ?? process.env.NODE_ENV ?? "development";
|
|
29
|
+
if (env === "production") {
|
|
30
|
+
throw new DemoSeedNotAllowed(
|
|
31
|
+
"Demo seeding is disabled. Requires a non-production env AND HAZO_AUTH_ALLOW_DEMO_SEED=true.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (process.env.HAZO_AUTH_ALLOW_DEMO_SEED !== "true") {
|
|
35
|
+
throw new DemoSeedNotAllowed(
|
|
36
|
+
"Demo seeding is disabled. Requires a non-production env AND HAZO_AUTH_ALLOW_DEMO_SEED=true.",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// section: types
|
|
42
|
+
|
|
43
|
+
export type CreateDemoUserOptions = {
|
|
44
|
+
email?: string; // default: `__demo_${Date.now()}@hazo.test`
|
|
45
|
+
password?: string; // default: 'Demo_Pass123!'
|
|
46
|
+
name?: string; // default: 'Demo User'
|
|
47
|
+
admin?: boolean; // default: false
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type CreateDemoUserResult = {
|
|
51
|
+
user_id: string;
|
|
52
|
+
email: string;
|
|
53
|
+
password: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type DeleteDemoUsersOptions = {
|
|
57
|
+
email?: string;
|
|
58
|
+
prefixes?: string[];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// section: helpers
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a demo user in the database, assigning them to the default system scope.
|
|
65
|
+
* Idempotent on email — if the user already exists their id is reused and the
|
|
66
|
+
* scope/role assignment is still ensured.
|
|
67
|
+
*
|
|
68
|
+
* REQUIRES: non-production env + HAZO_AUTH_ALLOW_DEMO_SEED=true
|
|
69
|
+
*/
|
|
70
|
+
export async function create_demo_user(
|
|
71
|
+
adapter: HazoConnectAdapter,
|
|
72
|
+
opts: CreateDemoUserOptions = {},
|
|
73
|
+
): Promise<CreateDemoUserResult> {
|
|
74
|
+
assert_demo_seed_allowed();
|
|
75
|
+
|
|
76
|
+
const email = opts.email ?? `__demo_${Date.now()}@hazo.test`;
|
|
77
|
+
const password = opts.password ?? "Demo_Pass123!";
|
|
78
|
+
const name = opts.name ?? "Demo User";
|
|
79
|
+
const admin = opts.admin ?? false;
|
|
80
|
+
|
|
81
|
+
const now = new Date().toISOString();
|
|
82
|
+
|
|
83
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
84
|
+
const roles_service = createCrudService(adapter, "hazo_roles");
|
|
85
|
+
const permissions_service = createCrudService(adapter, "hazo_permissions");
|
|
86
|
+
const role_permissions_service = createCrudService(
|
|
87
|
+
adapter,
|
|
88
|
+
"hazo_role_permissions",
|
|
89
|
+
{ primaryKeys: ["role_id", "permission_id"], autoId: false },
|
|
90
|
+
);
|
|
91
|
+
const user_scopes_service = createCrudService(adapter, "hazo_user_scopes", {
|
|
92
|
+
primaryKeys: ["user_id", "scope_id"],
|
|
93
|
+
autoId: false,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Idempotent: reuse existing user if email already registered
|
|
97
|
+
let user_id: string;
|
|
98
|
+
const existing_users = await users_service.findBy({ email_address: email });
|
|
99
|
+
if (Array.isArray(existing_users) && existing_users.length > 0) {
|
|
100
|
+
user_id = existing_users[0].id as string;
|
|
101
|
+
} else {
|
|
102
|
+
const password_hash = await argon2.hash(password);
|
|
103
|
+
user_id = randomUUID();
|
|
104
|
+
|
|
105
|
+
await users_service.insert({
|
|
106
|
+
id: user_id,
|
|
107
|
+
email_address: email,
|
|
108
|
+
password_hash,
|
|
109
|
+
email_verified: true,
|
|
110
|
+
status: "ACTIVE",
|
|
111
|
+
login_attempts: 0,
|
|
112
|
+
auth_providers: "email",
|
|
113
|
+
name,
|
|
114
|
+
created_at: now,
|
|
115
|
+
changed_at: now,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Ensure role exists
|
|
120
|
+
const role_name = admin ? "default_super_user_role" : "demo_member_role";
|
|
121
|
+
const existing_roles = await roles_service.findBy({ role_name });
|
|
122
|
+
let role_id: string;
|
|
123
|
+
if (Array.isArray(existing_roles) && existing_roles.length > 0) {
|
|
124
|
+
role_id = existing_roles[0].id as string;
|
|
125
|
+
} else {
|
|
126
|
+
const new_role = await roles_service.insert({ role_name, created_at: now, changed_at: now });
|
|
127
|
+
role_id = Array.isArray(new_role)
|
|
128
|
+
? (new_role[0] as { id: string }).id
|
|
129
|
+
: (new_role as { id: string }).id;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Admin users: ensure GLOBAL_ADMIN_PERMISSION is in the catalog and linked to the role
|
|
133
|
+
if (admin) {
|
|
134
|
+
// Ensure permission exists
|
|
135
|
+
const existing_perms = await permissions_service.findBy({
|
|
136
|
+
permission_name: GLOBAL_ADMIN_PERMISSION,
|
|
137
|
+
});
|
|
138
|
+
let permission_id: string;
|
|
139
|
+
if (Array.isArray(existing_perms) && existing_perms.length > 0) {
|
|
140
|
+
permission_id = existing_perms[0].id as string;
|
|
141
|
+
} else {
|
|
142
|
+
const new_perm = await permissions_service.insert({
|
|
143
|
+
permission_name: GLOBAL_ADMIN_PERMISSION,
|
|
144
|
+
description: "Global admin — access to all scopes and operations",
|
|
145
|
+
created_at: now,
|
|
146
|
+
changed_at: now,
|
|
147
|
+
});
|
|
148
|
+
permission_id = Array.isArray(new_perm)
|
|
149
|
+
? (new_perm[0] as { id: string }).id
|
|
150
|
+
: (new_perm as { id: string }).id;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Ensure role→permission link
|
|
154
|
+
const existing_rp = await role_permissions_service.findBy({ role_id, permission_id });
|
|
155
|
+
if (!Array.isArray(existing_rp) || existing_rp.length === 0) {
|
|
156
|
+
await role_permissions_service.insert({ role_id, permission_id });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Ensure user→scope assignment
|
|
161
|
+
const existing_user_scopes = await user_scopes_service.findBy({
|
|
162
|
+
user_id,
|
|
163
|
+
scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
164
|
+
});
|
|
165
|
+
if (!Array.isArray(existing_user_scopes) || existing_user_scopes.length === 0) {
|
|
166
|
+
await user_scopes_service.insert({
|
|
167
|
+
user_id,
|
|
168
|
+
scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
169
|
+
root_scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
170
|
+
role_id,
|
|
171
|
+
created_at: now,
|
|
172
|
+
changed_at: now,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { user_id, email, password };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Deletes demo users (and their scope rows) whose email matches the given
|
|
181
|
+
* address or any of the given prefixes. Defaults to the "__demo_" prefix.
|
|
182
|
+
*
|
|
183
|
+
* REQUIRES: non-production env + HAZO_AUTH_ALLOW_DEMO_SEED=true
|
|
184
|
+
*/
|
|
185
|
+
export async function delete_demo_users(
|
|
186
|
+
adapter: HazoConnectAdapter,
|
|
187
|
+
opts: DeleteDemoUsersOptions = {},
|
|
188
|
+
): Promise<{ deleted: number }> {
|
|
189
|
+
assert_demo_seed_allowed();
|
|
190
|
+
|
|
191
|
+
const prefixes = opts.prefixes ?? ["__demo_"];
|
|
192
|
+
|
|
193
|
+
const users = createCrudService<{ id: string; email_address: string }>(
|
|
194
|
+
adapter,
|
|
195
|
+
"hazo_users",
|
|
196
|
+
);
|
|
197
|
+
const user_scopes_service = createCrudService(adapter, "hazo_user_scopes", {
|
|
198
|
+
primaryKeys: ["user_id", "scope_id"],
|
|
199
|
+
autoId: false,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const all = await users.list((qb) => qb.limit(500));
|
|
203
|
+
const targets = all.filter((u) => {
|
|
204
|
+
if (opts.email && u.email_address === opts.email) return true;
|
|
205
|
+
return prefixes.some(
|
|
206
|
+
(p) => typeof u.email_address === "string" && u.email_address.startsWith(p),
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
let deleted = 0;
|
|
211
|
+
for (const u of targets) {
|
|
212
|
+
const uid = u.id;
|
|
213
|
+
try {
|
|
214
|
+
// Best-effort: delete this user's scope rows. hazo_user_scopes has a
|
|
215
|
+
// composite PK (no `id` column); deleteById on a multi-key table falls
|
|
216
|
+
// back to the first primary key (user_id), so this removes all of the
|
|
217
|
+
// user's scope rows in one call.
|
|
218
|
+
await user_scopes_service.deleteById(uid).catch(() => {});
|
|
219
|
+
await users.deleteById(uid);
|
|
220
|
+
deleted += 1;
|
|
221
|
+
} catch {
|
|
222
|
+
// best-effort — skip users that can't be deleted
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { deleted };
|
|
227
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type CreateDemoUserOptions, type DeleteDemoUsersOptions } from "../lib/services/demo_account_service.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a login-ready demo user and prints a summary.
|
|
4
|
+
* Exits with code 1 if demo seeding is not allowed or an error occurs.
|
|
5
|
+
*/
|
|
6
|
+
export declare function handle_demo_create(opts: CreateDemoUserOptions): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Deletes demo users matching the given email or prefix(es) and prints a summary.
|
|
9
|
+
* Exits with code 1 if demo seeding is not allowed or an error occurs.
|
|
10
|
+
*/
|
|
11
|
+
export declare function handle_demo_delete(opts: DeleteDemoUsersOptions): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=demo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/cli/demo.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,yCAAyC,CAAC;AAGjD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BnF;AAGD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBpF"}
|
package/dist/cli/demo.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// file_description: CLI command handlers for demo account seeding (dev-only)
|
|
2
|
+
// section: imports
|
|
3
|
+
import { get_hazo_connect_instance } from "../lib/hazo_connect_instance.server.js";
|
|
4
|
+
import { create_demo_user, delete_demo_users, DemoSeedNotAllowed, } from "../lib/services/demo_account_service.js";
|
|
5
|
+
// section: create_handler
|
|
6
|
+
/**
|
|
7
|
+
* Creates a login-ready demo user and prints a summary.
|
|
8
|
+
* Exits with code 1 if demo seeding is not allowed or an error occurs.
|
|
9
|
+
*/
|
|
10
|
+
export async function handle_demo_create(opts) {
|
|
11
|
+
try {
|
|
12
|
+
const adapter = get_hazo_connect_instance();
|
|
13
|
+
const result = await create_demo_user(adapter, opts);
|
|
14
|
+
console.log("");
|
|
15
|
+
console.log("=".repeat(60));
|
|
16
|
+
console.log("DEMO USER CREATED");
|
|
17
|
+
console.log("=".repeat(60));
|
|
18
|
+
console.log("");
|
|
19
|
+
console.log(` Email: ${result.email}`);
|
|
20
|
+
console.log(` Password: ${result.password}`);
|
|
21
|
+
console.log(` User ID: ${result.user_id}`);
|
|
22
|
+
console.log("");
|
|
23
|
+
console.log(" ✓ Email pre-verified — account is login-ready.");
|
|
24
|
+
console.log(" ✓ Assigned to the default system scope.");
|
|
25
|
+
if (opts.admin) {
|
|
26
|
+
console.log(" ✓ Admin account — global-admin role assigned.");
|
|
27
|
+
}
|
|
28
|
+
console.log("=".repeat(60));
|
|
29
|
+
console.log("");
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof DemoSeedNotAllowed) {
|
|
33
|
+
console.error("Demo seeding is disabled. Set HAZO_AUTH_ALLOW_DEMO_SEED=true in a non-production env.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// section: delete_handler
|
|
41
|
+
/**
|
|
42
|
+
* Deletes demo users matching the given email or prefix(es) and prints a summary.
|
|
43
|
+
* Exits with code 1 if demo seeding is not allowed or an error occurs.
|
|
44
|
+
*/
|
|
45
|
+
export async function handle_demo_delete(opts) {
|
|
46
|
+
try {
|
|
47
|
+
const adapter = get_hazo_connect_instance();
|
|
48
|
+
const result = await delete_demo_users(adapter, opts);
|
|
49
|
+
console.log(`✓ Deleted ${result.deleted} demo user(s).`);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof DemoSeedNotAllowed) {
|
|
53
|
+
console.error("Demo seeding is disabled. Set HAZO_AUTH_ALLOW_DEMO_SEED=true in a non-production env.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { handle_init } from "./init.js";
|
|
|
8
8
|
import { handle_init_users, show_init_users_help } from "./init_users.js";
|
|
9
9
|
import { handle_init_db } from "./init_db.js";
|
|
10
10
|
import { handle_init_permissions, show_init_permissions_help } from "./init_permissions.js";
|
|
11
|
+
import { handle_demo_create, handle_demo_delete } from "./demo.js";
|
|
11
12
|
// section: constants
|
|
12
13
|
const VERSION = "1.6.0";
|
|
13
14
|
const HELP_TEXT = `
|
|
@@ -20,6 +21,8 @@ Commands:
|
|
|
20
21
|
init-db Create SQLite database with hazo_auth schema (standalone)
|
|
21
22
|
init-permissions Create default permissions from config (no user required)
|
|
22
23
|
init-users Initialize permissions, roles, and super user from config
|
|
24
|
+
demo-create [DEV ONLY] Create a login-ready demo user (needs HAZO_AUTH_ALLOW_DEMO_SEED=true)
|
|
25
|
+
demo-delete [DEV ONLY] Delete demo users by email/prefix (needs HAZO_AUTH_ALLOW_DEMO_SEED=true)
|
|
23
26
|
validate Check your hazo_auth setup and configuration
|
|
24
27
|
generate-routes Generate API route files and pages in your project
|
|
25
28
|
schema Print the canonical SQLite schema SQL
|
|
@@ -38,6 +41,8 @@ Examples:
|
|
|
38
41
|
npx hazo_auth generate-routes --pages
|
|
39
42
|
npx hazo_auth generate-routes --all --dir=src/app
|
|
40
43
|
npx hazo_auth schema
|
|
44
|
+
HAZO_AUTH_ALLOW_DEMO_SEED=true npx hazo_auth demo-create --admin
|
|
45
|
+
HAZO_AUTH_ALLOW_DEMO_SEED=true npx hazo_auth demo-delete --all
|
|
41
46
|
|
|
42
47
|
Documentation:
|
|
43
48
|
https://github.com/your-repo/hazo_auth/blob/main/SETUP_CHECKLIST.md
|
|
@@ -200,6 +205,62 @@ Requires: better-sqlite3 (npm install better-sqlite3)
|
|
|
200
205
|
await handle_init_users({ email });
|
|
201
206
|
break;
|
|
202
207
|
}
|
|
208
|
+
case "demo-create": {
|
|
209
|
+
if (help) {
|
|
210
|
+
console.log(`
|
|
211
|
+
hazo_auth demo-create [DEV ONLY]
|
|
212
|
+
|
|
213
|
+
Create a login-ready demo user (email pre-verified, assigned to the default system scope).
|
|
214
|
+
Refuses to run unless the env is non-production AND HAZO_AUTH_ALLOW_DEMO_SEED=true.
|
|
215
|
+
|
|
216
|
+
Options:
|
|
217
|
+
--email=<addr> Email for the demo user (default: __demo_<timestamp>@hazo.test)
|
|
218
|
+
--password=<pw> Password (default: Demo_Pass123!)
|
|
219
|
+
--name=<name> Display name (default: Demo User)
|
|
220
|
+
--admin Grant the global-admin role instead of a plain member role
|
|
221
|
+
`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const demoCreateOpts = {};
|
|
225
|
+
for (const arg of args) {
|
|
226
|
+
if (arg.startsWith("--email="))
|
|
227
|
+
demoCreateOpts.email = arg.replace("--email=", "");
|
|
228
|
+
else if (arg.startsWith("--password="))
|
|
229
|
+
demoCreateOpts.password = arg.replace("--password=", "");
|
|
230
|
+
else if (arg.startsWith("--name="))
|
|
231
|
+
demoCreateOpts.name = arg.replace("--name=", "");
|
|
232
|
+
else if (arg === "--admin")
|
|
233
|
+
demoCreateOpts.admin = true;
|
|
234
|
+
}
|
|
235
|
+
await handle_demo_create(demoCreateOpts);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "demo-delete": {
|
|
239
|
+
if (help) {
|
|
240
|
+
console.log(`
|
|
241
|
+
hazo_auth demo-delete [DEV ONLY]
|
|
242
|
+
|
|
243
|
+
Delete demo users (and their scope rows) by email or prefix. Defaults to the __demo_ prefix.
|
|
244
|
+
Refuses to run unless the env is non-production AND HAZO_AUTH_ALLOW_DEMO_SEED=true.
|
|
245
|
+
|
|
246
|
+
Options:
|
|
247
|
+
--email=<addr> Delete only the user with this exact email
|
|
248
|
+
--prefix=<p> Delete users whose email starts with this prefix (default: __demo_)
|
|
249
|
+
--all Delete all __demo_-prefixed users (the default behaviour)
|
|
250
|
+
`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const demoDeleteOpts = {};
|
|
254
|
+
for (const arg of args) {
|
|
255
|
+
if (arg.startsWith("--email="))
|
|
256
|
+
demoDeleteOpts.email = arg.replace("--email=", "");
|
|
257
|
+
else if (arg.startsWith("--prefix="))
|
|
258
|
+
demoDeleteOpts.prefixes = [arg.replace("--prefix=", "")];
|
|
259
|
+
}
|
|
260
|
+
// --all is a no-op / explicit default (prefixes stays undefined → service defaults to ['__demo_'])
|
|
261
|
+
await handle_demo_delete(demoDeleteOpts);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
203
264
|
case "validate":
|
|
204
265
|
if (help) {
|
|
205
266
|
console.log(`
|
|
@@ -10,14 +10,20 @@ export type ProfilePictureUploadTabProps = {
|
|
|
10
10
|
imageCompressionMaxDimension?: number;
|
|
11
11
|
uploadFileHardLimitBytes?: number;
|
|
12
12
|
allowedImageMimeTypes?: string[];
|
|
13
|
+
cropTitle?: string;
|
|
14
|
+
cropConfirmLabel?: string;
|
|
15
|
+
cropCancelLabel?: string;
|
|
16
|
+
cropZoomLabel?: string;
|
|
13
17
|
};
|
|
14
18
|
/**
|
|
15
19
|
* Upload tab component for profile picture dialog
|
|
16
20
|
* Two columns: left = dropzone, right = preview
|
|
17
21
|
* Uses browser-image-compression for client-side compression
|
|
22
|
+
* Routes decodable images through HazoUiImageCropperDialog (round crop, 512² WebP)
|
|
23
|
+
* Falls back to direct upload for HEIC and other browser-undecodable formats
|
|
18
24
|
* @param props - Component props including upload state, file handler, and configuration
|
|
19
25
|
* @returns Upload tab component
|
|
20
26
|
*/
|
|
21
27
|
export declare function ProfilePictureUploadTab({ useUpload, onUseUploadChange, onFileSelect, maxSize, uploadEnabled, disabled, currentPreview, photoUploadDisabledMessage, imageCompressionMaxDimension, uploadFileHardLimitBytes, // 10MB default
|
|
22
|
-
allowedImageMimeTypes, }: ProfilePictureUploadTabProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
allowedImageMimeTypes, cropTitle, cropConfirmLabel, cropCancelLabel, cropZoomLabel, }: ProfilePictureUploadTabProps): import("react/jsx-runtime").JSX.Element;
|
|
23
29
|
//# sourceMappingURL=profile_picture_upload_tab.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile_picture_upload_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/my_settings/components/profile_picture_upload_tab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"profile_picture_upload_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/my_settings/components/profile_picture_upload_tab.tsx"],"names":[],"mappings":"AA2BA,MAAM,MAAM,4BAA4B,GAAG;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAGF;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,OAAO,EACP,aAAa,EACb,QAAgB,EAChB,cAAc,EACd,0BAA0B,EAC1B,4BAAkC,EAClC,wBAAmC,EAAE,eAAe;AACpD,qBAAgE,EAChE,SAAwB,EACxB,gBAA+B,EAC/B,eAA0B,EAC1B,aAAsB,GACvB,EAAE,4BAA4B,2CAgU9B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// file_description: Upload tab component for profile picture dialog with dropzone and preview
|
|
1
|
+
// file_description: Upload tab component for profile picture dialog with dropzone, crop step, and preview
|
|
2
2
|
// section: client_directive
|
|
3
3
|
"use client";
|
|
4
4
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -10,22 +10,37 @@ import { Avatar, AvatarImage, AvatarFallback } from "../../../ui/avatar.js";
|
|
|
10
10
|
import { Upload, X, Loader2, Info } from "lucide-react";
|
|
11
11
|
import { Button } from "../../../ui/button.js";
|
|
12
12
|
import imageCompression from "browser-image-compression";
|
|
13
|
+
import { HazoUiImageCropperDialog } from "hazo_ui";
|
|
14
|
+
// section: helpers
|
|
15
|
+
/** Resolves true if the browser can decode this image file, false otherwise (e.g. desktop HEIC). */
|
|
16
|
+
function can_decode_image(file) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const url = URL.createObjectURL(file);
|
|
19
|
+
const img = new Image();
|
|
20
|
+
img.onload = () => { URL.revokeObjectURL(url); resolve(true); };
|
|
21
|
+
img.onerror = () => { URL.revokeObjectURL(url); resolve(false); };
|
|
22
|
+
img.src = url;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
13
25
|
// section: component
|
|
14
26
|
/**
|
|
15
27
|
* Upload tab component for profile picture dialog
|
|
16
28
|
* Two columns: left = dropzone, right = preview
|
|
17
29
|
* Uses browser-image-compression for client-side compression
|
|
30
|
+
* Routes decodable images through HazoUiImageCropperDialog (round crop, 512² WebP)
|
|
31
|
+
* Falls back to direct upload for HEIC and other browser-undecodable formats
|
|
18
32
|
* @param props - Component props including upload state, file handler, and configuration
|
|
19
33
|
* @returns Upload tab component
|
|
20
34
|
*/
|
|
21
35
|
export function ProfilePictureUploadTab({ useUpload, onUseUploadChange, onFileSelect, maxSize, uploadEnabled, disabled = false, currentPreview, photoUploadDisabledMessage, imageCompressionMaxDimension = 200, uploadFileHardLimitBytes = 10485760, // 10MB default
|
|
22
|
-
allowedImageMimeTypes = ["image/jpeg", "image/jpg", "image/png"], }) {
|
|
36
|
+
allowedImageMimeTypes = ["image/jpeg", "image/jpg", "image/png"], cropTitle = "Crop photo", cropConfirmLabel = "Save photo", cropCancelLabel = "Cancel", cropZoomLabel = "Zoom", }) {
|
|
23
37
|
const [dragActive, setDragActive] = useState(false);
|
|
24
38
|
const [preview, setPreview] = useState(currentPreview || null);
|
|
25
39
|
const [isNewImage, setIsNewImage] = useState(false); // Track if preview is showing a newly uploaded image
|
|
26
40
|
const [uploading, setUploading] = useState(false);
|
|
27
41
|
const [compressing, setCompressing] = useState(false);
|
|
28
42
|
const [error, setError] = useState(null);
|
|
43
|
+
const [cropFile, setCropFile] = useState(null);
|
|
29
44
|
// Update preview when currentPreview changes (e.g., when dialog opens)
|
|
30
45
|
useEffect(() => {
|
|
31
46
|
if (currentPreview) {
|
|
@@ -40,17 +55,7 @@ allowedImageMimeTypes = ["image/jpeg", "image/jpg", "image/png"], }) {
|
|
|
40
55
|
}
|
|
41
56
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
57
|
}, [currentPreview]); // Only depend on currentPreview to avoid loops, isNewImage check is intentional
|
|
43
|
-
const
|
|
44
|
-
// Validate file type
|
|
45
|
-
if (!allowedImageMimeTypes.includes(file.type)) {
|
|
46
|
-
setError(`Invalid file type. Only ${allowedImageMimeTypes.map(t => t.split("/")[1].toUpperCase()).join(", ")} files are allowed.`);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
// Hard limit: reject files larger than configured limit (too large to process efficiently)
|
|
50
|
-
if (file.size > uploadFileHardLimitBytes) {
|
|
51
|
-
setError(`File is too large. Maximum size is ${Math.round(maxSize / 1024)}KB.`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
58
|
+
const process_and_upload = useCallback(async (file) => {
|
|
54
59
|
setError(null);
|
|
55
60
|
setCompressing(false);
|
|
56
61
|
setUploading(false);
|
|
@@ -112,7 +117,32 @@ allowedImageMimeTypes = ["image/jpeg", "image/jpg", "image/png"], }) {
|
|
|
112
117
|
setUploading(false);
|
|
113
118
|
}
|
|
114
119
|
}
|
|
115
|
-
}, [maxSize, onFileSelect]);
|
|
120
|
+
}, [maxSize, onFileSelect, imageCompressionMaxDimension]);
|
|
121
|
+
const handleFile = useCallback(async (file) => {
|
|
122
|
+
// Validate file type
|
|
123
|
+
if (!allowedImageMimeTypes.includes(file.type)) {
|
|
124
|
+
setError(`Invalid file type. Only ${allowedImageMimeTypes.map(t => t.split("/")[1].toUpperCase()).join(", ")} files are allowed.`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Hard limit: reject files larger than configured limit (too large to process efficiently)
|
|
128
|
+
if (file.size > uploadFileHardLimitBytes) {
|
|
129
|
+
setError(`File is too large. Maximum size is ${Math.round(maxSize / 1024)}KB.`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
setError(null);
|
|
133
|
+
const decodable = await can_decode_image(file);
|
|
134
|
+
if (decodable) {
|
|
135
|
+
setCropFile(file); // opens the crop dialog
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await process_and_upload(file); // HEIC/undecodable fallback: direct path
|
|
139
|
+
}
|
|
140
|
+
}, [allowedImageMimeTypes, uploadFileHardLimitBytes, maxSize, process_and_upload]);
|
|
141
|
+
const handle_crop_confirm = useCallback(async (blob) => {
|
|
142
|
+
const cropped = new File([blob], "avatar.webp", { type: "image/webp" });
|
|
143
|
+
await process_and_upload(cropped);
|
|
144
|
+
setCropFile(null);
|
|
145
|
+
}, [process_and_upload]);
|
|
116
146
|
const handleDrag = useCallback((e) => {
|
|
117
147
|
e.preventDefault();
|
|
118
148
|
e.stopPropagation();
|
|
@@ -165,5 +195,6 @@ allowedImageMimeTypes = ["image/jpeg", "image/jpg", "image/png"], }) {
|
|
|
165
195
|
if (!disabled && uploadEnabled) {
|
|
166
196
|
(_a = document.getElementById("file-upload-input")) === null || _a === void 0 ? void 0 : _a.click();
|
|
167
197
|
}
|
|
168
|
-
}, children: [_jsx("input", { id: "file-upload-input", type: "file", accept: allowedImageMimeTypes.join(","), onChange: handleChange, disabled: disabled || !uploadEnabled, className: "hidden", "aria-label": "Upload profile picture" }), _jsx(Upload, { className: "h-8 w-8 text-[var(--hazo-text-subtle)] mb-2", "aria-hidden": "true" }), _jsx("p", { className: "cls_profile_picture_upload_tab_dropzone_text text-sm text-[var(--hazo-text-muted)] text-center", children: "Drag and drop an image here, or click to select" }), _jsxs("p", { className: "cls_profile_picture_upload_tab_dropzone_hint text-xs text-[var(--hazo-text-muted)] text-center mt-1", children: ["JPG or PNG, max ", Math.round(maxSize / 1024), "KB"] })] }), error && (_jsx("p", { className: "cls_profile_picture_upload_tab_error text-sm text-red-600", role: "alert", children: error }))] }), _jsxs("div", { className: "cls_profile_picture_upload_tab_preview_container flex flex-col gap-2", children: [_jsx(Label, { className: "cls_profile_picture_upload_tab_preview_label text-sm font-medium text-[var(--hazo-text-secondary)]", children: isNewImage ? "Preview (new)" : "Preview (current)" }), _jsx("div", { className: "cls_profile_picture_upload_tab_preview_content flex flex-col items-center justify-center border border-[var(--hazo-border)] rounded-lg p-6 bg-[var(--hazo-bg-subtle)] min-h-[200px]", children: compressing ? (_jsxs("div", { className: "cls_profile_picture_upload_tab_compressing flex flex-col items-center gap-2", children: [_jsx(Loader2, { className: "h-8 w-8 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }), _jsx("p", { className: "cls_profile_picture_upload_tab_compressing_text text-sm text-[var(--hazo-text-muted)]", children: "Compressing image..." })] })) : uploading ? (_jsxs("div", { className: "cls_profile_picture_upload_tab_uploading flex flex-col items-center gap-2", children: [_jsx(Loader2, { className: "h-8 w-8 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }), _jsx("p", { className: "cls_profile_picture_upload_tab_uploading_text text-sm text-[var(--hazo-text-muted)]", children: "Uploading..." })] })) : preview ? (_jsxs("div", { className: "cls_profile_picture_upload_tab_preview_image_container flex flex-col items-center gap-4", children: [_jsxs("div", { className: "cls_profile_picture_upload_tab_preview_image_wrapper relative", children: [_jsxs(Avatar, { className: "cls_profile_picture_upload_tab_preview_avatar h-32 w-32", children: [_jsx(AvatarImage, { src: preview, alt: "Uploaded profile picture preview", className: "cls_profile_picture_upload_tab_preview_avatar_image" }), _jsx(AvatarFallback, { className: "cls_profile_picture_upload_tab_preview_avatar_fallback bg-[var(--hazo-bg-emphasis)] text-[var(--hazo-text-muted)] text-3xl", children: getInitials() })] }), _jsx(Button, { type: "button", onClick: handleRemove, variant: "ghost", size: "icon", className: "cls_profile_picture_upload_tab_preview_remove absolute -top-2 -right-2 rounded-full h-6 w-6 border border-[var(--hazo-border-emphasis)] bg-white hover:bg-[var(--hazo-bg-subtle)]", "aria-label": "Remove preview", children: _jsx(X, { className: "h-4 w-4", "aria-hidden": "true" }) })] }), _jsx("p", { className: "cls_profile_picture_upload_tab_preview_success_text text-sm text-[var(--hazo-text-muted)] text-center", children: "Preview of your uploaded photo" })] })) : (_jsxs("div", { className: "cls_profile_picture_upload_tab_preview_empty flex flex-col items-center gap-2", children: [_jsx(Avatar, { className: "cls_profile_picture_upload_tab_preview_empty_avatar h-32 w-32", children: _jsx(AvatarFallback, { className: "cls_profile_picture_upload_tab_preview_empty_avatar_fallback bg-[var(--hazo-bg-emphasis)] text-[var(--hazo-text-muted)] text-3xl", children: getInitials() }) }), _jsx("p", { className: "cls_profile_picture_upload_tab_preview_empty_text text-sm text-[var(--hazo-text-muted)] text-center", children: "Upload an image to see preview" })] })) })] })] })
|
|
198
|
+
}, children: [_jsx("input", { id: "file-upload-input", type: "file", accept: allowedImageMimeTypes.join(","), onChange: handleChange, disabled: disabled || !uploadEnabled, className: "hidden", "aria-label": "Upload profile picture" }), _jsx(Upload, { className: "h-8 w-8 text-[var(--hazo-text-subtle)] mb-2", "aria-hidden": "true" }), _jsx("p", { className: "cls_profile_picture_upload_tab_dropzone_text text-sm text-[var(--hazo-text-muted)] text-center", children: "Drag and drop an image here, or click to select" }), _jsxs("p", { className: "cls_profile_picture_upload_tab_dropzone_hint text-xs text-[var(--hazo-text-muted)] text-center mt-1", children: ["JPG or PNG, max ", Math.round(maxSize / 1024), "KB"] })] }), error && (_jsx("p", { className: "cls_profile_picture_upload_tab_error text-sm text-red-600", role: "alert", children: error }))] }), _jsxs("div", { className: "cls_profile_picture_upload_tab_preview_container flex flex-col gap-2", children: [_jsx(Label, { className: "cls_profile_picture_upload_tab_preview_label text-sm font-medium text-[var(--hazo-text-secondary)]", children: isNewImage ? "Preview (new)" : "Preview (current)" }), _jsx("div", { className: "cls_profile_picture_upload_tab_preview_content flex flex-col items-center justify-center border border-[var(--hazo-border)] rounded-lg p-6 bg-[var(--hazo-bg-subtle)] min-h-[200px]", children: compressing ? (_jsxs("div", { className: "cls_profile_picture_upload_tab_compressing flex flex-col items-center gap-2", children: [_jsx(Loader2, { className: "h-8 w-8 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }), _jsx("p", { className: "cls_profile_picture_upload_tab_compressing_text text-sm text-[var(--hazo-text-muted)]", children: "Compressing image..." })] })) : uploading ? (_jsxs("div", { className: "cls_profile_picture_upload_tab_uploading flex flex-col items-center gap-2", children: [_jsx(Loader2, { className: "h-8 w-8 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }), _jsx("p", { className: "cls_profile_picture_upload_tab_uploading_text text-sm text-[var(--hazo-text-muted)]", children: "Uploading..." })] })) : preview ? (_jsxs("div", { className: "cls_profile_picture_upload_tab_preview_image_container flex flex-col items-center gap-4", children: [_jsxs("div", { className: "cls_profile_picture_upload_tab_preview_image_wrapper relative", children: [_jsxs(Avatar, { className: "cls_profile_picture_upload_tab_preview_avatar h-32 w-32", children: [_jsx(AvatarImage, { src: preview, alt: "Uploaded profile picture preview", className: "cls_profile_picture_upload_tab_preview_avatar_image" }), _jsx(AvatarFallback, { className: "cls_profile_picture_upload_tab_preview_avatar_fallback bg-[var(--hazo-bg-emphasis)] text-[var(--hazo-text-muted)] text-3xl", children: getInitials() })] }), _jsx(Button, { type: "button", onClick: handleRemove, variant: "ghost", size: "icon", className: "cls_profile_picture_upload_tab_preview_remove absolute -top-2 -right-2 rounded-full h-6 w-6 border border-[var(--hazo-border-emphasis)] bg-white hover:bg-[var(--hazo-bg-subtle)]", "aria-label": "Remove preview", children: _jsx(X, { className: "h-4 w-4", "aria-hidden": "true" }) })] }), _jsx("p", { className: "cls_profile_picture_upload_tab_preview_success_text text-sm text-[var(--hazo-text-muted)] text-center", children: "Preview of your uploaded photo" })] })) : (_jsxs("div", { className: "cls_profile_picture_upload_tab_preview_empty flex flex-col items-center gap-2", children: [_jsx(Avatar, { className: "cls_profile_picture_upload_tab_preview_empty_avatar h-32 w-32", children: _jsx(AvatarFallback, { className: "cls_profile_picture_upload_tab_preview_empty_avatar_fallback bg-[var(--hazo-bg-emphasis)] text-[var(--hazo-text-muted)] text-3xl", children: getInitials() }) }), _jsx("p", { className: "cls_profile_picture_upload_tab_preview_empty_text text-sm text-[var(--hazo-text-muted)] text-center", children: "Upload an image to see preview" })] })) })] })] }), _jsx(HazoUiImageCropperDialog, { open: cropFile !== null, onOpenChange: (open) => { if (!open && !uploading && !compressing)
|
|
199
|
+
setCropFile(null); }, file: cropFile, onConfirm: handle_crop_confirm, title: cropTitle, confirmLabel: cropConfirmLabel, cancelLabel: cropCancelLabel, zoomLabel: cropZoomLabel })] }));
|
|
169
200
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HazoConnectAdapter } from "hazo_connect";
|
|
2
|
+
export declare class DemoSeedNotAllowed extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Throws DemoSeedNotAllowed if the current environment is production
|
|
7
|
+
* or if HAZO_AUTH_ALLOW_DEMO_SEED is not set to "true".
|
|
8
|
+
* This must be called at the top of every exported function in this module.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assert_demo_seed_allowed(): void;
|
|
11
|
+
export type CreateDemoUserOptions = {
|
|
12
|
+
email?: string;
|
|
13
|
+
password?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
admin?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type CreateDemoUserResult = {
|
|
18
|
+
user_id: string;
|
|
19
|
+
email: string;
|
|
20
|
+
password: string;
|
|
21
|
+
};
|
|
22
|
+
export type DeleteDemoUsersOptions = {
|
|
23
|
+
email?: string;
|
|
24
|
+
prefixes?: string[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Creates a demo user in the database, assigning them to the default system scope.
|
|
28
|
+
* Idempotent on email — if the user already exists their id is reused and the
|
|
29
|
+
* scope/role assignment is still ensured.
|
|
30
|
+
*
|
|
31
|
+
* REQUIRES: non-production env + HAZO_AUTH_ALLOW_DEMO_SEED=true
|
|
32
|
+
*/
|
|
33
|
+
export declare function create_demo_user(adapter: HazoConnectAdapter, opts?: CreateDemoUserOptions): Promise<CreateDemoUserResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Deletes demo users (and their scope rows) whose email matches the given
|
|
36
|
+
* address or any of the given prefixes. Defaults to the "__demo_" prefix.
|
|
37
|
+
*
|
|
38
|
+
* REQUIRES: non-production env + HAZO_AUTH_ALLOW_DEMO_SEED=true
|
|
39
|
+
*/
|
|
40
|
+
export declare function delete_demo_users(adapter: HazoConnectAdapter, opts?: DeleteDemoUsersOptions): Promise<{
|
|
41
|
+
deleted: number;
|
|
42
|
+
}>;
|
|
43
|
+
//# sourceMappingURL=demo_account_service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo_account_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/demo_account_service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AASvD,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAID;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAY/C;AAID,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAIF;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,GAAE,qBAA0B,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAwG/B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAuC9B"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// file_description: dev-only service for seeding demo accounts in hazo_auth; physically blocked in production
|
|
2
|
+
import { createCrudService } from "hazo_connect/server";
|
|
3
|
+
import argon2 from "argon2";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
import { DEFAULT_SYSTEM_SCOPE_ID } from "./scope_service.js";
|
|
6
|
+
import { GLOBAL_ADMIN_PERMISSION } from "../constants.js";
|
|
7
|
+
// section: errors
|
|
8
|
+
export class DemoSeedNotAllowed extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "DemoSeedNotAllowed";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// section: guards
|
|
15
|
+
/**
|
|
16
|
+
* Throws DemoSeedNotAllowed if the current environment is production
|
|
17
|
+
* or if HAZO_AUTH_ALLOW_DEMO_SEED is not set to "true".
|
|
18
|
+
* This must be called at the top of every exported function in this module.
|
|
19
|
+
*/
|
|
20
|
+
export function assert_demo_seed_allowed() {
|
|
21
|
+
var _a, _b;
|
|
22
|
+
const env = (_b = (_a = process.env.HAZO_ENV) !== null && _a !== void 0 ? _a : process.env.NODE_ENV) !== null && _b !== void 0 ? _b : "development";
|
|
23
|
+
if (env === "production") {
|
|
24
|
+
throw new DemoSeedNotAllowed("Demo seeding is disabled. Requires a non-production env AND HAZO_AUTH_ALLOW_DEMO_SEED=true.");
|
|
25
|
+
}
|
|
26
|
+
if (process.env.HAZO_AUTH_ALLOW_DEMO_SEED !== "true") {
|
|
27
|
+
throw new DemoSeedNotAllowed("Demo seeding is disabled. Requires a non-production env AND HAZO_AUTH_ALLOW_DEMO_SEED=true.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// section: helpers
|
|
31
|
+
/**
|
|
32
|
+
* Creates a demo user in the database, assigning them to the default system scope.
|
|
33
|
+
* Idempotent on email — if the user already exists their id is reused and the
|
|
34
|
+
* scope/role assignment is still ensured.
|
|
35
|
+
*
|
|
36
|
+
* REQUIRES: non-production env + HAZO_AUTH_ALLOW_DEMO_SEED=true
|
|
37
|
+
*/
|
|
38
|
+
export async function create_demo_user(adapter, opts = {}) {
|
|
39
|
+
var _a, _b, _c, _d;
|
|
40
|
+
assert_demo_seed_allowed();
|
|
41
|
+
const email = (_a = opts.email) !== null && _a !== void 0 ? _a : `__demo_${Date.now()}@hazo.test`;
|
|
42
|
+
const password = (_b = opts.password) !== null && _b !== void 0 ? _b : "Demo_Pass123!";
|
|
43
|
+
const name = (_c = opts.name) !== null && _c !== void 0 ? _c : "Demo User";
|
|
44
|
+
const admin = (_d = opts.admin) !== null && _d !== void 0 ? _d : false;
|
|
45
|
+
const now = new Date().toISOString();
|
|
46
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
47
|
+
const roles_service = createCrudService(adapter, "hazo_roles");
|
|
48
|
+
const permissions_service = createCrudService(adapter, "hazo_permissions");
|
|
49
|
+
const role_permissions_service = createCrudService(adapter, "hazo_role_permissions", { primaryKeys: ["role_id", "permission_id"], autoId: false });
|
|
50
|
+
const user_scopes_service = createCrudService(adapter, "hazo_user_scopes", {
|
|
51
|
+
primaryKeys: ["user_id", "scope_id"],
|
|
52
|
+
autoId: false,
|
|
53
|
+
});
|
|
54
|
+
// Idempotent: reuse existing user if email already registered
|
|
55
|
+
let user_id;
|
|
56
|
+
const existing_users = await users_service.findBy({ email_address: email });
|
|
57
|
+
if (Array.isArray(existing_users) && existing_users.length > 0) {
|
|
58
|
+
user_id = existing_users[0].id;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const password_hash = await argon2.hash(password);
|
|
62
|
+
user_id = randomUUID();
|
|
63
|
+
await users_service.insert({
|
|
64
|
+
id: user_id,
|
|
65
|
+
email_address: email,
|
|
66
|
+
password_hash,
|
|
67
|
+
email_verified: true,
|
|
68
|
+
status: "ACTIVE",
|
|
69
|
+
login_attempts: 0,
|
|
70
|
+
auth_providers: "email",
|
|
71
|
+
name,
|
|
72
|
+
created_at: now,
|
|
73
|
+
changed_at: now,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// Ensure role exists
|
|
77
|
+
const role_name = admin ? "default_super_user_role" : "demo_member_role";
|
|
78
|
+
const existing_roles = await roles_service.findBy({ role_name });
|
|
79
|
+
let role_id;
|
|
80
|
+
if (Array.isArray(existing_roles) && existing_roles.length > 0) {
|
|
81
|
+
role_id = existing_roles[0].id;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const new_role = await roles_service.insert({ role_name, created_at: now, changed_at: now });
|
|
85
|
+
role_id = Array.isArray(new_role)
|
|
86
|
+
? new_role[0].id
|
|
87
|
+
: new_role.id;
|
|
88
|
+
}
|
|
89
|
+
// Admin users: ensure GLOBAL_ADMIN_PERMISSION is in the catalog and linked to the role
|
|
90
|
+
if (admin) {
|
|
91
|
+
// Ensure permission exists
|
|
92
|
+
const existing_perms = await permissions_service.findBy({
|
|
93
|
+
permission_name: GLOBAL_ADMIN_PERMISSION,
|
|
94
|
+
});
|
|
95
|
+
let permission_id;
|
|
96
|
+
if (Array.isArray(existing_perms) && existing_perms.length > 0) {
|
|
97
|
+
permission_id = existing_perms[0].id;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const new_perm = await permissions_service.insert({
|
|
101
|
+
permission_name: GLOBAL_ADMIN_PERMISSION,
|
|
102
|
+
description: "Global admin — access to all scopes and operations",
|
|
103
|
+
created_at: now,
|
|
104
|
+
changed_at: now,
|
|
105
|
+
});
|
|
106
|
+
permission_id = Array.isArray(new_perm)
|
|
107
|
+
? new_perm[0].id
|
|
108
|
+
: new_perm.id;
|
|
109
|
+
}
|
|
110
|
+
// Ensure role→permission link
|
|
111
|
+
const existing_rp = await role_permissions_service.findBy({ role_id, permission_id });
|
|
112
|
+
if (!Array.isArray(existing_rp) || existing_rp.length === 0) {
|
|
113
|
+
await role_permissions_service.insert({ role_id, permission_id });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Ensure user→scope assignment
|
|
117
|
+
const existing_user_scopes = await user_scopes_service.findBy({
|
|
118
|
+
user_id,
|
|
119
|
+
scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
120
|
+
});
|
|
121
|
+
if (!Array.isArray(existing_user_scopes) || existing_user_scopes.length === 0) {
|
|
122
|
+
await user_scopes_service.insert({
|
|
123
|
+
user_id,
|
|
124
|
+
scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
125
|
+
root_scope_id: DEFAULT_SYSTEM_SCOPE_ID,
|
|
126
|
+
role_id,
|
|
127
|
+
created_at: now,
|
|
128
|
+
changed_at: now,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return { user_id, email, password };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Deletes demo users (and their scope rows) whose email matches the given
|
|
135
|
+
* address or any of the given prefixes. Defaults to the "__demo_" prefix.
|
|
136
|
+
*
|
|
137
|
+
* REQUIRES: non-production env + HAZO_AUTH_ALLOW_DEMO_SEED=true
|
|
138
|
+
*/
|
|
139
|
+
export async function delete_demo_users(adapter, opts = {}) {
|
|
140
|
+
var _a;
|
|
141
|
+
assert_demo_seed_allowed();
|
|
142
|
+
const prefixes = (_a = opts.prefixes) !== null && _a !== void 0 ? _a : ["__demo_"];
|
|
143
|
+
const users = createCrudService(adapter, "hazo_users");
|
|
144
|
+
const user_scopes_service = createCrudService(adapter, "hazo_user_scopes", {
|
|
145
|
+
primaryKeys: ["user_id", "scope_id"],
|
|
146
|
+
autoId: false,
|
|
147
|
+
});
|
|
148
|
+
const all = await users.list((qb) => qb.limit(500));
|
|
149
|
+
const targets = all.filter((u) => {
|
|
150
|
+
if (opts.email && u.email_address === opts.email)
|
|
151
|
+
return true;
|
|
152
|
+
return prefixes.some((p) => typeof u.email_address === "string" && u.email_address.startsWith(p));
|
|
153
|
+
});
|
|
154
|
+
let deleted = 0;
|
|
155
|
+
for (const u of targets) {
|
|
156
|
+
const uid = u.id;
|
|
157
|
+
try {
|
|
158
|
+
// Best-effort: delete this user's scope rows. hazo_user_scopes has a
|
|
159
|
+
// composite PK (no `id` column); deleteById on a multi-key table falls
|
|
160
|
+
// back to the first primary key (user_id), so this removes all of the
|
|
161
|
+
// user's scope rows in one call.
|
|
162
|
+
await user_scopes_service.deleteById(uid).catch(() => { });
|
|
163
|
+
await users.deleteById(uid);
|
|
164
|
+
deleted += 1;
|
|
165
|
+
}
|
|
166
|
+
catch (_b) {
|
|
167
|
+
// best-effort — skip users that can't be deleted
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { deleted };
|
|
171
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/services/index.ts"],"names":[],"mappings":"AAEA,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/services/index.ts"],"names":[],"mappings":"AAEA,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_auth",
|
|
3
|
-
"version": "10.2.
|
|
3
|
+
"version": "10.2.3",
|
|
4
4
|
"description": "Zero-config authentication UI components for Next.js with RBAC, OAuth, scope-based multi-tenancy, and invitations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"authentication",
|
|
@@ -206,6 +206,8 @@
|
|
|
206
206
|
"dev:server": "tsx src/server/index.ts",
|
|
207
207
|
"migrate": "tsx scripts/apply_migration.ts",
|
|
208
208
|
"init-users": "tsx scripts/init_users.ts init_users",
|
|
209
|
+
"demo:create": "tsx --conditions react-server scripts/demo.ts create",
|
|
210
|
+
"demo:delete": "tsx --conditions react-server scripts/demo.ts delete",
|
|
209
211
|
"test": "cross-env NODE_ENV=test jest --runInBand",
|
|
210
212
|
"test:watch": "cross-env NODE_ENV=test jest --watch"
|
|
211
213
|
},
|
|
@@ -259,7 +261,7 @@
|
|
|
259
261
|
"hazo_logs": "^2.0.3",
|
|
260
262
|
"hazo_notify": "^6.1.3",
|
|
261
263
|
"hazo_secure": "^1.1.0",
|
|
262
|
-
"hazo_ui": "^4.
|
|
264
|
+
"hazo_ui": "^4.2.0",
|
|
263
265
|
"input-otp": "^1.4.0",
|
|
264
266
|
"lucide-react": "^0.553.0",
|
|
265
267
|
"next": "^14.0.0",
|
|
@@ -392,13 +394,13 @@
|
|
|
392
394
|
"eslint": "^9.39.1",
|
|
393
395
|
"eslint-config-next": "^16.0.4",
|
|
394
396
|
"eslint-plugin-storybook": "^10.0.6",
|
|
395
|
-
"hazo_api": "^2.
|
|
397
|
+
"hazo_api": "^2.5.0",
|
|
396
398
|
"hazo_config": "^2.1.11",
|
|
397
399
|
"hazo_connect": "^3.7.0",
|
|
398
400
|
"hazo_core": "^1.2.0",
|
|
399
401
|
"hazo_logs": "^2.0.3",
|
|
400
402
|
"hazo_notify": "^6.1.3",
|
|
401
|
-
"hazo_ui": "^4.
|
|
403
|
+
"hazo_ui": "^4.2.0",
|
|
402
404
|
"input-otp": "^1.4.0",
|
|
403
405
|
"jest": "^30.2.0",
|
|
404
406
|
"jest-environment-jsdom": "^30.0.0",
|