hazo_auth 10.4.9 → 10.4.10
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/cli-src/cli/validate.ts +118 -0
- package/cli-src/lib/auth/auth_error_help.ts +67 -0
- package/cli-src/lib/auth/nextauth_config.ts +5 -0
- package/cli-src/lib/oauth_preflight.server.ts +188 -0
- package/cli-src/lib/oauth_routes.ts +21 -0
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +109 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/lib/auth/auth_error_help.d.ts +14 -0
- package/dist/lib/auth/auth_error_help.d.ts.map +1 -0
- package/dist/lib/auth/auth_error_help.js +53 -0
- package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.js +4 -0
- package/dist/lib/oauth_preflight.server.d.ts +35 -0
- package/dist/lib/oauth_preflight.server.d.ts.map +1 -0
- package/dist/lib/oauth_preflight.server.js +162 -0
- package/dist/lib/oauth_routes.d.ts +10 -0
- package/dist/lib/oauth_routes.d.ts.map +1 -1
- package/dist/lib/oauth_routes.js +18 -0
- package/dist/server-lib.d.ts +2 -0
- package/dist/server-lib.d.ts.map +1 -1
- package/dist/server-lib.js +4 -0
- package/package.json +1 -1
package/cli-src/cli/validate.ts
CHANGED
|
@@ -305,6 +305,113 @@ function check_env_vars(): CheckResult[] {
|
|
|
305
305
|
return results;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
// Canonical values live in src/lib/oauth_routes.ts; inlined here because the CLI
|
|
309
|
+
// compiles as an isolated tree and must not import the server-only lib.
|
|
310
|
+
const GOOGLE_NEXTAUTH_CALLBACK_PATH = "/api/auth/callback/google";
|
|
311
|
+
const FACEBOOK_NEXTAUTH_CALLBACK_PATH = "/api/auth/callback/facebook";
|
|
312
|
+
|
|
313
|
+
function read_oauth_origin(): { origin: string; resolved: boolean } {
|
|
314
|
+
const nextauth_url = process.env.NEXTAUTH_URL;
|
|
315
|
+
if (nextauth_url && nextauth_url.trim().length > 0) {
|
|
316
|
+
return { origin: nextauth_url.trim().replace(/\/+$/, ""), resolved: true };
|
|
317
|
+
}
|
|
318
|
+
const port = process.env.PORT ?? "3000";
|
|
319
|
+
return { origin: `http://localhost:${port}`, resolved: false };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function check_oauth(project_root: string): CheckResult[] {
|
|
323
|
+
const results: CheckResult[] = [];
|
|
324
|
+
|
|
325
|
+
const hazo_config_path = path.join(project_root, "config", "hazo_auth_config.ini");
|
|
326
|
+
const hazo_config = read_ini_file(hazo_config_path);
|
|
327
|
+
const oauth_section = hazo_config?.["hazo_auth__oauth"] ?? {};
|
|
328
|
+
const enable_google = (oauth_section["enable_google"] ?? "").toLowerCase() === "true";
|
|
329
|
+
const enable_facebook = (oauth_section["enable_facebook_oauth"] ?? "").toLowerCase() === "true";
|
|
330
|
+
|
|
331
|
+
if (!enable_google && !enable_facebook) {
|
|
332
|
+
results.push({
|
|
333
|
+
name: "OAuth providers",
|
|
334
|
+
status: "pass",
|
|
335
|
+
message: "No OAuth providers enabled (email/password only)",
|
|
336
|
+
});
|
|
337
|
+
return results;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const { origin, resolved } = read_oauth_origin();
|
|
341
|
+
|
|
342
|
+
// NEXTAUTH_URL — source of the redirect URI.
|
|
343
|
+
results.push(
|
|
344
|
+
resolved
|
|
345
|
+
? { name: "NEXTAUTH_URL", status: "pass", message: origin }
|
|
346
|
+
: {
|
|
347
|
+
name: "NEXTAUTH_URL",
|
|
348
|
+
status: "fail",
|
|
349
|
+
message: `Not set. The redirect URI below is a guess from PORT. Set NEXTAUTH_URL=${origin}`,
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// NEXTAUTH_SECRET — required for OAuth state/session signing.
|
|
354
|
+
const secret = process.env.NEXTAUTH_SECRET;
|
|
355
|
+
if (!secret || secret.length === 0) {
|
|
356
|
+
results.push({
|
|
357
|
+
name: "NEXTAUTH_SECRET",
|
|
358
|
+
status: "fail",
|
|
359
|
+
message: "Not set. Generate: openssl rand -base64 32",
|
|
360
|
+
});
|
|
361
|
+
} else if (secret.length < 32) {
|
|
362
|
+
results.push({
|
|
363
|
+
name: "NEXTAUTH_SECRET",
|
|
364
|
+
status: "warn",
|
|
365
|
+
message: `Only ${secret.length} chars; use at least 32. Generate: openssl rand -base64 32`,
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
results.push({ name: "NEXTAUTH_SECRET", status: "pass", message: "Set" });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (enable_google) {
|
|
372
|
+
const id = process.env.HAZO_AUTH_GOOGLE_CLIENT_ID;
|
|
373
|
+
const cs = process.env.HAZO_AUTH_GOOGLE_CLIENT_SECRET;
|
|
374
|
+
const missing = [
|
|
375
|
+
!id ? "HAZO_AUTH_GOOGLE_CLIENT_ID" : null,
|
|
376
|
+
!cs ? "HAZO_AUTH_GOOGLE_CLIENT_SECRET" : null,
|
|
377
|
+
].filter(Boolean);
|
|
378
|
+
results.push(
|
|
379
|
+
missing.length === 0
|
|
380
|
+
? { name: "Google OAuth credentials", status: "pass", message: "Set" }
|
|
381
|
+
: {
|
|
382
|
+
name: "Google OAuth credentials",
|
|
383
|
+
status: "fail",
|
|
384
|
+
message: `enable_google=true but missing: ${missing.join(", ")}`,
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return results;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Returns the exact redirect URIs to register, per enabled provider. Printed as
|
|
394
|
+
* a copy-pasteable box so the dev never guesses the port/path — the single thing
|
|
395
|
+
* that prevents redirect_uri_mismatch.
|
|
396
|
+
*/
|
|
397
|
+
function get_oauth_redirect_lines(project_root: string): string[] {
|
|
398
|
+
const hazo_config = read_ini_file(path.join(project_root, "config", "hazo_auth_config.ini"));
|
|
399
|
+
const oauth_section = hazo_config?.["hazo_auth__oauth"] ?? {};
|
|
400
|
+
const enable_google = (oauth_section["enable_google"] ?? "").toLowerCase() === "true";
|
|
401
|
+
const enable_facebook = (oauth_section["enable_facebook_oauth"] ?? "").toLowerCase() === "true";
|
|
402
|
+
if (!enable_google && !enable_facebook) return [];
|
|
403
|
+
|
|
404
|
+
const { origin, resolved } = read_oauth_origin();
|
|
405
|
+
const lines: string[] = [];
|
|
406
|
+
if (enable_google) lines.push(` google → ${origin}${GOOGLE_NEXTAUTH_CALLBACK_PATH}`);
|
|
407
|
+
if (enable_facebook) lines.push(` facebook → ${origin}${FACEBOOK_NEXTAUTH_CALLBACK_PATH}`);
|
|
408
|
+
if (!resolved) {
|
|
409
|
+
lines.push("");
|
|
410
|
+
lines.push(" ⚠ NEXTAUTH_URL not set — above is a guess from PORT. Set it and re-register.");
|
|
411
|
+
}
|
|
412
|
+
return lines;
|
|
413
|
+
}
|
|
414
|
+
|
|
308
415
|
function check_api_routes(project_root: string): CheckResult[] {
|
|
309
416
|
const results: CheckResult[] = [];
|
|
310
417
|
|
|
@@ -743,6 +850,17 @@ export function run_validation(): ValidationSummary {
|
|
|
743
850
|
all_results.push(...env_results);
|
|
744
851
|
console.log();
|
|
745
852
|
|
|
853
|
+
console.log("\x1b[1m🔑 OAuth Providers\x1b[0m");
|
|
854
|
+
const oauth_results = check_oauth(project_root);
|
|
855
|
+
oauth_results.forEach(print_result);
|
|
856
|
+
all_results.push(...oauth_results);
|
|
857
|
+
const redirect_lines = get_oauth_redirect_lines(project_root);
|
|
858
|
+
if (redirect_lines.length > 0) {
|
|
859
|
+
console.log("\n Register these redirect URIs in the provider console (must match exactly):");
|
|
860
|
+
redirect_lines.forEach(line => console.log(line));
|
|
861
|
+
}
|
|
862
|
+
console.log();
|
|
863
|
+
|
|
746
864
|
console.log("\x1b[1m🗄️ Database\x1b[0m");
|
|
747
865
|
const db_results = check_database(project_root);
|
|
748
866
|
db_results.forEach(print_result);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// file_description: client-safe decoder mapping NextAuth/hazo_auth `?error=` codes
|
|
2
|
+
// to human-readable guidance. Use on a login/error page to replace opaque codes.
|
|
3
|
+
|
|
4
|
+
export type AuthErrorHelp = {
|
|
5
|
+
/** The raw error code from the `?error=` query param. */
|
|
6
|
+
code: string;
|
|
7
|
+
/** Short, user-facing summary. */
|
|
8
|
+
title: string;
|
|
9
|
+
/** Developer-facing detail and remediation. */
|
|
10
|
+
detail: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const KNOWN: Record<string, Omit<AuthErrorHelp, "code">> = {
|
|
14
|
+
Configuration: {
|
|
15
|
+
title: "Sign-in is misconfigured.",
|
|
16
|
+
detail:
|
|
17
|
+
"The server's OAuth/NextAuth config is incomplete. Check NEXTAUTH_SECRET, NEXTAUTH_URL, and provider credentials. Run `npx hazo_auth validate`.",
|
|
18
|
+
},
|
|
19
|
+
AccessDenied: {
|
|
20
|
+
title: "Access denied.",
|
|
21
|
+
detail: "You don't have permission to sign in, or you cancelled the consent screen.",
|
|
22
|
+
},
|
|
23
|
+
Verification: {
|
|
24
|
+
title: "This sign-in link has expired.",
|
|
25
|
+
detail: "The link was already used or timed out. Request a new one.",
|
|
26
|
+
},
|
|
27
|
+
OAuthSignin: {
|
|
28
|
+
title: "Couldn't start sign-in.",
|
|
29
|
+
detail: "Error constructing the provider authorization URL. Check provider credentials.",
|
|
30
|
+
},
|
|
31
|
+
OAuthCallback: {
|
|
32
|
+
title: "Sign-in callback failed.",
|
|
33
|
+
detail:
|
|
34
|
+
"The provider rejected the callback. Most often the registered redirect URI doesn't match. Confirm the provider console lists the exact URI hazo_auth prints at boot.",
|
|
35
|
+
},
|
|
36
|
+
OAuthAccountNotLinked: {
|
|
37
|
+
title: "This email is already linked to a different sign-in method.",
|
|
38
|
+
detail: "Sign in with the original method, then link the new provider from account settings.",
|
|
39
|
+
},
|
|
40
|
+
GoogleTokenStorageUnconfigured: {
|
|
41
|
+
title: "Signed in, but offline Google access is unavailable.",
|
|
42
|
+
detail:
|
|
43
|
+
"Refresh-token storage needs HAZO_AUTH_OAUTH_KEY_CURRENT and HAZO_AUTH_OAUTH_KEY_<ID>. Login still works; set these only if you need offline Google API calls.",
|
|
44
|
+
},
|
|
45
|
+
redirect_uri_mismatch: {
|
|
46
|
+
title: "Sign-in blocked: redirect URI mismatch.",
|
|
47
|
+
detail:
|
|
48
|
+
"The redirect URI sent to the provider isn't registered. Register the exact URI hazo_auth prints at boot (origin = NEXTAUTH_URL + /api/auth/callback/<provider>). Note: providers usually show this on their own page, before returning to the app.",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolves a NextAuth/hazo_auth error code to friendly guidance. Unknown codes
|
|
54
|
+
* get a generic fallback so callers can always render something useful.
|
|
55
|
+
*/
|
|
56
|
+
export function describeAuthError(code: string | null | undefined): AuthErrorHelp {
|
|
57
|
+
const key = (code ?? "").trim();
|
|
58
|
+
const hit = key ? KNOWN[key] : undefined;
|
|
59
|
+
if (hit) return { code: key, ...hit };
|
|
60
|
+
return {
|
|
61
|
+
code: key || "Unknown",
|
|
62
|
+
title: "Sign-in failed.",
|
|
63
|
+
detail: key
|
|
64
|
+
? `Unrecognised error code "${key}". Check the server logs for details.`
|
|
65
|
+
: "No error code provided. Check the server logs for details.",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -13,6 +13,7 @@ import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
|
13
13
|
import { create_app_logger } from "../app_logger.js";
|
|
14
14
|
import { store_google_oauth_token, GoogleTokenStorageUnconfigured } from "../services/google_token_service.js";
|
|
15
15
|
import { GOOGLE_OAUTH_CALLBACK_PATH, FACEBOOK_OAUTH_CALLBACK_PATH } from "../oauth_routes.js";
|
|
16
|
+
import { logOAuthPreflight } from "../oauth_preflight.server.js";
|
|
16
17
|
|
|
17
18
|
// section: types
|
|
18
19
|
export type NextAuthCallbackUser = {
|
|
@@ -48,6 +49,10 @@ export type NextAuthCallbackProfile = {
|
|
|
48
49
|
* @returns NextAuth configuration object
|
|
49
50
|
*/
|
|
50
51
|
export function get_nextauth_config(): AuthOptions {
|
|
52
|
+
// Print enabled-provider redirect URIs + flag missing env once per process.
|
|
53
|
+
// No-op after the first call; safe to run on every config build.
|
|
54
|
+
logOAuthPreflight();
|
|
55
|
+
|
|
51
56
|
const oauth_config = get_oauth_config();
|
|
52
57
|
const providers = [];
|
|
53
58
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// file_description: server-only OAuth preflight — validates env and prints the exact
|
|
2
|
+
// redirect URIs to register, killing the recurring `redirect_uri_mismatch` for new apps.
|
|
3
|
+
import "server-only";
|
|
4
|
+
|
|
5
|
+
import { get_oauth_config } from "./oauth_config.server.js";
|
|
6
|
+
import { googleRedirectUri, facebookRedirectUri } from "./oauth_routes.js";
|
|
7
|
+
import { create_app_logger } from "./app_logger.js";
|
|
8
|
+
|
|
9
|
+
// section: types
|
|
10
|
+
export type PreflightStatus = "pass" | "warn" | "fail";
|
|
11
|
+
|
|
12
|
+
export type PreflightCheck = {
|
|
13
|
+
name: string;
|
|
14
|
+
status: PreflightStatus;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type OAuthPreflightReport = {
|
|
19
|
+
/** Origin used to derive redirect URIs (NEXTAUTH_URL, or a fallback). */
|
|
20
|
+
origin: string;
|
|
21
|
+
/** True when NEXTAUTH_URL was actually set (vs. a guessed fallback). */
|
|
22
|
+
originResolved: boolean;
|
|
23
|
+
/** Absolute redirect URIs to paste into the provider console, per enabled provider. */
|
|
24
|
+
redirectUris: { provider: "google" | "facebook"; uri: string }[];
|
|
25
|
+
checks: PreflightCheck[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// section: helpers
|
|
29
|
+
const MIN_SECRET_LEN = 32;
|
|
30
|
+
|
|
31
|
+
function resolve_origin(): { origin: string; resolved: boolean } {
|
|
32
|
+
const nextauth_url = process.env.NEXTAUTH_URL;
|
|
33
|
+
if (nextauth_url && nextauth_url.trim().length > 0) {
|
|
34
|
+
return { origin: nextauth_url.trim().replace(/\/+$/, ""), resolved: true };
|
|
35
|
+
}
|
|
36
|
+
// Fallback so we can still print a best-guess URI to register.
|
|
37
|
+
const port = process.env.PORT ?? "3000";
|
|
38
|
+
return { origin: `http://localhost:${port}`, resolved: false };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Builds a structured OAuth config report without printing. Safe to call
|
|
43
|
+
* anywhere server-side (CLI, route, instrumentation). Pure config inspection —
|
|
44
|
+
* no DB or network access.
|
|
45
|
+
*/
|
|
46
|
+
export function getOAuthPreflightReport(): OAuthPreflightReport {
|
|
47
|
+
const checks: PreflightCheck[] = [];
|
|
48
|
+
const { origin, resolved } = resolve_origin();
|
|
49
|
+
const oauth = get_oauth_config();
|
|
50
|
+
const redirectUris: OAuthPreflightReport["redirectUris"] = [];
|
|
51
|
+
|
|
52
|
+
// NEXTAUTH_URL — the source of the redirect URI.
|
|
53
|
+
if (resolved) {
|
|
54
|
+
checks.push({ name: "NEXTAUTH_URL", status: "pass", message: origin });
|
|
55
|
+
} else {
|
|
56
|
+
checks.push({
|
|
57
|
+
name: "NEXTAUTH_URL",
|
|
58
|
+
status: "fail",
|
|
59
|
+
message: `Not set. NextAuth derives the OAuth redirect URI from it. Set NEXTAUTH_URL=${origin} (matching the port you serve on).`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// NEXTAUTH_URL port vs. the port the app is told to listen on.
|
|
64
|
+
const declared_port = process.env.PORT;
|
|
65
|
+
if (resolved && declared_port) {
|
|
66
|
+
let url_port: string | null = null;
|
|
67
|
+
try {
|
|
68
|
+
const u = new URL(origin);
|
|
69
|
+
url_port = u.port || (u.protocol === "https:" ? "443" : "80");
|
|
70
|
+
} catch {
|
|
71
|
+
url_port = null;
|
|
72
|
+
}
|
|
73
|
+
if (url_port && url_port !== declared_port) {
|
|
74
|
+
checks.push({
|
|
75
|
+
name: "Port match",
|
|
76
|
+
status: "warn",
|
|
77
|
+
message: `NEXTAUTH_URL port (${url_port}) differs from PORT (${declared_port}). The redirect URI uses ${url_port}; if you serve on ${declared_port}, OAuth will fail with redirect_uri_mismatch.`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// NEXTAUTH_SECRET — required for OAuth session/state signing.
|
|
83
|
+
const secret = process.env.NEXTAUTH_SECRET;
|
|
84
|
+
if (!secret || secret.length === 0) {
|
|
85
|
+
checks.push({
|
|
86
|
+
name: "NEXTAUTH_SECRET",
|
|
87
|
+
status: "fail",
|
|
88
|
+
message: "Not set. Generate one: openssl rand -base64 32",
|
|
89
|
+
});
|
|
90
|
+
} else if (secret.length < MIN_SECRET_LEN) {
|
|
91
|
+
checks.push({
|
|
92
|
+
name: "NEXTAUTH_SECRET",
|
|
93
|
+
status: "warn",
|
|
94
|
+
message: `Only ${secret.length} chars; use at least ${MIN_SECRET_LEN}. Generate: openssl rand -base64 32`,
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
checks.push({ name: "NEXTAUTH_SECRET", status: "pass", message: "Set" });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Google provider.
|
|
101
|
+
if (oauth.enable_google) {
|
|
102
|
+
const id = process.env.HAZO_AUTH_GOOGLE_CLIENT_ID;
|
|
103
|
+
const secretG = process.env.HAZO_AUTH_GOOGLE_CLIENT_SECRET;
|
|
104
|
+
if (!id || !secretG) {
|
|
105
|
+
const missing = [
|
|
106
|
+
!id ? "HAZO_AUTH_GOOGLE_CLIENT_ID" : null,
|
|
107
|
+
!secretG ? "HAZO_AUTH_GOOGLE_CLIENT_SECRET" : null,
|
|
108
|
+
].filter(Boolean);
|
|
109
|
+
checks.push({
|
|
110
|
+
name: "Google OAuth credentials",
|
|
111
|
+
status: "fail",
|
|
112
|
+
message: `enable_google=true but missing: ${missing.join(", ")} (from Google Cloud Console).`,
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
checks.push({ name: "Google OAuth credentials", status: "pass", message: "Set" });
|
|
116
|
+
}
|
|
117
|
+
redirectUris.push({ provider: "google", uri: googleRedirectUri(origin) });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Facebook provider.
|
|
121
|
+
if (oauth.enable_facebook_oauth) {
|
|
122
|
+
const appId = oauth.facebook_app_id || process.env.HAZO_AUTH_FACEBOOK_APP_ID;
|
|
123
|
+
const appSecret = oauth.facebook_app_secret || process.env.HAZO_AUTH_FACEBOOK_APP_SECRET;
|
|
124
|
+
if (!appId || !appSecret) {
|
|
125
|
+
checks.push({
|
|
126
|
+
name: "Facebook OAuth credentials",
|
|
127
|
+
status: "fail",
|
|
128
|
+
message: "enable_facebook_oauth=true but app_id/app_secret missing.",
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
checks.push({ name: "Facebook OAuth credentials", status: "pass", message: "Set" });
|
|
132
|
+
}
|
|
133
|
+
redirectUris.push({ provider: "facebook", uri: facebookRedirectUri(origin) });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { origin, originResolved: resolved, redirectUris, checks };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// section: boot logger
|
|
140
|
+
let _logged_once = false;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Logs the OAuth preflight report once per process. Call from a Next.js
|
|
144
|
+
* `instrumentation.ts` `register()` hook to surface config problems — and the
|
|
145
|
+
* exact redirect URI to register — the moment the server boots, before anyone
|
|
146
|
+
* hits a broken sign-in.
|
|
147
|
+
*
|
|
148
|
+
* Idempotent: subsequent calls in the same process are no-ops.
|
|
149
|
+
*/
|
|
150
|
+
export function logOAuthPreflight(): OAuthPreflightReport | null {
|
|
151
|
+
if (_logged_once) return null;
|
|
152
|
+
_logged_once = true;
|
|
153
|
+
|
|
154
|
+
const report = getOAuthPreflightReport();
|
|
155
|
+
if (report.redirectUris.length === 0) return report; // No OAuth providers enabled.
|
|
156
|
+
|
|
157
|
+
const logger = create_app_logger();
|
|
158
|
+
|
|
159
|
+
for (const check of report.checks) {
|
|
160
|
+
if (check.status === "fail") {
|
|
161
|
+
logger.error("oauth_preflight_check_failed", { check: check.name, detail: check.message });
|
|
162
|
+
} else if (check.status === "warn") {
|
|
163
|
+
logger.warn("oauth_preflight_check_warning", { check: check.name, detail: check.message });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Highlighted, copy-pasteable block — the single thing that prevents
|
|
168
|
+
// redirect_uri_mismatch. console (not logger) so it renders verbatim.
|
|
169
|
+
const lines: string[] = [
|
|
170
|
+
"",
|
|
171
|
+
"──────────────────────────────────────────────────────────────",
|
|
172
|
+
" hazo_auth · OAuth redirect URIs to register in the provider console",
|
|
173
|
+
"──────────────────────────────────────────────────────────────",
|
|
174
|
+
];
|
|
175
|
+
for (const { provider, uri } of report.redirectUris) {
|
|
176
|
+
lines.push(` ${provider.padEnd(9)} → ${uri}`);
|
|
177
|
+
}
|
|
178
|
+
if (!report.originResolved) {
|
|
179
|
+
lines.push("");
|
|
180
|
+
lines.push(" ⚠ NEXTAUTH_URL not set — above is a guess from PORT.");
|
|
181
|
+
lines.push(" Set NEXTAUTH_URL to your real origin and re-register.");
|
|
182
|
+
}
|
|
183
|
+
lines.push("──────────────────────────────────────────────────────────────");
|
|
184
|
+
lines.push("");
|
|
185
|
+
console.log(lines.join("\n"));
|
|
186
|
+
|
|
187
|
+
return report;
|
|
188
|
+
}
|
|
@@ -11,3 +11,24 @@ export const FACEBOOK_NEXTAUTH_CALLBACK_PATH = "/api/auth/callback/facebook" as
|
|
|
11
11
|
|
|
12
12
|
/** hazo_auth's custom post-auth handler — creates the hazo_auth session after Facebook sign-in. */
|
|
13
13
|
export const FACEBOOK_OAUTH_CALLBACK_PATH = "/api/hazo_auth/oauth/facebook/callback" as const;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds the absolute Google redirect URI to register in Google Cloud Console.
|
|
17
|
+
* This is exactly the value NextAuth sends as `redirect_uri`; registering
|
|
18
|
+
* anything else (wrong port, https vs http, trailing slash) causes the
|
|
19
|
+
* `redirect_uri_mismatch` 400. Derive it from your origin (= NEXTAUTH_URL) so
|
|
20
|
+
* the two can never drift.
|
|
21
|
+
*/
|
|
22
|
+
export function googleRedirectUri(origin: string): string {
|
|
23
|
+
return joinOrigin(origin, GOOGLE_NEXTAUTH_CALLBACK_PATH);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Builds the absolute Facebook redirect URI to register in the Facebook Developer Console. */
|
|
27
|
+
export function facebookRedirectUri(origin: string): string {
|
|
28
|
+
return joinOrigin(origin, FACEBOOK_NEXTAUTH_CALLBACK_PATH);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function joinOrigin(origin: string, pathname: string): string {
|
|
32
|
+
// Tolerate a trailing slash on the origin; never emit a double slash.
|
|
33
|
+
return `${origin.replace(/\/+$/, "")}${pathname}`;
|
|
34
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cli/validate.ts"],"names":[],"mappings":"AAOA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cli/validate.ts"],"names":[],"mappings":"AAOA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC;AAsyBF,wBAAgB,cAAc,IAAI,iBAAiB,CAsFlD"}
|
package/dist/cli/validate.js
CHANGED
|
@@ -268,6 +268,105 @@ function check_env_vars() {
|
|
|
268
268
|
}
|
|
269
269
|
return results;
|
|
270
270
|
}
|
|
271
|
+
// Canonical values live in src/lib/oauth_routes.ts; inlined here because the CLI
|
|
272
|
+
// compiles as an isolated tree and must not import the server-only lib.
|
|
273
|
+
const GOOGLE_NEXTAUTH_CALLBACK_PATH = "/api/auth/callback/google";
|
|
274
|
+
const FACEBOOK_NEXTAUTH_CALLBACK_PATH = "/api/auth/callback/facebook";
|
|
275
|
+
function read_oauth_origin() {
|
|
276
|
+
var _a;
|
|
277
|
+
const nextauth_url = process.env.NEXTAUTH_URL;
|
|
278
|
+
if (nextauth_url && nextauth_url.trim().length > 0) {
|
|
279
|
+
return { origin: nextauth_url.trim().replace(/\/+$/, ""), resolved: true };
|
|
280
|
+
}
|
|
281
|
+
const port = (_a = process.env.PORT) !== null && _a !== void 0 ? _a : "3000";
|
|
282
|
+
return { origin: `http://localhost:${port}`, resolved: false };
|
|
283
|
+
}
|
|
284
|
+
function check_oauth(project_root) {
|
|
285
|
+
var _a, _b, _c;
|
|
286
|
+
const results = [];
|
|
287
|
+
const hazo_config_path = path.join(project_root, "config", "hazo_auth_config.ini");
|
|
288
|
+
const hazo_config = read_ini_file(hazo_config_path);
|
|
289
|
+
const oauth_section = (_a = hazo_config === null || hazo_config === void 0 ? void 0 : hazo_config["hazo_auth__oauth"]) !== null && _a !== void 0 ? _a : {};
|
|
290
|
+
const enable_google = ((_b = oauth_section["enable_google"]) !== null && _b !== void 0 ? _b : "").toLowerCase() === "true";
|
|
291
|
+
const enable_facebook = ((_c = oauth_section["enable_facebook_oauth"]) !== null && _c !== void 0 ? _c : "").toLowerCase() === "true";
|
|
292
|
+
if (!enable_google && !enable_facebook) {
|
|
293
|
+
results.push({
|
|
294
|
+
name: "OAuth providers",
|
|
295
|
+
status: "pass",
|
|
296
|
+
message: "No OAuth providers enabled (email/password only)",
|
|
297
|
+
});
|
|
298
|
+
return results;
|
|
299
|
+
}
|
|
300
|
+
const { origin, resolved } = read_oauth_origin();
|
|
301
|
+
// NEXTAUTH_URL — source of the redirect URI.
|
|
302
|
+
results.push(resolved
|
|
303
|
+
? { name: "NEXTAUTH_URL", status: "pass", message: origin }
|
|
304
|
+
: {
|
|
305
|
+
name: "NEXTAUTH_URL",
|
|
306
|
+
status: "fail",
|
|
307
|
+
message: `Not set. The redirect URI below is a guess from PORT. Set NEXTAUTH_URL=${origin}`,
|
|
308
|
+
});
|
|
309
|
+
// NEXTAUTH_SECRET — required for OAuth state/session signing.
|
|
310
|
+
const secret = process.env.NEXTAUTH_SECRET;
|
|
311
|
+
if (!secret || secret.length === 0) {
|
|
312
|
+
results.push({
|
|
313
|
+
name: "NEXTAUTH_SECRET",
|
|
314
|
+
status: "fail",
|
|
315
|
+
message: "Not set. Generate: openssl rand -base64 32",
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
else if (secret.length < 32) {
|
|
319
|
+
results.push({
|
|
320
|
+
name: "NEXTAUTH_SECRET",
|
|
321
|
+
status: "warn",
|
|
322
|
+
message: `Only ${secret.length} chars; use at least 32. Generate: openssl rand -base64 32`,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
results.push({ name: "NEXTAUTH_SECRET", status: "pass", message: "Set" });
|
|
327
|
+
}
|
|
328
|
+
if (enable_google) {
|
|
329
|
+
const id = process.env.HAZO_AUTH_GOOGLE_CLIENT_ID;
|
|
330
|
+
const cs = process.env.HAZO_AUTH_GOOGLE_CLIENT_SECRET;
|
|
331
|
+
const missing = [
|
|
332
|
+
!id ? "HAZO_AUTH_GOOGLE_CLIENT_ID" : null,
|
|
333
|
+
!cs ? "HAZO_AUTH_GOOGLE_CLIENT_SECRET" : null,
|
|
334
|
+
].filter(Boolean);
|
|
335
|
+
results.push(missing.length === 0
|
|
336
|
+
? { name: "Google OAuth credentials", status: "pass", message: "Set" }
|
|
337
|
+
: {
|
|
338
|
+
name: "Google OAuth credentials",
|
|
339
|
+
status: "fail",
|
|
340
|
+
message: `enable_google=true but missing: ${missing.join(", ")}`,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return results;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Returns the exact redirect URIs to register, per enabled provider. Printed as
|
|
347
|
+
* a copy-pasteable box so the dev never guesses the port/path — the single thing
|
|
348
|
+
* that prevents redirect_uri_mismatch.
|
|
349
|
+
*/
|
|
350
|
+
function get_oauth_redirect_lines(project_root) {
|
|
351
|
+
var _a, _b, _c;
|
|
352
|
+
const hazo_config = read_ini_file(path.join(project_root, "config", "hazo_auth_config.ini"));
|
|
353
|
+
const oauth_section = (_a = hazo_config === null || hazo_config === void 0 ? void 0 : hazo_config["hazo_auth__oauth"]) !== null && _a !== void 0 ? _a : {};
|
|
354
|
+
const enable_google = ((_b = oauth_section["enable_google"]) !== null && _b !== void 0 ? _b : "").toLowerCase() === "true";
|
|
355
|
+
const enable_facebook = ((_c = oauth_section["enable_facebook_oauth"]) !== null && _c !== void 0 ? _c : "").toLowerCase() === "true";
|
|
356
|
+
if (!enable_google && !enable_facebook)
|
|
357
|
+
return [];
|
|
358
|
+
const { origin, resolved } = read_oauth_origin();
|
|
359
|
+
const lines = [];
|
|
360
|
+
if (enable_google)
|
|
361
|
+
lines.push(` google → ${origin}${GOOGLE_NEXTAUTH_CALLBACK_PATH}`);
|
|
362
|
+
if (enable_facebook)
|
|
363
|
+
lines.push(` facebook → ${origin}${FACEBOOK_NEXTAUTH_CALLBACK_PATH}`);
|
|
364
|
+
if (!resolved) {
|
|
365
|
+
lines.push("");
|
|
366
|
+
lines.push(" ⚠ NEXTAUTH_URL not set — above is a guess from PORT. Set it and re-register.");
|
|
367
|
+
}
|
|
368
|
+
return lines;
|
|
369
|
+
}
|
|
271
370
|
function check_api_routes(project_root) {
|
|
272
371
|
const results = [];
|
|
273
372
|
const possible_app_dirs = [
|
|
@@ -682,6 +781,16 @@ export function run_validation() {
|
|
|
682
781
|
env_results.forEach(print_result);
|
|
683
782
|
all_results.push(...env_results);
|
|
684
783
|
console.log();
|
|
784
|
+
console.log("\x1b[1m🔑 OAuth Providers\x1b[0m");
|
|
785
|
+
const oauth_results = check_oauth(project_root);
|
|
786
|
+
oauth_results.forEach(print_result);
|
|
787
|
+
all_results.push(...oauth_results);
|
|
788
|
+
const redirect_lines = get_oauth_redirect_lines(project_root);
|
|
789
|
+
if (redirect_lines.length > 0) {
|
|
790
|
+
console.log("\n Register these redirect URIs in the provider console (must match exactly):");
|
|
791
|
+
redirect_lines.forEach(line => console.log(line));
|
|
792
|
+
}
|
|
793
|
+
console.log();
|
|
685
794
|
console.log("\x1b[1m🗄️ Database\x1b[0m");
|
|
686
795
|
const db_results = check_database(project_root);
|
|
687
796
|
db_results.forEach(print_result);
|
package/dist/index.d.ts
CHANGED
|
@@ -8,4 +8,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
9
|
export { GOOGLE_NEXTAUTH_CALLBACK_PATH, GOOGLE_OAUTH_CALLBACK_PATH, FACEBOOK_NEXTAUTH_CALLBACK_PATH, FACEBOOK_OAUTH_CALLBACK_PATH, } from "./lib/oauth_routes.js";
|
|
10
10
|
export { requestGoogleScopes } from "./lib/auth/request_google_scopes.js";
|
|
11
|
+
export { describeAuthError } from "./lib/auth/auth_error_help.js";
|
|
12
|
+
export type { AuthErrorHelp } from "./lib/auth/auth_error_help";
|
|
13
|
+
export { googleRedirectUri, facebookRedirectUri } from "./lib/oauth_routes.js";
|
|
11
14
|
//# 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;AACxG,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,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;AACxG,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAGvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGhE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -17,3 +17,7 @@ export { HAZO_AUTH_PERMISSIONS, ALL_ADMIN_PERMISSIONS, GLOBAL_ADMIN_PERMISSION }
|
|
|
17
17
|
export { GOOGLE_NEXTAUTH_CALLBACK_PATH, GOOGLE_OAUTH_CALLBACK_PATH, FACEBOOK_NEXTAUTH_CALLBACK_PATH, FACEBOOK_OAUTH_CALLBACK_PATH, } from "./lib/oauth_routes.js";
|
|
18
18
|
// section: google_oauth_exports
|
|
19
19
|
export { requestGoogleScopes } from "./lib/auth/request_google_scopes.js";
|
|
20
|
+
// section: auth_error_help_exports (client-safe)
|
|
21
|
+
export { describeAuthError } from "./lib/auth/auth_error_help.js";
|
|
22
|
+
// section: oauth_route_helper_exports (client-safe — pure string builders)
|
|
23
|
+
export { googleRedirectUri, facebookRedirectUri } from "./lib/oauth_routes.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type AuthErrorHelp = {
|
|
2
|
+
/** The raw error code from the `?error=` query param. */
|
|
3
|
+
code: string;
|
|
4
|
+
/** Short, user-facing summary. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** Developer-facing detail and remediation. */
|
|
7
|
+
detail: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Resolves a NextAuth/hazo_auth error code to friendly guidance. Unknown codes
|
|
11
|
+
* get a generic fallback so callers can always render something useful.
|
|
12
|
+
*/
|
|
13
|
+
export declare function describeAuthError(code: string | null | undefined): AuthErrorHelp;
|
|
14
|
+
//# sourceMappingURL=auth_error_help.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth_error_help.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/auth_error_help.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAC1B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAyCF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa,CAWhF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// file_description: client-safe decoder mapping NextAuth/hazo_auth `?error=` codes
|
|
2
|
+
// to human-readable guidance. Use on a login/error page to replace opaque codes.
|
|
3
|
+
const KNOWN = {
|
|
4
|
+
Configuration: {
|
|
5
|
+
title: "Sign-in is misconfigured.",
|
|
6
|
+
detail: "The server's OAuth/NextAuth config is incomplete. Check NEXTAUTH_SECRET, NEXTAUTH_URL, and provider credentials. Run `npx hazo_auth validate`.",
|
|
7
|
+
},
|
|
8
|
+
AccessDenied: {
|
|
9
|
+
title: "Access denied.",
|
|
10
|
+
detail: "You don't have permission to sign in, or you cancelled the consent screen.",
|
|
11
|
+
},
|
|
12
|
+
Verification: {
|
|
13
|
+
title: "This sign-in link has expired.",
|
|
14
|
+
detail: "The link was already used or timed out. Request a new one.",
|
|
15
|
+
},
|
|
16
|
+
OAuthSignin: {
|
|
17
|
+
title: "Couldn't start sign-in.",
|
|
18
|
+
detail: "Error constructing the provider authorization URL. Check provider credentials.",
|
|
19
|
+
},
|
|
20
|
+
OAuthCallback: {
|
|
21
|
+
title: "Sign-in callback failed.",
|
|
22
|
+
detail: "The provider rejected the callback. Most often the registered redirect URI doesn't match. Confirm the provider console lists the exact URI hazo_auth prints at boot.",
|
|
23
|
+
},
|
|
24
|
+
OAuthAccountNotLinked: {
|
|
25
|
+
title: "This email is already linked to a different sign-in method.",
|
|
26
|
+
detail: "Sign in with the original method, then link the new provider from account settings.",
|
|
27
|
+
},
|
|
28
|
+
GoogleTokenStorageUnconfigured: {
|
|
29
|
+
title: "Signed in, but offline Google access is unavailable.",
|
|
30
|
+
detail: "Refresh-token storage needs HAZO_AUTH_OAUTH_KEY_CURRENT and HAZO_AUTH_OAUTH_KEY_<ID>. Login still works; set these only if you need offline Google API calls.",
|
|
31
|
+
},
|
|
32
|
+
redirect_uri_mismatch: {
|
|
33
|
+
title: "Sign-in blocked: redirect URI mismatch.",
|
|
34
|
+
detail: "The redirect URI sent to the provider isn't registered. Register the exact URI hazo_auth prints at boot (origin = NEXTAUTH_URL + /api/auth/callback/<provider>). Note: providers usually show this on their own page, before returning to the app.",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Resolves a NextAuth/hazo_auth error code to friendly guidance. Unknown codes
|
|
39
|
+
* get a generic fallback so callers can always render something useful.
|
|
40
|
+
*/
|
|
41
|
+
export function describeAuthError(code) {
|
|
42
|
+
const key = (code !== null && code !== void 0 ? code : "").trim();
|
|
43
|
+
const hit = key ? KNOWN[key] : undefined;
|
|
44
|
+
if (hit)
|
|
45
|
+
return Object.assign({ code: key }, hit);
|
|
46
|
+
return {
|
|
47
|
+
code: key || "Unknown",
|
|
48
|
+
title: "Sign-in failed.",
|
|
49
|
+
detail: key
|
|
50
|
+
? `Unrecognised error code "${key}". Check the server logs for details.`
|
|
51
|
+
: "No error code provided. Check the server logs for details.",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nextauth_config.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/nextauth_config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"nextauth_config.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/nextauth_config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,WAAW,CAAC;AAgBtD,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,CA+RjD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAW7C"}
|
|
@@ -9,6 +9,7 @@ import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
|
|
|
9
9
|
import { create_app_logger } from "../app_logger.js";
|
|
10
10
|
import { store_google_oauth_token, GoogleTokenStorageUnconfigured } from "../services/google_token_service.js";
|
|
11
11
|
import { GOOGLE_OAUTH_CALLBACK_PATH, FACEBOOK_OAUTH_CALLBACK_PATH } from "../oauth_routes.js";
|
|
12
|
+
import { logOAuthPreflight } from "../oauth_preflight.server.js";
|
|
12
13
|
// section: config
|
|
13
14
|
/**
|
|
14
15
|
* Gets NextAuth.js configuration with enabled OAuth providers
|
|
@@ -16,6 +17,9 @@ import { GOOGLE_OAUTH_CALLBACK_PATH, FACEBOOK_OAUTH_CALLBACK_PATH } from "../oau
|
|
|
16
17
|
* @returns NextAuth configuration object
|
|
17
18
|
*/
|
|
18
19
|
export function get_nextauth_config() {
|
|
20
|
+
// Print enabled-provider redirect URIs + flag missing env once per process.
|
|
21
|
+
// No-op after the first call; safe to run on every config build.
|
|
22
|
+
logOAuthPreflight();
|
|
19
23
|
const oauth_config = get_oauth_config();
|
|
20
24
|
const providers = [];
|
|
21
25
|
// Add Google provider if enabled
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
export type PreflightStatus = "pass" | "warn" | "fail";
|
|
3
|
+
export type PreflightCheck = {
|
|
4
|
+
name: string;
|
|
5
|
+
status: PreflightStatus;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
export type OAuthPreflightReport = {
|
|
9
|
+
/** Origin used to derive redirect URIs (NEXTAUTH_URL, or a fallback). */
|
|
10
|
+
origin: string;
|
|
11
|
+
/** True when NEXTAUTH_URL was actually set (vs. a guessed fallback). */
|
|
12
|
+
originResolved: boolean;
|
|
13
|
+
/** Absolute redirect URIs to paste into the provider console, per enabled provider. */
|
|
14
|
+
redirectUris: {
|
|
15
|
+
provider: "google" | "facebook";
|
|
16
|
+
uri: string;
|
|
17
|
+
}[];
|
|
18
|
+
checks: PreflightCheck[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Builds a structured OAuth config report without printing. Safe to call
|
|
22
|
+
* anywhere server-side (CLI, route, instrumentation). Pure config inspection —
|
|
23
|
+
* no DB or network access.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getOAuthPreflightReport(): OAuthPreflightReport;
|
|
26
|
+
/**
|
|
27
|
+
* Logs the OAuth preflight report once per process. Call from a Next.js
|
|
28
|
+
* `instrumentation.ts` `register()` hook to surface config problems — and the
|
|
29
|
+
* exact redirect URI to register — the moment the server boots, before anyone
|
|
30
|
+
* hits a broken sign-in.
|
|
31
|
+
*
|
|
32
|
+
* Idempotent: subsequent calls in the same process are no-ops.
|
|
33
|
+
*/
|
|
34
|
+
export declare function logOAuthPreflight(): OAuthPreflightReport | null;
|
|
35
|
+
//# sourceMappingURL=oauth_preflight.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth_preflight.server.d.ts","sourceRoot":"","sources":["../../src/lib/oauth_preflight.server.ts"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAOrB,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,cAAc,EAAE,OAAO,CAAC;IACxB,uFAAuF;IACvF,YAAY,EAAE;QAAE,QAAQ,EAAE,QAAQ,GAAG,UAAU,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B,CAAC;AAeF;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,oBAAoB,CA2F9D;AAKD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,oBAAoB,GAAG,IAAI,CAsC/D"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// file_description: server-only OAuth preflight — validates env and prints the exact
|
|
2
|
+
// redirect URIs to register, killing the recurring `redirect_uri_mismatch` for new apps.
|
|
3
|
+
import "server-only";
|
|
4
|
+
import { get_oauth_config } from "./oauth_config.server.js";
|
|
5
|
+
import { googleRedirectUri, facebookRedirectUri } from "./oauth_routes.js";
|
|
6
|
+
import { create_app_logger } from "./app_logger.js";
|
|
7
|
+
// section: helpers
|
|
8
|
+
const MIN_SECRET_LEN = 32;
|
|
9
|
+
function resolve_origin() {
|
|
10
|
+
var _a;
|
|
11
|
+
const nextauth_url = process.env.NEXTAUTH_URL;
|
|
12
|
+
if (nextauth_url && nextauth_url.trim().length > 0) {
|
|
13
|
+
return { origin: nextauth_url.trim().replace(/\/+$/, ""), resolved: true };
|
|
14
|
+
}
|
|
15
|
+
// Fallback so we can still print a best-guess URI to register.
|
|
16
|
+
const port = (_a = process.env.PORT) !== null && _a !== void 0 ? _a : "3000";
|
|
17
|
+
return { origin: `http://localhost:${port}`, resolved: false };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Builds a structured OAuth config report without printing. Safe to call
|
|
21
|
+
* anywhere server-side (CLI, route, instrumentation). Pure config inspection —
|
|
22
|
+
* no DB or network access.
|
|
23
|
+
*/
|
|
24
|
+
export function getOAuthPreflightReport() {
|
|
25
|
+
const checks = [];
|
|
26
|
+
const { origin, resolved } = resolve_origin();
|
|
27
|
+
const oauth = get_oauth_config();
|
|
28
|
+
const redirectUris = [];
|
|
29
|
+
// NEXTAUTH_URL — the source of the redirect URI.
|
|
30
|
+
if (resolved) {
|
|
31
|
+
checks.push({ name: "NEXTAUTH_URL", status: "pass", message: origin });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
checks.push({
|
|
35
|
+
name: "NEXTAUTH_URL",
|
|
36
|
+
status: "fail",
|
|
37
|
+
message: `Not set. NextAuth derives the OAuth redirect URI from it. Set NEXTAUTH_URL=${origin} (matching the port you serve on).`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// NEXTAUTH_URL port vs. the port the app is told to listen on.
|
|
41
|
+
const declared_port = process.env.PORT;
|
|
42
|
+
if (resolved && declared_port) {
|
|
43
|
+
let url_port = null;
|
|
44
|
+
try {
|
|
45
|
+
const u = new URL(origin);
|
|
46
|
+
url_port = u.port || (u.protocol === "https:" ? "443" : "80");
|
|
47
|
+
}
|
|
48
|
+
catch (_a) {
|
|
49
|
+
url_port = null;
|
|
50
|
+
}
|
|
51
|
+
if (url_port && url_port !== declared_port) {
|
|
52
|
+
checks.push({
|
|
53
|
+
name: "Port match",
|
|
54
|
+
status: "warn",
|
|
55
|
+
message: `NEXTAUTH_URL port (${url_port}) differs from PORT (${declared_port}). The redirect URI uses ${url_port}; if you serve on ${declared_port}, OAuth will fail with redirect_uri_mismatch.`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// NEXTAUTH_SECRET — required for OAuth session/state signing.
|
|
60
|
+
const secret = process.env.NEXTAUTH_SECRET;
|
|
61
|
+
if (!secret || secret.length === 0) {
|
|
62
|
+
checks.push({
|
|
63
|
+
name: "NEXTAUTH_SECRET",
|
|
64
|
+
status: "fail",
|
|
65
|
+
message: "Not set. Generate one: openssl rand -base64 32",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else if (secret.length < MIN_SECRET_LEN) {
|
|
69
|
+
checks.push({
|
|
70
|
+
name: "NEXTAUTH_SECRET",
|
|
71
|
+
status: "warn",
|
|
72
|
+
message: `Only ${secret.length} chars; use at least ${MIN_SECRET_LEN}. Generate: openssl rand -base64 32`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
checks.push({ name: "NEXTAUTH_SECRET", status: "pass", message: "Set" });
|
|
77
|
+
}
|
|
78
|
+
// Google provider.
|
|
79
|
+
if (oauth.enable_google) {
|
|
80
|
+
const id = process.env.HAZO_AUTH_GOOGLE_CLIENT_ID;
|
|
81
|
+
const secretG = process.env.HAZO_AUTH_GOOGLE_CLIENT_SECRET;
|
|
82
|
+
if (!id || !secretG) {
|
|
83
|
+
const missing = [
|
|
84
|
+
!id ? "HAZO_AUTH_GOOGLE_CLIENT_ID" : null,
|
|
85
|
+
!secretG ? "HAZO_AUTH_GOOGLE_CLIENT_SECRET" : null,
|
|
86
|
+
].filter(Boolean);
|
|
87
|
+
checks.push({
|
|
88
|
+
name: "Google OAuth credentials",
|
|
89
|
+
status: "fail",
|
|
90
|
+
message: `enable_google=true but missing: ${missing.join(", ")} (from Google Cloud Console).`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
checks.push({ name: "Google OAuth credentials", status: "pass", message: "Set" });
|
|
95
|
+
}
|
|
96
|
+
redirectUris.push({ provider: "google", uri: googleRedirectUri(origin) });
|
|
97
|
+
}
|
|
98
|
+
// Facebook provider.
|
|
99
|
+
if (oauth.enable_facebook_oauth) {
|
|
100
|
+
const appId = oauth.facebook_app_id || process.env.HAZO_AUTH_FACEBOOK_APP_ID;
|
|
101
|
+
const appSecret = oauth.facebook_app_secret || process.env.HAZO_AUTH_FACEBOOK_APP_SECRET;
|
|
102
|
+
if (!appId || !appSecret) {
|
|
103
|
+
checks.push({
|
|
104
|
+
name: "Facebook OAuth credentials",
|
|
105
|
+
status: "fail",
|
|
106
|
+
message: "enable_facebook_oauth=true but app_id/app_secret missing.",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
checks.push({ name: "Facebook OAuth credentials", status: "pass", message: "Set" });
|
|
111
|
+
}
|
|
112
|
+
redirectUris.push({ provider: "facebook", uri: facebookRedirectUri(origin) });
|
|
113
|
+
}
|
|
114
|
+
return { origin, originResolved: resolved, redirectUris, checks };
|
|
115
|
+
}
|
|
116
|
+
// section: boot logger
|
|
117
|
+
let _logged_once = false;
|
|
118
|
+
/**
|
|
119
|
+
* Logs the OAuth preflight report once per process. Call from a Next.js
|
|
120
|
+
* `instrumentation.ts` `register()` hook to surface config problems — and the
|
|
121
|
+
* exact redirect URI to register — the moment the server boots, before anyone
|
|
122
|
+
* hits a broken sign-in.
|
|
123
|
+
*
|
|
124
|
+
* Idempotent: subsequent calls in the same process are no-ops.
|
|
125
|
+
*/
|
|
126
|
+
export function logOAuthPreflight() {
|
|
127
|
+
if (_logged_once)
|
|
128
|
+
return null;
|
|
129
|
+
_logged_once = true;
|
|
130
|
+
const report = getOAuthPreflightReport();
|
|
131
|
+
if (report.redirectUris.length === 0)
|
|
132
|
+
return report; // No OAuth providers enabled.
|
|
133
|
+
const logger = create_app_logger();
|
|
134
|
+
for (const check of report.checks) {
|
|
135
|
+
if (check.status === "fail") {
|
|
136
|
+
logger.error("oauth_preflight_check_failed", { check: check.name, detail: check.message });
|
|
137
|
+
}
|
|
138
|
+
else if (check.status === "warn") {
|
|
139
|
+
logger.warn("oauth_preflight_check_warning", { check: check.name, detail: check.message });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Highlighted, copy-pasteable block — the single thing that prevents
|
|
143
|
+
// redirect_uri_mismatch. console (not logger) so it renders verbatim.
|
|
144
|
+
const lines = [
|
|
145
|
+
"",
|
|
146
|
+
"──────────────────────────────────────────────────────────────",
|
|
147
|
+
" hazo_auth · OAuth redirect URIs to register in the provider console",
|
|
148
|
+
"──────────────────────────────────────────────────────────────",
|
|
149
|
+
];
|
|
150
|
+
for (const { provider, uri } of report.redirectUris) {
|
|
151
|
+
lines.push(` ${provider.padEnd(9)} → ${uri}`);
|
|
152
|
+
}
|
|
153
|
+
if (!report.originResolved) {
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push(" ⚠ NEXTAUTH_URL not set — above is a guess from PORT.");
|
|
156
|
+
lines.push(" Set NEXTAUTH_URL to your real origin and re-register.");
|
|
157
|
+
}
|
|
158
|
+
lines.push("──────────────────────────────────────────────────────────────");
|
|
159
|
+
lines.push("");
|
|
160
|
+
console.log(lines.join("\n"));
|
|
161
|
+
return report;
|
|
162
|
+
}
|
|
@@ -6,4 +6,14 @@ export declare const GOOGLE_OAUTH_CALLBACK_PATH: "/api/hazo_auth/oauth/google/ca
|
|
|
6
6
|
export declare const FACEBOOK_NEXTAUTH_CALLBACK_PATH: "/api/auth/callback/facebook";
|
|
7
7
|
/** hazo_auth's custom post-auth handler — creates the hazo_auth session after Facebook sign-in. */
|
|
8
8
|
export declare const FACEBOOK_OAUTH_CALLBACK_PATH: "/api/hazo_auth/oauth/facebook/callback";
|
|
9
|
+
/**
|
|
10
|
+
* Builds the absolute Google redirect URI to register in Google Cloud Console.
|
|
11
|
+
* This is exactly the value NextAuth sends as `redirect_uri`; registering
|
|
12
|
+
* anything else (wrong port, https vs http, trailing slash) causes the
|
|
13
|
+
* `redirect_uri_mismatch` 400. Derive it from your origin (= NEXTAUTH_URL) so
|
|
14
|
+
* the two can never drift.
|
|
15
|
+
*/
|
|
16
|
+
export declare function googleRedirectUri(origin: string): string;
|
|
17
|
+
/** Builds the absolute Facebook redirect URI to register in the Facebook Developer Console. */
|
|
18
|
+
export declare function facebookRedirectUri(origin: string): string;
|
|
9
19
|
//# sourceMappingURL=oauth_routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth_routes.d.ts","sourceRoot":"","sources":["../../src/lib/oauth_routes.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,eAAO,MAAM,6BAA6B,EAAG,2BAAoC,CAAC;AAElF,iGAAiG;AACjG,eAAO,MAAM,0BAA0B,EAAG,sCAA+C,CAAC;AAE1F,4FAA4F;AAC5F,eAAO,MAAM,+BAA+B,EAAG,6BAAsC,CAAC;AAEtF,mGAAmG;AACnG,eAAO,MAAM,4BAA4B,EAAG,wCAAiD,CAAC"}
|
|
1
|
+
{"version":3,"file":"oauth_routes.d.ts","sourceRoot":"","sources":["../../src/lib/oauth_routes.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,eAAO,MAAM,6BAA6B,EAAG,2BAAoC,CAAC;AAElF,iGAAiG;AACjG,eAAO,MAAM,0BAA0B,EAAG,sCAA+C,CAAC;AAE1F,4FAA4F;AAC5F,eAAO,MAAM,+BAA+B,EAAG,6BAAsC,CAAC;AAEtF,mGAAmG;AACnG,eAAO,MAAM,4BAA4B,EAAG,wCAAiD,CAAC;AAE9F;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D"}
|
package/dist/lib/oauth_routes.js
CHANGED
|
@@ -7,3 +7,21 @@ export const GOOGLE_OAUTH_CALLBACK_PATH = "/api/hazo_auth/oauth/google/callback"
|
|
|
7
7
|
export const FACEBOOK_NEXTAUTH_CALLBACK_PATH = "/api/auth/callback/facebook";
|
|
8
8
|
/** hazo_auth's custom post-auth handler — creates the hazo_auth session after Facebook sign-in. */
|
|
9
9
|
export const FACEBOOK_OAUTH_CALLBACK_PATH = "/api/hazo_auth/oauth/facebook/callback";
|
|
10
|
+
/**
|
|
11
|
+
* Builds the absolute Google redirect URI to register in Google Cloud Console.
|
|
12
|
+
* This is exactly the value NextAuth sends as `redirect_uri`; registering
|
|
13
|
+
* anything else (wrong port, https vs http, trailing slash) causes the
|
|
14
|
+
* `redirect_uri_mismatch` 400. Derive it from your origin (= NEXTAUTH_URL) so
|
|
15
|
+
* the two can never drift.
|
|
16
|
+
*/
|
|
17
|
+
export function googleRedirectUri(origin) {
|
|
18
|
+
return joinOrigin(origin, GOOGLE_NEXTAUTH_CALLBACK_PATH);
|
|
19
|
+
}
|
|
20
|
+
/** Builds the absolute Facebook redirect URI to register in the Facebook Developer Console. */
|
|
21
|
+
export function facebookRedirectUri(origin) {
|
|
22
|
+
return joinOrigin(origin, FACEBOOK_NEXTAUTH_CALLBACK_PATH);
|
|
23
|
+
}
|
|
24
|
+
function joinOrigin(origin, pathname) {
|
|
25
|
+
// Tolerate a trailing slash on the origin; never emit a double slash.
|
|
26
|
+
return `${origin.replace(/\/+$/, "")}${pathname}`;
|
|
27
|
+
}
|
package/dist/server-lib.d.ts
CHANGED
|
@@ -38,4 +38,6 @@ export { wireAdminIssueCapture } from "./admin-issues/wire.server.js";
|
|
|
38
38
|
export type { WireAdminIssueCaptureOptions } from "./admin-issues/wire.server";
|
|
39
39
|
export { denyPermission } from "./lib/auth/deny_permission.server.js";
|
|
40
40
|
export type { DenyPermissionInput } from "./lib/auth/deny_permission.server";
|
|
41
|
+
export { logOAuthPreflight, getOAuthPreflightReport } from "./lib/oauth_preflight.server.js";
|
|
42
|
+
export type { OAuthPreflightReport, PreflightCheck, PreflightStatus, } from "./lib/oauth_preflight.server";
|
|
41
43
|
//# sourceMappingURL=server-lib.d.ts.map
|
package/dist/server-lib.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-lib.d.ts","sourceRoot":"","sources":["../src/server-lib.ts"],"names":[],"mappings":"AAYA,OAAO,aAAa,CAAC;AAGrB,cAAc,kBAAkB,CAAC;AAGjC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AAGrF,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,6BAA6B,EAAE,MAAM,wCAAwC,CAAC;AACvF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,gCAAgC,EAAE,MAAM,2CAA2C,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,YAAY,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,cAAc,+BAA+B,CAAC;AAG9C,OAAO,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACvF,YAAY,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAGpF,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC"}
|
|
1
|
+
{"version":3,"file":"server-lib.d.ts","sourceRoot":"","sources":["../src/server-lib.ts"],"names":[],"mappings":"AAYA,OAAO,aAAa,CAAC;AAGrB,cAAc,kBAAkB,CAAC;AAGjC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AAGrF,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,6BAA6B,EAAE,MAAM,wCAAwC,CAAC;AACvF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,gCAAgC,EAAE,MAAM,2CAA2C,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,YAAY,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,cAAc,+BAA+B,CAAC;AAG9C,OAAO,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACvF,YAAY,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAGpF,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAK7E,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,eAAe,GAChB,MAAM,8BAA8B,CAAC"}
|
package/dist/server-lib.js
CHANGED
|
@@ -53,3 +53,7 @@ export { authIssueTypeDef, registerAuthIssuePlugin } from "./admin-issues/plugin
|
|
|
53
53
|
export { wireAdminIssueCapture } from "./admin-issues/wire.server.js";
|
|
54
54
|
// deny_permission helper
|
|
55
55
|
export { denyPermission } from "./lib/auth/deny_permission.server.js";
|
|
56
|
+
// section: oauth_preflight_exports
|
|
57
|
+
// Call logOAuthPreflight() from a Next.js instrumentation.ts register() hook to
|
|
58
|
+
// print the exact redirect URIs to register and flag missing OAuth env at boot.
|
|
59
|
+
export { logOAuthPreflight, getOAuthPreflightReport } from "./lib/oauth_preflight.server.js";
|
package/package.json
CHANGED