hazo_auth 10.0.0 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -0
- package/SETUP_CHECKLIST.md +18 -0
- package/cli-src/lib/auth/nextauth_config.ts +41 -0
- package/cli-src/lib/auth/request_google_scopes.ts +23 -0
- package/cli-src/lib/hazo_connect_instance.server.ts +16 -0
- package/cli-src/lib/schema/sqlite_schema.ts +16 -0
- package/cli-src/lib/services/google_token_service.ts +408 -0
- package/cli-src/lib/services/index.ts +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/components/layouts/google_token_test/index.d.ts +6 -0
- package/dist/components/layouts/google_token_test/index.d.ts.map +1 -0
- package/dist/components/layouts/google_token_test/index.js +74 -0
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/input-otp.d.ts +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/auth/nextauth_config.d.ts +2 -0
- package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.js +39 -1
- package/dist/lib/auth/request_google_scopes.d.ts +10 -0
- package/dist/lib/auth/request_google_scopes.d.ts.map +1 -0
- package/dist/lib/auth/request_google_scopes.js +13 -0
- package/dist/lib/hazo_connect_instance.server.d.ts +4 -0
- package/dist/lib/hazo_connect_instance.server.d.ts.map +1 -1
- package/dist/lib/hazo_connect_instance.server.js +15 -0
- package/dist/lib/schema/sqlite_schema.d.ts +1 -1
- package/dist/lib/schema/sqlite_schema.d.ts.map +1 -1
- package/dist/lib/schema/sqlite_schema.js +16 -0
- package/dist/lib/services/google_token_service.d.ts +48 -0
- package/dist/lib/services/google_token_service.d.ts.map +1 -0
- package/dist/lib/services/google_token_service.js +319 -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/dist/server/routes/google_token.d.ts +13 -0
- package/dist/server/routes/google_token.d.ts.map +1 -0
- package/dist/server/routes/google_token.js +66 -0
- package/dist/server/routes/index.d.ts +1 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +2 -0
- package/dist/server/routes/me.d.ts.map +1 -1
- package/dist/server/routes/me.js +3 -0
- package/dist/server/routes/user_management_users.d.ts +1 -1
- package/dist/server-lib.d.ts +1 -1
- package/dist/server-lib.d.ts.map +1 -1
- package/dist/server-lib.js +1 -1
- package/package.json +11 -7
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// file_description: stores, retrieves, refreshes, and revokes Google OAuth tokens with encrypted persistence
|
|
2
|
+
// section: imports
|
|
3
|
+
import { createCrudService } from "hazo_connect/server";
|
|
4
|
+
import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
5
|
+
import { create_app_logger } from "../app_logger.js";
|
|
6
|
+
import { optional_import } from "hazo_core";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
|
|
9
|
+
// section: errors
|
|
10
|
+
|
|
11
|
+
export class GoogleTokenStorageUnconfigured extends Error {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(
|
|
14
|
+
"Google token storage is not configured. Set HAZO_AUTH_OAUTH_KEY_CURRENT and HAZO_AUTH_OAUTH_KEY_<ID> env vars."
|
|
15
|
+
);
|
|
16
|
+
this.name = "GoogleTokenStorageUnconfigured";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// section: types
|
|
21
|
+
|
|
22
|
+
export type StoreGoogleOAuthTokenParams = {
|
|
23
|
+
user_id: string;
|
|
24
|
+
refresh_token: string;
|
|
25
|
+
access_token?: string;
|
|
26
|
+
scopes: string; // space-delimited
|
|
27
|
+
expires_at?: string; // ISO string for access token expiry
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type GoogleTokenResult =
|
|
31
|
+
| { ok: true; access_token: string; scopes: string }
|
|
32
|
+
| { ok: false; error: "not_connected" | "reconsent_required" | "refresh_failed" | "unconfigured" };
|
|
33
|
+
|
|
34
|
+
export type GoogleTokenStatus = {
|
|
35
|
+
connected: boolean;
|
|
36
|
+
scopes: string;
|
|
37
|
+
expires_at: string | null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// section: internal-types
|
|
41
|
+
|
|
42
|
+
type GoogleOAuthRow = {
|
|
43
|
+
id: string;
|
|
44
|
+
user_id: string;
|
|
45
|
+
provider: string;
|
|
46
|
+
refresh_token_enc: string;
|
|
47
|
+
access_token_enc: string | null;
|
|
48
|
+
scopes: string;
|
|
49
|
+
expires_at: string | null;
|
|
50
|
+
created_at: string;
|
|
51
|
+
changed_at: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// section: cache
|
|
55
|
+
|
|
56
|
+
const access_token_cache = new Map<string, { token: string; expires_at: string }>();
|
|
57
|
+
|
|
58
|
+
// section: helpers
|
|
59
|
+
|
|
60
|
+
function makeKeyProvider(
|
|
61
|
+
LookupKeyProvider: new (lookup: (name: string) => string | undefined) => unknown
|
|
62
|
+
) {
|
|
63
|
+
return new LookupKeyProvider((name: string) => {
|
|
64
|
+
if (name === "current") return process.env.HAZO_AUTH_OAUTH_KEY_CURRENT;
|
|
65
|
+
if (name.startsWith("key_")) {
|
|
66
|
+
const keyId = name.slice(4).toUpperCase();
|
|
67
|
+
return process.env[`HAZO_AUTH_OAUTH_KEY_${keyId}`];
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function load_crypto_module() {
|
|
74
|
+
const cryptoModule = await optional_import<typeof import("hazo_secure/crypto")>(
|
|
75
|
+
"hazo_secure/crypto"
|
|
76
|
+
);
|
|
77
|
+
if (!cryptoModule) throw new GoogleTokenStorageUnconfigured();
|
|
78
|
+
return cryptoModule;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// section: store
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Stores (inserts or updates) Google OAuth tokens for a user, encrypting sensitive fields.
|
|
85
|
+
* Throws GoogleTokenStorageUnconfigured if hazo_secure/crypto is unavailable or keys are missing.
|
|
86
|
+
*/
|
|
87
|
+
export async function store_google_oauth_token(
|
|
88
|
+
params: StoreGoogleOAuthTokenParams
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
const logger = create_app_logger();
|
|
91
|
+
|
|
92
|
+
const { encryptField, decryptField: _decryptField, aadFor, LookupKeyProvider } = await load_crypto_module();
|
|
93
|
+
|
|
94
|
+
const keys = makeKeyProvider(LookupKeyProvider as new (lookup: (name: string) => string | undefined) => unknown) as Parameters<typeof encryptField>[1]["keys"];
|
|
95
|
+
|
|
96
|
+
// Force-fail early if no key is configured
|
|
97
|
+
try {
|
|
98
|
+
await (keys as unknown as { current(): Promise<unknown> }).current();
|
|
99
|
+
} catch (err) {
|
|
100
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
+
logger.error("google_token_service_key_provider_failed", {
|
|
102
|
+
filename: "google_token_service.ts",
|
|
103
|
+
error: msg,
|
|
104
|
+
});
|
|
105
|
+
throw new GoogleTokenStorageUnconfigured();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const refresh_enc = await encryptField(params.refresh_token, {
|
|
109
|
+
keys,
|
|
110
|
+
aad: aadFor("hazo_google_oauth_tokens", params.user_id, "refresh_token"),
|
|
111
|
+
});
|
|
112
|
+
const refresh_token_enc = JSON.stringify(refresh_enc);
|
|
113
|
+
|
|
114
|
+
let access_token_enc: string | null = null;
|
|
115
|
+
if (params.access_token) {
|
|
116
|
+
const access_enc = await encryptField(params.access_token, {
|
|
117
|
+
keys,
|
|
118
|
+
aad: aadFor("hazo_google_oauth_tokens", params.user_id, "access_token"),
|
|
119
|
+
});
|
|
120
|
+
access_token_enc = JSON.stringify(access_enc);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const adapter = get_hazo_connect_instance();
|
|
124
|
+
const service = createCrudService(adapter, "hazo_google_oauth_tokens");
|
|
125
|
+
|
|
126
|
+
const existing_rows = (await service.findBy({
|
|
127
|
+
user_id: params.user_id,
|
|
128
|
+
provider: "google",
|
|
129
|
+
})) as GoogleOAuthRow[];
|
|
130
|
+
|
|
131
|
+
const now = new Date().toISOString();
|
|
132
|
+
|
|
133
|
+
if (existing_rows.length > 0) {
|
|
134
|
+
const existing = existing_rows[0];
|
|
135
|
+
await service.updateById(existing.id, {
|
|
136
|
+
refresh_token_enc,
|
|
137
|
+
access_token_enc,
|
|
138
|
+
scopes: params.scopes,
|
|
139
|
+
expires_at: params.expires_at ?? null,
|
|
140
|
+
changed_at: now,
|
|
141
|
+
});
|
|
142
|
+
logger.info("google_token_service_token_updated", {
|
|
143
|
+
filename: "google_token_service.ts",
|
|
144
|
+
user_id: params.user_id,
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
await service.insert({
|
|
148
|
+
id: randomUUID(),
|
|
149
|
+
user_id: params.user_id,
|
|
150
|
+
provider: "google",
|
|
151
|
+
refresh_token_enc,
|
|
152
|
+
access_token_enc,
|
|
153
|
+
scopes: params.scopes,
|
|
154
|
+
expires_at: params.expires_at ?? null,
|
|
155
|
+
created_at: now,
|
|
156
|
+
changed_at: now,
|
|
157
|
+
});
|
|
158
|
+
logger.info("google_token_service_token_stored", {
|
|
159
|
+
filename: "google_token_service.ts",
|
|
160
|
+
user_id: params.user_id,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// section: get
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Returns a valid access token for the user, refreshing via Google if needed.
|
|
169
|
+
* Returns a typed error result instead of throwing for expected failure modes.
|
|
170
|
+
*/
|
|
171
|
+
export async function getGoogleToken(
|
|
172
|
+
userId: string,
|
|
173
|
+
opts?: { scopes?: string[] }
|
|
174
|
+
): Promise<GoogleTokenResult> {
|
|
175
|
+
const logger = create_app_logger();
|
|
176
|
+
|
|
177
|
+
// Check in-memory cache first
|
|
178
|
+
const cached = access_token_cache.get(userId);
|
|
179
|
+
if (cached) {
|
|
180
|
+
const still_valid = new Date(cached.expires_at).getTime() - 60000 > Date.now();
|
|
181
|
+
if (still_valid) {
|
|
182
|
+
logger.info("google_token_service_cache_hit", {
|
|
183
|
+
filename: "google_token_service.ts",
|
|
184
|
+
user_id: userId,
|
|
185
|
+
});
|
|
186
|
+
// We need scopes from DB even on cache hit when scope check is requested
|
|
187
|
+
if (!opts?.scopes?.length) {
|
|
188
|
+
// No scope check needed — return cached token (scopes unknown from cache alone)
|
|
189
|
+
// Fall through to DB to get scopes for the result shape
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const adapter = get_hazo_connect_instance();
|
|
195
|
+
const service = createCrudService(adapter, "hazo_google_oauth_tokens");
|
|
196
|
+
|
|
197
|
+
const rows = (await service.findBy({ user_id: userId, provider: "google" })) as GoogleOAuthRow[];
|
|
198
|
+
if (rows.length === 0) {
|
|
199
|
+
return { ok: false, error: "not_connected" };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const row = rows[0];
|
|
203
|
+
|
|
204
|
+
// Scope check
|
|
205
|
+
if (opts?.scopes?.length) {
|
|
206
|
+
const stored_scopes = row.scopes.split(" ").filter(Boolean);
|
|
207
|
+
const missing = opts.scopes.some((s) => !stored_scopes.includes(s));
|
|
208
|
+
if (missing) {
|
|
209
|
+
return { ok: false, error: "reconsent_required" };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Return cached token now that we have the row (scope check passed)
|
|
214
|
+
if (cached) {
|
|
215
|
+
const still_valid = new Date(cached.expires_at).getTime() - 60000 > Date.now();
|
|
216
|
+
if (still_valid) {
|
|
217
|
+
return { ok: true, access_token: cached.token, scopes: row.scopes };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Need to refresh — load crypto
|
|
222
|
+
let cryptoModule: Awaited<ReturnType<typeof load_crypto_module>>;
|
|
223
|
+
try {
|
|
224
|
+
cryptoModule = await load_crypto_module();
|
|
225
|
+
} catch {
|
|
226
|
+
return { ok: false, error: "unconfigured" };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { decryptField, aadFor, LookupKeyProvider } = cryptoModule;
|
|
230
|
+
const keys = makeKeyProvider(LookupKeyProvider as new (lookup: (name: string) => string | undefined) => unknown) as Parameters<typeof cryptoModule.encryptField>[1]["keys"];
|
|
231
|
+
|
|
232
|
+
let decrypted_refresh: string;
|
|
233
|
+
try {
|
|
234
|
+
decrypted_refresh = await decryptField(JSON.parse(row.refresh_token_enc), {
|
|
235
|
+
keys,
|
|
236
|
+
aad: aadFor("hazo_google_oauth_tokens", userId, "refresh_token"),
|
|
237
|
+
});
|
|
238
|
+
} catch (err) {
|
|
239
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
240
|
+
logger.error("google_token_service_decrypt_failed", {
|
|
241
|
+
filename: "google_token_service.ts",
|
|
242
|
+
user_id: userId,
|
|
243
|
+
error: msg,
|
|
244
|
+
});
|
|
245
|
+
return { ok: false, error: "refresh_failed" };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Call Google token refresh endpoint
|
|
249
|
+
let resp: Response;
|
|
250
|
+
try {
|
|
251
|
+
resp = await fetch("https://oauth2.googleapis.com/token", {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
254
|
+
body: new URLSearchParams({
|
|
255
|
+
grant_type: "refresh_token",
|
|
256
|
+
refresh_token: decrypted_refresh,
|
|
257
|
+
client_id: process.env.HAZO_AUTH_GOOGLE_CLIENT_ID!,
|
|
258
|
+
client_secret: process.env.HAZO_AUTH_GOOGLE_CLIENT_SECRET!,
|
|
259
|
+
}),
|
|
260
|
+
});
|
|
261
|
+
} catch (err) {
|
|
262
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
263
|
+
logger.error("google_token_service_refresh_fetch_failed", {
|
|
264
|
+
filename: "google_token_service.ts",
|
|
265
|
+
user_id: userId,
|
|
266
|
+
error: msg,
|
|
267
|
+
});
|
|
268
|
+
return { ok: false, error: "refresh_failed" };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!resp.ok) {
|
|
272
|
+
logger.warn("google_token_service_refresh_rejected", {
|
|
273
|
+
filename: "google_token_service.ts",
|
|
274
|
+
user_id: userId,
|
|
275
|
+
status: resp.status,
|
|
276
|
+
});
|
|
277
|
+
return { ok: false, error: "refresh_failed" };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const data = await resp.json() as { access_token: string; expires_in: number };
|
|
281
|
+
|
|
282
|
+
// Persist new access token (encrypted)
|
|
283
|
+
const new_expires_at = new Date(Date.now() + data.expires_in * 1000).toISOString();
|
|
284
|
+
try {
|
|
285
|
+
const { encryptField } = cryptoModule;
|
|
286
|
+
const access_enc = await encryptField(data.access_token, {
|
|
287
|
+
keys,
|
|
288
|
+
aad: aadFor("hazo_google_oauth_tokens", userId, "access_token"),
|
|
289
|
+
});
|
|
290
|
+
const access_token_enc = JSON.stringify(access_enc);
|
|
291
|
+
await service.updateById(row.id, {
|
|
292
|
+
access_token_enc,
|
|
293
|
+
expires_at: new_expires_at,
|
|
294
|
+
changed_at: new Date().toISOString(),
|
|
295
|
+
});
|
|
296
|
+
} catch (err) {
|
|
297
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
298
|
+
logger.warn("google_token_service_persist_access_token_failed", {
|
|
299
|
+
filename: "google_token_service.ts",
|
|
300
|
+
user_id: userId,
|
|
301
|
+
error: msg,
|
|
302
|
+
note: "Access token refreshed successfully but could not be persisted",
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Update in-memory cache
|
|
307
|
+
access_token_cache.set(userId, { token: data.access_token, expires_at: new_expires_at });
|
|
308
|
+
|
|
309
|
+
logger.info("google_token_service_token_refreshed", {
|
|
310
|
+
filename: "google_token_service.ts",
|
|
311
|
+
user_id: userId,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return { ok: true, access_token: data.access_token, scopes: row.scopes };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// section: revoke
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Revokes the user's Google OAuth tokens — best-effort remote revocation, then deletes DB row.
|
|
321
|
+
*/
|
|
322
|
+
export async function revoke_google_oauth_token(
|
|
323
|
+
userId: string
|
|
324
|
+
): Promise<{ ok: boolean; error?: string }> {
|
|
325
|
+
const logger = create_app_logger();
|
|
326
|
+
|
|
327
|
+
const adapter = get_hazo_connect_instance();
|
|
328
|
+
const service = createCrudService(adapter, "hazo_google_oauth_tokens");
|
|
329
|
+
|
|
330
|
+
const rows = (await service.findBy({ user_id: userId, provider: "google" })) as GoogleOAuthRow[];
|
|
331
|
+
if (rows.length === 0) {
|
|
332
|
+
return { ok: false, error: "not_connected" };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const row = rows[0];
|
|
336
|
+
|
|
337
|
+
// Attempt remote revocation — best effort
|
|
338
|
+
try {
|
|
339
|
+
const { decryptField, aadFor, LookupKeyProvider } = await load_crypto_module();
|
|
340
|
+
const keys = makeKeyProvider(LookupKeyProvider as new (lookup: (name: string) => string | undefined) => unknown) as Parameters<typeof decryptField>[1]["keys"];
|
|
341
|
+
|
|
342
|
+
const decrypted_refresh = await decryptField(JSON.parse(row.refresh_token_enc), {
|
|
343
|
+
keys,
|
|
344
|
+
aad: aadFor("hazo_google_oauth_tokens", userId, "refresh_token"),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const revoke_resp = await fetch(
|
|
348
|
+
`https://oauth2.googleapis.com/revoke?token=${encodeURIComponent(decrypted_refresh)}`,
|
|
349
|
+
{ method: "POST" }
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
if (!revoke_resp.ok) {
|
|
353
|
+
logger.warn("google_token_service_revoke_remote_failed", {
|
|
354
|
+
filename: "google_token_service.ts",
|
|
355
|
+
user_id: userId,
|
|
356
|
+
status: revoke_resp.status,
|
|
357
|
+
note: "Google revocation returned non-OK status; proceeding to delete local record",
|
|
358
|
+
});
|
|
359
|
+
} else {
|
|
360
|
+
logger.info("google_token_service_revoke_remote_ok", {
|
|
361
|
+
filename: "google_token_service.ts",
|
|
362
|
+
user_id: userId,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
} catch (err) {
|
|
366
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
367
|
+
logger.warn("google_token_service_revoke_remote_error", {
|
|
368
|
+
filename: "google_token_service.ts",
|
|
369
|
+
user_id: userId,
|
|
370
|
+
error: msg,
|
|
371
|
+
note: "Remote revocation failed; proceeding to delete local record",
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Always delete the local row
|
|
376
|
+
await service.deleteById(row.id);
|
|
377
|
+
access_token_cache.delete(userId);
|
|
378
|
+
|
|
379
|
+
logger.info("google_token_service_revoked", {
|
|
380
|
+
filename: "google_token_service.ts",
|
|
381
|
+
user_id: userId,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return { ok: true };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// section: status
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Returns connection status and scope information for a user's Google OAuth connection.
|
|
391
|
+
* Does not decrypt or refresh tokens.
|
|
392
|
+
*/
|
|
393
|
+
export async function get_google_token_status(userId: string): Promise<GoogleTokenStatus> {
|
|
394
|
+
const adapter = get_hazo_connect_instance();
|
|
395
|
+
const service = createCrudService(adapter, "hazo_google_oauth_tokens");
|
|
396
|
+
|
|
397
|
+
const rows = (await service.findBy({ user_id: userId, provider: "google" })) as GoogleOAuthRow[];
|
|
398
|
+
if (rows.length === 0) {
|
|
399
|
+
return { connected: false, scopes: "", expires_at: null };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const row = rows[0];
|
|
403
|
+
return {
|
|
404
|
+
connected: true,
|
|
405
|
+
scopes: row.scopes,
|
|
406
|
+
expires_at: row.expires_at ?? null,
|
|
407
|
+
};
|
|
408
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -9,4 +9,5 @@ export { use_firm_branding, use_current_user_branding } from "./components/layou
|
|
|
9
9
|
export type { FirmBranding, UseFirmBrandingOptions, UseFirmBrandingResult } from "./components/layouts/shared/hooks/use_firm_branding";
|
|
10
10
|
export { HAZO_AUTH_PERMISSIONS, ALL_ADMIN_PERMISSIONS, GLOBAL_ADMIN_PERMISSION } from "./lib/constants.js";
|
|
11
11
|
export * from "./components/layouts/shared/utils/validation.js";
|
|
12
|
+
export { requestGoogleScopes } from "./lib/auth/request_google_scopes.js";
|
|
12
13
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAYA,cAAc,oBAAoB,CAAC;AAInC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAInI,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIpD,cAAc,uBAAuB,CAAC;AAItC,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,MAAM,mDAAmD,CAAC;AACjH,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC3G,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AAC7G,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,qDAAqD,CAAC;AACnH,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qDAAqD,CAAC;AAGvI,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAIxG,cAAc,8CAA8C,CAAC"}
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAYA,cAAc,oBAAoB,CAAC;AAInC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAInI,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIpD,cAAc,uBAAuB,CAAC;AAItC,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,MAAM,mDAAmD,CAAC;AACjH,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC3G,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AAC7G,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,qDAAqD,CAAC;AACnH,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qDAAqD,CAAC;AAGvI,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAIxG,cAAc,8CAA8C,CAAC;AAG7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC"}
|
package/dist/client.js
CHANGED
|
@@ -29,3 +29,5 @@ export { HAZO_AUTH_PERMISSIONS, ALL_ADMIN_PERMISSIONS, GLOBAL_ADMIN_PERMISSION }
|
|
|
29
29
|
// section: validation_exports
|
|
30
30
|
// Client-side validation utilities
|
|
31
31
|
export * from "./components/layouts/shared/utils/validation.js";
|
|
32
|
+
// section: google_oauth_exports
|
|
33
|
+
export { requestGoogleScopes } from "./lib/auth/request_google_scopes.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/google_token_test/index.tsx"],"names":[],"mappings":"AAeA,KAAK,0BAA0B,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EAAE,SAAS,EAAE,EAAE,0BAA0B,2CAsL9E"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// file_description: Test layout for Google OAuth token functionality
|
|
2
|
+
"use client";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useState, useEffect } from "react";
|
|
5
|
+
import { Button } from "../../ui/button.js";
|
|
6
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../ui/card.js";
|
|
7
|
+
import { cn } from "../../../lib/utils.js";
|
|
8
|
+
import { requestGoogleScopes } from "../../../lib/auth/request_google_scopes.js";
|
|
9
|
+
export function GoogleTokenTestLayout({ className }) {
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const [success, setSuccess] = useState(null);
|
|
13
|
+
const [authenticated, setAuthenticated] = useState(null);
|
|
14
|
+
const [tokenStatus, setTokenStatus] = useState(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
loadStatus();
|
|
17
|
+
}, []);
|
|
18
|
+
const loadStatus = async () => {
|
|
19
|
+
setLoading(true);
|
|
20
|
+
setError(null);
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch("/api/hazo_auth/google/token");
|
|
23
|
+
const body = await response.json();
|
|
24
|
+
if (response.status === 401) {
|
|
25
|
+
setAuthenticated(false);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
setAuthenticated(true);
|
|
29
|
+
if (body.ok) {
|
|
30
|
+
setTokenStatus(body.data);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
setError(body.error || "Failed to load token status");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
setError("Failed to load token status");
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
setLoading(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const handleConnect = () => {
|
|
44
|
+
requestGoogleScopes(["https://www.googleapis.com/auth/analytics.readonly"]);
|
|
45
|
+
};
|
|
46
|
+
const handleRevoke = async () => {
|
|
47
|
+
setLoading(true);
|
|
48
|
+
setError(null);
|
|
49
|
+
setSuccess(null);
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch("/api/hazo_auth/google/token", {
|
|
52
|
+
method: "DELETE",
|
|
53
|
+
});
|
|
54
|
+
const body = await response.json();
|
|
55
|
+
if (body.ok) {
|
|
56
|
+
setSuccess("Google token revoked successfully!");
|
|
57
|
+
await loadStatus();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
setError(body.error || "Failed to revoke token");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
setError("Failed to revoke token");
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
setLoading(false);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
if (authenticated === false) {
|
|
71
|
+
return (_jsx("div", { className: cn("w-full max-w-2xl mx-auto p-6", className), children: _jsx("div", { className: "p-4 border rounded-md bg-muted", children: "Please log in to test the Google OAuth token functionality." }) }));
|
|
72
|
+
}
|
|
73
|
+
return (_jsxs("div", { className: cn("w-full max-w-2xl mx-auto p-6 space-y-6", className), children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Google OAuth Token Status" }), _jsx(CardDescription, { children: "Test Google OAuth token storage, status retrieval, and revocation." })] }), _jsxs(CardContent, { className: "space-y-4", children: [error && (_jsx("div", { className: "p-4 border border-red-500 rounded-md bg-red-50 text-red-700", children: error })), success && (_jsx("div", { className: "p-4 border border-green-500 rounded-md bg-green-50 text-green-700", children: success })), _jsxs("div", { className: "pt-2", children: [_jsx("h4", { className: "text-sm font-medium mb-3", children: "Current Token Status:" }), tokenStatus === null ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Loading..." })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: "Connected:" }), tokenStatus.connected ? (_jsx("span", { className: "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800", children: "Connected" })) : (_jsx("span", { className: "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600", children: "Not Connected" }))] }), tokenStatus.connected && tokenStatus.scopes.length > 0 && (_jsxs("div", { children: [_jsx("span", { className: "text-sm font-medium", children: "Granted Scopes:" }), _jsx("ul", { className: "mt-1 ml-4 list-disc space-y-1", children: tokenStatus.scopes.map((scope) => (_jsx("li", { className: "text-sm text-muted-foreground font-mono", children: scope }, scope))) })] })), tokenStatus.connected && tokenStatus.expires_at && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: "Expires:" }), _jsx("span", { className: "text-sm text-muted-foreground", children: new Date(tokenStatus.expires_at).toLocaleString() })] }))] }))] }), _jsxs("div", { className: "flex flex-wrap gap-2 pt-2", children: [_jsx(Button, { onClick: handleConnect, disabled: loading, children: "Connect Google Account" }), _jsx(Button, { onClick: handleRevoke, variant: "destructive", disabled: loading || !(tokenStatus === null || tokenStatus === void 0 ? void 0 : tokenStatus.connected), children: "Revoke Google Token" }), _jsx(Button, { onClick: loadStatus, variant: "outline", disabled: loading, children: "Refresh Status" })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Note: triggers a real Google consent redirect. Requires HAZO_AUTH_GOOGLE_CLIENT_ID / SECRET and HAZO_AUTH_OAUTH_KEY_CURRENT to be configured." })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "API Reference" }) }), _jsxs(CardContent, { className: "space-y-3 text-sm", children: [_jsxs("div", { children: [_jsx("code", { className: "bg-muted px-2 py-1 rounded", children: "GET /api/hazo_auth/google/token" }), _jsx("p", { className: "text-muted-foreground mt-1", children: "Returns current Google OAuth token status" })] }), _jsxs("div", { children: [_jsx("code", { className: "bg-muted px-2 py-1 rounded", children: "DELETE /api/hazo_auth/google/token" }), _jsx("p", { className: "text-muted-foreground mt-1", children: "Revokes stored Google OAuth token (does not sign out)" })] })] })] })] }));
|
|
74
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/sidebar_layout_wrapper.tsx"],"names":[],"mappings":"AAwBA,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,EAAE,yBAAyB,
|
|
1
|
+
{"version":3,"file":"sidebar_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/sidebar_layout_wrapper.tsx"],"names":[],"mappings":"AAwBA,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,EAAE,yBAAyB,2CA2R3E"}
|
|
@@ -5,11 +5,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
// section: imports
|
|
6
6
|
import Link from "next/link";
|
|
7
7
|
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, SidebarInset, } from "../../../ui/sidebar.js";
|
|
8
|
-
import { LogIn, UserPlus, BookOpen, ExternalLink, Database, KeyRound, MailCheck, Key, User, ShieldCheck, CircleUserRound, FileJson, Building2, Palette, Settings2, Play } from "lucide-react";
|
|
8
|
+
import { LogIn, UserPlus, BookOpen, ExternalLink, Database, KeyRound, MailCheck, Key, User, ShieldCheck, CircleUserRound, FileJson, Building2, Palette, Settings2, Globe, Play } from "lucide-react";
|
|
9
9
|
import { use_auth_status } from "../hooks/use_auth_status.js";
|
|
10
10
|
import { ProfilePicMenu } from "./profile_pic_menu.js";
|
|
11
11
|
// section: component
|
|
12
12
|
export function SidebarLayoutWrapper({ children }) {
|
|
13
13
|
const authStatus = use_auth_status();
|
|
14
|
-
return (_jsx(SidebarProvider, { children: _jsxs("div", { className: "cls_sidebar_layout_wrapper flex min-h-screen w-full", children: [_jsxs(Sidebar, { children: [_jsx(SidebarHeader, { className: "cls_sidebar_layout_header", children: _jsx("div", { className: "cls_sidebar_layout_title flex items-center gap-2 px-2 py-4", children: _jsx("h1", { className: "cls_sidebar_layout_title_text text-lg font-semibold text-sidebar-foreground", children: "hazo auth" }) }) }), _jsxs(SidebarContent, { className: "cls_sidebar_layout_content", children: [_jsxs(SidebarGroup, { className: "cls_sidebar_layout_test_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Test components" }), _jsxs(SidebarMenu, { className: "cls_sidebar_layout_test_menu", children: [_jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_login_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/login", className: "cls_sidebar_layout_test_login_link flex items-center gap-2", "aria-label": "Test login layout component", children: [_jsx(LogIn, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test login" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_register_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/register", className: "cls_sidebar_layout_test_register_link flex items-center gap-2", "aria-label": "Test register layout component", children: [_jsx(UserPlus, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test register" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_forgot_password_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/forgot_password", className: "cls_sidebar_layout_test_forgot_password_link flex items-center gap-2", "aria-label": "Test forgot password layout component", children: [_jsx(KeyRound, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test forgot password" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_reset_password_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/reset_password", className: "cls_sidebar_layout_test_reset_password_link flex items-center gap-2", "aria-label": "Test reset password layout component", children: [_jsx(Key, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test reset password" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_email_verification_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/verify_email", className: "cls_sidebar_layout_test_email_verification_link flex items-center gap-2", "aria-label": "Test email verification layout component", children: [_jsx(MailCheck, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test email verification" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_sqlite_admin_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_connect/sqlite_admin", className: "cls_sidebar_layout_sqlite_admin_link flex items-center gap-2", "aria-label": "Open SQLite admin UI to browse and edit database", children: [_jsx(Database, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "SQLite Admin" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_user_management_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/user_management", className: "cls_sidebar_layout_user_management_link flex items-center gap-2", "aria-label": "Open User Management to manage users, roles, and permissions", children: [_jsx(User, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "User Management" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_rbac_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/rbac_test", className: "cls_sidebar_layout_rbac_test_link flex items-center gap-2", "aria-label": "Test RBAC and HRBAC access control", children: [_jsx(ShieldCheck, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "RBAC/HRBAC Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_profile_stamp_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/profile_stamp_test", className: "cls_sidebar_layout_profile_stamp_test_link flex items-center gap-2", "aria-label": "Test ProfileStamp component", children: [_jsx(CircleUserRound, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "ProfileStamp Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_app_user_data_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/app_user_data_test", className: "cls_sidebar_layout_app_user_data_test_link flex items-center gap-2", "aria-label": "Test app_user_data JSON storage", children: [_jsx(FileJson, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "App User Data Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_create_firm_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/create_firm", className: "cls_sidebar_layout_create_firm_link flex items-center gap-2", "aria-label": "Test create firm flow", children: [_jsx(Building2, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Create Firm" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_edit_firm_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/edit_firm", className: "cls_sidebar_layout_edit_firm_link flex items-center gap-2", "aria-label": "Test branding editor for firm", children: [_jsx(Palette, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Edit Firm" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_relationships_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/relationships", className: "cls_sidebar_layout_relationships_link flex items-center gap-2", "aria-label": "Test relationship accounts", children: [_jsx(User, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Relationships" })] }) }) })] })] }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_testing_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Testing" }), _jsx(SidebarMenu, { className: "cls_sidebar_layout_testing_menu", children: _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_scenarios_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/test_scenarios", className: "cls_sidebar_layout_test_scenarios_link flex items-center gap-2", "aria-label": "Test scenarios", children: [_jsx(Settings2, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test Scenarios" })] }) }) }) })] }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_auto_test_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Auto Tests" }), _jsx(SidebarMenu, { className: "cls_sidebar_layout_auto_test_menu", children: _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_auto_test_runner_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/auto_test", className: "cls_sidebar_layout_auto_test_runner_link flex items-center gap-2", "aria-label": "Run automated tests", children: [_jsx(Play, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Auto Test Runner" })] }) }) }) })] }), _jsx(ProfilePicMenu, { variant: "sidebar", avatar_size: "sm", className: "cls_sidebar_layout_profile_menu", sidebar_group_label: "Account" }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_resources_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Resources" }), _jsxs(SidebarMenu, { className: "cls_sidebar_layout_resources_menu", children: [_jsx(SidebarMenuItem, { className: "cls_sidebar_layout_storybook_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs("a", { href: "http://localhost:6006", target: "_blank", rel: "noopener noreferrer", className: "cls_sidebar_layout_storybook_link flex items-center gap-2", "aria-label": "Open Storybook preview for reusable components", children: [_jsx(BookOpen, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Storybook" }), _jsx(ExternalLink, { className: "ml-auto h-3 w-3", "aria-hidden": "true" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_docs_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs("a", { href: "https://ui.shadcn.com/docs", target: "_blank", rel: "noopener noreferrer", className: "cls_sidebar_layout_docs_link flex items-center gap-2", "aria-label": "Review shadcn documentation for styling guidance", children: [_jsx(BookOpen, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Shadcn docs" }), _jsx(ExternalLink, { className: "ml-auto h-3 w-3", "aria-hidden": "true" })] }) }) })] })] })] })] }), _jsxs(SidebarInset, { className: "cls_sidebar_layout_inset", children: [_jsxs("header", { className: "cls_sidebar_layout_main_header flex h-16 shrink-0 items-center gap-2 border-b px-4", children: [_jsx(SidebarTrigger, { className: "cls_sidebar_layout_trigger" }), _jsx("div", { className: "cls_sidebar_layout_main_header_content flex flex-1 items-center gap-2", children: _jsx("h2", { className: "cls_sidebar_layout_main_title text-lg font-semibold text-foreground", children: "hazo reusable ui library workspace" }) }), _jsx(ProfilePicMenu, { className: "cls_sidebar_layout_auth_status", avatar_size: "sm" })] }), _jsx("main", { className: "cls_sidebar_layout_main_content flex flex-1 items-center justify-center p-6", children: children })] })] }) }));
|
|
14
|
+
return (_jsx(SidebarProvider, { children: _jsxs("div", { className: "cls_sidebar_layout_wrapper flex min-h-screen w-full", children: [_jsxs(Sidebar, { children: [_jsx(SidebarHeader, { className: "cls_sidebar_layout_header", children: _jsx("div", { className: "cls_sidebar_layout_title flex items-center gap-2 px-2 py-4", children: _jsx("h1", { className: "cls_sidebar_layout_title_text text-lg font-semibold text-sidebar-foreground", children: "hazo auth" }) }) }), _jsxs(SidebarContent, { className: "cls_sidebar_layout_content", children: [_jsxs(SidebarGroup, { className: "cls_sidebar_layout_test_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Test components" }), _jsxs(SidebarMenu, { className: "cls_sidebar_layout_test_menu", children: [_jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_login_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/login", className: "cls_sidebar_layout_test_login_link flex items-center gap-2", "aria-label": "Test login layout component", children: [_jsx(LogIn, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test login" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_register_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/register", className: "cls_sidebar_layout_test_register_link flex items-center gap-2", "aria-label": "Test register layout component", children: [_jsx(UserPlus, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test register" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_forgot_password_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/forgot_password", className: "cls_sidebar_layout_test_forgot_password_link flex items-center gap-2", "aria-label": "Test forgot password layout component", children: [_jsx(KeyRound, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test forgot password" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_reset_password_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/reset_password", className: "cls_sidebar_layout_test_reset_password_link flex items-center gap-2", "aria-label": "Test reset password layout component", children: [_jsx(Key, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test reset password" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_email_verification_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/verify_email", className: "cls_sidebar_layout_test_email_verification_link flex items-center gap-2", "aria-label": "Test email verification layout component", children: [_jsx(MailCheck, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test email verification" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_sqlite_admin_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_connect/sqlite_admin", className: "cls_sidebar_layout_sqlite_admin_link flex items-center gap-2", "aria-label": "Open SQLite admin UI to browse and edit database", children: [_jsx(Database, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "SQLite Admin" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_user_management_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/user_management", className: "cls_sidebar_layout_user_management_link flex items-center gap-2", "aria-label": "Open User Management to manage users, roles, and permissions", children: [_jsx(User, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "User Management" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_rbac_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/rbac_test", className: "cls_sidebar_layout_rbac_test_link flex items-center gap-2", "aria-label": "Test RBAC and HRBAC access control", children: [_jsx(ShieldCheck, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "RBAC/HRBAC Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_profile_stamp_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/profile_stamp_test", className: "cls_sidebar_layout_profile_stamp_test_link flex items-center gap-2", "aria-label": "Test ProfileStamp component", children: [_jsx(CircleUserRound, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "ProfileStamp Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_app_user_data_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/app_user_data_test", className: "cls_sidebar_layout_app_user_data_test_link flex items-center gap-2", "aria-label": "Test app_user_data JSON storage", children: [_jsx(FileJson, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "App User Data Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_google_token_test_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/google_token_test", className: "cls_sidebar_layout_google_token_test_link flex items-center gap-2", "aria-label": "Test Google OAuth token storage and revocation", children: [_jsx(Globe, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Google Token Test" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_create_firm_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/create_firm", className: "cls_sidebar_layout_create_firm_link flex items-center gap-2", "aria-label": "Test create firm flow", children: [_jsx(Building2, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Create Firm" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_edit_firm_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/edit_firm", className: "cls_sidebar_layout_edit_firm_link flex items-center gap-2", "aria-label": "Test branding editor for firm", children: [_jsx(Palette, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Edit Firm" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_relationships_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/relationships", className: "cls_sidebar_layout_relationships_link flex items-center gap-2", "aria-label": "Test relationship accounts", children: [_jsx(User, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Relationships" })] }) }) })] })] }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_testing_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Testing" }), _jsx(SidebarMenu, { className: "cls_sidebar_layout_testing_menu", children: _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_test_scenarios_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/test_scenarios", className: "cls_sidebar_layout_test_scenarios_link flex items-center gap-2", "aria-label": "Test scenarios", children: [_jsx(Settings2, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Test Scenarios" })] }) }) }) })] }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_auto_test_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Auto Tests" }), _jsx(SidebarMenu, { className: "cls_sidebar_layout_auto_test_menu", children: _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_auto_test_runner_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs(Link, { href: "/hazo_auth/autotest", className: "cls_sidebar_layout_auto_test_runner_link flex items-center gap-2", "aria-label": "Run browser autotests", children: [_jsx(Play, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Browser Autotest" })] }) }) }) })] }), _jsx(ProfilePicMenu, { variant: "sidebar", avatar_size: "sm", className: "cls_sidebar_layout_profile_menu", sidebar_group_label: "Account" }), _jsxs(SidebarGroup, { className: "cls_sidebar_layout_resources_group", children: [_jsx(SidebarGroupLabel, { className: "cls_sidebar_layout_group_label", children: "Resources" }), _jsxs(SidebarMenu, { className: "cls_sidebar_layout_resources_menu", children: [_jsx(SidebarMenuItem, { className: "cls_sidebar_layout_storybook_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs("a", { href: "http://localhost:6006", target: "_blank", rel: "noopener noreferrer", className: "cls_sidebar_layout_storybook_link flex items-center gap-2", "aria-label": "Open Storybook preview for reusable components", children: [_jsx(BookOpen, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Storybook" }), _jsx(ExternalLink, { className: "ml-auto h-3 w-3", "aria-hidden": "true" })] }) }) }), _jsx(SidebarMenuItem, { className: "cls_sidebar_layout_docs_item", children: _jsx(SidebarMenuButton, { asChild: true, children: _jsxs("a", { href: "https://ui.shadcn.com/docs", target: "_blank", rel: "noopener noreferrer", className: "cls_sidebar_layout_docs_link flex items-center gap-2", "aria-label": "Review shadcn documentation for styling guidance", children: [_jsx(BookOpen, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: "Shadcn docs" }), _jsx(ExternalLink, { className: "ml-auto h-3 w-3", "aria-hidden": "true" })] }) }) })] })] })] })] }), _jsxs(SidebarInset, { className: "cls_sidebar_layout_inset", children: [_jsxs("header", { className: "cls_sidebar_layout_main_header flex h-16 shrink-0 items-center gap-2 border-b px-4", children: [_jsx(SidebarTrigger, { className: "cls_sidebar_layout_trigger" }), _jsx("div", { className: "cls_sidebar_layout_main_header_content flex flex-1 items-center gap-2", children: _jsx("h2", { className: "cls_sidebar_layout_main_title text-lg font-semibold text-foreground", children: "hazo reusable ui library workspace" }) }), _jsx(ProfilePicMenu, { className: "cls_sidebar_layout_auth_status", avatar_size: "sm" })] }), _jsx("main", { className: "cls_sidebar_layout_main_content flex flex-1 items-center justify-center p-6", children: children })] })] }) }));
|
|
15
15
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
3
|
declare const buttonVariants: (props?: ({
|
|
4
|
-
variant?: "
|
|
4
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
|
|
5
5
|
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
declare const InputOTP: React.ForwardRefExoticComponent<(Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "
|
|
2
|
+
declare const InputOTP: React.ForwardRefExoticComponent<(Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "maxLength" | "textAlign" | "onComplete" | "pushPasswordManagerStrategy" | "pasteTransformer" | "containerClassName" | "noScriptCSSFallback"> & {
|
|
3
3
|
value?: string;
|
|
4
4
|
onChange?: (newValue: string) => unknown;
|
|
5
5
|
maxLength: number;
|
|
@@ -12,7 +12,7 @@ declare const InputOTP: React.ForwardRefExoticComponent<(Omit<Omit<React.InputHT
|
|
|
12
12
|
} & {
|
|
13
13
|
render: (props: import("input-otp").RenderProps) => React.ReactNode;
|
|
14
14
|
children?: never;
|
|
15
|
-
} & React.RefAttributes<HTMLInputElement>, "ref"> | Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "
|
|
15
|
+
} & React.RefAttributes<HTMLInputElement>, "ref"> | Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "maxLength" | "textAlign" | "onComplete" | "pushPasswordManagerStrategy" | "pasteTransformer" | "containerClassName" | "noScriptCSSFallback"> & {
|
|
16
16
|
value?: string;
|
|
17
17
|
onChange?: (newValue: string) => unknown;
|
|
18
18
|
maxLength: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export { AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedErr
|
|
|
6
6
|
export type { LegalDoc, LegalAcceptanceRecord, LegalAcceptanceMap } from './lib/legal/legal_docs_types';
|
|
7
7
|
export { cn, merge_class_names } from "./lib/utils.js";
|
|
8
8
|
export { HAZO_AUTH_PERMISSIONS, ALL_ADMIN_PERMISSIONS, GLOBAL_ADMIN_PERMISSION } from "./lib/constants.js";
|
|
9
|
+
export { requestGoogleScopes } from "./lib/auth/request_google_scopes.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAG5C,cAAc,oBAAoB,CAAC;AAGnC,YAAY,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGxG,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAG5C,cAAc,oBAAoB,CAAC;AAGnC,YAAY,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGxG,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAGxG,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -14,3 +14,5 @@ export { AuthenticationRequiredError, TenantRequiredError, TenantAccessDeniedErr
|
|
|
14
14
|
export { cn, merge_class_names } from "./lib/utils.js";
|
|
15
15
|
// section: constant_exports
|
|
16
16
|
export { HAZO_AUTH_PERMISSIONS, ALL_ADMIN_PERMISSIONS, GLOBAL_ADMIN_PERMISSION } from "./lib/constants.js";
|
|
17
|
+
// section: google_oauth_exports
|
|
18
|
+
export { requestGoogleScopes } from "./lib/auth/request_google_scopes.js";
|