@autumnsgrove/groveengine 0.9.96 → 0.9.97
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/dist/config/petal.d.ts +23 -0
- package/dist/config/petal.js +26 -0
- package/dist/curios/timeline/index.js +31 -3
- package/dist/curios/timeline/voices/presets/casual.js +4 -1
- package/dist/curios/timeline/voices/presets/minimal.js +4 -1
- package/dist/curios/timeline/voices/presets/poetic.js +4 -1
- package/dist/curios/timeline/voices/presets/professional.js +4 -1
- package/dist/curios/timeline/voices/presets/quest.js +4 -1
- package/dist/grafts/login/LoginGraft.svelte +1 -0
- package/dist/grafts/login/config.d.ts +3 -1
- package/dist/grafts/login/config.js +3 -1
- package/dist/grafts/login/server/callback.js +20 -28
- package/dist/groveauth/client.js +16 -10
- package/dist/groveauth/errors.d.ts +165 -0
- package/dist/groveauth/errors.js +217 -0
- package/dist/groveauth/index.d.ts +13 -11
- package/dist/groveauth/index.js +9 -7
- package/dist/lumen/index.d.ts +1 -1
- package/dist/server/encryption.d.ts +24 -0
- package/dist/server/encryption.js +168 -0
- package/dist/server/petal/index.d.ts +2 -1
- package/dist/server/petal/index.js +5 -1
- package/dist/server/petal/layer1-csam.d.ts +22 -25
- package/dist/server/petal/layer1-csam.js +99 -43
- package/dist/server/petal/photodna-client.d.ts +84 -0
- package/dist/server/petal/photodna-client.js +150 -0
- package/dist/server/petal/types.d.ts +7 -1
- package/dist/server/rate-limits/config.d.ts +4 -0
- package/dist/server/rate-limits/config.js +3 -0
- package/dist/server/secrets-manager.d.ts +114 -0
- package/dist/server/secrets-manager.js +303 -0
- package/dist/server/secrets.d.ts +27 -0
- package/dist/server/secrets.js +28 -0
- package/dist/server/services/index.d.ts +16 -14
- package/dist/server/services/index.js +21 -14
- package/dist/server/services/storage.js +16 -1
- package/dist/server/services/trace-email.d.ts +24 -0
- package/dist/server/services/trace-email.js +113 -0
- package/dist/styles/content.css +4 -2
- package/dist/styles/fonts-optional.css +90 -0
- package/dist/ui/components/feedback/Trace.svelte +315 -0
- package/dist/ui/components/feedback/Trace.svelte.d.ts +15 -0
- package/dist/ui/components/feedback/index.d.ts +2 -0
- package/dist/ui/components/feedback/index.js +13 -0
- package/dist/ui/components/ui/Accordion.svelte +9 -3
- package/dist/ui/components/ui/GlassButton.svelte +51 -67
- package/dist/ui/components/ui/GlassButton.svelte.d.ts +4 -3
- package/dist/ui/components/ui/GlassCard.svelte +19 -3
- package/dist/ui/components/ui/GlassCard.svelte.d.ts +19 -3
- package/dist/ui/components/ui/Input.svelte +1 -2
- package/dist/ui/components/ui/Input.svelte.d.ts +1 -1
- package/dist/ui/components/ui/Select.svelte +7 -5
- package/dist/ui/components/ui/Select.svelte.d.ts +5 -2
- package/dist/ui/components/ui/index.d.ts +1 -1
- package/dist/ui/components/ui/index.js +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/markdown.d.ts +12 -2
- package/dist/utils/sanitize.d.ts +14 -0
- package/dist/utils/sanitize.js +67 -0
- package/dist/utils/trace-path.d.ts +36 -0
- package/dist/utils/trace-path.js +56 -0
- package/package.json +9 -3
package/dist/config/petal.d.ts
CHANGED
|
@@ -99,6 +99,29 @@ export declare const FAILOVER_CONFIG: {
|
|
|
99
99
|
/** Sanity check timeout (ms) */
|
|
100
100
|
readonly sanityTimeoutMs: 10000;
|
|
101
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* PhotoDNA Cloud Service configuration
|
|
104
|
+
*
|
|
105
|
+
* PhotoDNA is Microsoft's industry-standard perceptual hash system for
|
|
106
|
+
* detecting known CSAM content against the NCMEC database.
|
|
107
|
+
*
|
|
108
|
+
* INTEGRATION STATUS: Awaiting Microsoft approval
|
|
109
|
+
* Once approved, add PHOTODNA_SUBSCRIPTION_KEY to Cloudflare secrets.
|
|
110
|
+
*
|
|
111
|
+
* @see https://www.microsoft.com/en-us/photodna
|
|
112
|
+
*/
|
|
113
|
+
export declare const PHOTODNA_CONFIG: {
|
|
114
|
+
/** PhotoDNA Cloud Service endpoint */
|
|
115
|
+
readonly endpoint: "https://api.microsoftmoderator.com/photodna";
|
|
116
|
+
/** Request timeout in milliseconds */
|
|
117
|
+
readonly timeoutMs: 5000;
|
|
118
|
+
/** Environment variable name for subscription key */
|
|
119
|
+
readonly subscriptionKeyEnvVar: "PHOTODNA_SUBSCRIPTION_KEY";
|
|
120
|
+
/** Whether PhotoDNA is the primary CSAM detection method */
|
|
121
|
+
readonly isPrimary: true;
|
|
122
|
+
/** Fall back to vision model if PhotoDNA unavailable */
|
|
123
|
+
readonly fallbackToVision: true;
|
|
124
|
+
};
|
|
102
125
|
/**
|
|
103
126
|
* Approximate cost per image (in USD)
|
|
104
127
|
* Based on Workers AI pricing: $0.27/M input, $0.85/M output
|
package/dist/config/petal.js
CHANGED
|
@@ -198,6 +198,32 @@ export const FAILOVER_CONFIG = {
|
|
|
198
198
|
sanityTimeoutMs: 10_000,
|
|
199
199
|
};
|
|
200
200
|
// ============================================================================
|
|
201
|
+
// PhotoDNA Configuration
|
|
202
|
+
// ============================================================================
|
|
203
|
+
/**
|
|
204
|
+
* PhotoDNA Cloud Service configuration
|
|
205
|
+
*
|
|
206
|
+
* PhotoDNA is Microsoft's industry-standard perceptual hash system for
|
|
207
|
+
* detecting known CSAM content against the NCMEC database.
|
|
208
|
+
*
|
|
209
|
+
* INTEGRATION STATUS: Awaiting Microsoft approval
|
|
210
|
+
* Once approved, add PHOTODNA_SUBSCRIPTION_KEY to Cloudflare secrets.
|
|
211
|
+
*
|
|
212
|
+
* @see https://www.microsoft.com/en-us/photodna
|
|
213
|
+
*/
|
|
214
|
+
export const PHOTODNA_CONFIG = {
|
|
215
|
+
/** PhotoDNA Cloud Service endpoint */
|
|
216
|
+
endpoint: "https://api.microsoftmoderator.com/photodna",
|
|
217
|
+
/** Request timeout in milliseconds */
|
|
218
|
+
timeoutMs: 5_000,
|
|
219
|
+
/** Environment variable name for subscription key */
|
|
220
|
+
subscriptionKeyEnvVar: "PHOTODNA_SUBSCRIPTION_KEY",
|
|
221
|
+
/** Whether PhotoDNA is the primary CSAM detection method */
|
|
222
|
+
isPrimary: true,
|
|
223
|
+
/** Fall back to vision model if PhotoDNA unavailable */
|
|
224
|
+
fallbackToVision: true,
|
|
225
|
+
};
|
|
226
|
+
// ============================================================================
|
|
201
227
|
// Pricing (for cost tracking)
|
|
202
228
|
// ============================================================================
|
|
203
229
|
/**
|
|
@@ -50,6 +50,31 @@ formatHistoricalContextForPrompt, formatContinuationForPrompt, buildPromptContex
|
|
|
50
50
|
// =============================================================================
|
|
51
51
|
// Utility Functions
|
|
52
52
|
// =============================================================================
|
|
53
|
+
/**
|
|
54
|
+
* Strip hallucinated GitHub links from AI output.
|
|
55
|
+
*
|
|
56
|
+
* The AI sometimes invents URLs for section headers like:
|
|
57
|
+
* [Sentinel Stress Testing System](https://github.com/AutumnsGrove/Sentinel Stress Testing System)
|
|
58
|
+
*
|
|
59
|
+
* This function strips fake links while preserving real commit/PR links.
|
|
60
|
+
* Real links have patterns like /commit/abc123 or /pull/123
|
|
61
|
+
*/
|
|
62
|
+
function stripHallucinatedLinks(text) {
|
|
63
|
+
// Match markdown links to github.com/AutumnsGrove/...
|
|
64
|
+
const githubLinkPattern = /\[([^\]]+)\]\((https?:\/\/github\.com\/AutumnsGrove\/[^)]+)\)/g;
|
|
65
|
+
return text.replace(githubLinkPattern, (match, linkText, url) => {
|
|
66
|
+
// Real links contain: /commit/, /pull/, /issues/, /blob/, /tree/
|
|
67
|
+
const isRealLink = /\/(commit|pull|issues|blob|tree|compare)\//.test(url) &&
|
|
68
|
+
// And shouldn't have spaces in the URL (hallucinated links often do)
|
|
69
|
+
!/ /.test(url);
|
|
70
|
+
if (isRealLink) {
|
|
71
|
+
return match; // Keep valid links
|
|
72
|
+
}
|
|
73
|
+
// Strip fake links, keep just the text
|
|
74
|
+
console.warn(`[Timeline] Stripped hallucinated link: ${url}`);
|
|
75
|
+
return linkText;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
53
78
|
/**
|
|
54
79
|
* Parse AI response into structured summary data
|
|
55
80
|
*/
|
|
@@ -65,14 +90,17 @@ export function parseAIResponse(response) {
|
|
|
65
90
|
const validGutter = (parsed.gutter || [])
|
|
66
91
|
.filter((item) => item.anchor && item.content && typeof item.content === "string")
|
|
67
92
|
.map((item) => ({
|
|
68
|
-
anchor: item.anchor,
|
|
93
|
+
anchor: stripHallucinatedLinks(item.anchor),
|
|
69
94
|
type: item.type || "comment",
|
|
70
95
|
content: item.content.trim(),
|
|
71
96
|
}));
|
|
97
|
+
// Sanitize brief and detailed to remove any hallucinated links
|
|
98
|
+
const brief = stripHallucinatedLinks(parsed.brief || "Worked on a few things today.");
|
|
99
|
+
const detailed = stripHallucinatedLinks(parsed.detailed || "## Projects\n\nSome progress was made.");
|
|
72
100
|
return {
|
|
73
101
|
success: true,
|
|
74
|
-
brief
|
|
75
|
-
detailed
|
|
102
|
+
brief,
|
|
103
|
+
detailed,
|
|
76
104
|
gutter: validGutter,
|
|
77
105
|
};
|
|
78
106
|
}
|
|
@@ -83,7 +83,10 @@ REQUIREMENTS:
|
|
|
83
83
|
- JSON only, no markdown code blocks
|
|
84
84
|
- Escape newlines as \\n
|
|
85
85
|
- Gutter anchors must EXACTLY match "### ProjectName" headers
|
|
86
|
-
- Exactly ${gutterCount} gutter comments
|
|
86
|
+
- Exactly ${gutterCount} gutter comments
|
|
87
|
+
- NEVER create markdown links for section headers (no [Title](url) format)
|
|
88
|
+
- Only use plain text for headings like "### ProjectName"
|
|
89
|
+
- Do NOT invent URLs or link to non-existent repositories`;
|
|
87
90
|
},
|
|
88
91
|
};
|
|
89
92
|
export default casual;
|
|
@@ -84,7 +84,10 @@ REQUIREMENTS:
|
|
|
84
84
|
- Escape newlines as \\n
|
|
85
85
|
- Gutter anchors must EXACTLY match "### ProjectName" headers
|
|
86
86
|
- Exactly ${gutterCount} gutter comments
|
|
87
|
-
- Keep everything as short as possible
|
|
87
|
+
- Keep everything as short as possible
|
|
88
|
+
- NEVER create markdown links for section headers (no [Title](url) format)
|
|
89
|
+
- Only use plain text for headings
|
|
90
|
+
- Do NOT invent URLs or link to non-existent repositories`;
|
|
88
91
|
},
|
|
89
92
|
};
|
|
90
93
|
export default minimal;
|
|
@@ -84,7 +84,10 @@ REQUIREMENTS:
|
|
|
84
84
|
- Escape newlines as \\n
|
|
85
85
|
- Gutter anchors must EXACTLY match "### ProjectName" headers
|
|
86
86
|
- Exactly ${gutterCount} gutter comments
|
|
87
|
-
- Keep it genuine - find real meaning, don't force purple prose
|
|
87
|
+
- Keep it genuine - find real meaning, don't force purple prose
|
|
88
|
+
- NEVER create markdown links for section headers (no [Title](url) format)
|
|
89
|
+
- Only use plain text for headings like "### ProjectName"
|
|
90
|
+
- Do NOT invent URLs or link to non-existent repositories`;
|
|
88
91
|
},
|
|
89
92
|
};
|
|
90
93
|
export default poetic;
|
|
@@ -90,7 +90,10 @@ REQUIREMENTS:
|
|
|
90
90
|
- JSON only, no markdown code blocks
|
|
91
91
|
- Escape newlines as \\n
|
|
92
92
|
- Gutter anchors must EXACTLY match "### ProjectName" headers
|
|
93
|
-
- Exactly ${gutterCount} gutter comments
|
|
93
|
+
- Exactly ${gutterCount} gutter comments
|
|
94
|
+
- NEVER create markdown links for section headers (no [Title](url) format)
|
|
95
|
+
- Only use plain text for headings like "### ProjectName"
|
|
96
|
+
- Do NOT invent URLs or link to non-existent repositories`;
|
|
94
97
|
},
|
|
95
98
|
};
|
|
96
99
|
export default professional;
|
|
@@ -83,7 +83,10 @@ REQUIREMENTS:
|
|
|
83
83
|
- Escape newlines as \\n
|
|
84
84
|
- Gutter anchors must EXACTLY match the "### [ProjectName] Expedition" headers you create
|
|
85
85
|
- Exactly ${gutterCount} gutter comments
|
|
86
|
-
- Keep it fun but still informative about what actually happened
|
|
86
|
+
- Keep it fun but still informative about what actually happened
|
|
87
|
+
- NEVER create markdown links for section headers (no [Title](url) format)
|
|
88
|
+
- Only use plain text for headings
|
|
89
|
+
- Do NOT invent URLs or link to non-existent repositories`;
|
|
87
90
|
},
|
|
88
91
|
};
|
|
89
92
|
export default quest;
|
|
@@ -76,8 +76,10 @@ export declare const AUTH_COOKIE_NAMES: {
|
|
|
76
76
|
readonly refreshToken: "refresh_token";
|
|
77
77
|
/** Session ID (legacy) @deprecated */
|
|
78
78
|
readonly session: "session";
|
|
79
|
-
/** Better Auth session token (the new standard) */
|
|
79
|
+
/** Better Auth session token (the new standard) - unprefixed for dev */
|
|
80
80
|
readonly betterAuthSession: "better-auth.session_token";
|
|
81
|
+
/** Better Auth session token with __Secure- prefix (production HTTPS) */
|
|
82
|
+
readonly betterAuthSessionSecure: "__Secure-better-auth.session_token";
|
|
81
83
|
};
|
|
82
84
|
/**
|
|
83
85
|
* Cookie options for OAuth flow cookies.
|
|
@@ -114,8 +114,10 @@ export const AUTH_COOKIE_NAMES = {
|
|
|
114
114
|
refreshToken: "refresh_token",
|
|
115
115
|
/** Session ID (legacy) @deprecated */
|
|
116
116
|
session: "session",
|
|
117
|
-
/** Better Auth session token (the new standard) */
|
|
117
|
+
/** Better Auth session token (the new standard) - unprefixed for dev */
|
|
118
118
|
betterAuthSession: "better-auth.session_token",
|
|
119
|
+
/** Better Auth session token with __Secure- prefix (production HTTPS) */
|
|
120
|
+
betterAuthSessionSecure: "__Secure-better-auth.session_token",
|
|
119
121
|
};
|
|
120
122
|
/**
|
|
121
123
|
* Cookie options for OAuth flow cookies.
|
|
@@ -10,24 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { redirect } from "@sveltejs/kit";
|
|
12
12
|
import { AUTH_COOKIE_NAMES } from "../config.js";
|
|
13
|
-
|
|
14
|
-
// ERROR HANDLING
|
|
15
|
-
// =============================================================================
|
|
16
|
-
/**
|
|
17
|
-
* User-friendly error messages for common OAuth errors.
|
|
18
|
-
*/
|
|
19
|
-
const ERROR_MESSAGES = {
|
|
20
|
-
access_denied: "You cancelled the login process",
|
|
21
|
-
auth_failed: "Authentication failed, please try again",
|
|
22
|
-
no_session: "Session was not created, please try again",
|
|
23
|
-
rate_limited: "Too many login attempts. Please wait before trying again.",
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Get a user-friendly error message for an error code.
|
|
27
|
-
*/
|
|
28
|
-
function getFriendlyErrorMessage(errorCode) {
|
|
29
|
-
return ERROR_MESSAGES[errorCode] || "An error occurred during login";
|
|
30
|
-
}
|
|
13
|
+
import { AUTH_ERRORS, getAuthError, logAuthError, buildErrorParams, } from "../../../groveauth/errors.js";
|
|
31
14
|
// =============================================================================
|
|
32
15
|
// RATE LIMITING
|
|
33
16
|
// =============================================================================
|
|
@@ -104,10 +87,11 @@ export function createCallbackHandler(config = {}) {
|
|
|
104
87
|
const rateLimited = await isRateLimited(kv, `auth-callback:${clientIp}`, 10, // 10 attempts
|
|
105
88
|
900);
|
|
106
89
|
if (rateLimited) {
|
|
107
|
-
|
|
90
|
+
const authError = AUTH_ERRORS.RATE_LIMITED;
|
|
91
|
+
logAuthError(authError, { ip: clientIp });
|
|
108
92
|
return new Response(JSON.stringify({
|
|
109
|
-
error:
|
|
110
|
-
message:
|
|
93
|
+
error: authError.code,
|
|
94
|
+
message: authError.userMessage,
|
|
111
95
|
}), {
|
|
112
96
|
status: 429,
|
|
113
97
|
headers: {
|
|
@@ -120,19 +104,27 @@ export function createCallbackHandler(config = {}) {
|
|
|
120
104
|
// Check for error from OAuth provider
|
|
121
105
|
const errorParam = url.searchParams.get("error");
|
|
122
106
|
if (errorParam) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
107
|
+
const authError = getAuthError(errorParam);
|
|
108
|
+
logAuthError(authError, {
|
|
109
|
+
oauthError: errorParam,
|
|
110
|
+
ip: getClientIP(request),
|
|
111
|
+
});
|
|
112
|
+
redirect(302, `/auth/login?${buildErrorParams(authError)}`);
|
|
126
113
|
}
|
|
127
114
|
// Get return URL from query params (set by LoginGraft) or use default
|
|
128
115
|
const returnTo = url.searchParams.get("returnTo") || defaultReturnTo;
|
|
129
116
|
// Verify Better Auth session cookie was set
|
|
130
|
-
// Better Auth
|
|
131
|
-
|
|
117
|
+
// Better Auth uses __Secure- prefix in production (HTTPS)
|
|
118
|
+
// Check both prefixed and unprefixed names for compatibility
|
|
119
|
+
const sessionToken = cookies.get(AUTH_COOKIE_NAMES.betterAuthSessionSecure) ||
|
|
120
|
+
cookies.get(AUTH_COOKIE_NAMES.betterAuthSession);
|
|
132
121
|
if (!sessionToken) {
|
|
133
122
|
// No session cookie - auth may have failed silently
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
const authError = AUTH_ERRORS.NO_SESSION;
|
|
124
|
+
logAuthError(authError, {
|
|
125
|
+
ip: getClientIP(request),
|
|
126
|
+
});
|
|
127
|
+
redirect(302, `/auth/login?${buildErrorParams(authError)}`);
|
|
136
128
|
}
|
|
137
129
|
// Success! Redirect to the requested destination
|
|
138
130
|
console.log("[Auth Callback] Success, redirecting to:", returnTo);
|
package/dist/groveauth/client.js
CHANGED
|
@@ -190,9 +190,11 @@ export class GroveAuthClient {
|
|
|
190
190
|
code_verifier: codeVerifier,
|
|
191
191
|
}),
|
|
192
192
|
});
|
|
193
|
-
|
|
193
|
+
// Parse response body once (streams can only be consumed once)
|
|
194
|
+
const data = await response.json();
|
|
194
195
|
if (!response.ok) {
|
|
195
|
-
|
|
196
|
+
const error = data;
|
|
197
|
+
throw new GroveAuthError(error.error || "token_error", error.error_description || error.message || "Failed to exchange code", response.status);
|
|
196
198
|
}
|
|
197
199
|
return data;
|
|
198
200
|
}
|
|
@@ -219,9 +221,13 @@ export class GroveAuthClient {
|
|
|
219
221
|
client_secret: this.config.clientSecret,
|
|
220
222
|
}),
|
|
221
223
|
});
|
|
222
|
-
|
|
224
|
+
// Parse response body once (streams can only be consumed once)
|
|
225
|
+
const data = await response.json();
|
|
223
226
|
if (!response.ok) {
|
|
224
|
-
|
|
227
|
+
const error = data;
|
|
228
|
+
throw new GroveAuthError(error.error || "refresh_error", error.error_description ||
|
|
229
|
+
error.message ||
|
|
230
|
+
"Failed to refresh token", response.status);
|
|
225
231
|
}
|
|
226
232
|
return data;
|
|
227
233
|
}, { maxRetries: options.maxRetries ?? 3 });
|
|
@@ -255,8 +261,8 @@ export class GroveAuthClient {
|
|
|
255
261
|
}),
|
|
256
262
|
});
|
|
257
263
|
if (!response.ok) {
|
|
258
|
-
const
|
|
259
|
-
throw new GroveAuthError(
|
|
264
|
+
const error = (await response.json());
|
|
265
|
+
throw new GroveAuthError(error.error || "revoke_error", error.error_description || error.message || "Failed to revoke token", response.status);
|
|
260
266
|
}
|
|
261
267
|
}
|
|
262
268
|
// ===========================================================================
|
|
@@ -287,8 +293,8 @@ export class GroveAuthClient {
|
|
|
287
293
|
},
|
|
288
294
|
});
|
|
289
295
|
if (!response.ok) {
|
|
290
|
-
const
|
|
291
|
-
throw new GroveAuthError(
|
|
296
|
+
const error = (await response.json());
|
|
297
|
+
throw new GroveAuthError(error.error || "userinfo_error", error.error_description || error.message || "Failed to get user info", response.status);
|
|
292
298
|
}
|
|
293
299
|
return response.json();
|
|
294
300
|
}
|
|
@@ -305,8 +311,8 @@ export class GroveAuthClient {
|
|
|
305
311
|
},
|
|
306
312
|
});
|
|
307
313
|
if (!response.ok) {
|
|
308
|
-
const
|
|
309
|
-
throw new GroveAuthError(
|
|
314
|
+
const error = (await response.json());
|
|
315
|
+
throw new GroveAuthError(error.error || "subscription_error", error.message || "Failed to get subscription", response.status);
|
|
310
316
|
}
|
|
311
317
|
return response.json();
|
|
312
318
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heartwood Auth Error System
|
|
3
|
+
*
|
|
4
|
+
* Provides structured error codes for authentication failures.
|
|
5
|
+
* Error codes follow the format: HW-AUTH-XXX
|
|
6
|
+
*
|
|
7
|
+
* Categories:
|
|
8
|
+
* - user: User can fix this themselves (try again, use different method)
|
|
9
|
+
* - admin: User should contact admin/support (config issue, permissions)
|
|
10
|
+
* - bug: Internal error (should not happen, needs investigation)
|
|
11
|
+
*
|
|
12
|
+
* @see https://github.com/AutumnsGrove/GroveEngine/issues/668
|
|
13
|
+
*/
|
|
14
|
+
export type ErrorCategory = "user" | "admin" | "bug";
|
|
15
|
+
export interface AuthErrorDef {
|
|
16
|
+
/** Heartwood error code (e.g., HW-AUTH-001) */
|
|
17
|
+
code: string;
|
|
18
|
+
/** Error category for determining how to display */
|
|
19
|
+
category: ErrorCategory;
|
|
20
|
+
/** User-facing message (safe to display) */
|
|
21
|
+
userMessage: string;
|
|
22
|
+
/** Admin-facing message with more detail */
|
|
23
|
+
adminMessage: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* All Heartwood authentication error codes.
|
|
27
|
+
*
|
|
28
|
+
* Ranges:
|
|
29
|
+
* - 001-019: OAuth provider errors
|
|
30
|
+
* - 020-039: Session/token errors
|
|
31
|
+
* - 040-059: Client configuration errors
|
|
32
|
+
* - 060-079: Rate limiting and security
|
|
33
|
+
* - 080-099: Internal errors
|
|
34
|
+
*/
|
|
35
|
+
export declare const AUTH_ERRORS: {
|
|
36
|
+
readonly ACCESS_DENIED: {
|
|
37
|
+
readonly code: "HW-AUTH-001";
|
|
38
|
+
readonly category: "user";
|
|
39
|
+
readonly userMessage: "You cancelled the sign-in process. Try again when ready.";
|
|
40
|
+
readonly adminMessage: "User denied OAuth consent or cancelled the flow.";
|
|
41
|
+
};
|
|
42
|
+
readonly PROVIDER_ERROR: {
|
|
43
|
+
readonly code: "HW-AUTH-002";
|
|
44
|
+
readonly category: "bug";
|
|
45
|
+
readonly userMessage: "The sign-in provider encountered an error. Please try again.";
|
|
46
|
+
readonly adminMessage: "OAuth provider returned an error during authentication.";
|
|
47
|
+
};
|
|
48
|
+
readonly INVALID_SCOPE: {
|
|
49
|
+
readonly code: "HW-AUTH-003";
|
|
50
|
+
readonly category: "admin";
|
|
51
|
+
readonly userMessage: "Sign-in failed due to a configuration issue. Contact support.";
|
|
52
|
+
readonly adminMessage: "OAuth scope is invalid or not permitted for this client.";
|
|
53
|
+
};
|
|
54
|
+
readonly REDIRECT_URI_MISMATCH: {
|
|
55
|
+
readonly code: "HW-AUTH-004";
|
|
56
|
+
readonly category: "admin";
|
|
57
|
+
readonly userMessage: "Sign-in failed due to a configuration issue. Contact support.";
|
|
58
|
+
readonly adminMessage: "The redirect_uri does not match any registered URI for this client.";
|
|
59
|
+
};
|
|
60
|
+
readonly NO_SESSION: {
|
|
61
|
+
readonly code: "HW-AUTH-020";
|
|
62
|
+
readonly category: "user";
|
|
63
|
+
readonly userMessage: "Your session wasn't created. Please try signing in again.";
|
|
64
|
+
readonly adminMessage: "No session cookie was set after OAuth callback. May indicate cookie blocking.";
|
|
65
|
+
};
|
|
66
|
+
readonly SESSION_EXPIRED: {
|
|
67
|
+
readonly code: "HW-AUTH-021";
|
|
68
|
+
readonly category: "user";
|
|
69
|
+
readonly userMessage: "Your session has expired. Please sign in again.";
|
|
70
|
+
readonly adminMessage: "Session token has expired or been invalidated.";
|
|
71
|
+
};
|
|
72
|
+
readonly INVALID_TOKEN: {
|
|
73
|
+
readonly code: "HW-AUTH-022";
|
|
74
|
+
readonly category: "user";
|
|
75
|
+
readonly userMessage: "Your session is invalid. Please sign in again.";
|
|
76
|
+
readonly adminMessage: "Token validation failed - token is malformed or revoked.";
|
|
77
|
+
};
|
|
78
|
+
readonly TOKEN_EXCHANGE_FAILED: {
|
|
79
|
+
readonly code: "HW-AUTH-023";
|
|
80
|
+
readonly category: "bug";
|
|
81
|
+
readonly userMessage: "Sign-in failed. Please try again.";
|
|
82
|
+
readonly adminMessage: "Failed to exchange authorization code for tokens. Check client credentials.";
|
|
83
|
+
};
|
|
84
|
+
readonly LEGACY_SESSION_EXPIRED: {
|
|
85
|
+
readonly code: "HW-AUTH-024";
|
|
86
|
+
readonly category: "user";
|
|
87
|
+
readonly userMessage: "Your old session format has expired. Please sign in with a fresh login.";
|
|
88
|
+
readonly adminMessage: "Legacy session cookie found but migration deadline has passed.";
|
|
89
|
+
};
|
|
90
|
+
readonly UNREGISTERED_CLIENT: {
|
|
91
|
+
readonly code: "HW-AUTH-040";
|
|
92
|
+
readonly category: "admin";
|
|
93
|
+
readonly userMessage: "Sign-in failed due to a configuration issue. Contact support.";
|
|
94
|
+
readonly adminMessage: "Client ID is not registered with GroveAuth or has been revoked.";
|
|
95
|
+
};
|
|
96
|
+
readonly INVALID_CLIENT: {
|
|
97
|
+
readonly code: "HW-AUTH-041";
|
|
98
|
+
readonly category: "admin";
|
|
99
|
+
readonly userMessage: "Sign-in failed due to a configuration issue. Contact support.";
|
|
100
|
+
readonly adminMessage: "Client authentication failed (invalid client_id or secret).";
|
|
101
|
+
};
|
|
102
|
+
readonly ORIGIN_NOT_ALLOWED: {
|
|
103
|
+
readonly code: "HW-AUTH-042";
|
|
104
|
+
readonly category: "admin";
|
|
105
|
+
readonly userMessage: "Sign-in failed due to a configuration issue. Contact support.";
|
|
106
|
+
readonly adminMessage: "The request origin is not in the allowed origins list for this client.";
|
|
107
|
+
};
|
|
108
|
+
readonly RATE_LIMITED: {
|
|
109
|
+
readonly code: "HW-AUTH-060";
|
|
110
|
+
readonly category: "user";
|
|
111
|
+
readonly userMessage: "Too many sign-in attempts. Please wait a few minutes and try again.";
|
|
112
|
+
readonly adminMessage: "Rate limit exceeded for authentication endpoint.";
|
|
113
|
+
};
|
|
114
|
+
readonly CSRF_MISMATCH: {
|
|
115
|
+
readonly code: "HW-AUTH-061";
|
|
116
|
+
readonly category: "user";
|
|
117
|
+
readonly userMessage: "Sign-in failed for security reasons. Please try again.";
|
|
118
|
+
readonly adminMessage: "OAuth state parameter mismatch - possible CSRF attack.";
|
|
119
|
+
};
|
|
120
|
+
readonly INTERNAL_ERROR: {
|
|
121
|
+
readonly code: "HW-AUTH-080";
|
|
122
|
+
readonly category: "bug";
|
|
123
|
+
readonly userMessage: "An unexpected error occurred. Please try again later.";
|
|
124
|
+
readonly adminMessage: "Internal server error during authentication.";
|
|
125
|
+
};
|
|
126
|
+
readonly UNKNOWN_ERROR: {
|
|
127
|
+
readonly code: "HW-AUTH-099";
|
|
128
|
+
readonly category: "bug";
|
|
129
|
+
readonly userMessage: "An unexpected error occurred. Please try again.";
|
|
130
|
+
readonly adminMessage: "Unknown error code received from OAuth flow.";
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
export type AuthErrorKey = keyof typeof AUTH_ERRORS;
|
|
134
|
+
/**
|
|
135
|
+
* Get the Heartwood error definition for an OAuth error code.
|
|
136
|
+
*
|
|
137
|
+
* @param oauthError - The error code from OAuth (e.g., "access_denied")
|
|
138
|
+
* @returns The corresponding AuthErrorDef
|
|
139
|
+
*/
|
|
140
|
+
export declare function getAuthError(oauthError: string): AuthErrorDef;
|
|
141
|
+
/**
|
|
142
|
+
* Get error by Heartwood code (e.g., "HW-AUTH-001").
|
|
143
|
+
*/
|
|
144
|
+
export declare function getAuthErrorByCode(hwCode: string): AuthErrorDef | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* Log an auth error with structured context.
|
|
147
|
+
* Sensitive data (tokens, secrets) is NEVER logged.
|
|
148
|
+
*
|
|
149
|
+
* @param error - The error definition
|
|
150
|
+
* @param context - Additional context (sanitized)
|
|
151
|
+
*/
|
|
152
|
+
export declare function logAuthError(error: AuthErrorDef, context?: {
|
|
153
|
+
oauthError?: string;
|
|
154
|
+
ip?: string;
|
|
155
|
+
path?: string;
|
|
156
|
+
userAgent?: string;
|
|
157
|
+
}): void;
|
|
158
|
+
/**
|
|
159
|
+
* Build error URL parameters for redirecting to login page.
|
|
160
|
+
* Includes the error code so the login page can display appropriate messaging.
|
|
161
|
+
*
|
|
162
|
+
* @param error - The error definition
|
|
163
|
+
* @param isAdminFlow - Whether this is an admin-facing flow (shows more detail)
|
|
164
|
+
*/
|
|
165
|
+
export declare function buildErrorParams(error: AuthErrorDef, isAdminFlow?: boolean): string;
|